- 为LeadsPage的submitStatus函数的finally块添加关闭详情弹窗的逻辑,确保更新状态后自动关闭弹窗 - 修正ConciergePage页面的介绍文案,将原表述"停用后不会进入 Public API"调整为"停用后不会进入小程序显示" - 格式化两个页面的Vue模板代码,拆分长行并统一缩进,提升代码可读性
151 lines
6.1 KiB
Vue
151 lines
6.1 KiB
Vue
<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>
|