refactor: ♻️ 升级 3.x 版本,布局重构和代码规范调整

This commit is contained in:
Ray.Hao
2025-06-05 23:57:01 +08:00
96 changed files with 4186 additions and 2340 deletions

View File

@@ -547,7 +547,7 @@ const initSort = () => {
const setNodeSort = (oldIndex: number, newIndex: number) => {
// 使用arr复制一份表格数组数据
let arr = Object.assign([], genConfigFormData.value.fieldConfigs);
const arr = Object.assign([], genConfigFormData.value.fieldConfigs);
const currentRow = arr.splice(oldIndex, 1)[0];
arr.splice(newIndex, 0, currentRow);
arr.forEach((item: FieldConfig, index) => {

View File

@@ -21,7 +21,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
list: res.list,
};
},
indexAction: function (params) {
indexAction(params) {
return UserAPI.getPage(params);
},
deleteAction: UserAPI.deleteByIds,
@@ -35,7 +35,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
console.log("importsAction", data);
return Promise.resolve();
},
exportsAction: async function (params) {
async exportsAction(params) {
// 模拟获取到的是全量数据
const res = await UserAPI.getPage(params);
console.log("exportsAction", res.list);

View File

@@ -15,7 +15,7 @@ const modalConfig: IModalConfig<UserForm> = {
beforeSubmit(data) {
console.log("beforeSubmit", data);
},
formAction: function (data) {
formAction(data) {
return UserAPI.update(data.id as string, data);
},
formItems: [

View File

@@ -9,9 +9,9 @@ interface OptionType {
}
// 明确指定类型为 OptionType[]
export let deptArr = ref<OptionType[]>([]);
export let roleArr = ref<OptionType[]>([]);
export let stateArr = ref<OptionType[]>([
export const deptArr = ref<OptionType[]>([]);
export const roleArr = ref<OptionType[]>([]);
export const stateArr = ref<OptionType[]>([
{ label: "启用", value: 1 },
{ label: "禁用", value: 0 },
]);

View File

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

View File

@@ -7,7 +7,7 @@ const contentConfig: IContentConfig = {
},
pagePosition: "right",
toolbar: [],
indexAction: function (params) {
indexAction(params) {
// 模拟发起网络请求获取列表数据
console.log("indexAction:", params);
return Promise.resolve({

View File

@@ -15,7 +15,7 @@ const modalConfig: IModalConfig = {
beforeSubmit(data) {
console.log("beforeSubmit", data);
},
formAction: function (data) {
formAction(data) {
// return UserAPI.update(data.id as string, data);
// 模拟发起网络请求修改字段
ElMessage.success(JSON.stringify(data));

View File

@@ -35,7 +35,7 @@ const searchConfig: ISearchConfig = {
attrs: { placeholder: "全部", clearable: true },
options: stateArr as any,
events: {
change: function (e) {
change(e) {
console.log("选中的值: ", e);
},
},

View File

@@ -1,10 +1,4 @@
<!-- 字典组件示例 -->
<script setup lang="ts">
const stringValue = ref("1"); // 性别(值为String)
const numberValue = ref(1); // 性别(值为Number)
const arrayValue = ref(["1", "2"]); // 性别(值为Array)
</script>
<template>
<div class="app-container">
<el-link
@@ -46,3 +40,9 @@ const arrayValue = ref(["1", "2"]); // 性别(值为Array)
</el-form>
</div>
</template>
<script setup lang="ts">
const stringValue = ref("1"); // 性别(值为String)
const numberValue = ref(1); // 性别(值为Number)
const arrayValue = ref(["1", "2"]); // 性别(值为Array)
</script>

View File

@@ -1,11 +1,4 @@
<!-- 图标选择器示例 -->
<script setup lang="ts">
// element-plus 图标格式以el-icon-开头
const iconName = ref("el-icon-edit");
// 本地SVG图标格式取 src/assets/icons 下的文件名不需要svg后缀
// const iconName = ref("api");
</script>
<template>
<div class="app-container">
<el-link
@@ -19,3 +12,10 @@ const iconName = ref("el-icon-edit");
<icon-select v-model="iconName" />
</div>
</template>
<script setup lang="ts">
// element-plus 图标格式以el-icon-开头
const iconName = ref("el-icon-edit");
// 本地SVG图标格式取 src/assets/icons 下的文件名不需要svg后缀
// const iconName = ref("api");
</script>

View File

@@ -1,3 +1,25 @@
<template>
<div class="canvas-dom">
<h3>基于canvas实现的签名组件</h3>
<header>
<el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
<el-button @click="handleToFile">保存到后端</el-button>
<el-button @click="handleClearSign">清空签名</el-button>
</header>
<canvas
ref="canvas"
height="200"
width="500"
@mousedown="onEventStart"
@mousemove.stop.prevent="onEventMove"
@mouseup="onEventEnd"
@touchstart="onEventStart"
@touchmove.stop.prevent="onEventMove"
@touchend="onEventEnd"
/>
<img v-if="imgUrl" :src="imgUrl" alt="签名" />
</div>
</template>
<script setup lang="ts">
import FileAPI from "@/api/file.api";
@@ -133,28 +155,6 @@ function paint(
ctx.stroke();
}
</script>
<template>
<div class="canvas-dom">
<h3>基于canvas实现的签名组件</h3>
<header>
<el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
<el-button @click="handleToFile">保存到后端</el-button>
<el-button @click="handleClearSign">清空签名</el-button>
</header>
<canvas
ref="canvas"
height="200"
width="500"
@mousedown="onEventStart"
@mousemove.stop.prevent="onEventMove"
@mouseup="onEventEnd"
@touchstart="onEventStart"
@touchmove.stop.prevent="onEventMove"
@touchend="onEventEnd"
/>
<img v-if="imgUrl" :src="imgUrl" alt="签名" />
</div>
</template>
<style scoped lang="scss">
.canvas-dom {
width: 100%;

View File

@@ -81,7 +81,7 @@ const selectConfig: ISelectConfig = {
},
},
],
indexAction: function (params) {
indexAction(params) {
if ("createAt" in params) {
const createAt = params.createAt as string[];
if (createAt?.length > 1) {

View File

@@ -1,4 +1,27 @@
<!-- 列表选择器示例 -->
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/table-select/index.vue"
type="primary"
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
</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>
</template>
<template #gender="scope">
<DictLabel v-model="scope.row.gender" code="gender" />
</template>
</table-select>
</div>
</template>
<script setup lang="ts">
import selectConfig from "./config/select";
import { useDictStore } from "@/store";
@@ -29,26 +52,3 @@ const text = computed(() => {
: "";
});
</script>
<template>
<div class="app-container">
<el-link
href="https://gitee.com/youlaiorg/vue3-element-admin/blob/master/src/views/demo/table-select/index.vue"
type="primary"
target="_blank"
class="mb-10"
>
示例源码 请点击>>>>
</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>
</template>
<template #gender="scope">
<DictLabel v-model="scope.row.gender" code="gender" />
</template>
</table-select>
</div>
</template>

View File

@@ -13,7 +13,7 @@
<TextScroll type="info" text="这是一条信息类型的滚动公告" />
<!-- 自定义速度和方向 -->
<TextScroll text="这是一条速度较慢、向右滚动的公告" :speed="30" direction="right" showClose />
<TextScroll text="这是一条速度较慢、向右滚动的公告" :speed="30" direction="right" show-close />
</div>
</template>

View File

@@ -1,10 +1,4 @@
<!-- wangEditor富文本编辑器示例 -->
<script setup lang="ts">
import WangEditor from "@/components/WangEditor/index.vue";
const value = ref("初始化内容");
</script>
<template>
<div class="app-container">
<el-link
@@ -22,3 +16,9 @@ const value = ref("初始化内容");
</div>
</div>
</template>
<script setup lang="ts">
import WangEditor from "@/components/WangEditor/index.vue";
const value = ref("初始化内容");
</script>

View File

@@ -86,22 +86,26 @@
</div>
<!-- 第三方登录 -->
<el-divider>
<el-text size="small">{{ t("login.otherLoginMethods") }}</el-text>
</el-divider>
<div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
<CommonWrapper>
<div text-20px class="i-svg:wechat" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:qq" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:github" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:gitee" />
</CommonWrapper>
<div class="third-party-login">
<div class="divider-container">
<div class="divider-line"></div>
<span class="divider-text">{{ t("login.otherLoginMethods") }}</span>
<div class="divider-line"></div>
</div>
<div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
<CommonWrapper>
<div text-20px class="i-svg:wechat" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:qq" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:github" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:gitee" />
</CommonWrapper>
</div>
</div>
</div>
</template>
@@ -122,9 +126,12 @@ const route = useRoute();
onMounted(() => getCaptcha());
const loginFormRef = ref<FormInstance>();
const loading = ref(false); // 按钮 loading 状态
const isCapsLock = ref(false); // 是否大写锁定
const captchaBase64 = ref(); // 验证码图片Base64字符串
const loading = ref(false);
// 是否大写锁定
const isCapsLock = ref(false);
// 验证码图片Base64字符串
const captchaBase64 = ref();
// 记住我
const rememberMe = Auth.getRememberMe();
const loginFormData = ref<LoginFormData>({
@@ -132,7 +139,7 @@ const loginFormData = ref<LoginFormData>({
password: "123456",
captchaKey: "",
captchaCode: "",
rememberMe: rememberMe,
rememberMe,
});
const loginRules = computed(() => {
@@ -178,7 +185,9 @@ function getCaptcha() {
.finally(() => (codeLoading.value = false));
}
// 登录提交处理
/**
* 登录提交
*/
async function handleLoginSubmit() {
try {
// 1. 表单验证
@@ -190,12 +199,16 @@ async function handleLoginSubmit() {
// 2. 执行登录
await userStore.login(loginFormData.value);
// 3. 获取用户信息
// 3. 获取用户信息(包含用户角色,用于路由生成)
await userStore.getUserInfo();
// 4. 解析并跳转目标地址
// 4. 登录成功,让路由守卫处理跳转逻辑
// 解析目标地址,但不直接跳转
const redirect = resolveRedirectTarget(route.query);
await router.push(redirect);
console.log("🎉 Login successful, target redirect:", redirect);
// 通过替换当前路由触发路由守卫,让守卫处理后续的路由生成和跳转
await router.replace(redirect);
// 5. 记住我功能已实现根据用户选择决定token的存储方式:
// - 选中"记住我": token存储在localStorage中浏览器关闭后仍然有效
@@ -211,8 +224,9 @@ async function handleLoginSubmit() {
/**
* 解析重定向目标
*
* @param query 路由查询参数
* @returns 标准化后的路由地址对象
* @returns 标准化后的路由地址
*/
function resolveRedirectTarget(query: LocationQuery): RouteLocationRaw {
// 默认跳转路径
@@ -247,3 +261,26 @@ function toOtherForm(type: "register" | "resetPwd") {
emit("update:modelValue", type);
}
</script>
<style lang="scss" scoped>
.third-party-login {
.divider-container {
display: flex;
align-items: center;
margin: 20px 0;
.divider-line {
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, var(--el-border-color-light), transparent);
}
.divider-text {
padding: 0 16px;
font-size: 12px;
color: var(--el-text-color-regular);
white-space: nowrap;
}
}
}
</style>

View File

@@ -46,7 +46,7 @@
<script setup lang="ts">
import logo from "@/assets/logo.png";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import CommonWrapper from "@/components/CommonWrapper/index.vue";
import DarkModeSwitch from "@/components/DarkModeSwitch/index.vue";