theme: 以 feature/theme-remote-p0 内容整体替换 main(主题通道化试点,非 merge)
- 本地 icons.css/layout.css 下线改走中央静态通道;新增 ThemeSwitcher.vue + useElementHeight.ts;
layout/main.ts/vite/menu 及各页适配对齐 feature 试点。
- 已知回退:本轮按 feature 覆盖,冲掉了 main 上 31ca694(/approval 可达性+modId 对齐) 与
1516eff(cc useTableHeight) 两处功能修复,待后续单独补回。
- 回滚锚点:archive/main-before-theme-20260924 = 替换前 main(1516eff)。
This commit is contained in:
Generated
+19
@@ -33,6 +33,7 @@
|
|||||||
"@unocss/preset-rem-to-px": "^66.6.0",
|
"@unocss/preset-rem-to-px": "^66.6.0",
|
||||||
"@unocss/preset-uno": "^66.6.0",
|
"@unocss/preset-uno": "^66.6.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@yunhai/vite-plugin-theme": "file:../../YschTheme/packages/vite-plugin",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.0",
|
||||||
@@ -43,6 +44,20 @@
|
|||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^1.8.27"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../../YschTheme/packages/vite-plugin": {
|
||||||
|
"name": "@yunhai/vite-plugin-theme",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dev": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"devDependencies": {
|
||||||
|
"tsup": "^8.3.5",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^5.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alova/shared": {
|
"node_modules/@alova/shared": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@alova/shared/-/shared-1.3.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@alova/shared/-/shared-1.3.4.tgz",
|
||||||
@@ -2576,6 +2591,10 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@yunhai/vite-plugin-theme": {
|
||||||
|
"resolved": "../../YschTheme/packages/vite-plugin",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz",
|
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"@unocss/preset-rem-to-px": "^66.6.0",
|
"@unocss/preset-rem-to-px": "^66.6.0",
|
||||||
"@unocss/preset-uno": "^66.6.0",
|
"@unocss/preset-uno": "^66.6.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@yunhai/vite-plugin-theme": "file:../../YschTheme/packages/vite-plugin",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"unocss": "^66.6.0",
|
"unocss": "^66.6.0",
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import type { IModule } from "@/store";
|
|||||||
*/
|
*/
|
||||||
export function getMenuList(): Promise<IModule[]> {
|
export function getMenuList(): Promise<IModule[]> {
|
||||||
const menu: IModule[] = [
|
const menu: IModule[] = [
|
||||||
{ id: 1000, modId: 1000, name: "已收到", page: "/" },
|
{ id: 1, modId: 1, name: "已收到", page: "/" },
|
||||||
{ id: 2000, modId: 2000, name: "已发送", page: "/list" },
|
{ id: 2, modId: 2, name: "已发送", page: "/list" },
|
||||||
{ id: 3000, modId: 3000, name: "全部通知", page: "/noticelist" },
|
{ id: 3, modId: 3, name: "全部通知", page: "/noticelist" },
|
||||||
{ id: 2100, modId: 2100, name: "待审核", page: "/approval" },
|
{ id: 4, modId: 4, name: "通知统计", page: "/stats" },
|
||||||
{ id: 4000, modId: 4000, name: "通知统计", page: "/stats" },
|
{ id: 21, modId: 21, name: "应用设置", page: "/setting" },
|
||||||
{ id: 5000, modId: 5000, name: "应用设置", page: "/setting" },
|
|
||||||
];
|
];
|
||||||
return Promise.resolve(menu);
|
return Promise.resolve(menu);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<path fill="#4aaff0" d="M512,585 C415,455 330,398 215,395 C185,395 160,415 160,450 L160,770 C160,815 190,838 240,845 C340,856 430,872 512,888 C594,872 684,856 784,845 C834,838 864,815 864,770 L864,450 C864,415 839,395 809,395 C694,398 609,455 512,585 Z"/>
|
||||||
|
<path fill="#4aaff0" d="M218,312 C335,292 462,352 514,462 C452,404 334,354 218,378 Z"/>
|
||||||
|
<circle fill="#4aaff0" cx="218" cy="345" r="33"/>
|
||||||
|
<path fill="#80d976" d="M508,462 C455,310 525,145 700,105 C758,250 668,425 508,462 Z"/>
|
||||||
|
<path fill="none" stroke="#4aaff0" stroke-width="44" stroke-linecap="round" d="M210,935 Q285.5,881 361,935 T512,935 T663,935 T814,935"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 701 B |
@@ -252,6 +252,33 @@ function fileUrl(id: number) {
|
|||||||
|
|
||||||
/* ============================ Mock 定义 ============================ */
|
/* ============================ Mock 定义 ============================ */
|
||||||
|
|
||||||
|
/* ---------- 我的待审核(flow-center 代理)内存数据(刷新/改本文件即重置为初始 6 条) ---------- */
|
||||||
|
const approvalTodos = [
|
||||||
|
{ taskId: 5001, instanceId: 8001, instanceVersion: 1, bizType: "notice", bizId: "1", bizTitle: "关于开展2026年秋季学期教学检查的通知", bizSnapshot: JSON.stringify({ summary: "定于下周开展秋季学期教学检查,请各教研组提前准备教案、听课记录。" }), nodeId: "n_dept", nodeName: "部门审核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-23T09:12:00" },
|
||||||
|
{ taskId: 5002, instanceId: 8002, instanceVersion: 1, bizType: "notice", bizId: "2", bizTitle: "校园网络安全培训通知", bizSnapshot: JSON.stringify({ summary: "本周五下午 15:00 在报告厅开展网络安全培训,请全体教职工参加。" }), nodeId: "n_school", nodeName: "校级审核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-23T10:40:00" },
|
||||||
|
{ taskId: 5003, instanceId: 8003, instanceVersion: 2, bizType: "notice", bizId: "5", bizTitle: "期中考试考务工作安排", bizSnapshot: JSON.stringify({ summary: "期中考试定于第 10 周,请各年级上报监考安排。" }), nodeId: "n_dept", nodeName: "部门审核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-22T15:05:00" },
|
||||||
|
{ taskId: 5004, instanceId: 8004, instanceVersion: 1, bizType: "notice", bizId: "7", bizTitle: "教师职称评审材料公示", bizSnapshot: JSON.stringify({ summary: "本年度职称评审材料已汇总,公示期 5 个工作日。" }), nodeId: "n_office", nodeName: "办公室复核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-22T08:30:00" },
|
||||||
|
{ taskId: 5005, instanceId: 8005, instanceVersion: 1, bizType: "notice", bizId: "9", bizTitle: "2027 年寒假放假通知", bizSnapshot: JSON.stringify({ summary: "寒假自 1 月 20 日起,下学期 2 月 15 日报到。" }), nodeId: "n_dept", nodeName: "部门审核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-21T16:20:00" },
|
||||||
|
{ taskId: 5006, instanceId: 8006, instanceVersion: 1, bizType: "notice", bizId: "12", bizTitle: "校园运动会报名通知", bizSnapshot: JSON.stringify({ summary: "秋季运动会报名截止本周五,请各班级汇总上报。" }), nodeId: "n_school", nodeName: "校级审核", assigneeId: 186532, assigneeName: "朱皓东", arriveTime: "2026-09-21T11:00:00" },
|
||||||
|
];
|
||||||
|
const approvalInstances = [
|
||||||
|
{ instanceId: 8001, status: 10, version: 1, bizType: "notice", bizId: "1", bizTitle: "关于开展2026年秋季学期教学检查的通知", bizUrl: null, submitterName: "刘文娟", submitTime: "2026-09-20T09:00:00", finishTime: null, currentNodes: "部门审核" },
|
||||||
|
{ instanceId: 8002, status: 20, version: 1, bizType: "notice", bizId: "2", bizTitle: "校园网络安全培训通知", bizUrl: null, submitterName: "黄瑶", submitTime: "2026-09-19T10:00:00", finishTime: "2026-09-21T14:00:00", currentNodes: "" },
|
||||||
|
{ instanceId: 8003, status: 30, version: 2, bizType: "notice", bizId: "5", bizTitle: "期中考试考务工作安排", bizUrl: null, submitterName: "张明", submitTime: "2026-09-18T08:30:00", finishTime: "2026-09-19T09:10:00", currentNodes: "" },
|
||||||
|
{ instanceId: 8004, status: 40, version: 1, bizType: "notice", bizId: "7", bizTitle: "教师职称评审材料公示", bizUrl: null, submitterName: "李强", submitTime: "2026-09-17T16:00:00", finishTime: "2026-09-17T16:30:00", currentNodes: "" },
|
||||||
|
{ instanceId: 8005, status: 10, version: 1, bizType: "notice", bizId: "9", bizTitle: "2027 年寒假放假通知", bizUrl: null, submitterName: "王芳", submitTime: "2026-09-16T09:00:00", finishTime: null, currentNodes: "校级审核" },
|
||||||
|
{ instanceId: 8006, status: 20, version: 1, bizType: "notice", bizId: "12", bizTitle: "校园运动会报名通知", bizUrl: null, submitterName: "陈垠", submitTime: "2026-09-15T10:00:00", finishTime: "2026-09-16T11:00:00", currentNodes: "" },
|
||||||
|
];
|
||||||
|
/** 组装 flow-center BasePageResult{total,content,pageIndex,pageSize,totalPages,hasNext} */
|
||||||
|
function approvalPage<T>(all: T[], query: any) {
|
||||||
|
const pageIndex = Number(query?.page || 1);
|
||||||
|
const pageSize = Number(query?.size || 10);
|
||||||
|
const start = (pageIndex - 1) * pageSize;
|
||||||
|
const total = all.length;
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||||
|
return { total, content: all.slice(start, start + pageSize), pageIndex, pageSize, totalPages, hasNext: pageIndex < totalPages };
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
/* ---------- 布局 / 用户 ---------- */
|
/* ---------- 布局 / 用户 ---------- */
|
||||||
{
|
{
|
||||||
@@ -272,13 +299,12 @@ export default [
|
|||||||
response: () =>
|
response: () =>
|
||||||
ok([
|
ok([
|
||||||
// icon:用户中心模块管理配置的 YschIcons 图标名,layout 渲染为 iconfont icon-<名称>
|
// icon:用户中心模块管理配置的 YschIcons 图标名,layout 渲染为 iconfont icon-<名称>
|
||||||
// id/modId 与平台模块管理的「配置 ID」一致(对照 /base/GetUserMod 现行数据)
|
{ id: 1, modId: 1, name: "已收到", parentId: 0, page: "/", icon: "sjx", orderNumber: 1, usable: true, children: [] },
|
||||||
{ id: 1000, modId: 1000, name: "已收到", parentId: 0, page: "/", icon: "sjx", orderNumber: 1, usable: true, children: [] },
|
{ id: 2, modId: 2, name: "已发送", parentId: 0, page: "/list", icon: "fasong", orderNumber: 2, usable: true, children: [] },
|
||||||
{ id: 2000, modId: 2000, name: "已发送", parentId: 0, page: "/list", icon: "fasong", orderNumber: 2, usable: true, children: [] },
|
{ id: 3, modId: 3, name: "全部通知", parentId: 0, page: "/noticelist", icon: "liebiao", orderNumber: 3, usable: true, children: [] },
|
||||||
{ id: 3000, modId: 3000, name: "全部通知", parentId: 0, page: "/noticelist", icon: "liebiao", orderNumber: 3, usable: true, children: [] },
|
{ id: 4, modId: 4, name: "通知统计", parentId: 0, page: "/stats", icon: "tongji", orderNumber: 4, usable: true, children: [] },
|
||||||
{ id: 2100, modId: 2100, name: "待审核", parentId: 0, page: "/approval", icon: "", orderNumber: 4, usable: true, children: [] },
|
{ id: 23, modId: 23, name: "待审核", parentId: 0, page: "/approval", icon: "shenpi", orderNumber: 5, usable: true, children: [] },
|
||||||
{ id: 4000, modId: 4000, name: "通知统计", parentId: 0, page: "/stats", icon: "tongji", orderNumber: 5, usable: true, children: [] },
|
{ id: 21, modId: 21, name: "应用设置", parentId: 0, page: "/setting", icon: "shezhi", orderNumber: 6, usable: true, children: [] },
|
||||||
{ id: 5000, modId: 5000, name: "应用设置", parentId: 0, page: "/setting", icon: "shezhi", orderNumber: 6, usable: true, children: [] },
|
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -724,4 +750,38 @@ export default [
|
|||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* ---------- 我的待审核(NoticeApproval → /api/notice/approval/**) ---------- */
|
||||||
|
{
|
||||||
|
url: "/api/notice/approval/Todo",
|
||||||
|
method: "get",
|
||||||
|
response: ({ query }: any) => ok(approvalPage(approvalTodos, query)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/notice/approval/Instances",
|
||||||
|
method: "get",
|
||||||
|
response: ({ query }: any) => {
|
||||||
|
const status = Number(query?.status || 0);
|
||||||
|
const list = status ? approvalInstances.filter((i) => i.status === status) : approvalInstances;
|
||||||
|
return ok(approvalPage(list, query));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/notice/approval/Approve",
|
||||||
|
method: "post",
|
||||||
|
response: ({ query }: any) => {
|
||||||
|
const idx = approvalTodos.findIndex((t) => t.taskId === Number(query?.taskId));
|
||||||
|
if (idx >= 0) approvalTodos.splice(idx, 1);
|
||||||
|
return ok(null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/api/notice/approval/Reject",
|
||||||
|
method: "post",
|
||||||
|
response: ({ query }: any) => {
|
||||||
|
const idx = approvalTodos.findIndex((t) => t.taskId === Number(query?.taskId));
|
||||||
|
if (idx >= 0) approvalTodos.splice(idx, 1);
|
||||||
|
return ok(null);
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -27,7 +27,7 @@
|
|||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
background: #5979ea;
|
background: var(--brand, #577afb);
|
||||||
height: 15px;
|
height: 15px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
h4.second-title {
|
h4.second-title {
|
||||||
@extend .base-subtitle;
|
@extend .base-subtitle;
|
||||||
&::before {
|
&::before {
|
||||||
background: #5979ea;
|
background: var(--brand, #577afb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ h4.subtitle {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
li {
|
li {
|
||||||
height: 42px;
|
height: 42px;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid var(--line, #e9edf5);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -87,8 +87,8 @@ h4.subtitle {
|
|||||||
}
|
}
|
||||||
li.active {
|
li.active {
|
||||||
// background: #f3f3f3;
|
// background: #f3f3f3;
|
||||||
color: #7892ed;
|
color: var(--brand-2, #7b8ffd);
|
||||||
border-right: 3px solid #5979ea;
|
border-right: 3px solid var(--brand, #577afb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ h4.subtitle {
|
|||||||
background-position: top center;
|
background-position: top center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border: none;
|
border: none;
|
||||||
color: #577afb;
|
color: var(--brand, #577afb);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
color: $color-warning !important;
|
color: $color-warning !important;
|
||||||
}
|
}
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: $color-primary !important;
|
color: var(--brand, #577afb) !important;
|
||||||
}
|
}
|
||||||
.text-normal {
|
.text-normal {
|
||||||
color: #404042 !important;
|
color: #404042 !important;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/*scss变量*/
|
/*scss变量*/
|
||||||
|
|
||||||
// 主题色
|
// 主题色(品牌主色不在此定义:由平台主题令牌 var(--brand) 经中央静态通道下发,
|
||||||
|
// 本文件只留业务语义色——成功/警告/错误不随主题档位变化)
|
||||||
$color-error: #e8684a;
|
$color-error: #e8684a;
|
||||||
$color-success: #45b972;
|
$color-success: #45b972;
|
||||||
$color-warning: #e9b954;
|
$color-warning: #e9b954;
|
||||||
$color-primary: #5979ea;
|
|
||||||
|
|
||||||
// 文字色
|
// 文字色
|
||||||
$color-black: #404042;
|
$color-black: #404042;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="table-region" ref="regionRef">
|
||||||
<Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据"
|
<Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据"
|
||||||
class="link-table table-bottom-box" @on-selection-change="selectChange">
|
class="link-table table-bottom-box" @on-selection-change="selectChange">
|
||||||
<template #loading>
|
<template #loading>
|
||||||
@@ -8,6 +9,7 @@
|
|||||||
<slot :name="slotName" v-bind="slotProps" />
|
<slot :name="slotName" v-bind="slotProps" />
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="page-panel">
|
<div class="page-panel">
|
||||||
<div class="fl ml-15 align-middle">
|
<div class="fl ml-15 align-middle">
|
||||||
@@ -22,7 +24,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useSlots } from 'vue'
|
import { computed, ref, useSlots } from 'vue'
|
||||||
import Utils from '@/utils'
|
import { useElementHeight } from '@/utils/hook/useElementHeight'
|
||||||
import { OperatorEnum } from '@/types'
|
import { OperatorEnum } from '@/types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -44,7 +46,10 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
const tableH = ref(props.tableHeight ?? Utils.useTableHeight())
|
const regionRef = ref<HTMLElement | null>(null)
|
||||||
|
const measuredH = useElementHeight(regionRef)
|
||||||
|
// 显式传 tableHeight 则尊重之;否则实测 .table-region(flex:1)高度喂固定表头,首帧未测出前用 undefined(auto)
|
||||||
|
const tableH = computed(() => props.tableHeight ?? (measuredH.value || undefined))
|
||||||
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[]>([])
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/* 管理端配色切换器(yh-theme 定稿 07-seam-pure 顶栏「配色」胶囊,方案 §6.5)。
|
||||||
|
* 状态唯一来源:utils/theme(localStorage 持久化 + <html> 档位类名),与门户皮肤包正交。 */
|
||||||
|
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
|
import {
|
||||||
|
loadThemeState,
|
||||||
|
saveThemeState,
|
||||||
|
applyThemeState,
|
||||||
|
type ThemeName,
|
||||||
|
type ThemeState,
|
||||||
|
} from "@/utils/theme";
|
||||||
|
|
||||||
|
const THEME_META: { name: ThemeName; label: string; color: string }[] = [
|
||||||
|
{ name: "blue", label: "原版靛蓝", color: "#577afb" },
|
||||||
|
{ name: "ocean", label: "云海蓝", color: "#2f7ce0" },
|
||||||
|
{ name: "ink", label: "石墨墨", color: "#3d4d63" },
|
||||||
|
{ name: "violet", label: "靛紫", color: "#6d5ae0" },
|
||||||
|
{ name: "teal", label: "松石绿", color: "#0d9488" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const wrapEl = ref<HTMLElement | null>(null);
|
||||||
|
const open = ref(false);
|
||||||
|
const state = ref<ThemeState>(loadThemeState());
|
||||||
|
/* 挂载即把存储态落到 <html>(与 main.ts initTheme 同源幂等;组件晚于引导挂载时也自洽) */
|
||||||
|
applyThemeState(state.value);
|
||||||
|
|
||||||
|
function commit(patch: Partial<ThemeState>) {
|
||||||
|
state.value = { ...state.value, ...patch };
|
||||||
|
applyThemeState(state.value);
|
||||||
|
saveThemeState(state.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(ev: MouseEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
open.value = !open.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 切换器外任意点击即收起(弹层内部点击由 wrap 判定放过) */
|
||||||
|
function onDocClick(ev: MouseEvent) {
|
||||||
|
if (open.value && wrapEl.value && !wrapEl.value.contains(ev.target as Node)) {
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => document.addEventListener("click", onDocClick, true));
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("click", onDocClick, true));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="wrapEl" class="theme-wrap">
|
||||||
|
<button type="button" class="theme-btn" :class="{ on: open }" @click="toggle">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 3a9 9 0 1 0 0 18c1.2 0 1.9-.9 1.9-1.8 0-.6-.3-1-.5-1.4-.2-.4-.4-.8-.4-1.3 0-.9.8-1.7 1.8-1.7H17a4 4 0 0 0 4-4c0-4.3-4-7.8-9-7.8z"/>
|
||||||
|
<circle cx="7.6" cy="11.2" r="1.1"/><circle cx="11" cy="7.6" r="1.1"/><circle cx="15.4" cy="8.6" r="1.1"/>
|
||||||
|
</svg>
|
||||||
|
<span>配色</span>
|
||||||
|
<svg class="caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M5.5 9.5l6.5 6.5 6.5-6.5"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="theme-pop" :class="{ open }">
|
||||||
|
<p class="tp-head">主题色</p>
|
||||||
|
<div class="tp-grid">
|
||||||
|
<button
|
||||||
|
v-for="t in THEME_META"
|
||||||
|
:key="t.name"
|
||||||
|
type="button"
|
||||||
|
class="theme-opt"
|
||||||
|
:class="{ active: state.theme === t.name }"
|
||||||
|
@click="commit({ theme: t.name })"
|
||||||
|
>
|
||||||
|
<i :style="{ background: t.color }"></i>{{ t.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="tp-sep"></div>
|
||||||
|
<p class="tp-head">侧栏样式</p>
|
||||||
|
<label class="tp-row">
|
||||||
|
<span>侧栏跟随顶栏色</span>
|
||||||
|
<span class="sw">
|
||||||
|
<input type="checkbox" :checked="state.sideFollow" @change="commit({ sideFollow: ($event.target as HTMLInputElement).checked })" />
|
||||||
|
<i></i>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="tp-row" style="margin-top: 9px">
|
||||||
|
<span>侧栏深色(关=浅色)</span>
|
||||||
|
<span class="sw">
|
||||||
|
<input type="checkbox" :checked="state.sideDeep" @change="commit({ sideDeep: ($event.target as HTMLInputElement).checked })" />
|
||||||
|
<i></i>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div class="tp-sep"></div>
|
||||||
|
<label class="tp-row">
|
||||||
|
<span>顶部过渡色(渐变)</span>
|
||||||
|
<span class="sw">
|
||||||
|
<input type="checkbox" :checked="state.gradOn" @change="commit({ gradOn: ($event.target as HTMLInputElement).checked })" />
|
||||||
|
<i></i>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 皮肤规格照搬定稿 07-seam-pure.html .theme-wrap/.theme-btn/.theme-pop 段 */
|
||||||
|
.theme-wrap {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.theme-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.34);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.14);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12.5px;
|
||||||
|
transition: background 0.16s, border-color 0.16s;
|
||||||
|
}
|
||||||
|
.theme-btn:hover,
|
||||||
|
.theme-btn.on {
|
||||||
|
background: rgba(255, 255, 255, 0.26);
|
||||||
|
border-color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
.theme-btn svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: block;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 1.7;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.theme-btn svg circle {
|
||||||
|
fill: currentColor;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
.theme-btn .caret {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
opacity: 0.85;
|
||||||
|
transition: transform 0.18s;
|
||||||
|
}
|
||||||
|
.theme-btn.on .caret {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
.theme-pop {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
right: 0;
|
||||||
|
width: 224px;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--text, #1e2635);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--line, #e9edf5);
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 12px 34px -10px rgba(16, 24, 40, 0.28), 0 2px 6px rgba(16, 24, 40, 0.08);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-6px);
|
||||||
|
transition: 0.18s;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
.theme-pop.open {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
.tp-head {
|
||||||
|
font-size: 11.5px;
|
||||||
|
color: var(--text-3, #98a1b2);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin: 0 2px 8px;
|
||||||
|
}
|
||||||
|
.tp-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.theme-opt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--line, #e9edf5);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--text-2, #5a6478);
|
||||||
|
font-size: 12px;
|
||||||
|
transition: 0.15s;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.theme-opt:hover {
|
||||||
|
border-color: #cfd7e6;
|
||||||
|
background: #fafbfe;
|
||||||
|
}
|
||||||
|
.theme-opt i {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
.theme-opt.active {
|
||||||
|
border-color: var(--brand, #577afb);
|
||||||
|
color: var(--brand, #577afb);
|
||||||
|
background: var(--brand-soft, #eef2ff);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.tp-sep {
|
||||||
|
height: 1px;
|
||||||
|
background: var(--line, #e9edf5);
|
||||||
|
margin: 12px 0 10px;
|
||||||
|
}
|
||||||
|
.tp-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--text-2, #5a6478);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
.sw {
|
||||||
|
position: relative;
|
||||||
|
width: 38px;
|
||||||
|
height: 21px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.sw input {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.sw i {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #d7dce6;
|
||||||
|
transition: background 0.2s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.sw i::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 2.5px;
|
||||||
|
left: 2.5px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 3px rgba(16, 24, 40, 0.28);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.sw input:checked + i {
|
||||||
|
background: var(--brand, #577afb);
|
||||||
|
}
|
||||||
|
.sw input:checked + i::after {
|
||||||
|
transform: translateX(17px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+21
-12
@@ -3,6 +3,7 @@ import { ref, onBeforeMount, watch, computed } from "vue";
|
|||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
import { resolveMenuPage } from "@/utils/menuRoute";
|
import { resolveMenuPage } from "@/utils/menuRoute";
|
||||||
|
import ThemeSwitcher from "@/components/ThemeSwitcher.vue";
|
||||||
|
|
||||||
const props = defineProps<{ layoutData: Record<string, any> }>();
|
const props = defineProps<{ layoutData: Record<string, any> }>();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -129,7 +130,7 @@ const childChange = (index: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** 菜单图标:读取用户中心模块管理配置的 YschIcons 图标名(GetUserMod 的 icon 字段,如 list → icon-list)。
|
/** 菜单图标:读取用户中心模块管理配置的 YschIcons 图标名(GetUserMod 的 icon 字段,如 list → icon-list)。
|
||||||
* 本地 icons.css 即 ysch-icons 掩码版(基类 iconfont,类名 icon-<名称>);
|
* icons.css(ysch-icons 掩码版,基类 iconfont,类名 icon-<名称>)现由中央静态通道下发;
|
||||||
* 平台约定留空不下发图标,此时返回空串由模板 v-if 不渲染 */
|
* 平台约定留空不下发图标,此时返回空串由模板 v-if 不渲染 */
|
||||||
const getMenuIcon = (item?: any): string => {
|
const getMenuIcon = (item?: any): string => {
|
||||||
const icon = String(item?.icon || "").trim();
|
const icon = String(item?.icon || "").trim();
|
||||||
@@ -149,16 +150,13 @@ function linkHomePage() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-wrap">
|
<div class="layout-wrap">
|
||||||
<header class="header-container fix">
|
<header class="header-container fix">
|
||||||
<div class="fl align-middle icon-box">
|
<!-- 云海 logo 徽标(结构与 Cms SchoolLayout 对齐,皮肤见通道 base.layout.css .logo-badge) -->
|
||||||
<div title="首页" class="icon-btn" @click="linkHomePage">
|
<span class="logo-badge fl"><img src="@/assets/image/yunhai-logo.svg" alt="云海" /></span>
|
||||||
<i class="iconfont icon-shouye"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="delimiter fl align-middle"></div>
|
<div class="delimiter fl align-middle"></div>
|
||||||
<h4 class="fl align-middle">{{ appName }}</h4>
|
<h4 class="fl align-middle">{{ appName }}</h4>
|
||||||
<div class="school-bg"></div>
|
<div class="school-bg"></div>
|
||||||
<div class="header-aside fr">
|
<div class="header-aside fr">
|
||||||
<img :src="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
|
<img :src="userInfo.avatar" v-if="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
|
||||||
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
|
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
|
||||||
{{ userInfo.name }}
|
{{ userInfo.name }}
|
||||||
</a>
|
</a>
|
||||||
@@ -167,6 +165,8 @@ function linkHomePage() {
|
|||||||
<i class="iconfont icon-logout"></i>
|
<i class="iconfont icon-logout"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 配色切换器(fr 顺序:DOM 后置即位于用户胶囊左侧) -->
|
||||||
|
<div class="theme-cap fr"><ThemeSwitcher /></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="left-menu" v-if="!isAlone">
|
<div class="left-menu" v-if="!isAlone">
|
||||||
<div class="menu-top-box"></div>
|
<div class="menu-top-box"></div>
|
||||||
@@ -210,7 +210,7 @@ function linkHomePage() {
|
|||||||
<a href="javascript:;" @click="linkHomePage">
|
<a href="javascript:;" @click="linkHomePage">
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<span class="crumb-home">
|
<span class="crumb-home">
|
||||||
<i class="iconfont icon-category"></i>
|
<i class="iconfont icon-shouye"></i>
|
||||||
首页
|
首页
|
||||||
</span>
|
</span>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
@@ -250,14 +250,23 @@ function linkHomePage() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 仅覆盖/补充必要的动态样式,其余样式由全局 layout.css 提供 */
|
/* 仅覆盖/补充必要的动态样式,其余样式由中央静态通道的 base.layout.css(骨架 + 皮肤)提供 */
|
||||||
/* 菜单图标(本地 bsch-icons 掩码类)与文字间距 */
|
/* 菜单图标(通道下发的 ysch-icons 掩码类)与文字间距 */
|
||||||
.left-menu a .iconfont {
|
.left-menu a .iconfont {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
/* 面包屑「首页」垂直居中:layout.css 遗留的 .path-nav .icon-category{position:relative;top:1px}
|
/* 顶栏已由通道皮肤改为 flex 垂直居中(skin.src.css .header-container),
|
||||||
|
这里不再用 float 时代的 13px 顶偏移补偿,仅留右侧间距;徽标大小/圆底/阴影属皮肤 */
|
||||||
|
.header-container .logo-badge {
|
||||||
|
margin: 0 10px 0 0;
|
||||||
|
}
|
||||||
|
.theme-cap {
|
||||||
|
height: var(--topbar-h);
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
/* 面包屑「首页」垂直居中:骨架遗留的 .path-nav .icon-category{position:relative;top:1px}
|
||||||
是给老 iconfont 字体字形做的基线补偿,换成 ysch mask 的 1em 方块后反而把图标顶偏;
|
是给老 iconfont 字体字形做的基线补偿,换成 ysch mask 的 1em 方块后反而把图标顶偏;
|
||||||
这里用 inline-flex 真居中并关掉该补偿(不动压缩产物 layout.css,覆盖收在本组件内) */
|
这里用 inline-flex 真居中并关掉该补偿(不动通道产物,覆盖收在本组件内) */
|
||||||
.crumb-home {
|
.crumb-home {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+2
-4
@@ -13,10 +13,8 @@ import mittBus from "@/utils/mitt";
|
|||||||
import "view-ui-plus/dist/styles/viewuiplus.css";
|
import "view-ui-plus/dist/styles/viewuiplus.css";
|
||||||
|
|
||||||
import "@/assets/styles/main.scss";
|
import "@/assets/styles/main.scss";
|
||||||
// 本地化的布局/主题样式(原远程 base.layout.beta / _iview_override / default-theme 合并)
|
// 布局/主题/图标(base.layout.css + iview.override.css + icons.css)改由中央静态通道下发:
|
||||||
import "@/assets/styles/layout.css";
|
// yhTheme() 在入口末位注入 virtual:yh-theme-loader,运行时按 manifest 版本挂 <link>
|
||||||
// 规范 iconfont 图标本地实现(icon-browse / icon-edit / icon-ashbin 等,平台主题不含字体)
|
|
||||||
import "@/assets/styles/icons.css";
|
|
||||||
//导入自定义指令
|
//导入自定义指令
|
||||||
import directive from "@/utils/directive/index";
|
import directive from "@/utils/directive/index";
|
||||||
|
|
||||||
|
|||||||
+14
-16
@@ -9,12 +9,10 @@ import type { RouteRecordRaw } from "vue-router";
|
|||||||
* - /list/detail?serid=&type=、/noticelist/detail?serid=&type=
|
* - /list/detail?serid=&type=、/noticelist/detail?serid=&type=
|
||||||
* type: 0=编辑视角(不标读) 1=普通查看(标读)
|
* type: 0=编辑视角(不标读) 1=普通查看(标读)
|
||||||
*
|
*
|
||||||
* modId 是菜单 ↔ 路由的稳定契约(对照后端 /base/GetUserMod 现行数据,即用户中心
|
* modId 是菜单 ↔ 路由的稳定契约(对照后端 GetUserMod 现行数据):
|
||||||
* 模块管理里的配置 ID):
|
* 1=已收到 2=已发送 3=全部通知 4=通知统计 21=应用设置
|
||||||
* 1000=已收到 2000=已发送 3000=全部通知 2100=待审核 4000=通知统计 5000=应用设置
|
* 发通知为「已发送」的深层子页(modId 22 不在菜单树,meta.parentId=2 关联高亮),
|
||||||
* 深层子页不在菜单树,编码沿用 UserBase 范式「父模块 ×10 + 序号」并靠 meta.parentId
|
* 与 UserBase「应用管理>应用类型管理」范式一致。
|
||||||
* 关联高亮:/list/new=20001、/list/detail=20002(父 2000),/noticelist/detail=30001(父 3000)。
|
|
||||||
* 深层页编码不得与任何平台一级模块 ID 重复。
|
|
||||||
*/
|
*/
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
// 已收通知(收件箱):后端菜单 page 配置为 "/",即应用入口页
|
// 已收通知(收件箱):后端菜单 page 配置为 "/",即应用入口页
|
||||||
@@ -22,14 +20,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: "/",
|
path: "/",
|
||||||
name: "notice_accept",
|
name: "notice_accept",
|
||||||
component: () => import("@/views/notice/NoticeAccept.vue"),
|
component: () => import("@/views/notice/NoticeAccept.vue"),
|
||||||
meta: { title: "已收到", modId: 1000 },
|
meta: { title: "已收到", modId: 1 },
|
||||||
},
|
},
|
||||||
// 已发送
|
// 已发送
|
||||||
{
|
{
|
||||||
path: "/list",
|
path: "/list",
|
||||||
name: "notice_list",
|
name: "notice_list",
|
||||||
component: () => import("@/views/notice/NoticeList.vue"),
|
component: () => import("@/views/notice/NoticeList.vue"),
|
||||||
meta: { title: "已发送", modId: 2000 },
|
meta: { title: "已发送", modId: 2 },
|
||||||
},
|
},
|
||||||
// 发通知(新建/编辑/转发/模板):已发送的深层子页,不在菜单里,
|
// 发通知(新建/编辑/转发/模板):已发送的深层子页,不在菜单里,
|
||||||
// layout.vue 按.meta.parentId 推导面包屑「已发送>发通知」并高亮父菜单
|
// layout.vue 按.meta.parentId 推导面包屑「已发送>发通知」并高亮父菜单
|
||||||
@@ -37,14 +35,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: "/list/new",
|
path: "/list/new",
|
||||||
name: "new_notice",
|
name: "new_notice",
|
||||||
component: () => import("@/views/notice/NewNotice.vue"),
|
component: () => import("@/views/notice/NewNotice.vue"),
|
||||||
meta: { title: "发通知", modId: 20001, parentId: 2000 },
|
meta: { title: "发通知", modId: 22, parentId: 2 },
|
||||||
},
|
},
|
||||||
// 全部通知(管理端)
|
// 全部通知(管理端)
|
||||||
{
|
{
|
||||||
path: "/noticelist",
|
path: "/noticelist",
|
||||||
name: "notice_manage",
|
name: "notice_manage",
|
||||||
component: () => import("@/views/notice/NoticeList.vue"),
|
component: () => import("@/views/notice/NoticeList.vue"),
|
||||||
meta: { title: "全部通知", modId: 3000 },
|
meta: { title: "全部通知", modId: 3 },
|
||||||
},
|
},
|
||||||
// 通知详情(同一组件挂两个路由,面包屑/菜单高亮由 meta.parentId 确定父节点:
|
// 通知详情(同一组件挂两个路由,面包屑/菜单高亮由 meta.parentId 确定父节点:
|
||||||
// 已发送下 /list/detail,全部通知下 /noticelist/detail;type 区分是否标读)
|
// 已发送下 /list/detail,全部通知下 /noticelist/detail;type 区分是否标读)
|
||||||
@@ -52,34 +50,34 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: "/list/detail",
|
path: "/list/detail",
|
||||||
name: "notice_list_detail",
|
name: "notice_list_detail",
|
||||||
component: () => import("@/views/notice/NoticeDetails.vue"),
|
component: () => import("@/views/notice/NoticeDetails.vue"),
|
||||||
meta: { title: "通知详情", modId: 20002, parentId: 2000 },
|
meta: { title: "通知详情", modId: 23, parentId: 2 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/noticelist/detail",
|
path: "/noticelist/detail",
|
||||||
name: "notice_manage_detail",
|
name: "notice_manage_detail",
|
||||||
component: () => import("@/views/notice/NoticeDetails.vue"),
|
component: () => import("@/views/notice/NoticeDetails.vue"),
|
||||||
meta: { title: "通知详情", modId: 30001, parentId: 3000 },
|
meta: { title: "通知详情", modId: 24, parentId: 3 },
|
||||||
},
|
},
|
||||||
// 通知统计(旧版行为:整页跳转外部统计系统)
|
// 通知统计(旧版行为:整页跳转外部统计系统)
|
||||||
{
|
{
|
||||||
path: "/stats",
|
path: "/stats",
|
||||||
name: "notice_stats",
|
name: "notice_stats",
|
||||||
component: () => import("@/views/notice/NoticeStats.vue"),
|
component: () => import("@/views/notice/NoticeStats.vue"),
|
||||||
meta: { title: "通知统计", modId: 4000 },
|
meta: { title: "通知统计", modId: 4 },
|
||||||
},
|
},
|
||||||
// 应用设置(发文部门 + 自定义组)
|
// 应用设置(发文部门 + 自定义组)
|
||||||
{
|
{
|
||||||
path: "/setting",
|
path: "/setting",
|
||||||
name: "notice_setting",
|
name: "notice_setting",
|
||||||
component: () => import("@/views/notice/NoticeSetting.vue"),
|
component: () => import("@/views/notice/NoticeSetting.vue"),
|
||||||
meta: { title: "应用设置", modId: 5000 },
|
meta: { title: "应用设置", modId: 21 },
|
||||||
},
|
},
|
||||||
// 待审核(应用内审批;平台模块管理已挂一级菜单,配置 ID = 2100,path = /approval)
|
// 我的待审核(应用内审批;modId 23 深层页不在菜单树,菜单挂载待后端 GetUserMod 配置)
|
||||||
{
|
{
|
||||||
path: "/approval",
|
path: "/approval",
|
||||||
name: "notice_approval",
|
name: "notice_approval",
|
||||||
component: () => import("@/views/notice/NoticeApproval.vue"),
|
component: () => import("@/views/notice/NoticeApproval.vue"),
|
||||||
meta: { title: "我的待审核", modId: 2100 },
|
meta: { title: "我的待审核", modId: 23, parentId: 2 },
|
||||||
},
|
},
|
||||||
|
|
||||||
// 抄送给我的(流程 NOTIFY 知会;modId 25 深层页不在菜单树,菜单挂载待后端 GetUserMod 配置;
|
// 抄送给我的(流程 NOTIFY 知会;modId 25 深层页不在菜单树,菜单挂载待后端 GetUserMod 配置;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ref, onMounted, onBeforeUnmount, watch, type Ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实测某容器元素的可用高度(px),随尺寸变化实时更新。
|
||||||
|
*
|
||||||
|
* 用途:表格页把 <Table> 放进 flex:1 的 .table-region 里,用本组合式量出该区域
|
||||||
|
* 的真实高度喂给 view-ui 的 `:height`(固定表头需要一个数值高度),从而彻底替代
|
||||||
|
* 旧的 useTableHeight()「视口高 - 一堆写死常量」的算法——加/删工具条、改顶栏高
|
||||||
|
* 都不再需要同步改任何魔法数字。
|
||||||
|
*
|
||||||
|
* @param el 目标容器 ref(模板里 ref 绑定的那个元素)
|
||||||
|
* @returns 响应式高度(px);挂载测量前为 0,调用方可 `:height="h || undefined"` 兜底
|
||||||
|
*/
|
||||||
|
export function useElementHeight(el: Ref<HTMLElement | null>): Ref<number> {
|
||||||
|
const height = ref(0);
|
||||||
|
let ro: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
const measure = () => {
|
||||||
|
if (el.value) height.value = Math.floor(el.value.clientHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
const observe = () => {
|
||||||
|
ro?.disconnect();
|
||||||
|
if (!el.value || typeof ResizeObserver === "undefined") return;
|
||||||
|
measure();
|
||||||
|
ro = new ResizeObserver(measure);
|
||||||
|
ro.observe(el.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(observe);
|
||||||
|
// v-if / v-show 切换导致容器重建时,重新挂观察
|
||||||
|
watch(el, observe);
|
||||||
|
onBeforeUnmount(() => ro?.disconnect());
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
@@ -423,11 +423,11 @@ async function saveEditAndLeave(data: NoticeRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.editor-wrap {
|
.editor-wrap {
|
||||||
border: 1px solid #dcdee2;
|
border: 1px solid var(--line);
|
||||||
border-radius: 4px;
|
border-radius: var(--r-s);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
.editor-toolbar {
|
.editor-toolbar {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
.editor-content {
|
.editor-content {
|
||||||
:deep(.w-e-text-container) {
|
:deep(.w-e-text-container) {
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ function shortDate(date: string) {
|
|||||||
/* 时间显示(列表 MM/dd HH:mm,同旧版) */</script>
|
/* 时间显示(列表 MM/dd HH:mm,同旧版) */</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper yh-fill">
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<div class="accept-wrap">
|
<div class="accept-wrap">
|
||||||
<!-- 左侧通知列表 -->
|
<!-- 左侧通知列表 -->
|
||||||
@@ -262,188 +262,279 @@ function shortDate(date: string) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 根容器 page-wrapper > main-container:内容区可视高度 = 100vh - 112(头部+面包屑) - 24(容器底padding) */
|
/* 已收到页 · 对齐原型 07-seam-pure.html 的 .nt-split(灰底上并排两张白卡)。
|
||||||
.accept-wrap {
|
通道把 .main-container 画成单张白卡,这里压平它、让两张卡直接坐灰底;
|
||||||
display: flex;
|
外层 .page-wrapper.yh-fill 由通道转 flex 纵向铺满,卡片 flex:1 撑满、四周留白 14/18/24 全站一致。
|
||||||
gap: 15px;
|
所有颜色/圆角/阴影一律走主题令牌,禁止硬编码。 */
|
||||||
height: calc(100vh - 136px);
|
.page-wrapper .main-container {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
.accept-wrap {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 296px minmax(0, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 左:列表卡(原型 .nt-list) ---------- */
|
||||||
.title-list {
|
.title-list {
|
||||||
width: 360px;
|
|
||||||
flex: none;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ebeef4;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 14px 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r);
|
||||||
|
box-shadow: var(--sh);
|
||||||
.total {
|
.total {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 15px 10px;
|
padding: 13px 16px 9px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-2);
|
||||||
.num {
|
.num {
|
||||||
padding: 0 5px;
|
padding: 0 2px;
|
||||||
color: #5c9edc;
|
color: var(--brand);
|
||||||
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.clear-icon {
|
.clear-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
|
transition: color 0.15s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.search-box {
|
.search-box {
|
||||||
padding: 0 15px;
|
padding: 0 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
/* 让 iview 搜索框吃原型灰底胶囊观感(浅改,不动结构) */
|
||||||
|
:deep(.ivu-input-wrapper) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ivu-input) {
|
||||||
|
background: #f7f9fc;
|
||||||
|
border-color: transparent;
|
||||||
|
border-radius: var(--r-s);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
:deep(.ivu-input:hover) {
|
||||||
|
background: #f1f4f9;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
:deep(.ivu-input-wrapper-focused .ivu-input) {
|
||||||
|
background: #fff;
|
||||||
|
border-color: var(--brand);
|
||||||
|
box-shadow: 0 0 0 3px var(--brand-a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.notice-items {
|
.notice-items {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-top: 12px;
|
padding: 0 8px 12px;
|
||||||
.list_item {
|
.list_item {
|
||||||
padding: 10px 15px;
|
padding: 10px 12px;
|
||||||
cursor: pointer;
|
margin-bottom: 2px;
|
||||||
|
border-radius: var(--r-s);
|
||||||
border-left: 3px solid transparent;
|
border-left: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
.tt {
|
.tt {
|
||||||
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #272829;
|
color: var(--text);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.other {
|
.other {
|
||||||
margin-top: 6px;
|
margin-top: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 12px;
|
font-size: 11.5px;
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f6f9ff;
|
background: #f7f9fc;
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background: #ecf3ff;
|
background: var(--brand-soft);
|
||||||
border-left-color: #2d8cf0;
|
border-left-color: var(--brand);
|
||||||
|
.tt {
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* 已读弱化 */
|
/* 已读弱化 */
|
||||||
&.readed .tt {
|
&.readed .tt {
|
||||||
color: #8a94a6;
|
color: var(--text-2);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 右:详情卡(原型 .nt-detail) ---------- */
|
||||||
.detail-content {
|
.detail-content {
|
||||||
flex: 1;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ebeef4;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 16px 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r);
|
||||||
|
box-shadow: var(--sh);
|
||||||
|
padding: 18px 22px 24px;
|
||||||
&.empty-detail {
|
&.empty-detail {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
.title-header {
|
.title-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
.tt {
|
.tt {
|
||||||
font-size: 18px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #272829;
|
letter-spacing: 0.2px;
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
.ops {
|
.ops {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 14px;
|
||||||
.op {
|
.op {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 5px;
|
||||||
font-size: 13px;
|
font-size: 12.5px;
|
||||||
|
transition: color 0.15s;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.title-content {
|
.title-content {
|
||||||
margin-top: 10px;
|
margin-top: 14px;
|
||||||
padding: 8px 0;
|
padding-bottom: 16px;
|
||||||
border-bottom: 1px dashed #e8e8e8;
|
border-bottom: 1px dashed var(--line);
|
||||||
font-size: 13px;
|
display: grid;
|
||||||
color: #808695;
|
gap: 9px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--text-2);
|
||||||
p {
|
p {
|
||||||
margin: 4px 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.to-file {
|
.to-file {
|
||||||
margin-left: 10px;
|
margin-left: 8px;
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.notice-content {
|
.notice-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-top: 10px;
|
padding-top: 14px;
|
||||||
|
.ql-editor {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
.file {
|
.file {
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r);
|
||||||
|
overflow: hidden;
|
||||||
.file-title {
|
.file-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: #f7f7f7;
|
align-items: center;
|
||||||
padding: 8px 12px;
|
padding: 9px 14px;
|
||||||
font-size: 13px;
|
background: #f7f9fc;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: var(--text-2);
|
||||||
.all-down {
|
.all-down {
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.file-list {
|
.file-list {
|
||||||
padding: 10px 12px;
|
padding: 2px 0;
|
||||||
.file-item {
|
.file-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 8px 6px;
|
padding: 11px 14px;
|
||||||
border-bottom: 1px dashed #f0f0f0;
|
transition: background 0.15s;
|
||||||
|
&:hover {
|
||||||
|
background: #fafbfe;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
.file-type-icon {
|
.file-type-icon {
|
||||||
font-size: 26px;
|
width: 30px;
|
||||||
color: #515a6e;
|
height: 30px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--brand-soft);
|
||||||
|
color: var(--brand);
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.center {
|
.center {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
min-width: 0;
|
||||||
.name {
|
.name {
|
||||||
color: #272829;
|
font-size: 12.5px;
|
||||||
font-size: 13px;
|
color: var(--text);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.size {
|
.size {
|
||||||
font-size: 12px;
|
font-size: 11.5px;
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
a {
|
||||||
|
color: var(--brand);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12.5px;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right a {
|
|
||||||
margin-right: 8px;
|
|
||||||
color: #2d8cf0;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+118
-185
@@ -1,77 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-wrapper yh-fill">
|
||||||
|
<div class="main-container yh-tabs-card">
|
||||||
|
<Tabs class="tabs-panel" v-model="tab" @on-click="load">
|
||||||
|
<TabPane label="我的待审核" name="todo" />
|
||||||
|
<TabPane label="办件视角" name="instances" />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<!-- 待审核列表 -->
|
||||||
|
<div class="tab-body" v-if="tab === 'todo'">
|
||||||
|
<div class="table-region" ref="todoRegionRef">
|
||||||
|
<Table :columns="todoCols" :data="todoRows" :height="todoH || undefined" :loading="loading" no-data-text="暂无待审核通知">
|
||||||
|
<template #opinion="{ row }">
|
||||||
|
<span class="opinion">{{ row._opinion }}</span>
|
||||||
|
</template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<Button size="small" type="primary" :loading="acting" @click="approve(row)">通过</Button>
|
||||||
|
<Button size="small" type="error" style="margin-left: 6px" @click="openReject(row)"
|
||||||
|
>驳回</Button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<div class="page-panel" v-if="todoPage.total > todoPage.pageSize">
|
||||||
|
<Page class="page-box fr" :current="todoPage.pageIndex" :page-size="todoPage.pageSize"
|
||||||
|
:total="todoPage.total" show-total @on-change="loadTodo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 办件视角 -->
|
||||||
|
<div class="tab-body" v-else>
|
||||||
|
<div class="filter-bar">
|
||||||
|
<RadioGroup v-model="instStatus" type="button" @on-change="loadInstances(1)">
|
||||||
|
<Radio :label="0">全部</Radio>
|
||||||
|
<Radio :label="10">审批中</Radio>
|
||||||
|
<Radio :label="20">已完成</Radio>
|
||||||
|
<Radio :label="30">已拒绝</Radio>
|
||||||
|
<Radio :label="40">已撤回</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="table-region" ref="instRegionRef">
|
||||||
|
<Table :columns="instCols" :data="instRows" :height="instH || undefined" :loading="loading" no-data-text="暂无办件">
|
||||||
|
<template #status="{ row }">
|
||||||
|
<Tag :color="instStatusColor(row.status)">{{ instStatusText(row.status) }}</Tag>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<div class="page-panel" v-if="instPage.total > instPage.pageSize">
|
||||||
|
<Page class="page-box fr" :current="instPage.pageIndex" :page-size="instPage.pageSize"
|
||||||
|
:total="instPage.total" show-total @on-change="loadInstances" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 驳回弹窗:意见必填 -->
|
||||||
|
<Modal
|
||||||
|
v-model="rejectVisible"
|
||||||
|
title="驳回审核"
|
||||||
|
:loading="acting"
|
||||||
|
@on-ok="doReject"
|
||||||
|
@on-cancel="rejectTaskId = null"
|
||||||
|
>
|
||||||
|
<p style="margin-bottom: 8px">
|
||||||
|
驳回「<b>{{ rejectTitle }}</b
|
||||||
|
>」,必须填写意见:
|
||||||
|
</p>
|
||||||
|
<Input v-model="rejectOpinion" type="textarea" :rows="3" placeholder="驳回意见(必填)" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
|
import { useElementHeight } from "@/utils/hook/useElementHeight";
|
||||||
import approvalApi, {
|
import approvalApi, {
|
||||||
type ApprovalInstance,
|
type ApprovalInstance,
|
||||||
type ApprovalTodoTask,
|
type ApprovalTodoTask,
|
||||||
} from "@/api/module/approval";
|
} from "@/api/module/approval";
|
||||||
import Utils from "@/utils";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 我的待审核(应用内审批,第三步):待办列表 + 办件视角。
|
* 我的待审核(应用内审批,第三步):待办列表 + 办件视角,通过/驳回走本应用后端代理
|
||||||
* 通过/驳回走本应用后端代理(operator=登录态,对端按任务办理人复核;非办理人会收到业务错提示)。
|
* (operator=登录态,对端按任务办理人复核;非办理人会收到业务错提示)。
|
||||||
* bizSnapshot 是发起时透传的 JSON 摘要,解析失败只展示标题。
|
* bizSnapshot 是发起时透传的 JSON 摘要,解析失败只展示标题。
|
||||||
*
|
|
||||||
* 规范:bsch应用页规范 §1(tabs-wrapper + tabs-panel type=card,切换只 v-show)、
|
|
||||||
* §2(操作列 operatingHandle 文字链式)、§3(customPageCount 页长记忆)、§8(必填红星 + 统一提示)。
|
|
||||||
*/
|
*/
|
||||||
const tab = ref<"todo" | "instances">("todo");
|
const tab = ref<"todo" | "instances">("todo");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const acting = ref(false);
|
const acting = ref(false);
|
||||||
|
|
||||||
/* ============ 分页(规范 §3:customPageCount 页长记忆) ============ */
|
/* 两个表格高度:实测各自 .table-region(flex:1),替代写死常量 */
|
||||||
const defaultSize = parseInt(localStorage.getItem("customPageCount") || "10");
|
const todoRegionRef = ref<HTMLElement | null>(null);
|
||||||
const todoRows = ref<(ApprovalTodoTask & { _summary: string })[]>([]);
|
const instRegionRef = ref<HTMLElement | null>(null);
|
||||||
const todoPage = ref({ total: 0, pageIndex: 1, pageSize: defaultSize });
|
const todoH = useElementHeight(todoRegionRef);
|
||||||
|
const instH = useElementHeight(instRegionRef);
|
||||||
|
|
||||||
const instRows = ref<(ApprovalInstance & { cellClassName?: Record<string, string> })[]>([]);
|
const todoRows = ref<(ApprovalTodoTask & { _summary: string })[]>([]);
|
||||||
const instPage = ref({ total: 0, pageIndex: 1, pageSize: defaultSize });
|
const todoPage = ref({ total: 0, pageIndex: 1, pageSize: 10 });
|
||||||
|
|
||||||
|
const instRows = ref<ApprovalInstance[]>([]);
|
||||||
|
const instPage = ref({ total: 0, pageIndex: 1, pageSize: 10 });
|
||||||
const instStatus = ref(0);
|
const instStatus = ref(0);
|
||||||
|
|
||||||
const tableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isSearch: true, isPage: true }));
|
|
||||||
|
|
||||||
/* ============ 驳回弹窗(规范 §1/§8:必填红星 + 统一提示"请填写红色星号内容!") ============ */
|
|
||||||
const rejectVisible = ref(false);
|
const rejectVisible = ref(false);
|
||||||
const rejectTaskId = ref<number | null>(null);
|
const rejectTaskId = ref<number | null>(null);
|
||||||
const rejectTitle = ref("");
|
const rejectTitle = ref("");
|
||||||
const rejectFormRef = ref();
|
const rejectOpinion = ref("");
|
||||||
const rejectForm = reactive({ opinion: "" });
|
|
||||||
const rejectRule = {
|
|
||||||
opinion: [{ required: true, message: "请输入驳回意见", trigger: "blur" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const todoCols = [
|
const todoCols = [
|
||||||
{ title: "通知", key: "bizTitle", minWidth: 220, align: "left", ellipsis: true, tooltip: true },
|
{ title: "通知", key: "bizTitle", minWidth: 220, ellipsis: true, tooltip: true },
|
||||||
{ title: "摘要", key: "_summary", minWidth: 200, align: "left", ellipsis: true, tooltip: true },
|
{ title: "摘要", key: "_summary", minWidth: 200, ellipsis: true, tooltip: true },
|
||||||
{ title: "当前节点", key: "nodeName", width: 110, align: "center" },
|
{ title: "当前节点", key: "nodeName", width: 110 },
|
||||||
{ title: "到达时间", key: "arriveTime", width: 170, align: "center", render: fmtTime },
|
{ title: "到达时间", key: "arriveTime", width: 170, render: fmtTime },
|
||||||
{
|
{ title: "操作", slot: "action", width: 150 },
|
||||||
title: "操作",
|
|
||||||
width: 180,
|
|
||||||
align: "center",
|
|
||||||
fixed: "right",
|
|
||||||
render: (h: any, p: any) => [
|
|
||||||
Utils.operatingTextHandle(h, "通过", "", () => approve(p.row)),
|
|
||||||
Utils.operatingTextHandle(h, "驳回", "text-error", () => openReject(p.row)),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const instCols = [
|
const instCols = [
|
||||||
{ title: "通知", key: "bizTitle", minWidth: 240, align: "left", ellipsis: true, tooltip: true },
|
{ title: "通知", key: "bizTitle", minWidth: 240, ellipsis: true, tooltip: true },
|
||||||
{
|
{ title: "状态", slot: "status", width: 100 },
|
||||||
title: "状态",
|
{ title: "提交人", key: "submitterName", width: 100 },
|
||||||
width: 100,
|
{ title: "提交时间", key: "submitTime", width: 170, render: fmtTime },
|
||||||
align: "center",
|
{ title: "办结时间", key: "finishTime", width: 170, render: fmtTime },
|
||||||
render: (h: any, p: any) => h("span", {}, instStatusText(p.row.status)),
|
|
||||||
},
|
|
||||||
{ title: "提交人", key: "submitterName", width: 110, align: "center" },
|
|
||||||
{ title: "提交时间", key: "submitTime", width: 170, align: "center", render: fmtTime },
|
|
||||||
{ title: "办结时间", key: "finishTime", width: 170, align: "center", render: fmtTime },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function fmtTime(h: any, p: any) {
|
function fmtTime(h: any, p: any) {
|
||||||
return h("span", {}, String(p.row[p.column.key] || "").replace("T", " ").slice(0, 19) || "—");
|
return h(
|
||||||
|
"span",
|
||||||
|
{},
|
||||||
|
String(p.row[p.column.key] || "").replace("T", " ").slice(0, 19) || "—"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSummary(task: ApprovalTodoTask): string {
|
function parseSummary(task: ApprovalTodoTask): string {
|
||||||
@@ -84,17 +139,6 @@ function parseSummary(task: ApprovalTodoTask): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ Tab(规范 §1:切换只 v-show,两列表分页状态各自独立) ============ */
|
|
||||||
function tabChange(name: string) {
|
|
||||||
tab.value = name as "todo" | "instances";
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
function load() {
|
|
||||||
if (tab.value === "todo") loadTodo(1);
|
|
||||||
else loadInstances(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTodo(page = 1) {
|
async function loadTodo(page = 1) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -115,11 +159,7 @@ async function loadInstances(page = 1) {
|
|||||||
.instances(instStatus.value || undefined, page, instPage.value.pageSize)
|
.instances(instStatus.value || undefined, page, instPage.value.pageSize)
|
||||||
.send();
|
.send();
|
||||||
instPage.value = res;
|
instPage.value = res;
|
||||||
instRows.value = (res.content || []).map((r) => ({
|
instRows.value = res.content || [];
|
||||||
...r,
|
|
||||||
// 状态列主题色(规范 §7):view-ui-plus 的 cellClassName 挂在行上,按列 key 取
|
|
||||||
cellClassName: { status: instStatusClass(r.status) },
|
|
||||||
}));
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Message.error(e?.message || "办件列表加载失败");
|
Message.error(e?.message || "办件列表加载失败");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -127,25 +167,12 @@ async function loadInstances(page = 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function todoPageUpdate(p: number) {
|
function load() {
|
||||||
todoPage.value.pageIndex = p;
|
if (tab.value === "todo") {
|
||||||
loadTodo(p);
|
|
||||||
}
|
|
||||||
function todoPageSizeUpdate(size: number) {
|
|
||||||
todoPage.value.pageIndex = 1;
|
|
||||||
todoPage.value.pageSize = size;
|
|
||||||
localStorage.setItem("customPageCount", String(size));
|
|
||||||
loadTodo(1);
|
loadTodo(1);
|
||||||
}
|
} else {
|
||||||
function instPageUpdate(p: number) {
|
|
||||||
instPage.value.pageIndex = p;
|
|
||||||
loadInstances(p);
|
|
||||||
}
|
|
||||||
function instPageSizeUpdate(size: number) {
|
|
||||||
instPage.value.pageIndex = 1;
|
|
||||||
instPage.value.pageSize = size;
|
|
||||||
localStorage.setItem("customPageCount", String(size));
|
|
||||||
loadInstances(1);
|
loadInstances(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function approve(row: ApprovalTodoTask) {
|
async function approve(row: ApprovalTodoTask) {
|
||||||
@@ -155,7 +182,7 @@ async function approve(row: ApprovalTodoTask) {
|
|||||||
Message.success("已通过");
|
Message.success("已通过");
|
||||||
loadTodo(todoPage.value.pageIndex);
|
loadTodo(todoPage.value.pageIndex);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// 非办理人/已被办理等业务错,后端信封 msg 直接展示
|
// 非办理人/已被办理等业务错,后端信封 msg 直接展示
|
||||||
Message.error(e?.message || "操作失败");
|
Message.error(e?.message || "操作失败");
|
||||||
} finally {
|
} finally {
|
||||||
acting.value = false;
|
acting.value = false;
|
||||||
@@ -165,20 +192,18 @@ async function approve(row: ApprovalTodoTask) {
|
|||||||
function openReject(row: ApprovalTodoTask) {
|
function openReject(row: ApprovalTodoTask) {
|
||||||
rejectTaskId.value = row.taskId;
|
rejectTaskId.value = row.taskId;
|
||||||
rejectTitle.value = row.bizTitle;
|
rejectTitle.value = row.bizTitle;
|
||||||
rejectForm.opinion = "";
|
rejectOpinion.value = "";
|
||||||
rejectVisible.value = true;
|
rejectVisible.value = true;
|
||||||
nextTick(() => rejectFormRef.value?.resetFields());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doReject() {
|
async function doReject() {
|
||||||
const valid = await rejectFormRef.value?.validate();
|
if (!rejectOpinion.value.trim()) {
|
||||||
if (!valid) {
|
Message.warning("驳回必须填写意见");
|
||||||
Message.info("请填写红色星号内容!");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
acting.value = true;
|
acting.value = true;
|
||||||
try {
|
try {
|
||||||
await approvalApi.reject(rejectTaskId.value!, rejectForm.opinion.trim()).send();
|
await approvalApi.reject(rejectTaskId.value!, rejectOpinion.value.trim()).send();
|
||||||
Message.success("已驳回");
|
Message.success("已驳回");
|
||||||
rejectVisible.value = false;
|
rejectVisible.value = false;
|
||||||
loadTodo(todoPage.value.pageIndex);
|
loadTodo(todoPage.value.pageIndex);
|
||||||
@@ -189,111 +214,19 @@ async function doReject() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 状态文案 / 主题状态色(规范 §7:cellClassName 挂 .status-color-N) */
|
|
||||||
function instStatusText(s: number) {
|
function instStatusText(s: number) {
|
||||||
return ({ 10: "审批中", 20: "已完成", 30: "已拒绝", 40: "已撤回" } as Record<number, string>)[s] || String(s);
|
return ({ 10: "审批中", 20: "已完成", 30: "已拒绝", 40: "已撤回" } as Record<number, string>)[s] || String(s);
|
||||||
}
|
}
|
||||||
function instStatusClass(s: number) {
|
|
||||||
return ({ 10: "status-color-2", 20: "status-color-3", 30: "status-color-4", 40: "status-color-1" } as Record<number, string>)[s] || "";
|
function instStatusColor(s: number) {
|
||||||
|
return ({ 10: "blue", 20: "green", 30: "red", 40: "default" } as Record<number, string>)[s] || "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(load);
|
onMounted(load);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<style scoped>
|
||||||
<div class="tabs-wrapper">
|
.filter-bar {
|
||||||
<Tabs class="tabs-panel" type="card" :value="tab" @on-click="tabChange">
|
margin-bottom: 12px;
|
||||||
<TabPane label="我的待审核" name="todo"></TabPane>
|
|
||||||
<TabPane label="办件视角" name="instances"></TabPane>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<!-- 我的待审核 -->
|
|
||||||
<div class="main-container" v-show="tab === 'todo'">
|
|
||||||
<div class="search-wrap fix">
|
|
||||||
<Button class="fl" @click="loadTodo(1)">刷新</Button>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
:columns="todoCols"
|
|
||||||
:data="todoRows"
|
|
||||||
class="link-table table-bottom-box"
|
|
||||||
:height="tableHeight"
|
|
||||||
:loading="loading"
|
|
||||||
no-data-text="暂无待审核通知"
|
|
||||||
></Table>
|
|
||||||
<div class="page-panel">
|
|
||||||
<div class="fl ml-15 align-middle"></div>
|
|
||||||
<Page
|
|
||||||
class="page-box fr"
|
|
||||||
:total="todoPage.total"
|
|
||||||
:current="todoPage.pageIndex"
|
|
||||||
:page-size="todoPage.pageSize"
|
|
||||||
:page-size-opts="[10, 20, 30, 40]"
|
|
||||||
@on-page-size-change="todoPageSizeUpdate"
|
|
||||||
@on-change="todoPageUpdate"
|
|
||||||
placement="top"
|
|
||||||
show-total
|
|
||||||
show-sizer
|
|
||||||
></Page>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 办件视角 -->
|
|
||||||
<div class="main-container" v-show="tab === 'instances'">
|
|
||||||
<div class="search-wrap fix">
|
|
||||||
<RadioGroup v-model="instStatus" class="fl" type="button" @on-change="loadInstances(1)">
|
|
||||||
<Radio :label="0">全部</Radio>
|
|
||||||
<Radio :label="10">审批中</Radio>
|
|
||||||
<Radio :label="20">已完成</Radio>
|
|
||||||
<Radio :label="30">已拒绝</Radio>
|
|
||||||
<Radio :label="40">已撤回</Radio>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
<Table
|
|
||||||
:columns="instCols"
|
|
||||||
:data="instRows"
|
|
||||||
class="link-table table-bottom-box"
|
|
||||||
:height="tableHeight"
|
|
||||||
:loading="loading"
|
|
||||||
no-data-text="暂无办件"
|
|
||||||
></Table>
|
|
||||||
<div class="page-panel">
|
|
||||||
<div class="fl ml-15 align-middle"></div>
|
|
||||||
<Page
|
|
||||||
class="page-box fr"
|
|
||||||
:total="instPage.total"
|
|
||||||
:current="instPage.pageIndex"
|
|
||||||
:page-size="instPage.pageSize"
|
|
||||||
:page-size-opts="[10, 20, 30, 40]"
|
|
||||||
@on-page-size-change="instPageSizeUpdate"
|
|
||||||
@on-change="instPageUpdate"
|
|
||||||
placement="top"
|
|
||||||
show-total
|
|
||||||
show-sizer
|
|
||||||
></Page>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 驳回弹窗:意见必填 -->
|
|
||||||
<Modal v-model="rejectVisible" :mask-closable="false" width="450px" title="驳回审核">
|
|
||||||
<Form ref="rejectFormRef" :model="rejectForm" :rules="rejectRule" :label-width="90">
|
|
||||||
<FormItem label="通知:">
|
|
||||||
<span class="reject-title">{{ rejectTitle || "—" }}</span>
|
|
||||||
</FormItem>
|
|
||||||
<FormItem label="驳回意见:" prop="opinion">
|
|
||||||
<Input type="textarea" :rows="3" placeholder="请输入驳回意见" v-model="rejectForm.opinion" />
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
<template #footer>
|
|
||||||
<Button type="primary" :loading="acting" @click="doReject">提交</Button>
|
|
||||||
<Button @click="rejectVisible = false">取消</Button>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.reject-title {
|
|
||||||
color: #515a6e;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-wrapper yh-fill">
|
<div class="page-wrapper yh-fill">
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<div class="table-region">
|
<div class="table-region" ref="regionRef">
|
||||||
<Table :columns="cols" :data="rows" :height="tableH || undefined" :loading="loading"
|
<Table :columns="cols" :data="rows" :height="tableH || undefined" :loading="loading"
|
||||||
no-data-text="暂无抄送通知">
|
no-data-text="暂无抄送通知">
|
||||||
<template #title="{ row }">
|
<template #title="{ row }">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
import Utils from "@/utils";
|
import { useElementHeight } from "@/utils/hook/useElementHeight";
|
||||||
import ccApi, { type CcItem } from "@/api/module/cc";
|
import ccApi, { type CcItem } from "@/api/module/cc";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,8 +41,8 @@ const loading = ref(false);
|
|||||||
const rows = ref<CcItem[]>([]);
|
const rows = ref<CcItem[]>([]);
|
||||||
const page = ref({ total: 0, pageIndex: 1, pageSize: 10 });
|
const page = ref({ total: 0, pageIndex: 1, pageSize: 10 });
|
||||||
|
|
||||||
// main 分支无 useElementHeight 钩子(theme 试点分支才有):用平台工具算高,仅扣分页条
|
const regionRef = ref<HTMLElement | null>(null);
|
||||||
const tableH = Utils.useTableHeight({ isPage: true });
|
const tableH = useElementHeight(regionRef);
|
||||||
|
|
||||||
const cols = [
|
const cols = [
|
||||||
{ title: "通知标题", slot: "title", minWidth: 260, ellipsis: true, tooltip: true },
|
{ title: "通知标题", slot: "title", minWidth: 260, ellipsis: true, tooltip: true },
|
||||||
|
|||||||
@@ -395,14 +395,16 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
}
|
}
|
||||||
.notice-title2 {
|
.notice-title2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
}
|
}
|
||||||
.flow-card {
|
.flow-card {
|
||||||
border: 1px solid #e8eaec;
|
border: 1px solid var(--line);
|
||||||
border-radius: 4px;
|
border-radius: var(--r-s);
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
background: #f8f8f9;
|
background: #f7f9fc;
|
||||||
}
|
}
|
||||||
.flow-card-head {
|
.flow-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -413,10 +415,10 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
}
|
}
|
||||||
.flow-title {
|
.flow-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #17233d;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
.flow-nodes {
|
.flow-nodes {
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
.flow-ops {
|
.flow-ops {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@@ -424,24 +426,24 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
.flow-opinion {
|
.flow-opinion {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: #fff;
|
background: var(--panel);
|
||||||
border-radius: 4px;
|
border-radius: var(--r-s);
|
||||||
color: #ed4014;
|
color: var(--red);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.flow-timeline {
|
.flow-timeline {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
background: #fff;
|
background: var(--panel);
|
||||||
border-radius: 4px;
|
border-radius: var(--r-s);
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
}
|
}
|
||||||
.flow-step {
|
.flow-step {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
border-bottom: 1px dashed #f0f0f0;
|
border-bottom: 1px dashed var(--line);
|
||||||
}
|
}
|
||||||
.flow-step:last-child {
|
.flow-step:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@@ -451,21 +453,21 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
}
|
}
|
||||||
.flow-step-user {
|
.flow-step-user {
|
||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
}
|
}
|
||||||
.flow-step-time {
|
.flow-step-time {
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
.flow-step-op {
|
.flow-step-op {
|
||||||
color: #ed4014;
|
color: var(--red);
|
||||||
}
|
}
|
||||||
.flow-step-active .flow-step-node {
|
.flow-step-active .flow-step-node {
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.flow-step-status {
|
.flow-step-status {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
.flow-step .flow-step-op {
|
.flow-step .flow-step-op {
|
||||||
@@ -483,9 +485,9 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
border-bottom: 1px dashed #e8e8e8;
|
border-bottom: 1px dashed var(--line);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
/* 管理视角统计条 */
|
/* 管理视角统计条 */
|
||||||
.stat-bar {
|
.stat-bar {
|
||||||
@@ -494,7 +496,7 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #17233d;
|
color: var(--text);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.progress-box {
|
.progress-box {
|
||||||
@@ -504,7 +506,7 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
.percent-label {
|
.percent-label {
|
||||||
color: #f60;
|
color: var(--brand);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
@@ -516,14 +518,14 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.stat-nums .item {
|
.stat-nums .item {
|
||||||
color: #8a94a6;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
.stat-nums b {
|
.stat-nums b {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
.stat-nums .blue {
|
.stat-nums .blue {
|
||||||
color: #5c9edc;
|
color: var(--brand);
|
||||||
}
|
}
|
||||||
.stat-nums .orange {
|
.stat-nums .orange {
|
||||||
color: #f60;
|
color: #f60;
|
||||||
@@ -534,38 +536,39 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.divider {
|
.divider {
|
||||||
border-bottom: 1px dashed #e8e8e8;
|
border-bottom: 1px dashed var(--line);
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
.files {
|
.files {
|
||||||
border-top: 1px dashed #e8e8e8;
|
border-top: 1px dashed var(--line);
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
.files-title {
|
.files-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.down-all {
|
.down-all {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.file-item {
|
.file-item {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
margin: 0 16px 8px 0;
|
margin: 0 16px 8px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
.file-type-icon {
|
.file-type-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
}
|
}
|
||||||
.file-name {
|
.file-name {
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
@@ -574,26 +577,26 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.file-size {
|
.file-size {
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.file-op {
|
.file-op {
|
||||||
color: #2d8cf0;
|
color: var(--brand);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bottom-btns {
|
.bottom-btns {
|
||||||
border-top: 1px dashed #e8e8e8;
|
border-top: 1px dashed var(--line);
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
/* 阅读情况弹窗 */
|
/* 阅读情况弹窗 */
|
||||||
.read-tip {
|
.read-tip {
|
||||||
background: #e8f4ff;
|
background: var(--brand-soft);
|
||||||
border: 1px solid #d3e9ff;
|
border: 1px solid var(--line);
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 4px;
|
border-radius: var(--r-s);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
@@ -610,7 +613,7 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
flex: none;
|
flex: none;
|
||||||
width: 130px;
|
width: 130px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
@@ -626,13 +629,13 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.green {
|
.green {
|
||||||
color: #19be6b;
|
color: var(--green);
|
||||||
}
|
}
|
||||||
.red {
|
.red {
|
||||||
color: #ed4014;
|
color: var(--red);
|
||||||
}
|
}
|
||||||
.empty {
|
.empty {
|
||||||
color: #999;
|
color: var(--text-3);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { noticeApi } from "@/api/module/notice";
|
|||||||
import type { NoticeVo, NoticeQuery } from "@/types/notice";
|
import type { NoticeVo, NoticeQuery } from "@/types/notice";
|
||||||
import MyConfirm from "@/components/MyConfirm";
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
import Utils from "@/utils";
|
import Utils from "@/utils";
|
||||||
|
import { useElementHeight } from "@/utils/hook/useElementHeight";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -170,7 +171,7 @@ const columns = [
|
|||||||
align: "center",
|
align: "center",
|
||||||
render: (h: any, p: any) => {
|
render: (h: any, p: any) => {
|
||||||
const st = p.row.auditStatus;
|
const st = p.row.auditStatus;
|
||||||
if (st === null || st === undefined) return h("span", { style: "color:#999;" }, "—");
|
if (st === null || st === undefined) return h("span", { style: "color:var(--text-3);" }, "—");
|
||||||
const colors: Record<number, string> = { 0: "default", 10: "blue", 20: "green", 30: "red", 40: "orange" };
|
const colors: Record<number, string> = { 0: "default", 10: "blue", 20: "green", 30: "red", 40: "orange" };
|
||||||
return h("Tag", { color: colors[st] || "default" }, () => p.row.auditStatusDesc || "—");
|
return h("Tag", { color: colors[st] || "default" }, () => p.row.auditStatusDesc || "—");
|
||||||
},
|
},
|
||||||
@@ -189,7 +190,7 @@ const columns = [
|
|||||||
render: (h: any, p: any) =>
|
render: (h: any, p: any) =>
|
||||||
p.row.UpFile
|
p.row.UpFile
|
||||||
? Utils.operatingHandle(h, "附件下载", "icon-attachment", () => downloadFiles(p.row))
|
? Utils.operatingHandle(h, "附件下载", "icon-attachment", () => downloadFiles(p.row))
|
||||||
: h("span", { style: "color:#999;" }, "无"),
|
: h("span", { style: "color:var(--text-3);" }, "无"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
@@ -207,11 +208,13 @@ const columns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOperat: true, isPage: true }));
|
/* 表格高度:不再用 useTableHeight 猜常量,改为实测 .table-region(flex:1)真实高度喂给固定表头 */
|
||||||
|
const tableRegionRef = ref<HTMLElement | null>(null);
|
||||||
|
const tableH = useElementHeight(tableRegionRef);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper yh-fill">
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<!-- 1. 操作按钮区 -->
|
<!-- 1. 操作按钮区 -->
|
||||||
<div class="fix mb-12">
|
<div class="fix mb-12">
|
||||||
@@ -250,14 +253,16 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 3. 表格 + 分页 -->
|
<!-- 3. 表格 + 分页 -->
|
||||||
|
<div class="table-region" ref="tableRegionRef">
|
||||||
<Table
|
<Table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="page.content"
|
:data="page.content"
|
||||||
class="link-table table-bottom-box"
|
class="link-table table-bottom-box"
|
||||||
:height="tableHeight"
|
:height="tableH || undefined"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
no-data-text="暂无数据"
|
no-data-text="暂无数据"
|
||||||
></Table>
|
></Table>
|
||||||
|
</div>
|
||||||
<div class="page-panel">
|
<div class="page-panel">
|
||||||
<div class="fl ml-15 align-middle"></div>
|
<div class="fl ml-15 align-middle"></div>
|
||||||
<Page
|
<Page
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import { settingApi, baseApi, noticeApi } from "@/api/module/notice";
|
|||||||
import type { DeptManageItem, ContactGroupPage, ContactUserPage, SchoolTreeNode, FlowOption } from "@/types/notice";
|
import type { DeptManageItem, ContactGroupPage, ContactUserPage, SchoolTreeNode, FlowOption } from "@/types/notice";
|
||||||
import config from "@/assets/config/host.config";
|
import config from "@/assets/config/host.config";
|
||||||
import Utils from "@/utils";
|
import Utils from "@/utils";
|
||||||
|
import { useElementHeight } from "@/utils/hook/useElementHeight";
|
||||||
|
|
||||||
/* ============ Tab 切换(规范 §1:tabs-wrapper + Tabs type=card,切换只 v-show 内容区) ============ */
|
/* ============ Tab 切换(单卡片 yh-tabs-card 内含线性 Tabs,切换只 v-show 对应 tab-body) ============ */
|
||||||
const currentType = ref(1);
|
const currentType = ref(1);
|
||||||
|
|
||||||
function tabChange(name: string) {
|
function tabChange(name: string) {
|
||||||
@@ -397,19 +398,22 @@ const groupColumns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isSearch: true, isPage: true }));
|
/* 自定义组表格高度:实测 .table-region(flex:1),替代 useTableHeight 常量减法 */
|
||||||
|
const groupRegionRef = ref<HTMLElement | null>(null);
|
||||||
|
const groupH = useElementHeight(groupRegionRef);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tabs-wrapper">
|
<div class="page-wrapper yh-fill">
|
||||||
<Tabs class="tabs-panel" type="card" :value="String(currentType)" @on-click="tabChange">
|
<div class="main-container yh-tabs-card">
|
||||||
|
<Tabs class="tabs-panel" :value="String(currentType)" @on-click="tabChange">
|
||||||
<TabPane label="发文部门" name="1"></TabPane>
|
<TabPane label="发文部门" name="1"></TabPane>
|
||||||
<TabPane label="自定义组" name="3"></TabPane>
|
<TabPane label="自定义组" name="3"></TabPane>
|
||||||
<TabPane label="流程绑定" name="2"></TabPane>
|
<TabPane label="流程绑定" name="2"></TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<!-- 发文部门 -->
|
<!-- 发文部门 -->
|
||||||
<div class="main-container" v-show="currentType === 1">
|
<div class="tab-body" v-show="currentType === 1">
|
||||||
<Alert show-icon>设置说明:拖动卡片调整部门排序;停用的部门不出现在「通知发送」的部门下拉中</Alert>
|
<Alert show-icon>设置说明:拖动卡片调整部门排序;停用的部门不出现在「通知发送」的部门下拉中</Alert>
|
||||||
<div class="search-wrap fix">
|
<div class="search-wrap fix">
|
||||||
<Button class="fl" type="primary" @click="addDeptShow">新增部门</Button>
|
<Button class="fl" type="primary" @click="addDeptShow">新增部门</Button>
|
||||||
@@ -437,17 +441,19 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义组 -->
|
<!-- 自定义组 -->
|
||||||
<div class="main-container" v-show="currentType === 3">
|
<div class="tab-body" v-show="currentType === 3">
|
||||||
<div class="search-wrap fix">
|
<div class="search-wrap fix">
|
||||||
<Button class="fl" type="primary" @click="addGroupShow">新建联系组</Button>
|
<Button class="fl" type="primary" @click="addGroupShow">新建联系组</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="table-region" ref="groupRegionRef">
|
||||||
<Table
|
<Table
|
||||||
:columns="groupColumns"
|
:columns="groupColumns"
|
||||||
:data="groupData"
|
:data="groupData"
|
||||||
class="link-table table-bottom-box"
|
class="link-table table-bottom-box"
|
||||||
:height="groupTableHeight"
|
:height="groupH || undefined"
|
||||||
no-data-text="暂无数据"
|
no-data-text="暂无数据"
|
||||||
></Table>
|
></Table>
|
||||||
|
</div>
|
||||||
<div class="page-panel">
|
<div class="page-panel">
|
||||||
<div class="fl ml-15 align-middle"></div>
|
<div class="fl ml-15 align-middle"></div>
|
||||||
<Page
|
<Page
|
||||||
@@ -478,7 +484,7 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 流程绑定(应用设置 > 流程绑定) -->
|
<!-- 流程绑定(应用设置 > 流程绑定) -->
|
||||||
<div class="main-container" v-show="currentType === 2">
|
<div class="tab-body" v-show="currentType === 2">
|
||||||
<Alert show-icon type="warning">
|
<Alert show-icon type="warning">
|
||||||
绑定说明:绑定后「发送全部用户」的通知必须审核通过才发布(发通知页只显示「提交审核」按钮,
|
绑定说明:绑定后「发送全部用户」的通知必须审核通过才发布(发通知页只显示「提交审核」按钮,
|
||||||
隐藏发送与定时发送);「发送指定人员」不受影响,仍直接发布。未绑定则保持现有发送方式。
|
隐藏发送与定时发送);「发送指定人员」不受影响,仍直接发布。未绑定则保持现有发送方式。
|
||||||
@@ -520,6 +526,7 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 新增发文部门 Modal(编辑走 TypeSetBox 内联编辑) -->
|
<!-- 新增发文部门 Modal(编辑走 TypeSetBox 内联编辑) -->
|
||||||
<Modal v-model="deptModal" :mask-closable="false" width="450px" title="新增部门">
|
<Modal v-model="deptModal" :mask-closable="false" width="450px" title="新增部门">
|
||||||
@@ -601,23 +608,23 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.flow-binding-form .label {
|
.flow-binding-form .label {
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
}
|
}
|
||||||
.flow-binding-state {
|
.flow-binding-state {
|
||||||
color: #515a6e;
|
color: var(--text-2);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
.flow-binding-state .node-summary {
|
.flow-binding-state .node-summary {
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
.readonly-hint {
|
.readonly-hint {
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
.flow-binding-empty {
|
.flow-binding-empty {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
color: #808695;
|
color: var(--text-3);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { Icon } from "view-ui-plus";
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper yh-fill">
|
||||||
<div class="main-container stats-dev">
|
<div class="main-container stats-dev">
|
||||||
<Icon type="ios-stats" size="56" color="#c5c8ce" />
|
<Icon type="ios-stats" size="56" color="var(--text-3)" />
|
||||||
<p class="tip">通知统计功能开发中,敬请期待……</p>
|
<p class="tip">通知统计功能开发中,敬请期待……</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,8 +21,7 @@ import { Icon } from "view-ui-plus";
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: calc(100vh - 136px);
|
color: var(--text-3);
|
||||||
color: #999;
|
|
||||||
}
|
}
|
||||||
.tip {
|
.tip {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
|||||||
+5
-1
@@ -11,6 +11,8 @@ import presetUno from "@unocss/preset-uno";
|
|||||||
|
|
||||||
import presetRemToPx from "@unocss/preset-rem-to-px";
|
import presetRemToPx from "@unocss/preset-rem-to-px";
|
||||||
|
|
||||||
|
import { yhTheme } from "@yunhai/vite-plugin-theme";
|
||||||
|
|
||||||
export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
||||||
// 构建产物直出 dist/(env-config 模式,与 Cms/UserBase 同风格):CI zip 打包取 dist 根,
|
// 构建产物直出 dist/(env-config 模式,与 Cms/UserBase 同风格):CI zip 打包取 dist 根,
|
||||||
// 不再有 dist/base 中间层(此前 outDir=dist/base 时 zip 打 dist 整层,zip 根只有 base/
|
// 不再有 dist/base 中间层(此前 outDir=dist/base 时 zip 打 dist 整层,zip 根只有 base/
|
||||||
@@ -73,7 +75,6 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
"flex-center": "flex items-center justify-center",
|
"flex-center": "flex items-center justify-center",
|
||||||
// 常用组件
|
// 常用组件
|
||||||
btn: "px-16px py-8px rounded-4px cursor-pointer transition-all duration-200",
|
btn: "px-16px py-8px rounded-4px cursor-pointer transition-all duration-200",
|
||||||
"btn-primary": "bg-blue-600 text-white hover:bg-blue-700",
|
|
||||||
card: "bg-white rounded-8px shadow-md p-16px",
|
card: "bg-white rounded-8px shadow-md p-16px",
|
||||||
// 文本截断
|
// 文本截断
|
||||||
"text-ellipsis": "truncate",
|
"text-ellipsis": "truncate",
|
||||||
@@ -90,6 +91,9 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// 主题插件放末位:transformIndexHtml 用 order:'post',必须晚于 createHtmlPlugin 才能把
|
||||||
|
// init script + 兜底 style 追加到最终 html;dev 的 /static 代理也由它自动配
|
||||||
|
yhTheme(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user