Files
svc-hall-front/src/views/svc/ServiceItemList.vue
T
chenxing 18ddb17374
build / build (push) Successful in 1m58s
refactor(admin): 六页对齐 bsch/ysch 应用页规范(09-18 升级补入库,与 flow-center 同批次)
- types/index.ts 补 OperatorEnum(此前 import 悬空,接入 TablePage* 即构建失败)
- TablePageAlovaNew 对齐 flow-center 版:页长记忆 localStorage.customPageCount
  + 重载清空勾选 + watch(tableHeight) 跟随页面侧 ResizeObserver 重算
- ServiceItemList/ApplyManage:三段式骨架 + search-wrap(下拉+关键字 200px
  即输即搜)+ TablePageAlovaNew + Utils.operatingHandle 图标操作列(编辑/发布
  icon-fasong/下架 icon-stop/删除/详情 icon-browse)+ MyConfirm + 增删后智能翻页
- ServiceDictManage:tabs-wrapper + card Tabs 双容器 v-show + 定高 + 弹窗校验
  统一文案「请填写红色星号内容!」
- ServiceFormManage:无分页定高 + 前端过滤 + 按状态条件图标
- ServiceItemEdit:page-wrapper 根容器 + 必填红星
- 状态 Tag 色常量迁 api 层(APPLY_STATUS_COLOR/ITEM_STATUS_COLOR)
- dataApi 返回 alova Method 不调 .send();分页 formatResponse 适配 {total,list}
- vite build 通过(27s)
2026-09-20 02:50:36 +08:00

144 lines
6.6 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">
/**
* 办事事项列表(modId 23):筛选/发布/下架/删除/同步部门名。
* 规范骨架(bsch应用页规范 §1):操作按钮区 → 搜索区 → 表格 + 分页,分页走 TablePageAlovaNew。
*/
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import { useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm";
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
import { OperatorEnum } from "@/types";
import { svcItemApi, ITEM_STATUS, ITEM_STATUS_COLOR } from "@/api/module/svc";
import { useSite } from "@/utils/site";
import Utils from "@/utils";
const router = useRouter();
const { currentSiteCode, sites, loadSites, setSite } = useSite();
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true, isPage: true }));
// 首挂载时视口/样式可能尚未定型(body 高偏小甚至 0,且其变化不触发 window resize):
// ResizeObserver 观察 body,任何高度来源(样式注入/视口调整)变化即重算,卸载时断开
let bodyObserver: ResizeObserver | null = null;
const recalcTableHeight = () => {
tableHeight.value = Utils.useTableHeight({ isSearch: true, isOperat: true, isPage: true });
};
onMounted(() => {
recalcTableHeight();
bodyObserver = new ResizeObserver(recalcTableHeight);
bodyObserver.observe(document.body);
});
onUnmounted(() => bodyObserver?.disconnect());
const dataParams = ref<{ status?: number; keyword: string }>({ status: undefined, keyword: "" });
const handleModeName = (m: number) => (m === 1 ? "线上" : m === 2 ? "线下" : "线上线下");
const columns = [
{ title: "序号", type: "index", width: 70, align: "center" },
{ title: "编码", key: "itemCode", width: 140 },
{ title: "名称", key: "title", minWidth: 200 },
{ title: "类别", key: "categoryName", width: 110 },
{ title: "部门", key: "deptName", width: 130 },
{ title: "办理方式", width: 90, align: "center", render: (h: any, p: any) => h("span", handleModeName(p.row.handleMode)) },
{ title: "在线办理", width: 110, align: "center", render: (h: any, p: any) =>
p.row.onlineFlag === 1
? h("Tag", { color: "green" }, () => "可在线办理")
: h("span", "-") },
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) =>
h("Tag", { color: ITEM_STATUS_COLOR[p.row.status] || "default" }, () => ITEM_STATUS[p.row.status] || p.row.status) },
{ title: "浏览量", key: "viewCount", width: 80, align: "center" },
{ title: "操作", width: 180, align: "center", fixed: "right", render: (h: any, p: any) => {
const btns = [Utils.operatingHandle(h, "编辑", "icon-edit", () => edit(p.row))];
btns.push(p.row.status === 1
? Utils.operatingHandle(h, "下架", "icon-stop", () => offline(p.row))
: Utils.operatingHandle(h, "发布", "icon-fasong", () => publish(p.row)));
btns.push(Utils.operatingHandle(h, "删除", "icon-ashbin", () => remove(p.row)));
return btns;
} },
];
// dataApi 返回 Method(alova usePagination 负责发送与解包),不能调 .send()
const tableApi = (p: { pageIndex: number; pageSize: number }) => {
const params: any = { siteCode: currentSiteCode.value, page: p.pageIndex, pageSize: p.pageSize };
if (dataParams.value.status !== undefined) params.status = dataParams.value.status;
if (dataParams.value.keyword) params.keyword = dataParams.value.keyword.trim();
return svcItemApi.page(params);
};
// 站点/筛选变化回到第 1 页
function onSearch() {
tableRef.value?.refreshData();
}
function edit(row?: any) {
router.push({ path: "/svc/item/edit", query: { id: row?.id ? String(row.id) : undefined } });
}
async function publish(row: any) {
await svcItemApi.publish(currentSiteCode.value, row.id).send();
Message.success("已发布");
tableRef.value?.reloadData(OperatorEnum.edit);
}
async function offline(row: any) {
await svcItemApi.offline(currentSiteCode.value, row.id).send();
Message.success("已下架");
tableRef.value?.reloadData(OperatorEnum.edit);
}
function remove(row: any) {
MyConfirm({
title: "删除",
msg: `确定删除事项「${row.title}」吗?`,
info: "删除后不可恢复",
async onOk() {
await svcItemApi.delete(currentSiteCode.value, row.id).send();
Message.success("已删除");
tableRef.value?.reloadData(OperatorEnum.del);
},
});
}
async function syncDept() {
const res: any = await svcItemApi.syncDeptName(currentSiteCode.value).send();
Message.success(`已同步 ${res.updated} 条部门名快照`);
tableRef.value?.refreshData();
}
// 站点列表就绪后再拉首屏(siteCode 为定位键,避免空站点空跑一页)
onMounted(async () => {
await loadSites();
tableRef.value?.reloadData(OperatorEnum.sel);
});
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<!-- 操作按钮区:主操作靠左、其余靠右(bsch应用页规范 §1) -->
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="edit()">新建事项</Button>
<Button class="fr" size="large" @click="syncDept">同步部门名</Button>
</div>
<!-- 搜索区:下拉靠左,关键字即输即搜不设查询按钮 -->
<div class="search-wrap fix">
<Select :model-value="currentSiteCode" class="search-wrap_sel" placeholder="站点"
@on-change="(c: string) => { setSite(c); onSearch(); }">
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
</Select>
<Select v-model="dataParams.status" class="search-wrap_sel ml-12" clearable placeholder="状态" @on-change="onSearch">
<Option v-for="(name, k) in ITEM_STATUS" :key="k" :value="Number(k)" :label="name" />
</Select>
<Input class="fr" v-model="dataParams.keyword" placeholder="名称/编码" search suffix="ios-search"
style="width: 200px" @on-search="onSearch" @on-clear="onSearch" />
</div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight"
:autoLoad="false"
:formatResponse="(res: any) => ({ total: res.total, list: res.list })" />
</div>
</div>
</template>