Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
690d329371 | ||
|
|
6a63a7cb8f | ||
|
|
f90aefc8e3 | ||
|
|
c1fe0783fb | ||
|
|
3de71aa99b | ||
|
|
8fe3e2ef8c | ||
|
|
13afb22060 | ||
|
|
dbbabc56eb | ||
|
|
5e3f151ce7 | ||
|
|
ab647c67ba | ||
|
|
e8ccc7d3ba | ||
|
|
b685ff5dc0 | ||
|
|
9c8b0f900a | ||
|
|
1af6005a55 | ||
|
|
fea12895c1 | ||
|
|
599fed01b5 | ||
|
|
e0d1b2b769 | ||
|
|
71452fcfd2 | ||
|
|
eee4c10155 | ||
|
|
f611c15a6e | ||
|
|
941031e1b4 | ||
|
|
59f8fad4ca | ||
|
|
f4d0c7d97a | ||
|
|
e37ff9f8d1 | ||
|
|
c81f097188 | ||
|
|
05804f41a9 | ||
|
|
29c051350b | ||
|
|
8c2d539f34 | ||
|
|
693aba9968 | ||
|
|
21a0bd36b6 | ||
|
|
796a1b9a7b | ||
|
|
74b9e80b0d | ||
|
|
1516effb69 | ||
|
|
edd91b53b7 |
+22
@@ -29,6 +29,28 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<!-- 主题通道引导(A 类免插件接入,方案 §11):唯一接入点,不装依赖、不改 vite.config 也能吃主题。
|
||||||
|
origin 零写死,优先级四档:
|
||||||
|
__APP_CONFIG__.staticHost(专用通道覆盖,projman env-config 在线配)
|
||||||
|
> __APP_CONFIG__.baseHost(换域名改配置即生效)
|
||||||
|
> %VITE_THEME_HOST%(仅 dev 注入:主题通道专用域,需通道已开 CORS)
|
||||||
|
> 同源相对 "/static/…"(生产常态:通道与应用同域;本地 dev 由 vite /static 代理兜到通道)
|
||||||
|
为何不直接复用 .env.dev 的 VITE_BASE_HOST:那会让 dev 跨源直连线上域,而通道 Caddy 至今没有
|
||||||
|
Access-Control-Allow-Origin(方案 §10.4 现网无该头)→ manifest 是 fetch 跨源,会被拦成降级。
|
||||||
|
运维加上那行头之后,在 .env.dev 补 VITE_THEME_HOST=http://base.yunhaixingtong.com 即切直连,零改码。
|
||||||
|
document.write 保证 parser-blocking:init 的档位挂载与 FOUC 门先于首帧(同旧插件 initBootstrap 机制,
|
||||||
|
且 vite build 不解析内联脚本里的字符串,规避 "/static/…" 被当本仓资源解析报错;build 产物不读
|
||||||
|
.env.*,占位符原样留字面量 → 判首字符 % 置空,产物零环境值烧入)。
|
||||||
|
挂载/FOUC/CSS 排序全部由通道 init 脚本承担,逻辑升级全走 /static 通道,无需重新构建本应用 -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var c = window.__APP_CONFIG__ || {};
|
||||||
|
var themeHost = "%VITE_THEME_HOST%";
|
||||||
|
if (themeHost.charAt(0) === "%") themeHost = "";
|
||||||
|
var host = String(c.staticHost || c.baseHost || themeHost).replace(/\/+$/, "");
|
||||||
|
document.write('<script src="' + host + '/static/js/yh-theme-init.js"><\/script>');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<title><%- title %></title>
|
<title><%- title %></title>
|
||||||
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
||||||
<script src="./libs/jquery.min.js"></script>
|
<script src="./libs/jquery.min.js"></script>
|
||||||
|
|||||||
Generated
+777
@@ -75,6 +75,431 @@
|
|||||||
"alova": "dist/bin/cli.js"
|
"alova": "dist/bin/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/openharmony-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alova/wormhole/node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.25.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||||
|
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alova/wormhole/node_modules/@esbuild/win32-x64": {
|
"node_modules/@alova/wormhole/node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||||
@@ -10766,6 +11191,358 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.21.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||||
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite/node_modules/esbuild": {
|
"node_modules/vite/node_modules/esbuild": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
|
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Method } from "alova";
|
||||||
|
import { alovaInstance } from "@/utils/alova/alova-iview";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用内「抄送给我的」接口封装(2026-09-24)。
|
||||||
|
* 走本应用后端 /api/notice/cc/My(身份=CAS 登录态,前端不传身份);
|
||||||
|
* 数据源是 flow.cc.sent 回调落的 notice_cc 表,点击行跳通知详情。
|
||||||
|
*/
|
||||||
|
|
||||||
|
function get<T = any>(url: string, params?: Record<string, any>): Method<T> {
|
||||||
|
return new Method<T>("GET", alovaInstance, url, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CcItem {
|
||||||
|
id: number;
|
||||||
|
noticeId: number;
|
||||||
|
flowInstanceId: number | null;
|
||||||
|
nodeId: string | null;
|
||||||
|
nodeName: string | null;
|
||||||
|
sentTime: string;
|
||||||
|
title: string | null;
|
||||||
|
deptName: string | null;
|
||||||
|
publisherName: string | null;
|
||||||
|
noticeStatus: number | null;
|
||||||
|
auditStatus: number | null;
|
||||||
|
auditStatusDesc: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CcPage {
|
||||||
|
total: number;
|
||||||
|
content: CcItem[];
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasNext: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ccApi = {
|
||||||
|
/** 抄送给我的(分页,sent_time 倒序) */
|
||||||
|
my(page = 1, size = 10): Method<CcPage> {
|
||||||
|
return get(`/api/notice/cc/My`, { page, size });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ccApi;
|
||||||
@@ -127,6 +127,10 @@ class NoticeApi {
|
|||||||
flowWithdraw(serid: number) {
|
flowWithdraw(serid: number) {
|
||||||
return postForm<void>(`${NOTICE}/flow/Withdraw`, { serid });
|
return postForm<void>(`${NOTICE}/flow/Withdraw`, { serid });
|
||||||
}
|
}
|
||||||
|
/** 办件进度嵌入页地址(Drawer iframe 直嵌;pageUrl 含短时 token 现取现发,勿缓存复用) */
|
||||||
|
flowEmbed(serid: number) {
|
||||||
|
return get<{ pageUrl: string }>(`${NOTICE}/flow/Embed`, { serid });
|
||||||
|
}
|
||||||
/** 应用级流程绑定(应用设置 > 流程绑定;发通知页据此判断「提交审核」还是「发送」) */
|
/** 应用级流程绑定(应用设置 > 流程绑定;发通知页据此判断「提交审核」还是「发送」) */
|
||||||
getFlowBinding() {
|
getFlowBinding() {
|
||||||
return get<FlowBindingVo>(`${NOTICE}/flow/Binding`);
|
return get<FlowBindingVo>(`${NOTICE}/flow/Binding`);
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ 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: 89, modId: 89, name: "待审核", page: "/approval" },
|
||||||
{ id: 4000, modId: 4000, name: "通知统计", page: "/stats" },
|
{ id: 4, modId: 4, name: "通知统计", page: "/stats" },
|
||||||
{ id: 5000, modId: 5000, name: "应用设置", page: "/setting" },
|
{ id: 21, modId: 21, name: "应用设置", page: "/setting" },
|
||||||
|
{ id: 91, modId: 91, name: "抄送给我的", page: "/cc" },
|
||||||
];
|
];
|
||||||
return Promise.resolve(menu);
|
return Promise.resolve(menu);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M859.977 336.128H608.251V171.489c0-41.159-33.318-74.836-74.039-74.836h-44.421c-40.729 0-74.044 33.677-74.044 74.836v164.639H164.023c-16.289 0-29.616 13.473-29.616 29.936v149.669c0 16.469 13.326 29.942 29.616 29.942h14.803v231.241c0 65.859-14.803 120.493-14.803 120.493 0 16.459 13.324 29.938 29.611 29.938h636.729c16.291 0 29.615-13.481 29.615-29.938 0 0-14.81-56.882-14.81-123.486V545.675h14.81c16.287 0 29.616-13.477 29.616-29.938V366.064c-0.001-16.469-13.323-29.936-29.617-29.936zM682.282 897.403V699.089c0-5.989-5.18-11.225-11.103-11.225h-7.407c-5.917 0-11.103 5.236-11.103 11.225v198.314H534.212V699.089c0-5.989-5.181-11.225-11.116-11.225H515.7c-5.918 0-11.106 5.236-11.106 11.225v198.314H378.731V699.089c0-5.989-5.186-11.225-11.111-11.225h-7.399c-5.918 0-11.106 5.236-11.106 11.225v198.314H193.634s14.812-53.884 14.812-120.487V545.675h607.113v231.241c0 66.606 14.804 120.487 14.804 120.487H682.282z m177.695-396.634c0 8.233-6.66 14.968-14.81 14.968H178.826c-8.145 0-14.807-6.735-14.807-14.968V381.028c0-8.235 6.662-14.964 14.807-14.964H445.36V178.971c0-29.184 22.956-52.382 51.835-52.382h29.61c28.876 0 51.828 23.199 51.828 52.382v187.093h266.535c8.15 0 14.81 6.729 14.81 14.964v119.741z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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 [
|
||||||
/* ---------- 布局 / 用户 ---------- */
|
/* ---------- 布局 / 用户 ---------- */
|
||||||
{
|
{
|
||||||
@@ -271,14 +298,15 @@ export default [
|
|||||||
method: "get",
|
method: "get",
|
||||||
response: () =>
|
response: () =>
|
||||||
ok([
|
ok([
|
||||||
// icon:用户中心模块管理配置的 YschIcons 图标名,layout 渲染为 iconfont icon-<名称>
|
// id/modId 对齐平台模块管理的「配置 ID」(对照 /base/GetUserMod 现行数据);
|
||||||
// id/modId 与平台模块管理的「配置 ID」一致(对照 /base/GetUserMod 现行数据)
|
// icon:YschIcons 图标名,layout 渲染为 iconfont icon-<名称>
|
||||||
{ id: 1000, modId: 1000, name: "已收到", parentId: 0, page: "/", icon: "sjx", orderNumber: 1, usable: true, children: [] },
|
{ id: 1, modId: 1, name: "已收到", parentId: 0, page: "/", icon: "sjx", orderNumber: 1, usable: true, children: [] },
|
||||||
{ id: 2000, modId: 2000, name: "已发送", parentId: 0, page: "/list", icon: "fasong", orderNumber: 2, usable: true, children: [] },
|
{ id: 2, modId: 2, name: "已发送", parentId: 0, page: "/list", icon: "fasong", orderNumber: 2, usable: true, children: [] },
|
||||||
{ id: 3000, modId: 3000, name: "全部通知", parentId: 0, page: "/noticelist", icon: "liebiao", orderNumber: 3, usable: true, children: [] },
|
{ id: 3, modId: 3, name: "全部通知", parentId: 0, page: "/noticelist", icon: "liebiao", orderNumber: 3, usable: true, children: [] },
|
||||||
{ id: 2100, modId: 2100, name: "待审核", parentId: 0, page: "/approval", icon: "", orderNumber: 4, usable: true, children: [] },
|
{ id: 89, modId: 89, name: "待审核", parentId: 0, page: "/approval", icon: "shenpi", orderNumber: 4, usable: true, children: [] },
|
||||||
{ id: 4000, modId: 4000, name: "通知统计", parentId: 0, page: "/stats", icon: "tongji", orderNumber: 5, usable: true, children: [] },
|
{ id: 4, modId: 4, name: "通知统计", parentId: 0, page: "/stats", icon: "tongji", orderNumber: 5, usable: true, children: [] },
|
||||||
{ id: 5000, modId: 5000, name: "应用设置", parentId: 0, page: "/setting", icon: "shezhi", orderNumber: 6, usable: true, children: [] },
|
{ id: 21, modId: 21, name: "应用设置", parentId: 0, page: "/setting", icon: "shezhi", orderNumber: 6, usable: true, children: [] },
|
||||||
|
{ id: 91, modId: 91, name: "抄送给我的", parentId: 0, page: "/cc", icon: "", orderNumber: 7, usable: true, children: [] },
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -724,4 +752,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,3 +115,16 @@ h4.subtitle {
|
|||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 修复表格幻影省略号(2026-10-10):.ivu-table-cell 基础样式自带 text-overflow:ellipsis,
|
||||||
|
tooltip 列(.ivu-tooltip 行内块包装 + width:100%)与操作列(图标外包 Tooltip)未溢出也常显「...」。
|
||||||
|
恢复组件预期的块级/flex 渲染层级即可;内层 .ivu-table-cell-tooltip-content 的真省略号与
|
||||||
|
悬浮 tooltip 不受影响(已在 dev:mock 实测:短内容无省略号、长标题正常截断+悬浮提示)。 */
|
||||||
|
/* 两条同为 3 类特异性(0,3,0):后者居后胜出 → tooltip 列(.ivu-table-cell-tooltip)最终 flex
|
||||||
|
(组件预期布局),操作列裸 .ivu-tooltip 包装取 block。 */
|
||||||
|
.ivu-table .ivu-table-cell > .ivu-tooltip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.ivu-table .ivu-table-cell > .ivu-table-cell-tooltip {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -92,16 +92,18 @@ export default {
|
|||||||
display: block;
|
display: block;
|
||||||
.upload-row {
|
.upload-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start; /* 提示语与按钮首行对齐(列表换行后不再垂直居中整块) */
|
||||||
}
|
}
|
||||||
:deep(.ivu-upload) {
|
:deep(.ivu-upload) {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap; /* 允许内部列表换行 */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
/* 文件列表整体另起一行,每项独占一行,不再与按钮挤在一起 */
|
/* 文件列表整体另起一行,每项独占一行,不再与按钮挤在一起 */
|
||||||
:deep(.ivu-upload-list) {
|
:deep(.ivu-upload-list) {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
width: 100%; /* 文件列表整体独占下一行 */
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
:deep(.ivu-upload-list-file) {
|
:deep(.ivu-upload-list-file) {
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
@@ -110,7 +112,7 @@ export default {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: #f90;
|
color: #f90;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1;
|
line-height: 32px; /* 与默认 Button 等高,紧贴按钮右侧 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/* 默认头像三态组件(YschTheme 公共源,scripts/sync-apps.mjs 下发三端 src/components/UserAvatar.vue):
|
||||||
|
头像 URL 非空 → <img>;否则 style==="text" 且能取到字 → 首字;否则 → 剪影(纯 CSS/SVG,零资源文件)。
|
||||||
|
取值链/取字契约见《设置页修复与默认头像落地方案》§10.1/§10.2;样式全在本文件 <style scoped>(唯一 CSS 源,
|
||||||
|
不进通道 icons.src.css——通道 CSS 对 UserHome 不可达,内联分发才能消双份漂移,见同方案 §10.4)。
|
||||||
|
根元素即 .yh-avatar 圆:尺寸内联必须挂圆本身(挂外层 span 会被 flex 拉成椭圆,§12 踩坑)。 */
|
||||||
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
|
import { pickAvatarText, resolveAvatarStyle } from "@/utils/avatar";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 头像 URL:非空直接 <img>(三态第一优先) */
|
||||||
|
src?: string | null;
|
||||||
|
/** 取字来源(首字态用):经 pickAvatarText 判定,取不到字回落剪影 */
|
||||||
|
name?: string | null;
|
||||||
|
/** 直径 px;font-size = round(size * 0.44) */
|
||||||
|
size?: number;
|
||||||
|
/** 配色场景:light=页面浅底(品牌实底+白内容);brand=header 品牌色底(剪影 S1 玻璃底 / 首字 S2 白底反色,§10.3 分档) */
|
||||||
|
placement?: "light" | "brand";
|
||||||
|
}>(),
|
||||||
|
{ src: null, name: null, size: 28, placement: "light" },
|
||||||
|
);
|
||||||
|
|
||||||
|
const text = computed(() => (props.src ? "" : pickAvatarText(props.name)));
|
||||||
|
/* 外观偏好异步补齐(yh-theme-init 在挂载后拉服务端真源收敛 localStorage 键)会在
|
||||||
|
document 上派发 yh-appearance-updated;tick 自增让 style 依赖失效重读键,三态即时切换(无需刷新)。 */
|
||||||
|
const appearanceTick = ref(0);
|
||||||
|
const onAppearanceUpdated = () => { appearanceTick.value += 1; };
|
||||||
|
onMounted(() => document.addEventListener("yh-appearance-updated", onAppearanceUpdated));
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("yh-appearance-updated", onAppearanceUpdated));
|
||||||
|
const style = computed(() => {
|
||||||
|
appearanceTick.value;
|
||||||
|
return resolveAvatarStyle();
|
||||||
|
});
|
||||||
|
/** 首字态 = style==="text" 且能取到字(契约 §10.2:二者缺一即剪影,取到字但档位是剪影时绝不能出字) */
|
||||||
|
const showText = computed(() => style.value === "text" && !!text.value);
|
||||||
|
|
||||||
|
const rootClass = computed(() => [
|
||||||
|
"yh-avatar",
|
||||||
|
props.placement === "brand" ? "yh-avatar--brand" : "yh-avatar--light",
|
||||||
|
{ "is-text": showText.value },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const rootStyle = computed(() => ({
|
||||||
|
width: `${props.size}px`,
|
||||||
|
height: `${props.size}px`,
|
||||||
|
fontSize: `${Math.round(props.size * 0.44)}px`,
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span :class="rootClass" :style="rootStyle">
|
||||||
|
<img v-if="src" class="yh-avatar__img" :src="src" alt="" />
|
||||||
|
<template v-else-if="showText">{{ text }}</template>
|
||||||
|
<svg v-else class="yh-avatar__sil" viewBox="0 0 1024 1024" aria-hidden="true">
|
||||||
|
<circle cx="512" cy="399" r="174" />
|
||||||
|
<ellipse cx="512" cy="946" rx="420" ry="240" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.yh-avatar {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.yh-avatar__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* 剪影 SVG:容器圆 overflow:hidden 裁切肩部溢出(禁用 clipPath);overflow:visible 放行 viewBox 外形状 */
|
||||||
|
.yh-avatar__sil {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
/* 页面内(浅底)——品牌实底 + 白内容 */
|
||||||
|
.yh-avatar--light {
|
||||||
|
background: var(--brand, #577afb);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
/* header(品牌色底)——与页面内反向:剪影档 = S1 半透明白,首字档 = S2 纯白底 + 品牌色字 */
|
||||||
|
.yh-avatar--brand {
|
||||||
|
background: rgba(255, 255, 255, 0.22);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.yh-avatar--brand.is-text {
|
||||||
|
background: #fff;
|
||||||
|
color: var(--brand, #577afb);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+64
-30
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onBeforeMount, watch, computed } from "vue";
|
import { ref, onBeforeMount, onBeforeUnmount, onMounted, 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";
|
||||||
|
import UserAvatar from "@/components/UserAvatar.vue";
|
||||||
|
import defaultLogo from "@/assets/image/yunhai-logo.svg";
|
||||||
|
|
||||||
const props = defineProps<{ layoutData: Record<string, any> }>();
|
const props = defineProps<{ layoutData: Record<string, any> }>();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -22,6 +25,27 @@ const modList = computed(() => props.layoutData?.modList || []);
|
|||||||
|
|
||||||
const pathNavStyle = computed(() => (isAlone.value ? { left: "0", width: "100%" } : null));
|
const pathNavStyle = computed(() => (isAlone.value ? { left: "0", width: "100%" } : null));
|
||||||
|
|
||||||
|
// 顶栏 Logo:localStorage["yh-logo-url"](平台键,yh-theme-init 异步补齐段写入)优先,本仓资产兜底——绝无空 src/裂图。
|
||||||
|
// 刷新:mounted 读一次 + 监听 yh-appearance-updated(服务端偏好到达后即时切换,无需刷新页面)。
|
||||||
|
const logoUrl = ref<string>(defaultLogo);
|
||||||
|
const readLogoUrl = () => {
|
||||||
|
let v: string | null = null;
|
||||||
|
try {
|
||||||
|
v = localStorage.getItem("yh-logo-url");
|
||||||
|
} catch {
|
||||||
|
v = null;
|
||||||
|
}
|
||||||
|
logoUrl.value = v || defaultLogo;
|
||||||
|
};
|
||||||
|
const onLogoError = () => {
|
||||||
|
if (logoUrl.value !== defaultLogo) logoUrl.value = defaultLogo;
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
readLogoUrl();
|
||||||
|
document.addEventListener("yh-appearance-updated", readLogoUrl);
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("yh-appearance-updated", readLogoUrl));
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
routeChange(route);
|
routeChange(route);
|
||||||
});
|
});
|
||||||
@@ -94,15 +118,21 @@ const routeChange = (to: any) => {
|
|||||||
navPath.value = [];
|
navPath.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 统一解析菜单项目标:容忍平台配置的尾空格/旧 /notice 前缀/外链,返回内部路由 path 或外链 URL。
|
||||||
|
* 模板高亮与点击都走这里,避免「raw page(带尾空格)vs 解析后 path」不一致导致点不动/不高亮。 */
|
||||||
|
const resolveItemPage = (item: any) =>
|
||||||
|
resolveMenuPage(item?.page, item?.modId ?? item?.id, item?.name);
|
||||||
|
const pageOf = (item: any): string => resolveItemPage(item).path;
|
||||||
|
|
||||||
const linkMenu = (item: any) => {
|
const linkMenu = (item: any) => {
|
||||||
const page: string = item?.page || "";
|
const { path: page, external } = resolveItemPage(item);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
// 解析不出有效页面:显式提示,而不是静默不动或被重定向吞到默认页
|
// 解析不出有效页面:显式提示,而不是静默不动或被重定向吞到默认页
|
||||||
Message.warning(`菜单「${item?.name || "未命名"}」未配置有效页面路径,请检查基础平台模块设置`);
|
Message.warning(`菜单「${item?.name || "未命名"}」未配置有效页面路径,请检查基础平台模块设置`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 平台配置的外部链接模块:整页跳转
|
// 平台配置的外部链接模块:整页跳转
|
||||||
if (item?.external) {
|
if (external) {
|
||||||
window.location.href = page;
|
window.location.href = page;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +144,8 @@ const linkMenu = (item: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const linkUserInfo = () => {
|
const linkUserInfo = () => {
|
||||||
window.location.href = baseHost.value + "/v/userinfo";
|
// 头像/姓名点击统一跳 UserHome 设置页「个人信息」pane(默认头像轮 2026-09-30,原 /v/userinfo 已下线语义)
|
||||||
|
window.location.href = baseHost.value + "/userhome/front/index.html#/settings?pane=profile";
|
||||||
};
|
};
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
let url = window.location.href;
|
let url = window.location.href;
|
||||||
@@ -129,12 +160,10 @@ 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 不渲染 */
|
* 平台未配置图标时统一兜底 icon-list(2026-10-09 定稿:菜单项恒有图标,保整列缩进/行高一致) */
|
||||||
const getMenuIcon = (item?: any): string => {
|
const getMenuIcon = (item?: any): string =>
|
||||||
const icon = String(item?.icon || "").trim();
|
`icon-${String(item?.icon || "").trim() || "list"}`;
|
||||||
return icon ? `icon-${icon}` : "";
|
|
||||||
};
|
|
||||||
|
|
||||||
function linkHomePage() {
|
function linkHomePage() {
|
||||||
if (baseHost.value) {
|
if (baseHost.value) {
|
||||||
@@ -149,16 +178,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="logoUrl" alt="云海" @error="onLogoError" /></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" />
|
<UserAvatar class="fl align-middle" :src="userInfo.avatar" :name="userInfo.name" :size="28" placement="brand" @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 +193,8 @@ function linkHomePage() {
|
|||||||
<i class="iconfont icon-logout"></i>
|
<i class="iconfont icon-logout"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 配色切换器已移除(oapi 代理轮 2026-10-08 拍板):设置入口收敛在 UserHome 设置中心,
|
||||||
|
本地切换会被服务端生效值收敛打回,不留半残入口 -->
|
||||||
</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>
|
||||||
@@ -176,14 +204,14 @@ function linkHomePage() {
|
|||||||
<li>
|
<li>
|
||||||
<a href="javascript:;" @click="childChange(index)">
|
<a href="javascript:;" @click="childChange(index)">
|
||||||
<i :class="item.childState ? 'arrow-down' : 'arrow-up'"></i>
|
<i :class="item.childState ? 'arrow-down' : 'arrow-up'"></i>
|
||||||
<i v-if="getMenuIcon(item)" :class="['iconfont', getMenuIcon(item)]"></i>
|
<i :class="['iconfont', getMenuIcon(item)]"></i>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<transition name="fade_child_ul">
|
<transition name="fade_child_ul">
|
||||||
<div class="child-item-wrap" v-show="item.childState">
|
<div class="child-item-wrap" v-show="item.childState">
|
||||||
<li
|
<li
|
||||||
:class="citem.page == currPath ? 'active child-item' : 'child-item'"
|
:class="pageOf(citem) == currPath ? 'active child-item' : 'child-item'"
|
||||||
v-for="citem in item.children"
|
v-for="citem in item.children"
|
||||||
:key="citem.id"
|
:key="citem.id"
|
||||||
>
|
>
|
||||||
@@ -194,9 +222,9 @@ function linkHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
<li v-else :class="item.page == currPath ? 'active' : ''">
|
<li v-else :class="pageOf(item) == currPath ? 'active' : ''">
|
||||||
<a href="javascript:;" @click="linkMenu(item)">
|
<a href="javascript:;" @click="linkMenu(item)">
|
||||||
<i v-if="getMenuIcon(item)" :class="['iconfont', getMenuIcon(item)]"></i>
|
<i :class="['iconfont', getMenuIcon(item)]"></i>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -210,7 +238,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,21 +278,27 @@ 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),
|
||||||
是给老 iconfont 字体字形做的基线补偿,换成 ysch mask 的 1em 方块后反而把图标顶偏;
|
这里不再用 float 时代的 13px 顶偏移补偿,仅留右侧间距;徽标大小/圆底/阴影属皮肤 */
|
||||||
这里用 inline-flex 真居中并关掉该补偿(不动压缩产物 layout.css,覆盖收在本组件内) */
|
.header-container .logo-badge {
|
||||||
.crumb-home {
|
margin: 0 10px 0 0;
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
}
|
||||||
|
/* 面包屑「首页」垂直居中:骨架遗留的 .path-nav .icon-category{position:relative;top:1px}
|
||||||
|
是给老 iconfont 字体字形做的基线补偿,换成 ysch mask 的 1em 方块后反而把图标顶偏;
|
||||||
|
这里用 inline-flex 真居中并关掉该补偿(不动通道产物,覆盖收在本组件内) */
|
||||||
|
/* 面包屑「首页」与后续级目的文字对齐(2026-10-09 定稿):此前 .crumb-home 用 inline-flex,
|
||||||
|
行内级盒的基线取首项——16px 无字图标(其基线=方块底缘),整盒被顶高约 4px,首页文字
|
||||||
|
比相邻级目高。改回普通行内流:图标(.iconfont 基类 inline-block,基线=底缘)以 -0.125em
|
||||||
|
基线偏移做光学居中,文字与相邻级目共用同一行盒基线,天然对齐 */
|
||||||
.crumb-home .iconfont {
|
.crumb-home .iconfont {
|
||||||
position: static;
|
position: static;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: -0.125em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+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";
|
// index.html 的 yh-theme-init.js 在 DOMContentLoaded 后按 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";
|
||||||
|
|
||||||
|
|||||||
+24
-16
@@ -9,12 +9,12 @@ 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 是菜单 ↔ 路由的稳定契约(严格对照后端 /base/GetUserMod 现行返回的 modId):
|
||||||
* 模块管理里的配置 ID):
|
* 1=已收到 2=已发送 3=全部通知 4=通知统计 21=应用设置 89=待审核 91=抄送给我的
|
||||||
* 1000=已收到 2000=已发送 3000=全部通知 2100=待审核 4000=通知统计 5000=应用设置
|
* (后两条平台已挂一级菜单,page 分别为 /approval、/cc)。
|
||||||
* 深层子页不在菜单树,编码沿用 UserBase 范式「父模块 ×10 + 序号」并靠 meta.parentId
|
* 深层子页不在菜单树,靠 meta.parentId 关联高亮父菜单:/list/new=22(父2)、
|
||||||
* 关联高亮:/list/new=20001、/list/detail=20002(父 2000),/noticelist/detail=30001(父 3000)。
|
* /list/detail=23(父2)、/noticelist/detail=24(父3)。深层页 modId 不得与任何
|
||||||
* 深层页编码不得与任何平台一级模块 ID 重复。
|
* 平台一级模块 ID(含 89/91)重复。
|
||||||
*/
|
*/
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
// 已收通知(收件箱):后端菜单 page 配置为 "/",即应用入口页
|
// 已收通知(收件箱):后端菜单 page 配置为 "/",即应用入口页
|
||||||
@@ -22,14 +22,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 +37,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 +52,42 @@ 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)
|
// 待审核(应用内审批;平台模块管理已挂一级菜单,配置 ID = 89,path = /approval)
|
||||||
{
|
{
|
||||||
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: 89 },
|
||||||
|
},
|
||||||
|
|
||||||
|
// 抄送给我的(流程 NOTIFY 知会;平台模块管理已挂一级菜单,配置 ID = 91,path = /cc)
|
||||||
|
{
|
||||||
|
path: "/cc",
|
||||||
|
name: "notice_cc",
|
||||||
|
component: () => import("@/views/notice/NoticeCc.vue"),
|
||||||
|
meta: { title: "抄送给我的", modId: 91 },
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ---- 旧版路径兼容重定向(书签、残留的平台模块配置) ---- */
|
/* ---- 旧版路径兼容重定向(书签、残留的平台模块配置) ---- */
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ export interface NoticeVo {
|
|||||||
smsFlag: number;
|
smsFlag: number;
|
||||||
/** 接收人列表(JSON) */
|
/** 接收人列表(JSON) */
|
||||||
receiveList: string | null;
|
receiveList: string | null;
|
||||||
|
/** 附件ID串("1,2,3";后端 Lombok 序列化名为 upFile,对齐旧 .NET UpFile 语义) */
|
||||||
|
upFile?: string | null;
|
||||||
|
/** @deprecated 旧 .NET 接口的 PascalCase 附件字段,仅作读取兼容 */
|
||||||
|
UpFile?: string | null;
|
||||||
createBy: string | null;
|
createBy: string | null;
|
||||||
createTime: string | null;
|
createTime: string | null;
|
||||||
updateBy: string | null;
|
updateBy: string | null;
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* 默认头像样式(首字 / 剪影)公共源(YschTheme,scripts/sync-apps.mjs 下发三端 src/utils/avatar.ts)。
|
||||||
|
* 取值链与主题同构(theme-core.js resolveEffectiveThemeState 的合成链写法):
|
||||||
|
* URL ?yh-avatar=text|silhouette(仅预览/排障覆盖:只影响读、绝不写回)
|
||||||
|
* > localStorage["yh-avatar"](用户自选,纯字符串)
|
||||||
|
* > localStorage["yh-avatar-sch-default"](学校默认,UserHome 登录后写入)
|
||||||
|
* > "text"(系统默认)
|
||||||
|
* 值域恒为 "text" | "silhouette",键缺失/值非法均静默回落下一级,不报错不阻塞渲染。
|
||||||
|
* 渲染三态与取字规则的唯一契约见 UserHome《设置页修复与默认头像落地方案》§10.1/§10.2。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type AvatarStyle = "text" | "silhouette";
|
||||||
|
|
||||||
|
interface StorageLike {
|
||||||
|
getItem(k: string): string | null;
|
||||||
|
setItem(k: string, v: string): void;
|
||||||
|
removeItem(k: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AVATAR_STORAGE_KEY = "yh-avatar";
|
||||||
|
|
||||||
|
/** 学校默认头像存储键(UserHome 登录时写入;值格式与用户键相同,仅无"用户自选"语义) */
|
||||||
|
export const SCHOOL_DEFAULT_AVATAR_STORAGE_KEY = "yh-avatar-sch-default";
|
||||||
|
|
||||||
|
export const AVATAR_STYLES: readonly AvatarStyle[] = ["text", "silhouette"];
|
||||||
|
|
||||||
|
export const DEFAULT_AVATAR_STYLE: AvatarStyle = "text";
|
||||||
|
|
||||||
|
/** 值合法才采用(用户键/学校键同一判据;脏数据不生效) */
|
||||||
|
function readAvatarStyleOrNull(raw: string | null): AvatarStyle | null {
|
||||||
|
return raw === "text" || raw === "silhouette" ? raw : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** URL 覆盖参数(?yh-avatar=text|silhouette):非法值忽略,且只影响"读哪一档"、绝不写回 localStorage */
|
||||||
|
function readAvatarOverride(): AvatarStyle | null {
|
||||||
|
try {
|
||||||
|
const search = (globalThis.location && globalThis.location.search) || "";
|
||||||
|
const m = new RegExp("[?&]yh-avatar=([^&]*)").exec(search);
|
||||||
|
return m ? readAvatarStyleOrNull(decodeURIComponent(m[1])) : null;
|
||||||
|
} catch {
|
||||||
|
/* 无 location 环境(测试/SSR):无覆盖 */
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生效样式合成(与 resolveEffectiveThemeState 同构):
|
||||||
|
* URL 覆盖 > localStorage["yh-avatar"](用户自选)> localStorage["yh-avatar-sch-default"](学校默认)
|
||||||
|
* > DEFAULT_AVATAR_STYLE。
|
||||||
|
*/
|
||||||
|
export function resolveAvatarStyle(storage: StorageLike | undefined = globalThis.localStorage): AvatarStyle {
|
||||||
|
let style: AvatarStyle | null = null;
|
||||||
|
if (storage) {
|
||||||
|
style = readAvatarStyleOrNull(storage.getItem(AVATAR_STORAGE_KEY));
|
||||||
|
if (!style) style = readAvatarStyleOrNull(storage.getItem(SCHOOL_DEFAULT_AVATAR_STORAGE_KEY));
|
||||||
|
}
|
||||||
|
if (!style) style = DEFAULT_AVATAR_STYLE;
|
||||||
|
return readAvatarOverride() || style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存用户自选(直写 localStorage,无响应式;调用方操作后自行刷新 UI) */
|
||||||
|
export function saveAvatarStyle(style: AvatarStyle, storage: StorageLike | undefined = globalThis.localStorage): void {
|
||||||
|
try {
|
||||||
|
storage?.setItem(AVATAR_STORAGE_KEY, style);
|
||||||
|
} catch {
|
||||||
|
/* 隐私模式/配额失败静默:仅丢持久化,不丢本次会话生效 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跟随学校默认 = 删用户自选键(之后回落学校默认/系统默认) */
|
||||||
|
export function clearAvatarStyle(storage: StorageLike | undefined = globalThis.localStorage): void {
|
||||||
|
try {
|
||||||
|
storage?.removeItem(AVATAR_STORAGE_KEY);
|
||||||
|
} catch {
|
||||||
|
/* 隐私模式静默 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取字(顺序判定,先命中先返回):含 "@" 先截 "@" 前段(邮箱取首字母);
|
||||||
|
* 首字符必须用展开 [...s][0](禁用 charAt(0),避免代理对截半出乱码)。
|
||||||
|
* ASCII 字母转大写;纯数字回落 ""(剪影);符号/emoji/空白回落 ""(剪影);其余(CJK 等)原样。
|
||||||
|
*/
|
||||||
|
export function pickAvatarText(name: string | null | undefined): string {
|
||||||
|
let s = String(name ?? "");
|
||||||
|
const at = s.indexOf("@");
|
||||||
|
if (at > -1) s = s.slice(0, at);
|
||||||
|
const ch = [...s.trim()][0];
|
||||||
|
if (ch === undefined) return "";
|
||||||
|
if (/^[A-Za-z]$/.test(ch)) return ch.toUpperCase();
|
||||||
|
if (/^[0-9]$/.test(ch)) return "";
|
||||||
|
if (/^[\p{P}\p{S}\s]$/u.test(ch)) return "";
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* 流程示意图 PNG 显示口径(2026-10-10 用户反馈:嵌入页图过大,同 flow-center/front 同名工具):
|
||||||
|
* 服务端按 SCALE=2 出图(视网膜清晰度),按自然尺寸渲染等于整体放大一倍。
|
||||||
|
* 须按逻辑尺寸(自然宽一半)显示;超宽长图收缩到容器宽。
|
||||||
|
* 用法:<img ref="diagramEl" :src="..." @load="onDiagramLoad">(图均为打开时挂载,@load 必触发)。
|
||||||
|
*/
|
||||||
|
export function fitDiagram(el: HTMLImageElement | null | undefined): void {
|
||||||
|
if (!el || !el.naturalWidth) return;
|
||||||
|
const want = el.naturalWidth / 2;
|
||||||
|
const cap = (el.parentElement?.clientWidth || 0) - 8;
|
||||||
|
el.style.width = Math.round(Math.min(want, cap > 120 ? cap : want)) + "px";
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+1
-1
@@ -100,7 +100,7 @@ export default class Utils {
|
|||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
class: `operating-btn iconfont ${iconClass}`,
|
class: `operating-btn iconfont ${iconClass}`,
|
||||||
onClick: callback,
|
onClick: disabled ? null : callback,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
|
|||||||
+73
-1
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20260923-1454-9619fbf),勿手改。
|
* 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20261009-1655-488a3c3),勿手改。
|
||||||
* 分发方式:YschTheme `npm run sync` 拷进各端 src/utils/theme.ts(与 ThemeSwitcher.vue 配套,MD5 对齐报告)。
|
* 分发方式:YschTheme `npm run sync` 拷进各端 src/utils/theme.ts(与 ThemeSwitcher.vue 配套,MD5 对齐报告)。
|
||||||
* 运行时档位类名由 /static/js/yh-theme-init.js 在 <head> 内先行挂好,本模块只负责读写与切换。
|
* 运行时档位类名由 /static/js/yh-theme-init.js 在 <head> 内先行挂好,本模块只负责读写与切换。
|
||||||
*/
|
*/
|
||||||
@@ -25,6 +25,9 @@ export const THEME_NAMES: readonly ThemeName[] = ["blue", "ocean", "ink", "viole
|
|||||||
|
|
||||||
export const THEME_STORAGE_KEY = "yh-theme";
|
export const THEME_STORAGE_KEY = "yh-theme";
|
||||||
|
|
||||||
|
/* 学校默认档存储键(UserHome 登录时写入;值格式与 yh-theme 完全相同,仅无"用户自选"语义) */
|
||||||
|
export const SCHOOL_DEFAULT_STORAGE_KEY = "yh-theme-sch-default";
|
||||||
|
|
||||||
export const DEFAULT_THEME_STATE: ThemeState = {
|
export const DEFAULT_THEME_STATE: ThemeState = {
|
||||||
theme: "blue",
|
theme: "blue",
|
||||||
sideDeep: true,
|
sideDeep: true,
|
||||||
@@ -49,6 +52,75 @@ export function parseThemeState(raw: string | null): ThemeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 严格读取:四字段全部合法才返回状态,否则 null(合成链"值非法→回落下一级"用;用户键仍走宽松解析保现状) */
|
||||||
|
function readThemeStateOrNull(raw: string | null): ThemeState | null {
|
||||||
|
if (!raw) return null;
|
||||||
|
try {
|
||||||
|
const o = JSON.parse(raw);
|
||||||
|
if (!o || typeof o !== "object") return null;
|
||||||
|
if (!THEME_NAMES.includes(o.theme)) return null;
|
||||||
|
if (typeof o.sideDeep !== "boolean" || typeof o.gradOn !== "boolean" || typeof o.sideFollow !== "boolean") return null;
|
||||||
|
return { theme: o.theme, sideDeep: o.sideDeep, gradOn: o.gradOn, sideFollow: o.sideFollow };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** URL 覆盖参数(?yh-theme= / ?yh-side=deep|light / ?yh-grad=on|off):仅预览/排障用,
|
||||||
|
非法值忽略(对应字段不覆盖),且只影响"读哪一档"、绝不写回 localStorage。 */
|
||||||
|
function readThemeOverrides(): { theme?: ThemeName; sideDeep?: boolean; gradOn?: boolean } | null {
|
||||||
|
let out = null;
|
||||||
|
try {
|
||||||
|
const search = (globalThis.location && globalThis.location.search) || "";
|
||||||
|
const get = (name) => {
|
||||||
|
const m = new RegExp("[?&]" + name + "=([^&]*)").exec(search);
|
||||||
|
return m ? decodeURIComponent(m[1]) : null;
|
||||||
|
};
|
||||||
|
const t = get("yh-theme");
|
||||||
|
if (t && THEME_NAMES.includes(t)) {
|
||||||
|
out = out || {};
|
||||||
|
out.theme = t;
|
||||||
|
}
|
||||||
|
const s = get("yh-side");
|
||||||
|
if (s === "deep" || s === "light") {
|
||||||
|
out = out || {};
|
||||||
|
out.sideDeep = s === "deep";
|
||||||
|
}
|
||||||
|
const g = get("yh-grad");
|
||||||
|
if (g === "on" || g === "off") {
|
||||||
|
out = out || {};
|
||||||
|
out.gradOn = g === "on";
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* 无 location 环境(测试/SSR):无覆盖 */
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生效档合成(唯一实现,init 启动段与 UserHome theme.ts 共用):
|
||||||
|
* URL 覆盖 > localStorage["yh-theme"](用户自选,存在即用——宽松修补,与历史行为一致)
|
||||||
|
* > localStorage["yh-theme-sch-default"](学校默认,整体合法才生效)
|
||||||
|
* > DEFAULT_THEME_STATE。
|
||||||
|
* 键不存在/解析失败/值非法均静默回落,不报错不阻塞首帧。
|
||||||
|
*/
|
||||||
|
export function resolveEffectiveThemeState(storage: StorageLike | undefined = globalThis.localStorage): ThemeState {
|
||||||
|
let state = null;
|
||||||
|
if (storage) {
|
||||||
|
const userRaw = storage.getItem(THEME_STORAGE_KEY);
|
||||||
|
if (userRaw) state = parseThemeState(userRaw);
|
||||||
|
if (!state) state = readThemeStateOrNull(storage.getItem(SCHOOL_DEFAULT_STORAGE_KEY));
|
||||||
|
}
|
||||||
|
if (!state) state = { ...DEFAULT_THEME_STATE };
|
||||||
|
const ov = readThemeOverrides();
|
||||||
|
if (ov) {
|
||||||
|
if (ov.theme) state = { ...state, theme: ov.theme };
|
||||||
|
if (ov.sideDeep != null) state = { ...state, sideDeep: ov.sideDeep };
|
||||||
|
if (ov.gradOn != null) state = { ...state, gradOn: ov.gradOn };
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
export function loadThemeState(storage: StorageLike | undefined = globalThis.localStorage): ThemeState {
|
export function loadThemeState(storage: StorageLike | undefined = globalThis.localStorage): ThemeState {
|
||||||
if (!storage) return { ...DEFAULT_THEME_STATE };
|
if (!storage) return { ...DEFAULT_THEME_STATE };
|
||||||
return parseThemeState(storage.getItem(THEME_STORAGE_KEY));
|
return parseThemeState(storage.getItem(THEME_STORAGE_KEY));
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { NoticeDetailVo, NoticeRequest, UpFileVo, FlowBindingVo } from "@/t
|
|||||||
import UserCheckInput from "@/components/UserCheckInput.vue";
|
import UserCheckInput from "@/components/UserCheckInput.vue";
|
||||||
import NoticeUpload from "@/components/NoticeUpload.vue";
|
import NoticeUpload from "@/components/NoticeUpload.vue";
|
||||||
import config from "@/assets/config/host.config";
|
import config from "@/assets/config/host.config";
|
||||||
|
import { fitDiagram } from "@/utils/diagramImg";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -85,11 +86,21 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 流程图:flow-center 示意图深链(Notice 指定到人场景用人员视图) */
|
/** 流程图 Drawer(后端 DiagramDef 中转 PNG:同源免 token;与设置页同款,修复旧深链 404) */
|
||||||
|
const flowDiagramVisible = ref(false);
|
||||||
|
const flowDiagramEl = ref<HTMLImageElement | null>(null);
|
||||||
|
const flowDiagramSrc = computed(() =>
|
||||||
|
flowDiagramVisible.value && auditFlowGuid.value
|
||||||
|
? `${config.serviceHost}/api/notice/flow/DiagramDef?flowGuid=${encodeURIComponent(auditFlowGuid.value)}`
|
||||||
|
: "",
|
||||||
|
);
|
||||||
function openFlowDiagram() {
|
function openFlowDiagram() {
|
||||||
if (auditFlowGuid.value) {
|
if (!auditFlowGuid.value) return;
|
||||||
window.open(`${config.flowHost}/#/diagram?flowGuid=${auditFlowGuid.value}&mode=user`, "_blank");
|
flowDiagramVisible.value = true;
|
||||||
}
|
}
|
||||||
|
function onFlowDiagramError() {
|
||||||
|
flowDiagramVisible.value = false;
|
||||||
|
Message.error("流程图加载失败(流程可能未发布或绑定已解除)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ 富文本编辑器(wangeditor v5 for vue3) ============ */
|
/* ============ 富文本编辑器(wangeditor v5 for vue3) ============ */
|
||||||
@@ -408,6 +419,14 @@ async function saveEditAndLeave(data: NoticeRequest) {
|
|||||||
<Button @click="isModal.timiSend = false" style="margin-left: 8px">取消</Button>
|
<Button @click="isModal.timiSend = false" style="margin-left: 8px">取消</Button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<!-- 流程图 Drawer(后端 DiagramDef 同源 PNG;竖版长图容器内纵向滚动) -->
|
||||||
|
<Drawer v-model="flowDiagramVisible" title="流程示意图" :width="860" :transfer="false"
|
||||||
|
:styles="{ body: { padding: '0' } }">
|
||||||
|
<div class="flow-diagram-frame">
|
||||||
|
<img ref="flowDiagramEl" v-if="flowDiagramSrc" :src="flowDiagramSrc" alt="流程示意图" @load="fitDiagram(flowDiagramEl)" @error="onFlowDiagramError" />
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -423,11 +442,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) {
|
||||||
@@ -439,4 +458,18 @@ async function saveEditAndLeave(data: NoticeRequest) {
|
|||||||
.bottom-btn {
|
.bottom-btn {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
/* 流程图 Drawer:浅底色 + 纵向滚动(竖版长图),图片撑满可读宽度 */
|
||||||
|
.flow-diagram-frame {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.flow-diagram-frame img {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Message, Tooltip } from "view-ui-plus";
|
|||||||
import { noticeApi } from "@/api/module/notice";
|
import { noticeApi } from "@/api/module/notice";
|
||||||
import type { NoticeDetailVo, NoticeQuery, NoticeVo, NoticePageResult } from "@/types/notice";
|
import type { NoticeDetailVo, NoticeQuery, NoticeVo, NoticePageResult } from "@/types/notice";
|
||||||
import FilePreviewModal from "@/components/FilePreviewModal.vue";
|
import FilePreviewModal from "@/components/FilePreviewModal.vue";
|
||||||
|
import SvgIcon from "@/components/SvgIcon.vue";
|
||||||
import Utils from "@/utils";
|
import Utils from "@/utils";
|
||||||
|
|
||||||
/* ============ 左侧列表 ============ */
|
/* ============ 左侧列表 ============ */
|
||||||
@@ -111,7 +112,7 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 附件图标(按扩展名,图标名对齐旧版 matchSuffix → icon-word/excel/ppt/pdf1/txt/tupian1/yasuo/weizhi) */
|
/* 附件图标(按扩展名,图标名对齐旧版 matchSuffix → icon-word/excel/ppt/pdf1/txt/tupian/yasuo/weizhi) */
|
||||||
function getFileIcon(name: string) {
|
function getFileIcon(name: string) {
|
||||||
const suffix = String(name || "").split(".").pop()?.toLowerCase() || "";
|
const suffix = String(name || "").split(".").pop()?.toLowerCase() || "";
|
||||||
if (["doc", "docx", "wps"].includes(suffix)) return "icon-word";
|
if (["doc", "docx", "wps"].includes(suffix)) return "icon-word";
|
||||||
@@ -119,7 +120,8 @@ function getFileIcon(name: string) {
|
|||||||
if (["ppt", "pptx"].includes(suffix)) return "icon-ppt";
|
if (["ppt", "pptx"].includes(suffix)) return "icon-ppt";
|
||||||
if (["pdf"].includes(suffix)) return "icon-pdf1";
|
if (["pdf"].includes(suffix)) return "icon-pdf1";
|
||||||
if (["txt"].includes(suffix)) return "icon-txt";
|
if (["txt"].includes(suffix)) return "icon-txt";
|
||||||
if (["jpg", "jpeg", "png", "gif", "bmp"].includes(suffix)) return "icon-tupian1";
|
// 平台 icons.css 的 icon-tupian 是线性「图片」图标;icon-tupian1 是旧彩色图转单色,看不出是图片
|
||||||
|
if (["jpg", "jpeg", "png", "gif", "bmp"].includes(suffix)) return "icon-tupian";
|
||||||
if (["zip", "rar"].includes(suffix)) return "icon-yasuo";
|
if (["zip", "rar"].includes(suffix)) return "icon-yasuo";
|
||||||
return "icon-weizhi";
|
return "icon-weizhi";
|
||||||
}
|
}
|
||||||
@@ -163,7 +165,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">
|
||||||
<!-- 左侧通知列表 -->
|
<!-- 左侧通知列表 -->
|
||||||
@@ -171,7 +173,7 @@ function shortDate(date: string) {
|
|||||||
<div class="total">
|
<div class="total">
|
||||||
<span>未读<span class="num">{{ page.unreadCount || 0 }}</span>条</span>
|
<span>未读<span class="num">{{ page.unreadCount || 0 }}</span>条</span>
|
||||||
<Tooltip content="一键已读" placement="top" transfer>
|
<Tooltip content="一键已读" placement="top" transfer>
|
||||||
<i class="iconfont icon-saoba clear-icon" @click="allRead"></i>
|
<SvgIcon class="clear-icon" name="read-all" :size="18" color="var(--text-3)" hover-color="var(--brand)" @click="allRead" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
@@ -211,11 +213,11 @@ function shortDate(date: string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title-content">
|
<div class="title-content">
|
||||||
<p>来源:{{ detail.DeptName }}</p>
|
<p><span class="meta-label">来源</span>:{{ detail.DeptName }}</p>
|
||||||
<p>发布人:{{ detail.TeaName }}</p>
|
<p><span class="meta-label">发布人</span>:{{ detail.TeaName }}</p>
|
||||||
<p>发布时间:{{ detail.EditDate }}</p>
|
<p><span class="meta-label">发布时间</span>:{{ detail.EditDate }}</p>
|
||||||
<p>
|
<p>
|
||||||
<span>附件:{{ (detail.ChaoSend || []).length }}个</span>
|
<span class="meta-label">附件</span>:{{ (detail.ChaoSend || []).length }}个
|
||||||
<span class="to-file" v-if="detail.ChaoSend && detail.ChaoSend.length > 0" @click="downAll">全部下载</span>
|
<span class="to-file" v-if="detail.ChaoSend && detail.ChaoSend.length > 0" @click="downAll">全部下载</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,188 +264,280 @@ 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;
|
|
||||||
color: #999;
|
|
||||||
&:hover {
|
|
||||||
color: #2d8cf0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.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;
|
||||||
|
}
|
||||||
|
/* 标签两端撑开:冒号纵向对齐、值从同一列起(宽度取「发布时间」4 字) */
|
||||||
|
.meta-label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 4em;
|
||||||
|
text-align: justify;
|
||||||
|
text-align-last: justify;
|
||||||
}
|
}
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+159
-185
@@ -1,77 +1,162 @@
|
|||||||
|
<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 }">
|
||||||
|
<span class="operating-link" @click="openEmbed(row)">查看详情</span>
|
||||||
|
<span class="operating-link" @click="approve(row)">通过</span>
|
||||||
|
<span class="operating-link text-error" @click="openReject(row)">驳回</span>
|
||||||
|
</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>
|
||||||
|
<template #instAction="{ row }">
|
||||||
|
<Button size="small" @click="openEmbed(row)">审批记录</Button>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<!-- 审批记录 Drawer(flow-center 嵌入页 iframe 撑满:状态/基本信息/流程示意图/轨迹/审批流水/表单六块) -->
|
||||||
|
<Drawer v-model="embedVisible" title="审批记录" :width="740" :transfer="false" :styles="{ body: { padding: '0' } }">
|
||||||
|
<div class="embed-frame-wrap">
|
||||||
|
<iframe v-if="embedPageUrl" :src="embedPageUrl" title="办件进度"></iframe>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
</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";
|
import { noticeApi } from "@/api/module/notice";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 我的待审核(应用内审批,第三步):待办列表 + 办件视角。
|
* 我的待审核(应用内审批,第三步):待办列表 + 办件视角,通过/驳回走本应用后端代理
|
||||||
* 通过/驳回走本应用后端代理(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: 190, align: "center" },
|
||||||
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: "操作", slot: "instAction", width: 100 },
|
||||||
},
|
|
||||||
{ 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 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/* 审批记录 Drawer(嵌入页 iframe):pageUrl 含 10 分钟短时 token,每次打开现取,
|
||||||
|
* 不缓存、不落 storage(既有安全纪律);业务错(尚未进入流程/无权限 403)原文提示,不打断列表 */
|
||||||
|
const embedVisible = ref(false);
|
||||||
|
const embedPageUrl = ref("");
|
||||||
|
|
||||||
|
async function openEmbed(row: ApprovalInstance | ApprovalTodoTask) {
|
||||||
|
try {
|
||||||
|
const res = await noticeApi.flowEmbed(Number(row.bizId)).send();
|
||||||
|
embedPageUrl.value = res?.pageUrl || "";
|
||||||
|
if (!embedPageUrl.value) {
|
||||||
|
Message.error("审批记录地址获取失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
embedVisible.value = true;
|
||||||
|
} catch (e: any) {
|
||||||
|
Message.error(e?.message || "审批记录打开失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 +169,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 +189,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,35 +197,23 @@ 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) {
|
||||||
|
if (acting.value) return; // 链接式操作无 :loading 反馈,用 acting 拦截连点重复提交
|
||||||
acting.value = true;
|
acting.value = true;
|
||||||
try {
|
try {
|
||||||
await approvalApi.approve(row.taskId).send();
|
await approvalApi.approve(row.taskId).send();
|
||||||
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 +223,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 +245,29 @@ 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>
|
/* 审批记录 Drawer:iframe 撑满,嵌入页内部自己滚动 */
|
||||||
</Tabs>
|
.embed-frame-wrap {
|
||||||
|
height: 100%;
|
||||||
<!-- 我的待审核 -->
|
}
|
||||||
<div class="main-container" v-show="tab === 'todo'">
|
.embed-frame-wrap iframe {
|
||||||
<div class="search-wrap fix">
|
display: block;
|
||||||
<Button class="fl" @click="loadTodo(1)">刷新</Button>
|
width: 100%;
|
||||||
</div>
|
height: 100%;
|
||||||
<Table
|
border: 0;
|
||||||
: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>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-wrapper yh-fill">
|
||||||
|
<div class="main-container">
|
||||||
|
<TablePageAlovaNew ref="listRef" :data-api="fetchPage" :columns="cols">
|
||||||
|
<template #title="{ row }">
|
||||||
|
<a v-if="row.title" class="notice-link" @click="openDetail(row)">{{ row.title }}</a>
|
||||||
|
<span v-else class="notice-gone">通知已删除</span>
|
||||||
|
</template>
|
||||||
|
<template #status="{ row }">
|
||||||
|
<Tag v-if="row.auditStatusDesc && row.auditStatusDesc !== '通知已删除'"
|
||||||
|
:color="statusColor(row.auditStatus)">{{ row.auditStatusDesc }}</Tag>
|
||||||
|
<Tag v-else-if="row.noticeStatus === 1" color="green">已发布</Tag>
|
||||||
|
<Tag v-else>—</Tag>
|
||||||
|
</template>
|
||||||
|
</TablePageAlovaNew>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
|
||||||
|
import ccApi, { type CcItem } from "@/api/module/cc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抄送给我的(2026-09-24):流程 NOTIFY 节点激活即抄送(审批过程中就出现在列表里,
|
||||||
|
* 此时通知未发布——被抄送人可见由 NoticeFlowGuard 第 4 条放行)。点击标题跳通知详情;
|
||||||
|
* 编辑视角(type=0)不标读:未发布通知的阅读统计无意义。
|
||||||
|
*/
|
||||||
|
const router = useRouter();
|
||||||
|
const listRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
|
// usePagination 的 handler 必须返回 Method(组件内部统一 send 并吃全局响应拦截),返回 Promise 会卡在 loading
|
||||||
|
const fetchPage = (p: { pageIndex: number; pageSize: number }) => ccApi.my(p.pageIndex, p.pageSize);
|
||||||
|
|
||||||
|
const cols = [
|
||||||
|
{ title: "通知标题", slot: "title", minWidth: 260, ellipsis: true, tooltip: true },
|
||||||
|
{ title: "来源部门", key: "deptName", width: 140 },
|
||||||
|
{ title: "抄送环节", key: "nodeName", width: 140 },
|
||||||
|
{ title: "抄送时间", key: "sentTime", width: 170, render: fmtTime },
|
||||||
|
{ title: "状态", slot: "status", width: 100 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function fmtTime(h: any, p: any) {
|
||||||
|
return h(
|
||||||
|
"span",
|
||||||
|
{},
|
||||||
|
String(p.row[p.column.key] || "").replace("T", " ").slice(0, 19) || "—"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusColor(auditStatus: number | null) {
|
||||||
|
return ({ 0: "default", 10: "blue", 20: "green", 30: "red", 40: "orange" } as Record<number, string>)[
|
||||||
|
auditStatus ?? -1
|
||||||
|
] || "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDetail(row: CcItem) {
|
||||||
|
router.push({ path: "/list/detail", query: { serid: row.noticeId, type: 0 } });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.notice-link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-gone {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,6 +6,7 @@ import { noticeApi } from "@/api/module/notice";
|
|||||||
import type { NoticeDetailVo, NextPrevVo, ReadGroup, FlowTraceVo } from "@/types/notice";
|
import type { NoticeDetailVo, NextPrevVo, ReadGroup, FlowTraceVo } from "@/types/notice";
|
||||||
import FilePreviewModal from "@/components/FilePreviewModal.vue";
|
import FilePreviewModal from "@/components/FilePreviewModal.vue";
|
||||||
import Utils from "@/utils";
|
import Utils from "@/utils";
|
||||||
|
import { fitDiagram } from "@/utils/diagramImg";
|
||||||
import config from "@/assets/config/host.config";
|
import config from "@/assets/config/host.config";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -77,6 +78,7 @@ async function flowAction(action: "withdraw" | "urge") {
|
|||||||
|
|
||||||
/* 流程图弹窗(2026-09-17:后端同源出图 /flow/Diagram,img 免 token 免跨域,不再深链 flow-center) */
|
/* 流程图弹窗(2026-09-17:后端同源出图 /flow/Diagram,img 免 token 免跨域,不再深链 flow-center) */
|
||||||
const diagramVisible = ref(false);
|
const diagramVisible = ref(false);
|
||||||
|
const diagramEl = ref<HTMLImageElement | null>(null);
|
||||||
function openFlowDiagram() {
|
function openFlowDiagram() {
|
||||||
if (!detail.value?.flowGuid || !detail.value?.flowInstanceId) return;
|
if (!detail.value?.flowGuid || !detail.value?.flowInstanceId) return;
|
||||||
diagramVisible.value = true;
|
diagramVisible.value = true;
|
||||||
@@ -90,6 +92,24 @@ function onDiagramError() {
|
|||||||
diagramVisible.value = false;
|
diagramVisible.value = false;
|
||||||
Message.error("流程图加载失败(可能已过期,请刷新页面重试)");
|
Message.error("流程图加载失败(可能已过期,请刷新页面重试)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 办件进度嵌入页(iframe 免签嵌入 flow-center 统一进度页):每次打开现取 pageUrl——
|
||||||
|
* token 10 分钟短时效,不缓存、不落 localStorage 复用;嵌入页内部自己滚动 */
|
||||||
|
const embedVisible = ref(false);
|
||||||
|
const embedPageUrl = ref("");
|
||||||
|
async function openFlowEmbed() {
|
||||||
|
if (!detail.value?.flowInstanceId) return;
|
||||||
|
embedVisible.value = true;
|
||||||
|
embedPageUrl.value = "";
|
||||||
|
try {
|
||||||
|
const res = await noticeApi.flowEmbed(serid.value).send();
|
||||||
|
embedPageUrl.value = res?.pageUrl || "";
|
||||||
|
} catch (e: any) {
|
||||||
|
embedVisible.value = false;
|
||||||
|
Message.error(e?.msg || e?.message || "嵌入页地址获取失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const readGroups = ref<ReadGroup[]>([]);
|
const readGroups = ref<ReadGroup[]>([]);
|
||||||
const showRead = ref(false);
|
const showRead = ref(false);
|
||||||
const prevId = ref(0);
|
const prevId = ref(0);
|
||||||
@@ -189,7 +209,7 @@ function downAll() {
|
|||||||
function displayName(item: { name?: string; filename?: string }) {
|
function displayName(item: { name?: string; filename?: string }) {
|
||||||
return item.name || item.filename || "";
|
return item.name || item.filename || "";
|
||||||
}
|
}
|
||||||
/* 附件图标(按扩展名,图标名对齐旧版 matchSuffix → icon-word/excel/ppt/pdf1/txt/tupian1/yasuo/weizhi) */
|
/* 附件图标(按扩展名,图标名对齐旧版 matchSuffix → icon-word/excel/ppt/pdf1/txt/tupian/yasuo/weizhi) */
|
||||||
function getFileIcon(name: string) {
|
function getFileIcon(name: string) {
|
||||||
const suffix = String(name || "").split(".").pop()?.toLowerCase() || "";
|
const suffix = String(name || "").split(".").pop()?.toLowerCase() || "";
|
||||||
if (["doc", "docx", "wps"].includes(suffix)) return "icon-word";
|
if (["doc", "docx", "wps"].includes(suffix)) return "icon-word";
|
||||||
@@ -197,7 +217,8 @@ function getFileIcon(name: string) {
|
|||||||
if (["ppt", "pptx"].includes(suffix)) return "icon-ppt";
|
if (["ppt", "pptx"].includes(suffix)) return "icon-ppt";
|
||||||
if (["pdf"].includes(suffix)) return "icon-pdf1";
|
if (["pdf"].includes(suffix)) return "icon-pdf1";
|
||||||
if (["txt"].includes(suffix)) return "icon-txt";
|
if (["txt"].includes(suffix)) return "icon-txt";
|
||||||
if (["jpg", "jpeg", "png", "gif", "bmp"].includes(suffix)) return "icon-tupian1";
|
// 平台 icons.css 的 icon-tupian 是线性「图片」图标;icon-tupian1 是旧彩色图转单色,看不出是图片
|
||||||
|
if (["jpg", "jpeg", "png", "gif", "bmp"].includes(suffix)) return "icon-tupian";
|
||||||
if (["zip", "rar"].includes(suffix)) return "icon-yasuo";
|
if (["zip", "rar"].includes(suffix)) return "icon-yasuo";
|
||||||
return "icon-weizhi";
|
return "icon-weizhi";
|
||||||
}
|
}
|
||||||
@@ -265,6 +286,13 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
<span v-if="flowTrace.currentNodes" class="flow-nodes">当前节点:{{ flowTrace.currentNodes }}</span>
|
<span v-if="flowTrace.currentNodes" class="flow-nodes">当前节点:{{ flowTrace.currentNodes }}</span>
|
||||||
<div class="flow-ops">
|
<div class="flow-ops">
|
||||||
<Button v-if="detail.flowGuid" size="small" @click="openFlowDiagram">查看流程图</Button>
|
<Button v-if="detail.flowGuid" size="small" @click="openFlowDiagram">查看流程图</Button>
|
||||||
|
<Button
|
||||||
|
v-if="detail.flowInstanceId"
|
||||||
|
size="small"
|
||||||
|
style="margin-left: 8px"
|
||||||
|
@click="openFlowEmbed"
|
||||||
|
>审批进度</Button
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
v-if="detail.flowInstanceId && flowTrace.auditStatus === 10"
|
v-if="detail.flowInstanceId && flowTrace.auditStatus === 10"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -320,10 +348,17 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
<!-- 流程图弹窗(后端同源出图,免 token 免跨域) -->
|
<!-- 流程图弹窗(后端同源出图,免 token 免跨域) -->
|
||||||
<Modal v-model="diagramVisible" title="流程示意图" :width="860" footer-hide>
|
<Modal v-model="diagramVisible" title="流程示意图" :width="860" footer-hide>
|
||||||
<div class="diagram-wrap">
|
<div class="diagram-wrap">
|
||||||
<img v-if="diagramSrc" :src="diagramSrc" alt="流程示意图" @error="onDiagramError" />
|
<img v-if="diagramSrc" ref="diagramEl" :src="diagramSrc" alt="流程示意图" @load="fitDiagram(diagramEl)" @error="onDiagramError" />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<!-- 办件进度嵌入页(Drawer 内 iframe 撑满,嵌入页内部滚动;token 每次打开现取) -->
|
||||||
|
<Drawer v-model="embedVisible" title="审批进度" :width="740" :transfer="false" :styles="{ body: { padding: '0' } }">
|
||||||
|
<div class="embed-frame-wrap">
|
||||||
|
<iframe v-if="embedPageUrl" :src="embedPageUrl" title="办件进度"></iframe>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
<div v-if="viewType !== 0" class="meta">
|
<div v-if="viewType !== 0" class="meta">
|
||||||
<span>发文单位:{{ detail.DeptName || "—" }}</span>
|
<span>发文单位:{{ detail.DeptName || "—" }}</span>
|
||||||
<span>发布人:{{ detail.TeaName || "—" }}</span>
|
<span>发布人:{{ detail.TeaName || "—" }}</span>
|
||||||
@@ -384,6 +419,19 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 高度模型(2026-10-10 定稿):卡片撑满视口(与全站其他页一致),但必须能"随长文生长"。
|
||||||
|
不能直接用通道 .yh-fill:其卡片是 flex:1(basis:0)+ min-height:420px,卡片被钉在容器高,
|
||||||
|
而 .main-container 带 overflow:hidden(base.layout.css:222)→ 长文被裁掉且滚不到(最小复现已验证)。
|
||||||
|
故此处 page-wrapper 转 flex 纵向(全局已给 min-height:100%),卡片 flex:1 0 auto(basis:auto)
|
||||||
|
→ 短文撑满、长文随内容长高、滚动留在 main-wrapper 整页滚。 */
|
||||||
|
.page-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.page-wrapper > .main-container {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
.ops-top {
|
.ops-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -395,14 +443,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 +463,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 +474,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 +501,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 {
|
||||||
@@ -477,15 +527,26 @@ function openPreview(item: { name?: string; filename?: string; fileid?: number;
|
|||||||
}
|
}
|
||||||
.diagram-wrap img {
|
.diagram-wrap img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
/* 嵌入页 iframe 撑满 Drawer body(body padding 已由 :styles 置 0),嵌入页内部滚动 */
|
||||||
|
.embed-frame-wrap {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.embed-frame-wrap iframe {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
.meta {
|
.meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
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 +555,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 +565,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 +577,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 +595,42 @@ 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;
|
||||||
|
/* 正文区最小高度:短通知别让附件/按钮紧贴一行字(旧版用 clientHeight-356 铺满视口,
|
||||||
|
会把附件顶出首屏;这里只留"呼吸区",剩余空白落在按钮下方的卡片底部) */
|
||||||
|
min-height: 200px;
|
||||||
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 +639,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 +675,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 +691,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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch, h } from "vue";
|
import { ref, computed, onMounted, watch, h, resolveComponent } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useRequest } from "alova/client";
|
import { useRequest } from "alova/client";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
@@ -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();
|
||||||
@@ -32,9 +33,10 @@ const auditStatusOptions = [
|
|||||||
{ value: -1, label: "无需审核" },
|
{ value: -1, label: "无需审核" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function fmtDate(d: Date) {
|
function fmtDate(d: Date | string) {
|
||||||
|
const dt = new Date(d);
|
||||||
const pad = (n: number) => String(n).padStart(2, "0");
|
const pad = (n: number) => String(n).padStart(2, "0");
|
||||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============ 分页(规范 §3:customPageCount 页长记忆) ============ */
|
/* ============ 分页(规范 §3:customPageCount 页长记忆) ============ */
|
||||||
@@ -54,8 +56,9 @@ async function run() {
|
|||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
orderByTime: "1",
|
orderByTime: "1",
|
||||||
};
|
};
|
||||||
if (startDt.value) params.startTime = startDt.value;
|
// DatePicker 选过日期后 v-model 值是 Date 对象,后端 LocalDate 只认 yyyy-MM-dd → 统一格式化
|
||||||
if (endDt.value) params.endTime = endDt.value;
|
if (startDt.value) params.startTime = fmtDate(new Date(startDt.value));
|
||||||
|
if (endDt.value) params.endTime = fmtDate(new Date(endDt.value));
|
||||||
if (auditType.value !== "" && auditType.value !== null) params.auditStatus = Number(auditType.value);
|
if (auditType.value !== "" && auditType.value !== null) params.auditStatus = Number(auditType.value);
|
||||||
if (!isManage.value) {
|
if (!isManage.value) {
|
||||||
params.scope = "user";
|
params.scope = "user";
|
||||||
@@ -143,9 +146,10 @@ function del(row: NoticeVo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 附件下载:逐个触发(间隔 500ms,同旧版) */
|
/* 附件下载:逐个触发(间隔 500ms,同旧版) */
|
||||||
function downloadFiles(row: NoticeVo & { UpFile?: string }) {
|
function downloadFiles(row: NoticeVo) {
|
||||||
if (!row.UpFile) return;
|
const upFile = row.upFile ?? row.UpFile;
|
||||||
row.UpFile.split(",").forEach((id: string, i: number) => {
|
if (!upFile) return;
|
||||||
|
upFile.split(",").forEach((id: string, i: number) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = noticeApi.fileDownUrl(Number(id));
|
window.location.href = noticeApi.fileDownUrl(Number(id));
|
||||||
}, i * 500);
|
}, i * 500);
|
||||||
@@ -170,9 +174,10 @@ 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" };
|
// 色系对齐流程侧:待提交灰 / 审批中橙 / 已通过绿 / 已驳回红 / 已撤回灰
|
||||||
return h("Tag", { color: colors[st] || "default" }, () => p.row.auditStatusDesc || "—");
|
const colors: Record<number, string> = { 0: "default", 10: "warning", 20: "success", 30: "error", 40: "default" };
|
||||||
|
return h(resolveComponent("Tag"), { color: colors[st] || "default" }, () => p.row.auditStatusDesc || "—");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ title: "已读人数", key: "readCount", width: 110, align: "left" },
|
{ title: "已读人数", key: "readCount", width: 110, align: "left" },
|
||||||
@@ -187,9 +192,9 @@ const columns = [
|
|||||||
width: 70,
|
width: 70,
|
||||||
align: "center",
|
align: "center",
|
||||||
render: (h: any, p: any) =>
|
render: (h: any, p: any) =>
|
||||||
p.row.UpFile
|
(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: "操作",
|
||||||
@@ -197,21 +202,23 @@ const columns = [
|
|||||||
align: "center",
|
align: "center",
|
||||||
fixed: "right",
|
fixed: "right",
|
||||||
render: (h: any, p: any) => {
|
render: (h: any, p: any) => {
|
||||||
// BP-R5-1:审批中(audit=10)禁止删除(后端 403)——不再渲染死按钮,审批通过/驳回/撤回后恢复
|
// BP-R5-1:审批中(audit=10)禁止删除(后端 403)——渲染禁用态而非隐藏,保持操作列对齐
|
||||||
const ops = [Utils.operatingHandle(h, "查看通知", "icon-browse", () => openDetail(p.row))];
|
const delDisabled = p.row.auditStatus === 10;
|
||||||
if (p.row.auditStatus !== 10) {
|
return [
|
||||||
ops.push(Utils.operatingHandle(h, "删除通知", "icon-ashbin", () => del(p.row)));
|
Utils.operatingHandle(h, "查看通知", "icon-browse", () => openDetail(p.row)),
|
||||||
}
|
Utils.operatingHandle(h, "删除通知", "icon-ashbin", () => del(p.row), delDisabled),
|
||||||
return ops;
|
];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
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 +257,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,10 @@ 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 { fitDiagram } from "@/utils/diagramImg";
|
||||||
|
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) {
|
||||||
@@ -79,9 +81,30 @@ function unbindFlow() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 流程图(flow-center 示意图深链,与发通知页同一入口) */
|
/** 流程图 Drawer(后端 DiagramDef 中转 PNG:同源免 token 免跨域。修复旧深链 404——
|
||||||
function openFlowDiagram(guid: string) {
|
* 生产前端静态挂 /flow-center/front*,Caddy 无 /flow-center/ 根路由,window.open 深链必 404) */
|
||||||
if (guid) window.open(`${config.flowHost}/#/diagram?flowGuid=${guid}`, "_blank");
|
const flowDiagramVisible = ref(false);
|
||||||
|
const flowDiagramEl = ref<HTMLImageElement | null>(null);
|
||||||
|
const flowDiagramGuid = ref(""); // 打开抽屉时定格的流程 GUID(绑定态=boundFlowGuid,预览态=selectedGuid)
|
||||||
|
const flowDiagramSrc = computed(() =>
|
||||||
|
flowDiagramVisible.value && flowDiagramGuid.value
|
||||||
|
? `${config.serviceHost}/api/notice/flow/DiagramDef?flowGuid=${encodeURIComponent(flowDiagramGuid.value)}`
|
||||||
|
: "",
|
||||||
|
);
|
||||||
|
function openFlowDiagram() {
|
||||||
|
if (!boundFlowGuid.value) return;
|
||||||
|
flowDiagramGuid.value = boundFlowGuid.value;
|
||||||
|
flowDiagramVisible.value = true;
|
||||||
|
}
|
||||||
|
/** 预览下拉当前选中(未必已保存)的流程 */
|
||||||
|
function previewSelectedFlow() {
|
||||||
|
if (!selectedGuid.value) return;
|
||||||
|
flowDiagramGuid.value = selectedGuid.value;
|
||||||
|
flowDiagramVisible.value = true;
|
||||||
|
}
|
||||||
|
function onFlowDiagramError() {
|
||||||
|
flowDiagramVisible.value = false;
|
||||||
|
Message.error("流程图加载失败(流程可能未发布或绑定已解除)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前绑定流程的节点摘要(下拉里回显) */
|
/** 当前绑定流程的节点摘要(下拉里回显) */
|
||||||
@@ -397,19 +420,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 +463,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 +506,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">
|
||||||
绑定说明:绑定后「发送全部用户」的通知必须审核通过才发布(发通知页只显示「提交审核」按钮,
|
绑定说明:绑定后「发送全部用户」的通知必须审核通过才发布(发通知页只显示「提交审核」按钮,
|
||||||
隐藏发送与定时发送);「发送指定人员」不受影响,仍直接发布。未绑定则保持现有发送方式。
|
隐藏发送与定时发送);「发送指定人员」不受影响,仍直接发布。未绑定则保持现有发送方式。
|
||||||
@@ -502,6 +530,7 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
<template v-if="flowCanEdit">
|
<template v-if="flowCanEdit">
|
||||||
<Button type="primary" :loading="bindingSaving" @click="saveFlowBinding">保存绑定</Button>
|
<Button type="primary" :loading="bindingSaving" @click="saveFlowBinding">保存绑定</Button>
|
||||||
<Button v-if="boundFlowGuid" @click="unbindFlow">解除绑定</Button>
|
<Button v-if="boundFlowGuid" @click="unbindFlow">解除绑定</Button>
|
||||||
|
<Button v-if="selectedGuid" @click="previewSelectedFlow">查看流程图</Button>
|
||||||
</template>
|
</template>
|
||||||
<span v-else class="readonly-hint">仅本校管理员可修改流程绑定</span>
|
<span v-else class="readonly-hint">仅本校管理员可修改流程绑定</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -511,7 +540,7 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
已绑定
|
已绑定
|
||||||
<Tag color="blue">{{ boundFlowName || boundFlowGuid }}</Tag>
|
<Tag color="blue">{{ boundFlowName || boundFlowGuid }}</Tag>
|
||||||
<span class="node-summary">{{ boundFlowSummary }}</span>
|
<span class="node-summary">{{ boundFlowSummary }}</span>
|
||||||
<Button type="text" size="small" @click="openFlowDiagram(boundFlowGuid)">查看流程图</Button>
|
<Button type="text" size="small" @click="openFlowDiagram">查看流程图</Button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Tag>未绑定</Tag>
|
<Tag>未绑定</Tag>
|
||||||
@@ -520,6 +549,15 @@ const groupTableHeight = computed(() => Utils.useTableHeight({ isTabs: true, isS
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 流程图 Drawer(后端 DiagramDef 同源 PNG;竖版长图容器内纵向滚动) -->
|
||||||
|
<Drawer v-model="flowDiagramVisible" title="流程示意图" :width="860" :transfer="false"
|
||||||
|
:styles="{ body: { padding: '0' } }">
|
||||||
|
<div class="flow-diagram-frame">
|
||||||
|
<img ref="flowDiagramEl" v-if="flowDiagramSrc" :src="flowDiagramSrc" alt="流程示意图" @load="fitDiagram(flowDiagramEl)" @error="onFlowDiagramError" />
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
<!-- 新增发文部门 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 +639,37 @@ 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);
|
||||||
|
}
|
||||||
|
/* 流程图 Drawer:浅底色 + 纵向滚动(竖版长图),图片撑满可读宽度 */
|
||||||
|
.flow-diagram-frame {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.flow-diagram-frame img {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
</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;
|
||||||
|
|||||||
+12
-1
@@ -73,7 +73,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 +89,9 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
// 主题接入已免插件化(A 类,YschTheme《管理端主题远程化与公共静态通道方案》§11):
|
||||||
|
// 唯一接入点 = index.html 一段同步 loader,挂载/FOUC/CSS 排序全由通道 init 承担;
|
||||||
|
// 下面的 /static 代理只是 dev 联调便利(prod 同源用不到),删除不影响线上主题
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
@@ -106,6 +108,15 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
target: "http://localhost:8101",
|
target: "http://localhost:8101",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
// 主题通道本地联调:dev 下 loader 走同源相对(见 index.html 注释的优先级四档),
|
||||||
|
// manifest/CSS 经此代理到中央通道;changeOrigin 会把 Host 改写成目标域,Caddy 按 Host 匹配站点,
|
||||||
|
// 无需再手工补 Host 头(这是旧 vite 插件里那段 headers.Host 的等价替代)。
|
||||||
|
// 联调本地通道源:YschTheme 侧 npm run serve:static(默认 9527),再用
|
||||||
|
// YH_THEME_PROXY_TARGET=http://localhost:9527 覆盖本 target
|
||||||
|
"/static": {
|
||||||
|
target: process.env.YH_THEME_PROXY_TARGET || "http://base.yunhaixingtong.com",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// css预处理器
|
// css预处理器
|
||||||
|
|||||||
Reference in New Issue
Block a user