This commit is contained in:
MeSHard
2025-11-20 18:08:12 +08:00
parent 26ecddea1e
commit 186a92e834
2 changed files with 444 additions and 473 deletions

View File

@@ -48,47 +48,43 @@
ruleInline: {}, ruleInline: {},
columns: [{ columns: [{
title: '充电站名称', title: '充电站名称',
key: 'id', key: 'charge_station_name',
}, },
{ {
title: '桩类型', title: '桩类型',
key: 'out_trade_no', key: 'type',
}, },
{ {
title: '桩数量', title: '桩数量',
key: 'openid', key: 'pile_num_2',
}, },
{ {
title: '枪数量', title: '枪数量',
key: 'total', key: 'pile_num',
}, },
{ {
title: '充电次数', title: '充电次数(次)',
key: 'total_used', key: 'order_num',
}, },
{ {
title: '充电电量(度)', title: '充电电量(度)',
key: 'trade_state', key: 'power',
}, },
{ {
title: '充电金额(元)', title: '充电金额(元)',
key: 'success_time', key: 'money',
}, },
{ {
title: '电费', title: '电费(元)',
key: 'success_time', key: 'elec',
}, },
{ {
title: '服务费', title: '服务费(元)',
key: 'success_time', key: 'sevice',
}, },
{ {
title: '总时长', title: '总时长(小时)',
key: 'success_time', key: 'length',
},
{
title: '尖峰平谷电量',
key: 'success_time',
}, },
{ {
title: '操作', title: '操作',
@@ -121,9 +117,8 @@
page: this.page, page: this.page,
pageSize: this.pageSize, pageSize: this.pageSize,
}).then((res) => { }).then((res) => {
this.total = res.total this.total = res.data.total
this.data = res.data this.data = res.data.data
this.page = res.current_page
}) })
}, },
handleSubmit(name) { handleSubmit(name) {

View File

@@ -1,466 +1,442 @@
<template> <template>
<div class="container"> <div class="container">
<div class="search-area"> <div class="search-area">
<Form ref="searchForm" inline :label-width="100" :model="formInline"> <Form ref="searchForm" inline :label-width="100" :model="formInline">
<FormItem prop="username" label="用户名:"> <FormItem prop="username" label="用户名:">
<Input <Input v-model="formInline.username" clearable placeholder="请输入用户名" @on-enter="handleSubmit" />
v-model="formInline.username" </FormItem>
clearable <FormItem>
placeholder="请输入用户名" <Button type="primary" @click="handleSubmit">搜索</Button>
@on-enter="handleSubmit" <Button style="margin-left: 8px" @click="resetSearch">重置</Button>
/> </FormItem>
</FormItem> </Form>
<FormItem> </div>
<Button type="primary" @click="handleSubmit">搜索</Button> <div class="action-btn">
<Button style="margin-left: 8px" @click="resetSearch">重置</Button> <Button type="primary" @click="handleAdd">添加用户</Button>
</FormItem> </div>
</Form> <div class="table-container">
</div> <Table border stripe :loading="tableLoading" :columns="columns" :height="tableHeight" :data="data">
<div class="action-btn"> <template #roles="{ row }">
<Button type="primary" @click="handleAdd">添加用户</Button> <span>{{ formatRoleLabel(row.roles) }}</span>
</div> </template>
<div class="table-container"> <template #action="{ row }">
<Table <Button type="primary" size="small" style="margin-right: 5px"
border @click="handleEdit(row.id)">编辑</Button>
stripe <Button type="error" size="small" @click="handleRemove(row.id)">删除</Button>
:loading="tableLoading" </template>
:columns="columns" </Table>
:height="tableHeight"
:data="data"
>
<template #roles="{ row }">
<span>{{ formatRoleLabel(row.roles) }}</span>
</template>
<template #action="{ row }">
<Button type="primary" size="small" style="margin-right: 5px" @click="handleEdit(row.id)">编辑</Button>
<Button type="error" size="small" @click="handleRemove(row.id)">删除</Button>
</template>
</Table>
<Page
:current="page"
:page-size="pageSize"
:total="total"
show-total
show-sizer
show-elevator
class="page"
@on-change="onChangePage"
@on-page-size-change="onChangePageSize"
/>
</div>
<Modal <Page :total="total" show-total show-sizer class="page" @on-change="onChangePage"
v-model="showModal" @on-page-size-change="onChangePageSize" />
:title="modalTitle" </div>
:mask-closable="false"
@on-cancel="cancel" <Modal v-model="showModal" :title="modalTitle" :mask-closable="false" @on-cancel="cancel">
> <Form ref="formValidate" :model="formValidate" :rules="formRules" :label-width="100">
<Form ref="formValidate" :model="formValidate" :rules="formRules" :label-width="100"> <FormItem label="用户名" prop="username" :required="true">
<FormItem label="用户名" prop="username" :required="true"> <Input v-model="formValidate.username" placeholder="请输入用户名" />
<Input v-model="formValidate.username" placeholder="请输入用户名" /> </FormItem>
</FormItem> <FormItem label="姓名" prop="nickname" :required="true">
<FormItem label="姓名" prop="nickname" :required="true"> <Input v-model="formValidate.nickname" placeholder="请输入姓名" />
<Input v-model="formValidate.nickname" placeholder="请输入姓名" /> </FormItem>
</FormItem> <FormItem label="登录密码" prop="password" :required="!isEdit">
<FormItem label="登录密码" prop="password" :required="!isEdit"> <Input type="password" password v-model="formValidate.password"
<Input :placeholder="isEdit ? '不修改可留空' : '请输入登录密码'" />
type="password" </FormItem>
password <FormItem label="手机号" prop="phone" :required="true">
v-model="formValidate.password" <Input v-model="formValidate.phone" placeholder="请输入11位手机号" />
:placeholder="isEdit ? '不修改可留空' : '请输入登录密码'" </FormItem>
/> <FormItem label="邮箱" prop="email">
</FormItem> <Input v-model="formValidate.email" placeholder="选填,需符合邮箱格式" />
<FormItem label="手机号" prop="phone" :required="true"> </FormItem>
<Input v-model="formValidate.phone" placeholder="请输入11位手机号" /> <FormItem label="角色" prop="roles" :required="true">
</FormItem> <Select v-model="formValidate.roles" placeholder="请选择角色">
<FormItem label="邮箱" prop="email"> <Option v-for="item in roleOptions" :value="item.id" :key="item.id">{{ item.name }}</Option>
<Input v-model="formValidate.email" placeholder="选填,需符合邮箱格式" /> </Select>
</FormItem> </FormItem>
<FormItem label="角色" prop="roles" :required="true"> </Form>
<Select v-model="formValidate.roles" placeholder="请选择角色"> <div slot="footer">
<Option v-for="item in roleOptions" :value="item.id" :key="item.id">{{ item.name }}</Option> <Button type="text" @click="cancel">取消</Button>
</Select> <Button type="primary" :loading="submitLoading" @click="ok">确定</Button>
</FormItem> </div>
</Form> </Modal>
<div slot="footer"> </div>
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" :loading="submitLoading" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template> </template>
<script> <script>
import { import {
getAdminList, getAdminList,
addAdmin, addAdmin,
getAdmin, getAdmin,
getAdminUpdate, getAdminUpdate,
getAdminDelete, getAdminDelete,
getRoleList, getRoleList,
} from '@/api' } from '@/api'
const createDefaultForm = () => ({ const createDefaultForm = () => ({
id: '', id: '',
username: '', username: '',
nickname: '', nickname: '',
password: '', password: '',
email: '', email: '',
phone: '', phone: '',
roles: '', roles: '',
}) })
export default { export default {
name: 'system_admin', name: 'system_admin',
data() { data() {
return { return {
showModal: false, showModal: false,
tableHeight: 500, tableHeight: 500,
tableLoading: false, tableLoading: false,
submitLoading: false, submitLoading: false,
page: 1, page: 1,
pageSize: 10, pageSize: 10,
total: 0, total: 0,
formInline: { formInline: {
username: '', username: '',
}, },
// 表格列的结构描述,保持字段展示的一致性 // 表格列的结构描述,保持字段展示的一致性
columns: [ columns: [{
{ title: 'ID',
title: 'ID', key: 'id',
key: 'id', minWidth: 80,
minWidth: 80, },
}, {
{ title: '用户名',
title: '用户名', key: 'username',
key: 'username', minWidth: 140,
minWidth: 140, },
}, {
{ title: '姓名',
title: '姓名', key: 'nickname',
key: 'nickname', minWidth: 120,
minWidth: 120, },
}, {
{ title: '手机号',
title: '手机号', key: 'phone',
key: 'phone', minWidth: 140,
minWidth: 140, },
}, {
{ title: '邮箱',
title: '邮箱', key: 'email',
key: 'email', minWidth: 180,
minWidth: 180, },
}, {
{ title: '角色',
title: '角色', slot: 'roles',
slot: 'roles', minWidth: 120,
minWidth: 120, },
}, {
{ title: '操作',
title: '操作', slot: 'action',
slot: 'action', width: 150,
width: 150, align: 'center',
align: 'center', },
}, ],
], data: [], // 系统用户表格数据
data: [], // 系统用户表格数据 roleOptions: [], // 角色下拉选项
roleOptions: [], // 角色下拉选项 formValidate: createDefaultForm(), // 弹窗表单数据
formValidate: createDefaultForm(), // 弹窗表单数据 }
} },
}, computed: {
computed: { isEdit() {
isEdit() { return Boolean(this.formValidate.id)
return Boolean(this.formValidate.id) },
}, modalTitle() {
modalTitle() { return this.isEdit ? '编辑系统用户' : '新增系统用户'
return this.isEdit ? '编辑系统用户' : '新增系统用户' },
}, formRules() {
formRules() { return {
return { username: [{
username: [ required: true,
{ required: true, message: '请输入用户名', trigger: 'blur' }, message: '请输入用户名',
], trigger: 'blur',
nickname: [ }],
{ required: true, message: '请输入姓名', trigger: 'blur' }, nickname: [{
], required: true,
password: [ message: '请输入姓名',
{ validator: this.validatePassword, trigger: 'blur' }, trigger: 'blur',
], }],
phone: [ password: [{
{ validator: this.validatePhone, trigger: 'blur' }, validator: this.validatePassword,
], trigger: 'blur',
email: [ }],
{ validator: this.validateEmail, trigger: 'blur' }, phone: [{
], validator: this.validatePhone,
roles: [ trigger: 'blur',
{ validator: this.validateRoles, trigger: 'change' }, }],
], email: [{
} validator: this.validateEmail,
}, trigger: 'blur',
}, }],
mounted() { roles: [{
this.getList() validator: this.validateRoles,
this.fetchRoles() trigger: 'change',
this.calculateTableHeight() }],
window.addEventListener('resize', this.calculateTableHeight) }
}, },
beforeDestroy() { },
window.removeEventListener('resize', this.calculateTableHeight) mounted() {
}, this.getList()
methods: { this.fetchRoles()
// 统一创建弹窗表单的初始值,方便新增/编辑时复用 this.calculateTableHeight()
getDefaultForm() { window.addEventListener('resize', this.calculateTableHeight)
return createDefaultForm() },
}, beforeDestroy() {
calculateTableHeight() { window.removeEventListener('resize', this.calculateTableHeight)
// 根据浏览器高度动态计算表格剩余空间,保证页面结构自适应 },
const searchArea = document.querySelector('.search-area') methods: {
const actionArea = document.querySelector('.action-btn') // 统一创建弹窗表单的初始值,方便新增/编辑时复用
const searchHeight = searchArea ? searchArea.offsetHeight : 0 getDefaultForm() {
const actionHeight = actionArea ? actionArea.offsetHeight : 0 return createDefaultForm()
const pageHeight = 70 },
const basePadding = 80 calculateTableHeight() {
const height = window.innerHeight - searchHeight - actionHeight - pageHeight - basePadding const searchHeight = document.querySelector('.search-area').offsetHeight
this.tableHeight = height > 320 ? height : 320 const pageHeight = 32 // 分页组件大约高度
}, const margins = 40 // 上下边距总和
async fetchRoles() { this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 230
// 角色列表仅需偶尔变动,进入页面时加载一次即可 console.log(this.tableHeight)
try { },
const res = await getRoleList() async fetchRoles() {
const list = res.data || res // 角色列表仅需偶尔变动,进入页面时加载一次即可
this.roleOptions = Array.isArray(list) ? list : [] try {
} catch (error) { const res = await getRoleList()
this.roleOptions = [] const list = res.data || res
} this.roleOptions = Array.isArray(list) ? list : []
}, } catch (error) {
async getList() { this.roleOptions = []
// 列表查询会根据分页与搜索条件拼接参数,接口结果兼容 data/list 两种结构 }
this.tableLoading = true },
try { async getList() {
const res = await getAdminList({ this.tableLoading = true
page: this.page, try {
pageSize: this.pageSize, const res = await getAdminList({
...this.formInline, page: this.page,
}) pageSize: this.pageSize,
const responseData = res.data || {} ...this.formInline,
const list = responseData.data || responseData.list || [] })
this.data = Array.isArray(list) ? list : [] this.data = res.data.data
this.total = typeof responseData.total === 'number' ? responseData.total : (res.total || 0) this.total = res.data.total
} finally { } finally {
this.tableLoading = false this.tableLoading = false
} }
}, },
handleSubmit() { handleSubmit() {
this.page = 1 this.page = 1
this.getList() this.getList()
}, },
resetSearch() { resetSearch() {
this.formInline.username = '' this.formInline.username = ''
this.handleSubmit() this.handleSubmit()
}, },
handleAdd() { handleAdd() {
this.formValidate = this.getDefaultForm() this.formValidate = this.getDefaultForm()
this.showModal = true this.showModal = true
}, },
async handleEdit(id) { async handleEdit(id) {
// 编辑前需要获取详情,避免列表中数据字段不完整 // 编辑前需要获取详情,避免列表中数据字段不完整
try { try {
const res = await getAdmin({ id }) const res = await getAdmin({
const detail = res.data || {} id,
this.formValidate = { })
id: detail.id, const detail = res.data || {}
username: detail.username || '', this.formValidate = {
nickname: detail.nickname || '', id: detail.id,
password: detail.password || '', username: detail.username || '',
email: detail.email || '', nickname: detail.nickname || '',
phone: detail.phone || '', password: detail.password || '',
roles: detail.roles === 0 || detail.roles ? Number(detail.roles) : '', email: detail.email || '',
} phone: detail.phone || '',
this.showModal = true roles: detail.roles === 0 || detail.roles ? Number(detail.roles) : '',
} catch (error) { }
this.$Message.error('获取用户信息失败') this.showModal = true
} } catch (error) {
}, this.$Message.error('获取用户信息失败')
async handleRemove(id) { }
// 删除需二次确认,避免误操作 },
const confirm = await new Promise(resolve => { async handleRemove(id) {
this.$Modal.confirm({ // 删除需二次确认,避免误操作
title: '删除确认', const confirm = await new Promise(resolve => {
content: '删除后不可恢复,是否继续?', this.$Modal.confirm({
onOk: () => resolve(true), title: '删除确认',
onCancel: () => resolve(false), content: '删除后不可恢复,是否继续?',
}) onOk: () => resolve(true),
}) onCancel: () => resolve(false),
if (!confirm) { })
return })
} if (!confirm) {
try { return
await getAdminDelete({ id }) }
this.$Message.success('删除成功') try {
if (this.data.length === 1 && this.page > 1) { await getAdminDelete({
this.page -= 1 id,
} })
this.getList() this.$Message.success('删除成功')
} catch (error) { if (this.data.length === 1 && this.page > 1) {
this.$Message.error('删除失败,请稍后重试') this.page -= 1
} }
}, this.getList()
validatePhone(rule, value, callback) { } catch (error) {
// 手机号验证:要求以 1 开头且总长 11 位,避免明显输入错误 this.$Message.error('删除失败,请稍后重试')
const phonePattern = /^1[3-9]\d{9}$/ }
if (!value) { },
callback(new Error('请输入手机号')) validatePhone(rule, value, callback) {
return // 手机号验证:要求以 1 开头且总长 11 位,避免明显输入错误
} const phonePattern = /^1[3-9]\d{9}$/
if (!phonePattern.test(value)) { if (!value) {
callback(new Error('手机号格式不正确')) callback(new Error('请输入手机号'))
return return
} }
callback() if (!phonePattern.test(value)) {
}, callback(new Error('手机号格式不正确'))
validateEmail(rule, value, callback) { return
// 邮箱验证:允许省略,填写时需符合基础格式,连字符放在末尾避免正则范围冲突 }
if (!value) { callback()
callback() },
return validateEmail(rule, value, callback) {
} // 邮箱验证:允许省略,填写时需符合基础格式,连字符放在末尾避免正则范围冲突
const emailPattern = /^[\w.-]+@[\w-]+(\.[\w-]+)+$/ if (!value) {
if (!emailPattern.test(value)) { callback()
callback(new Error('邮箱格式不正确')) return
return }
} const emailPattern = /^[\w.-]+@[\w-]+(\.[\w-]+)+$/
callback() if (!emailPattern.test(value)) {
}, callback(new Error('邮箱格式不正确'))
validatePassword(rule, value, callback) { return
// 密码验证:编辑时可留空,新建或主动修改时必须至少 6 位 }
if (this.isEdit && !value) { callback()
callback() },
return validatePassword(rule, value, callback) {
} // 密码验证:编辑时可留空,新建或主动修改时必须至少 6 位
if (!value) { if (this.isEdit && !value) {
callback(new Error('请输入登录密码')) callback()
return return
} }
if (value.length < 6) { if (!value) {
callback(new Error('密码至少6位')) callback(new Error('请输入登录密码'))
return return
} }
callback() if (value.length < 6) {
}, callback(new Error('密码至少6位'))
validateRoles(rule, value, callback) { return
// 角色校验:接口允许 0 作为合法角色 ID因此判空时需单独处理 }
if (value === 0 || value === '0') { callback()
callback() },
return validateRoles(rule, value, callback) {
} // 角色校验:接口允许 0 作为合法角色 ID因此判空时需单独处理
if (!value) { if (value === 0 || value === '0') {
callback(new Error('请选择角色')) callback()
return return
} }
callback() if (!value) {
}, callback(new Error('请选择角色'))
formatRoleLabel(value) { return
// 将角色 ID 转为人类可读名称,兼容数组或单个值 }
if (Array.isArray(value)) { callback()
return value.map(item => this.formatRoleLabel(item)).join('、') },
} formatRoleLabel(value) {
const target = this.roleOptions.find(item => String(item.id) === String(value)) // 将角色 ID 转为人类可读名称,兼容数组或单个值
return target ? target.name : value || '--' if (Array.isArray(value)) {
}, return value.map(item => this.formatRoleLabel(item)).join('、')
async ok() { }
const valid = await new Promise(resolve => this.$refs.formValidate.validate(resolve)) const target = this.roleOptions.find(item => String(item.id) === String(value))
if (!valid) { return target ? target.name : value || '--'
this.$Message.error('请正确填写表单') },
return async ok() {
} const valid = await new Promise(resolve => this.$refs.formValidate.validate(resolve))
const payload = { if (!valid) {
...this.formValidate, this.$Message.error('请正确填写表单')
} return
if (this.isEdit) { }
if (!payload.password) { const payload = {
delete payload.password ...this.formValidate,
} }
} else { if (this.isEdit) {
delete payload.id if (!payload.password) {
} delete payload.password
this.submitLoading = true }
try { } else {
if (this.isEdit) { delete payload.id
await getAdminUpdate(payload) }
this.$Message.success('更新成功') this.submitLoading = true
} else { try {
await addAdmin(payload) if (this.isEdit) {
this.$Message.success('新增成功') await getAdminUpdate(payload)
} this.$Message.success('更新成功')
this.cancel() } else {
this.getList() await addAdmin(payload)
} finally { this.$Message.success('新增成功')
this.submitLoading = false }
} this.cancel()
}, this.getList()
cancel() { } finally {
this.showModal = false this.submitLoading = false
this.formValidate = this.getDefaultForm() }
this.$nextTick(() => { },
if (this.$refs.formValidate) { cancel() {
this.$refs.formValidate.resetFields() this.showModal = false
} this.formValidate = this.getDefaultForm()
}) this.$nextTick(() => {
}, if (this.$refs.formValidate) {
onChangePage(page) { this.$refs.formValidate.resetFields()
if (this.page !== page) { }
this.page = page })
this.getList() },
} onChangePage(page) {
}, if (this.page !== page) {
onChangePageSize(size) { this.page = page
if (this.pageSize !== size) { this.getList()
this.page = 1 }
this.pageSize = size },
this.getList() onChangePageSize(size) {
} if (this.pageSize !== size) {
}, this.page = 1
}, this.pageSize = size
} this.getList()
}
},
},
}
</script> </script>
<style scoped> <style scoped>
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
padding: 20px; padding: 20px;
box-sizing: border-box; box-sizing: border-box;
} }
.action-btn { .action-btn {
margin: 20px 0; margin: 20px 0;
background-color: white; background-color: white;
padding: 20px; padding: 20px;
border: 1px solid #F3F7FD; border: 1px solid #F3F7FD;
border-radius: 15px; border-radius: 15px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.action-btn button { .action-btn button {
margin-right: 20px; margin-right: 20px;
} }
.table-container { .table-container {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.page { .page {
margin-top: 10px; margin-top: 10px;
text-align: right; text-align: right;
} }
.search-area { .search-area {
background-color: white; background-color: white;
padding: 20px; padding: 20px;
border: 1px solid #F3F7FD; border: 1px solid #F3F7FD;
border-radius: 15px; border-radius: 15px;
} }
</style> </style>