refactor: 优化代码规范和可读性

This commit is contained in:
Ray.Hao
2026-05-25 22:22:42 +08:00
parent e3e5b87f89
commit 2a538d7da7
56 changed files with 324 additions and 294 deletions

View File

@@ -1 +1,2 @@
npx --no-install commitlint --edit $1
#!/usr/bin/env sh
npx --no-install commitlint --edit "$1"

View File

@@ -1 +1,2 @@
npm run lint:lint-staged
#!/usr/bin/env sh
npm run lint:lint-staged

View File

@@ -18,7 +18,7 @@
![](https://foruda.gitee.com/images/1708618984641188532/a7cca095_716974.png "rainbow.png")
<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>&nbsp;&nbsp;&nbsp;&nbsp;</td>

View File

@@ -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",

View File

@@ -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",

View File

@@ -1,6 +1,5 @@
<template>
<el-config-provider :locale="locale" :size="size">
<!-- 开启水印-->
<el-watermark
:font="{ color: fontColor }"
:content="showWatermark ? watermarkContent : ''"

View File

@@ -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",
});

View File

@@ -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,

View File

@@ -3,7 +3,7 @@
*/
/** API 响应结构 */
export interface ApiResponse<T = any> {
export interface ApiResponse<T = unknown> {
/** 响应码 */
code: string;
/** 响应数据 */

View File

@@ -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,

View File

@@ -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",
});

View File

@@ -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) {

View File

@@ -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),
},
});
},

View File

@@ -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",
});

View File

@@ -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) {

View File

@@ -109,7 +109,7 @@ export interface Meta {
/** 【菜单】是否开启页面缓存 */
keepAlive?: boolean;
/** 路由参数 */
params?: Record<string, any>;
params?: Record<string, unknown>;
/** 角色集合 */
roles?: string[];
/** 路由title */

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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",
});

View File

@@ -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",
});

View File

@@ -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",
});

View File

Before

Width:  |  Height:  |  Size: 4.1 MiB

After

Width:  |  Height:  |  Size: 4.1 MiB

View File

@@ -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(

View File

@@ -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("读取文件失败");

View File

@@ -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();

View File

@@ -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,

View File

@@ -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 }}

View File

@@ -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));
};
/**

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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">

View File

@@ -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按钮");
}

View File

@@ -14,9 +14,6 @@ const modalConfig: IModalConfig<UserForm> = {
labelWidth: 100,
},
formAction: UserAPI.create,
beforeSubmit(data) {
console.log("提交之前处理", data);
},
formItems: [
{
label: "用户名",

View File

@@ -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",

View File

@@ -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);
},

View File

@@ -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);
},

View File

@@ -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);
// 模拟发起网络请求修改字段

View File

@@ -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() {},
},
},
{

View File

@@ -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("二级弹窗提交成功");
};

View File

@@ -11,7 +11,6 @@ defineOptions({
});
const route = useRoute();
console.log(route);
const value = ref("");
</script>

View File

@@ -17,7 +17,4 @@ import MultiLevel2 from "../level2.vue";
defineOptions({ name: "MultiLevel31" });
const value = ref("");
onMounted(() => {
console.log("Multilevel31 onMounted");
});
</script>

View File

@@ -17,7 +17,4 @@ import MultiLevel2 from "../level2.vue";
defineOptions({ name: "MultiLevel32" });
const value = ref("");
onMounted(() => {
console.log("Multilevel32 onMounted");
});
</script>

View File

@@ -6,8 +6,4 @@
defineOptions({ name: "MultiLevel2" });
const value = ref("");
onMounted(() => {
console.log("MultiLevel2 mounted");
});
</script>

View File

@@ -3,8 +3,4 @@
</template>
<script setup lang="ts">
defineOptions({ name: "MultiLevel1" });
onMounted(() => {
console.log("MultiLevel1 mounted");
});
</script>

View File

@@ -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(); //比较值相等则为空
};
// 保存为图片

View File

@@ -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}`
: "";

View File

@@ -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",
});
}
});
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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 }}