refactor: ♻️ 升级 3.x 版本,布局重构和代码规范调整
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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 },
|
||||
]);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const contentConfig: IContentConfig = {
|
||||
},
|
||||
pagePosition: "right",
|
||||
toolbar: [],
|
||||
indexAction: function (params) {
|
||||
indexAction(params) {
|
||||
// 模拟发起网络请求获取列表数据
|
||||
console.log("indexAction:", params);
|
||||
return Promise.resolve({
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -35,7 +35,7 @@ const searchConfig: ISearchConfig = {
|
||||
attrs: { placeholder: "全部", clearable: true },
|
||||
options: stateArr as any,
|
||||
events: {
|
||||
change: function (e) {
|
||||
change(e) {
|
||||
console.log("选中的值: ", e);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user