Merge pull request 'add(用户模块):完善修改用户信息功能和删除用户功能' (#2) from dev into main

Reviewed-on: #2
This commit is contained in:
西街长安 2025-07-23 15:30:10 +08:00
commit ac354c4e5d
8 changed files with 381 additions and 33 deletions

View File

@ -0,0 +1,79 @@
<template>
<transition name="modal-fade">
<div v-if="visible" class="modal" tabindex="-1" role="dialog" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" @click="cancel"></button>
</div>
<div class="modal-body">{{ message }}</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="cancel">关闭</button>
<button type="button" class="btn btn-primary" @click="commit">{{ btnTxt }}</button>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name:'Confirm',
props: {
title: {
type:String
},
message:{
type:String
},
btnTxt: {
type:String,
default:'确认'
}
},
data(){
return {
visible: false,
resolve: null
}
},
methods: {
open(){
this.visible = true
return new Promise((resolve) => {
this.resolve = resolve
})
},
commit()
{
this.visible = false
this.resolve && this.resolve(true)
},
cancel(){
this.visible = false
this.resolve && this.resolve(false)
}
}
}
</script>
<style scoped>
.modal {
background: rgba(0, 0, 0, 0.5);;
}
/* 进入前 */
.modal-fade-enter-active, .modal-fade-leave-active {
transition: opacity 0.3s ease;
}
.modal-fade-enter, .modal-fade-leave-to {
opacity: 0;
}
</style>

View File

