feat: 项目结构重构优化

This commit is contained in:
Ray.Hao
2025-12-26 12:35:37 +08:00
parent 65ad4fe59f
commit aa374dd2ba
164 changed files with 11305 additions and 3103 deletions

View File

@@ -1,4 +1,4 @@
<!-- 文件上传组件示例 -->
<!-- 文件上传组件示例 -->
<template>
<div class="app-container">
<el-link
@@ -7,17 +7,17 @@
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
示例源码 请点击>>>
</el-link>
<div>
<h3>自适应表格操作</h3>
<h3>自适应表格操作</h3>
<div class="text-14px color-#999">
该组件适用于含有操作列的表格在某些情况下按钮可能需要根据数据状态或其他条件动态展示无法预设固定宽度操作列组件能根据按钮数量自适应宽度不需要再手动设置宽度
</div>
<div class="mt-30px">
<el-checkbox v-model="checked1" label="查看" size="large" />
<el-checkbox v-model="checked2" label="超过了六个字会怎么" size="large" />
<el-checkbox v-model="checked2" label="超过了六个字会怎么显示?" size="large" />
<el-checkbox v-model="checked3" label="新增" size="large" />
<el-checkbox v-model="checked4" label="返回很多个字" size="large" />
<el-checkbox v-model="checked5" label="编辑" size="large" />
@@ -34,7 +34,7 @@
<template #default="{ row }">
<el-button v-if="checked1" link type="primary" size="small">查看</el-button>
<el-button v-if="checked2" link type="primary" size="small">
超过了六个字会怎么
超过了六个字会怎么显示
</el-button>
<el-button v-if="checked3" link type="primary" size="small">新增</el-button>
<el-button v-if="checked4" link type="primary" size="small">返回很多个字</el-button>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container h-full flex flex-1 flex-col">
<div class="mb-10">
<el-link
@@ -6,7 +6,7 @@
type="primary"
target="_blank"
>
整合版示例源码 请点击>>>>
整合版示例源码 请点击 >>>
</el-link>
</div>
@@ -35,7 +35,7 @@
</el-tag>
</template>
<template #gender="scope">
<DictLabel v-model="scope.row[scope.prop]" code="gender" />
<DictTag v-model="scope.row[scope.prop]" code="gender" />
</template>
<template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text>
@@ -71,7 +71,7 @@
import UserAPI from "@/api/system/user";
import DeptAPI from "@/api/system/dept";
import RoleAPI from "@/api/system/role";
import type { UserForm, UserPageQuery } from "@/api/system/user";
import type { UserForm, UserPageQuery } from "@/types/api";
import type { IObject, IModalConfig, IContentConfig, ISearchConfig } from "@/components/CURD/types";
import { DeviceEnum } from "@/enums/settings";
import { useAppStore } from "@/store";
@@ -118,7 +118,7 @@ const searchConfig: ISearchConfig = reactive({
label: "关键字",
prop: "keywords",
attrs: {
placeholder: "用户名/昵称/手机",
placeholder: "用户名/昵称/手机",
clearable: true,
style: { width: "200px" },
},
@@ -209,7 +209,7 @@ const contentConfig: IContentConfig<UserPageQuery> = reactive({
"export",
{
name: "custom1",
text: "自定义1",
text: "自定义",
perm: "add",
attrs: { icon: "plus", color: "#626AEF" },
},
@@ -542,7 +542,7 @@ const {
handleFilterChange,
} = usePage();
// 其他工具
// 其他工具
function handleToolbarClick(name: string) {
console.log(name);
if (name === "custom1") {
@@ -550,7 +550,7 @@ function handleToolbarClick(name: string) {
}
}
// 表格工具
// 表格工具
const handleOperateClick = (data: IObject) => {
if (data.name === "detail") {
editModalConfig.drawer = { ...editModalConfig.drawer, title: "查看" };
@@ -563,13 +563,13 @@ const handleOperateClick = (data: IObject) => {
return await UserAPI.getFormData(data.row.id);
});
} else if (data.name === "reset_pwd") {
ElMessageBox.prompt("请输入用户" + data.row.username + "」的新密码", "重置密码", {
ElMessageBox.prompt("请输入用户" + data.row.username + "」的新密码", "重置密码", {
confirmButtonText: "确定",
cancelButtonText: "取消",
})
.then(({ value }: any) => {
if (!value || value.length < 6) {
ElMessage.warning("密码至少需6位字符请重新输入");
ElMessage.warning("密码至少需6位字符请重新输入");
return false;
}
UserAPI.resetPassword(data.row.id, value).then(() => {

View File

@@ -1,4 +1,5 @@
import UserAPI, { type UserForm } from "@/api/system/user";
import UserAPI from "@/api/system/user";
import type { UserForm } from "@/types/api";
import type { IModalConfig } from "@/components/CURD/types";
import { deptArr, roleArr } from "./options";

View File

@@ -1,6 +1,6 @@
import UserAPI from "@/api/system/user";
import RoleAPI from "@/api/system/role";
import type { UserPageQuery } from "@/api/system/user";
import type { UserPageQuery } from "@/types/api";
import type { IContentConfig } from "@/components/CURD/types";
const contentConfig: IContentConfig<UserPageQuery> = {

View File

@@ -1,4 +1,5 @@
import UserAPI, { type UserForm } from "@/api/system/user";
import UserAPI from "@/api/system/user";
import type { UserForm } from "@/types/api";
import type { IModalConfig } from "@/components/CURD/types";
import { DeviceEnum } from "@/enums/settings";
import { useAppStore } from "@/store";

View File

@@ -1,4 +1,4 @@
import type { UserForm } from "@/api/system/user";
import type { UserForm } from "@/types/api";
import type { IModalConfig } from "@/components/CURD/types";
import { deptArr } from "../config/options";

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container h-full flex flex-1 flex-col">
<div class="flex-x-between mb-10">
<el-link
@@ -6,7 +6,7 @@
type="primary"
target="_blank"
>
示例源码 请点击>>>>
示例源码 请点击>>>
</el-link>
<el-button type="primary" plain round size="small" @click="isA = !isA">切换示例</el-button>
</div>
@@ -38,7 +38,7 @@
</el-tag>
</template>
<template #gender="scope">
<DictLabel v-model="scope.row[scope.prop]" code="gender" />
<DictTag v-model="scope.row[scope.prop]" code="gender" />
</template>
<template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text>
@@ -143,7 +143,7 @@ const {
handleFilterChange,
} = usePage();
// 其他工具
// 其他工具
function handleToolbarClick(name: string) {
console.log(name);
if (name === "custom1") {
@@ -151,12 +151,12 @@ function handleToolbarClick(name: string) {
}
}
// 表格工具
// 表格工具
const handleOperateClick = (data: IObject) => {
if (data.name === "detail") {
editModalConfig.drawer = { ...editModalConfig.drawer, title: "查看" };
handleViewClick(data.row, async () => {
// 加载下拉数据源,建议在初始化配置项 initFn 中加载,避免多次请求
// 加载下拉数据源,建议在初始化配置项"initFn 中加载,避免多次请求
// editModalConfig.formItems[2]!.attrs!.data = await DeptAPI.getOptions();
return await UserAPI.getFormData(data.row.id); // 根据ID获取详情
});
@@ -166,13 +166,13 @@ const handleOperateClick = (data: IObject) => {
return await UserAPI.getFormData(data.row.id); // 根据ID获取详情
});
} else if (data.name === "reset_pwd") {
ElMessageBox.prompt("请输入用户" + data.row.username + "」的新密码", "重置密码", {
ElMessageBox.prompt("请输入用户" + data.row.username + "」的新密码", "重置密码", {
confirmButtonText: "确定",
cancelButtonText: "取消",
})
.then(({ value }) => {
if (!value || value.length < 6) {
ElMessage.warning("密码至少需6位字符请重新输入");
ElMessage.warning("密码至少需6位字符请重新输入");
return false;
}
UserAPI.resetPassword(data.row.id, value).then(() => {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
@@ -19,7 +19,7 @@
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="flex justify-between items-center">
<span>性别字典 - </span>
<span>性别字典 - </span>
<el-button type="warning" size="small" @click="loadMaleDict">重新加载</el-button>
</div>
</template>
@@ -69,7 +69,7 @@
</el-card>
</el-col>
<!-- 2: 字典组件展示 -->
<!-- 2: 字典组件展示 -->
<el-col :span="8">
<el-card shadow="hover" class="dict-card">
<template #header>
@@ -105,14 +105,14 @@
</div>
<div class="mt-4 pt-3 border-top">
<div class="text-muted mb-2">已选择: {{ selectedGender }}</div>
<div class="text-muted">最后更新: {{ lastUpdateTime }}</div>
<div class="text-muted mb-2">已选择 {{ selectedGender }}</div>
<div class="text-muted">最后更新 {{ lastUpdateTime }}</div>
</div>
</div>
</el-card>
</el-col>
<!-- 3: 字典缓存数据 -->
<!-- 3: 字典缓存数据 -->
<el-col :span="8">
<el-card shadow="hover" class="dict-card">
<template #header>
@@ -139,9 +139,10 @@
</template>
<script setup lang="ts">
import { useDictStoreHook } from "@/store/modules/dict-store";
import { useDictStoreHook } from "@/store/modules/dict";
import { useDateFormat } from "@vueuse/core";
import DictAPI, { DictItemForm } from "@/api/system/dict";
import DictAPI from "@/api/system/dict";
import type { DictItemForm } from "@/types/api";
import { useDictSync, DictMessage } from "@/composables";
// 性别字典编码
@@ -181,10 +182,10 @@ const dictCacheStatus = computed(() => {
// 设置WebSocket
const setupWebSocket = () => {
// 初始化WebSocket连接
dictWebSocket.initWebSocket();
dictWebSocket.initialize();
// 注册字典消息回调
unregisterCallback = dictWebSocket.onDictMessage((message: DictMessage) => {
unregisterCallback = dictWebSocket.onDictChange((message: DictMessage) => {
// 只有当消息是关于性别字典的更新时才处理
if (message.dictCode === DICT_CODE) {
// 更新最后更新时间
@@ -212,13 +213,13 @@ const loadMaleDict = async () => {
dictForm.value = data;
};
// 保存字典
// 保存字典
const saveDict = async () => {
if (!dictForm.value) return;
saving.value = true;
try {
// dictForm的类型已经是DictItemForm直接传
// dictForm的类型已经是DictItemForm直接传
await DictAPI.updateDictItem(DICT_CODE, MALE_ITEM_ID, dictForm.value);
// 更新时间
@@ -226,7 +227,7 @@ const saveDict = async () => {
ElMessage.success("保存成功后端将通过WebSocket通知所有客户端");
} catch (error) {
console.error("保存字典项失败:", error);
console.error("保存字典项失败", error);
ElMessage.error("保存失败");
} finally {
saving.value = false;
@@ -278,7 +279,7 @@ onUnmounted(() => {
pre {
padding: 8px;
overflow-y: auto;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: pre-wrap;
background-color: #f8f9fa;
border-radius: 4px;

View File

@@ -7,7 +7,7 @@
target="_blank"
class="mb-[20px]"
>
示例源码 请点击>>>>
ç¤ºä¾æº<EFBFBD>ç <EFBFBD> 请ç¹å?>>>
</el-link>
<el-form>
<el-form-item label="性别">

View File

@@ -62,11 +62,11 @@
import { VueDraggable } from "vue-draggable-plus";
const userList = ref([
{ name: "路飞", roles: "船长·格斗家·D之一族" },
{ name: "索隆", roles: "剑豪·战斗员·三刀流大师" },
{ name: "路飞", roles: "船长·格斗家" },
{ name: "索隆", roles: "剑豪·战斗员·三刀流" },
{ name: "娜美", roles: "航海士·气象学家·财务官" },
{ name: "山治", roles: "厨师·格斗家·黑足" },
{ name: "罗宾", roles: "考古学家·历史正文解读者" },
{ name: "罗宾", roles: "考古学家·历史学家" },
]);
</script>

View File

@@ -1,4 +1,4 @@
<!-- 图标选择器示例 -->
<!-- 徿 éæ©å¨ç¤ºä¾?-->
<template>
<div class="app-container">
<el-link
@@ -7,15 +7,15 @@
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
ç¤ºä¾æº<EFBFBD>ç <EFBFBD> 请ç¹å?>>>
</el-link>
<icon-select v-model="iconName" />
</div>
</template>
<script setup lang="ts">
// element-plus 图标格式以el-icon-开头
// element-plus 徿 ‡æ ¼å¼<EFBFBD>以el-icon-å¼€å¤?
const iconName = ref("el-icon-edit");
// 本地SVG图标格式取 src/assets/icons 下的文件名不需要svg后缀
// 本地SVG徿 ‡æ ¼å¼<EFBFBD>å<EFBFBD>?src/assets/icons ä¸çš„æ‡ä»¶å<C2B6><C3A5>,ä¸<C3A4>需è¦<C3A8>svgå<67>Žç¼€
// const iconName = ref("api");
</script>

View File

@@ -1,5 +1,5 @@
<template>
<el-alert :closable="false" title="菜单一" />
<el-alert :closable="false" title="菜单一" />
</template>
<script setup lang="ts">
defineOptions({ name: "MultiLevel1" });

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="canvas-dom">
<h3>基于canvas实现的签名组件</h3>
<header>
@@ -34,7 +34,7 @@ let painting = false;
const getOffset = (event: MouseEvent | TouchEvent) => {
let offset: [number, number];
if ((event as MouseEvent).offsetX) {
// pc
// PC
const { offsetX, offsetY } = event as MouseEvent;
offset = [offsetX, offsetY];
} else {
@@ -60,11 +60,11 @@ const onEventStart = (event: MouseEvent | TouchEvent) => {
const onEventMove = (event: MouseEvent | TouchEvent) => {
if (painting) {
// 鼠标/触摸 移动时,保存 移动点相对 被触发dom的左、上 距离
// 鼠标/触摸 移动时,保存移动点相对被触发 DOM 的左、上距离
const [endX, endY] = getOffset(event);
paint(startX, startY, endX, endY, ctx);
// 每次绘制清除结束后,起点要重置为上次的终点
// 每次绘制清除结束后,起点要重置为上次的终点
startX = endX;
startY = endY;
}

View File

@@ -1,4 +1,4 @@
<!-- 列表选择器示例 -->
<!-- åˆè¡¨éæ©å¨ç¤ºä¾?-->
<template>
<div class="app-container">
<el-link
@@ -7,7 +7,7 @@
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
ç¤ºä¾æº<EFBFBD>ç <EFBFBD> 请ç¹å?>>>
</el-link>
<table-select :text="text" :select-config="selectConfig" @confirm-click="handleConfirm">
<template #status="scope">
@@ -16,7 +16,7 @@
</el-tag>
</template>
<template #gender="scope">
<DictLabel v-model="scope.row.gender" code="gender" />
<DictTag v-model="scope.row.gender" code="gender" />
</template>
</table-select>
</div>

View File

@@ -1,9 +1,9 @@
<template>
<div class="app-container">
<!-- 基础用法 -->
<TextScroll text="这是一条基础的滚动公告,默认向左滚动" typewriter />
<TextScroll text="这是一条基础的滚动公告,默认向左滚动" typewriter />
<!-- 使用不同的类 -->
<!-- 使用不同的类 -->
<TextScroll type="success" text="这是一条成功类型的滚动公告" typewriter />
<TextScroll type="warning" text="这是一条警告类型的滚动公告" />

View File

@@ -7,7 +7,7 @@
target="_blank"
class="mb-10"
>
示例源码 请点>>>>
示例源码 请点<EFBFBD><EFBFBD>?>>>
</el-link>
<el-form>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container">
<!-- 表格 -->
<vxe-grid ref="xGrid" v-bind="gridOptions" v-on="gridEvents">
@@ -30,7 +30,7 @@
批量删除
</vxe-button>
</template>
<!-- 展开 -->
<!-- 展开 -->
<template #column-expand="{ row }">
<div style="padding: 20px">
<ul>
@@ -39,11 +39,11 @@
<span>{{ row.id }}</span>
</li>
<li>
<span>UserName</span>
<span>用户名</span>
<span>{{ row.username }}</span>
</li>
<li>
<span>CreateTime</span>
<span>创建时间</span>
<span>{{ row.createTime }}</span>
</li>
</ul>
@@ -75,20 +75,12 @@
</template>
<script setup lang="ts">
import { reactive, onMounted } from "vue";
import type {
VxeGridInstance,
VxeGridProps,
VxeGridListeners,
VxeModalInstance,
VxeModalProps,
VxeFormInstance,
VxeFormProps,
} from "vxe-table";
import { ref, reactive, onMounted } from "vue";
import type { VxeGridInstance, VxeGridProps, VxeGridListeners } from "vxe-table";
import { VXETable } from "vxe-table";
const options = [
{ label: "管理", value: "admin" },
{ label: "管理", value: "admin" },
{ label: "用户", value: "user" },
{ label: "访客", value: "guest" },
];
@@ -125,7 +117,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
// roles: "-",
// phone: "-",
// email: "-",
// status: "启用7条",
// status: "启用/禁用",
// createTime: "-",
// },
// ],
@@ -136,7 +128,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
if (columnIndex === 0 || column.field === undefined) {
return "";
} else if (column.field === "status") {
return `启用${data.reduce((sum, row) => sum + (row.status ? 1 : 0), 0)}`;
return `启用 ${data.reduce((sum, row) => sum + (row.status ? 1 : 0), 0)} `;
}
return "-";
}),
@@ -186,7 +178,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
],
// 列配置信息
columnConfig: {
// 每一列是否启用列宽调整
// 每一列是否启用列宽拖动
resizable: true,
},
// 自定义列配置项
@@ -225,7 +217,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
titlePrefix: {
useHTML: true,
content:
'点击链接<a class="link" href="https://vxetable.cn" target="_blank">vxe-table官网</a>',
'点击链接 <a class="link" href="https://vxetable.cn" target="_blank">vxe-table官网</a>',
icon: "vxe-icon-question-circle-fill",
},
// 项渲染器配置项
@@ -335,7 +327,7 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
enabled: true,
pageSize: 10,
},
// 数据代理配置项
// 数据代理配置项"
proxyConfig: {
// 是否自动加载查询数据
autoLoad: true,
@@ -349,9 +341,9 @@ const gridOptions = reactive<VxeGridProps<RowMeta>>({
sort: true,
// 获取响应的值配置
response: {
// 只对 pager-config 配置有效,响应结果中获取数据列表的属性(分页场景)
// 只对 pager-config 配置有效,响应结果中获取数据列表的属性(分页场景)
result: "result",
// 只对 pager-config 配置有效,响应结果中获取分页的属性(分页场景)
// 只对 pager-config 配置有效,响应结果中获取分页的属性(分页场景)
total: "total",
},
ajax: {
@@ -480,8 +472,8 @@ const gridEvents: VxeGridListeners<RowMeta> = {
// #endregion
// #region vxe-modal
const xModal = ref<VxeModalInstance>();
const modalOptions = reactive<VxeModalProps>({
const xModal = ref();
const modalOptions = reactive({
// 窗口的标题
title: "",
// 是否允许点击遮罩层关闭窗口
@@ -497,8 +489,8 @@ const modalOptions = reactive<VxeModalProps>({
// #endregion
// #region vxe-form
const xForm = ref<VxeFormInstance>();
const formOptions = reactive<VxeFormProps>({
const xForm = ref();
const formOptions = reactive({
// 所有项的栅格占据的列数
span: 24,
// 所有项的标题宽度
@@ -508,7 +500,7 @@ const formOptions = reactive<VxeFormProps>({
username: "",
password: "",
},
// 项列表
// 项配置
items: [
{
field: "username",
@@ -531,7 +523,7 @@ const formOptions = reactive<VxeFormProps>({
},
},
{
align: "right",
align: "right" as const,
itemRender: {
name: "$buttons",
children: [
@@ -562,7 +554,7 @@ const formOptions = reactive<VxeFormProps>({
username: [
{
required: true,
validator: ({ itemValue }) => {
validator: ({ itemValue }: { itemValue: string }) => {
switch (true) {
case !itemValue:
return new Error("请输入");
@@ -575,7 +567,7 @@ const formOptions = reactive<VxeFormProps>({
password: [
{
required: true,
validator: ({ itemValue }) => {
validator: ({ itemValue }: { itemValue: string }) => {
switch (true) {
case !itemValue:
return new Error("请输入");
@@ -622,7 +614,7 @@ const curd = {
VXETable.modal.confirm("确定要删除吗?").then((type) => {
if (type === "confirm") {
// 执行删除操作
console.log("删除的ID", ids);
console.log("删除的ID", ids);
}
});
},

View File

@@ -7,7 +7,7 @@
target="_blank"
class="mb-[20px]"
>
示例源码 请点击>>>>
示例源码 请点击>>>
</el-link>
<WangEditor v-model="value" height="400px" />

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/websocket.vue"
@@ -6,7 +6,7 @@
target="_blank"
class="mb-[20px]"
>
示例源码 请点击>>>>
示例源码 请点击>>>
</el-link>
<el-row :gutter="10">
<el-col :span="12">
@@ -98,12 +98,12 @@
<script setup lang="ts">
import { useStomp } from "@/composables/websocket/useStomp";
import { useUserStoreHook } from "@/store/modules/user-store";
import { useUserStoreHook } from "@/store/modules/user";
const userStore = useUserStoreHook();
// 用于手动调整 WebSocket 地址
const socketEndpoint = ref(import.meta.env.VITE_APP_WS_ENDPOINT);
// 同步连接状态
// 同步连接状态"
interface MessageType {
type?: string;
sender?: string;