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++ { for i := 0; i < data.Number; i++ {
name := utils.RandString(12) name := utils.RandString(12)
_, err := GetUser(name) _, err := GetUser(name)
@ -117,7 +117,7 @@ func (s *Server) BatchAddUserHandle(c *gin.Context) {
Status: true} Status: true}
err = PutUser(user) err = PutUser(user)
if err == nil { 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)) user.RemainingCalls = int(v.(float64))
} }
if v, ok := data["expired_time"]; ok { 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 { if v, ok := data["api_key"]; ok {
user.ApiKey = v.(string) 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}) 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 获取用户列表 // GetUserListHandle 获取用户列表
func (s *Server) GetUserListHandle(c *gin.Context) { func (s *Server) GetUserListHandle(c *gin.Context) {
username := c.PostForm("username") username := c.PostForm("username")
@ -231,5 +244,29 @@ func (s *Server) GetUserListHandle(c *gin.Context) {
return 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 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 {
--el-main-padding: 0; --el-main-padding: 0;
overflow: hidden;
margin: 0; margin: 0;
background-image url("~@/assets/img/bg_01.jpeg") background-image url("~@/assets/img/bg_01.jpeg")

View File

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

View File

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

View File

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