This commit is contained in:
MeSHard
2025-11-24 15:25:50 +08:00
parent 186a92e834
commit e228528c96
14 changed files with 2624 additions and 768 deletions

BIN
dist.zip

Binary file not shown.

View File

@@ -25,14 +25,7 @@ export function EditContent(params) {
export function exportAdminAll(params) {
return request.post('/admin/admin_all', params)
}
// 用户权限修改
export function PermissUpdate(params) {
return request.post('/UpdatePermission', params)
}
// 用户权限读取
export function PermissRead(params) {
return request.post('/UpdatePermission', params)
}
/**
* 订单
@@ -373,24 +366,74 @@ export function ShowAppointmentTime(params) {
export function ModifyAppointmentTime(params) {
return request.post('/ModifyAppointmentTime', params)
}
// 活动
/**
* 活动发布
*/
// 活动列表
export function getEventList(params) {
return request.post('/eventList2', params)
return request.get('/getEventList', { params })
}
// 活动新增
export function addEvent(params) {
return request.post('/addEvent', params)
}
// 活动详情
export function getEvent(params) {
return request.get('/getEvent', { params })
}
// 活动更新
export function getEventUpdate(params) {
return request.post('/getEventUpdate', params)
}
// 活动删除
export function getEventDelete(params) {
return request.post('/getEventDelete', params)
}
// 富文本上传
export function upload(data) {
return request.post('/upload', data, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
}
// 退款订单
export function getRefundOrder(params) {
return request.get('/getRefundOrder', { params })
}
// 权限
// 用户权限修改
export function PermissUpdate(params) {
return request.post('/UpdatePermission', params)
}
// 用户权限读取
export function PermissRead(params) {
return request.post('/UpdatePermission', params)
}
// 获取角色列表
export function getRoleList(params) {
return request.get('/getRoleList', { params })
}
// 新增角色
export function addRole(params) {
return request.post('/addRole', params)
}
// 修改角色
export function getRoleUpdate(params) {
return request.post('/getRoleUpdate', params)
}
// 角色详情
export function getRoleRead(params) {
return request.get('/getRoleRead', { params })
}
// 删除角色
export function getRoleDelete(params) {
return request.post('/getRoleDelete', params)
}
// 权限
export function GetRole(params) {
return request.get('/getRoleList', { params })
}
// 权限
// 菜单
export function getMenuList(params) {
return request.get('/getMenuList', { params })
}
@@ -455,6 +498,52 @@ export function getUserMoneyLog(params) {
export function orderTotal(params) {
return request.get('/orderTotal', { params })
}
/**
* 优惠券
*/
// 优惠券列表
export function getCouponList(params) {
return request.get('/getCouponList', { params })
}
// 新增优惠券
export function addCoupon(params) {
return request.post('/addCoupon', params)
}
// 优惠券详情
export function getCoupon(params) {
return request.get('/getCoupon', { params })
}
// 更新优惠券
export function getCouponUpdate(params) {
return request.post('/getCouponUpdate', params)
}
// 删除优惠券
export function getCouponDelete(params) {
return request.post('/getCouponDelete', params)
}
/**
* 会员
*/
// 会员列表
export function getVipList(params) {
return request.get('/getVipList', { params })
}
// 新增会员
export function addVip(params) {
return request.post('/addVip', params)
}
// 会员详情
export function getVip(params) {
return request.get('/getVip', { params })
}
// 更新会员
export function getVipUpdate(params) {
return request.post('/getVipUpdate', params)
}
// 删除会员
export function getVipDelete(params) {
return request.post('/getVipDelete', params)
}
/**
* 用户
*/
@@ -478,3 +567,7 @@ export function getAdminUpdate(params) {
export function getAdminDelete(params) {
return request.post('/getAdminDelete', params)
}
// 充电桩分析
export function getChargePile(params) {
return request.get('/getChargePile', params)
}

View File

@@ -0,0 +1,280 @@
<template>
<div class="rich-editor">
<div class="rich-editor__toolbar">
<ButtonGroup size="small" class="rich-editor__toolbar-group">
<Button
v-for="btn in toolbarButtons"
:key="btn.key"
:title="btn.label"
:icon="btn.icon"
@click="handleToolbar(btn)"
>
{{ btn.label }}
</Button>
</ButtonGroup>
<Button size="small" icon="ios-image" type="primary" class="rich-editor__upload" :loading="uploading"
@click="triggerUpload">
插入图片
</Button>
<input ref="fileInput" class="rich-editor__file" type="file" accept="image/*" @change="handleUpload">
</div>
<div ref="editor" class="rich-editor__content" :data-placeholder="placeholder" :style="{ minHeight: `${height}px` }"
contenteditable
@input="handleInput" @focus="saveSelection" @keyup="saveSelection" @mouseup="saveSelection" />
</div>
</template>
<script>
import { upload } from '@/api'
export default {
name: 'RichEditor',
props: {
// 富文本初始内容
value: {
type: String,
default: '',
},
// 提示语,用于空状态展示
placeholder: {
type: String,
default: '请输入内容',
},
// 文本区域高度
height: {
type: Number,
default: 280,
},
},
data() {
return {
innerHTML: this.value,
currentRange: null,
uploading: false,
toolbarButtons: [{
key: 'bold',
icon: 'ios-bold',
label: '加粗',
command: 'bold',
},
{
key: 'italic',
icon: 'ios-italic',
label: '斜体',
command: 'italic',
},
{
key: 'underline',
icon: 'ios-underline',
label: '下划线',
command: 'underline',
},
// 段落排版按钮:左/中/右对齐与分割线,覆盖常用排版需求
{
key: 'align-left',
icon: 'ios-arrow-back',
label: '左对齐',
command: 'justifyLeft',
},
{
key: 'align-center',
icon: 'ios-remove',
label: '居中对齐',
command: 'justifyCenter',
},
{
key: 'align-right',
icon: 'ios-arrow-forward',
label: '右对齐',
command: 'justifyRight',
},
{
key: 'horizontal-rule',
icon: 'md-more',
label: '分割线',
command: 'insertHorizontalRule',
},
{
key: 'clear',
icon: 'md-trash',
label: '清空内容',
action: 'clear',
},
],
}
},
watch: {
value(val) {
if (val !== this.innerHTML) {
this.innerHTML = val || ''
this.setContent(this.innerHTML)
}
},
},
mounted() {
this.setContent(this.innerHTML)
},
methods: {
// 将外部内容同步到编辑器
setContent(content) {
if (this.$refs.editor) {
this.$refs.editor.innerHTML = content || ''
}
},
// 执行基础格式化命令
execCommand(command) {
this.focusEditor()
document.execCommand(command, false, null)
this.syncContent()
},
// 点击工具栏按钮时统一处理,支持命令或自定义方法
handleToolbar(btn) {
if (btn.action === 'clear') {
this.clearContent()
return
}
if (btn.command) {
this.execCommand(btn.command)
}
},
// 清空内容
clearContent() {
this.setContent('')
this.syncContent()
},
// 编辑区域获得焦点,方便触发格式化命令
focusEditor() {
if (this.$refs.editor) {
this.$refs.editor.focus()
this.restoreSelection()
}
},
// 监听输入事件将HTML回写给父组件
handleInput() {
this.syncContent()
},
// 记录当前的光标选区,便于插入图片或文本
saveSelection() {
const selection = window.getSelection()
if (selection.rangeCount > 0) {
this.currentRange = selection.getRangeAt(0)
}
},
// 当外部操作打断焦点时,恢复光标
restoreSelection() {
if (!this.currentRange) {
return
}
const selection = window.getSelection()
selection.removeAllRanges()
selection.addRange(this.currentRange)
},
// 同步内容到父组件
syncContent() {
if (!this.$refs.editor) {
return
}
this.innerHTML = this.$refs.editor.innerHTML
this.$emit('input', this.innerHTML)
this.$emit('change', this.innerHTML)
},
// 点击上传按钮
triggerUpload() {
if (this.uploading) {
return
}
this.$refs.fileInput.value = ''
this.$refs.fileInput.click()
},
// 选择图片后上传,并插入富文本
async handleUpload(event) {
const file = event.target.files && event.target.files[0]
if (!file) {
return
}
await this.uploadImage(file)
event.target.value = ''
},
// 将图片上传到后端
async uploadImage(file) {
const isImage = file.type && file.type.indexOf('image/') === 0
if (!isImage) {
this.$Message.error('仅支持上传图片文件')
return
}
this.uploading = true
try {
const formData = new FormData()
formData.append('file', file)
const res = await upload(formData)
const fileUrl = res.data
if (!fileUrl) {
throw new Error('上传接口未返回图片地址')
}
this.insertImage(fileUrl)
this.$Message.success('图片上传成功')
} catch (error) {
this.$Message.error(error.msg || '图片上传失败,请稍后重试')
} finally {
this.uploading = false
}
},
// 插入图片到当前光标位置
insertImage(url) {
this.focusEditor()
document.execCommand('insertImage', false, url)
this.syncContent()
},
},
}
</script>
<style scoped>
.rich-editor {
width: 100%;
}
.rich-editor__toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.rich-editor__content {
height: 100%;
padding: 12px;
border: 1px solid #dcdee2;
border-radius: 8px;
line-height: 1.6;
overflow-y: auto;
}
.rich-editor__content:focus {
outline: none;
border-color: #2d8cf0;
box-shadow: 0 0 0 2px rgba(45, 140, 240, 0.1);
}
.rich-editor__content:empty:before {
content: attr(data-placeholder);
color: #c5c8ce;
}
.rich-editor__upload {
margin-left: 16px;
}
.rich-editor__toolbar-group .ivu-btn {
display: inline-flex;
align-items: center;
padding: 0 12px;
}
.rich-editor__toolbar-group .ivu-btn .ivu-icon {
margin-right: 4px;
}
.rich-editor__file {
display: none;
}
</style>

View File

@@ -3,7 +3,7 @@ import router from './router'
import store from './store'
import createRoutes from '@/utils/createRoutes'
import { getDocumentTitle, resetTokenAndClearUser } from './utils'
// import { getMenuList } from './api'
// 是否有菜单数据
let hasMenus = false
router.beforeEach(async (to, from, next) => {
@@ -16,10 +16,22 @@ router.beforeEach(async (to, from, next) => {
next()
} else {
try {
let menuItems = store.state.menuItems
// let apiMenu = []
// await getMenuList({ token: localStorage.getItem('token') }).then((res) => {
// res.forEach((i) => {
// apiMenu.push({
// text: i.text,
// type: i.type,
// children: i.children,
// })
// })
// menuItems = apiMenu
// })
// 这里可以用 await 配合请求后台数据来生成路由
// const data = await axios.get('xxx')
// const routes = createRoutes(data)
const routes = createRoutes(store.state.menuItems)
const routes = createRoutes(menuItems)
// 动态添加路由
router.addRoutes(routes)
hasMenus = true

View File

@@ -67,11 +67,11 @@ const store = new Vuex.Store({
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
size: 18,
text: '钱包账户',
type: 'ios-paper',
},
// {
// size: 18,
// text: '钱包账户',
// type: 'ios-paper',
// },
{
size: 18,
text: '车辆管理',

View File

@@ -2,107 +2,115 @@
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="充电桩名称:">
<Input type="text" v-model="formInline.user" placeholder="" />
<FormItem label="活动标题:" prop="title">
<Input type="text" clearable v-model="formInline.title" placeholder="请输入活动标题" />
</FormItem>
<FormItem label="状态:" prop="status">
<Select v-model="formInline.status" clearable placeholder="全部状态" style="width: 200px">
<Option v-for="item in statusOptions" :key="item.value" :value="item.value">
{{ item.label }}
</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="handleSubmit">搜索</Button>
<Button style="margin-left: 8px" @click="resetSearch">重置</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</Button> -->
<Button type="primary" @click="add">添加</Button>
</div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #action="{ row, index }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(index)">编辑</Button>
<Button type="error" size="small" @click="remove(index)">删除</Button>
<Table border :columns="columns" stripe :height="tableHeight" :data="data" :loading="loading">
<template #status="{ row }">
{{ getStatusText(row.status) }}
</template>
<template #action="{ row }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(row.id)">编辑</Button>
<Button type="error" size="small" @click="remove(row.id)">删除</Button>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
<Page :total="total" show-total show-sizer class="page" :current="page" :page-size="pageSize"
@on-change="onChangePage" @on-page-size-change="onChangePageSize" />
</div>
<!-- 添加编辑 -->
<Modal v-model="show_modal" title="Common Modal dialog box title" :mask-closable="false" @on-ok="ok"
@on-cancel="cancel">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="80">
<FormItem label="Name" prop="name">
<Input v-model="formValidate.name" placeholder="Enter your name" />
<Modal v-model="show_modal" :title="modalTitle" :mask-closable="false" :width="820" @on-cancel="cancel">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="100">
<FormItem label="活动标题" prop="title">
<Input v-model="formValidate.title" placeholder="请输入活动标题" />
</FormItem>
<FormItem label="E-mail" prop="mail">
<Input v-model="formValidate.mail" placeholder="Enter your e-mail" />
</FormItem>
<FormItem label="City" prop="city">
<Select v-model="formValidate.city" placeholder="Select your city">
<Option value="beijing">New York</Option>
<Option value="shanghai">London</Option>
<Option value="shenzhen">Sydney</Option>
</Select>
</FormItem>
<FormItem label="Date">
<Row>
<Col span="11">
<DatePicker type="date" placeholder="Select date" v-model="formValidate.date"></DatePicker>
</Col>
<Col span="2" style="text-align: center">-</Col>
<Col span="11">
<TimePicker type="time" placeholder="Select time" v-model="formValidate.time"></TimePicker>
</Col>
</Row>
</FormItem>
<FormItem label="Gender" prop="gender">
<RadioGroup v-model="formValidate.gender">
<Radio label="male">Male</Radio>
<Radio label="female">Female</Radio>
<FormItem label="活动状态" prop="status">
<RadioGroup v-model="formValidate.status">
<Radio label="1">已发布</Radio>
<Radio label="0">未发布</Radio>
</RadioGroup>
</FormItem>
<FormItem label="Hobby" prop="interest">
<CheckboxGroup v-model="formValidate.interest">
<Checkbox label="Eat"></Checkbox>
<Checkbox label="Sleep"></Checkbox>
<Checkbox label="Run"></Checkbox>
<Checkbox label="Movie"></Checkbox>
</CheckboxGroup>
</FormItem>
<FormItem label="Desc" prop="desc">
<Input v-model="formValidate.desc" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }"
placeholder="Enter something..." />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit2('formValidate')">Submit</Button>
<Button @click="handleReset('formValidate')" style="margin-left: 8px">Reset</Button>
<FormItem label="活动内容" prop="content">
<RichEditor v-model="formValidate.content" placeholder="请输入活动内容,支持图片上传" :height="320"
@change="handleContentChange" />
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" :loading="submitLoading" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import { getEventList } from '@/api'
import {
getEventList,
addEvent,
getEvent,
getEventUpdate,
getEventDelete,
} from '@/api'
import RichEditor from '@/components/RichEditor.vue'
export default {
name: 'charging_station',
name: 'activity',
components: {
RichEditor,
},
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
loading: false, // 列表查询 loading 态
show_modal: false, // 新增/编辑弹窗显隐
modalTitle: '添加活动', // 弹窗标题
page: 1, // 当前页码
pageSize: 10, // 每页数量
total: 0, // 数据总条数
tableHeight: 500, // 表格高度,用于自适应
formInline: {
user: '',
password: '',
title: '',
status: '',
},
ruleInline: {}, // 站点管理页面无搜索校验,此处保持空对象占位
statusOptions: [{
label: '全部状态',
value: '', // 传空值表示不筛选状态
},
ruleInline: {},
columns: [
{
title: '活动名称',
label: '未发布',
value: '0',
},
{
label: '已发布',
value: '1',
},
],
statusMap: {
0: '未发布',
1: '已发布',
},
columns: [{ // 表格列配置,沿用站点管理页面 Table 风格
title: '活动标题',
key: 'title',
},
{
title: '活动状态',
key: 'statusMsg',
slot: 'status',
},
{
title: '创建时间',
@@ -115,46 +123,31 @@ export default {
align: 'center',
},
],
data: [],
data: [], // 活动列表数据源
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
id: '', // 活动主键,编辑时使用
title: '', // 活动标题
status: '1', // 活动状态1 已发布0 未发布
content: '', // 富文本正文
},
ruleValidate: {
name: [
{ required: true, message: 'The name cannot be empty', trigger: 'blur' },
],
mail: [
{ required: true, message: 'Mailbox cannot be empty', trigger: 'blur' },
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' },
],
city: [
{ required: true, message: 'Please select the city', trigger: 'change' },
],
gender: [
{ required: true, message: 'Please select gender', trigger: 'change' },
],
interest: [
{ required: true, type: 'array', min: 1, message: 'Choose at least one hobby', trigger: 'change' },
{ type: 'array', max: 2, message: 'Choose two hobbies at best', trigger: 'change' },
],
date: [
{ required: true, type: 'date', message: 'Please select the date', trigger: 'change' },
],
time: [
{ required: true, type: 'string', message: 'Please select time', trigger: 'change' },
],
desc: [
{ required: true, message: 'Please enter a personal introduction', trigger: 'blur' },
{ type: 'string', min: 20, message: 'Introduce no less than 20 words', trigger: 'blur' },
],
title: [{
required: true,
message: '请输入活动标题',
trigger: 'blur',
}],
status: [{
required: true,
message: '请选择活动状态',
trigger: 'change',
}],
content: [{
required: true,
message: '请输入活动内容',
trigger: 'change',
}],
},
submitLoading: false,
}
},
mounted() {
@@ -166,56 +159,213 @@ export default {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
// 自适应计算表格高度,保持页面视觉与站点管理页面一致
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const actionBtnHeight = document.querySelector('.action-btn').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - actionBtnHeight - searchHeight - pageHeight - margins - 130
const search = document.querySelector('.search-area')
const action = document.querySelector('.action-btn')
const searchHeight = search ? search.offsetHeight : 0
const actionHeight = action ? action.offsetHeight : 0
const pageHeight = 32
const margins = 40
this.tableHeight = window.innerHeight - searchHeight - actionHeight - pageHeight - margins - 130
if (this.tableHeight < 300) {
this.tableHeight = 300
}
},
// 获取活动列表数据
async getList() {
await getEventList({ page: 1 }).then((res) => {
this.total = res.total
this.data = res.data
this.page = res.current_page
})
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
this.loading = true
try {
const params = {
page: this.page,
pageSize: this.pageSize,
title: this.formInline.title,
status: this.formInline.status,
}
if (params.status === '') {
delete params.status
}
const res = await getEventList(params)
// 兼容后端返回 { data: { list: [], total: 0 } } 或 { data: [], total: 0 } 的场景
const responseData = res.data || res || {}
let list = []
if (Array.isArray(responseData)) {
list = responseData
this.total = responseData.length
} else if (responseData && typeof responseData === 'object') {
list = responseData.data || responseData.list || responseData.records || []
const total = responseData.total || responseData.count || responseData.totalCount
if (typeof total === 'number') {
this.total = total
} else if (Array.isArray(list)) {
this.total = list.length
} else {
this.$Message.error('Fail!')
this.total = 0
}
if (responseData.current_page) {
this.page = responseData.current_page
}
}
this.data = Array.isArray(list) ? list : []
} catch (error) {
this.data = []
this.total = 0
} finally {
this.loading = false
}
},
// 点击搜索按钮,重置页码并刷新列表
handleSubmit() {
this.page = 1
this.getList()
},
// 重置查询条件,保持行为与站点管理搜索一致
resetSearch() {
this.formInline.title = ''
this.formInline.status = ''
if (this.$refs.formInline) {
this.$refs.formInline.resetFields()
}
this.handleSubmit()
},
// 新增活动
add() {
this.modalTitle = '添加活动'
this.show_modal = true
this.resetForm()
},
// 重置弹窗表单字段
resetForm() {
Object.assign(this.formValidate, {
id: '',
title: '',
status: '1',
content: '',
})
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.resetFields()
}
})
},
handleReset(name) {
this.$refs[name].resetFields()
},
handleSubmit2(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
}
})
},
show(index) {
this.$Modal.info({
title: 'User Info',
content: `Name${this.data[index].name}<br>Age${this.data[index].age}<br>Address${this.data[index].address}`,
})
},
remove(index) {
this.data.splice(index, 1)
},
ok() {
this.$Message.info('Clicked ok')
},
// 取消弹窗
cancel() {
this.$Message.info('Clicked cancel')
this.show_modal = false
if (this.$refs.formValidate) {
this.$refs.formValidate.resetFields()
}
},
// 点击编辑时加载活动详情
async show(id) {
if (!id) {
return
}
try {
const res = await getEvent({
id,
})
const info = res.data || {}
Object.assign(this.formValidate, {
id: info.id,
title: info.title || '',
status: info.status != null ? String(info.status) : '0',
content: info.content || '',
})
this.modalTitle = '编辑活动'
this.show_modal = true
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
} catch (error) {
this.$Message.error('获取活动详情失败,请稍后重试')
}
},
// 删除活动前二次确认
async remove(id) {
if (!id) {
return
}
const confirm = await new Promise(resolve => {
this.$Modal.confirm({
title: '确认删除',
content: '删除后不可恢复,确定继续吗?',
onOk: () => resolve(true),
onCancel: () => resolve(false),
})
})
if (!confirm) {
return
}
await getEventDelete({
id,
})
this.$Message.success('删除成功')
if (this.data.length === 1 && this.page > 1) {
this.page -= 1
}
this.getList()
},
// 新增/编辑提交保存
async ok() {
const valid = await new Promise(resolve => this.$refs.formValidate.validate(resolve))
if (!valid) {
this.$Message.error('请完善活动信息')
return
}
this.submitLoading = true
const payload = {
id: this.formValidate.id,
title: this.formValidate.title.trim(),
status: Number(this.formValidate.status),
content: this.formValidate.content,
}
try {
if (payload.id) {
await getEventUpdate(payload)
this.$Message.success('活动更新成功')
} else {
await addEvent(payload)
this.$Message.success('活动创建成功')
}
this.cancel()
this.getList()
} catch (error) {
this.$Message.error('保存失败,请稍后重试')
} finally {
this.submitLoading = false
}
},
// 分页切换
onChangePage(e) {
if (this.page !== e) {
this.page = e
this.getList()
}
},
// pageSize切换
onChangePageSize(e) {
if (this.pageSize !== e) {
this.page = 1
this.pageSize = e
this.getList()
}
},
// 状态文案
getStatusText(status) {
const key = Number(status)
return this.statusMap[key] || '未发布'
},
// 状态颜色
getStatusColor(status) {
return Number(status) === 1 ? 'success' : 'default'
},
// 富文本内容变更时主动触发校验,防止用户未输入内容直接提交
handleContentChange() {
if (this.show_modal && this.$refs.formValidate) {
this.$refs.formValidate.validateField('content')
}
},
},
}
@@ -237,9 +387,10 @@ export default {
border-radius: 15px;
display: flex;
align-items: center;
button {
margin-right: 20px;
}
.action-btn button {
margin-right: 20px;
}
.table-container {
@@ -252,6 +403,7 @@ export default {
margin-top: 10px;
text-align: right;
}
.search-area {
background-color: white;
padding-top: 20px;

View File

@@ -2,220 +2,565 @@
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="优惠券名称:">
<Input type="text" v-model="formInline.user" placeholder="" />
<FormItem label="优惠券名称:" prop="name">
<Input type="text" clearable v-model="formInline.name" placeholder="请输入优惠券名称" />
</FormItem>
<FormItem label="状态:" prop="status">
<Select v-model="formInline.status" clearable placeholder="全部状态" style="width: 200px">
<Option v-for="option in statusOptions" :key="`search-status-${option.value}`" :value="option.value">
{{ option.label }}
</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="handleSubmit">搜索</Button>
<Button style="margin-left: 8px" @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</Button> -->
<Button type="primary" @click="add">添加优惠券</Button>
</div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #action="{ row, index }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(index)">编辑</Button>
<Button type="error" size="small" @click="remove(index)">删除</Button>
<Table border :columns="columns" stripe :height="tableHeight" :data="data" :loading="tableLoading">
<template #station="{ row }">
<span>{{ row.stationLabel }}</span>
</template>
<template #action="{ row }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(row.id)">编辑</Button>
<Button type="error" size="small" @click="remove(row.id)">删除</Button>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
<Page :total="total" :current="page" :page-size="pageSize" show-total show-sizer class="page"
@on-change="onChangePage" @on-page-size-change="onChangePageSize" />
</div>
<!-- 添加编辑 -->
<Modal v-model="show_modal" title="Common Modal dialog box title" :mask-closable="false" @on-ok="ok"
@on-cancel="cancel">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="80">
<FormItem label="Name" prop="name">
<Input v-model="formValidate.name" placeholder="Enter your name" />
<Modal v-model="show_modal" :title="formValidate.id ? '编辑优惠券' : '添加优惠券'" :mask-closable="false"
:closable="false" @on-cancel="cancel">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="120">
<FormItem label="优惠券名称" prop="name">
<Input v-model="formValidate.name" placeholder="请输入优惠券名称" />
</FormItem>
<FormItem label="E-mail" prop="mail">
<Input v-model="formValidate.mail" placeholder="Enter your e-mail" />
</FormItem>
<FormItem label="City" prop="city">
<Select v-model="formValidate.city" placeholder="Select your city">
<Option value="beijing">New York</Option>
<Option value="shanghai">London</Option>
<Option value="shenzhen">Sydney</Option>
<FormItem label="适用站点" prop="station_id">
<Select v-model="formValidate.station_id" placeholder="请选择关联站点" filterable style="width: 240px">
<Option v-for="station in stationOptions" :key="station.charge_station_id"
:value="station.charge_station_id">
{{ station.charge_station_name }}
</Option>
</Select>
</FormItem>
<FormItem label="Date">
<Row>
<Col span="11">
<DatePicker type="date" placeholder="Select date" v-model="formValidate.date"></DatePicker>
</Col>
<Col span="2" style="text-align: center">-</Col>
<Col span="11">
<TimePicker type="time" placeholder="Select time" v-model="formValidate.time"></TimePicker>
</Col>
</Row>
</FormItem>
<FormItem label="Gender" prop="gender">
<RadioGroup v-model="formValidate.gender">
<Radio label="male">Male</Radio>
<Radio label="female">Female</Radio>
<FormItem label="用户类型" prop="user_type">
<RadioGroup v-model="formValidate.user_type">
<Radio v-for="option in userTypeOptions" :key="`user-${option.value}`" :label="option.value">
{{ option.label }}
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="Hobby" prop="interest">
<CheckboxGroup v-model="formValidate.interest">
<Checkbox label="Eat"></Checkbox>
<Checkbox label="Sleep"></Checkbox>
<Checkbox label="Run"></Checkbox>
<Checkbox label="Movie"></Checkbox>
</CheckboxGroup>
<FormItem label="使用门槛(元)" prop="threshold">
<InputNumber v-model="formValidate.threshold" :min="0" :precision="2" :step="1" style="width: 240px" />
</FormItem>
<FormItem label="Desc" prop="desc">
<Input v-model="formValidate.desc" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }"
placeholder="Enter something..." />
<FormItem label="优惠金额(元)" prop="value">
<InputNumber v-model="formValidate.value" :min="0" :precision="2" :step="1" style="width: 240px" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit2('formValidate')">Submit</Button>
<Button @click="handleReset('formValidate')" style="margin-left: 8px">Reset</Button>
<FormItem label="过期时间" prop="expiration_time">
<DatePicker type="datetime" format="yyyy-MM-dd HH:mm" v-model="formValidate.expiration_time"
placeholder="请选择过期时间" style="width: 240px" />
</FormItem>
<FormItem label="状态" prop="status">
<RadioGroup v-model="formValidate.status">
<Radio v-for="option in statusOptions" :key="`status-${option.value}`" :label="option.value">
{{ option.label }}
</Radio>
</RadioGroup>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" :loading="submitLoading" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import { getEventList } from '@/api'
import {
getCouponList,
addCoupon,
getCoupon,
getCouponUpdate,
getCouponDelete,
getStationList,
} from '@/api'
const createDefaultCouponForm = () => ({
id: '',
name: '',
station_id: '',
user_type: 1,
threshold: 0,
value: 0,
expiration_time: '',
status: 1,
})
export default {
name: 'charging_station',
name: 'coupon_manage',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
page: 1,
pageSize: 10,
total: 0,
tableLoading: false,
submitLoading: false,
formInline: {
user: '',
password: '',
name: '',
status: '',
},
ruleInline: {},
columns: [
{
title: '优惠券名称',
key: 'title',
key: 'name',
minWidth: 160,
},
{
title: '所属站点',
slot: 'station',
minWidth: 160,
},
{
title: '用户类型',
key: 'userTypeText',
minWidth: 120,
},
{
title: '使用门槛(元)',
key: 'thresholdText',
minWidth: 140,
},
{
title: '优惠金额(元)',
key: 'valueText',
minWidth: 140,
},
{
title: '过期时间',
key: 'expirationTimeText',
minWidth: 170,
},
{
title: '状态',
key: 'statusMsg',
key: 'statusText',
minWidth: 100,
},
{
title: '创建时间',
key: 'create_time',
key: 'createTimeText',
minWidth: 170,
},
{
title: '操作',
slot: 'action',
width: 150,
width: 180,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
stationOptions: [],
userTypeOptions: [
{ label: '所有用户', value: 1 },
{ label: '会员用户', value: 2 },
],
statusOptions: [
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 },
],
formValidate: createDefaultCouponForm(),
ruleValidate: {
name: [
{ required: true, message: 'The name cannot be empty', trigger: 'blur' },
{
required: true,
message: '请输入优惠券名称',
trigger: 'blur',
},
],
mail: [
{ required: true, message: 'Mailbox cannot be empty', trigger: 'blur' },
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' },
station_id: [
{
required: true,
message: '请选择适用站点',
trigger: 'change',
},
],
city: [
{ required: true, message: 'Please select the city', trigger: 'change' },
user_type: [
{
required: true,
type: 'number',
message: '请选择用户类型',
trigger: 'change',
},
],
gender: [
{ required: true, message: 'Please select gender', trigger: 'change' },
threshold: [
{
required: true,
type: 'number',
message: '请输入使用门槛',
trigger: 'change',
},
],
interest: [
{ required: true, type: 'array', min: 1, message: 'Choose at least one hobby', trigger: 'change' },
{ type: 'array', max: 2, message: 'Choose two hobbies at best', trigger: 'change' },
value: [
{
required: true,
type: 'number',
message: '请输入优惠金额',
trigger: 'change',
},
],
date: [
{ required: true, type: 'date', message: 'Please select the date', trigger: 'change' },
expiration_time: [
{
required: true,
type: 'date',
message: '请选择过期时间',
trigger: 'change',
},
],
time: [
{ required: true, type: 'string', message: 'Please select time', trigger: 'change' },
],
desc: [
{ required: true, message: 'Please enter a personal introduction', trigger: 'blur' },
{ type: 'string', min: 20, message: 'Introduce no less than 20 words', trigger: 'blur' },
status: [
{
required: true,
type: 'number',
message: '请选择状态',
trigger: 'change',
},
],
},
}
},
mounted() {
// this.getList()
this.calculateTableHeight()
async mounted() {
await this.fetchStationOptions()
await this.getList()
this.$nextTick(this.calculateTableHeight)
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
// 计算表格高度保持布局与站点管理页面一<E99DA2>?
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const actionBtnHeight = document.querySelector('.action-btn').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - actionBtnHeight - searchHeight - pageHeight - margins - 130
const searchArea = document.querySelector('.search-area')
const actionArea = document.querySelector('.action-btn')
const searchHeight = searchArea ? searchArea.offsetHeight : 0
const actionHeight = actionArea ? actionArea.offsetHeight : 0
const pageHeight = 32
const margins = 40
const availableHeight = window.innerHeight - actionHeight - searchHeight - pageHeight - margins - 130
this.tableHeight = Math.max(320, availableHeight)
},
// 拉取站点列表,供下拉选择使用
async fetchStationOptions() {
try {
const res = await getStationList({
page: 1,
pageSize: 9999,
})
const list = res && res.data && Array.isArray(res.data.data) ? res.data.data : []
this.stationOptions = list
.map(item => ({
charge_station_id: this.extractStationId(item),
charge_station_name: this.extractStationName(item),
}))
.filter(option => option.charge_station_id)
} catch (error) {
this.$Message.error('获取站点列表失败,请稍后重试')
}
},
// 搜索优惠券列表
handleSubmit() {
this.page = 1
this.getList()
},
// 重置搜索条件
handleReset() {
this.formInline = {
name: '',
status: '',
}
this.handleSubmit()
},
// 查询优惠券列表数据
async getList() {
await getEventList({ page: 1 }).then((res) => {
this.total = res.total
this.data = res.data
this.page = res.current_page
})
const params = {
page: this.page,
pageSize: this.pageSize,
}
if (this.formInline.name) {
params.name = this.formInline.name
}
if (this.formInline.status !== '' && this.formInline.status !== null && this.formInline.status !== undefined) {
params.status = this.formInline.status
}
this.tableLoading = true
try {
const res = await getCouponList(params)
const list = res && res.data && Array.isArray(res.data.data) ? res.data.data : []
this.total = (res && res.data && res.data.total) || 0
this.data = list.map(item => this.normalizeCouponRow(item))
} catch (error) {
this.$Message.error('获取优惠券列表失败,请稍后重试')
} finally {
this.tableLoading = false
}
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
// 正常化表格显示字段,保证列表列与站点管理页面风格统一
normalizeCouponRow(item) {
const stationLabel = this.resolveStationLabel(item)
return {
id: item.id,
name: item.name || '--',
stationLabel,
userTypeText: Number(item.user_type) === 2 ? '会员用户' : '所有用户',
thresholdText: this.formatAmount(item.threshold),
valueText: this.formatAmount(item.value),
expirationTimeText: this.formatTimestamp(item.expiration_time),
statusText: Number(item.status) === 1 ? '开启' : '关闭',
createTimeText: this.formatTimestamp(item.create_time),
}
},
// 格式化金额,两位小数展示
formatAmount(value) {
if (value === undefined || value === null || value === '') {
return '--'
}
const numberValue = Number(value)
if (Number.isNaN(numberValue)) {
return '--'
}
return numberValue.toFixed(2)
},
// 将时间戳转换为标准日期文案
formatTimestamp(timestamp) {
if (!timestamp) {
return '--'
}
const value = Number(timestamp)
if (Number.isNaN(value)) {
return '--'
}
const date = `${value}`.length === 13 ? new Date(value) : new Date(value * 1000)
if (Number.isNaN(date.getTime())) {
return '--'
}
const Y = date.getFullYear()
const M = `${date.getMonth() + 1}`.padStart(2, '0')
const D = `${date.getDate()}`.padStart(2, '0')
const h = `${date.getHours()}`.padStart(2, '0')
const m = `${date.getMinutes()}`.padStart(2, '0')
const s = `${date.getSeconds()}`.padStart(2, '0')
return `${Y}-${M}-${D} ${h}:${m}:${s}`
},
// 抽取站点 ID兼容不同接口字段
extractStationId(item) {
const candidateKeys = ['charge_station_id', 'station_id', 'id']
for (let i = 0; i < candidateKeys.length; i += 1) {
const key = candidateKeys[i]
const value = item && item[key]
if (value !== undefined && value !== null && `${value}` !== '') {
return String(value)
}
}
return ''
},
// 抽取站点显示名,若接口只返回 ID 则兜底
extractStationName(item) {
if (!item) {
return ''
}
return item.charge_station_name || item.station_name || item.name || ''
},
// 根据列表数据推断站点名称
resolveStationLabel(item) {
if (!item) {
return '--'
}
if (item.station_name) {
return item.station_name
}
if (item.station && item.station.charge_station_name) {
return item.station.charge_station_name
}
const targetId = item.station_id ? String(item.station_id) : ''
if (targetId) {
const matched = this.stationOptions.find(option => option.charge_station_id === targetId)
if (matched) {
return matched.charge_station_name
}
return `站点-${targetId}`
}
return '--'
},
// 切换分页
onChangePage(page) {
if (this.page !== page) {
this.page = page
this.getList()
}
},
onChangePageSize(size) {
if (this.pageSize !== size) {
this.pageSize = size
this.page = 1
this.getList()
}
},
// 打开新增弹窗
add() {
this.formValidate = createDefaultCouponForm()
this.show_modal = true
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
},
handleReset(name) {
this.$refs[name].resetFields()
},
handleSubmit2(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
// 查看并编辑任务
async show(id) {
if (!id) {
return
}
try {
const res = await getCoupon({ id })
const detail = res && res.data ? res.data : {}
this.formValidate = {
...createDefaultCouponForm(),
id: detail.id,
name: detail.name || '',
station_id: detail.station_id ? String(detail.station_id) : '',
user_type: detail.user_type !== undefined ? Number(detail.user_type) : 1,
threshold: this.normalizeNumber(detail.threshold),
value: this.normalizeNumber(detail.value),
expiration_time: this.transformTimestampToDate(detail.expiration_time),
status: detail.status !== undefined ? Number(detail.status) : 1,
}
this.show_modal = true
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
} catch (error) {
this.$Message.error('获取优惠券详情失败,请稍后重试')
}
},
show(index) {
this.$Modal.info({
title: 'User Info',
content: `Name${this.data[index].name}<br>Age${this.data[index].age}<br>Address${this.data[index].address}`,
})
// 数字入参归一化
normalizeNumber(value) {
const numberValue = Number(value)
return Number.isNaN(numberValue) ? 0 : numberValue
},
remove(index) {
this.data.splice(index, 1)
},
ok() {
this.$Message.info('Clicked ok')
// 将时间戳转为 DatePicker 可识别的 Date 对象
transformTimestampToDate(ts) {
if (!ts) {
return ''
}
const value = Number(ts)
if (Number.isNaN(value)) {
return ''
}
const date = `${value}`.length === 13 ? new Date(value) : new Date(value * 1000)
return Number.isNaN(date.getTime()) ? '' : date
},
// 点击取消按钮
cancel() {
this.$Message.info('Clicked cancel')
this.show_modal = false
this.formValidate = createDefaultCouponForm()
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
},
// 提交新增/编辑
async ok() {
const valid = await new Promise(resolve => {
this.$refs.formValidate.validate(validRes => {
resolve(validRes)
})
})
if (!valid) {
this.$Message.error('请完善表单信息后再提交')
return
}
this.submitLoading = true
const payload = this.buildSubmitPayload()
try {
if (payload.id) {
const res = await getCouponUpdate(payload)
this.$Message.success(res.msg || '更新成功')
} else {
const res = await addCoupon(payload)
this.$Message.success(res.msg || '新增成功')
}
this.cancel()
this.getList()
} catch (error) {
this.$Message.error(error.msg || '提交失败,请稍后重试')
} finally {
this.submitLoading = false
}
},
// 组装提交参数,统一在此处理单位转换
buildSubmitPayload() {
const payload = {
...this.formValidate,
}
payload.station_id = payload.station_id ? Number(payload.station_id) : ''
payload.user_type = Number(payload.user_type)
payload.threshold = Number(payload.threshold)
payload.value = Number(payload.value)
payload.status = Number(payload.status)
payload.expiration_time = this.transformDateToTimestamp(payload.expiration_time)
return payload
},
// DatePicker 值转时间戳(秒)
transformDateToTimestamp(value) {
if (!value) {
return ''
}
if (typeof value === 'number') {
return `${value}`.length === 10 ? value : Math.floor(value / 1000)
}
const date = value instanceof Date ? value : new Date(value)
if (Number.isNaN(date.getTime())) {
return ''
}
return Math.floor(date.getTime() / 1000)
},
// 删除优惠券
async remove(id) {
if (!id) {
return
}
const confirm = await new Promise(resolve => {
this.$Modal.confirm({
title: '确认删除',
content: '删除后不可恢复,是否继续?',
onOk: () => resolve(true),
onCancel: () => resolve(false),
})
})
if (!confirm) {
return
}
try {
const res = await getCouponDelete({ id })
this.$Message.success(res.msg || '删除成功')
if (this.data.length === 1 && this.page > 1) {
this.page -= 1
}
this.getList()
} catch (error) {
this.$Message.error(error.msg || '删除失败,请稍后重试')
}
},
},
}
@@ -237,6 +582,7 @@ export default {
border-radius: 15px;
display: flex;
align-items: center;
button {
margin-right: 20px;
}
@@ -252,6 +598,7 @@ export default {
margin-top: 10px;
text-align: right;
}
.search-area {
background-color: white;
padding-top: 20px;

View File

@@ -1,9 +1,451 @@
<template>
<view>safsa</view>
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem label="会员名称:" prop="name">
<Input type="text" clearable v-model="formInline.name" placeholder="请输入会员名称" />
</FormItem>
<FormItem label="状态:" prop="status">
<Select v-model="formInline.status" clearable placeholder="全部状态" style="width: 200px">
<Option v-for="option in statusOptions" :key="`vip-status-${option.value}`" :value="option.value">
{{ option.label }}
</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit">搜索</Button>
<Button style="margin-left: 8px" @click="handleReset">重置</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="add">添加会员</Button>
</div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data" :loading="tableLoading">
<template #remarks="{ row }">
<Tooltip v-if="row.remarks" transfer :content="row.remarks" placement="top">
<span class="ellipsis">{{ row.remarks }}</span>
</Tooltip>
<span v-else>--</span>
</template>
<template #action="{ row }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(row.id)">编辑</Button>
<Button type="error" size="small" @click="remove(row.id)">删除</Button>
</template>
</Table>
<Page :total="total" :current="page" :page-size="pageSize" show-total show-sizer class="page"
@on-change="onChangePage" @on-page-size-change="onChangePageSize" />
</div>
<Modal v-model="show_modal" :title="formValidate.id ? '编辑会员' : '添加会员'" :mask-closable="false"
:closable="false" width="600" @on-cancel="cancel">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="120">
<FormItem label="会员名称" prop="name">
<Input v-model="formValidate.name" placeholder="请输入会员名称" />
</FormItem>
<FormItem label="会员介绍" prop="remarks">
<Input v-model="formValidate.remarks" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }"
placeholder="请输入会员权益/说明" />
</FormItem>
<FormItem label="原价(元)" prop="original_price">
<InputNumber v-model="formValidate.original_price" :min="0" :precision="2" :step="1"
style="width: 240px" />
</FormItem>
<FormItem label="售价(元)" prop="selling_price">
<InputNumber v-model="formValidate.selling_price" :min="0" :precision="2" :step="1"
style="width: 240px" />
</FormItem>
<FormItem label="有效期(天)" prop="time">
<InputNumber v-model="formValidate.time" :min="1" :step="1" style="width: 240px" />
</FormItem>
<FormItem label="状态" prop="status">
<RadioGroup v-model="formValidate.status">
<Radio v-for="option in statusOptions" :key="`vip-form-status-${option.value}`" :label="option.value">
{{ option.label }}
</Radio>
</RadioGroup>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" :loading="submitLoading" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
getVipList,
addVip,
getVip,
getVipUpdate,
getVipDelete,
} from '@/api'
const createDefaultVipForm = () => ({
id: '',
name: '',
remarks: '',
original_price: 0,
selling_price: 0,
time: 1,
status: 1,
})
export default {
name: 'user_vip',
data() {
return {
show_modal: false,
tableHeight: 500,
tableLoading: false,
submitLoading: false,
page: 1,
pageSize: 10,
total: 0,
formInline: {
name: '',
status: '',
},
ruleInline: {},
columns: [
{ title: '会员名称', key: 'name', minWidth: 160 },
{ title: '会员介绍', slot: 'remarks', minWidth: 200 },
{ title: '原价(元)', key: 'originalPriceText', minWidth: 120 },
{ title: '售价(元)', key: 'sellingPriceText', minWidth: 120 },
{ title: '有效期(天)', key: 'timeText', minWidth: 120 },
{ title: '状态', key: 'statusText', minWidth: 100 },
{ title: '创建时间', key: 'createTimeText', minWidth: 170 },
{ title: '操作', slot: 'action', width: 180, align: 'center' },
],
data: [],
statusOptions: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 },
],
formValidate: createDefaultVipForm(),
ruleValidate: {
name: [
{ required: true, message: '请输入会员名称', trigger: 'blur' },
],
remarks: [
{ required: false },
],
original_price: [
{ required: true, type: 'number', message: '请输入原价', trigger: 'change' },
],
selling_price: [
{ required: true, type: 'number', message: '请输入售价', trigger: 'change' },
],
time: [
{ required: true, type: 'number', message: '请输入有效期', trigger: 'change' },
],
status: [
{ required: true, type: 'number', message: '请选择状态', trigger: 'change' },
],
},
}
},
async mounted() {
await this.getList()
this.$nextTick(this.calculateTableHeight)
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
// 计算表格高度,保持与站点管理页面布局一致
calculateTableHeight() {
const searchArea = document.querySelector('.search-area')
const actionArea = document.querySelector('.action-btn')
const searchHeight = searchArea ? searchArea.offsetHeight : 0
const actionHeight = actionArea ? actionArea.offsetHeight : 0
const pageHeight = 32
const margins = 40
const availableHeight = window.innerHeight - actionHeight - searchHeight - pageHeight - margins - 130
this.tableHeight = Math.max(320, availableHeight)
},
// 请求会员列表
async getList() {
const params = {
page: this.page,
pageSize: this.pageSize,
}
if (this.formInline.name) {
params.name = this.formInline.name
}
if (this.formInline.status !== '' && this.formInline.status !== null && this.formInline.status !== undefined) {
params.status = this.formInline.status
}
this.tableLoading = true
try {
const res = await getVipList(params)
const list = res && res.data && Array.isArray(res.data.data) ? res.data.data : []
this.total = (res && res.data && res.data.total) || 0
this.data = list.map(item => this.normalizeVipRow(item))
} catch (error) {
this.$Message.error('获取会员列表失败,请稍后重试')
} finally {
this.tableLoading = false
}
},
// 组装表格展示字段
normalizeVipRow(item) {
return {
id: item.id,
name: item.name || '--',
remarks: item.remarks || '',
originalPriceText: this.formatAmount(item.original_price),
sellingPriceText: this.formatAmount(item.selling_price),
timeText: this.formatDays(item.time),
statusText: Number(item.status) === 1 ? '启用' : '禁用',
createTimeText: this.formatTimestamp(item.createtime),
updateTimeText: this.formatTimestamp(item.updatetime),
}
},
// 金额格式化
formatAmount(value) {
if (value === undefined || value === null || value === '') {
return '--'
}
const numberValue = Number(value)
if (Number.isNaN(numberValue)) {
return '--'
}
return numberValue.toFixed(2)
},
// 有效期格式化
formatDays(value) {
if (value === undefined || value === null || value === '') {
return '--'
}
const numberValue = Number(value)
if (Number.isNaN(numberValue)) {
return '--'
}
return `${numberValue}`
},
// 时间戳转日期
formatTimestamp(timestamp) {
if (!timestamp) {
return '--'
}
const value = Number(timestamp)
if (Number.isNaN(value)) {
return '--'
}
const date = `${value}`.length === 13 ? new Date(value) : new Date(value * 1000)
if (Number.isNaN(date.getTime())) {
return '--'
}
const Y = date.getFullYear()
const M = `${date.getMonth() + 1}`.padStart(2, '0')
const D = `${date.getDate()}`.padStart(2, '0')
const h = `${date.getHours()}`.padStart(2, '0')
const m = `${date.getMinutes()}`.padStart(2, '0')
const s = `${date.getSeconds()}`.padStart(2, '0')
return `${Y}-${M}-${D} ${h}:${m}:${s}`
},
// 搜索
handleSubmit() {
this.page = 1
this.getList()
},
// 重置搜索
handleReset() {
this.formInline = {
name: '',
status: '',
}
this.handleSubmit()
},
// 分页
onChangePage(page) {
if (this.page !== page) {
this.page = page
this.getList()
}
},
onChangePageSize(size) {
if (this.pageSize !== size) {
this.pageSize = size
this.page = 1
this.getList()
}
},
// 打开新增弹窗
add() {
this.formValidate = createDefaultVipForm()
this.show_modal = true
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
},
// 编辑详情
async show(id) {
if (!id) {
return
}
try {
const res = await getVip({ id })
const detail = res && res.data ? res.data : {}
this.formValidate = {
...createDefaultVipForm(),
id: detail.id,
name: detail.name || '',
remarks: detail.remarks || '',
original_price: this.normalizeNumber(detail.original_price),
selling_price: this.normalizeNumber(detail.selling_price),
time: this.normalizeNumber(detail.time) || 1,
status: detail.status !== undefined ? Number(detail.status) : 1,
}
this.show_modal = true
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
} catch (error) {
this.$Message.error('获取会员详情失败,请稍后重试')
}
},
// 数字归一化
normalizeNumber(value) {
const numberValue = Number(value)
return Number.isNaN(numberValue) ? 0 : numberValue
},
// 取消操作
cancel() {
this.show_modal = false
this.formValidate = createDefaultVipForm()
this.$nextTick(() => {
if (this.$refs.formValidate) {
this.$refs.formValidate.clearValidate()
}
})
},
// 确认提交
async ok() {
const valid = await new Promise(resolve => {
if (!this.$refs.formValidate) {
resolve(false)
return
}
this.$refs.formValidate.validate(result => resolve(result))
})
if (!valid) {
this.$Message.error('请完善表单后再提交')
return
}
this.submitLoading = true
const payload = this.buildSubmitPayload()
try {
if (payload.id) {
const res = await getVipUpdate(payload)
this.$Message.success(res.msg || '更新成功')
} else {
const res = await addVip(payload)
this.$Message.success(res.msg || '新增成功')
}
this.cancel()
this.getList()
} catch (error) {
this.$Message.error(error.msg || '提交失败,请稍后重试')
} finally {
this.submitLoading = false
}
},
// 组装提交参数
buildSubmitPayload() {
const payload = { ...this.formValidate }
payload.original_price = Number(payload.original_price)
payload.selling_price = Number(payload.selling_price)
payload.time = Number(payload.time)
payload.status = Number(payload.status)
return payload
},
// 删除会员配置
async remove(id) {
if (!id) {
return
}
const confirm = await new Promise(resolve => {
this.$Modal.confirm({
title: '确认删除',
content: '删除后不可恢复,是否继续?',
onOk: () => resolve(true),
onCancel: () => resolve(false),
})
})
if (!confirm) {
return
}
try {
const res = await getVipDelete({ id })
this.$Message.success(res.msg || '删除成功')
if (this.data.length === 1 && this.page > 1) {
this.page -= 1
}
this.getList()
} catch (error) {
this.$Message.error(error.msg || '删除失败,请稍后重试')
}
},
},
}
</script>
<style>
<style scoped>
.container {
display: flex;
flex-direction: column;
height: 100vh;
padding: 20px;
box-sizing: border-box;
}
.action-btn {
margin: 20px 0;
background-color: white;
padding: 20px;
border: 1px solid #F3F7FD;
border-radius: 15px;
display: flex;
align-items: center;
button {
margin-right: 20px;
}
}
.table-container {
flex: 1;
display: flex;
flex-direction: column;
}
.page {
margin-top: 10px;
text-align: right;
}
.search-area {
background-color: white;
padding-top: 20px;
border: 1px solid #F3F7FD;
border-radius: 15px;
}
.ellipsis {
display: inline-block;
max-width: 160px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -3,10 +3,14 @@
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="用户手机号">
<Input type="text" v-model="formInline.phone" placeholder="" />
<Input type="text" clearable v-model="formInline.phone" placeholder="" />
</FormItem>
<FormItem prop="order_number" label="订单编号">
<Input type="text" clearable v-model="formInline.order_number" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="handleSubmit('formInline')"><Icon type="ios-search" />搜索</Button>
<Button style="margin-left: 8px" @click="handleReset('formInline')"><Icon type="md-refresh" />重置</Button>
</FormItem>
</Form>
</div>
@@ -52,7 +56,9 @@
</template>
<script>
import { getChargeOrder } from '@/api'
import {
getChargeOrder,
} from '@/api'
export default {
name: 'charging_station',
@@ -64,12 +70,11 @@ export default {
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
phone: '',
order_number: '',
},
ruleInline: {},
columns: [
{
columns: [{
title: 'id',
key: 'order_id',
width: '80',
@@ -126,7 +131,11 @@ export default {
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
await getChargeOrder({ page: this.page, pageSize: this.pageSize, ...this.formInline }).then((res) => {
await getChargeOrder({
page: this.page,
pageSize: this.pageSize,
...this.formInline,
}).then((res) => {
this.total = res.total
this.data = res.data
this.page = res.current_page
@@ -135,8 +144,12 @@ export default {
handleSubmit() {
this.getList()
},
handleReset(name) {
this.$refs[name].resetFields()
handleReset() {
this.formInline = {
phone: '',
order_number: '',
}
this.getList()
},
show(index) {
this.$Modal.info({
@@ -177,6 +190,7 @@ export default {
border-radius: 15px;
display: flex;
align-items: center;
button {
margin-right: 20px;
}
@@ -192,6 +206,7 @@ export default {
margin-top: 10px;
text-align: right;
}
.search-area {
background-color: white;
padding-top: 20px;

View File

@@ -2,17 +2,26 @@
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="用户">
<Input type="text" v-model="formInline.user" placeholder="" />
<FormItem prop="openid" label="openid">
<Input type="text" v-model="formInline.openid" placeholder="" />
</FormItem>
<FormItem prop="user" label="类型">
<Input type="text" v-model="formInline.user" placeholder="" />
<FormItem prop="phone" label="手机号">
<Input type="text" v-model="formInline.phone" placeholder="" />
</FormItem>
<FormItem prop="user" label="起止时间">
<Input type="text" v-model="formInline.user" placeholder="" />
<FormItem prop="type" label="类型">
<Select v-model="formInline.type" style="width:200px">
<Option value="1">充电订单</Option>
<Option value="2">充值</Option>
<Option value="3">提现</Option>
</Select>
</FormItem>
<FormItem prop="" label="起止时间">
<DatePicker type="daterange" placement="bottom-end" placeholder="请选择时间" style="width: 200px"
@on-change="chage" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="handleSubmit('formInline')"><Icon type="ios-search" />搜索</Button>
<Button style="margin-left: 8px" @click="resetSearch"><Icon type="md-refresh" />重置</Button>
</FormItem>
</Form>
</div>
@@ -30,7 +39,9 @@
</template>
<script>
import { getCaiWuData } from '@/api'
import {
getCaiWuData,
} from '@/api'
export default {
name: 'charging_station',
@@ -42,8 +53,11 @@
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
openid: '',
type: '',
start_time: '',
end_time: '',
phone: '',
},
ruleInline: {},
columns: [{
@@ -54,6 +68,10 @@
title: '用户',
key: 'openid',
},
{
title: '手机号',
key: 'phone',
},
{
title: '类型',
key: 'type_text',
@@ -100,23 +118,25 @@
await getCaiWuData({
page: this.page,
pageSize: this.pageSize,
...this.formInline,
}).then((res) => {
this.total = res.total
this.data = res.data
this.page = res.current_page
})
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
}
})
handleSubmit() {
this.getList()
},
handleReset(name) {
this.$refs[name].resetFields()
resetSearch(name) {
this.formInline = {
openid: '',
type: '',
start_time: '',
end_time: '',
phone: '',
}
this.getList()
},
show(index) {
this.$Modal.info({
@@ -137,6 +157,12 @@
this.getList()
}
},
chage(e) {
this.formInline.start_time = ''
this.formInline.end_time = ''
this.formInline.start_time = e[0]
this.formInline.end_time = e[1]
},
},
}
</script>

View File

@@ -1,9 +1,309 @@
<template>
<view>11</view>
<div>
<div class="search-area">
<Form ref="formInline" :model="formInline" inline :label-width="120">
<FormItem prop="user" label="时间">
<Select v-model="formInline.time_type" clearable style="width:200px">
<Option :value="1"></Option>
<Option :value="4"></Option>
<Option :value="2"></Option>
<Option :value="3"></Option>
<Option :value="5">自定义</Option>
</Select>
<DatePicker v-if="formInline.time_type == 1" :value="formInline.time" type="date"
placeholder="请选择日期" style="width: 200px" @on-change="chage" />
<DatePicker v-if="formInline.time_type == 2" type="month" placeholder="请选择日期" style="width: 200px"
@on-change="chage" />
<DatePicker v-if="formInline.time_type == 3" type="year" placeholder="请选择日期" style="width: 200px"
@on-change="chage" />
<DatePicker v-if="formInline.time_type == 4" type="daterange" show-week-numbers
placement="bottom-end" placeholder="请选择日期" style="width: 200px" @on-change="chage" />
<DatePicker v-if="formInline.time_type == 5" type="daterange" placement="bottom-end"
placeholder="请选择日期" style="width: 200px" @on-change="chage" />
</FormItem>
<FormItem prop="password" label="充电站">
<Select v-model="formInline.charge_station_id" style="width:200px" clearable
@on-change="changeStation">
<Option v-for="item in stationList" :value="item.charge_station_id"
:key="item.charge_station_id">{{ item.charge_station_name }}</Option>
</Select>
</FormItem>
<FormItem prop="password" label="充电桩">
<Select v-model="formInline.connectorID" style="width:200px" clearable>
<Option v-for="item in pileList" :value="item.ConnectorID" :key="item.charge_pile_id">
{{ item.ConnectorID }}
</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div id="main" ref="chartContainer" style="width: 100%; height: 400px;"></div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #action="{ row, index }">
<!-- <Button type="primary" size="small" style="margin-right: 5px" @click="show(index)">详情</Button> -->
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" @on-change="onChangePage"
@on-page-size-change="onChangePageSize" />
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import {
getChargePile,
getStationList,
getPileList,
} from '@/api'
export default {
name: 'Total',
data() {
return {
data: [],
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
chartInstance: null,
formInline: {
charge_station_id: '',
time_type: 1,
connectorID: '',
time: '',
},
stationList: [],
pileList: [],
columns: [{
title: '充电桩编号',
key: 'ConnectorID',
},
{
title: '充电站名称',
key: 'charge_station_name',
},
{
title: '总电量',
key: 'totalPower',
},
{
title: '总时间',
key: 'totalTimeFormatted',
},
{
title: '总费用',
key: 'totalPrice',
},
],
}
},
mounted() {
this.initChart()
this.getStationList()
this.getPileList()
},
beforeDestroy() {
if (this.chartInstance) {
this.chartInstance.dispose()
}
window.removeEventListener('resize', this.handleResize)
},
methods: {
async initChart() {
if (!this.$refs.chartContainer) return
// 销毁旧实例
if (this.chartInstance) {
this.chartInstance.dispose()
}
this.formInline.time = new Date().toLocaleDateString().replace('///g', '-')
this.chartInstance = echarts.init(this.$refs.chartContainer)
const xAxisData = []
const powerData = []
const priceData = []
const timeData = []
await getChargePile({
...this.formInline,
}).then((res) => {
this.data = res
this.$nextTick()
res.forEach((i) => {
xAxisData.push(i.no)
powerData.push(parseFloat(i.totalPower))
priceData.push(parseFloat(i.totalPrice))
timeData.push(parseFloat(i.totalTimeFormatted))
})
})
const option = {
color: ['#409eff', '#67C23A', '#E6A23C'],
grid: {
left: 5,
right: 15,
top: 40,
bottom: 5,
containLabel: true,
},
title: {
text: '充电量、费用及时长统计',
left: 'center',
},
tooltip: {
trigger: 'axis',
formatter: (params) => {
const [power, price, time] = params
return `
${power.axisValue}<br>
${power.seriesName}${power.data} KW<br>
${price.seriesName}${price.data} 元<br>
${time.seriesName}${time.data} 小时
`
},
},
legend: {
data: ['总电量', '总费用', '总时长'],
right: 80, // 距离右侧 10px
top: 10, // 垂直居中
},
xAxis: {
type: 'category',
data: xAxisData,
},
yAxis: [{
type: 'value',
name: '电量/KW',
axisLabel: {
formatter: '{value} KW',
},
},
{
type: 'value',
name: '费用/元',
axisLabel: {
formatter: '{value} 元',
},
min: 0,
max: (value) => Math.ceil(value.max / 100) * 100, // 自动调整为100的整数倍
interval: 100, // 每100元一个刻度
splitNumber: 5, // 建议5-7个刻度
axisLine: {
lineStyle: {
color: '#EE6666', // 设置轴线颜色与对应系列一致
},
},
},
],
series: [{
name: '总电量',
type: 'line',
smooth: true,
label: {
show: true,
position: 'top',
},
lineStyle: {
width: 3,
},
data: powerData,
},
{
name: '总费用',
type: 'line',
smooth: true,
label: {
show: true,
position: 'top',
},
lineStyle: {
width: 3,
},
data: priceData,
},
{
name: '总时长',
type: 'line',
smooth: true,
label: {
show: true,
position: 'top',
},
lineStyle: {
width: 3,
},
yAxisIndex: 1,
data: timeData,
},
],
}
this.chartInstance.setOption(option)
// 响应式调整
window.addEventListener('resize', this.handleResize)
},
handleResize() {
if (this.chartInstance) {
this.chartInstance.resize()
}
},
async getStationList() {
await getStationList({
page: 1,
pageSize: 999,
}).then((res) => {
this.stationList = res.data.data
})
},
async getPileList() {
await getPileList({
page: 1,
pageSize: 999,
charge_station_id: this.formInline.charge_station_id,
}).then((res) => {
this.pileList = res.data.data
})
},
chage(e) {
this.formInline.time = ''
this.formInline.start_time = ''
this.formInline.end_time = ''
if (this.formInline.time_type == 4 || this.formInline.time_type == 5) {
this.formInline.start_time = e[0]
this.formInline.end_time = e[1]
} else {
this.formInline.time = e
}
},
handleSubmit() {
this.initChart()
},
changeStation() {
this.getPileList()
},
},
}
</script>
<style>
<style scoped>
#main {
background: #fff;
border-radius: 8px;
padding: 20px;
margin: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.search-area {
background-color: white;
border: 1px solid #F3F7FD;
border-radius: 15px;
}
</style>

View File

@@ -2,17 +2,40 @@
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="站点">
<Input type="text" v-model="formInline.user" placeholder="" />
<div>
<FormItem prop="user" label="报表类型">
<Space direction="vertical" size="large">
<RadioGroup v-model="formInline.animal" @on-change="changeRadio">
<Radio :label="1">充电桩分类运营统计</Radio>
<Radio :label="2">充电桩单桩统计</Radio>
<Radio :label="3" disabled>VIN充电统计</Radio>
<Radio :label="4" disabled>二维码充电统计</Radio>
<Radio :label="5">充电订单流水</Radio>
</RadioGroup>
</Space>
</FormItem>
<FormItem prop="user" label="充电桩">
<Input type="text" v-model="formInline.user" placeholder="" />
</div>
<FormItem prop="user" label="站点">
<Select v-model="formInline.charge_station_id" style="width:200px" @on-change="changeStation">
<Option v-for="item in stationData" :value="item.charge_station_id"
:key="item.charge_station_id">{{ item.charge_station_name }}</Option>
</Select>
</FormItem>
<FormItem prop="user" label="充电桩" v-if="formInline.animal == 2">
<Select v-model="formInline.charge_pile_id" style="width:200px">
<Option v-for="item in pileData" :value="item.charge_pile_id" :key="item.charge_pile_id">
{{ item.ConnectorID }}
</Option>
</Select>
</FormItem>
<FormItem prop="user" label="起止时间">
<Input type="text" v-model="formInline.user" placeholder="" />
<DatePicker type="daterange" placement="bottom-end" placeholder="请选择时间" style="width: 200px"
@on-change="chage" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="handleSubmit('formInline')"><Icon type="ios-search" />生成报表</Button>
<Button style="margin-left: 8px" @click="resetSearch"><Icon type="md-refresh" />重置</Button>
<!-- <Button style="margin-left: 8px" @click="report"><Icon type="md-arrow-down" />导出报表</Button> -->
</FormItem>
</Form>
</div>
@@ -30,7 +53,11 @@
</template>
<script>
import { getYunYingData } from '@/api'
import {
getYunYingData,
getStationList,
getPileList,
} from '@/api'
export default {
name: 'charging_station',
@@ -42,11 +69,15 @@
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
animal: 1,
charge_station_id: '',
charge_pile_id: '',
start_time: '',
end_time: '',
},
ruleInline: {},
columns: [{
columns: [],
columns_1: [{
title: '充电站名称',
key: 'charge_station_name',
},
@@ -93,11 +124,90 @@
align: 'center',
},
],
columns_2: [{
title: '充电桩ID',
key: 'charge_pile_id',
},
{
title: '桩名称',
key: 'ConnectorID',
},
{
title: '充电站名称',
key: 'charge_station_name',
},
{
title: '枪数量',
key: 'pile_num',
},
{
title: '充电次数(次)',
key: 'order_num',
},
{
title: '充电电量(度)',
key: 'power',
},
{
title: '充电金额(元)',
key: 'money',
},
{
title: '电费(元)',
key: 'elec',
},
{
title: '服务费(元)',
key: 'sevice',
},
{
title: '总时长(小时)',
key: 'length',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
columns_5: [{
title: '充电订单号',
key: 'order_number',
},
{
title: '充电站名称',
key: 'charge_station_name',
},
{
title: '桩名称',
key: 'ConnectorID',
},
{
title: '充电停止原因',
key: 'stop_type_text',
},
{
title: '车架号',
key: 'vin',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
phone: '',
stationData: [],
pileData: [],
}
},
mounted() {
this.getList()
this.getStationList()
this.getPileList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
@@ -113,25 +223,51 @@
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
if (this.formInline.animal == 1) {
this.columns = this.columns_1
} else if (this.formInline.animal == 2) {
this.columns = this.columns_2
} else if (this.formInline.animal == 5) {
this.columns = this.columns_5
}
await getYunYingData({
page: this.page,
pageSize: this.pageSize,
...this.formInline,
}).then((res) => {
this.total = res.data.total
this.data = res.data.data
})
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
}
async getStationList() {
await getStationList({
page: 1,
pageSize: 999,
}).then((res) => {
this.stationData = res.data.data
})
},
handleReset(name) {
this.$refs[name].resetFields()
async getPileList() {
await getPileList({
page: 1,
pageSize: 999,
charge_station_id: this.formInline.charge_station_id,
}).then((res) => {
this.pileData = res.data.data
})
},
handleSubmit() {
this.getList()
},
resetSearch() {
this.formInline = {
animal: 1,
charge_station_id: '',
charge_pile_id: '',
start_time: '',
end_time: '',
}
this.getList()
},
show(index) {
this.$Modal.info({
@@ -152,6 +288,24 @@
this.getList()
}
},
changeStation() {
if (this.formInline.animal == 2) {
this.getPileList()
}
},
chage(e) {
this.formInline.start_time = ''
this.formInline.end_time = ''
this.formInline.start_time = e[0]
this.formInline.end_time = e[1]
},
changeRadio(e) {
this.page = 1
this.pageSize = 10
},
report() {
this.$Message.info('测试中')
},
},
}
</script>

View File

@@ -196,17 +196,10 @@
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 230
console.log(this.tableHeight)
},
async fetchRoles() {
// 角色列表仅需偶尔变动,进入页面时加载一次即可
try {
const res = await getRoleList()
const list = res.data || res
this.roleOptions = Array.isArray(list) ? list : []
} catch (error) {
this.roleOptions = []
}
this.roleOptions = res.data.data
},
async getList() {
this.tableLoading = true

View File

@@ -17,11 +17,13 @@
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #action="{ row, index }">
<Button type="primary" size="small" style="margin-right: 5px" @click="show(index)">编辑</Button>
<Button type="error" size="small" @click="remove(index)">删除</Button>
<Button type="primary" size="small" style="margin-right: 5px" @click="show(row.id)"
v-if="row.id != 1">编辑</Button>
<Button type="error" size="small" @click="remove(row.id)" v-if="row.id != 1">删除</Button>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
<Page :total="total" show-total show-sizer class="page" @on-change="onChangePage"
@on-page-size-change="onChangePageSize" />
</div>
@@ -33,12 +35,12 @@
</FormItem>
<FormItem label="所属上级" prop="pid">
<Select v-model="formValidate.pid" style="width:200px">
<Option :value="0"></Option>
<Option v-for="item in roles_data" :value="item.id" :key="item.id">{{ item.name }}</Option>
<Option v-for="item in data" :value="item.id" :key="item.id">{{ item.name }}</Option>
</Select>
</FormItem>
<FormItem label="权限菜单" prop="roles">
<Tree :data="menu_data" ref="tree" show-checkbox style="height: 300px;overflow-y: auto;"></Tree>
<Tree :data="menu_data" :checked-keys="defaultCheckedKeys" ref="tree" show-checkbox
style="height: 300px;overflow-y: auto;"></Tree>
</FormItem>
</Form>
<div slot="footer">
@@ -53,6 +55,10 @@
import {
GetRole,
getMenuList,
addRole,
getRoleRead,
getRoleUpdate,
getRoleDelete,
} from '@/api'
export default {
@@ -60,6 +66,8 @@
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
@@ -84,6 +92,7 @@
],
data: [],
formValidate: {
id: '',
name: '',
pid: 0,
roles: '',
@@ -103,38 +112,13 @@
value: 'New York',
label: 'New York',
}],
menu_data: [{
title: 'parent 1',
expand: false,
children: [{
title: 'parent 1-1',
expand: true,
children: [{
title: 'leaf 1-1-1',
id: 1,
},
{
title: 'leaf 1-1-2',
},
],
},
{
title: 'parent 1-2',
expand: true,
children: [{
title: 'leaf 1-2-1',
},
{
title: 'leaf 1-2-1',
},
],
},
],
}],
menu_data: [],
defaultCheckedKeys: [],
}
},
mounted() {
this.getList()
this.getMenuList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
@@ -152,11 +136,11 @@
},
async getList() {
await GetRole({
page: 1,
page: this.page,
pageSize: this.pageSize,
}).then((res) => {
this.total = res.total
this.data = res.data
this.page = res.current_page
this.total = res.data.total
this.data = res.data.data
})
},
handleSubmit(name) {
@@ -168,14 +152,36 @@
}
})
},
show(index) {
this.$Modal.info({
title: 'User Info',
content: `Name${this.data[index].name}<br>Age${this.data[index].age}<br>Address${this.data[index].address}`,
async show(id) {
await getRoleRead({
id,
}).then((res) => {
this.show_modal = true
this.formValidate = {
...res.data,
}
this.getMenuList()
})
},
remove(index) {
this.data.splice(index, 1)
async remove(id) {
const confirm = await new Promise(resolve => {
this.$Modal.confirm({
title: '确认删除',
content: '您确定要删除此项吗?',
onOk: () => resolve(true),
onCancel: () => resolve(false),
})
})
if (!confirm) {
this.$Message.info('已取消删除')
return
}
await getRoleDelete({
id,
}).then((res) => {
this.$Message.success(res.msg)
this.getList()
})
},
async ok() {
let menuIds = this.$refs.tree.getCheckedAndIndeterminateNodes()
@@ -185,22 +191,58 @@
this.$Message.error('请正确填写表单')
return
}
// if (this.formValidate.id) { } else { }
this.$Message.info('Clicked ok')
if (this.formValidate.id) {
await getRoleUpdate(this.formValidate).then((res) => {
this.$Message.success(res.msg)
this.getList()
this.cancel()
})
} else {
await addRole(this.formValidate).then((res) => {
this.$Message.success(res.msg)
this.getList()
this.cancel()
})
}
},
cancel() {
this.show_modal = false
this.$refs.formValidate.resetFields()
this.formValidate = {
id: '',
name: '',
pid: 0,
roles: '',
}
},
async add() {
this.show_modal = true
await this.getMenuList()
this.formValidate = {
id: '',
name: '',
pid: 0,
roles: '',
}
},
async getMenuList() {
await getMenuList().then((res) => {
await getMenuList({
roles: this.formValidate.roles,
}).then((res) => {
this.menu_data = res
})
},
onChangePage(e) {
if (this.page != e) {
this.page = e
this.getList()
}
},
onChangePageSize(e) {
if (this.pageSize != e) {
this.page = 1
this.pageSize = e
this.getList()
}
},
},
}
</script>