feat: 状态展示统一为圆点

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

View File

@@ -36,7 +36,9 @@ const tableList = [
},
];
/** 下划线命名转驼峰首字母大写,如 sys_user → SysUser */
/**
* 下划线命名转驼峰首字母大写,如 sys_user → SysUser
*/
function toPascalCase(name: string): string {
return name
.split("_")
@@ -196,7 +198,9 @@ const genConfigMap: Record<string, any> = {
},
};
/** 未配置表的默认生成配置(无 id,进入抽屉后从基础配置步骤开始) */
/**
* 未配置表的默认生成配置(无 id,进入抽屉后从基础配置步骤开始)
*/
function buildDefaultConfig(tableName: string) {
return {
tableName,
@@ -214,7 +218,9 @@ function buildDefaultConfig(tableName: string) {
};
}
/** 构造代码生成预览文件列表 */
/**
* 构造代码生成预览文件列表
*/
function buildPreviewFiles(tableName: string, tableComment: string) {
const bizName = tableName.replace(/^sys_/, "");
const entityName = toPascalCase(bizName);

View File

@@ -17,7 +17,7 @@
"lint:lint-staged": "lint-staged",
"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",
"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}\"",
"check": "pnpm run type-check && pnpm run lint:check && pnpm run format:check && pnpm run style:check",
"preinstall": "npx only-allow pnpm",

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>

View File

@@ -161,7 +161,9 @@ export interface ISelectConfig<T = any> {
placeholder?: string;
/** popover 组件属性 */
popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">>;
/** 列表的网络请求函数(需返回 Promise) */
/**
* 列表的网络请求函数(需返回 Promise)
*/
indexAction: (_queryParams: T) => Promise<any>;
/** 主键(跨页选择必填,默认为 id) */
pk?: string;

View File

@@ -78,7 +78,9 @@ export function usePageTable<T, Q extends PageQuery = PageQuery>(
export interface UsePageTableOptions<T, Q extends PageQuery> {
/** 初值会深复制,请求和编辑不会改变重置基准。 */
initialParams: Q;
/** 接收查询快照;请求异常由调用方处理。 */
/**
* 接收查询快照;请求异常由调用方处理。
*/
request: (params: Q) => Promise<PageResult<T>>;
/** 恢复参数前,同步重置页面自有的搜索控件。 */
onBeforeReset?: () => void;
@@ -93,10 +95,16 @@ export interface UsePageTableReturn<T, Q extends PageQuery> {
total: Ref<number>;
params: Q;
fetchData: () => Promise<void>;
/** 回到第一页并查询。 */
/**
* 回到第一页并查询。
*/
handleQuery: () => Promise<void>;
/** 恢复初始条件后重新查询。 */
/**
* 恢复初始条件后重新查询。
*/
handleResetQuery: () => Promise<void>;
/** 仅恢复参数,不发请求。 */
/**
* 仅恢复参数,不发请求。
*/
resetParams: () => void;
}

View File

@@ -721,11 +721,17 @@ const xModalEvent: VxeModalListeners = {
const crudStore = {
/** 表单类型,true 表示修改,false 表示新增 */
isUpdate: true,
/** 加载表格数据 */
/**
* 加载表格数据
*/
commitQuery: () => xGridDom.value?.commitProxy("query"),
/** 清空表格数据 */
/**
* 清空表格数据
*/
clearTable: () => xGridDom.value?.reloadData([]),
/** 新增后是否跳入最后一页 */
/**
* 新增后是否跳入最后一页
*/
afterInsert: () => {
const pager = xGridDom.value?.getProxyInfo()?.pager;
if (pager) {
@@ -735,7 +741,9 @@ const crudStore = {
}
}
},
/** 删除后是否返回上一页 */
/**
* 删除后是否返回上一页
*/
afterDelete: () => {
const tableData: RowMeta[] = xGridDom.value!.getData();
const pager = xGridDom.value?.getProxyInfo()?.pager;
@@ -743,7 +751,9 @@ const crudStore = {
--pager.currentPage;
}
},
/** 点击显示弹窗 */
/**
* 点击显示弹窗
*/
onShowModal: (row?: RowMeta) => {
if (row) {
crudStore.isUpdate = true;
@@ -755,7 +765,9 @@ const crudStore = {
}
xModalDom.value?.open();
},
/** 确定并保存 */
/**
* 确定并保存
*/
onSubmitForm: () => {
if (xModalOpt.loading) return;
// 表单校验
@@ -775,7 +787,9 @@ const crudStore = {
callback();
});
},
/** 删除 */
/**
* 删除
*/
onDelete: (row?: RowMeta) => {
let ids = [];
if (row === undefined) {
@@ -808,7 +822,9 @@ const crudStore = {
onModify: (id: number, field: string, value: any) => {
console.debug("🚀 ~ onModify", { id, field, value });
},
/** 更多自定义方法 */
/**
* 更多自定义方法
*/
moreFn: () => {},
};
</script>

View File

@@ -954,6 +954,18 @@ $input-h: 44px;
.login-brand {
padding: 24px 24px 28px;
/* 版本号移到名称下方,避免与右上角工具栏图标重叠 */
&__identity {
flex-direction: column;
gap: 6px;
align-items: flex-start;
}
&__name {
font-size: 20px;
white-space: nowrap;
}
}
.login-card {

View File

@@ -61,9 +61,10 @@
<el-table-column label="排序" prop="sort" align="center" width="80" />
<el-table-column label="状态" align="center" width="100">
<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 ? "启用" : "禁用" }}
</el-tag>
</span>
</template>
</el-table-column>
@@ -172,6 +173,7 @@ import { FullScreen, QuestionFilled, Refresh } from "@element-plus/icons-vue";
import DictAPI from "@/api/system/dict";
import type { DictItem, DictItemForm, DictItemQuery } from "@/api/system/dict";
import { usePageTable, useTableSelection } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums";
import { useDictStoreHook } from "@/stores/dict";
@@ -378,4 +380,10 @@ onMounted(() => {
font-size: 13px;
color: var(--el-text-color-placeholder);
}
.dict-item-panel__status {
display: inline-flex;
gap: 6px;
align-items: center;
}
</style>

View File

@@ -29,7 +29,10 @@
@click="handleTypeClick(row)"
>
<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>
</div>
<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 type { DictTypeForm, DictTypeItem, DictTypeQuery } from "@/api/system/dict";
import { usePageTable } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums";
import DictItemPanel from "./components/DictItemPanel.vue";
@@ -376,6 +380,20 @@ onMounted(() => {
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 {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -34,10 +34,7 @@
>
<div class="role-list__info">
<span class="role-list__title">
<span
class="role-list__dot"
:class="{ 'is-stopped': row.status !== CommonStatus.ENABLED }"
/>
<StatusDot :active="row.status === CommonStatus.ENABLED" />
<span class="role-list__name" :title="row.name">{{ row.name }}</span>
</span>
<span class="role-list__meta">
@@ -310,6 +307,7 @@ import type { MenuItem } from "@/api/system/menu";
import DeptAPI from "@/api/system/dept";
import type { OptionItem } from "@/api/common";
import { usePageTable } from "@/composables";
import StatusDot from "@/components/StatusDot/index.vue";
import { CommonStatus } from "@/enums";
import { ROLE_ROOT } from "@/constants";
@@ -957,18 +955,6 @@ onMounted(() => {
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 {
display: flex;
gap: 6px;

View File

@@ -153,7 +153,7 @@
<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="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">
<TableActions :actions="getUserActions(scope.row as UserItem)" />
</template>

View File

@@ -381,9 +381,13 @@ onBeforeUnmount(() => {
defineExpose({
/** 供承载页触发的保存动作(与工具栏保存共用一套导出逻辑) */
save: handleSave,
/** 画布是否有未保存的修改 */
/**
* 画布是否有未保存的修改
*/
isDirty: () => dirty.value,
/** 持久化成功后清除脏标记(由承载页调用) */
/**
* 持久化成功后清除脏标记(由承载页调用)
*/
markSaved: () => {
dirty.value = false;
},

View File

@@ -195,6 +195,13 @@ function handleAppClick(app: AppCard): void {
</script>
<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 {
padding-top: 24px;
margin-top: 24px;