feat(ui):用户

This commit is contained in:
huangqj 2024-03-07 14:03:55 +08:00
parent 8e7413da97
commit cb0e7d64ff
6 changed files with 331 additions and 8 deletions

View File

@ -84,7 +84,7 @@ const optionsEvent = {
</AFormItem>
</AGridItem>
<AGridItem suffix>
<ASpace>
<ASpace class="flex-end">
<slot name="search-options" :option="optionsEvent">
<AButton
type="primary"
@ -114,4 +114,8 @@ const optionsEvent = {
.search-form-conteiner {
padding: 16px 0;
}
.flex-end {
display: flex;
justify-content: end;
}
</style>

View File

@ -33,8 +33,9 @@ const handleSearch = async (tips?: boolean) => {
onActivated(handleSearch);
</script>
<template>
<div class="search-table">
<div ref="tableContainerRef" class="search-table-container">
<slot name="header" v-bind="{ reload: handleSearch }" />
<div class="simple-table">
<div ref="tableContainerRef" class="simple-table-container">
<ATable
v-bind="{
...$attrs,
@ -52,15 +53,15 @@ onActivated(handleSearch);
</div>
</template>
<style scoped>
.search-table {
.simple-table {
display: flex;
flex-direction: column;
height: 100%;
}
.search-table-container {
.simple-table-container {
flex: 1;
}
.search-table-header {
.simple-table-header {
display: flex;
align-items: center;
justify-content: space-between;

View File

@ -1,4 +1,115 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
import SearchTable from "@/components/SearchTable/SearchTable.vue";
import type { SearchTableColumns } from "@/components/SearchTable/type";
import { getList, save as saveApi, deletApi, resetPassword } from "./api";
import UserForm from "./UserForm.vue";
import { ref } from "vue";
import { Message } from "@arco-design/web-vue";
import { dateFormat } from "@gpt-vue/packages/utils";
import usePopup from "@/composables/usePopup";
import UserPassword from "./UserPassword.vue";
const columns: SearchTableColumns[] = [
{
title: "账号",
dataIndex: "username",
search: {
valueType: "input",
},
},
{
title: "剩余对话次数",
dataIndex: "calls",
},
{
title: "剩余绘图次数",
dataIndex: "img_calls",
},
{
title: "累计消耗tokens",
dataIndex: "total_tokens",
},
{
title: "状态",
dataIndex: "status",
render: ({ record }) => {
return record.status ? "正常" : "停用";
},
},
{
title: "过期时间",
dataIndex: "expired_time",
render: ({ record }) => {
return dateFormat(record.expired_time);
},
},
{
title: "注册时间",
dataIndex: "created_at",
render: ({ record }) => {
return dateFormat(record.created_at);
},
},
{
title: "操作",
slotName: "actions",
},
];
//
const popup = (node, api) => {
const nodeProps = (arg) => {
return {
data: arg[0].record,
};
};
const popupProps = (arg, getExposed) => {
return {
width: 700,
onBeforeOk: async () => {
const exposed = getExposed();
const validateRes = await exposed?.formRef.value.validate();
if (validateRes) {
return false;
}
const res = await api(exposed?.form.value);
if (res.code === 0) {
Message.success("操作成功");
}
arg[0].reload();
return res.code === 0;
},
};
};
return usePopup(node, { nodeProps, popupProps });
};
const editModal = popup(UserForm, saveApi);
const password = popup(UserPassword, resetPassword);
const handleDelete = async ({ id }: { id: string }, reload) => {
const res = await deletApi(id);
if (res.code === 0) {
Message.success("操作成功");
reload();
}
};
</script>
<template>
<div></div>
<SearchTable :request="getList" :columns="columns">
<template #actions="{ record, reload }">
<a-link @click="editModal({ record, reload })">编辑</a-link>
<a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
<a-link>删除</a-link>
</a-popconfirm>
<a-link @click="password({ record, reload })">重置密码</a-link>
</template>
<template #search-extra="{ reload }">
<a-button @click="editModal({ reload })" status="success" size="small"
><icon-plus />新增用户</a-button
>
</template>
</SearchTable>
</template>

View File

@ -0,0 +1,117 @@
<template>
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
<a-form-item
field="username"
label="账号"
:rules="[{ required: true, message: 'name is required' }]"
:validate-trigger="['change', 'input']"
>
<a-input v-model="form.username" placeholder="请输入账号" />
</a-form-item>
<a-form-item
v-if="!props.data.id"
field="password"
label="密码"
:rules="[{ required: true, message: 'password is required' }]"
:validate-trigger="['change', 'input']"
showable
>
<a-input v-model="form.password" placeholder="请输入密码" />
</a-form-item>
<a-form-item
field="calls"
label="对话次数"
:rules="[
{ required: true, message: 'count is required' },
{ type: 'number', message: 'age is max than 200' },
]"
>
<a-input-number v-model="form.calls" placeholder="请输入对话次数" />
</a-form-item>
<a-form-item
field="img_calls"
label="绘图次数"
:rules="[
{ required: true, message: 'count is required' },
{ type: 'number', message: 'age is max than 200' },
]"
>
<a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
</a-form-item>
<a-form-item field="expired_time" label="有效期">
<a-date-picker v-model="form.expired_time" placeholder="请选择有效期" />
</a-form-item>
<a-form-item field="chat_roles" label="聊天角色">
<a-select
:field-names="{ value: 'key', label: 'name' }"
v-model="form.chat_roles"
placeholder="请选择聊天角色"
multiple
:options="roleOption"
>
</a-select>
</a-form-item>
<a-form-item field="chat_models" label="模型角色">
<a-select
:field-names="{ value: 'value', label: 'name' }"
v-model="form.chat_models"
placeholder="请选择模型角色"
multiple
:options="modalOption"
>
</a-select>
</a-form-item>
<a-form-item field="status" label="启用状态">
<a-switch v-model="form.status" />
</a-form-item>
<a-form-item field="vip" label="开通VIP">
<a-switch v-model="form.vip" />
</a-form-item>
</a-form>
</template>
<script setup>
import { ref, defineExpose, defineProps } from "vue";
import { getModel, getRole } from "./api";
const props = defineProps({
data: {},
});
const formRef = ref();
const form = ref({
id: "",
username: "",
password: "",
calls: "",
img_calls: "",
expired_time: "",
chat_roles: [],
chat_models: [],
status: false,
vip: false,
});
if (props.data?.id) {
form.value = Object.assign({}, props.data);
if (form.value.expired_time === 0) {
form.value.expired_time = "";
}
}
//
const modalOption = ref([]);
const roleOption = ref([]);
const getOption = (api, container) => {
api().then(({ code, data }) => {
if (code === 0) {
container.value = data;
}
});
};
getOption(getModel, modalOption);
getOption(getRole, roleOption);
defineExpose({
formRef,
form,
});
</script>

View File

@ -0,0 +1,44 @@
<template>
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
<a-form-item
field="username"
label="账号"
:rules="[{ required: true, message: 'name is required' }]"
:validate-trigger="['change', 'input']"
:disabled="true"
>
<a-input v-model="form.username" placeholder="请输入账号" />
</a-form-item>
<a-form-item
field="password"
label="新密码"
:rules="[{ required: true, message: 'password is required' }]"
:validate-trigger="['change', 'input']"
showable
>
<a-input v-model="form.password" placeholder="请输入密码" />
</a-form-item>
</a-form>
</template>
<script setup>
import { ref, defineExpose, defineProps } from "vue";
import { getModel, getRole } from "./api";
const props = defineProps({
data: {},
});
const formRef = ref();
const form = ref({
id: "",
username: "",
password: "",
});
form.value.id = props.data.id;
form.value.username = props.data.username;
defineExpose({
formRef,
form,
});
</script>

View File

@ -0,0 +1,46 @@
import http from "@/http/config";
export const getList = (params?: Record<string, unknown>) => {
return http({
url: "/api/admin/user/list",
method: "get",
params,
});
};
export const save = (data?: Record<string, unknown>) => {
return http({
url: "/api/admin/user/save",
method: "post",
data,
});
};
export const deletApi = (id: string | number) => {
return http({
url: `/api/admin/user/remove?id=${id}`,
method: "get",
});
};
export const getRole = () => {
return http({
url: `/api/admin/role/list`,
method: "get",
});
};
export const getModel = () => {
return http({
url: `/api/admin/model/list`,
method: "get",
});
};
export const resetPassword = (data) => {
return http({
url: `/api/admin/user/resetPass`,
method: "post",
data,
});
};