初始化本地仓库

This commit is contained in:
2026-08-31 11:33:59 +08:00
commit 663f74b633
2390 changed files with 402918 additions and 0 deletions
@@ -0,0 +1,269 @@
<!-- 用户管理页面 -->
<!-- art-full-height 自动计算出页面剩余高度 -->
<!-- art-table-card 一个符合系统样式的 class,同时自动撑满剩余高度 -->
<!-- 更多 useTable 使用示例请移步至 功能示例 下面的高级表格示例或者查看官方文档 -->
<!-- useTable 文档:https://www.artd.pro/docs/zh/guide/hooks/use-table.html -->
<template>
<div class="user-page art-full-height">
<!-- 搜索栏 -->
<UserSearch
v-model="searchForm"
@search="handleSearch"
@reset="resetSearchParams"
></UserSearch>
<ElCard class="art-table-card" shadow="never">
<!-- 表格头部 -->
<ArtTableHeader
v-model:columns="columnChecks"
:loading="loading"
@refresh="refreshData"
>
<template #left>
<ElSpace wrap>
<ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>
</ElSpace>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
:loading="loading"
:data="data"
:columns="columns"
:pagination="pagination"
@selection-change="handleSelectionChange"
@pagination:size-change="handleSizeChange"
@pagination:current-change="handleCurrentChange"
>
</ArtTable>
<!-- 用户弹窗 -->
<UserDialog
v-model:visible="dialogVisible"
:type="dialogType"
:user-data="currentUserData"
@submit="handleDialogSubmit"
/>
</ElCard>
</div>
</template>
<script setup lang="ts">
import ArtButtonTable from "@/components/core/forms/art-button-table/index.vue";
import { ACCOUNT_TABLE_DATA } from "@/apps/demo/mock/temp/formData";
import { useTable } from "@/hooks/core/useTable";
import { fetchGetUserList } from "@/apps/demo/api/system-manage";
import UserSearch from "./modules/user-search.vue";
import UserDialog from "./modules/user-dialog.vue";
import { ElTag, ElMessageBox, ElImage } from "element-plus";
import { DialogType } from "@/types";
defineOptions({ name: "User" });
type UserListItem = Api.SystemManage.UserListItem;
// 弹窗相关
const dialogType = ref<DialogType>("add");
const dialogVisible = ref(false);
const currentUserData = ref<Partial<UserListItem>>({});
// 选中行
const selectedRows = ref<UserListItem[]>([]);
// 搜索表单
const searchForm = ref({
userName: undefined,
userGender: undefined,
userPhone: undefined,
userEmail: undefined,
status: "1",
});
// 用户状态配置
const USER_STATUS_CONFIG = {
"1": { type: "success" as const, text: "在线" },
"2": { type: "info" as const, text: "离线" },
"3": { type: "warning" as const, text: "异常" },
"4": { type: "danger" as const, text: "注销" },
} as const;
/**
* 获取用户状态配置
*/
const getUserStatusConfig = (status: string) => {
return (
USER_STATUS_CONFIG[status as keyof typeof USER_STATUS_CONFIG] || {
type: "info" as const,
text: "未知",
}
);
};
const {
columns,
columnChecks,
data,
loading,
pagination,
getData,
searchParams,
resetSearchParams,
handleSizeChange,
handleCurrentChange,
refreshData,
} = useTable({
// 核心配置
core: {
apiFn: fetchGetUserList,
apiParams: {
current: 1,
size: 20,
...searchForm.value,
},
// 自定义分页字段映射,未设置时将使用全局配置 tableConfig.ts 中的 paginationKey
// paginationKey: {
// current: 'pageNum',
// size: 'pageSize'
// },
columnsFactory: () => [
{ type: "selection" }, // 勾选列
{ type: "index", width: 60, label: "序号" }, // 序号
{
prop: "userInfo",
label: "用户名",
width: 280,
// visible: false, // 默认是否显示列
formatter: (row) => {
return h("div", { class: "user flex-c" }, [
h(ElImage, {
class: "size-9.5 rounded-md",
src: row.avatar,
previewSrcList: [row.avatar],
// 图片预览是否插入至 body 元素上,用于解决表格内部图片预览样式异常
previewTeleported: true,
}),
h("div", { class: "ml-2" }, [
h("p", { class: "user-name" }, row.userName),
h("p", { class: "email" }, row.userEmail),
]),
]);
},
},
{
prop: "userGender",
label: "性别",
sortable: true,
formatter: (row) => row.userGender,
},
{ prop: "userPhone", label: "手机号" },
{
prop: "status",
label: "状态",
formatter: (row) => {
const statusConfig = getUserStatusConfig(row.status);
return h(ElTag, { type: statusConfig.type }, () => statusConfig.text);
},
},
{
prop: "createTime",
label: "创建日期",
sortable: true,
},
{
prop: "operation",
label: "操作",
width: 120,
fixed: "right", // 固定列
formatter: (row) =>
h("div", [
h(ArtButtonTable, {
type: "edit",
onClick: () => showDialog("edit", row),
}),
h(ArtButtonTable, {
type: "delete",
onClick: () => deleteUser(row),
}),
]),
},
],
},
// 数据处理
transform: {
// 数据转换器 - 替换头像
dataTransformer: (records) => {
// 类型守卫检查
if (!Array.isArray(records)) {
console.warn("数据转换器: 期望数组类型,实际收到:", typeof records);
return [];
}
// 使用本地头像替换接口返回的头像
return records.map((item, index: number) => {
return {
...item,
avatar: ACCOUNT_TABLE_DATA[index % ACCOUNT_TABLE_DATA.length].avatar,
};
});
},
},
});
/**
* 搜索处理
* @param params 参数
*/
const handleSearch = (params: Record<string, any>) => {
console.log(params);
// 搜索参数赋值
Object.assign(searchParams, params);
getData();
};
/**
* 显示用户弹窗
*/
const showDialog = (type: DialogType, row?: UserListItem): void => {
console.log("打开弹窗:", { type, row });
dialogType.value = type;
currentUserData.value = row || {};
nextTick(() => {
dialogVisible.value = true;
});
};
/**
* 删除用户
*/
const deleteUser = (row: UserListItem): void => {
console.log("删除用户:", row);
ElMessageBox.confirm(`确定要注销该用户吗?`, "注销用户", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "error",
}).then(() => {
ElMessage.success("注销成功");
});
};
/**
* 处理弹窗提交事件
*/
const handleDialogSubmit = async () => {
try {
dialogVisible.value = false;
currentUserData.value = {};
} catch (error) {
console.error("提交失败:", error);
}
};
/**
* 处理表格行选择变化
*/
const handleSelectionChange = (selection: UserListItem[]): void => {
selectedRows.value = selection;
console.log("选中行数据:", selectedRows.value);
};
</script>
@@ -0,0 +1,143 @@
<template>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '添加用户' : '编辑用户'"
width="30%"
align-center
>
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="80px">
<ElFormItem label="用户名" prop="username">
<ElInput v-model="formData.username" placeholder="请输入用户名" />
</ElFormItem>
<ElFormItem label="手机号" prop="phone">
<ElInput v-model="formData.phone" placeholder="请输入手机号" />
</ElFormItem>
<ElFormItem label="性别" prop="gender">
<ElSelect v-model="formData.gender">
<ElOption label="男" value="男" />
<ElOption label="女" value="女" />
</ElSelect>
</ElFormItem>
<ElFormItem label="角色" prop="role">
<ElSelect v-model="formData.role" multiple>
<ElOption
v-for="role in roleList"
:key="role.roleCode"
:value="role.roleCode"
:label="role.roleName"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit">提交</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ROLE_LIST_DATA } from '@/apps/demo/mock/temp/formData'
import type { FormInstance, FormRules } from 'element-plus'
interface Props {
visible: boolean
type: string
userData?: Partial<Api.SystemManage.UserListItem>
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'submit'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 角色列表数据
const roleList = ref(ROLE_LIST_DATA)
// 对话框显示控制
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
const dialogType = computed(() => props.type)
// 表单实例
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive({
username: '',
phone: '',
gender: '男',
role: [] as string[]
})
// 表单验证规则
const rules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
gender: [{ required: true, message: '请选择性别', trigger: 'blur' }],
role: [{ required: true, message: '请选择角色', trigger: 'blur' }]
}
/**
* 初始化表单数据
* 根据对话框类型(新增/编辑)填充表单
*/
const initFormData = () => {
const isEdit = props.type === 'edit' && props.userData
const row = props.userData
Object.assign(formData, {
username: isEdit && row ? row.userName || '' : '',
phone: isEdit && row ? row.userPhone || '' : '',
gender: isEdit && row ? row.userGender || '男' : '男',
role: isEdit && row ? (Array.isArray(row.userRoles) ? row.userRoles : []) : []
})
}
/**
* 监听对话框状态变化
* 当对话框打开时初始化表单数据并清除验证状态
*/
watch(
() => [props.visible, props.type, props.userData],
([visible]) => {
if (visible) {
initFormData()
nextTick(() => {
formRef.value?.clearValidate()
})
}
},
{ immediate: true }
)
/**
* 提交表单
* 验证通过后触发提交事件
*/
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
ElMessage.success(dialogType.value === 'add' ? '添加成功' : '更新成功')
dialogVisible.value = false
emit('submit')
}
})
}
</script>
@@ -0,0 +1,112 @@
<template>
<ArtSearchBar
ref="searchBarRef"
v-model="formData"
:items="formItems"
:rules="rules"
@reset="handleReset"
@search="handleSearch"
>
</ArtSearchBar>
</template>
<script setup lang="ts">
interface Props {
modelValue: Record<string, any>
}
interface Emits {
(e: 'update:modelValue', value: Record<string, any>): void
(e: 'search', params: Record<string, any>): void
(e: 'reset'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 表单数据双向绑定
const searchBarRef = ref()
const formData = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 校验规则
const rules = {
// userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }]
}
// 动态 options
const statusOptions = ref<{ label: string; value: string; disabled?: boolean }[]>([])
// 模拟接口返回状态数据
function fetchStatusOptions(): Promise<typeof statusOptions.value> {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ label: '在线', value: '1' },
{ label: '离线', value: '2' },
{ label: '异常', value: '3' },
{ label: '注销', value: '4' }
])
}, 1000)
})
}
onMounted(async () => {
statusOptions.value = await fetchStatusOptions()
})
// 表单配置
const formItems = computed(() => [
{
label: '用户名',
key: 'userName',
type: 'input',
placeholder: '请输入用户名',
clearable: true
},
{
label: '手机号',
key: 'userPhone',
type: 'input',
props: { placeholder: '请输入手机号', maxlength: '11' }
},
{
label: '邮箱',
key: 'userEmail',
type: 'input',
props: { placeholder: '请输入邮箱' }
},
{
label: '状态',
key: 'status',
type: 'select',
props: {
placeholder: '请选择状态',
options: statusOptions.value
}
},
{
label: '性别',
key: 'userGender',
type: 'radiogroup',
props: {
options: [
{ label: '男', value: '1' },
{ label: '女', value: '2' }
]
}
}
])
// 事件
function handleReset() {
console.log('重置表单')
emit('reset')
}
async function handleSearch() {
await searchBarRef.value.validate()
emit('search', formData.value)
console.log('表单数据', formData.value)
}
</script>