build / build (push) Successful in 1m58s
- 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)
144 lines
6.6 KiB
Vue
144 lines
6.6 KiB
Vue
<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>
|