User manager page is ready

This commit is contained in:
RockYang 2023-05-04 16:04:36 +08:00
parent 7a67af642d
commit 87b225eb4a
6 changed files with 407 additions and 154 deletions

View File

@ -100,7 +100,7 @@ func (s *Server) BatchAddUserHandle(c *gin.Context) {
}
var users = make([]types.User, 0)
var users = make([]UserVo, 0)
for i := 0; i < data.Number; i++ {
name := utils.RandString(12)
_, err := GetUser(name)
@ -117,7 +117,7 @@ func (s *Server) BatchAddUserHandle(c *gin.Context) {
Status: true}
err = PutUser(user)
if err == nil {
users = append(users, user)
users = append(users, user2vo(user))
}
}
@ -165,7 +165,7 @@ func (s *Server) SetUserHandle(c *gin.Context) {
user.RemainingCalls = int(v.(float64))
}
if v, ok := data["expired_time"]; ok {
user.ExpiredTime = int64(v.(float64))
user.ExpiredTime = utils.Str2stamp(v.(string))
}
if v, ok := data["api_key"]; ok {
user.ApiKey = v.(string)
@ -218,6 +218,19 @@ func (s *Server) RemoveUserHandle(c *gin.Context) {
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Message: types.OkMsg})
}
type UserVo struct {
Name string `json:"name"`
MaxCalls int `json:"max_calls"` // 最多调用次数,如果为 0 则表示不限制
RemainingCalls int `json:"remaining_calls"` // 剩余调用次数
EnableHistory bool `json:"enable_history"` // 是否启用聊天记录
Status bool `json:"status"` // 当前状态
Term int `json:"term" default:"30"` // 会员有效期,单位:天
ActiveTime string `json:"active_time"` // 激活时间
ExpiredTime string `json:"expired_time"` // 到期时间
ApiKey string `json:"api_key"` // OpenAI API KEY
ChatRoles []string `json:"chat_roles"` // 当前用户已订阅的聊天角色 map[role_key] => 0/1
}
// GetUserListHandle 获取用户列表
func (s *Server) GetUserListHandle(c *gin.Context) {
username := c.PostForm("username")
@ -231,5 +244,29 @@ func (s *Server) GetUserListHandle(c *gin.Context) {
return
}
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Message: types.OkMsg, Data: GetUsers()})
users := make([]UserVo, 0)
for _, u := range GetUsers() {
users = append(users, user2vo(u))
}
c.JSON(http.StatusOK, types.BizVo{Code: types.Success, Message: types.OkMsg, Data: users})
}
// 将 User 实体转为 UserVo 实体
func user2vo(user types.User) UserVo {
vo := UserVo{
Name: user.Name,
MaxCalls: user.MaxCalls,
RemainingCalls: user.RemainingCalls,
EnableHistory: user.EnableHistory,
Status: user.Status,
Term: user.Term,
ActiveTime: utils.Stamp2str(user.ActiveTime),
ExpiredTime: utils.Stamp2str(user.ExpiredTime),
ChatRoles: make([]string, 0),
}
for k := range user.ChatRoles {
vo.ChatRoles = append(vo.ChatRoles, k)
}
return vo
}

View File

@ -34,3 +34,21 @@ func ContainsStr(slice []string, item string) bool {
}
return false
}
// Stamp2str 时间戳转字符串
func Stamp2str(timestamp int64) string {
if timestamp == 0 {
return ""
}
return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
}
// Str2stamp 字符串转时间戳
func Str2stamp(str string) int64 {
layout := "2006-01-02 15:04:05"
t, err := time.Parse(layout, str)
if err != nil {
return 0
}
return t.Unix()
}

View File

