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'; import ButtonComponent from './button.vue';
console.log(ButtonComponent) // console.log(ButtonComponent)
export const Button = ButtonComponent export const Button = ButtonComponent

View File

@ -2,12 +2,71 @@
<p>输出计算</p> <p>输出计算</p>
<!-- <DataField /> --> <!-- <DataField /> -->
<Button @click="showMessage">test</Button> <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> </template>
<script setup lang="ts"> <script setup lang="ts">
import DataField from "@/components/data-fields/index.vue" import { reactive, computed } from 'vue'
import Button from "../components/button/button.vue"; import { Form, Input, Button,Select,SelectOption,Space } from "ant-design-vue"
import {message} from "../components/message"; 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 = () => { const showMessage = () => {
message.show( message.show(
'This notification does not automatically close, and you need to click the close button to close.', '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"> <script setup lang="ts">
import { ref, h } from "vue"; import { ref, h, watch } from "vue";
import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { import {
Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin, Pagination, Select, SelectOption, Input, Button, Space, Empty, Spin,
Modal,message Modal, message
} from 'ant-design-vue' } from 'ant-design-vue'
import { RoleEnum, AllRoleList } from '@/core/role' 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 PageHeader from '@/components/page-header.vue'
import useRequest from "@/service/useRequest"; import useRequest from "@/service/useRequest";
import EditModal from './modal.vue' import EditModal from './modal.vue'
const editUser = ref<UserInfo>() const editUser = ref<Partial<UserInfo>>()
const searchParams = ref<UserSearchParam>({ const searchParams = ref<UserSearchParam>({
page: 1, 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)) watch(searchParams, run, { deep: true })
// onMounted(() => {
// getList(searchParams.value).then(ret=>{
// allDataList.value = ret
// })
// })
function search() { function search() {
searchParams.value = { searchParams.value.page = 1;
...searchParams.value,
page: 1,
limit: 10
}
run() run()
} }
function deleteUserById(id: number) { function deleteUserById(id: number) {
deleteUser(id).then(() => { deleteUser(id).then(() => {
message.success('删除成功') message.success('删除成功')
run() refresh()
}) })
} }
function handleDelete(id: number) { function handleDelete(id: number) {
Modal.confirm({ Modal.confirm({
title:'删除账号', title: '删除账号',
content:'确定要删除该账号吗?', content: '确定要删除该账号吗?',
cancelText:'取消', cancelText: '取消',
okText:'删除', okText: '删除',
okType: 'danger', okType: 'danger',
okButtonProps:{ okButtonProps: {
}, },
onOk:()=>{ onOk: () => {
deleteUserById(id) deleteUserById(id)
} }
}) })
@ -72,13 +66,16 @@ function handleDelete(id: number) {
<Space class="form-item"> <Space class="form-item">
<span>类型</span> <span>类型</span>
<Select class="search-item" v-model:value="searchParams.role" style="width: 200px;"> <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> </SelectOption>
</Select> </Select>
</Space> </Space>
<Space class="form-item" :size="15"> <Space class="form-item" :size="15">
<Button :icon="h(SearchOutlined)" class="flex item-center" type="primary" @click="search()">查询</Button> <Button :icon="h(SearchOutlined)" type="primary"
<Button :icon="h(PlusOutlined)" class="btn-info flex item-center" @click="editUser = {id:0}">新增</Button> @click="search()">查询</Button>
<Button :icon="h(PlusOutlined)" class="btn-info"
@click="editUser = { id: 0 }">新增</Button>
</Space> </Space>
</Space> </Space>
</div> </div>
@ -99,7 +96,7 @@ function handleDelete(id: number) {
<td>{{ user.role == RoleEnum.ROOT ? '超级管理员' : '管理员' }}</td> <td>{{ user.role == RoleEnum.ROOT ? '超级管理员' : '管理员' }}</td>
<td> <td>
<Space :size="20"> <Space :size="20">
<a @click="editUser = user">编辑</a> <a @click="editUser = { ...user, password: '' }">编辑</a>
<a @click="handleDelete(user.id)">删除</a> <a @click="handleDelete(user.id)">删除</a>
</Space> </Space>
</td> </td>
@ -117,15 +114,16 @@ function handleDelete(id: number) {
:total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" /> :total="allDataList.total" :show-total="(total: number) => `共 ${total} 条`" />
</div> </div>
</div> </div>
<EditModal v-if="!!editUser" v-model="editUser" /> <EditModal v-if="!!editUser" v-model="editUser" @saved="refresh" />
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
.ant-btn-dangerous{ .ant-btn-dangerous {
background-color: hsl(359, 100%, 60%); background-color: hsl(359, 100%, 60%);
color: white !important; color: white !important;
&:hover{
&:hover {
background-color: hsl(359, 100%, 70%); background-color: hsl(359, 100%, 70%);
} }
} }

View File

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

View File

@ -2,6 +2,10 @@ import { sleep } from "@/core/sleep";
import { get, post } from "./request"; 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) { export function userLogin(params: LoginModel) {
return post<{ token: string; user: UserInfo }>(`/user/login`, params); return post<{ token: string; user: UserInfo }>(`/user/login`, params);
@ -21,7 +25,7 @@ export function deleteUser(id:number){
} }
export const columns = [ export const columns = [
{ title: '账号', dataIndex: 'account' },
{ title: '姓名', dataIndex: 'nickname' }, { title: '姓名', dataIndex: 'nickname' },
{ title: '账号', dataIndex: 'account' },
{ title: '角色', dataIndex: 'role' }, { 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>( type UseRequestResult<R = any, P extends any[] = any[]> = {
service: () => Promise<T>, 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?: { options?: {
defaultParams?: P;
manual?: boolean; manual?: boolean;
refreshDeps?: any[]; refreshDeps?: any[];
} }
) { ): UseRequestResult<T, P> {
const params = ref<P>((options?.defaultParams || []) as P) as Ref<P>;
const data = ref<T>(); const data = ref<T>();
const loading = ref(false); const loading = ref(false);
const error = ref<string>(); const error = ref<Error>();
const _request = () => { const _request = (...args: P) => {
loading.value = true loading.value = true
return service() return service(...args)
.then((res) => { .then((res) => {
data.value = res data.value = res
}) })
.catch((e: Error) => { .catch((e: Error) => {
error.value = e.message error.value = e
}) })
.finally(() => { .finally(() => {
loading.value = false 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 = () => { const refresh = () => {
_request() _request(...params.value)
} }
onMounted(() => { onMounted(() => {
if (!options?.manual) { if (!options?.manual) {
_request() _request(...params.value)
} }
}); });
if (options?.refreshDeps) { if (options?.refreshDeps) {
watch(options.refreshDeps, () => { watch(options.refreshDeps, () => {
_request() _request(...params.value)
}) })
} }
return { return {
@ -42,6 +66,7 @@ export default function useRequest<T>(
refresh, refresh,
loading, loading,
error, error,
run: _request run: _request,
runAsync
} }
} }

View File

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