@ -8,17 +8,17 @@
<div id="zero-conf_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="dataTables_length" id="zero-conf_length"><label>Show <select name="zero-conf_length"
<div class="dataTables_length" id="zero-conf_length"><label>展示 <select name="zero-conf_length"
aria-controls="zero-conf" class="custom-select custom-select-sm form-control form-control-sm"
v-model="pageSize">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select> entries</label></div>
</select>条数据</label></div>
</div>
<div class="col-sm-12 col-md-6">
<div id="zero-conf_filter" class="dataTables_filter"><label>Search:<input type="search"
<div id="zero-conf_filter" class="dataTables_filter"><label>搜索:<input type="search"
class="form-control form-control-sm" placeholder="" aria-controls="zero-conf"></label></div>
</div>
</div>
@ -37,7 +37,28 @@
<tr role="row" v-for="(item, rowIndex) in rows" :key="rowIndex"
:class="rowIndex % 2 ? 'even' : 'odd'">
<td v-for="(header, colIndex) in headers" :key="colIndex">
{{ item[header.value] }}
<div v-if="header.type == null">
<span>{{ item[header.value] }}</span>
</div>
<div v-if="header.type == 'bool'">
<span :class="['badge',item[header.value] ? 'bg-danger' : 'bg-success']">{{ item[header.value] ? '禁用' : '正常'}}</span>
</div>
<div v-if="header.type == 'arrObj'">
<span class="badge bg-info" v-for="(objItem,objIndex) in item[header.value]" :key="objIndex">
{{ objItem[header.child] }}
</span>
</div>
<div v-if="header.type == 'money'">
<span>{{ item[header.value].toFixed(2) }}</span>
</div>
<div v-if="header.type == 'datetime'">
<span>{{ formatDateTime(item[header.value]) }}</span>
</div>
</td>
<td>
<button class="btn btn-outline-secondary m-b-xs" @click="modify(item.id)">修改</button>
<button class="btn btn-outline-danger m-b-xs" @click="deleteData(item.id)">删除</button>
</td>
</tr>
</tbody>
@ -51,8 +72,7 @@
</div>
<div class="row">
<div class="col-sm-12 col-md-5">
<div class="dataTables_info" id="zero-conf_info" role="status" aria-live="polite">Showing 1 to 10 of 57
entries</div>
<div class="dataTables_info" id="zero-conf_info" role="status" aria-live="polite">正在展示 {{ (currentPageIndex * pageSize) - (pageSize - 1) }} {{ currentPageIndex * pageSize }} 条数据 总计 {{dataCount}} </div>
</div>
<div class="col-sm-12 col-md-7">
<div class="dataTables_paginate paging_simple_numbers" id="zero-conf_paginate">
@ -163,6 +183,29 @@ export default {
}
//
this.pageBtn.push({text: `${this.pageCount}`, index: this.pageCount})
},
formatDateTime(str){
if (!str) return '-';
const date = new Date(str);
if (isNaN(date)) return '-'; // Safari Invalid Date
const pad = n => n.toString().padStart(2, '0');
const Y = date.getFullYear();
const M = pad(date.getMonth() + 1);
const D = pad(date.getDate());
const h = pad(date.getHours());
const m = pad(date.getMinutes());
const s = pad(date.getSeconds());
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
},
modify(id){
this.$emit('dataModify',id)
},
deleteData(id){
this.$emit('dataDelete',id)
}
},
watch: {

View File

@ -0,0 +1,111 @@
<template>
<div v-if="visible" class="modal fade show" id="exampleModalCenter" tabindex="-1" aria-labelledby="exampleModalLabel" style="display: block; padding-right: 15px;" _mstvisible="0" aria-modal="true" role="dialog">
<div class="modal-dialog modal-dialog-centered" _mstvisible="1">
<div class="modal-content" _mstvisible="2">
<div class="modal-header" _mstvisible="3">
<h5 class="modal-title" id="exampleModalCenterTitle" _msttexthash="13222534" _msthash="147" _mstvisible="4">{{title}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="关闭" _mstaria-label="59709" _msthash="148" _mstvisible="4" @click="cancel"></button>
</div>
<div class="formcontent">
<form>
<div class="row mb-3">
<label for="inputEmail3" class="col-sm-2 col-form-label">邮箱</label>
<div class="col-sm-10">
<input type="email" v-model="form.email" class="form-control" id="inputEmail3" :disabled="formType == 'edit' ? true : false">
</div>
</div>
<div class="row mb-3">
<label for="inputUsername3" class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
<input type="text" v-model="form.userName" class="form-control" id="inputUsername3" :disabled="formType == 'edit' ? true : false">
</div>
</div>
<div class="row mb-3">
<label for="inputPassword3" class="col-sm-2 col-form-label">新密码</label>
<div class="col-sm-10">
<input type="password" v-model="form.password" class="form-control" id="inputPassword3">
</div>
</div>
<div class="row mb-3">
<label for="inputBalance3" class="col-sm-2 col-form-label">余额</label>
<div class="col-sm-10">
<input type="text" v-model="form.balance" class="form-control" id="inputBalance3" v-money>
</div>
</div>
</form>
</div>
<div class="modal-footer" _mstvisible="3">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" _msttexthash="5889065" _msthash="150" _mstvisible="4" @click="cancel">关闭</button>
<button type="button" class="btn btn-primary" _msttexthash="10744773" _msthash="151" _mstvisible="4" @click="submit">保存更改</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'UserFormModal',
props:{
title:{
type:String
},
formType: {
type: String,
default: 'edit'
}
},
data() {
return {
visible: false,
userId: -2,
form: {
userName: '',
email: '',
balance: '',
password: ''
},
resolve: null
};
},
methods: {
open(userId,userInfo = null) {
this.resetForm();
this.visible = true;
this.userId = userId;
this.form.email = userInfo.email;
this.form.userName = userInfo.userName;
this.form.balance = userInfo.balance;
return new Promise((resolve) => {
this.resolve = resolve;
});
},
submit() {
this.visible = false;
this.resolve && this.resolve(this.form);
},
cancel() {
this.visible = false;
this.resolve && this.resolve(null);
},
resetForm() {
this.form = {
userName: '',
email: '',
balance: '',
password: ''
};
}
}
};
</script>
<style scoped>
.modal {
background: rgba(0, 0, 0, 0.5);
}
.formcontent {
padding: 0 30px;
}
</style>

28
src/directives/money.js Normal file
View File

@ -0,0 +1,28 @@
export default {
bind(el) {
const handler = function (e) {
let val = e.target.value;
// 只允许输入数字和一个小数点
val = val
.replace(/[^\d.]/g, '') // 移除非数字和点
.replace(/^\./g, '') // 不能以点开头
.replace(/\.{2,}/g, '.') // 多个点只留一个
.replace('.', '#') // 临时标记第一个点
.replace(/\./g, '') // 移除其他点
.replace('#', '.') // 还原第一个点
.replace(/^0+(\d)/, '$1') // 去掉前导 0
.replace(/^(\d+\.\d{0,2}).*/, '$1') // 最多两位小数
e.target.value = val
el.dispatchEvent(new Event('input')) // 手动触发 v-model 更新
}
el.__moneyInputHandler__ = handler
el.addEventListener('input', handler)
},
unbind(el) {
el.removeEventListener('input', el.__moneyInputHandler__)
delete el.__moneyInputHandler__
}
}

View File

@ -4,6 +4,8 @@ import router from './router'
import store from './store'
import api from './request/api'
import Alert from '@/plugins/alert'
import Confirm from '@/plugins/confirm'
import MoneyDirective from '@/directives/money'
import 'jquery'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
import 'bootstrap/dist/css/bootstrap.min.css'
@ -18,6 +20,9 @@ Vue.config.productionTip = false
//挂载后端请求函数到全局
Vue.prototype.$api = api
Vue.use(Alert)
Vue.use(Confirm)
Vue.directive('money',MoneyDirective)
/**
* 初始化系统

28
src/plugins/confirm.js Normal file
View File

@ -0,0 +1,28 @@
import Confirm from "@/components/Confirm.vue"
import Vue from "vue"
const ConfirmConstructor = Vue.extend(Confirm)
function showConfirm(title,message,btnTxt){
const instance = new ConfirmConstructor({
propsData:{title,message,btnTxt}
})
const vm = instance.$mount()
document.body.appendChild(vm.$el)
return instance.open().then(result => {
cleanup()
return result
})
function cleanup() {
document.body.removeChild(vm.$el)
instance.$destroy()
}
}
export default {
install(){
Vue.prototype.$confirm = showConfirm
}
}

View File

@ -20,6 +20,14 @@ const getUserList = async (pageIndex,pageSize,desc) => await request.get(`/api/A
//获取用户数量
const getUserCount = async () => await request.get('/api/Admin/UserCount')
//删除用户
const deleteUser = async (id) => await request.delete(`/api/Admin/DeleteUser?userId=${id}`)
//获取指定用户信息
const getUserInfoById = async (id) => await request.get(`/api/Admin/UserInfo?userId=${id}`)
//更新用户信息
const updateUserInfo = async (id,param) => await request.post(`/api/Admin/UpdateUser?userId=${id}`,param)
export default {
login,
register,
@ -27,5 +35,8 @@ export default {
getAllConfig,
getUserInfo,
getUserList,
getUserCount
getUserCount,
deleteUser,
getUserInfoById,
updateUserInfo
}

View File

@ -3,7 +3,9 @@
<div class="row">
<div class="col" v-if="isLoaded">
<DataTable :title="'用户管理'" :headers="tableHeaders" :rows="tableData" :data-count="dataCount" @pageChanged="pageChangedHandle"/>
<DataTable title="用户管理" :headers="tableHeaders" :rows="tableData" :data-count="dataCount"
@pageChanged="pageChangedHandle" @dataDelete="deleteHandle" @dataModify="modifyHandle" />
<user-form-modal title="修改用户" ref="userModal" />
</div>
</div>
</div>
@ -11,9 +13,13 @@
<script>
import DataTable from '@/components/DataTable.vue';
import UserFormModal from '../../components/UserFormModal.vue';
export default {
components: { DataTable },
components: {
DataTable,
UserFormModal
},
data() {
return {
dataCount: 0,
@ -22,10 +28,10 @@ export default {
{ text: "编号", value: "id", width: "155px" },
{ text: "用户名", value: "userName", width: "214px" },
{ text: "邮箱", value: "email", width: "48px" },
{ text: "余额", value: "balance", width: "29px" },
{ text: "状态", value: "isBan", width: "82px",type:"state"},
{ text: "角色", value: "roles", width: "103px" },
{ text: "创建时间", value: "created", width: "103px" },
{ text: "余额", value: "balance", width: "29px", type: "money" },
{ text: "状态", value: "isBan", width: "82px", type: "bool" },
{ text: "角色", value: "roles", width: "103px", type: "arrObj", child: "role" },
{ text: "创建时间", value: "created", width: "103px", type: "datetime" }
],
tableData: [],
};
@ -43,8 +49,45 @@ export default {
}
},
async pageChangedHandle(newval) {
console.log(newval)
await this.loadUserList(newval.pageIndex, newval.pageSize, false)
},
async deleteHandle(id) {
const confirmRes = await this.$confirm('删除', '是否删除用户?')
if (!confirmRes) return;
try {
const res = await this.$api.deleteUser(id)
if (res.code != 1000) {
this.$alert(res.message, 'danger')
return;
}
this.$alert('删除成功', 'success')
} catch (e) {
this.$alert(e, 'danger')
}
},
async modifyHandle(id) {
try {
//
const userinfoRes = await this.$api.getUserInfoById(id)
if(userinfoRes.code != 1000){
this.$alert(userinfoRes.message,'danger')
return
}
//
const res = await this.$refs.userModal.open(id,userinfoRes.data)
//null
if(res == null) return
const modifyRes = await this.$api.updateUserInfo(id,res)
if(modifyRes.code != 1000){
this.$alert(modifyRes.message, 'danger')
return
}
this.$alert('修改成功', 'success')
} catch (e) {
this.$alert(e, 'danger')
}
}
},
async mounted() {