@ -319,7 +319,6 @@ $borderColor = #4676d0;
.el-main {
--el-main-padding: 0;
overflow: hidden;
margin: 0;
background-image url("~@/assets/img/bg_01.jpeg")

View File

@ -819,6 +819,7 @@ export default defineComponent({
background-color #232425
cursor pointer
}
.text {
margin-left 10px;
font-size 12px;

View File

@ -48,7 +48,7 @@
</el-row>
<el-form-item label="对话上下文">
<el-switch v-model="form['enable_context']" />
<el-switch v-model="form['enable_context']"/>
</el-form-item>
<el-form-item>
@ -65,7 +65,9 @@
>
<template #prepend>
<el-button type="primary">
<el-icon><Plus /></el-icon>
<el-icon>
<Plus/>
</el-icon>
</el-button>
</template>
<template #append>
@ -76,7 +78,7 @@
<el-row>
<el-table :data="apiKeys" style="width: 100%">
<el-table-column prop="value" label="API-KEY" />
<el-table-column prop="value" label="API-KEY"/>
<el-table-column prop="last_used" label="最后使用" width="180">
<template #default="scope">
<span v-if="scope.row['last_used'] > 0">{{ dateFormat(scope.row['last_used']) }}</span>
@ -85,12 +87,22 @@
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button
size="small"
type="danger"
@click="removeApiKey(scope.row.value)"
>删除</el-button
<el-popconfirm
width="220"
confirm-button-text="确定"
cancel-button-text="取消"
title="确定删除该记录吗?"
:hide-after="0"
@confirm="removeApiKey(scope.row.value)"
>
<template #reference>
<el-button
size="small"
type="danger">删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@ -111,7 +123,6 @@ export default defineComponent({
components: {Plus},
data() {
return {
title: "系统管理",
apiKey: '',
form: {},
apiKeys: [],
@ -143,13 +154,12 @@ export default defineComponent({
},
},
methods: {
saveConfig: function (e) {
this.form['temperature'] = parseFloat(this.form['temperature'])
this.form['chat_context_expire_time'] = parseInt(this.form['chat_context_expire_time'])
this.form['max_tokens'] = parseInt(this.form['max_tokens'])
saveConfig: function () {
this.form['temperature'] = parseFloat(this.form.temperature)
this.form['chat_context_expire_time'] = parseInt(this.form.chat_context_expire_time)
this.form['max_tokens'] = parseInt(this.form.max_tokens)
httpPost("/api/admin/config/set", this.form).then(() => {
ElMessage.success("保存成功");
e.currentTarget.blur()
}).catch((e) => {
console.log(e.message);
ElMessage.error("保存失败");
@ -164,21 +174,21 @@ export default defineComponent({
httpPost('api/admin/apikey/add', {api_key: this.apiKey.trim()}).then(() => {
ElMessage.success('添加成功')
this.apiKeys.push({value: this.apiKey, last_used: 0})
this.apiKeys.unshift({value: this.apiKey, last_used: 0})
this.apiKey = ''
}).catch((e) => {
ElMessage.error('添加失败,'+e.message)
ElMessage.error('添加失败,' + e.message)
})
},
removeApiKey:function (key) {
removeApiKey: function (key) {
httpPost('api/admin/apikey/remove', {api_key: key}).then(() => {
ElMessage.success('删除成功')
this.apiKeys = removeArrayItem(this.apiKeys, key, function (v1,v2) {
this.apiKeys = removeArrayItem(this.apiKeys, key, function (v1, v2) {
return v1.value === v2
})
}).catch((e) => {
ElMessage.error('删除失败,'+e.message)
ElMessage.error('删除失败,' + e.message)
})
}
}

View File

@ -1,30 +1,36 @@
<template>
<div class="system-config" v-loading="loading">
<div class="user-list" v-loading="loading">
<el-row class="opt-box">
<el-button type="primary" @click="showUserDialog = true">
<el-icon><Plus /></el-icon>
<el-icon>
<Plus/>
</el-icon>
新增
</el-button>
<el-button type="success">
<el-icon><Plus /></el-icon>
<el-button type="success" @click="showBatchAddUserDialog = true">
<el-icon>
<Plus/>
</el-icon>
批量新增
</el-button>
</el-row>
<el-row>
<el-table :data="users">
<el-table-column prop="name" label="用户名" />
<el-table-column prop="max_calls" label="最大调用次数" />
<el-table-column prop="remaining_calls" label="剩余调用次数" />
<el-table-column label="激活时间">
<el-table-column prop="name" label="用户名"/>
<el-table-column prop="max_calls" label="最大提问次数"/>
<el-table-column prop="remaining_calls" label="剩余提问次数"/>
<el-table-column label="激活时间" width="180">
<template #default="scope">
<el-tag type="info" v-if="scope.row.active_time === 0">未激活</el-tag>
<span v-else>{{dateFormat(scope.row.active_time)}}</span>
<el-tag type="info" v-if="scope.row.active_time === ''">未激活</el-tag>
<span v-else>{{ scope.row.active_time }}</span>
</template>
</el-table-column>
<el-table-column label="过期时间">
<el-table-column label="过期时间" width="180">
<template #default="scope">
<el-tag type="info" v-if="scope.row.active_time === 0">未激活</el-tag>
<span v-else>{{dateFormat(scope.row.expired_time)}}</span>
<el-tag type="info" v-if="scope.row.expired_time === ''">未激活</el-tag>
<span v-else>{{ scope.row.expired_time }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="180">
@ -36,8 +42,20 @@
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" type="primary" @click="removeApiKey(scope.row.value)">编辑</el-button>
<el-button size="small" type="danger" @click="removeApiKey(scope.row.value)">删除</el-button>
<el-button size="small" type="primary" @click="userEdit(scope.row)">编辑</el-button>
<el-popconfirm
width="220"
confirm-button-text="确定"
cancel-button-text="取消"
title="确定删除该记录吗?"
:hide-after="0"
@confirm="removeUser(scope.row)"
>
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@ -46,37 +64,37 @@
<el-dialog
v-model="showUserDialog"
title="新增用户"
width="30%"
width="50%"
:destroy-on-close="true"
>
<el-form :model="user" label-width="100px" ref="userAddFormRef" :rules="rules">
<el-form :model="form1" label-width="100px" ref="userAddFormRef" :rules="rules">
<el-form-item label="用户名:" prop="name">
<el-input
v-model="user.name"
v-model="form1.name"
autocomplete="off"
placeholder="请输入用户名"
/>
</el-form-item>
<el-form-item label="调用次数:">
<el-form-item label="提问次数:" prop="max_calls">
<el-input
v-model="user.max_calls"
v-model.number="form1.max_calls"
autocomplete="off"
placeholder="0 表示不限制调用次数"
placeholder="0 表示不限制提问次数"
/>
</el-form-item>
<el-form-item label="有效期:">
<el-form-item label="有效期:" prop="term">
<el-input
v-model="user.term"
v-model.number="form1.term"
autocomplete="off"
placeholder="单位:天"
/>
</el-form-item>
<el-form-item label="聊天角色">
<el-form-item label="聊天角色" prop="chat_roles">
<el-select
v-model="user.chat_roles"
v-model="form1.chat_roles"
multiple
:filterable="true"
placeholder="选择聊天角色,多选"
@ -93,14 +111,147 @@
</el-form-item>
<el-form-item label="聊天记录">
<el-switch v-model="user.enable_history" />
<el-switch v-model="form1.enable_history"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showUserDialog = false">取消</el-button>
<el-button type="primary" @click="saveUser">提交</el-button>
<el-button type="primary" @click="addUser">提交</el-button>
</span>
</template>
</el-dialog>
<el-dialog
v-model="showBatchAddUserDialog"
title="批量生成用户"
width="50%"
:destroy-on-close="true"
>
<el-form :model="form3" label-width="100px" ref="userEditFormRef" :rules="rules">
<el-form-item label="提问次数:" prop="max_calls">
<el-input
v-model.number="form3.max_calls"
autocomplete="off"
placeholder="最大提问次数"
/>
</el-form-item>
<el-form-item label="用户数量:" prop="number">
<el-input
v-model.number="form3.number"
autocomplete="off"
placeholder="批量生成的用户数量"
/>
</el-form-item>
<el-form-item label="有效期:" prop="term">
<el-input
v-model.number="form3.term"
autocomplete="off"
placeholder="单位:天"
/>
</el-form-item>
<el-form-item label="聊天角色" prop="chat_roles">
<el-select
v-model="form3.chat_roles"
multiple
:filterable="true"
placeholder="选择聊天角色,多选"
@change="selectRole"
>
<el-option
v-for="item in roles"
:key="item.key"
:label="item.name"
:value="item.key"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="聊天记录">
<el-switch v-model="form2.enable_history"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showBatchAddUserDialog = false">取消</el-button>
<el-button type="success" @click="batchAddUser">提交</el-button>
</span>
</template>
</el-dialog>
<el-dialog
v-model="showUserEditDialog"
title="编辑用户"
width="50%"
:destroy-on-close="true"
>
<el-form :model="form2" label-width="100px" ref="userEditFormRef" :rules="rules">
<el-form-item label="用户名:" prop="name">
<el-input
v-model="form2.name"
autocomplete="off"
placeholder="请输入用户名"
readonly
/>
</el-form-item>
<el-form-item label="提问次数:" prop="remaining_calls">
<el-input
v-model.number="form2.remaining_calls"
autocomplete="off"
placeholder="0"
/>
</el-form-item>
<el-form-item label="有效期:" prop="term">
<el-date-picker
v-model="form2.expired_time"
type="datetime"
placeholder="选择时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="聊天角色" prop="chat_roles">
<el-select
v-model="form2.chat_roles"
multiple
:filterable="true"
placeholder="选择聊天角色,多选"
@change="selectRole"
>
<el-option
v-for="item in roles"
:key="item.key"
:label="item.name"
:value="item.key"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="聊天记录">
<el-switch v-model="form2.enable_history"/>
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="form2.status"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showUserEditDialog = false">取消</el-button>
<el-button type="primary" @click="updateUser">提交</el-button>
</span>
</template>
</el-dialog>
@ -108,127 +259,160 @@
</template>
<script setup>
import {onMounted} from "vue";
import {nextTick, onMounted, reactive, ref} from "vue";
import {Plus} from "@element-plus/icons-vue";
import {httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {arrayContains, removeArrayItem} from "@/utils/libs";
//
const users = ref([])
const form1 = ref({chat_roles: []})
const form2 = ref({})
const roles = ref([])
const showUserDialog = ref(false)
const showUserEditDialog = ref(false)
const rules = reactive({
name: [{required: true, message: '请输入用户名', trigger: 'change',}],
max_calls: [
{required: true, message: '请输入提问次数'},
{type: 'number', message: '请输入有效数字'},
],
remaining_calls: [
{required: true, message: '请输入提问次数'},
{type: 'number', message: '请输入有效数字'},
],
term: [
{required: true, message: '请输入有效期', trigger: 'change'},
{type: 'number', message: '请输入有效数字'},
],
number: [
{required: true, message: '请输入用户数量', trigger: 'change'},
{type: 'number', message: '请输入有效数字'},
],
chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
})
const loading = ref(true)
const userAddFormRef = ref(null)
const userEditFormRef = ref(null)
const userAddFormRef = ref(null);
onMounted(() => {
alert('xxxx')
//
httpPost('/api/admin/user/list').then((res) => {
users.value = res.data;
}).catch(() => {
ElMessage.error('获取系统配置失败')
})
//
httpPost("/api/admin/chat-roles/get").then((res) => {
roles.value = res.data;
roles.value.unshift({name: '全部', key: 'all'})
}).catch(() => {
ElMessage.error("获取聊天角色失败");
})
nextTick(() => {
loading.value = false
})
})
const saveUser = ()=> {
//
const addUser = () => {
userAddFormRef.value.validate((valid) => {
if (valid) {
this.showUserDialog = false
this.user.term = parseInt(this.user.term)
this.user.max_calls = parseInt(this.user.max_calls)
httpPost('/api/admin/user/add', this.user).then((res) => {
ElMessage.success('添加用户成功')
this.user = {}
this.users.push(res.data)
}).catch((e) => {
ElMessage.error('添加用户失败,'+e.message)
})
showUserDialog.value = false
form1.value.term = parseInt(form1.value.term)
form1.value.max_calls = parseInt(form1.value.max_calls)
httpPost('/api/admin/user/add', form1.value).then((res) => {
ElMessage.success('添加用户成功')
form1.value = {chat_roles: []}
users.value.unshift(res.data)
}).catch((e) => {
ElMessage.error('添加用户失败,' + e.message)
})
} else {
ElMessage.error('error submit !')
return false
}
})
}
</script>
<script>
import {defineComponent, nextTick, reactive, ref} from "vue";
import {Plus} from "@element-plus/icons-vue";
import {httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {arrayContains, dateFormat} from "@/utils/libs";
export default defineComponent({
name: 'UserList',
components: {Plus},
data() {
return {
title: "用户管理",
users: [],
user: {
term: 30,
enable_history: true
},
roles: [],
showUserDialog: false,
rules: reactive({
name: [
{
required: true,
message: 'Please select Activity zone',
trigger: 'change',
},
]
}),
loading: true
}
},
mounted() {
//
httpPost('/api/admin/user/list').then((res) => {
this.users = res.data;
}).catch(() => {
ElMessage.error('获取系统配置失败')
})
//
httpPost("/api/admin/chat-roles/get").then((res) => {
this.roles = res.data;
this.roles.unshift({name:'全部',key:'all'})
}).catch(() => {
ElMessage.error("获取聊天角色失败");
})
nextTick(() => {
this.loading = false
})
},
computed: {
dateFormat() {
return dateFormat
},
},
methods: {
selectRole: function (items) {
if (arrayContains(items, 'all')) {
for (let i =0;i < this.roles.length;i++) {
if (this.roles[i].key === 'all') {
continue
}
this.roles[i].disabled = true
this.user.chat_roles = ['all']
}
} else {
for (let i =0;i < this.roles.length;i++) {
if (this.roles[i].key === 'all') {
continue
}
this.roles[i].disabled = false
}
//
const selectRole = function (items) {
if (arrayContains(items, 'all')) {
for (let i = 0; i < roles.value.length; i++) {
if (roles.value[i].key === 'all') {
continue
}
},
removeUser: function (user) {
console.log(user)
},
updateUser:function (user) {
console.log(user)
roles.value[i].disabled = true
form1.value.chat_roles = ['all']
form2.value.chat_roles = ['all']
form3.value.chat_roles = ['all']
}
} else {
for (let i = 0; i < roles.value.length; i++) {
if (roles.value[i].key === 'all') {
continue
}
roles.value[i].disabled = false
}
}
})
}
//
const removeUser = function (user) {
httpPost('/api/admin/user/remove', {name: user.name}).then(() => {
ElMessage.success('删除用户成功')
users.value = removeArrayItem(users.value, user, function (v1, v2) {
return v1.name === v2.name
})
}).catch((e) => {
ElMessage.error('删除用户失败,' + e.message)
})
}
const userEdit = function (user) {
form2.value = user
showUserEditDialog.value = true
}
//
const updateUser = function () {
userEditFormRef.value.validate((valid) => {
if (valid) {
showUserEditDialog.value = false
form2.value.term = parseInt(form2.value.term)
form2.value.remaining_calls = parseInt(form2.value.remaining_calls)
httpPost('/api/admin/user/set', form2.value).then(() => {
ElMessage.success('更新用户成功')
}).catch((e) => {
ElMessage.error('更新用户失败,' + e.message)
})
} else {
return false
}
})
}
//
const showBatchAddUserDialog = ref(false)
const form3 = ref({chat_roles: []})
const batchAddUser = function () {
httpPost('api/admin/user/batch-add', form3.value).then((res) => {
console.log(res.data)
ElMessage.success('添加用户成功')
users.value = [...res.data, ...users.value]
showBatchAddUserDialog.value = false
}).catch((e) => {
console.log('添加用户失败,' + e.message)
})
}
</script>
<style lang="stylus" scoped>
.system-config {
.user-list {
.opt-box {
padding-bottom: 10px;
@ -238,5 +422,9 @@ export default defineComponent({
}
}
.el-select {
width 100%
}
}
</style>