refactor: 优化代码规范和可读性
This commit is contained in:
@@ -1 +1,2 @@
|
||||
npx --no-install commitlint --edit $1
|
||||
#!/usr/bin/env sh
|
||||
npx --no-install commitlint --edit "$1"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
npm run lint:lint-staged
|
||||
#!/usr/bin/env sh
|
||||
npm run lint:lint-staged
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||

|
||||
|
||||
<div align="center">
|
||||
<a target="_blank" href="https://vue.youlai.tech">🖥️ 在线预览</a> | <a target="_blank" href="https://app.youlai.tech">📲 移动端预览</a> | <a target="_blank" href="https://juejin.cn/post/7228990409909108793">📑 阅读文档</a>| <a target="_blank" href="https://www.youlai.tech//vue3-element-admin">🌐 官网</a> | <a href="./README.en-US.md">💬 English
|
||||
<a target="_blank" href="https://vue.youlai.tech">🖥️ 在线预览</a> | <a target="_blank" href="https://app.youlai.tech">📲 移动端预览</a> | <a target="_blank" href="https://juejin.cn/post/7228990409909108793">📑 阅读文档</a>| <a target="_blank" href="https://www.youlai.tech/docs/web/">🌐 官网</a> | <a href="./README.en-US.md">💬 English
|
||||
</div>
|
||||
|
||||
## 项目简介
|
||||
@@ -100,7 +100,7 @@
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 视频教程 | [https://www.bilibili.com/video/BV1eFUuYyEFj](https://www.bilibili.com/video/BV1eFUuYyEFj) |
|
||||
| 项目搭建 | [基于 Vue3 + Vite + TypeScript + Element-Plus 从0到1搭建后台管理系统](https://blog.csdn.net/u013737132/article/details/130191394) |
|
||||
| 官方文档 | [https://www.youlai.tech/vue3-element-admin](https://www.youlai.tech/vue3-element-admin/) |
|
||||
| 官方文档 | [https://www.youlai.tech/docs/web/](https://www.youlai.tech/docs/web/) |
|
||||
| 代码规范 | [ESLint V9 + Prettier + Stylelint + EditorConfig 约束和统一前端代码规范](https://youlai.blog.csdn.net/article/details/145608723) |
|
||||
| 提交规范 | [Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范](https://youlai.blog.csdn.net/article/details/145615236) |
|
||||
| 接口文档 | [https://www.apifox.cn](https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5) |
|
||||
@@ -177,7 +177,7 @@ server {
|
||||
|
||||
1. 获取基于 `Java` 和 `SpringBoot` 开发的后端 [youlai-boot](https://gitee.com/youlaiorg/youlai-boot.git) 源码。
|
||||
2. 根据后端工程的说明文档 [README.md](https://gitee.com/youlaiorg/youlai-boot#%E9%A1%B9%E7%9B%AE%E8%BF%90%E8%A1%8C) 完成本地启动。
|
||||
3. 修改 `.env.development` 文件中的 `VITE_APP_API_URL` 的值,将其从 https://api.youlai.tech 更改为 http://localhost:8989 即可。
|
||||
3. 修改 `.env.development` 文件中的 `VITE_APP_API_URL` 的值,将其从 https://api.youlai.tech 更改为 http://localhost:8000 即可。
|
||||
|
||||
## 注意事项
|
||||
|
||||
@@ -223,7 +223,7 @@ Thanks to all the contributors!
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="./src/assets/images/qr/wechat-offical.png" height="180" alt="公众号「有来技术」"><br>
|
||||
<img src="./src/assets/images/qr/wechat-official.png" height="180" alt="公众号「有来技术」"><br>
|
||||
<sub>公众号「有来技术」</sub>
|
||||
</td>
|
||||
<td> </td>
|
||||
|
||||
@@ -107,8 +107,8 @@ export default [
|
||||
},
|
||||
rules: {
|
||||
// 基础规则
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-console": ["error", { allow: ["warn", "error", "debug"] }],
|
||||
"no-debugger": "error",
|
||||
|
||||
// ES6+ 规则
|
||||
"prefer-const": "error",
|
||||
@@ -116,7 +116,7 @@ export default [
|
||||
"object-shorthand": "error",
|
||||
|
||||
// 最佳实践
|
||||
eqeqeq: "off",
|
||||
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": ["error", { max: 1, maxBOF: 0, maxEOF: 0 }],
|
||||
|
||||
@@ -171,7 +171,7 @@ export default [
|
||||
},
|
||||
],
|
||||
"vue/component-name-in-template-casing": ["error", "PascalCase"],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -189,7 +189,7 @@ export default [
|
||||
},
|
||||
rules: {
|
||||
// TypeScript 规则
|
||||
"@typescript-eslint/no-explicit-any": "off", // 允许使用any类型,方便开发
|
||||
"@typescript-eslint/no-explicit-any": "warn", // 逐步收敛 any 类型
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
|
||||
@@ -145,7 +145,7 @@ export default defineMock([
|
||||
|
||||
let list = null;
|
||||
|
||||
if (dictCode == "gender") {
|
||||
if (dictCode === "gender") {
|
||||
list = [
|
||||
{
|
||||
value: "1",
|
||||
@@ -160,7 +160,7 @@ export default defineMock([
|
||||
label: "保密",
|
||||
},
|
||||
];
|
||||
} else if (dictCode == "notice_level") {
|
||||
} else if (dictCode === "notice_level") {
|
||||
list = [
|
||||
{
|
||||
value: "L",
|
||||
@@ -178,7 +178,7 @@ export default defineMock([
|
||||
tagType: "D",
|
||||
},
|
||||
];
|
||||
} else if (dictCode == "notice_type") {
|
||||
} else if (dictCode === "notice_type") {
|
||||
list = [
|
||||
{
|
||||
value: "1",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<el-config-provider :locale="locale" :size="size">
|
||||
<!-- 开启水印-->
|
||||
<el-watermark
|
||||
:font="{ color: fontColor }"
|
||||
:content="showWatermark ? watermarkContent : ''"
|
||||
|
||||
@@ -6,7 +6,10 @@ const AUTH_BASE_URL = "/api/v1/auth";
|
||||
const AuthAPI = {
|
||||
/** 登录接口*/
|
||||
login(data: LoginRequest) {
|
||||
const payload: Record<string, any> = {
|
||||
const payload: Pick<
|
||||
LoginRequest,
|
||||
"username" | "password" | "captchaId" | "captchaCode" | "tenantId"
|
||||
> = {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
captchaId: data.captchaId,
|
||||
@@ -18,7 +21,7 @@ const AuthAPI = {
|
||||
payload.tenantId = data.tenantId;
|
||||
}
|
||||
|
||||
return request<any, LoginResponse>({
|
||||
return request<unknown, LoginResponse>({
|
||||
url: `${AUTH_BASE_URL}/login`,
|
||||
method: "post",
|
||||
data: payload,
|
||||
@@ -27,7 +30,7 @@ const AuthAPI = {
|
||||
|
||||
/** 切换租户(平台用户) - 返回新的 token */
|
||||
switchTenant(tenantId: number) {
|
||||
return request<any, LoginResponse>({
|
||||
return request<unknown, LoginResponse>({
|
||||
url: `${AUTH_BASE_URL}/switch-tenant`,
|
||||
method: "post",
|
||||
params: { tenantId },
|
||||
@@ -36,7 +39,7 @@ const AuthAPI = {
|
||||
|
||||
/** 刷新 token 接口*/
|
||||
refreshToken(refreshToken: string) {
|
||||
return request<any, LoginResponse>({
|
||||
return request<unknown, LoginResponse>({
|
||||
url: `${AUTH_BASE_URL}/refresh-token`,
|
||||
method: "post",
|
||||
params: { refreshToken },
|
||||
@@ -56,7 +59,7 @@ const AuthAPI = {
|
||||
|
||||
/** 获取验证码接口*/
|
||||
getCaptcha() {
|
||||
return request<any, CaptchaInfo>({
|
||||
return request<unknown, CaptchaInfo>({
|
||||
url: `${AUTH_BASE_URL}/captcha`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ const GENERATOR_BASE_URL = "/api/v1/codegen";
|
||||
const GeneratorAPI = {
|
||||
/** 获取数据表分页列表 */
|
||||
getTablePage(params: TableQueryParams) {
|
||||
return request<any, PageResult<TableItem>>({
|
||||
return request<unknown, PageResult<TableItem>>({
|
||||
url: `${GENERATOR_BASE_URL}/table`,
|
||||
method: "get",
|
||||
params,
|
||||
@@ -15,7 +15,7 @@ const GeneratorAPI = {
|
||||
|
||||
/** 获取代码生成配置 */
|
||||
getGenConfig(tableName: string) {
|
||||
return request<any, GenConfigForm>({
|
||||
return request<unknown, GenConfigForm>({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/config`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -39,7 +39,7 @@ const GeneratorAPI = {
|
||||
if (type) {
|
||||
params.type = type;
|
||||
}
|
||||
return request<any, GeneratorPreviewItem[]>({
|
||||
return request<unknown, GeneratorPreviewItem[]>({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/preview`,
|
||||
method: "get",
|
||||
params: Object.keys(params).length ? params : undefined,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
/** API 响应结构 */
|
||||
export interface ApiResponse<T = any> {
|
||||
export interface ApiResponse<T = unknown> {
|
||||
/** 响应码 */
|
||||
code: string;
|
||||
/** 响应数据 */
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { FileInfo } from "./types";
|
||||
const FileAPI = {
|
||||
/** 上传文件 (传入 FormData,上传进度回调) */
|
||||
upload(formData: FormData, onProgress?: (percent: number) => void) {
|
||||
return request<any, FileInfo>({
|
||||
return request<unknown, FileInfo>({
|
||||
url: "/api/v1/files",
|
||||
method: "post",
|
||||
data: formData,
|
||||
@@ -22,7 +22,7 @@ const FileAPI = {
|
||||
uploadFile(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request<any, FileInfo>({
|
||||
return request<unknown, FileInfo>({
|
||||
url: "/api/v1/files",
|
||||
method: "post",
|
||||
data: formData,
|
||||
|
||||
@@ -6,7 +6,7 @@ const CONFIG_BASE_URL = "/api/v1/configs";
|
||||
const ConfigAPI = {
|
||||
/** 获取配置分页数据 */
|
||||
getPage(queryParams?: ConfigQueryParams) {
|
||||
return request<any, PageResult<ConfigItem>>({
|
||||
return request<unknown, PageResult<ConfigItem>>({
|
||||
url: `${CONFIG_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -14,7 +14,7 @@ const ConfigAPI = {
|
||||
},
|
||||
/** 获取配置表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, ConfigForm>({
|
||||
return request<unknown, ConfigForm>({
|
||||
url: `${CONFIG_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const DEPT_BASE_URL = "/api/v1/depts";
|
||||
const DeptAPI = {
|
||||
/** 获取部门树形列表 */
|
||||
getList(queryParams?: DeptQueryParams) {
|
||||
return request<any, DeptItem[]>({
|
||||
return request<unknown, DeptItem[]>({
|
||||
url: `${DEPT_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -15,11 +15,11 @@ const DeptAPI = {
|
||||
},
|
||||
/** 获取部门下拉数据源 */
|
||||
getOptions() {
|
||||
return request<any, OptionItem[]>({ url: `${DEPT_BASE_URL}/options`, method: "get" });
|
||||
return request<unknown, OptionItem[]>({ url: `${DEPT_BASE_URL}/options`, method: "get" });
|
||||
},
|
||||
/** 获取部门表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, DeptForm>({ url: `${DEPT_BASE_URL}/${id}/form`, method: "get" });
|
||||
return request<unknown, DeptForm>({ url: `${DEPT_BASE_URL}/${id}/form`, method: "get" });
|
||||
},
|
||||
/** 新增部门 */
|
||||
create(data: DeptForm) {
|
||||
|
||||
@@ -12,9 +12,11 @@ import type { OptionItem } from "@/api/common";
|
||||
|
||||
const DICT_BASE_URL = "/api/v1/dicts";
|
||||
|
||||
type DictTagType = DictItemForm["tagType"];
|
||||
type DictTagTypeCode = "N" | "P" | "S" | "W" | "I" | "D";
|
||||
|
||||
const decodeDictTagType = (code?: unknown): DictItemForm["tagType"] => {
|
||||
// 将后端标签类型转换为前端标签类型
|
||||
const decodeDictTagType = (code?: unknown): DictTagType => {
|
||||
const val = String(code ?? "")
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
@@ -35,6 +37,7 @@ const decodeDictTagType = (code?: unknown): DictItemForm["tagType"] => {
|
||||
}
|
||||
};
|
||||
|
||||
// 将前端标签类型转换为后端标签类型
|
||||
const encodeDictTagType = (tagType?: unknown): DictTagTypeCode => {
|
||||
const val = String(tagType ?? "")
|
||||
.trim()
|
||||
@@ -58,10 +61,20 @@ const encodeDictTagType = (tagType?: unknown): DictTagTypeCode => {
|
||||
}
|
||||
};
|
||||
|
||||
// 统一转换字典项标签类型
|
||||
const normalizeDictTagType = <T extends { tagType?: unknown }>(
|
||||
item: T
|
||||
): Omit<T, "tagType"> & {
|
||||
tagType: DictTagType;
|
||||
} => ({
|
||||
...item,
|
||||
tagType: decodeDictTagType(item.tagType),
|
||||
});
|
||||
|
||||
const DictAPI = {
|
||||
/** 字典分页列表 */
|
||||
getPage(queryParams: DictTypeQueryParams) {
|
||||
return request<any, PageResult<DictTypeItem>>({
|
||||
return request<unknown, PageResult<DictTypeItem>>({
|
||||
url: `${DICT_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -69,11 +82,11 @@ const DictAPI = {
|
||||
},
|
||||
/** 字典列表 */
|
||||
getList() {
|
||||
return request<any, OptionItem[]>({ url: `${DICT_BASE_URL}/options`, method: "get" });
|
||||
return request<unknown, OptionItem[]>({ url: `${DICT_BASE_URL}/options`, method: "get" });
|
||||
},
|
||||
/** 字典表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, DictTypeForm>({ url: `${DICT_BASE_URL}/${id}/form`, method: "get" });
|
||||
return request<unknown, DictTypeForm>({ url: `${DICT_BASE_URL}/${id}/form`, method: "get" });
|
||||
},
|
||||
/** 新增字典 */
|
||||
create(data: DictTypeForm) {
|
||||
@@ -90,29 +103,21 @@ const DictAPI = {
|
||||
|
||||
/** 获取字典项分页列表 */
|
||||
getDictItemPage(dictCode: string, queryParams: DictItemQueryParams) {
|
||||
return request<any, PageResult<DictItem>>({
|
||||
return request<unknown, PageResult<DictItem>>({
|
||||
url: `${DICT_BASE_URL}/${dictCode}/items`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
}).then((data) => ({
|
||||
...data,
|
||||
list: (data.list ?? []).map((item: DictItem & { tagType?: string }) => ({
|
||||
...item,
|
||||
tagType: decodeDictTagType(item.tagType),
|
||||
})),
|
||||
list: (data.list ?? []).map(normalizeDictTagType),
|
||||
}));
|
||||
},
|
||||
/** 获取字典项列表 */
|
||||
getDictItems(dictCode: string) {
|
||||
return request<any, DictItemOption[]>({
|
||||
return request<unknown, DictItemOption[]>({
|
||||
url: `${DICT_BASE_URL}/${dictCode}/items/options`,
|
||||
method: "get",
|
||||
}).then((items) =>
|
||||
(items ?? []).map((item) => ({
|
||||
...item,
|
||||
tagType: decodeDictTagType((item as any).tagType),
|
||||
}))
|
||||
);
|
||||
}).then((items) => (items ?? []).map(normalizeDictTagType));
|
||||
},
|
||||
/** 新增字典项 */
|
||||
createDictItem(dictCode: string, data: DictItemForm) {
|
||||
@@ -121,19 +126,16 @@ const DictAPI = {
|
||||
method: "post",
|
||||
data: {
|
||||
...data,
|
||||
tagType: encodeDictTagType((data as any).tagType),
|
||||
tagType: encodeDictTagType(data.tagType),
|
||||
},
|
||||
});
|
||||
},
|
||||
/** 获取字典项表单数据 */
|
||||
getDictItemFormData(dictCode: string, id: string) {
|
||||
return request<any, DictItemForm>({
|
||||
return request<unknown, DictItemForm>({
|
||||
url: `${DICT_BASE_URL}/${dictCode}/items/${id}/form`,
|
||||
method: "get",
|
||||
}).then((form) => ({
|
||||
...form,
|
||||
tagType: decodeDictTagType((form as any).tagType),
|
||||
}));
|
||||
}).then(normalizeDictTagType);
|
||||
},
|
||||
/** 修改字典项 */
|
||||
updateDictItem(dictCode: string, id: string, data: DictItemForm) {
|
||||
@@ -142,7 +144,7 @@ const DictAPI = {
|
||||
method: "put",
|
||||
data: {
|
||||
...data,
|
||||
tagType: encodeDictTagType((data as any).tagType),
|
||||
tagType: encodeDictTagType(data.tagType),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ const LOG_BASE_URL = "/api/v1/logs";
|
||||
const LogAPI = {
|
||||
/** 获取日志分页列表 */
|
||||
getPage(queryParams: LogQueryParams) {
|
||||
return request<any, PageResult<LogItem>>({
|
||||
return request<unknown, PageResult<LogItem>>({
|
||||
url: `${LOG_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -21,7 +21,7 @@ const LogAPI = {
|
||||
|
||||
/** 获取访问趋势统计 */
|
||||
getVisitTrend(queryParams: VisitTrendQueryParams) {
|
||||
return request<any, VisitTrendDetail>({
|
||||
return request<unknown, VisitTrendDetail>({
|
||||
url: `${LOG_BASE_URL}/analytics/trend`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -30,7 +30,7 @@ const LogAPI = {
|
||||
|
||||
/** 获取访问概览统计 */
|
||||
getVisitOverview() {
|
||||
return request<any, VisitOverviewDetail>({
|
||||
return request<unknown, VisitOverviewDetail>({
|
||||
url: `${LOG_BASE_URL}/analytics/overview`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
@@ -7,11 +7,11 @@ const MENU_BASE_URL = "/api/v1/menus";
|
||||
const MenuAPI = {
|
||||
/** 获取当前用户的路由列表 */
|
||||
getRoutes() {
|
||||
return request<any, RouteItem[]>({ url: `${MENU_BASE_URL}/routes`, method: "get" });
|
||||
return request<unknown, RouteItem[]>({ url: `${MENU_BASE_URL}/routes`, method: "get" });
|
||||
},
|
||||
/** 获取菜单树形列表 */
|
||||
getList(queryParams: MenuQueryParams) {
|
||||
return request<any, MenuItem[]>({
|
||||
return request<unknown, MenuItem[]>({
|
||||
url: `${MENU_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -19,7 +19,7 @@ const MenuAPI = {
|
||||
},
|
||||
/** 获取菜单下拉数据源 */
|
||||
getOptions(onlyParent?: boolean, scope?: number) {
|
||||
return request<any, OptionItem[]>({
|
||||
return request<unknown, OptionItem[]>({
|
||||
url: `${MENU_BASE_URL}/options`,
|
||||
method: "get",
|
||||
params: { onlyParent, scope },
|
||||
@@ -27,7 +27,7 @@ const MenuAPI = {
|
||||
},
|
||||
/** 获取菜单表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, MenuForm>({ url: `${MENU_BASE_URL}/${id}/form`, method: "get" });
|
||||
return request<unknown, MenuForm>({ url: `${MENU_BASE_URL}/${id}/form`, method: "get" });
|
||||
},
|
||||
/** 新增菜单 */
|
||||
create(data: MenuForm) {
|
||||
|
||||
@@ -109,7 +109,7 @@ export interface Meta {
|
||||
/** 【菜单】是否开启页面缓存 */
|
||||
keepAlive?: boolean;
|
||||
/** 路由参数 */
|
||||
params?: Record<string, any>;
|
||||
params?: Record<string, unknown>;
|
||||
/** 角色集合 */
|
||||
roles?: string[];
|
||||
/** 路由title */
|
||||
|
||||
@@ -6,7 +6,7 @@ const NOTICE_BASE_URL = "/api/v1/notices";
|
||||
const NoticeAPI = {
|
||||
/** 获取通知公告分页数据 */
|
||||
getPage(queryParams?: NoticeQueryParams) {
|
||||
return request<any, PageResult<NoticeItem>>({
|
||||
return request<unknown, PageResult<NoticeItem>>({
|
||||
url: `${NOTICE_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -14,7 +14,7 @@ const NoticeAPI = {
|
||||
},
|
||||
/** 获取通知公告表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, NoticeForm>({ url: `${NOTICE_BASE_URL}/${id}/form`, method: "get" });
|
||||
return request<unknown, NoticeForm>({ url: `${NOTICE_BASE_URL}/${id}/form`, method: "get" });
|
||||
},
|
||||
/** 添加通知公告 */
|
||||
create(data: NoticeForm) {
|
||||
@@ -38,7 +38,10 @@ const NoticeAPI = {
|
||||
},
|
||||
/** 查看通知 */
|
||||
getDetail(id: string) {
|
||||
return request<any, NoticeDetail>({ url: `${NOTICE_BASE_URL}/${id}/detail`, method: "get" });
|
||||
return request<unknown, NoticeDetail>({
|
||||
url: `${NOTICE_BASE_URL}/${id}/detail`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
/** 全部已读 */
|
||||
readAll() {
|
||||
@@ -46,7 +49,7 @@ const NoticeAPI = {
|
||||
},
|
||||
/** 获取我的通知分页列表 */
|
||||
getMyNoticePage(queryParams?: NoticeQueryParams) {
|
||||
return request<any, PageResult<NoticeItem>>({
|
||||
return request<unknown, PageResult<NoticeItem>>({
|
||||
url: `${NOTICE_BASE_URL}/my`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
|
||||
@@ -7,7 +7,7 @@ const ROLE_BASE_URL = "/api/v1/roles";
|
||||
const RoleAPI = {
|
||||
/** 获取角色分页数据 */
|
||||
getPage(queryParams?: RoleQueryParams) {
|
||||
return request<any, PageResult<RoleItem>>({
|
||||
return request<unknown, PageResult<RoleItem>>({
|
||||
url: `${ROLE_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -15,11 +15,14 @@ const RoleAPI = {
|
||||
},
|
||||
/** 获取角色下拉数据源 */
|
||||
getOptions() {
|
||||
return request<any, OptionItem[]>({ url: `${ROLE_BASE_URL}/options`, method: "get" });
|
||||
return request<unknown, OptionItem[]>({ url: `${ROLE_BASE_URL}/options`, method: "get" });
|
||||
},
|
||||
/** 获取角色的菜单ID集合 */
|
||||
getRoleMenuIds(roleId: string) {
|
||||
return request<any, string[]>({ url: `${ROLE_BASE_URL}/${roleId}/menu-ids`, method: "get" });
|
||||
return request<unknown, string[]>({
|
||||
url: `${ROLE_BASE_URL}/${roleId}/menu-ids`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
/** 分配菜单权限 */
|
||||
updateRoleMenus(roleId: string, data: number[]) {
|
||||
@@ -27,11 +30,14 @@ const RoleAPI = {
|
||||
},
|
||||
/** 获取角色表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<any, RoleForm>({ url: `${ROLE_BASE_URL}/${id}/form`, method: "get" });
|
||||
return request<unknown, RoleForm>({ url: `${ROLE_BASE_URL}/${id}/form`, method: "get" });
|
||||
},
|
||||
/** 获取角色的部门ID集合(自定义数据权限) */
|
||||
getRoleDeptIds(roleId: string) {
|
||||
return request<any, string[]>({ url: `${ROLE_BASE_URL}/${roleId}/dept-ids`, method: "get" });
|
||||
return request<unknown, string[]>({
|
||||
url: `${ROLE_BASE_URL}/${roleId}/dept-ids`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
/** 新增角色 */
|
||||
create(data: RoleForm) {
|
||||
|
||||
@@ -7,7 +7,7 @@ const TENANT_PLAN_BASE_URL = "/api/v1/tenant-plans";
|
||||
const TenantPlanAPI = {
|
||||
/** 获取租户套餐分页数据 */
|
||||
getPage(queryParams?: TenantPlanQueryParams) {
|
||||
return request<any, PageResult<TenantPlanItem>>({
|
||||
return request<unknown, PageResult<TenantPlanItem>>({
|
||||
url: `${TENANT_PLAN_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -16,7 +16,7 @@ const TenantPlanAPI = {
|
||||
|
||||
/** 获取租户套餐表单数据 */
|
||||
getFormData(planId: string) {
|
||||
return request<any, TenantPlanForm>({
|
||||
return request<unknown, TenantPlanForm>({
|
||||
url: `${TENANT_PLAN_BASE_URL}/${planId}/form`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -39,7 +39,7 @@ const TenantPlanAPI = {
|
||||
|
||||
/** 获取租户方案下拉选项 */
|
||||
getOptions() {
|
||||
return request<any, OptionItem[]>({
|
||||
return request<unknown, OptionItem[]>({
|
||||
url: `${TENANT_PLAN_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -47,7 +47,7 @@ const TenantPlanAPI = {
|
||||
|
||||
/** 获取方案菜单ID集合 */
|
||||
getPlanMenuIds(planId: number) {
|
||||
return request<any, number[]>({
|
||||
return request<unknown, number[]>({
|
||||
url: `${TENANT_PLAN_BASE_URL}/${planId}/menuIds`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ const TenantAPI = {
|
||||
* 获取当前用户可访问的租户列表
|
||||
*/
|
||||
getTenantList() {
|
||||
return request<any, TenantInfo[]>({
|
||||
return request<unknown, TenantInfo[]>({
|
||||
url: `${TENANT_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -29,7 +29,7 @@ const TenantAPI = {
|
||||
* 获取当前租户信息
|
||||
*/
|
||||
getCurrentTenant() {
|
||||
return request<any, TenantInfo>({
|
||||
return request<unknown, TenantInfo>({
|
||||
url: `${TENANT_BASE_URL}/current`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -41,7 +41,7 @@ const TenantAPI = {
|
||||
* @param tenantId 目标租户ID
|
||||
*/
|
||||
switchTenant(tenantId: number) {
|
||||
return request<any, TenantInfo>({
|
||||
return request<unknown, TenantInfo>({
|
||||
url: `${TENANT_BASE_URL}/${tenantId}/switch`,
|
||||
method: "post",
|
||||
});
|
||||
@@ -49,7 +49,7 @@ const TenantAPI = {
|
||||
|
||||
/** 获取租户分页数据(平台租户管理) */
|
||||
getPage(queryParams?: TenantQueryParams) {
|
||||
return request<any, PageResult<TenantItem>>({
|
||||
return request<unknown, PageResult<TenantItem>>({
|
||||
url: `${TENANT_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -58,7 +58,7 @@ const TenantAPI = {
|
||||
|
||||
/** 获取租户表单数据 */
|
||||
getFormData(tenantId: string) {
|
||||
return request<any, TenantForm>({
|
||||
return request<unknown, TenantForm>({
|
||||
url: `${TENANT_BASE_URL}/${tenantId}/form`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -66,7 +66,7 @@ const TenantAPI = {
|
||||
|
||||
/** 新增租户并初始化默认数据 */
|
||||
create(data: TenantCreateForm) {
|
||||
return request<any, TenantCreateResult>({
|
||||
return request<unknown, TenantCreateResult>({
|
||||
url: `${TENANT_BASE_URL}`,
|
||||
method: "post",
|
||||
data,
|
||||
@@ -101,7 +101,7 @@ const TenantAPI = {
|
||||
|
||||
/** 获取租户菜单ID集合 */
|
||||
getTenantMenuIds(tenantId: number) {
|
||||
return request<any, number[]>({
|
||||
return request<unknown, number[]>({
|
||||
url: `${TENANT_BASE_URL}/${tenantId}/menuIds`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ const UserAPI = {
|
||||
* @returns 登录用户昵称、头像信息,包括角色和权限
|
||||
*/
|
||||
getInfo() {
|
||||
return request<any, UserInfo>({
|
||||
return request<unknown, UserInfo>({
|
||||
url: `${USER_BASE_URL}/me`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -34,7 +34,7 @@ const UserAPI = {
|
||||
* @param queryParams 查询参数
|
||||
*/
|
||||
getPage(queryParams: UserQueryParams) {
|
||||
return request<any, PageResult<UserItem>>({
|
||||
return request<unknown, PageResult<UserItem>>({
|
||||
url: `${USER_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
@@ -48,7 +48,7 @@ const UserAPI = {
|
||||
* @returns 用户表单详情
|
||||
*/
|
||||
getFormData(userId: string) {
|
||||
return request<any, UserForm>({
|
||||
return request<unknown, UserForm>({
|
||||
url: `${USER_BASE_URL}/${userId}/form`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -138,7 +138,7 @@ const UserAPI = {
|
||||
import(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request<any, ExcelResult>({
|
||||
return request<unknown, ExcelResult>({
|
||||
url: `${USER_BASE_URL}/import`,
|
||||
method: "post",
|
||||
data: formData,
|
||||
@@ -150,7 +150,7 @@ const UserAPI = {
|
||||
|
||||
/** 获取个人中心用户信息 */
|
||||
getProfile() {
|
||||
return request<any, UserProfileDetail>({
|
||||
return request<unknown, UserProfileDetail>({
|
||||
url: `${USER_BASE_URL}/profile`,
|
||||
method: "get",
|
||||
});
|
||||
@@ -232,7 +232,7 @@ const UserAPI = {
|
||||
* 获取用户下拉列表
|
||||
*/
|
||||
getOptions() {
|
||||
return request<any, OptionItem[]>({
|
||||
return request<unknown, OptionItem[]>({
|
||||
url: `${USER_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 MiB After Width: | Height: | Size: 4.1 MiB |
@@ -15,56 +15,72 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouteLocationMatched } from "vue-router";
|
||||
import type { RouteLocationMatched } from "vue-router";
|
||||
import { compile } from "path-to-regexp";
|
||||
import router from "@/router";
|
||||
import { translateRouteTitle } from "@/lang/utils";
|
||||
|
||||
type BreadcrumbRoute = {
|
||||
path: string;
|
||||
name?: RouteLocationMatched["name"];
|
||||
redirect?: string;
|
||||
meta: RouteLocationMatched["meta"];
|
||||
};
|
||||
|
||||
const currentRoute = useRoute();
|
||||
// 默认补一个首页面包屑
|
||||
const dashboardRoute: BreadcrumbRoute = { path: "/dashboard", meta: { title: "dashboard" } };
|
||||
|
||||
// 根据当前路由参数生成跳转路径
|
||||
const pathCompile = (path: string) => {
|
||||
const { params } = currentRoute;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
|
||||
const breadcrumbs = ref<Array<RouteLocationMatched>>([]);
|
||||
const breadcrumbs = ref<BreadcrumbRoute[]>([]);
|
||||
|
||||
// 生成面包屑列表
|
||||
function getBreadcrumb() {
|
||||
let matched = currentRoute.matched.filter((item) => item.meta && item.meta.title);
|
||||
let matched: BreadcrumbRoute[] = currentRoute.matched
|
||||
.filter((item) => item.meta && item.meta.title)
|
||||
.map(({ path, name, redirect, meta }) => ({
|
||||
path,
|
||||
name,
|
||||
redirect: typeof redirect === "string" ? redirect : undefined,
|
||||
meta,
|
||||
}));
|
||||
|
||||
const first = matched[0];
|
||||
if (!isDashboard(first)) {
|
||||
matched = [{ path: "/dashboard", meta: { title: "dashboard" } } as any].concat(matched);
|
||||
matched = [dashboardRoute].concat(matched);
|
||||
}
|
||||
breadcrumbs.value = matched.filter((item) => {
|
||||
return item.meta && item.meta.title && item.meta.breadcrumb !== false;
|
||||
});
|
||||
}
|
||||
|
||||
function isDashboard(route: RouteLocationMatched) {
|
||||
const name = route && route.name;
|
||||
// 判断是否为首页路由
|
||||
function isDashboard(route?: BreadcrumbRoute) {
|
||||
const name = route?.name;
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return name.toString().trim().toLocaleLowerCase() === "Dashboard".toLocaleLowerCase();
|
||||
}
|
||||
|
||||
function handleLink(item: any) {
|
||||
// 处理面包屑跳转
|
||||
function handleLink(item: BreadcrumbRoute) {
|
||||
const { redirect, path } = item;
|
||||
if (redirect) {
|
||||
router.push(redirect).then(
|
||||
() => {},
|
||||
(err) => {
|
||||
console.warn(err);
|
||||
}
|
||||
);
|
||||
router.push(redirect).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.push(pathCompile(path)).then(
|
||||
() => {},
|
||||
(err) => {
|
||||
console.warn(err);
|
||||
}
|
||||
);
|
||||
router.push(pathCompile(path)).catch((err) => {
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="flex flex-col md:flex-row justify-between gap-y-2.5 mb-2.5">
|
||||
<!-- 左侧工具 -->
|
||||
<div class="toolbar-left flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
||||
<template v-for="(btn, index) in toolbarLeftBtn" :key="index">
|
||||
<template v-for="(btn, index) in leftToolbarButtons" :key="index">
|
||||
<el-button
|
||||
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||
v-bind="btn.attrs"
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<!-- 右侧工具 -->
|
||||
<div class="toolbar-right flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
||||
<template v-for="(btn, index) in toolbarRightBtn" :key="index">
|
||||
<template v-for="(btn, index) in rightToolbarButtons" :key="index">
|
||||
<el-popover v-if="btn.name === 'filter'" placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<el-button v-bind="btn.attrs"></el-button>
|
||||
@@ -412,13 +412,13 @@ function createToolbar(toolbar: Array<string | IToolsButton>, attr = {}) {
|
||||
}
|
||||
|
||||
// 左侧工具栏按钮
|
||||
const toolbarLeftBtn = computed(() => {
|
||||
const leftToolbarButtons = computed(() => {
|
||||
if (!config.value.toolbar || config.value.toolbar.length === 0) return [];
|
||||
return createToolbar(config.value.toolbar, {});
|
||||
});
|
||||
|
||||
// 右侧工具栏按钮
|
||||
const toolbarRightBtn = computed(() => {
|
||||
const rightToolbarButtons = computed(() => {
|
||||
if (!config.value.defaultToolbar || config.value.defaultToolbar.length === 0) return [];
|
||||
return createToolbar(config.value.defaultToolbar, { circle: true });
|
||||
});
|
||||
@@ -602,7 +602,7 @@ function handleExports() {
|
||||
(buffer) => {
|
||||
saveXlsx(buffer, filename as string);
|
||||
},
|
||||
(error) => console.log(error)
|
||||
(error) => console.error(error)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@@ -616,7 +616,7 @@ function handleExports() {
|
||||
(buffer) => {
|
||||
saveXlsx(buffer, filename as string);
|
||||
},
|
||||
(error) => console.log(error)
|
||||
(error) => console.error(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -750,7 +750,7 @@ function handleImports() {
|
||||
handleRefresh(true);
|
||||
});
|
||||
},
|
||||
(error) => console.log(error)
|
||||
(error) => console.error(error)
|
||||
);
|
||||
} else {
|
||||
ElMessage.error("读取文件失败");
|
||||
|
||||
@@ -106,7 +106,7 @@ const {
|
||||
|
||||
const displayList = computed(() => (results.value.length ? results.value : history.value));
|
||||
|
||||
const handleInputKeydown: (evt: KeyboardEvent | Event) => any = (evt) => {
|
||||
const handleInputKeydown: (evt: KeyboardEvent | Event) => void = (evt) => {
|
||||
if (!(evt instanceof KeyboardEvent)) return;
|
||||
const e = evt;
|
||||
const key = e.key.toLowerCase();
|
||||
|
||||
@@ -31,13 +31,13 @@ const dictStore = useDictStore();
|
||||
* @param value 字典项的值
|
||||
* @returns 包含 label 和 tagType 的对象
|
||||
*/
|
||||
const getLabelAndTagByValue = async (dictCode: string, value: any) => {
|
||||
const getLabelAndTagByValue = async (dictCode: string, value: string | number) => {
|
||||
// 按需加载字典数据
|
||||
await dictStore.loadDictItems(dictCode);
|
||||
// 从缓存中获取字典数据
|
||||
const dictItems = dictStore.getDictItems(dictCode);
|
||||
// 查找对应的字典项
|
||||
const dictItem = dictItems.find((item) => item.value == value);
|
||||
const dictItem = dictItems.find((item) => String(item.value) === String(value));
|
||||
return {
|
||||
label: dictItem?.label || "",
|
||||
tagType: dictItem?.tagType,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<el-dropdown-item
|
||||
v-for="item of sizeOptions"
|
||||
:key="item.value"
|
||||
:disabled="appStore.size == item.value"
|
||||
:disabled="appStore.size === item.value"
|
||||
:command="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
|
||||
@@ -190,9 +190,11 @@ const handleSuccess = (fileInfo: FileInfo, uploadFile: UploadUserFile) => {
|
||||
/**
|
||||
* 上传失败回调
|
||||
*/
|
||||
const handleError = (error: any) => {
|
||||
console.log("handleError");
|
||||
ElMessage.error("上传失败: " + error.message);
|
||||
const getErrorMessage = (error: unknown) =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
ElMessage.error("上传失败: " + getErrorMessage(error));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
class="single-upload__image"
|
||||
:src="modelValue"
|
||||
:preview-src-list="[modelValue]"
|
||||
@click.stop="handlePreview"
|
||||
@click.stop
|
||||
/>
|
||||
<el-icon class="single-upload__delete-btn" @click.stop="handleDelete">
|
||||
<CircleCloseFilled />
|
||||
@@ -148,13 +148,6 @@ function handleUpload(options: UploadRequestOptions) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览图片
|
||||
*/
|
||||
function handlePreview() {
|
||||
console.log("预览图片,停止冒泡");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
*/
|
||||
@@ -175,9 +168,11 @@ const onSuccess = (fileInfo: FileInfo) => {
|
||||
/**
|
||||
* 上传失败回调
|
||||
*/
|
||||
const onError = (error: any) => {
|
||||
console.log("onError");
|
||||
ElMessage.error("上传失败: " + error.message);
|
||||
const getErrorMessage = (error: unknown) =>
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
const onError = (error: unknown) => {
|
||||
ElMessage.error("上传失败: " + getErrorMessage(error));
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -10,7 +10,13 @@ export interface UseSseOptions {
|
||||
maxReconnectAttempts?: number; // 最大重试次数,超过后停止重连
|
||||
}
|
||||
|
||||
type EventHandler = (data: any) => void;
|
||||
type EventHandler = (data: unknown) => void;
|
||||
|
||||
type SseParseState = {
|
||||
currentEvent: string;
|
||||
currentData: string;
|
||||
buffer: string;
|
||||
};
|
||||
|
||||
export enum SseConnectionState {
|
||||
DISCONNECTED = "DISCONNECTED", // 未连接
|
||||
@@ -46,9 +52,14 @@ function createSseConnection(options: UseSseOptions = {}) {
|
||||
|
||||
const eventHandlers = new Map<string, Set<EventHandler>>();
|
||||
|
||||
const log = (...args: any[]) => console.log("[SSE]", ...args);
|
||||
const logError = (...args: any[]) => console.error("[SSE]", ...args);
|
||||
const log = (...args: unknown[]) => {
|
||||
if (config.debug) {
|
||||
console.debug("[SSE]", ...args);
|
||||
}
|
||||
};
|
||||
const logError = (...args: unknown[]) => console.error("[SSE]", ...args);
|
||||
|
||||
// 清理定时器并返回空值
|
||||
const clearTimer = (timer: typeof connectionTimeoutTimer) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
@@ -57,6 +68,74 @@ function createSseConnection(options: UseSseOptions = {}) {
|
||||
return timer;
|
||||
};
|
||||
|
||||
// 重置重连计数和间隔
|
||||
const resetReconnectState = () => {
|
||||
reconnectAttempts = 0;
|
||||
currentReconnectInterval = config.reconnectInterval;
|
||||
};
|
||||
|
||||
// 更新下一次重连间隔
|
||||
const advanceReconnectState = () => {
|
||||
currentReconnectInterval = Math.min(currentReconnectInterval * 2, config.maxReconnectInterval);
|
||||
};
|
||||
|
||||
// 分发一条完整的 SSE 事件
|
||||
const flushSseEvent = (eventName: string, data: string) => {
|
||||
if (!data) return;
|
||||
const handlers = eventHandlers.get(eventName);
|
||||
if (handlers) {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
handlers.forEach((handler) => handler(parsed));
|
||||
} catch {
|
||||
handlers.forEach((handler) => handler(data));
|
||||
}
|
||||
}
|
||||
log(`收到事件[${eventName}]:`, data);
|
||||
};
|
||||
|
||||
// 解析单行 SSE 文本并更新当前事件状态
|
||||
const handleSseLine = (line: string, state: SseParseState) => {
|
||||
if (line.startsWith(":")) return;
|
||||
if (line.startsWith("event:")) {
|
||||
state.currentEvent = line.slice(6).trim() || "message";
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("data:")) {
|
||||
const dataLine = line.slice(5).trim();
|
||||
state.currentData = state.currentData ? `${state.currentData}\n${dataLine}` : dataLine;
|
||||
return;
|
||||
}
|
||||
if (line === "") {
|
||||
flushSseEvent(state.currentEvent, state.currentData);
|
||||
state.currentEvent = "message";
|
||||
state.currentData = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 持续读取 SSE 流并按行解析
|
||||
const consumeSseStream = async (streamReader: ReadableStreamDefaultReader<Uint8Array>) => {
|
||||
const decoder = new TextDecoder();
|
||||
const state: SseParseState = { currentEvent: "message", currentData: "", buffer: "" };
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await streamReader.read();
|
||||
if (done) {
|
||||
connectionState.value = SseConnectionState.DISCONNECTED;
|
||||
log("SSE 连接已关闭");
|
||||
return;
|
||||
}
|
||||
|
||||
state.buffer += decoder.decode(value, { stream: true });
|
||||
const lines = state.buffer.split("\n");
|
||||
state.buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
handleSseLine(line, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 指数退避重连
|
||||
const scheduleReconnect = () => {
|
||||
if (isManualDisconnect) return;
|
||||
@@ -70,13 +149,11 @@ function createSseConnection(options: UseSseOptions = {}) {
|
||||
|
||||
reconnectTimer = setTimeout(() => {
|
||||
connect();
|
||||
currentReconnectInterval = Math.min(
|
||||
currentReconnectInterval * 2,
|
||||
config.maxReconnectInterval
|
||||
);
|
||||
advanceReconnectState();
|
||||
}, currentReconnectInterval);
|
||||
};
|
||||
|
||||
// 建立 SSE 连接
|
||||
const connect = () => {
|
||||
isManualDisconnect = false;
|
||||
|
||||
@@ -122,66 +199,17 @@ function createSseConnection(options: UseSseOptions = {}) {
|
||||
}
|
||||
connectionTimeoutTimer = clearTimer(connectionTimeoutTimer);
|
||||
connectionState.value = SseConnectionState.CONNECTED;
|
||||
reconnectAttempts = 0;
|
||||
currentReconnectInterval = config.reconnectInterval;
|
||||
resetReconnectState();
|
||||
log("SSE 连接已建立");
|
||||
return response.body?.getReader();
|
||||
})
|
||||
.then((r) => {
|
||||
if (!r) return;
|
||||
reader = r;
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
let currentEvent = "message";
|
||||
let currentData = "";
|
||||
|
||||
// SSE 文本协议解析:event / data / 空行分隔
|
||||
const processChunk = ({
|
||||
done,
|
||||
value,
|
||||
}: ReadableStreamReadResult<Uint8Array>): Promise<void> | void => {
|
||||
if (done) {
|
||||
connectionState.value = SseConnectionState.DISCONNECTED;
|
||||
log("SSE 连接已关闭");
|
||||
return;
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith(":")) continue;
|
||||
if (line.startsWith("event:")) {
|
||||
currentEvent = line.slice(6).trim();
|
||||
} else if (line.startsWith("data:")) {
|
||||
const dataLine = line.slice(5).trim();
|
||||
currentData = currentData ? `${currentData}\n${dataLine}` : dataLine;
|
||||
} else if (line === "") {
|
||||
if (currentData) {
|
||||
const handlers = eventHandlers.get(currentEvent);
|
||||
if (handlers) {
|
||||
try {
|
||||
const data = JSON.parse(currentData);
|
||||
handlers.forEach((h) => h(data));
|
||||
} catch {
|
||||
handlers.forEach((h) => h(currentData));
|
||||
}
|
||||
}
|
||||
log(`收到事件[${currentEvent}]:`, currentData);
|
||||
}
|
||||
currentEvent = "message";
|
||||
currentData = "";
|
||||
}
|
||||
}
|
||||
|
||||
return reader!.read().then(processChunk);
|
||||
};
|
||||
|
||||
return reader.read().then(processChunk);
|
||||
.then((streamReader) => {
|
||||
if (!streamReader) return;
|
||||
reader = streamReader;
|
||||
return consumeSseStream(streamReader);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.name === "AbortError") {
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name === "AbortError") {
|
||||
log("SSE 连接已主动断开");
|
||||
} else {
|
||||
logError("SSE 连接错误:", err);
|
||||
@@ -192,17 +220,18 @@ function createSseConnection(options: UseSseOptions = {}) {
|
||||
};
|
||||
|
||||
// 订阅事件,返回取消函数
|
||||
const on = (eventName: string, handler: EventHandler): (() => void) => {
|
||||
const on = <T = unknown>(eventName: string, handler: (data: T) => void): (() => void) => {
|
||||
if (!eventHandlers.has(eventName)) {
|
||||
eventHandlers.set(eventName, new Set());
|
||||
}
|
||||
eventHandlers.get(eventName)!.add(handler);
|
||||
const wrappedHandler: EventHandler = (data) => handler(data as T);
|
||||
eventHandlers.get(eventName)!.add(wrappedHandler);
|
||||
log(`已订阅事件: ${eventName}`);
|
||||
|
||||
return () => {
|
||||
const handlers = eventHandlers.get(eventName);
|
||||
if (handlers) {
|
||||
handlers.delete(handler);
|
||||
handlers.delete(wrappedHandler);
|
||||
if (handlers.size === 0) {
|
||||
eventHandlers.delete(eventName);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import "animate.css";
|
||||
import { setupDirective } from "@/directives";
|
||||
import { setupRouter } from "@/router";
|
||||
import { setupStore } from "@/stores";
|
||||
import { setupI18n } from "@/lang";
|
||||
|
||||
// ===== 全局组件 =====
|
||||
import * as ElementPlusIcons from "@element-plus/icons-vue";
|
||||
@@ -40,6 +41,7 @@ const app = createApp(App);
|
||||
|
||||
// 1️⃣ 核心配置
|
||||
setupDirective(app);
|
||||
setupI18n(app);
|
||||
setupRouter(app);
|
||||
setupStore(app);
|
||||
|
||||
|
||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -4,7 +4,7 @@
|
||||
* @deprecated 请使用 @/types 下的具名导出
|
||||
*/
|
||||
declare global {
|
||||
type ApiResponse<T = any> = import("@/types/api").ApiResponse<T>;
|
||||
type ApiResponse<T = unknown> = import("@/types/api").ApiResponse<T>;
|
||||
type BaseQueryParams = import("@/types/api").BaseQueryParams;
|
||||
type PageResult<T> = import("@/types/api").PageResult<T>;
|
||||
type OptionItem = import("@/types/api").OptionItem;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { LocationQuery } from "vue-router";
|
||||
|
||||
/**
|
||||
* 标签页相关类型定义
|
||||
*/
|
||||
@@ -10,5 +12,5 @@ export interface TagView {
|
||||
icon?: string;
|
||||
affix?: boolean;
|
||||
keepAlive?: boolean;
|
||||
query?: any;
|
||||
query?: LocationQuery;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Storage {
|
||||
/**
|
||||
* 存储数据到 localStorage
|
||||
*/
|
||||
static set(key: string, value: any): void {
|
||||
static set(key: string, value: unknown): void {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export class Storage {
|
||||
/**
|
||||
* 存储数据到 sessionStorage
|
||||
*/
|
||||
static sessionSet(key: string, value: any): void {
|
||||
static sessionSet(key: string, value: unknown): void {
|
||||
sessionStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
<el-table-column label="字段信息" min-width="360">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="field-info" style=" flex-shrink: 0;width: 140px">
|
||||
<div class="field-info" style="flex-shrink: 0; width: 140px">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm">{{ row.columnName }}</span>
|
||||
<el-tag v-if="row.isPrimaryKey" size="small" type="warning" effect="dark">
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
@filter-change="handleFilterChange"
|
||||
>
|
||||
<template #status="scope">
|
||||
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
|
||||
<el-tag :type="scope.row[scope.prop] === 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #gender="scope">
|
||||
@@ -187,13 +187,11 @@ const contentConfig: IContentConfig<UserQueryParams, UserItem> = reactive({
|
||||
},
|
||||
exportAction: UserAPI.export,
|
||||
importTemplate: UserAPI.downloadTemplate,
|
||||
importsAction(data: any) {
|
||||
console.log("importsAction", data);
|
||||
importsAction() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
async exportsAction(params: any) {
|
||||
const data = await UserAPI.getPage(params);
|
||||
console.log("exportsAction", data.list);
|
||||
return data.list;
|
||||
},
|
||||
pk: "id",
|
||||
@@ -299,9 +297,6 @@ const addModalConfig: IModalConfig<UserForm> = reactive({
|
||||
labelWidth: 100,
|
||||
},
|
||||
formAction: UserAPI.create,
|
||||
beforeSubmit(data: any) {
|
||||
console.log("提交之前处理", data);
|
||||
},
|
||||
formItems: [
|
||||
{
|
||||
label: "用户名",
|
||||
@@ -415,9 +410,6 @@ const editModalConfig: IModalConfig<UserForm> = reactive({
|
||||
size: useAppStore().device === DeviceEnum.MOBILE ? "80%" : 500,
|
||||
},
|
||||
pk: "id",
|
||||
beforeSubmit(data: any) {
|
||||
console.log("beforeSubmit", data);
|
||||
},
|
||||
formAction(data: any) {
|
||||
return UserAPI.update(data.id as string, data);
|
||||
},
|
||||
@@ -539,7 +531,6 @@ const {
|
||||
|
||||
// 其他工具
|
||||
function handleToolbarClick(name: string) {
|
||||
console.log(name);
|
||||
if (name === "custom1") {
|
||||
ElMessage.success("点击了自定义1按钮");
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ const modalConfig: IModalConfig<UserForm> = {
|
||||
labelWidth: 100,
|
||||
},
|
||||
formAction: UserAPI.create,
|
||||
beforeSubmit(data) {
|
||||
console.log("提交之前处理", data);
|
||||
},
|
||||
formItems: [
|
||||
{
|
||||
label: "用户名",
|
||||
|
||||
@@ -24,15 +24,13 @@ const contentConfig: IContentConfig<UserQueryParams, UserItem> = {
|
||||
},
|
||||
exportAction: UserAPI.export,
|
||||
importTemplate: UserAPI.downloadTemplate,
|
||||
importsAction(data) {
|
||||
importsAction() {
|
||||
// 模拟导入数据
|
||||
console.log("importsAction", data);
|
||||
return Promise.resolve();
|
||||
},
|
||||
async exportsAction(params) {
|
||||
// 模拟获取到的是全量数据
|
||||
const data = await UserAPI.getPage(params);
|
||||
console.log("exportsAction", data.list);
|
||||
return data.list;
|
||||
},
|
||||
pk: "id",
|
||||
|
||||
@@ -13,9 +13,6 @@ const modalConfig: IModalConfig<UserForm> = {
|
||||
size: useAppStore().device === DeviceEnum.MOBILE ? "80%" : 500,
|
||||
},
|
||||
pk: "id",
|
||||
beforeSubmit(data) {
|
||||
console.log("beforeSubmit", data);
|
||||
},
|
||||
formAction(data) {
|
||||
return UserAPI.update(data.id as string, data);
|
||||
},
|
||||
|
||||
@@ -30,7 +30,6 @@ const contentConfig: IContentConfig<DemoQueryParams, DemoItem> = {
|
||||
toolbar: [],
|
||||
indexAction(params) {
|
||||
// 模拟发起网络请求获取列表数据
|
||||
console.log("indexAction:", params);
|
||||
const list = [
|
||||
{
|
||||
id: 1,
|
||||
@@ -74,7 +73,6 @@ const contentConfig: IContentConfig<DemoQueryParams, DemoItem> = {
|
||||
},
|
||||
modifyAction(data) {
|
||||
// 模拟发起网络请求修改字段
|
||||
// console.log("modifyAction:", data);
|
||||
ElMessage.success(JSON.stringify(data));
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
|
||||
@@ -12,9 +12,6 @@ const modalConfig: IModalConfig = {
|
||||
size: useAppStore().device === DeviceEnum.MOBILE ? "80%" : 500,
|
||||
},
|
||||
form: { labelPosition: "right", labelWidth: "auto" },
|
||||
beforeSubmit(data) {
|
||||
console.log("beforeSubmit", data);
|
||||
},
|
||||
formAction(data) {
|
||||
// return UserAPI.update(data.id as string, data);
|
||||
// 模拟发起网络请求修改字段
|
||||
|
||||
@@ -15,8 +15,7 @@ const searchConfig: ISearchConfig = {
|
||||
prop: "testInput",
|
||||
attrs: { placeholder: "请输入", clearable: true },
|
||||
events: {
|
||||
change: (e) => {
|
||||
console.log("输入框的值: ", e);
|
||||
change: () => {
|
||||
// 级联操作示例,需要使用reactive提前定义数组
|
||||
// selectOptions.push({ label: e, value: e });
|
||||
},
|
||||
@@ -35,9 +34,7 @@ const searchConfig: ISearchConfig = {
|
||||
attrs: { placeholder: "全部", clearable: true },
|
||||
options: stateArr as any,
|
||||
events: {
|
||||
change(e) {
|
||||
console.log("选中的值: ", e);
|
||||
},
|
||||
change() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
@filter-change="handleFilterChange"
|
||||
>
|
||||
<template #status="scope">
|
||||
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
|
||||
<el-tag :type="scope.row[scope.prop] === 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #gender="scope">
|
||||
@@ -91,8 +91,8 @@
|
||||
@operate-click="handleOperateClick2"
|
||||
>
|
||||
<template #status="scope">
|
||||
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
|
||||
<el-tag :type="scope.row[scope.prop] === 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</page-content>
|
||||
@@ -145,7 +145,6 @@ const {
|
||||
|
||||
// 其他工具
|
||||
function handleToolbarClick(name: string) {
|
||||
console.log(name);
|
||||
if (name === "custom1") {
|
||||
ElMessage.success("点击了自定义1按钮");
|
||||
}
|
||||
@@ -203,8 +202,7 @@ const addModalRef2 = ref();
|
||||
const openSecondModal = () => {
|
||||
handleAddClick(addModalRef2 as Ref<PageModalInstance>);
|
||||
};
|
||||
const secondSubmit = (formData: any) => {
|
||||
console.log("secondSubmit", formData);
|
||||
const secondSubmit = () => {
|
||||
ElMessage.success("二级弹窗提交成功");
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ defineOptions({
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
console.log(route);
|
||||
|
||||
const value = ref("");
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,4 @@ import MultiLevel2 from "../level2.vue";
|
||||
defineOptions({ name: "MultiLevel31" });
|
||||
|
||||
const value = ref("");
|
||||
onMounted(() => {
|
||||
console.log("Multilevel31 onMounted");
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -17,7 +17,4 @@ import MultiLevel2 from "../level2.vue";
|
||||
|
||||
defineOptions({ name: "MultiLevel32" });
|
||||
const value = ref("");
|
||||
onMounted(() => {
|
||||
console.log("Multilevel32 onMounted");
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -6,8 +6,4 @@
|
||||
defineOptions({ name: "MultiLevel2" });
|
||||
|
||||
const value = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
console.log("MultiLevel2 mounted");
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,4 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: "MultiLevel1" });
|
||||
|
||||
onMounted(() => {
|
||||
console.log("MultiLevel1 mounted");
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -104,7 +104,7 @@ const isCanvasBlank = (canvas: HTMLCanvasElement) => {
|
||||
const blank = document.createElement("canvas"); //系统获取一个空canvas对象
|
||||
blank.width = canvas.width;
|
||||
blank.height = canvas.height;
|
||||
return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
|
||||
return canvas.toDataURL() === blank.toDataURL(); //比较值相等则为空
|
||||
};
|
||||
|
||||
// 保存为图片
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</el-link>
|
||||
<table-select :text="text" :select-config="selectConfig" @confirm-click="handleConfirm">
|
||||
<template #status="scope">
|
||||
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
|
||||
<el-tag :type="scope.row[scope.prop] === 1 ? 'success' : 'info'">
|
||||
{{ scope.row[scope.prop] === 1 ? "启用" : "禁用" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #gender="scope">
|
||||
@@ -46,7 +46,9 @@ function handleConfirm(data: IUser[]) {
|
||||
const text = computed(() => {
|
||||
// 获取字典数据
|
||||
const dictData = dictStore.getDictItems("gender");
|
||||
const genderLabel = dictData.find((item: any) => item.value == selectedUser.value?.gender)?.label;
|
||||
const genderLabel = dictData.find(
|
||||
(item) => String(item.value) === String(selectedUser.value?.gender)
|
||||
)?.label;
|
||||
return selectedUser.value
|
||||
? `${selectedUser.value.username} - ${genderLabel} - ${selectedUser.value.deptName}`
|
||||
: "";
|
||||
|
||||
@@ -348,8 +348,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
|
||||
},
|
||||
ajax: {
|
||||
// 接收 Promise
|
||||
query: ({ page: { currentPage, pageSize }, form, filters, sort, sorts }) => {
|
||||
console.log({ currentPage, pageSize, form, filters, sort, sorts });
|
||||
query: ({ page: { currentPage, pageSize } }) => {
|
||||
return new Promise<{ total: number; result: RowMeta[] }>((resolve) => {
|
||||
setTimeout(() => {
|
||||
const list = [
|
||||
@@ -465,9 +464,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
|
||||
});
|
||||
const gridEvents: VxeGridListeners<RowMeta> = {
|
||||
// 只对 form-config 配置时有效,表单重置时会触发该事件
|
||||
formReset() {
|
||||
console.log("Form Reset");
|
||||
},
|
||||
formReset() {},
|
||||
};
|
||||
// #endregion
|
||||
|
||||
@@ -593,7 +590,10 @@ const curd = {
|
||||
},
|
||||
/** 确定并保存 */
|
||||
onSubmitForm: () => {
|
||||
console.log("提交表单");
|
||||
VXETable.modal.message({
|
||||
content: "提交成功",
|
||||
status: "success",
|
||||
});
|
||||
},
|
||||
onDelete: (row?: RowMeta) => {
|
||||
let ids = [];
|
||||
@@ -614,7 +614,10 @@ const curd = {
|
||||
VXETable.modal.confirm("确定要删除吗?").then((type) => {
|
||||
if (type === "confirm") {
|
||||
// 执行删除操作
|
||||
console.log("删除的ID", ids);
|
||||
VXETable.modal.message({
|
||||
content: `已删除 ${ids.length} 条数据`,
|
||||
status: "success",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
<footer class="login-card__footer">
|
||||
<el-text size="small">
|
||||
Copyright © 2021 - 2025 youlai.tech
|
||||
Copyright © 2021 - 2026 youlai.tech
|
||||
<a href="http://beian.miit.gov.cn/" target="_blank">皖ICP备00064962号</a>
|
||||
</el-text>
|
||||
</footer>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<el-table-column align="center" label="发布人" prop="publisherName" width="150" />
|
||||
<el-table-column align="center" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.isRead == 1" type="success">已读</el-tag>
|
||||
<el-tag v-if="scope.row.isRead === 1" type="success">已读</el-tag>
|
||||
<el-tag v-else type="info">未读</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<el-table-column prop="code" label="部门编号" width="200" />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
|
||||
<el-tag v-if="scope.row.status === 1" type="success">正常</el-tag>
|
||||
<el-tag v-else type="info">禁用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<el-table-column fixed="right" align="center" label="操作" width="220">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.type == MenuTypeEnum.CATALOG || scope.row.type == MenuTypeEnum.MENU"
|
||||
v-if="scope.row.type === MenuTypeEnum.CATALOG || scope.row.type === MenuTypeEnum.MENU"
|
||||
v-hasPerm="['sys:menu:create']"
|
||||
type="primary"
|
||||
link
|
||||
@@ -155,7 +155,10 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink" prop="routeName">
|
||||
<el-form-item
|
||||
v-if="formData.type === MenuTypeEnum.MENU && !isExternalLink"
|
||||
prop="routeName"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
路由名称
|
||||
@@ -173,7 +176,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU"
|
||||
v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
|
||||
prop="routePath"
|
||||
>
|
||||
<template #label>
|
||||
@@ -191,14 +194,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<el-input
|
||||
v-if="formData.type == MenuTypeEnum.CATALOG"
|
||||
v-if="formData.type === MenuTypeEnum.CATALOG"
|
||||
v-model="formData.routePath"
|
||||
placeholder="system"
|
||||
/>
|
||||
<el-input v-else v-model="formData.routePath" placeholder="user 或 https://example.com" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink" prop="component">
|
||||
<el-form-item
|
||||
v-if="formData.type === MenuTypeEnum.MENU && !isExternalLink"
|
||||
prop="component"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
组件路径
|
||||
@@ -214,12 +220,12 @@
|
||||
</template>
|
||||
|
||||
<el-input v-model="formData.component" placeholder="system/user/index" style="width: 95%">
|
||||
<template v-if="formData.type == MenuTypeEnum.MENU" #prepend>src/views/</template>
|
||||
<template v-if="formData.type == MenuTypeEnum.MENU" #append>.vue</template>
|
||||
<template v-if="formData.type === MenuTypeEnum.MENU" #prepend>src/views/</template>
|
||||
<template v-if="formData.type === MenuTypeEnum.MENU" #append>.vue</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.MENU && !isExternalLink">
|
||||
<el-form-item v-if="formData.type === MenuTypeEnum.MENU && !isExternalLink">
|
||||
<template #label>
|
||||
<div class="flex-y-center">
|
||||
路由参数
|
||||
@@ -332,7 +338,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<!-- 权限标识 -->
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.BUTTON" label="权限标识" prop="perm">
|
||||
<el-form-item v-if="formData.type === MenuTypeEnum.BUTTON" label="权限标识" prop="perm">
|
||||
<el-input v-model="formData.perm" placeholder="sys:user:create" />
|
||||
</el-form-item>
|
||||
|
||||
@@ -341,7 +347,7 @@
|
||||
<icon-select v-model="formData.icon" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formData.type == MenuTypeEnum.CATALOG" label="跳转路由">
|
||||
<el-form-item v-if="formData.type === MenuTypeEnum.CATALOG" label="跳转路由">
|
||||
<el-input v-model="formData.redirect" placeholder="跳转路由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -529,7 +535,7 @@ function handleSubmit(): void {
|
||||
if (isValid) {
|
||||
const menuId = formData.value.id;
|
||||
if (menuId) {
|
||||
if (formData.value.parentId == menuId) {
|
||||
if (formData.value.parentId === menuId) {
|
||||
ElMessage.error("父级菜单不能为当前菜单");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -77,15 +77,15 @@
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="通告目标类型" prop="targetType" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.targetType == 1" type="warning">全体</el-tag>
|
||||
<el-tag v-if="scope.row.targetType == 2" type="success">指定</el-tag>
|
||||
<el-tag v-if="scope.row.targetType === 1" type="warning">全体</el-tag>
|
||||
<el-tag v-if="scope.row.targetType === 2" type="success">指定</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="发布状态" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.publishStatus == 0" type="info">未发布</el-tag>
|
||||
<el-tag v-if="scope.row.publishStatus == 1" type="success">已发布</el-tag>
|
||||
<el-tag v-if="scope.row.publishStatus == -1" type="warning">已撤回</el-tag>
|
||||
<el-tag v-if="scope.row.publishStatus === 0" type="info">未发布</el-tag>
|
||||
<el-tag v-if="scope.row.publishStatus === 1" type="success">已发布</el-tag>
|
||||
<el-tag v-if="scope.row.publishStatus === -1" type="warning">已撤回</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作时间" width="250">
|
||||
@@ -111,7 +111,7 @@
|
||||
查看
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.publishStatus != 1"
|
||||
v-if="scope.row.publishStatus !== 1"
|
||||
v-hasPerm="['sys:notice:publish']"
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -121,7 +121,7 @@
|
||||
发布
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.publishStatus == 1"
|
||||
v-if="scope.row.publishStatus === 1"
|
||||
v-hasPerm="['sys:notice:revoke']"
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -131,7 +131,7 @@
|
||||
撤回
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.publishStatus != 1"
|
||||
v-if="scope.row.publishStatus !== 1"
|
||||
v-hasPerm="['sys:notice:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -141,7 +141,7 @@
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.publishStatus != 1"
|
||||
v-if="scope.row.publishStatus !== 1"
|
||||
v-hasPerm="['sys:notice:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
@@ -207,7 +207,7 @@
|
||||
<el-radio :value="2">指定</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.targetType == 2" label="指定用户" prop="targetUsers">
|
||||
<el-form-item v-if="formData.targetType === 2" label="指定用户" prop="targetUsers">
|
||||
<el-select v-model="formData.targetUsers" multiple search placeholder="请选择指定用户">
|
||||
<el-option
|
||||
v-for="item in userOptions"
|
||||
@@ -252,9 +252,9 @@
|
||||
{{ currentNotice.title }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布状态:">
|
||||
<el-tag v-if="currentNotice.publishStatus == 0" type="info">未发布</el-tag>
|
||||
<el-tag v-else-if="currentNotice.publishStatus == 1" type="success">已发布</el-tag>
|
||||
<el-tag v-else-if="currentNotice.publishStatus == -1" type="warning">已撤回</el-tag>
|
||||
<el-tag v-if="currentNotice.publishStatus === 0" type="info">未发布</el-tag>
|
||||
<el-tag v-else-if="currentNotice.publishStatus === 1" type="success">已发布</el-tag>
|
||||
<el-tag v-else-if="currentNotice.publishStatus === -1" type="warning">已撤回</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布人:">
|
||||
{{ currentNotice.publisherName }}
|
||||
|
||||
Reference in New Issue
Block a user