feat: 完善基础组件;完成用户管理

This commit is contained in:
LittleBoy 2024-12-19 19:49:40 +08:00
parent d23f5d5668
commit f57daee407
8 changed files with 254 additions and 87 deletions

View File

@ -1,4 +1,4 @@
import ButtonComponent from './button.vue';
console.log(ButtonComponent)
// console.log(ButtonComponent)
export const Button = ButtonComponent

View File

@ -2,12 +2,71 @@
<p>输出计算</p>
<!-- <DataField /> -->
<Button @click="showMessage">test</Button>
<div>
<Demo />
</div>
<div>
<Form @finish="handleSave" @finishFailed="handleFinishError" autocomplete="off" :model="userData"
:label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
<Form.Item label="用户名" name="nickname" :rules="[{ required: true, message: '请输入姓名' }]">
<Input placeholder="请输入姓名" v-model:value="userData.nickname" />
</Form.Item>
<Form.Item label="账号" name="account" :rules="[{ required: true, message: '请输入手机号或邮箱' }]">
<Input placeholder="请输入手机号或邮箱" v-model:value="userData.account" />
</Form.Item>
<Form.Item label="密码" name="password" :rules="passwordRules">
<Input placeholder="请输入6-12位密码" type="password" v-model:value="userData.password" />
<div>{{ userData?.id ? '如果不需要修改,请不要填写' : '' }}</div>
</Form.Item>
<Form.Item label="角色" name="role" :rules="[{ required: true, message: '请选择用户角色' }]">
<Select placeholder="请选择" style="width: 100%;" v-model:value="userData.role">
<SelectOption v-for="(it, opIndex) in RoleList" :key="opIndex" :value="it.value">{{ it.label }}
</SelectOption>
</Select>
</Form.Item>
<Form.Item class="flex justify-end mt-10 mb-0">
<Space :size="20">
<Button html-type="submit">确定</Button>
</Space>
</Form.Item>
</Form>
</div>
</template>
<script setup lang="ts">
import DataField from "@/components/data-fields/index.vue"
import Button from "../components/button/button.vue";
import {message} from "../components/message";
import { reactive, computed } from 'vue'
import { Form, Input, Button,Select,SelectOption,Space } from "ant-design-vue"
import { RoleList } from '@/core/role';
// import DataField from "@/components/data-fields/index.vue"
// import Button from "../components/button/button.vue";
import { message } from "../components/message";
import Demo from './result/demo.vue'
const userData = reactive<Partial<UserInfo>>({});
const passwordRules = computed(() => {
return userData?.id ? [
{ min: 6, max: 12, message: '请输入6-12位密码' }
] : [
{ required: true, message: '请输入6-12位密码' },
{ min: 6, max: 12, message: '请输入6-12位密码' }
]
})
function handleSave() {
console.log(userData)
if (userData) {
// run(userData)
}
//
}
function handleFinishError(errorInfo: any) {
console.log('handleFinishError', errorInfo)
}
const showMessage = () => {
message.show(
'This notification does not automatically close, and you need to click the close button to close.',

45
src/pages/result/demo.vue Normal file
View File

@ -0,0 +1,45 @@
<template>
<Form v-model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off"
@finish="onFinish" @finishFailed="onFinishFailed">
<Form.Item label="Username" name="username"
:rules="[{ required: true, message: 'Please input your username!' }]">
<Input v-model:value="formState.username" />
</Form.Item>
<Form.Item label="Password" name="password"
:rules="[{ required: true, message: 'Please input your password!' }]">
<InputPassword v-model:value="formState.password" />
</Form.Item>
<Form.Item name="remember" :wrapper-col="{ offset: 8, span: 16 }">
<Checkbox v-model:checked="formState.remember">Remember me</Checkbox>
</Form.Item>
<Form.Item :wrapper-col="{ offset: 8, span: 16 }">
<Button type="primary" html-type="submit">Submit</Button>
</Form.Item>
</Form>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { Form, Input, Checkbox, Button, InputPassword } from 'ant-design-vue'
interface FormState {
username: string;
password: string;
remember: boolean;
}
const formState = reactive<FormState>({
username: '',
password: '',
remember: true,
});
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
</script>

View File

@ -1,56 +1,50 @@
<script setup lang="ts">
import { ref, h } from "vue";
import { ref, h, watch } from "vue";
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
import {
Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin,
Modal,message
Modal, message
} from 'ant-design-vue'
import { RoleEnum, AllRoleList } from '@/core/role'
import { columns, getList,deleteUser } from "@/service/api/user";
import { columns, getList, deleteUser } from "@/service/api/user";
import PageHeader from '@/components/page-header.vue'
import useRequest from "@/service/useRequest";
import EditModal from './modal.vue'
const editUser = ref<UserInfo>()
const editUser = ref<Partial<UserInfo>>()
const searchParams = ref<UserSearchParam>({
page: 1,
limit: 10, role: ''
limit: 10,
role: ''
})
const { data: allDataList, loading, run, refresh } = useRequest(() => getList(searchParams.value), {
refreshDeps: [searchParams]
})
const { data: allDataList, loading, run } = useRequest(() => getList(searchParams.value))
// onMounted(() => {
// getList(searchParams.value).then(ret=>{
// allDataList.value = ret
// })
// })
watch(searchParams, run, { deep: true })
function search() {
searchParams.value = {
...searchParams.value,
page: 1,
limit: 10
}
searchParams.value.page = 1;
run()
}
function deleteUserById(id: number) {
deleteUser(id).then(() => {
message.success('删除成功')
run()
refresh()
})
}
function handleDelete(id: number) {
Modal.confirm({
title:'删除账号',
content:'确定要删除该账号吗?',
cancelText:'取消',
okText:'删除',
title: '删除账号',
content: '确定要删除该账号吗?',
cancelText: '取消',
okText: '删除',
okType: 'danger',
okButtonProps:{
okButtonProps: {
},
onOk:()=>{
onOk: () => {
deleteUserById(id)
}
})
@ -72,13 +66,16 @@ function handleDelete(id: number) {
<Space class="form-item">
<span>类型</span>
<Select class="search-item" v-model:value="searchParams.role" style="width: 200px;">
<SelectOption v-for="(it, opIndex) in AllRoleList" :key="opIndex" :value="it.value">{{ it.label }}
<SelectOption v-for="(it, opIndex) in AllRoleList" :key="opIndex" :value="it.value">{{ it.label
}}
</SelectOption>
</Select>
</Space>
<Space class="form-item" :size="15">
<Button :icon="h(SearchOutlined)" class="flex item-center" type="primary" @click="search()">查询</Button>
<Button :icon="h(PlusOutlined)" class="btn-info flex item-center" @click="editUser = {id:0}">新增</Button>
<Button :icon="h(SearchOutlined)" type="primary"
@click="search()">查询</Button>
<Button :icon="h(PlusOutlined)" class="btn-info"
@click="editUser = { id: 0 }">新增</Button>
</Space>
</Space>
</div>
@ -99,7 +96,7 @@ function handleDelete(id: number) {
<td>{{ user.role == RoleEnum.ROOT ? '超级管理员' : '管理员' }}</td>
<td>
<Space :size="20">
<a @click="editUser = user">编辑</a>
<a @click="editUser = { ...user, password: '' }">编辑</a>
<a @click="handleDelete(user.id)">删除</a>
</Space>
</td>
@ -117,15 +114,16 @@ function handleDelete(id: number) {
:total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" />
</div>
</div>
<EditModal v-if="!!editUser" v-model="editUser" />
<EditModal v-if="!!editUser" v-model="editUser" @saved="refresh" />
</div>
</template>
<style lang="scss">
.ant-btn-dangerous{
.ant-btn-dangerous {
background-color: hsl(359, 100%, 60%);
color: white !important;
&:hover{
&:hover {
background-color: hsl(359, 100%, 70%);
}
}

View File

@ -1,57 +1,94 @@
<script lang="ts" setup>
import { defineModel } from 'vue';
import { Modal, Form, FormItem, Input, Select, SelectOption, Space, Button } from 'ant-design-vue';
import { computed, defineModel, reactive } from 'vue';
import { Modal, Form, Input, Select, SelectOption, Space, Button } from 'ant-design-vue';
import { md5 } from 'js-md5';
import { RoleList } from '@/core/role';
import useRequest from '@/service/useRequest';
import { saveUser } from '@/service/api/user';
const editUser = defineModel<UserInfo>();
const editUser = defineModel<Partial<UserInfo>>();
const userData = reactive<Partial<UserInfo>>({ ...(editUser ? editUser.value : {}) });
function handleSave(values: UserInfo) {
console.log(values)
defineExpose({
})
const emit = defineEmits<{
(e: 'saved'): void
}>()
const { loading, runAsync, error } = useRequest(saveUser, {
manual: true
})
function handleSave() {
runAsync({
id: userData.id,
nickname: userData.nickname,
account: userData.account,
role: userData.role,
password: userData.password ? md5(userData.password) : undefined,
}).then(() => {
editUser.value = undefined;
emit('saved')
})
//
}
function handleCancel() {
editUser.value = undefined;
}
const passwordRules = computed(() => {
return editUser?.value?.id ? [
{ min: 6, max: 12, message: '请输入6-12位密码' }
] : [
{ required: true, message: '请输入6-12位密码' },
{ min: 6, max: 12, message: '请输入6-12位密码' }
]
})
function handleFinishError(errorInfo: any) {
console.log('handleFinishError', errorInfo)
}
function onValuesChange() {
error.value = undefined
}
</script>
<template>
<Modal :width="400" :open="true" :footer="null" @cancel="handleCancel">
<Modal :width="400" :open="true" :footer="null" @cancel="handleCancel" :mask-closable="false"
:destroy-on-close="true">
<div class="pt-12">
<Form @finish="handleSave" autocomplete="off" v-model="editUser" :label-col="{ span: 5 }"
:wrapper-col="{ span: 18 }">
<FormItem label="用户名" name="nickname" :rules="[{ required: true, message: '请输入姓名' }]">
<Input placeholder="请输入姓名" />
</FormItem>
<Form @finish="handleSave" @finishFailed="handleFinishError" @validate="onValuesChange" autocomplete="off"
:model="userData" :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
<Form.Item label="姓名" name="nickname" :rules="[{ required: true, message: '请输入姓名' }]">
<Input placeholder="请输入姓名" v-model:value="userData.nickname" />
</Form.Item>
<FormItem label="账号" name="account" :rules="[{ required: true, message: '请输入手机号或邮箱' }]">
<Input placeholder="请输入手机号或邮箱" />
</FormItem>
<Form.Item label="账号" name="account" :rules="[{ required: true, message: '请输入手机号或邮箱' }]">
<Input placeholder="请输入手机号或邮箱" v-model:value="userData.account" />
</Form.Item>
<FormItem label="密码" name="password" :rules="[{
validator: async (_:any, value:string) => {
console.log('mima',value)
if(editUser.value?.id){
return true;
}
if(!value || value.length < 6 || value.length > 12){
throw new Error('密码长度6-12位');
};
return true;
}
}]">
<Input placeholder="请输入6-12位密码" type="password" />
</FormItem>
<Form.Item label="密码" name="password" :rules="passwordRules">
<Input placeholder="请输入6-12位密码" type="password" v-model:value="userData.password" />
<template #extra v-if="editUser?.id">
<div>留空为不修改密码</div>
</template>
</Form.Item>
<FormItem label="角色" name="role" :rules="[{ required: true, message: '请选择用户角色' }]">
<Select placeholder="请选择" style="width: 100%;">
<Form.Item label="角色" name="role" :rules="[{ required: true, message: '请选择用户角色' }]">
<Select placeholder="请选择" style="width: 100%;" v-model:value="userData.role">
<SelectOption v-for="(it, opIndex) in RoleList" :key="opIndex" :value="it.value">{{ it.label }}
</SelectOption>
</Select>
</FormItem>
<FormItem class="flex justify-end mt-10 mb-0">
</Form.Item>
<div v-if="error" class="text-red-500 text-center">
{{ error?.message }}
</div>
<Form.Item class="flex justify-end mt-6 mb-0">
<Space :size="20">
<Button @click="handleCancel">取消</Button>
<Button html-type="submit" class="btn-info">确定</Button>
<Button :loading="loading" html-type="submit" class="btn-info">确定</Button>
</Space>
</FormItem>
</Form.Item>
</Form>
</div>
</Modal>

View File

@ -2,6 +2,10 @@ import { sleep } from "@/core/sleep";
import { get, post } from "./request";
export async function saveUser(params: Partial<UserInfo>) {
await sleep(200);
return await post<UserInfo>(`/user/save`, params);
}
export function userLogin(params: LoginModel) {
return post<{ token: string; user: UserInfo }>(`/user/login`, params);
@ -21,7 +25,7 @@ export function deleteUser(id:number){
}
export const columns = [
{ title: '账号', dataIndex: 'account' },
{ title: '姓名', dataIndex: 'nickname' },
{ title: '账号', dataIndex: 'account' },
{ title: '角色', dataIndex: 'role' },
]

View File

@ -1,40 +1,64 @@
import { onMounted, ref, watch } from "vue";
import { onMounted, Ref, ref, watch } from "vue";
export default function useRequest<T>(
service: () => Promise<T>,
type UseRequestResult<R = any, P extends any[] = any[]> = {
data: Ref<R | undefined>;
loading: Ref<boolean>;
error: Ref<Error | undefined>;
refresh: () => void;
run: (...args: P) => Promise<void>;
runAsync: (...args: P) => Promise<void>;
}
export type Service<R, P extends any[]> = (...args: P) => Promise<R>;
export default function useRequest<T = any, P extends any[] = any[]>(
service: Service<T, P>,
options?: {
defaultParams?: P;
manual?: boolean;
refreshDeps?: any[];
}
) {
): UseRequestResult<T, P> {
const params = ref<P>((options?.defaultParams || []) as P) as Ref<P>;
const data = ref<T>();
const loading = ref(false);
const error = ref<string>();
const error = ref<Error>();
const _request = () => {
const _request = (...args: P) => {
loading.value = true
return service()
return service(...args)
.then((res) => {
data.value = res
})
.catch((e: Error) => {
error.value = e.message
error.value = e
})
.finally(() => {
loading.value = false
})
}
const runAsync = async (...args: P) => {
try {
loading.value = true
const _ = await service(...args);
data.value = _
loading.value = false
} catch (e) {
loading.value = false
error.value = e as Error;
throw e
}
}
const refresh = () => {
_request()
_request(...params.value)
}
onMounted(() => {
if (!options?.manual) {
_request()
_request(...params.value)
}
});
if (options?.refreshDeps) {
watch(options.refreshDeps, () => {
_request()
_request(...params.value)
})
}
return {
@ -42,6 +66,7 @@ export default function useRequest<T>(
refresh,
loading,
error,
run: _request
run: _request,
runAsync
}
}

View File

@ -168,9 +168,8 @@ img {
}
.ant-btn {
.anticon {
position: relative;
transform: translateY(1px);
svg {
vertical-align: -1px;
}
&.btn-info {