初始化本地仓库
This commit is contained in:
@@ -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>
|
||||
+143
@@ -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>
|
||||
+112
@@ -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>
|
||||
Reference in New Issue
Block a user