Files
WonderQ-Project/WonderQ-Admin-UI-Vue/src/pages/ConciergePage.vue
T
duanshuwen cb1c695453 fix(admin-ui): 修复线索状态更新后详情弹窗未关闭,优化管家顾问页面文案与格式
- 为LeadsPage的submitStatus函数的finally块添加关闭详情弹窗的逻辑,确保更新状态后自动关闭弹窗
- 修正ConciergePage页面的介绍文案,将原表述"停用后不会进入 Public API"调整为"停用后不会进入小程序显示"
- 格式化两个页面的Vue模板代码,拆分长行并统一缩进,提升代码可读性
2026-08-27 10:06:52 +08:00

151 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
createConciergeAdvisor,
deleteConciergeAdvisor,
getConcierge,
reorderConciergeAdvisors,
updateConciergeAdvisor,
} from "@/api/client";
import ConciergeAdvisorEditor from "@/components/concierge/ConciergeAdvisorEditor.vue";
import { resolveAdminMediaUrl } from "@/lib/media-url";
import { useAuthStore } from "@/stores/auth";
import type { ConciergeAdvisor, ConciergeAdvisorCreate } from "@/types";
const auth = useAuthStore();
const advisors = ref<ConciergeAdvisor[]>([]);
const loading = ref(true);
const error = ref("");
const editorOpen = ref(false);
const editing = ref<ConciergeAdvisor | undefined>();
const savingKey = ref("");
const canWrite = computed(() => auth.hasPermission("admin:concierge:read"));
async function load() {
loading.value = true;
error.value = "";
try {
advisors.value = await getConcierge();
} catch (loadError) {
error.value = loadError instanceof Error ? loadError.message : "管家顾问加载失败";
} finally {
loading.value = false;
}
}
function openCreate() {
if (!canWrite.value) return;
editing.value = undefined;
editorOpen.value = true;
}
function openEdit(advisor: ConciergeAdvisor) {
if (!canWrite.value) return;
editing.value = advisor;
editorOpen.value = true;
}
async function save(input: ConciergeAdvisorCreate) {
if (savingKey.value) return;
const isEditing = Boolean(editing.value);
savingKey.value = "advisor";
try {
const saved = isEditing
? await updateConciergeAdvisor(editing.value!.id, input)
: await createConciergeAdvisor(input);
advisors.value = isEditing
? advisors.value.map((item) => item.id === saved.id ? saved : item)
: [...advisors.value, saved];
editorOpen.value = false;
ElMessage.success(isEditing ? "管家顾问已保存" : "管家顾问已创建");
} catch (saveError) {
ElMessage.error(saveError instanceof Error ? saveError.message : "管家顾问保存失败");
} finally {
savingKey.value = "";
}
}
async function move(advisor: ConciergeAdvisor, direction: -1 | 1) {
const index = advisors.value.findIndex((item) => item.id === advisor.id);
const nextIndex = index + direction;
if (index < 0 || nextIndex < 0 || nextIndex >= advisors.value.length || savingKey.value) return;
const next = [...advisors.value];
[next[index], next[nextIndex]] = [next[nextIndex], next[index]];
savingKey.value = "reorder";
try {
const result = await reorderConciergeAdvisors(next.map((item) => item.id));
advisors.value = result.items;
ElMessage.success("顾问展示顺序已更新");
} catch (moveError) {
ElMessage.error(moveError instanceof Error ? moveError.message : "顾问排序失败");
} finally {
savingKey.value = "";
}
}
async function remove(advisor: ConciergeAdvisor) {
try {
await ElMessageBox.confirm(`确认删除「${advisor.name}」吗?详情记录中的管家关联会自动失效。`, "删除管家顾问", { type: "warning" });
} catch {
return;
}
savingKey.value = "delete";
try {
await deleteConciergeAdvisor(advisor.id);
advisors.value = advisors.value.filter((item) => item.id !== advisor.id);
ElMessage.success("管家顾问已删除");
} catch (deleteError) {
ElMessage.error(deleteError instanceof Error ? deleteError.message : "管家顾问删除失败");
} finally {
savingKey.value = "";
}
}
function closeEditor() {
if (!savingKey.value) editorOpen.value = false;
}
onMounted(load);
</script>
<template>
<div class="page-stack">
<div class="page-intro site-config-intro">
<div>
<p class="eyebrow">SERVICE / CONCIERGE</p>
<h2>管家顾问</h2>
<p>维护前台顾问卡片、服务详情和二维码;停用后不会进入小程序显示。</p>
</div><el-button v-permission="'admin:concierge:read'" type="primary" @click="openCreate">新增管家</el-button>
</div>
<el-alert v-if="error" :title="error" type="error" show-icon :closable="false"><template #default><el-button link
type="primary" @click="load">重试</el-button></template></el-alert>
<el-skeleton v-if="loading" :rows="7" animated />
<el-empty v-else-if="!advisors.length" description="暂无管家顾问"><el-button v-permission="'admin:concierge:read'"
type="primary" @click="openCreate">创建第一位顾问</el-button></el-empty>
<el-card v-else shadow="never" class="panel-card">
<div class="concierge-list">
<div v-for="(advisor, index) in advisors" :key="advisor.id" class="concierge-row">
<div class="concierge-summary"><el-avatar :size="48" :src="resolveAdminMediaUrl(advisor.avatar)">{{
advisor.name.slice(0, 1) }}</el-avatar>
<div><strong>{{ advisor.name }}</strong><span>{{ advisor.role }} · {{ advisor.details?.length ?? 0 }}
条服务详情</span></div>
</div>
<div class="concierge-actions"><el-tag :type="advisor.isActive ? 'success' : 'info'" effect="plain">{{
advisor.isActive ? "启用" : "停用" }}</el-tag><el-button v-permission="'admin:concierge:read'" link
:disabled="index === 0 || Boolean(savingKey)" @click="move(advisor, -1)">上移</el-button><el-button
v-permission="'admin:concierge:read'" link :disabled="index === advisors.length - 1 || Boolean(savingKey)"
@click="move(advisor, 1)">下移</el-button><el-button v-permission="'admin:concierge:read'" link
type="primary" @click="openEdit(advisor)">编辑</el-button><el-button v-permission="'admin:concierge:read'"
link type="danger" :disabled="Boolean(savingKey)" @click="remove(advisor)">删除</el-button></div>
</div>
</div>
</el-card>
<el-drawer v-model="editorOpen" class="editor-drawer" size="min(720px, 100%)" :with-header="false"
:show-close="false" :close-on-press-escape="!savingKey" @close="closeEditor">
<ConciergeAdvisorEditor v-if="editorOpen" :advisor="editing" :editing="Boolean(editing)"
:saving="savingKey === 'advisor'" @save="save" @cancel="closeEditor" />
</el-drawer>
</div>
</template>