refactor(admin): 六页对齐 bsch/ysch 应用页规范(09-18 升级补入库,与 flow-center 同批次)
build / build (push) Successful in 1m58s
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)
This commit is contained in:
@@ -1,92 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
/** 办事事项列表(modId 23):筛选/发布/下架/删除/同步部门名。 */
|
||||
import { h, onMounted, ref } from "vue";
|
||||
/**
|
||||
* 办事事项列表(modId 23):筛选/发布/下架/删除/同步部门名。
|
||||
* 规范骨架(bsch应用页规范 §1):操作按钮区 → 搜索区 → 表格 + 分页,分页走 TablePageAlovaNew。
|
||||
*/
|
||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { Message, Modal } from "view-ui-plus";
|
||||
import { svcItemApi } from "@/api/module/svc";
|
||||
import type { SvcItemPageItem } from "@/types/svc";
|
||||
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 loading = ref(false);
|
||||
const list = ref<SvcItemPageItem[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const status = ref<number | undefined>(undefined);
|
||||
const keyword = ref("");
|
||||
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 statusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "已下架" : "草稿");
|
||||
const statusColor = (s: number) => (s === 1 ? "success" : s === 2 ? "warning" : "default");
|
||||
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,
|
||||
render: (h: any, p: any) => h("span", handleModeName(p.row.handleMode)),
|
||||
},
|
||||
{ title: "在线办理", slot: "online", width: 110 },
|
||||
{ title: "状态", slot: "status", width: 90 },
|
||||
{ title: "浏览量", key: "viewCount", width: 80 },
|
||||
{ title: "操作", slot: "action", width: 200 },
|
||||
{ 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;
|
||||
} },
|
||||
];
|
||||
|
||||
async function load() {
|
||||
if (!currentSiteCode.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await svcItemApi
|
||||
.page({
|
||||
siteCode: currentSiteCode.value,
|
||||
status: status.value,
|
||||
keyword: keyword.value,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
.send();
|
||||
total.value = res.total || 0;
|
||||
list.value = res.list || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
// 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 onSiteChange(code: string) {
|
||||
setSite(code);
|
||||
page.value = 1;
|
||||
load();
|
||||
}
|
||||
|
||||
function edit(row?: SvcItemPageItem) {
|
||||
function edit(row?: any) {
|
||||
router.push({ path: "/svc/item/edit", query: { id: row?.id ? String(row.id) : undefined } });
|
||||
}
|
||||
|
||||
async function publish(row: SvcItemPageItem) {
|
||||
async function publish(row: any) {
|
||||
await svcItemApi.publish(currentSiteCode.value, row.id).send();
|
||||
Message.success("已发布");
|
||||
load();
|
||||
tableRef.value?.reloadData(OperatorEnum.edit);
|
||||
}
|
||||
|
||||
async function offline(row: SvcItemPageItem) {
|
||||
async function offline(row: any) {
|
||||
await svcItemApi.offline(currentSiteCode.value, row.id).send();
|
||||
Message.success("已下架");
|
||||
load();
|
||||
tableRef.value?.reloadData(OperatorEnum.edit);
|
||||
}
|
||||
|
||||
function remove(row: SvcItemPageItem) {
|
||||
Modal.confirm({
|
||||
title: "删除确认",
|
||||
content: `确定删除事项「${row.title}」吗?删除后不可恢复。`,
|
||||
onOk: async () => {
|
||||
function remove(row: any) {
|
||||
MyConfirm({
|
||||
title: "删除",
|
||||
msg: `确定删除事项「${row.title}」吗?`,
|
||||
info: "删除后不可恢复",
|
||||
async onOk() {
|
||||
await svcItemApi.delete(currentSiteCode.value, row.id).send();
|
||||
Message.success("已删除");
|
||||
load();
|
||||
tableRef.value?.reloadData(OperatorEnum.del);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -94,75 +103,41 @@ function remove(row: SvcItemPageItem) {
|
||||
async function syncDept() {
|
||||
const res: any = await svcItemApi.syncDeptName(currentSiteCode.value).send();
|
||||
Message.success(`已同步 ${res.updated} 条部门名快照`);
|
||||
load();
|
||||
tableRef.value?.refreshData();
|
||||
}
|
||||
|
||||
// 站点列表就绪后再拉首屏(siteCode 为定位键,避免空站点空跑一页)
|
||||
onMounted(async () => {
|
||||
await loadSites();
|
||||
load();
|
||||
tableRef.value?.reloadData(OperatorEnum.sel);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<Card :bordered="false" dis-hover>
|
||||
<div class="bar">
|
||||
<Select :model-value="currentSiteCode" style="width: 240px" @on-change="onSiteChange">
|
||||
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">
|
||||
{{ s.name }}({{ s.siteCode }})
|
||||
</Option>
|
||||
</Select>
|
||||
<Select v-model="status" clearable placeholder="状态" style="width: 120px" @on-change="load">
|
||||
<Option :value="0">草稿</Option>
|
||||
<Option :value="1">已发布</Option>
|
||||
<Option :value="2">已下架</Option>
|
||||
</Select>
|
||||
<Input
|
||||
v-model="keyword"
|
||||
search
|
||||
enter-button
|
||||
placeholder="名称/编码"
|
||||
style="width: 240px"
|
||||
@on-search="load"
|
||||
/>
|
||||
<Button type="primary" @click="edit()">新建事项</Button>
|
||||
<Button @click="syncDept">同步部门名</Button>
|
||||
<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>
|
||||
<Table :loading="loading" :columns="columns" :data="list">
|
||||
<template #status="{ row }">
|
||||
<Tag :color="statusColor(row.status)">{{ statusName(row.status) }}</Tag>
|
||||
</template>
|
||||
<template #online="{ row }">
|
||||
<Tag v-if="row.onlineFlag === 1" color="green">可在线办理</Tag>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Button size="small" type="text" @click="edit(row)">编辑</Button>
|
||||
<Button v-if="row.status !== 1" size="small" type="text" @click="publish(row)">发布</Button>
|
||||
<Button v-if="row.status === 1" size="small" type="text" @click="offline(row)">下架</Button>
|
||||
<Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button>
|
||||
</template>
|
||||
</Table>
|
||||
<Page
|
||||
class="pager"
|
||||
:total="total"
|
||||
:current="page"
|
||||
:page-size="pageSize"
|
||||
show-total
|
||||
@on-change="(p: number) => { page = p; load(); }"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<!-- 搜索区:下拉靠左,关键字即输即搜不设查询按钮 -->
|
||||
<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>
|
||||
|
||||
<style scoped>
|
||||
.bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.pager {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user