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:
@@ -29,6 +29,26 @@ const APPLY = "/api/apply";
|
|||||||
|
|
||||||
const FILE = "/api/file";
|
const FILE = "/api/file";
|
||||||
|
|
||||||
|
/** 办件流程状态(文案与 Tag 色和用户面 hall 同口径) */
|
||||||
|
export const APPLY_STATUS: Record<number, string> = {
|
||||||
|
0: "待提交",
|
||||||
|
10: "审批中",
|
||||||
|
20: "已完成",
|
||||||
|
30: "已拒绝",
|
||||||
|
40: "已撤回",
|
||||||
|
};
|
||||||
|
export const APPLY_STATUS_COLOR: Record<number, string> = {
|
||||||
|
0: "default",
|
||||||
|
10: "primary",
|
||||||
|
20: "success",
|
||||||
|
30: "error",
|
||||||
|
40: "warning",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 事项发布状态(0 草稿 / 1 已发布 / 2 已下架) */
|
||||||
|
export const ITEM_STATUS: Record<number, string> = { 0: "草稿", 1: "已发布", 2: "已下架" };
|
||||||
|
export const ITEM_STATUS_COLOR: Record<number, string> = { 0: "default", 1: "success", 2: "warning" };
|
||||||
|
|
||||||
/** GET 请求 */
|
/** GET 请求 */
|
||||||
function get<T = any>(url: string, params?: Record<string, any>): Method<T> {
|
function get<T = any>(url: string, params?: Record<string, any>): Method<T> {
|
||||||
return new Method<T>("GET", alovaInstance, url, { params });
|
return new Method<T>("GET", alovaInstance, url, { params });
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useSlots } from 'vue'
|
import { computed, ref, useSlots, watch } from 'vue'
|
||||||
import Utils from '@/utils'
|
import Utils from '@/utils'
|
||||||
import { OperatorEnum } from '@/types'
|
import { OperatorEnum } from '@/types'
|
||||||
|
|
||||||
@@ -45,6 +45,10 @@ const emit = defineEmits<{
|
|||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
const tableH = ref(props.tableHeight ?? Utils.useTableHeight())
|
const tableH = ref(props.tableHeight ?? Utils.useTableHeight())
|
||||||
|
// 页面侧挂载后可能重算高度(嵌入面板初始视口先小后大),跟随 props 更新
|
||||||
|
watch(() => props.tableHeight, v => {
|
||||||
|
if (v) tableH.value = v
|
||||||
|
})
|
||||||
const tableSlotNames = computed(() => Object.keys(slots).filter(name => !['default', 'loading'].includes(name)))
|
const tableSlotNames = computed(() => Object.keys(slots).filter(name => !['default', 'loading'].includes(name)))
|
||||||
|
|
||||||
const selectList = ref<number[]>([])
|
const selectList = ref<number[]>([])
|
||||||
@@ -68,18 +72,30 @@ const {
|
|||||||
total: res => props.formatResponse(res).total,
|
total: res => props.formatResponse(res).total,
|
||||||
data: res => props.formatResponse(res).list,
|
data: res => props.formatResponse(res).list,
|
||||||
initialPage: 1,
|
initialPage: 1,
|
||||||
initialPageSize: 10,
|
// 页长记忆(bsch 应用页规范 §3:customPageCount localStorage)
|
||||||
|
initialPageSize: parseInt(localStorage.getItem('customPageCount') || '10') || 10,
|
||||||
immediate: props.autoLoad,
|
immediate: props.autoLoad,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 数据重载后清空勾选,避免批量操作用到旧选中行
|
||||||
|
watch(resp, () => {
|
||||||
|
if (selectList.value.length) {
|
||||||
|
selectList.value = []
|
||||||
|
emit('selection-change', selectList.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const list = computed(() => {
|
const list = computed(() => {
|
||||||
if (!resp.value) return []
|
if (!resp.value) return []
|
||||||
return props.dataFilter ? props.dataFilter(resp.value) : resp.value
|
return props.dataFilter ? props.dataFilter(resp.value) : resp.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPageChange = (p: number) => (page.value = p)
|
const onPageChange = (p: number) => (page.value = p)
|
||||||
const onSizeChange = (s: number) => (pageSize.value = s)
|
const onSizeChange = (s: number) => {
|
||||||
|
pageSize.value = s
|
||||||
|
localStorage.setItem('customPageCount', String(s))
|
||||||
|
}
|
||||||
|
|
||||||
function refreshData() {
|
function refreshData() {
|
||||||
page.value = 1
|
page.value = 1
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/** 列表增删改查后的智能翻页语义(TablePageAlovaNew.reloadData 参数,与 flow-center/UserBase 同源) */
|
||||||
|
export enum OperatorEnum {
|
||||||
|
add,
|
||||||
|
del,
|
||||||
|
edit,
|
||||||
|
sel,
|
||||||
|
}
|
||||||
@@ -6,21 +6,34 @@
|
|||||||
* svc-hall-public(BP-R4-3),管理端不该跨应用跳转;管理员查办件要的是"快照 + 填报内容 +
|
* svc-hall-public(BP-R4-3),管理端不该跨应用跳转;管理员查办件要的是"快照 + 填报内容 +
|
||||||
* 审批轨迹 + 流程图",一个只读抽屉就够,也不必给管理员看"撤回/重提"这类提交人动作。
|
* 审批轨迹 + 流程图",一个只读抽屉就够,也不必给管理员看"撤回/重提"这类提交人动作。
|
||||||
* 数据面复用用户面同口径接口(/api/apply/{id} 与 /{id}/progress,管理员在守卫白名单内)。
|
* 数据面复用用户面同口径接口(/api/apply/{id} 与 /{id}/progress,管理员在守卫白名单内)。
|
||||||
|
* 规范骨架(bsch应用页规范 §1):搜索区 → 表格 + 分页(纯查询页,无操作按钮区)。
|
||||||
*/
|
*/
|
||||||
import { onMounted, ref } from "vue";
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
import { applyApi } from "@/api/module/svc";
|
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
|
||||||
|
import { OperatorEnum } from "@/types";
|
||||||
|
import { applyApi, APPLY_STATUS, APPLY_STATUS_COLOR } from "@/api/module/svc";
|
||||||
import type { ApplyVo, FlowProgressVo } from "@/types/svc";
|
import type { ApplyVo, FlowProgressVo } from "@/types/svc";
|
||||||
import { useSite } from "@/utils/site";
|
import { useSite } from "@/utils/site";
|
||||||
import { buildFormDataGroups, FormDataGroup } from "@/utils/formDataGroups";
|
import { buildFormDataGroups, FormDataGroup } from "@/utils/formDataGroups";
|
||||||
|
import Utils from "@/utils";
|
||||||
|
|
||||||
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
||||||
const loading = ref(false);
|
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
const list = ref<ApplyVo[]>([]);
|
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isPage: true }));
|
||||||
const total = ref(0);
|
// 首挂载时视口/样式可能尚未定型(body 高偏小甚至 0,且其变化不触发 window resize):
|
||||||
const page = ref(1);
|
// ResizeObserver 观察 body,任何高度来源(样式注入/视口调整)变化即重算,卸载时断开
|
||||||
const flowStatus = ref<number | undefined>(undefined);
|
let bodyObserver: ResizeObserver | null = null;
|
||||||
const keyword = ref("");
|
const recalcTableHeight = () => {
|
||||||
|
tableHeight.value = Utils.useTableHeight({ isSearch: true, isPage: true });
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
recalcTableHeight();
|
||||||
|
bodyObserver = new ResizeObserver(recalcTableHeight);
|
||||||
|
bodyObserver.observe(document.body);
|
||||||
|
});
|
||||||
|
onUnmounted(() => bodyObserver?.disconnect());
|
||||||
|
const dataParams = ref<{ flowStatus?: number; keyword: string }>({ flowStatus: undefined, keyword: "" });
|
||||||
|
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const detailLoading = ref(false);
|
const detailLoading = ref(false);
|
||||||
@@ -29,14 +42,6 @@ const progress = ref<FlowProgressVo | null>(null);
|
|||||||
const diagramVisible = ref(false);
|
const diagramVisible = ref(false);
|
||||||
const diagramUrl = ref("");
|
const diagramUrl = ref("");
|
||||||
|
|
||||||
const STATUS: Record<number, { text: string; color: string }> = {
|
|
||||||
0: { text: "待提交", color: "default" },
|
|
||||||
10: { text: "审批中", color: "primary" },
|
|
||||||
20: { text: "已完成", color: "success" },
|
|
||||||
30: { text: "已拒绝", color: "error" },
|
|
||||||
40: { text: "已撤回", color: "warning" },
|
|
||||||
};
|
|
||||||
|
|
||||||
const SNAPSHOT_LABEL: Record<string, string> = {
|
const SNAPSHOT_LABEL: Record<string, string> = {
|
||||||
itemTitle: "事项名称",
|
itemTitle: "事项名称",
|
||||||
summary: "事项说明",
|
summary: "事项说明",
|
||||||
@@ -49,33 +54,28 @@ const SNAPSHOT_LABEL: Record<string, string> = {
|
|||||||
const contentGroups = ref<FormDataGroup[]>([]);
|
const contentGroups = ref<FormDataGroup[]>([]);
|
||||||
|
|
||||||
const cols = [
|
const cols = [
|
||||||
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
{ title: "办件号", key: "id", width: 90 },
|
{ title: "办件号", key: "id", width: 90 },
|
||||||
{ title: "事项", key: "itemTitle", minWidth: 180 },
|
{ title: "事项", key: "itemTitle", minWidth: 180 },
|
||||||
{ title: "编码", key: "itemCode", width: 130 },
|
{ title: "编码", key: "itemCode", width: 130 },
|
||||||
{ title: "提交人", key: "submitterName", width: 100 },
|
{ title: "提交人", key: "submitterName", width: 100 },
|
||||||
{ title: "提交时间", key: "submitTime", width: 160 },
|
{ title: "提交时间", key: "submitTime", width: 160 },
|
||||||
{ title: "状态", slot: "status", width: 100 },
|
{ title: "状态", width: 100, align: "center", render: (h: any, p: any) =>
|
||||||
{ title: "操作", slot: "action", width: 100 },
|
h("Tag", { color: APPLY_STATUS_COLOR[p.row.flowStatus] || "default" }, () => APPLY_STATUS[p.row.flowStatus] || p.row.flowStatus) },
|
||||||
|
{ title: "操作", width: 100, align: "center", fixed: "right", render: (h: any, p: any) =>
|
||||||
|
Utils.operatingHandle(h, "详情", "icon-browse", () => openDetail(p.row)) },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function load() {
|
// dataApi 返回 Method(alova usePagination 负责发送与解包),不能调 .send()
|
||||||
if (!currentSiteCode.value) return;
|
const tableApi = (p: { pageIndex: number; pageSize: number }) => {
|
||||||
loading.value = true;
|
const params: any = { siteCode: currentSiteCode.value, page: p.pageIndex, pageSize: p.pageSize };
|
||||||
try {
|
if (dataParams.value.flowStatus !== undefined) params.flowStatus = dataParams.value.flowStatus;
|
||||||
const res: any = await applyApi
|
if (dataParams.value.keyword) params.keyword = dataParams.value.keyword.trim();
|
||||||
.adminPage({
|
return applyApi.adminPage(params);
|
||||||
siteCode: currentSiteCode.value,
|
};
|
||||||
flowStatus: flowStatus.value,
|
|
||||||
keyword: keyword.value,
|
function onSearch() {
|
||||||
page: page.value,
|
tableRef.value?.refreshData();
|
||||||
pageSize: 10,
|
|
||||||
})
|
|
||||||
.send();
|
|
||||||
total.value = res.total || 0;
|
|
||||||
list.value = res.list || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(json?: string): Record<string, any> {
|
function parse(json?: string): Record<string, any> {
|
||||||
@@ -126,38 +126,33 @@ function openDiagram() {
|
|||||||
diagramVisible.value = true;
|
diagramVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 站点列表就绪后再拉首屏(siteCode 为定位键,避免空站点空跑一页)
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSites();
|
await loadSites();
|
||||||
load();
|
tableRef.value?.reloadData(OperatorEnum.sel);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page-wrapper">
|
||||||
<Card :bordered="false" dis-hover>
|
<div class="main-container">
|
||||||
<div class="bar">
|
<!-- 搜索区:站点/状态下拉靠左,关键字即输即搜不设查询按钮 -->
|
||||||
<Select :model-value="currentSiteCode" style="width: 240px" @on-change="(c: string) => { setSite(c); page = 1; load(); }">
|
<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>
|
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Select v-model="flowStatus" clearable placeholder="状态" style="width: 140px" @on-change="() => { page = 1; load(); }">
|
<Select v-model="dataParams.flowStatus" class="search-wrap_sel ml-12" clearable placeholder="状态" @on-change="onSearch">
|
||||||
<Option v-for="(v, k) in STATUS" :key="k" :value="Number(k)">{{ v.text }}</Option>
|
<Option v-for="(v, k) in APPLY_STATUS" :key="k" :value="Number(k)" :label="v" />
|
||||||
</Select>
|
</Select>
|
||||||
<Input v-model="keyword" search enter-button placeholder="事项/提交人" style="width: 220px" @on-search="load" />
|
<Input class="fr" v-model="dataParams.keyword" placeholder="事项/提交人" search suffix="ios-search"
|
||||||
<span class="sh-muted" style="color: #999; font-size: 13px">用户面办理入口在独立前端 /svc-hall/public/</span>
|
style="width: 200px" @on-search="onSearch" @on-clear="onSearch" />
|
||||||
</div>
|
</div>
|
||||||
<Table :loading="loading" :columns="cols" :data="list">
|
|
||||||
<template #status="{ row }">
|
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="cols" :tableHeight="tableHeight"
|
||||||
<Tag :color="STATUS[row.flowStatus]?.color || 'default'">
|
:autoLoad="false"
|
||||||
{{ STATUS[row.flowStatus]?.text || row.flowStatus }}
|
:formatResponse="(res: any) => ({ total: res.total, list: res.list })" />
|
||||||
</Tag>
|
</div>
|
||||||
</template>
|
|
||||||
<template #action="{ row }">
|
|
||||||
<Button size="small" type="text" @click="openDetail(row)">详情</Button>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
<Page class="pager" :total="total" :current="page" :page-size="10" show-total
|
|
||||||
@on-change="(p: number) => { page = p; load(); }" />
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Drawer v-model="detailVisible" title="办件详情(只读)" width="620">
|
<Drawer v-model="detailVisible" title="办件详情(只读)" width="620">
|
||||||
<Spin v-if="detailLoading" fix></Spin>
|
<Spin v-if="detailLoading" fix></Spin>
|
||||||
@@ -166,8 +161,8 @@ onMounted(async () => {
|
|||||||
<DescriptionsItem label="办件号">{{ detail.id }}</DescriptionsItem>
|
<DescriptionsItem label="办件号">{{ detail.id }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="事项">{{ detail.itemTitle }}({{ detail.itemCode }})</DescriptionsItem>
|
<DescriptionsItem label="事项">{{ detail.itemTitle }}({{ detail.itemCode }})</DescriptionsItem>
|
||||||
<DescriptionsItem label="状态">
|
<DescriptionsItem label="状态">
|
||||||
<Tag :color="STATUS[detail.flowStatus]?.color || 'default'">
|
<Tag :color="APPLY_STATUS_COLOR[detail.flowStatus] || 'default'">
|
||||||
{{ STATUS[detail.flowStatus]?.text || detail.flowStatus }}
|
{{ APPLY_STATUS[detail.flowStatus] || detail.flowStatus }}
|
||||||
</Tag>
|
</Tag>
|
||||||
</DescriptionsItem>
|
</DescriptionsItem>
|
||||||
<DescriptionsItem label="提交人">{{ detail.submitterName }}</DescriptionsItem>
|
<DescriptionsItem label="提交人">{{ detail.submitterName }}</DescriptionsItem>
|
||||||
@@ -225,16 +220,6 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.pager {
|
|
||||||
margin-top: 12px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.kv {
|
.kv {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
|
|||||||
@@ -1,38 +1,81 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/** 服务字典管理(modId 24):object/category 双 Tab,引用守护。 */
|
/**
|
||||||
import { onMounted, ref } from "vue";
|
* 服务字典管理(modId 24):object/category 双 Tab,引用守护。
|
||||||
import { Message, Modal } from "view-ui-plus";
|
* 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区,列表状态各自独立。
|
||||||
|
*/
|
||||||
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
|
import { Message } from "view-ui-plus";
|
||||||
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
import { svcDictApi } from "@/api/module/svc";
|
import { svcDictApi } from "@/api/module/svc";
|
||||||
import type { SvcDictVo } from "@/types/svc";
|
import type { SvcDictVo } from "@/types/svc";
|
||||||
import { useSite } from "@/utils/site";
|
import { useSite } from "@/utils/site";
|
||||||
|
import Utils from "@/utils";
|
||||||
|
|
||||||
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
||||||
const tab = ref<"object" | "category">("object");
|
const tab = ref<"object" | "category">("object");
|
||||||
const loading = ref(false);
|
|
||||||
const list = ref<SvcDictVo[]>([]);
|
|
||||||
const modal = ref(false);
|
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
const modal = ref(false);
|
||||||
|
const formRef = ref<any>(null);
|
||||||
const form = ref<any>({});
|
const form = ref<any>({});
|
||||||
|
|
||||||
|
// tabs 页:表格高度在常规骨架上再扣 Tab 条(无分页)
|
||||||
|
const tableHeight = ref(Utils.useTableHeight({ isTabs: true, isOperat: true }));
|
||||||
|
// 首挂载时视口/样式可能尚未定型(body 高偏小甚至 0,且其变化不触发 window resize):
|
||||||
|
// ResizeObserver 观察 body,任何高度来源(样式注入/视口调整)变化即重算,卸载时断开
|
||||||
|
let bodyObserver: ResizeObserver | null = null;
|
||||||
|
const recalcTableHeight = () => {
|
||||||
|
tableHeight.value = Utils.useTableHeight({ isTabs: true, isOperat: true });
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
recalcTableHeight();
|
||||||
|
bodyObserver = new ResizeObserver(recalcTableHeight);
|
||||||
|
bodyObserver.observe(document.body);
|
||||||
|
});
|
||||||
|
onUnmounted(() => bodyObserver?.disconnect());
|
||||||
|
|
||||||
|
// 两个 Tab 各自的列表状态(切换只 v-show,互不重置)
|
||||||
|
const objectRows = ref<SvcDictVo[]>([]);
|
||||||
|
const objectLoading = ref(false);
|
||||||
|
const categoryRows = ref<SvcDictVo[]>([]);
|
||||||
|
const categoryLoading = ref(false);
|
||||||
|
|
||||||
const cols = [
|
const cols = [
|
||||||
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
{ title: "键", key: "dictKey", width: 160 },
|
{ title: "键", key: "dictKey", width: 160 },
|
||||||
{ title: "名称", key: "dictName", minWidth: 160 },
|
{ title: "名称", key: "dictName", minWidth: 160 },
|
||||||
{ title: "排序", key: "sortNo", width: 80 },
|
{ title: "排序", key: "sortNo", width: 80, align: "center" },
|
||||||
{ title: "被引用", key: "referenced", width: 90 },
|
{ title: "被引用", key: "referenced", width: 90, align: "center" },
|
||||||
{ title: "状态", slot: "status", width: 90 },
|
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) =>
|
||||||
{ title: "操作", slot: "action", width: 140 },
|
h("Tag", { color: p.row.status === 1 ? "green" : "gray" }, () => (p.row.status === 1 ? "启用" : "停用")) },
|
||||||
|
{ title: "操作", width: 140, align: "center", fixed: "right", render: (h: any, p: any) => [
|
||||||
|
Utils.operatingHandle(h, "编辑", "icon-edit", () => open(p.row)),
|
||||||
|
Utils.operatingHandle(h, "删除", "icon-ashbin", () => remove(p.row)),
|
||||||
|
] },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
if (!currentSiteCode.value) return;
|
if (!currentSiteCode.value) return;
|
||||||
loading.value = true;
|
const loadingRef = tab.value === "object" ? objectLoading : categoryLoading;
|
||||||
|
const rowsRef = tab.value === "object" ? objectRows : categoryRows;
|
||||||
|
loadingRef.value = true;
|
||||||
try {
|
try {
|
||||||
list.value = (await svcDictApi.list(currentSiteCode.value, tab.value).send()) || [];
|
rowsRef.value = (await svcDictApi.list(currentSiteCode.value, tab.value).send()) || [];
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loadingRef.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切 Tab 拉当前页数据(字典量小,直接刷新保持实时)
|
||||||
|
function switchTab(name: string) {
|
||||||
|
tab.value = name as any;
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveRules = {
|
||||||
|
dictKey: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
|
||||||
|
dictName: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
|
||||||
|
};
|
||||||
|
|
||||||
function open(row?: SvcDictVo) {
|
function open(row?: SvcDictVo) {
|
||||||
form.value = row
|
form.value = row
|
||||||
? { ...row }
|
? { ...row }
|
||||||
@@ -40,23 +83,27 @@ function open(row?: SvcDictVo) {
|
|||||||
modal.value = true;
|
modal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
function save() {
|
||||||
saving.value = true;
|
formRef.value?.validate(async (valid: boolean) => {
|
||||||
try {
|
if (!valid) return;
|
||||||
await svcDictApi.save(form.value).send();
|
saving.value = true;
|
||||||
Message.success("已保存");
|
try {
|
||||||
modal.value = false;
|
await svcDictApi.save(form.value).send();
|
||||||
load();
|
Message.success("已保存");
|
||||||
} finally {
|
modal.value = false;
|
||||||
saving.value = false;
|
load();
|
||||||
}
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(row: SvcDictVo) {
|
function remove(row: SvcDictVo) {
|
||||||
Modal.confirm({
|
MyConfirm({
|
||||||
title: "删除确认",
|
title: "删除",
|
||||||
content: `确定删除字典「${row.dictName}」吗?被引用的删除会被拒绝。`,
|
msg: `确定删除字典「${row.dictName}」吗?`,
|
||||||
onOk: async () => {
|
info: "被引用的删除会被拒绝",
|
||||||
|
async onOk() {
|
||||||
await svcDictApi.delete(currentSiteCode.value, row.id).send();
|
await svcDictApi.delete(currentSiteCode.value, row.id).send();
|
||||||
Message.success("已删除");
|
Message.success("已删除");
|
||||||
load();
|
load();
|
||||||
@@ -71,36 +118,47 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="tabs-wrapper">
|
||||||
<Card :bordered="false" dis-hover>
|
<Tabs type="card" :value="tab" @on-click="switchTab">
|
||||||
<div class="bar">
|
<TabPane label="服务对象" name="object" />
|
||||||
<Select :model-value="currentSiteCode" style="width: 240px" @on-change="(c: string) => { setSite(c); load(); }">
|
<TabPane label="事项类别" name="category" />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<div class="main-container" v-show="tab === 'object'">
|
||||||
|
<div class="fix mb-12">
|
||||||
|
<Button class="fl" type="primary" size="large" @click="open()">新建服务对象</Button>
|
||||||
|
</div>
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select :model-value="currentSiteCode" class="search-wrap_sel" placeholder="站点"
|
||||||
|
@on-change="(c: string) => { setSite(c); load(); }">
|
||||||
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button type="primary" @click="open()">新建{{ tab === "object" ? "服务对象" : "事项类别" }}</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<Tabs v-model="tab" @on-click="() => load()">
|
<Table :columns="cols" :data="objectRows" :loading="objectLoading" class="link-table table-bottom-box"
|
||||||
<TabPane label="服务对象" name="object" />
|
:height="tableHeight" no-data-text="暂无数据" />
|
||||||
<TabPane label="事项类别" name="category" />
|
</div>
|
||||||
</Tabs>
|
|
||||||
<Table :loading="loading" :columns="cols" :data="list">
|
|
||||||
<template #status="{ row }">
|
|
||||||
<Tag :color="row.status === 1 ? 'success' : 'default'">{{ row.status === 1 ? "启用" : "停用" }}</Tag>
|
|
||||||
</template>
|
|
||||||
<template #action="{ row }">
|
|
||||||
<Button size="small" type="text" @click="open(row)">编辑</Button>
|
|
||||||
<Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Modal v-model="modal" :title="form.id ? '编辑字典' : '新建字典'">
|
<div class="main-container" v-show="tab === 'category'">
|
||||||
<Form :label-width="90">
|
<div class="fix mb-12">
|
||||||
<FormItem label="字典键" required>
|
<Button class="fl" type="primary" size="large" @click="open()">新建事项类别</Button>
|
||||||
|
</div>
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select :model-value="currentSiteCode" class="search-wrap_sel" placeholder="站点"
|
||||||
|
@on-change="(c: string) => { setSite(c); load(); }">
|
||||||
|
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Table :columns="cols" :data="categoryRows" :loading="categoryLoading" class="link-table table-bottom-box"
|
||||||
|
:height="tableHeight" no-data-text="暂无数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal v-model="modal" :title="form.id ? '编辑字典' : '新建字典'" :mask-closable="false">
|
||||||
|
<Form ref="formRef" :model="form" :rules="saveRules" :label-width="90">
|
||||||
|
<FormItem label="字典键" prop="dictKey">
|
||||||
<Input v-model="form.dictKey" :disabled="!!form.id && (form.referenced || 0) > 0" placeholder="字母/数字/下划线" />
|
<Input v-model="form.dictKey" :disabled="!!form.id && (form.referenced || 0) > 0" placeholder="字母/数字/下划线" />
|
||||||
<div v-if="(form.referenced || 0) > 0" class="tip">已被 {{ form.referenced }} 个事项引用,仅可改名称</div>
|
<div v-if="(form.referenced || 0) > 0" class="tip">已被 {{ form.referenced }} 个事项引用,仅可改名称</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="名称" required>
|
<FormItem label="名称" prop="dictName">
|
||||||
<Input v-model="form.dictName" :maxlength="128" />
|
<Input v-model="form.dictName" :maxlength="128" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="排序">
|
<FormItem label="排序">
|
||||||
@@ -112,18 +170,13 @@ onMounted(async () => {
|
|||||||
</Form>
|
</Form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Button @click="modal = false">取消</Button>
|
<Button @click="modal = false">取消</Button>
|
||||||
<Button type="primary" :loading="saving" @click="save">保存</Button>
|
<Button type="primary" :loading="saving" @click="save">提交</Button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.tip {
|
.tip {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/** 表单管理(modId 25):列表/发布/删除/版本轨迹/绑定明细。编辑走独立全屏设计器(/svc/form/designer)。 */
|
/**
|
||||||
import { onMounted, ref } from "vue";
|
* 表单管理(modId 25):列表/发布/删除/版本轨迹/绑定明细。编辑走独立全屏设计器(/svc/form/designer)。
|
||||||
|
* 规范骨架(bsch应用页规范 §1):操作按钮区 → 搜索区 → 表格(接口全量返回,无分页,前端关键字过滤)。
|
||||||
|
*/
|
||||||
|
import { computed, nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Message, Modal } from "view-ui-plus";
|
import { Message, Modal } from "view-ui-plus";
|
||||||
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
import { svcFormApi } from "@/api/module/svc";
|
import { svcFormApi } from "@/api/module/svc";
|
||||||
import type { SvcFormItemBrief, SvcFormVo } from "@/types/svc";
|
import type { SvcFormItemBrief, SvcFormVo } from "@/types/svc";
|
||||||
import { useSite } from "@/utils/site";
|
import { useSite } from "@/utils/site";
|
||||||
|
import Utils from "@/utils";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const list = ref<SvcFormVo[]>([]);
|
const keyword = ref("");
|
||||||
|
const rows = ref<SvcFormVo[]>([]);
|
||||||
|
// 操作按钮区 + 搜索区、无分页
|
||||||
|
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true }));
|
||||||
|
// 首挂载时视口/样式可能尚未定型(body 高偏小甚至 0,且其变化不触发 window resize):
|
||||||
|
// ResizeObserver 观察 body,任何高度来源(样式注入/视口调整)变化即重算,卸载时断开
|
||||||
|
let bodyObserver: ResizeObserver | null = null;
|
||||||
|
const recalcTableHeight = () => {
|
||||||
|
tableHeight.value = Utils.useTableHeight({ isSearch: true, isOperat: true });
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
recalcTableHeight();
|
||||||
|
bodyObserver = new ResizeObserver(recalcTableHeight);
|
||||||
|
bodyObserver.observe(document.body);
|
||||||
|
});
|
||||||
|
onUnmounted(() => bodyObserver?.disconnect());
|
||||||
|
|
||||||
const versionsModal = ref(false);
|
const versionsModal = ref(false);
|
||||||
const versionRows = ref<SvcFormVo[]>([]);
|
const versionRows = ref<SvcFormVo[]>([]);
|
||||||
const refsModal = ref(false);
|
const refsModal = ref(false);
|
||||||
@@ -18,14 +39,21 @@ const refRows = ref<SvcFormItemBrief[]>([]);
|
|||||||
const refFormName = ref("");
|
const refFormName = ref("");
|
||||||
|
|
||||||
const statusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "停用" : "草稿");
|
const statusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "停用" : "草稿");
|
||||||
const statusColor = (s: number) => (s === 1 ? "success" : "default");
|
|
||||||
const itemStatusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "已下线" : "草稿");
|
const itemStatusName = (s: number) => (s === 1 ? "已发布" : s === 2 ? "已下线" : "草稿");
|
||||||
|
|
||||||
|
// 关键字前端过滤(list 接口全量返回,无服务端 keyword)
|
||||||
|
const filteredRows = computed(() => {
|
||||||
|
const kw = keyword.value.trim().toLowerCase();
|
||||||
|
if (!kw) return rows.value;
|
||||||
|
return rows.value.filter((r: any) =>
|
||||||
|
String(r.name || "").toLowerCase().includes(kw) || String(r.code || "").toLowerCase().includes(kw));
|
||||||
|
});
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
if (!currentSiteCode.value) return;
|
if (!currentSiteCode.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
list.value = (await svcFormApi.list(currentSiteCode.value).send()) || [];
|
rows.value = (await svcFormApi.list(currentSiteCode.value).send()) || [];
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -56,10 +84,11 @@ async function publish(row: SvcFormVo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function remove(row: SvcFormVo) {
|
function remove(row: SvcFormVo) {
|
||||||
Modal.confirm({
|
MyConfirm({
|
||||||
title: "删除确认",
|
title: "删除",
|
||||||
content: `确定删除表单「${row.name}」的全部版本吗?被事项绑定的删除会被拒绝。`,
|
msg: `确定删除表单「${row.name}」的全部版本吗?`,
|
||||||
onOk: async () => {
|
info: "被事项绑定的删除会被拒绝",
|
||||||
|
async onOk() {
|
||||||
await svcFormApi.delete(currentSiteCode.value, row.code).send();
|
await svcFormApi.delete(currentSiteCode.value, row.code).send();
|
||||||
Message.success("已删除");
|
Message.success("已删除");
|
||||||
load();
|
load();
|
||||||
@@ -85,49 +114,56 @@ function editRefItem(item: SvcFormItemBrief) {
|
|||||||
router.push(`/svc/item/edit?id=${item.id}`);
|
router.push(`/svc/item/edit?id=${item.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cols = [
|
||||||
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
|
{ title: "编码", key: "code", width: 160 },
|
||||||
|
{ title: "名称", key: "name", minWidth: 140 },
|
||||||
|
{ title: "最新版本", key: "version", width: 90, align: "center" },
|
||||||
|
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) =>
|
||||||
|
h("Tag", { color: p.row.status === 1 ? "success" : "default" }, () => statusName(p.row.status)) },
|
||||||
|
{ title: "被绑定", width: 110, align: "center", render: (h: any, p: any) =>
|
||||||
|
p.row.referenced
|
||||||
|
? h("a", { style: "cursor:pointer", onClick: () => showRefs(p.row) }, `已绑定 ${(p.row.refItems || []).length} 个事项`)
|
||||||
|
: h("span", { style: "color:#999" }, "未绑定") },
|
||||||
|
{ title: "发布时间", key: "publishTime", width: 160 },
|
||||||
|
{ title: "操作", width: 220, align: "center", fixed: "right", render: (h: any, p: any) => {
|
||||||
|
const btns = [Utils.operatingHandle(h, "编辑", "icon-edit", () => editForm(p.row))];
|
||||||
|
if (p.row.status === 0) btns.push(Utils.operatingHandle(h, "发布", "icon-fasong", () => publish(p.row)));
|
||||||
|
if (!p.row.referenced) btns.push(Utils.operatingHandle(h, "去绑定", "icon-peizhi", () => router.push("/svc/item")));
|
||||||
|
btns.push(Utils.operatingHandle(h, "版本", "icon-chakan", () => showVersions(p.row)));
|
||||||
|
btns.push(Utils.operatingHandle(h, "删除", "icon-ashbin", () => remove(p.row)));
|
||||||
|
return btns;
|
||||||
|
} },
|
||||||
|
];
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSites();
|
await loadSites();
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
|
|
||||||
const cols = [
|
|
||||||
{ title: "编码", key: "code", width: 160 },
|
|
||||||
{ title: "名称", key: "name", minWidth: 140 },
|
|
||||||
{ title: "最新版本", key: "version", width: 90 },
|
|
||||||
{ title: "状态", slot: "status", width: 90 },
|
|
||||||
{ title: "被绑定", slot: "referenced", width: 110 },
|
|
||||||
{ title: "发布时间", key: "publishTime", width: 160 },
|
|
||||||
{ title: "操作", slot: "action", width: 260 },
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page-wrapper">
|
||||||
<Card :bordered="false" dis-hover>
|
<div class="main-container">
|
||||||
<div class="bar">
|
<!-- 操作按钮区:主操作靠左,说明文字靠右 -->
|
||||||
<Select :model-value="currentSiteCode" style="width: 240px" @on-change="(c: string) => { setSite(c); load(); }">
|
<div class="fix mb-12">
|
||||||
|
<Button class="fl" type="primary" size="large" @click="createForm">新建表单</Button>
|
||||||
|
<span class="fr bar-tip">拖拽式设计器编辑表单结构,保存为草稿后发布即可在事项发起页使用</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索区:站点下拉靠左,关键字前端过滤即输即搜 -->
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select :model-value="currentSiteCode" class="search-wrap_sel" placeholder="站点"
|
||||||
|
@on-change="(c: string) => { setSite(c); load(); }">
|
||||||
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button type="primary" @click="createForm">新建表单</Button>
|
<Input class="fr" v-model="keyword" placeholder="名称/编码" search suffix="ios-search"
|
||||||
<div class="bar-tip">拖拽式设计器编辑表单结构,保存为草稿后发布即可在事项发起页使用</div>
|
style="width: 200px" />
|
||||||
</div>
|
</div>
|
||||||
<Table :loading="loading" :columns="cols" :data="list">
|
|
||||||
<template #status="{ row }">
|
<Table :columns="cols" :data="filteredRows" :loading="loading" class="link-table table-bottom-box"
|
||||||
<Tag :color="statusColor(row.status)">{{ statusName(row.status) }}</Tag>
|
:height="tableHeight" no-data-text="暂无数据" />
|
||||||
</template>
|
</div>
|
||||||
<template #referenced="{ row }">
|
|
||||||
<a v-if="row.referenced" @click="showRefs(row)">已绑定 {{ (row.refItems || []).length }} 个事项</a>
|
|
||||||
<span v-else style="color: #999">未绑定</span>
|
|
||||||
</template>
|
|
||||||
<template #action="{ row }">
|
|
||||||
<Button size="small" type="text" @click="editForm(row)">编辑</Button>
|
|
||||||
<Button v-if="row.status === 0" size="small" type="text" @click="publish(row)">发布</Button>
|
|
||||||
<Button v-if="!row.referenced" size="small" type="text" @click="router.push('/svc/item')">去绑定</Button>
|
|
||||||
<Button size="small" type="text" @click="showVersions(row)">版本</Button>
|
|
||||||
<Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button>
|
|
||||||
</template>
|
|
||||||
</Table>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Modal v-model="refsModal" width="720" :title="`「${refFormName}」被以下事项使用`">
|
<Modal v-model="refsModal" width="720" :title="`「${refFormName}」被以下事项使用`">
|
||||||
<Table size="small" :columns="[
|
<Table size="small" :columns="[
|
||||||
@@ -167,15 +203,10 @@ const cols = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.bar-tip {
|
.bar-tip {
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
.tip {
|
.tip {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
+195
-181
@@ -17,6 +17,13 @@ import RichTextEditor from "@/components/RichTextEditor.vue";
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentSiteCode, loadSites } = useSite();
|
const { currentSiteCode, loadSites } = useSite();
|
||||||
|
const formRef = ref<any>(null);
|
||||||
|
|
||||||
|
// 必填校验(bsch应用页规范 §1:校验失败提示统一"请填写红色星号内容!")
|
||||||
|
const saveRules = {
|
||||||
|
itemCode: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
|
||||||
|
title: [{ required: true, message: "请填写红色星号内容!", trigger: "blur" }],
|
||||||
|
};
|
||||||
|
|
||||||
const form = ref<SvcItemSaveRequest & Record<string, any>>({
|
const form = ref<SvcItemSaveRequest & Record<string, any>>({
|
||||||
siteCode: "",
|
siteCode: "",
|
||||||
@@ -109,6 +116,11 @@ async function uploadGuide(file: File) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save(publishAfter: boolean) {
|
async function save(publishAfter: boolean) {
|
||||||
|
const valid = await new Promise<boolean>(resolve => formRef.value?.validate(resolve));
|
||||||
|
if (!valid) {
|
||||||
|
Message.warning("请填写红色星号内容!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const payload: SvcItemSaveRequest = {
|
const payload: SvcItemSaveRequest = {
|
||||||
...form.value,
|
...form.value,
|
||||||
id: route.query.id ? Number(route.query.id) : undefined,
|
id: route.query.id ? Number(route.query.id) : undefined,
|
||||||
@@ -139,195 +151,197 @@ onMounted(loadAll);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page-wrapper">
|
||||||
<Card :bordered="false" dis-hover>
|
<div class="main-container">
|
||||||
<template #title>{{ pageTitle }}</template>
|
<Card :bordered="false" dis-hover>
|
||||||
<Form :label-width="130">
|
<template #title>{{ pageTitle }}</template>
|
||||||
<Divider orientation="left">基本信息</Divider>
|
<Form ref="formRef" :model="form" :rules="saveRules" :label-width="130">
|
||||||
<Row :gutter="16">
|
<Divider orientation="left">基本信息</Divider>
|
||||||
<Col span="12">
|
<Row :gutter="16">
|
||||||
<FormItem label="事项编码" required>
|
<Col span="12">
|
||||||
<Input v-model="form.itemCode" placeholder="字母/数字/下划线/中划线,2~64 位" />
|
<FormItem label="事项编码" prop="itemCode">
|
||||||
</FormItem>
|
<Input v-model="form.itemCode" placeholder="字母/数字/下划线/中划线,2~64 位" />
|
||||||
</Col>
|
</FormItem>
|
||||||
<Col span="12">
|
</Col>
|
||||||
<FormItem label="事项名称" required>
|
<Col span="12">
|
||||||
<Input v-model="form.title" :maxlength="200" />
|
<FormItem label="事项名称" prop="title">
|
||||||
</FormItem>
|
<Input v-model="form.title" :maxlength="200" />
|
||||||
</Col>
|
</FormItem>
|
||||||
<Col span="24">
|
</Col>
|
||||||
<FormItem label="一句话简介">
|
<Col span="24">
|
||||||
<Input v-model="form.summary" type="textarea" :rows="2" :maxlength="500" />
|
<FormItem label="一句话简介">
|
||||||
</FormItem>
|
<Input v-model="form.summary" type="textarea" :rows="2" :maxlength="500" />
|
||||||
</Col>
|
</FormItem>
|
||||||
<Col span="8">
|
</Col>
|
||||||
<FormItem label="服务对象">
|
<Col span="8">
|
||||||
<Select v-model="form.objectKey" clearable>
|
<FormItem label="服务对象">
|
||||||
<Option v-for="d in objects" :key="d.dictKey" :value="d.dictKey">{{ d.dictName }}</Option>
|
<Select v-model="form.objectKey" clearable>
|
||||||
</Select>
|
<Option v-for="d in objects" :key="d.dictKey" :value="d.dictKey">{{ d.dictName }}</Option>
|
||||||
</FormItem>
|
</Select>
|
||||||
</Col>
|
</FormItem>
|
||||||
<Col span="8">
|
</Col>
|
||||||
<FormItem label="事项类别">
|
<Col span="8">
|
||||||
<Select v-model="form.categoryKey" clearable>
|
<FormItem label="事项类别">
|
||||||
<Option v-for="d in categories" :key="d.dictKey" :value="d.dictKey">{{ d.dictName }}</Option>
|
<Select v-model="form.categoryKey" clearable>
|
||||||
</Select>
|
<Option v-for="d in categories" :key="d.dictKey" :value="d.dictKey">{{ d.dictName }}</Option>
|
||||||
</FormItem>
|
</Select>
|
||||||
</Col>
|
</FormItem>
|
||||||
<Col span="8">
|
</Col>
|
||||||
<FormItem label="服务部门">
|
<Col span="8">
|
||||||
<Select v-model="form.deptGroupId" clearable>
|
<FormItem label="服务部门">
|
||||||
<Option v-for="d in deptOptions" :key="d.id" :value="d.id">{{ d.label }}</Option>
|
<Select v-model="form.deptGroupId" clearable>
|
||||||
</Select>
|
<Option v-for="d in deptOptions" :key="d.id" :value="d.id">{{ d.label }}</Option>
|
||||||
</FormItem>
|
</Select>
|
||||||
</Col>
|
</FormItem>
|
||||||
</Row>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Divider orientation="left">办理信息</Divider>
|
<Divider orientation="left">办理信息</Divider>
|
||||||
<Row :gutter="16">
|
<Row :gutter="16">
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="办理方式">
|
<FormItem label="办理方式">
|
||||||
<RadioGroup v-model="form.handleMode">
|
<RadioGroup v-model="form.handleMode">
|
||||||
<Radio :label="1">线上</Radio>
|
<Radio :label="1">线上</Radio>
|
||||||
<Radio :label="2">线下</Radio>
|
<Radio :label="2">线下</Radio>
|
||||||
<Radio :label="3">线上线下</Radio>
|
<Radio :label="3">线上线下</Radio>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="可在线办理">
|
<FormItem label="可在线办理">
|
||||||
<Switch v-model="form.onlineFlag" :true-value="1" :false-value="0" />
|
<Switch v-model="form.onlineFlag" :true-value="1" :false-value="0" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="办理地点">
|
<FormItem label="办理地点">
|
||||||
<Input v-model="form.handlePlace" :maxlength="200" />
|
<Input v-model="form.handlePlace" :maxlength="200" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="办理时限">
|
<FormItem label="办理时限">
|
||||||
<Input v-model="form.handleTimeLimit" :maxlength="64" />
|
<Input v-model="form.handleTimeLimit" :maxlength="64" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="承诺时限">
|
<FormItem label="承诺时限">
|
||||||
<Input v-model="form.promiseTimeLimit" :maxlength="64" />
|
<Input v-model="form.promiseTimeLimit" :maxlength="64" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="咨询电话">
|
<FormItem label="咨询电话">
|
||||||
<Input v-model="form.contactPhone" :maxlength="64" />
|
<Input v-model="form.contactPhone" :maxlength="64" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="监督投诉电话">
|
<FormItem label="监督投诉电话">
|
||||||
<Input v-model="form.supervisePhone" :maxlength="64" />
|
<Input v-model="form.supervisePhone" :maxlength="64" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Divider orientation="left">在线办理绑定</Divider>
|
<Divider orientation="left">在线办理绑定</Divider>
|
||||||
<Row :gutter="16">
|
<Row :gutter="16">
|
||||||
<Col span="12">
|
<Col span="12">
|
||||||
<FormItem label="办理流程">
|
<FormItem label="办理流程">
|
||||||
<div class="flow-select">
|
<div class="flow-select">
|
||||||
<Select v-model="form.flowGuid" clearable :disabled="flowDisabled" placeholder="流程中心流程">
|
<Select v-model="form.flowGuid" clearable :disabled="flowDisabled" placeholder="流程中心流程">
|
||||||
<Option v-for="f in flows" :key="f.flowGuid" :value="f.flowGuid">
|
<Option v-for="f in flows" :key="f.flowGuid" :value="f.flowGuid">
|
||||||
{{ f.name }}(v{{ f.version }})
|
{{ f.name }}(v{{ f.version }})
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
|
<Button v-if="form.flowGuid" size="small" @click="openFlowDiagram">流程图</Button>
|
||||||
|
</div>
|
||||||
|
<div v-if="flowDisabled" class="tip">{{ flowDisabledReason || "流程中心未接入,暂只能配置线下事项" }}</div>
|
||||||
|
<div v-else class="tip">可在线办理的事项必须绑定流程</div>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col span="12">
|
||||||
|
<FormItem label="办理表单">
|
||||||
|
<Select v-model="form.formCode" clearable placeholder="该事项发起时填写的自定义表单">
|
||||||
|
<Option v-for="f in forms" :key="f.code" :value="f.code">
|
||||||
|
{{ f.name }}{{ f.status !== 1 ? `(${f.status === 2 ? "已停用" : "草稿"})` : "" }}
|
||||||
</Option>
|
</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Button v-if="form.flowGuid" size="small" @click="openFlowDiagram">流程图</Button>
|
<div v-if="selectedFormDraft" class="tip" style="color: #ed4014">
|
||||||
</div>
|
该表单尚未发布,发布后才能在发起页实际填写;请到「表单管理」发布。
|
||||||
<div v-if="flowDisabled" class="tip">{{ flowDisabledReason || "流程中心未接入,暂只能配置线下事项" }}</div>
|
</div>
|
||||||
<div v-else class="tip">可在线办理的事项必须绑定流程</div>
|
<div v-else class="tip">发起页按表单编码取最新已发布版本渲染</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="12">
|
<Col span="24">
|
||||||
<FormItem label="办理表单">
|
<FormItem label="我要办理外链">
|
||||||
<Select v-model="form.formCode" clearable placeholder="该事项发起时填写的自定义表单">
|
<Input v-model="form.applyUrl" placeholder="http(s):// 完整地址;填写后优先于自动生成地址" />
|
||||||
<Option v-for="f in forms" :key="f.code" :value="f.code">
|
<div class="tip">优先级:本外链 > 办事大厅自动生成 > 不显示按钮</div>
|
||||||
{{ f.name }}{{ f.status !== 1 ? `(${f.status === 2 ? "已停用" : "草稿"})` : "" }}
|
</FormItem>
|
||||||
</Option>
|
</Col>
|
||||||
</Select>
|
</Row>
|
||||||
<div v-if="selectedFormDraft" class="tip" style="color: #ed4014">
|
|
||||||
该表单尚未发布,发布后才能在发起页实际填写;请到「表单管理」发布。
|
|
||||||
</div>
|
|
||||||
<div v-else class="tip">发起页按表单编码取最新已发布版本渲染</div>
|
|
||||||
</FormItem>
|
|
||||||
</Col>
|
|
||||||
<Col span="24">
|
|
||||||
<FormItem label="我要办理外链">
|
|
||||||
<Input v-model="form.applyUrl" placeholder="http(s):// 完整地址;填写后优先于自动生成地址" />
|
|
||||||
<div class="tip">优先级:本外链 > 办事大厅自动生成 > 不显示按钮</div>
|
|
||||||
</FormItem>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Divider orientation="left">关联栏目与资产</Divider>
|
<Divider orientation="left">关联栏目与资产</Divider>
|
||||||
<Row :gutter="16">
|
<Row :gutter="16">
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="常见问题栏目">
|
<FormItem label="常见问题栏目">
|
||||||
<InputNumber v-model="form.faqCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
<InputNumber v-model="form.faqCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="公告信息栏目">
|
<FormItem label="公告信息栏目">
|
||||||
<InputNumber v-model="form.noticeCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
<InputNumber v-model="form.noticeCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<FormItem label="文件规定栏目">
|
<FormItem label="文件规定栏目">
|
||||||
<InputNumber v-model="form.ruleCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
<InputNumber v-model="form.ruleCategoryId" :min="1" style="width: 100%" placeholder="CMS 栏目 id" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="12">
|
<Col span="12">
|
||||||
<FormItem label="事项封面">
|
<FormItem label="事项封面">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<img v-if="form.coverImage" :src="form.coverImage" class="cover-img" />
|
<img v-if="form.coverImage" :src="form.coverImage" class="cover-img" />
|
||||||
<Upload :before-upload="uploadCover" :show-upload-list="false" accept="image/*">
|
<Upload :before-upload="uploadCover" :show-upload-list="false" accept="image/*">
|
||||||
<Button :loading="uploadingCover" icon="ios-image-outline">上传封面</Button>
|
<Button :loading="uploadingCover" icon="ios-image-outline">上传封面</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
<Button v-if="form.coverImage" size="small" type="text" @click="form.coverImage = ''">移除</Button>
|
<Button v-if="form.coverImage" size="small" type="text" @click="form.coverImage = ''">移除</Button>
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="6">
|
<Col span="6">
|
||||||
<FormItem label="排序值">
|
<FormItem label="排序值">
|
||||||
<InputNumber v-model="form.sortNo" style="width: 100%" />
|
<InputNumber v-model="form.sortNo" style="width: 100%" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Divider orientation="left">指南内容</Divider>
|
<Divider orientation="left">指南内容</Divider>
|
||||||
<FormItem label="办理流程">
|
<FormItem label="办理流程">
|
||||||
<RichTextEditor v-model="form.processHtml" placeholder="办理流程说明" />
|
<RichTextEditor v-model="form.processHtml" placeholder="办理流程说明" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="所需材料">
|
<FormItem label="所需材料">
|
||||||
<RichTextEditor v-model="form.materialHtml" placeholder="所需材料说明" />
|
<RichTextEditor v-model="form.materialHtml" placeholder="所需材料说明" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="注意事项">
|
<FormItem label="注意事项">
|
||||||
<RichTextEditor v-model="form.noticeHtml" placeholder="注意事项" />
|
<RichTextEditor v-model="form.noticeHtml" placeholder="注意事项说明" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="指南附件">
|
<FormItem label="指南附件">
|
||||||
<Upload :before-upload="uploadGuide" :show-upload-list="false">
|
<Upload :before-upload="uploadGuide" :show-upload-list="false">
|
||||||
<Button icon="ios-cloud-upload-outline">上传附件</Button>
|
<Button icon="ios-cloud-upload-outline">上传附件</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
<div class="att-list">
|
<div class="att-list">
|
||||||
<div v-for="a in attachments" :key="a.id" class="att-item">
|
<div v-for="a in attachments" :key="a.id" class="att-item">
|
||||||
<a :href="a.fileUrl" target="_blank" rel="noopener">{{ a.fileName }}</a>
|
<a :href="a.fileUrl" target="_blank" rel="noopener">{{ a.fileName }}</a>
|
||||||
<Button size="small" type="text" style="color: #ed4014"
|
<Button size="small" type="text" style="color: #ed4014"
|
||||||
@click="attachments = attachments.filter(x => x.id !== a.id)">移除</Button>
|
@click="attachments = attachments.filter(x => x.id !== a.id)">移除</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FormItem>
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<Button type="primary" :loading="false" @click="save(false)">保存</Button>
|
<Button type="primary" :loading="false" @click="save(false)">保存</Button>
|
||||||
<Button type="success" @click="save(true)">保存并发布</Button>
|
<Button type="success" @click="save(true)">保存并发布</Button>
|
||||||
<Button @click="router.back()">返回</Button>
|
<Button @click="router.back()">返回</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +1,101 @@
|
|||||||
<script setup lang="ts">
|
<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 { useRouter } from "vue-router";
|
||||||
import { Message, Modal } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
import { svcItemApi } from "@/api/module/svc";
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
import type { SvcItemPageItem } from "@/types/svc";
|
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 { useSite } from "@/utils/site";
|
||||||
|
import Utils from "@/utils";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
const { currentSiteCode, sites, loadSites, setSite } = useSite();
|
||||||
const loading = ref(false);
|
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
const list = ref<SvcItemPageItem[]>([]);
|
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true, isPage: true }));
|
||||||
const total = ref(0);
|
// 首挂载时视口/样式可能尚未定型(body 高偏小甚至 0,且其变化不触发 window resize):
|
||||||
const page = ref(1);
|
// ResizeObserver 观察 body,任何高度来源(样式注入/视口调整)变化即重算,卸载时断开
|
||||||
const pageSize = ref(10);
|
let bodyObserver: ResizeObserver | null = null;
|
||||||
const status = ref<number | undefined>(undefined);
|
const recalcTableHeight = () => {
|
||||||
const keyword = ref("");
|
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 handleModeName = (m: number) => (m === 1 ? "线上" : m === 2 ? "线下" : "线上线下");
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
{ title: "编码", key: "itemCode", width: 140 },
|
{ title: "编码", key: "itemCode", width: 140 },
|
||||||
{ title: "名称", key: "title", minWidth: 200 },
|
{ title: "名称", key: "title", minWidth: 200 },
|
||||||
{ title: "类别", key: "categoryName", width: 110 },
|
{ title: "类别", key: "categoryName", width: 110 },
|
||||||
{ title: "部门", key: "deptName", width: 130 },
|
{ title: "部门", key: "deptName", width: 130 },
|
||||||
{
|
{ title: "办理方式", width: 90, align: "center", render: (h: any, p: any) => h("span", handleModeName(p.row.handleMode)) },
|
||||||
title: "办理方式",
|
{ title: "在线办理", width: 110, align: "center", render: (h: any, p: any) =>
|
||||||
width: 90,
|
p.row.onlineFlag === 1
|
||||||
render: (h: any, p: any) => h("span", handleModeName(p.row.handleMode)),
|
? h("Tag", { color: "green" }, () => "可在线办理")
|
||||||
},
|
: h("span", "-") },
|
||||||
{ title: "在线办理", slot: "online", width: 110 },
|
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) =>
|
||||||
{ title: "状态", slot: "status", width: 90 },
|
h("Tag", { color: ITEM_STATUS_COLOR[p.row.status] || "default" }, () => ITEM_STATUS[p.row.status] || p.row.status) },
|
||||||
{ title: "浏览量", key: "viewCount", width: 80 },
|
{ title: "浏览量", key: "viewCount", width: 80, align: "center" },
|
||||||
{ title: "操作", slot: "action", width: 200 },
|
{ 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() {
|
// dataApi 返回 Method(alova usePagination 负责发送与解包),不能调 .send()
|
||||||
if (!currentSiteCode.value) return;
|
const tableApi = (p: { pageIndex: number; pageSize: number }) => {
|
||||||
loading.value = true;
|
const params: any = { siteCode: currentSiteCode.value, page: p.pageIndex, pageSize: p.pageSize };
|
||||||
try {
|
if (dataParams.value.status !== undefined) params.status = dataParams.value.status;
|
||||||
const res: any = await svcItemApi
|
if (dataParams.value.keyword) params.keyword = dataParams.value.keyword.trim();
|
||||||
.page({
|
return svcItemApi.page(params);
|
||||||
siteCode: currentSiteCode.value,
|
};
|
||||||
status: status.value,
|
|
||||||
keyword: keyword.value,
|
// 站点/筛选变化回到第 1 页
|
||||||
page: page.value,
|
function onSearch() {
|
||||||
pageSize: pageSize.value,
|
tableRef.value?.refreshData();
|
||||||
})
|
|
||||||
.send();
|
|
||||||
total.value = res.total || 0;
|
|
||||||
list.value = res.list || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSiteChange(code: string) {
|
function edit(row?: any) {
|
||||||
setSite(code);
|
|
||||||
page.value = 1;
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
function edit(row?: SvcItemPageItem) {
|
|
||||||
router.push({ path: "/svc/item/edit", query: { id: row?.id ? String(row.id) : undefined } });
|
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();
|
await svcItemApi.publish(currentSiteCode.value, row.id).send();
|
||||||
Message.success("已发布");
|
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();
|
await svcItemApi.offline(currentSiteCode.value, row.id).send();
|
||||||
Message.success("已下架");
|
Message.success("已下架");
|
||||||
load();
|
tableRef.value?.reloadData(OperatorEnum.edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(row: SvcItemPageItem) {
|
function remove(row: any) {
|
||||||
Modal.confirm({
|
MyConfirm({
|
||||||
title: "删除确认",
|
title: "删除",
|
||||||
content: `确定删除事项「${row.title}」吗?删除后不可恢复。`,
|
msg: `确定删除事项「${row.title}」吗?`,
|
||||||
onOk: async () => {
|
info: "删除后不可恢复",
|
||||||
|
async onOk() {
|
||||||
await svcItemApi.delete(currentSiteCode.value, row.id).send();
|
await svcItemApi.delete(currentSiteCode.value, row.id).send();
|
||||||
Message.success("已删除");
|
Message.success("已删除");
|
||||||
load();
|
tableRef.value?.reloadData(OperatorEnum.del);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -94,75 +103,41 @@ function remove(row: SvcItemPageItem) {
|
|||||||
async function syncDept() {
|
async function syncDept() {
|
||||||
const res: any = await svcItemApi.syncDeptName(currentSiteCode.value).send();
|
const res: any = await svcItemApi.syncDeptName(currentSiteCode.value).send();
|
||||||
Message.success(`已同步 ${res.updated} 条部门名快照`);
|
Message.success(`已同步 ${res.updated} 条部门名快照`);
|
||||||
load();
|
tableRef.value?.refreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 站点列表就绪后再拉首屏(siteCode 为定位键,避免空站点空跑一页)
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSites();
|
await loadSites();
|
||||||
load();
|
tableRef.value?.reloadData(OperatorEnum.sel);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page-wrapper">
|
||||||
<Card :bordered="false" dis-hover>
|
<div class="main-container">
|
||||||
<div class="bar">
|
<!-- 操作按钮区:主操作靠左、其余靠右(bsch应用页规范 §1) -->
|
||||||
<Select :model-value="currentSiteCode" style="width: 240px" @on-change="onSiteChange">
|
<div class="fix mb-12">
|
||||||
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">
|
<Button class="fl" type="primary" size="large" @click="edit()">新建事项</Button>
|
||||||
{{ s.name }}({{ s.siteCode }})
|
<Button class="fr" size="large" @click="syncDept">同步部门名</Button>
|
||||||
</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>
|
</div>
|
||||||
<Table :loading="loading" :columns="columns" :data="list">
|
|
||||||
<template #status="{ row }">
|
<!-- 搜索区:下拉靠左,关键字即输即搜不设查询按钮 -->
|
||||||
<Tag :color="statusColor(row.status)">{{ statusName(row.status) }}</Tag>
|
<div class="search-wrap fix">
|
||||||
</template>
|
<Select :model-value="currentSiteCode" class="search-wrap_sel" placeholder="站点"
|
||||||
<template #online="{ row }">
|
@on-change="(c: string) => { setSite(c); onSearch(); }">
|
||||||
<Tag v-if="row.onlineFlag === 1" color="green">可在线办理</Tag>
|
<Option v-for="s in sites" :key="s.siteCode" :value="s.siteCode">{{ s.name }}({{ s.siteCode }})</Option>
|
||||||
<span v-else>—</span>
|
</Select>
|
||||||
</template>
|
<Select v-model="dataParams.status" class="search-wrap_sel ml-12" clearable placeholder="状态" @on-change="onSearch">
|
||||||
<template #action="{ row }">
|
<Option v-for="(name, k) in ITEM_STATUS" :key="k" :value="Number(k)" :label="name" />
|
||||||
<Button size="small" type="text" @click="edit(row)">编辑</Button>
|
</Select>
|
||||||
<Button v-if="row.status !== 1" size="small" type="text" @click="publish(row)">发布</Button>
|
<Input class="fr" v-model="dataParams.keyword" placeholder="名称/编码" search suffix="ios-search"
|
||||||
<Button v-if="row.status === 1" size="small" type="text" @click="offline(row)">下架</Button>
|
style="width: 200px" @on-search="onSearch" @on-clear="onSearch" />
|
||||||
<Button size="small" type="text" style="color: #ed4014" @click="remove(row)">删除</Button>
|
</div>
|
||||||
</template>
|
|
||||||
</Table>
|
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight"
|
||||||
<Page
|
:autoLoad="false"
|
||||||
class="pager"
|
:formatResponse="(res: any) => ({ total: res.total, list: res.list })" />
|
||||||
:total="total"
|
</div>
|
||||||
:current="page"
|
|
||||||
:page-size="pageSize"
|
|
||||||
show-total
|
|
||||||
@on-change="(p: number) => { page = p; load(); }"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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