feat: 状态展示统一为圆点

- 新增状态圆点组件,用户管理、角色与字典的状态改用圆点
- 用户管理操作列放宽到 200,登录页移动端版本号紧随项目名
- 应用中心分组按内容高度排列
- 函数注释统一为展开式多行
- 格式校验脚本忽略不存在的目录
This commit is contained in:
Ray.Hao committed 2026-10-29 08:13:50 +08:00
1 parent 53a97cac21
commit f443eb6bbe
13 files changed
+133 -39

No files matched your search

+9 -3
View File
@@ -36,7 +36,9 @@ const tableList = [
}, },
]; ];
/** 下划线命名转驼峰首字母大写,如 sys_user → SysUser */ /**
* 下划线命名转驼峰首字母大写,如 sys_user → SysUser
*/
function toPascalCase(name: string): string { function toPascalCase(name: string): string {
return name return name
.split("_") .split("_")
@@ -196,7 +198,9 @@ const genConfigMap: Record<string, any> = {
}, },
}; };
/** 未配置表的默认生成配置(无 id,进入抽屉后从基础配置步骤开始) */ /**
* 未配置表的默认生成配置(无 id,进入抽屉后从基础配置步骤开始)
*/
function buildDefaultConfig(tableName: string) { function buildDefaultConfig(tableName: string) {
return { return {
tableName, tableName,
@@ -214,7 +218,9 @@ function buildDefaultConfig(tableName: string) {
}; };
} }
/** 构造代码生成预览文件列表 */ /**
* 构造代码生成预览文件列表
*/
function buildPreviewFiles(tableName: string, tableComment: string) { function buildPreviewFiles(tableName: string, tableComment: string) {
const bizName = tableName.replace(/^sys_/, ""); const bizName = tableName.replace(/^sys_/, "");
const entityName = toPascalCase(bizName); const entityName = toPascalCase(bizName);
+1 -1
View File
@@ -17,7 +17,7 @@
"lint:lint-staged": "lint-staged", "lint:lint-staged": "lint-staged",
"lint": "pnpm run lint:eslint && pnpm run lint:prettier && pnpm run lint:stylelint", "lint": "pnpm run lint:eslint && pnpm run lint:prettier && pnpm run lint:stylelint",
"lint:check": "eslint src mock vite.config.ts uno.config.ts eslint.config.ts", "lint:check": "eslint src mock vite.config.ts uno.config.ts eslint.config.ts",
"format:check": "prettier --check \"src/**/*.{ts,vue,scss,css,json}\" \"mock/**/*.ts\" \"*.{ts,cjs,mjs,json,yaml}\" \".github/workflows/*.{yml,yaml}\"", "format:check": "prettier --check --no-error-on-unmatched-pattern \"src/**/*.{ts,vue,scss,css,json}\" \"mock/**/*.ts\" \"*.{ts,cjs,mjs,json,yaml}\" \".github/workflows/*.{yml,yaml}\"",
"style:check": "stylelint \"src/**/*.{css,scss,vue}\"", "style:check": "stylelint \"src/**/*.{css,scss,vue}\"",
"check": "pnpm run type-check && pnpm run lint:check && pnpm run format:check && pnpm run style:check", "check": "pnpm run type-check && pnpm run lint:check && pnpm run format:check && pnpm run style:check",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
+27
View File
@@ -0,0 +1,27 @@
<!-- 状态圆点:启用为成功色,停用为占位色 -->
<template>
<span class="status-dot" :class="{ 'is-stopped': !active }" />
</template>
<script setup lang="ts">
defineOptions({ name: "StatusDot" });
/**
* 是否处于启用状态
*/
defineProps<{ active: boolean }>();
</script>
<style lang="scss" scoped>
.status-dot {
display: inline-block;
width: 6px;
height: 6px;
background-color: var(--el-color-success);
border-radius: 50%;
}
.status-dot.is-stopped {
background-color: var(--el-text-color-placeholder);
}
</style>
+3 -1
View File
@@ -161,7 +161,9 @@ export interface ISelectConfig<T = any> {
placeholder?: string; placeholder?: string;
/** popover 组件属性 */ /** popover 组件属性 */
popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">>; popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">>;
/** 列表的网络请求函数(需返回 Promise) */ /**
* 列表的网络请求函数(需返回 Promise)
*/
indexAction: (_queryParams: T) => Promise<any>; indexAction: (_queryParams: T) => Promise<any>;
/** 主键(跨页选择必填,默认为 id) */ /** 主键(跨页选择必填,默认为 id) */
pk?: string; pk?: string;
+12 -4
View File
@@ -78,7 +78,9 @@ export function usePageTable<T, Q extends PageQuery = PageQuery>(
export interface UsePageTableOptions<T, Q extends PageQuery> { export interface UsePageTableOptions<T, Q extends PageQuery> {
/** 初值会深复制,请求和编辑不会改变重置基准。 */ /** 初值会深复制,请求和编辑不会改变重置基准。 */
initialParams: Q; initialParams: Q;
/** 接收查询快照;请求异常由调用方处理。 */ /**
* 接收查询快照;请求异常由调用方处理。
*/
request: (params: Q) => Promise<PageResult<T>>; request: (params: Q) => Promise<PageResult<T>>;
/** 恢复参数前,同步重置页面自有的搜索控件。 */ /** 恢复参数前,同步重置页面自有的搜索控件。 */
onBeforeReset?: () => void; onBeforeReset?: () => void;
@@ -93,10 +95,16 @@ export interface UsePageTableReturn<T, Q extends PageQuery> {
total: Ref<number>; total: Ref<number>;
params: Q; params: Q;
fetchData: () => Promise<void>; fetchData: () => Promise<void>;
/** 回到第一页并查询。 */ /**
* 回到第一页并查询。
*/
handleQuery: () => Promise<void>; handleQuery: () => Promise<void>;
/** 恢复初始条件后重新查询。 */ /**
* 恢复初始条件后重新查询。
*/
handleResetQuery: () => Promise<void>; handleResetQuery: () => Promise<void>;
/** 仅恢复参数,不发请求。 */ /**
* 仅恢复参数,不发请求。
*/
resetParams: () => void; resetParams: () => void;
} }
+24 -8
View File
@@ -721,11 +721,17 @@ const xModalEvent: VxeModalListeners = {
const crudStore = { const crudStore = {
/** 表单类型,true 表示修改,false 表示新增 */ /** 表单类型,true 表示修改,false 表示新增 */
isUpdate: true, isUpdate: true,
/** 加载表格数据 */ /**
* 加载表格数据
*/
commitQuery: () => xGridDom.value?.commitProxy("query"), commitQuery: () => xGridDom.value?.commitProxy("query"),
/** 清空表格数据 */ /**
* 清空表格数据
*/
clearTable: () => xGridDom.value?.reloadData([]), clearTable: () => xGridDom.value?.reloadData([]),
/** 新增后是否跳入最后一页 */ /**
* 新增后是否跳入最后一页
*/
afterInsert: () => { afterInsert: () => {
const pager = xGridDom.value?.getProxyInfo()?.pager; const pager = xGridDom.value?.getProxyInfo()?.pager;
if (pager) { if (pager) {
@@ -735,7 +741,9 @@ const crudStore = {
} }
} }
}, },
/** 删除后是否返回上一页 */ /**
* 删除后是否返回上一页
*/
afterDelete: () => { afterDelete: () => {
const tableData: RowMeta[] = xGridDom.value!.getData(); const tableData: RowMeta[] = xGridDom.value!.getData();
const pager = xGridDom.value?.getProxyInfo()?.pager; const pager = xGridDom.value?.getProxyInfo()?.pager;
@@ -743,7 +751,9 @@ const crudStore = {
--pager.currentPage; --pager.currentPage;
} }
}, },
/** 点击显示弹窗 */ /**
* 点击显示弹窗
*/
onShowModal: (row?: RowMeta) => { onShowModal: (row?: RowMeta) => {
if (row) { if (row) {
crudStore.isUpdate = true; crudStore.isUpdate = true;
@@ -755,7 +765,9 @@ const crudStore = {
} }
xModalDom.value?.open(); xModalDom.value?.open();
}, },
/** 确定并保存 */ /**
* 确定并保存
*/
onSubmitForm: () => { onSubmitForm: () => {
if (xModalOpt.loading) return; if (xModalOpt.loading) return;
// 表单校验 // 表单校验
@@ -775,7 +787,9 @@ const crudStore = {
callback(); callback();
}); });
}, },
/** 删除 */ /**
* 删除
*/
onDelete: (row?: RowMeta) => { onDelete: (row?: RowMeta) => {
let ids = []; let ids = [];
if (row === undefined) { if (row === undefined) {
@@ -808,7 +822,9 @@ const crudStore = {
onModify: (id: number, field: string, value: any) => { onModify: (id: number, field: string, value: any) => {
console.debug("🚀 ~ onModify", { id, field, value }); console.debug("🚀 ~ onModify", { id, field, value });
}, },
/** 更多自定义方法 */ /**
* 更多自定义方法
*/
moreFn: () => {}, moreFn: () => {},
}; };
</script> </script>
+12
View File
@@ -954,6 +954,18 @@ $input-h: 44px;
.login-brand { .login-brand {
padding: 24px 24px 28px; padding: 24px 24px 28px;
/* 版本号移到名称下方,避免与右上角工具栏图标重叠 */
&__identity {
flex-direction: column;
gap: 6px;
align-items: flex-start;
}
&__name {
font-size: 20px;
white-space: nowrap;
}
} }
.login-card { .login-card {
@@ -61,9 +61,10 @@
<el-table-column label="排序" prop="sort" align="center" width="80" /> <el-table-column label="排序" prop="sort" align="center" width="80" />
<el-table-column label="状态" align="center" width="100"> <el-table-column label="状态" align="center" width="100">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.status === CommonStatus.ENABLED ? 'success' : 'info'"> <span class="dict-item-panel__status">
<StatusDot :active="scope.row.status === CommonStatus.ENABLED" />
{{ scope.row.status === CommonStatus.ENABLED ? "启用" : "禁用" }} {{ scope.row.status === CommonStatus.ENABLED ? "启用" : "禁用" }}
</el-tag> </span>
</template> </template>
</el-table-column> </el-table-column>
@@ -172,6 +173,7 @@ import { FullScreen, QuestionFilled, Refresh } from "@element-plus/icons-vue";
import DictAPI from "@/api/system/dict"; import DictAPI from "@/api/system/dict";
import type { DictItem, DictItemForm, DictItemQuery } from "@/api/system/dict"; import type { DictItem, DictItemForm, DictItemQuery } from "@/api/system/dict";
import { usePageTable, useTableSelection } from "@/composables"; import { usePageTable, useTableSelection } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums"; import { CommonStatus } from "@/enums";
import { useDictStoreHook } from "@/stores/dict"; import { useDictStoreHook } from "@/stores/dict";
@@ -378,4 +380,10 @@ onMounted(() => {
font-size: 13px; font-size: 13px;
color: var(--el-text-color-placeholder); color: var(--el-text-color-placeholder);
} }
.dict-item-panel__status {
display: inline-flex;
gap: 6px;
align-items: center;
}
</style> </style>
+19 -1
View File
@@ -29,7 +29,10 @@
@click="handleTypeClick(row)" @click="handleTypeClick(row)"
> >
<div class="dict-type__info"> <div class="dict-type__info">
<span class="dict-type__name" :title="row.name">{{ row.name }}</span> <span class="dict-type__name" :title="row.name">
<StatusDot :active="row.status === CommonStatus.ENABLED" />
{{ row.name }}
</span>
<span class="dict-type__code" :title="row.dictCode">{{ row.dictCode }}</span> <span class="dict-type__code" :title="row.dictCode">{{ row.dictCode }}</span>
</div> </div>
<div class="dict-type__actions"> <div class="dict-type__actions">
@@ -143,6 +146,7 @@ import { ArrowLeft, ArrowRight, Delete, Edit, Plus } from "@element-plus/icons-v
import DictAPI from "@/api/system/dict"; import DictAPI from "@/api/system/dict";
import type { DictTypeForm, DictTypeItem, DictTypeQuery } from "@/api/system/dict"; import type { DictTypeForm, DictTypeItem, DictTypeQuery } from "@/api/system/dict";
import { usePageTable } from "@/composables"; import { usePageTable } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums"; import { CommonStatus } from "@/enums";
import DictItemPanel from "./components/DictItemPanel.vue"; import DictItemPanel from "./components/DictItemPanel.vue";
@@ -376,6 +380,20 @@ onMounted(() => {
line-height: 1.35; line-height: 1.35;
} }
&__dot {
display: inline-block;
width: 6px;
height: 6px;
margin-right: 6px;
vertical-align: middle;
background-color: var(--el-color-success);
border-radius: 50%;
&.is-stopped {
background-color: var(--el-text-color-placeholder);
}
}
&__name { &__name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
+2 -16
View File
@@ -34,10 +34,7 @@
> >
<div class="role-list__info"> <div class="role-list__info">
<span class="role-list__title"> <span class="role-list__title">
<span <StatusDot :active="row.status === CommonStatus.ENABLED" />
class="role-list__dot"
:class="{ 'is-stopped': row.status !== CommonStatus.ENABLED }"
/>
<span class="role-list__name" :title="row.name">{{ row.name }}</span> <span class="role-list__name" :title="row.name">{{ row.name }}</span>
</span> </span>
<span class="role-list__meta"> <span class="role-list__meta">
@@ -310,6 +307,7 @@ import type { MenuItem } from "@/api/system/menu";
import DeptAPI from "@/api/system/dept"; import DeptAPI from "@/api/system/dept";
import type { OptionItem } from "@/api/common"; import type { OptionItem } from "@/api/common";
import { usePageTable } from "@/composables"; import { usePageTable } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums"; import { CommonStatus } from "@/enums";
import { ROLE_ROOT } from "@/constants"; import { ROLE_ROOT } from "@/constants";
@@ -957,18 +955,6 @@ onMounted(() => {
white-space: nowrap; white-space: nowrap;
} }
&__dot {
flex: 0 0 auto;
width: 6px;
height: 6px;
background-color: var(--el-color-success);
border-radius: 50%;
&.is-stopped {
background-color: var(--el-text-color-placeholder);
}
}
&__meta { &__meta {
display: flex; display: flex;
gap: 6px; gap: 6px;
+1 -1
View File
@@ -153,7 +153,7 @@
<el-table-column label="手机号码" prop="mobile" width="130" /> <el-table-column label="手机号码" prop="mobile" width="130" />
<el-table-column label="邮箱" prop="email" min-width="180" show-overflow-tooltip /> <el-table-column label="邮箱" prop="email" min-width="180" show-overflow-tooltip />
<el-table-column label="创建时间" prop="createTime" width="160" show-overflow-tooltip /> <el-table-column label="创建时间" prop="createTime" width="160" show-overflow-tooltip />
<el-table-column label="操作" fixed="right" width="260"> <el-table-column label="操作" fixed="right" width="200">
<template #default="scope"> <template #default="scope">
<TableActions :actions="getUserActions(scope.row as UserItem)" /> <TableActions :actions="getUserActions(scope.row as UserItem)" />
</template> </template>
@@ -381,9 +381,13 @@ onBeforeUnmount(() => {
defineExpose({ defineExpose({
/** 供承载页触发的保存动作(与工具栏保存共用一套导出逻辑) */ /** 供承载页触发的保存动作(与工具栏保存共用一套导出逻辑) */
save: handleSave, save: handleSave,
/** 画布是否有未保存的修改 */ /**
* 画布是否有未保存的修改
*/
isDirty: () => dirty.value, isDirty: () => dirty.value,
/** 持久化成功后清除脏标记(由承载页调用) */ /**
* 持久化成功后清除脏标记(由承载页调用)
*/
markSaved: () => { markSaved: () => {
dirty.value = false; dirty.value = false;
}, },
+7
View File
@@ -195,6 +195,13 @@ function handleAppClick(app: AppCard): void {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
/* 分组按内容高度排列,不占用页面剩余高度 */
.page-content :deep(.el-card__body) {
grid-template-rows: none;
grid-auto-rows: min-content;
align-content: start;
}
.app-section + .app-section { .app-section + .app-section {
padding-top: 24px; padding-top: 24px;
margin-top: 24px; margin-top: 24px;