This commit is contained in:
MeSHard
2025-11-10 16:14:42 +08:00
parent 709d6b0399
commit 89369e9cf6
69 changed files with 46811 additions and 0 deletions

2
.browserslistrc Normal file
View File

@@ -0,0 +1,2 @@
> 1%
last 2 versions

7
.editorconfig Normal file
View File

@@ -0,0 +1,7 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 140

67
.eslintrc.js Normal file
View File

@@ -0,0 +1,67 @@
module.exports = {
root: false,
env: {
node: true,
},
extends: [
'plugin:vue/essential',
'@vue/airbnb',
],
rules: {
'indent': 'off',
'no-tabs': 'off', // 禁用 no-tabs 规则
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'array-element-newline': ['error', 'consistent'],
// 'indent': ['error',4,{ 'MemberExpression': 0, 'SwitchCase': 1 }],
'quotes': ['error', 'single'],
'comma-dangle': ['error', 'always-multiline'],
'semi': ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
'max-len': ['error', 140],
'no-new': 'off',
'linebreak-style': 'off',
'import/extensions': 'off',
'eol-last': 'off',
'no-shadow': 'off',
'no-unused-vars': 'warn',
'import/no-cycle': 'off',
'arrow-parens': 'off',
'eqeqeq': 'off',
'no-param-reassign': 'off',
'import/prefer-default-export': 'off',
'no-use-before-define': 'off',
'no-continue': 'off',
'prefer-destructuring': 'off',
'no-plusplus': 'off',
'prefer-const': 'off',
'global-require': 'off',
'no-prototype-builtins': 'off',
'consistent-return': 'off',
'vue/require-component-is': 'off',
'prefer-template': 'off',
'one-var-declaration-per-line': 'off',
'one-var': 'off',
'import/named': 'off',
'object-curly-newline': 'off',
'default-case': 'off',
'import/no-dynamic-require': 'off',
'vue/no-parsing-error': [2, {
'x-invalid-end-tag': false
}],
},
parserOptions: {
parser: 'babel-eslint',
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
jest: true,
},
},
],
}

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 bin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
],
};

BIN
dist.zip Normal file

Binary file not shown.

3
jest.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
};

39300
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "vue-admin-template",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "vue-cli-service test:unit"
},
"dependencies": {
"axios": "^0.19.0",
"core-js": "^3.31.0",
"echarts": "^6.0.0",
"element-ui": "^2.15.14",
"view-design": "^4.0.2",
"view-ui-plus": "^1.3.21",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-unit-jest": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"@vue/eslint-config-airbnb": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "^10.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>爱京科技</title>
</head>
<body>
<noscript>
<strong>We're sorry but new doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

61
src/App.vue Normal file
View File

@@ -0,0 +1,61 @@
<template>
<div id="app">
<router-view/>
<div class="global-loading" v-show="isShowLoading">
<Spin size="large"></Spin>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'App',
data() {
return {
keepAliveData: ['manage'],
}
},
computed: {
...mapState([
'isShowLoading',
]),
},
}
</script>
<style>
body {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
li, ul, p, div, body, html, table {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
}
li {
list-style: none;
}
#app {
height: 100%;
}
/* loading */
.global-loading {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(255,255,255,.5);
display: flex;
justify-content: center;
align-items: center;
}
</style>

427
src/api/index.js Normal file
View File

@@ -0,0 +1,427 @@
import request from '@/utils/request'
export function fetchUserData() {
return request.get('https://api.github.com/users/woai3c')
}
// 登录
export function login(params) {
return request.post('/login_check', params)
}
// 修改密码
export function Modify(params) {
return request.post('/admin/update_password', params)
}
// 显示通知
export function showContent(params) {
return request.post('/user/DiscountInfo', params)
}
// 修改通知
export function EditContent(params) {
return request.post('/UpdateDiscount', params)
}
/**
* 用户
*/
// 用户列表
export function GetAdmin(params) {
return request.post('/admin_list', params)
}
// 查询用户
export function searchAdmin(params) {
return request.post('/admin/read', params)
}
// 编辑用户
export function editAdmin(params) {
return request.post('/admin/update', params)
}
// 用户删除
export function deleteAdmin(params) {
return request.post('/admin/delete', params)
}
// 用户新增
export function addAdmin(params) {
return request.post('/admin/save', 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)
}
/**
* 订单
*/
// 潜江-------获取充电订单数据
export function getChargeOrder(params) {
return request.get('/getChargeOrder', { params })
}
// 潜江-------获取充值订单数据
export function getReChargeOrder(params) {
return request.get('/getRechargeOrder', { params })
}
// 获取订单信息
export function GetOrder(params) {
return request.post('/ChargeOrder/read_order', params)
}
// 获取大足地址信息
export function GetpositionData(params) {
return request.post('/ChargeOrder/StreetWithName', params)
}
// 订单号查询订单
export function GetOneOrder(params) {
return request.post('/ChargeOrder/read_order', params)
}
// 获取充电桩信息
export function GetChargeZ(params) {
return request.post('/ChargeOrder/read_equipment', params)
}
// 获取总量
export function GetTotal(params) {
return request.post('/ChargeOrder/read_total', params)
}
// 按照时间查询
export function GetTimeData(params) {
return request.post('/ChargeStation/Time_Data', params)
}
// 充值按照时间查询
export function ReGetTimeData(params) {
return request.post('/ChargeStation/Recharge_Total', params)
}
// 导出充电站信息
export function exportstationAll(params) {
return request.post('/ChargeStation/station_all', params)
}
// 导出订单
export function exportOrderAll(params) {
return request.post('/OrderExport', params)
}
// 导出充值
export function exportRecharge(params) {
return request.post('/RechargeAllExport', params)
}
// 按照范围查询
export function GetDatabyRound(params) {
return request.post('/ChargeStation/Order_Total', params)
}
// 按照范围查询
export function GetTotalround(params) {
return request.post('/ChargeStation/Time_Data_Total', params)
}
// 充值记录
export function GetRecharge(params) {
return request.post('/RechargeAll', params)
}
// 订单模糊查询
export function GetFuzzyOrder(params) {
return request.post('/FuzzyOrder', params)
}
// 订单模糊查询2
export function GetFuzzyOrder1(params) {
return request.post('/FuzzyOrder', params)
}
// 订单查询
export function GettheOrder(params) {
return request.post('/FuzzyOrder', params)
}
// /尖峰
export function Electricity(params) {
return request.post('/Electricity', params)
}
// /尖峰
export function ElectricityStation(params) {
return request.post('/Electricity', params)
}
export function FindRechargeOrder(params) {
return request.post('/FindRechargeOrder', params)
}
export function RefundQuery(params) {
return request.post('/RefundQuery', params)
}
export function RefundQuery1(params) {
return request.post('/RefundQuery', params)
}
// 退款可视化
export function RefundTotal(params) {
return request.post('/Refund_Total', params)
}
// 收支报表
export function AmountExcel(params) {
return request.post('/AmountExcel', params)
}
/**
* 车辆
*/
// 公交车报表
export function SearchBusMessage(params) {
return request.post('/Bus/BusOrderPark', params)
}
// 公交车总报表
export function SearchtotalBusMessage(params) {
return request.post('/Bus/SearchMessage', params)
}
// 公交车报表单个
export function SearchperBusMessage(params) {
return request.post('/Bus/SearchMessage', params)
}
// 公交车更新余额
export function ChangeSpecialUserBalance(params) {
return request.post('/Bus/ChangeSpecialUserBalance', params)
}
// 公交车汇总表
export function BusDataStatistics(params) {
return request.post('/Bus/DataStatistics', params)
}
// 展示VIN和车牌号
export function ShowVinLicense(params) {
return request.post('/Bus/ShowVinLicense', params)
}
export function ShowVinLicense1(params) {
return request.post('/Bus/ShowVinLicense', params)
}
// 绑定车辆和车牌号
export function AddVinLicense(params) {
return request.post('/Bus/AddVinLicense', params)
}
// 删除车辆和车牌号
export function DeleteVinLicense(params) {
return request.post('/Bus/DeleteVinLicense', params)
}
// 编辑车辆信息
export function UpdateVinLicense(params) {
return request.post('/Bus/UpdateVinLicense', params)
}
// 车辆列表
export function ParkNoShow(params) {
return request.post('/Bus/ParkNo', params)
}
/**
* event
*/
// 活动列表
export function ShowEvent(params) {
return request.post('/ShowEvent', params)
}
// 新增列表
export function AddEvent(params) {
return request.post('/AddEvent', params)
}
// 编辑列表
export function EditEvent(params) {
return request.post('/EditEvent', params)
}
// 删除列表
export function DeleteEvent(params) {
return request.post('/DeleteEvent', params)
}
// 监控列表
export function MonitorList(params) {
return request.post('/MonitorList', params)
}
// 查询监控
export function ParkNoShowEvent(params) {
return request.post('/MonitorId', params)
}
/**
* position
*/
// 获取位置信息
export function GetPosition(params) {
return request.post('/ChargeStation/index', params)
}
// 搜索充电站
export function searchStation(params) {
return request.post('/ChargeStation/index', params)
}
// 修改定价
export function ChangeCharge(params) {
return request.post('/StationPriceUpdate', params)
}
// 导出
export function exportAll(params) {
return request.post('/ChargeStation/station_all', params)
}
// 故障
export function Getwarning(params) {
return request.post('/ChargeStation/FaultInfo', params)
}
// 获取电站价格
export function GetPerPrice(params) {
return request.post('/StationPrice', params)
}
/**
* table
*/
// 用户列表
export function GetUser(params) {
return request.post('/UserList', params)
}
// 查询用户
export function searchUser(params) {
return request.post('/user/read', params)
}
// 编辑用户
export function editUser(params) {
return request.post('/user/update', params)
}
// 用户删除
export function deleteUser(params) {
return request.post('/DeleteEnterpriseUser', params)
}
// 导出
export function exportUserAll(params) {
return request.post('/user/user_all', params)
}
// 查订单
export function OrderQuery(params) {
return request.post('/OrderQuery', params)
}
// 获取企业用户组
export function GetGroup(params) {
return request.post('/GetGroup', params)
}
// 获取企业列表
export function getEnterpriseList(params) {
return request.get('/enterprise/index', { params })
}
// 企业列表-添加
export function getEnterpriseSave(params) {
return request.post('/enterprise/save', params)
}
// 企业列表-只读
export function getEnterpriseRead(params) {
return request.post('/enterprise/read', params)
}
// 企业列表-编辑
export function getEnterpriseUpdate(params) {
return request.post('/enterprise/update', params)
}
// 企业列表-删除
export function getEnterpriseDelete(params) {
return request.post('/enterprise/delete', params)
}
// 获取企业用户列表
export function getEnterpriseUserList(params) {
return request.get('/enterpriseUser/index', { params })
}
// 企业用户-添加
export function getEnterpriseUserSave(params) {
return request.post('/enterpriseUser/save', params)
}
// 企业用户-只读
export function getEnterpriseUserRead(params) {
return request.post('/enterpriseUser/read', params)
}
// 企业用户-编辑
export function getEnterpriseUserUpdate(params) {
return request.post('/enterpriseUser/update', params)
}
// 企业用户-删除
export function getEnterpriseUserDelete(params) {
return request.post('/enterpriseUser/delete', params)
}
// 获取企业车辆列表
export function getEnterpriseCarList(params) {
return request.get('/enterpriseCar/index', { params })
}
// 企业车辆-添加
export function getEnterpriseCarSave(params) {
return request.post('/enterpriseCar/save', params)
}
// 企业车辆-只读
export function getEnterpriseCarRead(params) {
return request.post('/enterpriseCar/read', params)
}
// 企业车辆-编辑
export function getEnterpriseCarUpdate(params) {
return request.post('/enterpriseCar/update', params)
}
// 企业车辆-删除
export function getEnterpriseCarDelete(params) {
return request.post('/enterpriseCar/delete', params)
}
// 企业组搜索用户
export function NormalSearch(params) {
return request.post('/NormalSearch', params)
}
// 查询企业
export function EnterpriseGroupList(params) {
return request.post('/EnterpriseGroupAdd', params)
}
// 新增企业成员
export function NormalToEnterprise(params) {
return request.post('/NormalToEnterprise', params)
}
// 删除组
export function DeleteEnterpriseGroup(params) {
return request.post('/DeleteEnterpriseGroup', params)
}
// 电话查找
export function PhoneSearch(params) {
return request.post('/PhoneSearch', params)
}
// 修改组
export function EnterpriserUserEdit(params) {
return request.post('/EnterpriserUserEdit', params)
}
// 查询服务费
export function ShowServiceFee(params) {
return request.post('/ShowServiceFee', params)
}
// 更新服务费
export function UpdateServiceFee(params) {
return request.post('/UpdateServiceFee', params)
}
// 查看该用户充值详情
export function RechargeMessage(params) {
return request.post('/RechargeMessage', params)
}
// 查看该用户提现详情
export function RefundMessage(params) {
return request.post('/RefundMessage', params)
}
// 显示预约时间
export function ShowAppointmentTime(params) {
return request.post('/ShowAppointmentTime', params)
}
// 修改预约时间
export function ModifyAppointmentTime(params) {
return request.post('/ModifyAppointmentTime', params)
}
// 活动
export function getEventList(params) {
return request.post('/eventList2', params)
}
// 退款订单
export function getRefundOrder(params) {
return request.get('/getRefundOrder', { params })
}
// 权限
export function GetRole(params) {
return request.get('/getRoleList', { params })
}
// 权限
export function getMenuList(params) {
return request.get('/getMenuList', { params })
}
// 日志
export function getLogList(params) {
return request.get('/getLogList', { params })
}
// 运营数据
export function getYunYingData(params) {
return request.get('/getYunYingData', { params })
}
// 财务数据
export function getCaiWuData(params) {
return request.get('/getCaiWuData', { params })
}

BIN
src/assets/imgs/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/imgs/bg00.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

BIN
src/assets/imgs/bg01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

BIN
src/assets/imgs/bg02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
src/assets/imgs/bg03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

BIN
src/assets/imgs/bg04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

BIN
src/assets/imgs/bg05.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

BIN
src/assets/imgs/bg06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

BIN
src/assets/imgs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
src/assets/imgs/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

49
src/components/404.vue Normal file
View File

@@ -0,0 +1,49 @@
<template>
<div>
<img :src="img">
<p>未到找指定页面</p>
<Button class="back" @click="back">返回页面</Button>
</div>
</template>
<script>
export default {
name: 'error',
data() {
return {
img: require('../assets/imgs/404.jpg'),
}
},
methods: {
back() {
this.$router.back()
},
},
}
</script>
<style scoped>
div {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #f8f5ec;
}
img {
display: block;
margin: auto;
}
p {
font-size: 40px;
text-align: center;
margin-bottom: 100px;
}
.back {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
}
</style>

702
src/components/Index.vue Normal file
View File

@@ -0,0 +1,702 @@
<template>
<div class="index-vue">
<!-- 侧边栏 -->
<aside :class="asideClassName">
<!-- logo -->
<div class="logo-c">
<img src="../assets/imgs/logo.png" alt="logo" class="logo">
<span v-show="isShowAsideTitle">潜江市充电桩</span>
</div>
<!-- 菜单栏 -->
<Menu class="menu" ref="asideMenu" theme="dark" width="100%" @on-select="selectMenuCallback"
accordion :open-names="openMenus" :active-name="currentPage" @on-open-change="menuChange">
<!-- 动态菜单 -->
<div v-for="(item, index) in menuItems" :key="index">
<Submenu :class="isShowAsideTitle? '' : 'shrink'" v-if="item.children" :name="index">
<template slot="title">
<Icon :size="item.size" :type="item.type"/>
<span v-show="isShowAsideTitle">{{item.text}}</span>
</template>
<div v-for="(subItem, i) in item.children" :key="index + i">
<Submenu :class="isShowAsideTitle? '' : 'shrink'" v-if="subItem.children" :name="index + '-' + i">
<template slot="title">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</template>
<template v-for="(threeItem, k) in subItem.children">
<a href="https://www.baidu.com" target="_blank" :key="index + i + k" v-if="threeItem.isExternal">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'" class="menu-level-3"
:name="'external-link-' + index + i + k">
<template v-if="!threeItem.hidden">
<a :href="threeItem.url" target="_blank" class="external">
<Icon :size="threeItem.size" :type="threeItem.type"/>
<span v-show="isShowAsideTitle">{{threeItem.text}}</span>
</a>
</template>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" class="menu-level-3"
:name="threeItem.name" :key="index + i + k">
<template v-if="!threeItem.hidden">
<Icon :size="threeItem.size" :type="threeItem.type"/>
<span v-show="isShowAsideTitle">{{threeItem.text}}</span>
</template>
</MenuItem>
</template>
</Submenu>
<template v-else-if="!subItem.hidden">
<a :href="subItem.url" v-if="subItem.isExternal" target="_blank" class="external">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'"
:name="'external-link-' + index + '-' + i">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" :name="subItem.name">
<Icon :size="subItem.size" :type="subItem.type"/>
<span v-show="isShowAsideTitle">{{subItem.text}}</span>
</MenuItem>
</template>
</div>
</Submenu>
<template v-else-if="!item.hidden">
<a :href="item.url" v-if="item.isExternal" target="_blank" class="external">
<MenuItem :class="isShowAsideTitle? '' : 'shrink'" :name="'external-link-' + index">
<Icon :size="item.size" :type="item.type"/>
<span v-show="isShowAsideTitle">{{item.text}}</span>
</MenuItem>
</a>
<MenuItem v-else :class="isShowAsideTitle? '' : 'shrink'" :name="item.name">
<Icon :size="item.size" :type="item.type" />
<span v-show="isShowAsideTitle">{{item.text}}</span>
</MenuItem>
</template>
</div>
</Menu>
</aside>
<!-- 右侧部分 -->
<section class="sec-right">
<!-- 头部 -->
<div class="top-c">
<header>
<div class="h-left">
<div class="pointer" @click="isShrinkAside" title="收缩/展开">
<Icon type="ios-apps" />
</div>
<!-- 面包屑功能 -->
<p class="crumbs">{{crumbs}}</p>
</div>
<div class="h-right">
<!-- 消息 -->
<div class="notice-c" @click="info" title="查看新消息">
<div :class="{newMsg: hasNewMsg}"></div>
<Icon type="ios-notifications-outline" />
</div>
<!-- 用户头像 -->
<div class="user-img-c">
<img :src="userImg">
</div>
<!-- 下拉菜单 -->
<Dropdown trigger="click" @on-click="userOperate" @on-visible-change="showArrow">
<div class="pointer">
<span>{{userName}}</span>
<Icon v-show="arrowDown" type="md-arrow-dropdown"/>
<Icon v-show="arrowUp" type="md-arrow-dropup"/>
</div>
<DropdownMenu slot="list">
<!-- name标识符 -->
<DropdownItem name="1">修改密码</DropdownItem>
<DropdownItem name="2">基本资料</DropdownItem>
<DropdownItem divided name="3">退出登陆</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</header>
<!-- 标签栏 -->
<div class="div-tags">
<ul class="ul-c">
<li v-for="(item, index) in tagsArry" :key="index" :class="{active: isActive(item.name)}" @click="activeTag(index)">
<a class="li-a">
{{item.text}}
</a>
<Icon size="16" @click="closeTag(index)" type="md-close" />
</li>
</ul>
<!-- 标签栏相关功能 -->
<div class="div-icons">
<div class="refresh-c" @click="reloadPage" title="刷新当前标签页">
<Icon type="md-refresh" />
</div>
<div class="tag-options" title="关闭标签">
<Dropdown trigger="click" @on-click="closeTags">
<Icon type="ios-options" />
<DropdownMenu slot="list">
<DropdownItem name="1">关闭其他标签</DropdownItem>
<DropdownItem name="2">关闭所有标签</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
</div>
</div>
<!-- 页面主体 -->
<div class="main-content">
<div class="view-c">
<keep-alive :include="keepAliveData">
<!-- 子页面 -->
<router-view v-if="isShowRouter"/>
</keep-alive>
</div>
</div>
</section>
</div>
</template>
<script>
import { resetTokenAndClearUser } from '../utils'
export default {
name: 'index',
data() {
return {
// 用于储存页面路径
paths: {},
// 当前显示页面
currentPage: '',
openMenus: [], // 要打开的菜单名字 name属性
menuCache: [], // 缓存已经打开的菜单
hasNewMsg: true, // 是否有新消息
isShowRouter: true,
msgNum: '10', // 新消息条数
// 标签栏 标签标题 路由名称
// 数据格式 {text: '首页', name: 'home'}
// 用于缓存打开的路由 在标签栏上展示
tagsArry: [],
arrowUp: false, // 用户详情向上箭头
arrowDown: true, // 用户详情向下箭头
isShowAsideTitle: true, // 是否展示侧边栏内容
main: null, // 页面主要内容区域
asideClassName: 'aside-big', // 控制侧边栏宽度变化
asideArrowIcons: [], // 缓存侧边栏箭头图标 收缩时用
// 面包屑
crumbs: '主页',
userName: '',
userImg: '',
// 主页路由名称
home: 'home',
}
},
mounted() {
// 第一个标签
const name = this.$route.name
this.currentPage = name
this.tagsArry.push({
text: this.nameToTitle[name],
name,
})
// 根据路由打开对应的菜单栏
this.openMenus = this.getMenus(name)
this.$nextTick(() => {
this.$refs.asideMenu.updateOpened()
})
// 设置用户信息
this.userName = localStorage.getItem('userName')
this.userImg = localStorage.getItem('userImg')
this.main = document.querySelector('.sec-right')
this.asideArrowIcons = document.querySelectorAll('aside .ivu-icon-ios-arrow-down')
// 监听窗口大小 自动收缩侧边栏
this.monitorWindowSize()
},
watch: {
$route(to) {
const name = to.name
this.currentPage = name
if (name == 'error') {
this.crumbs = '404'
return
}
if (!this.keepAliveData.includes(name)) {
// 如果标签超过8个 则将第一个标签删除
if (this.tagsArry.length == 8) {
this.tagsArry.shift()
}
this.tagsArry.push({ name, text: this.nameToTitle[name] })
}
setTimeout(() => {
this.crumbs = this.paths[name]
}, 0)
},
},
computed: {
// 菜单栏
menuItems() {
return this.$store.state.menuItems
},
// 需要缓存的路由
keepAliveData() {
return this.tagsArry.map(item => item.name)
},
// 由于iView的导航菜单比较坑 只能设定一个name参数
// 所以需要在这定义组件名称和标签栏标题的映射表 有多少个页面就有多少个映射条数
nameToTitle() {
const obj = {}
this.menuItems.forEach(e => {
this.processNameToTitle(obj, e)
})
return obj
},
},
methods: {
getMenus(name) {
let menus
const tagTitle = this.nameToTitle[name]
for (let i = 0, l = this.menuItems.length; i < l; i++) {
const item = this.menuItems[i]
menus = []
menus[0] = i
if (item.text == tagTitle) {
return menus
}
if (item.children) {
for (let j = 0, ll = item.children.length; j < ll; j++) {
const child = item.children[j]
menus[1] = i + '-' + j
menus.length = 2
if (child.text == tagTitle) {
return menus
}
if (child.children) {
for (let k = 0, lll = child.children.length; k < lll; k++) {
const grandson = child.children[k]
menus[2] = i + '-' + j + '-' + k
if (grandson.text == tagTitle) {
return menus
}
}
}
}
}
}
},
monitorWindowSize() {
let w = document.documentElement.clientWidth || document.body.clientWidth
if (w < 1300) {
this.shrinkAside()
}
window.onresize = () => {
// 可视窗口宽度太小 自动收缩侧边栏
if (w < 1300 && this.isShowAsideTitle
&& w > (document.documentElement.clientWidth || document.body.clientWidth)) {
this.shrinkAside()
}
w = document.documentElement.clientWidth || document.body.clientWidth
}
},
// 判断当前标签页是否激活状态
isActive(name) {
return this.$route.name === name
},
// 跳转页面 路由名称和参数
gotoPage(name, params) {
this.currentPage = name
this.crumbs = this.paths[name]
this.$router.push({ name, params })
if (!this.keepAliveData.includes(name)) {
// 如果标签超过8个 则将第一个标签删除
if (this.tagsArry.length == 8) {
this.tagsArry.shift()
}
this.tagsArry.push({ name, text: this.nameToTitle[name] })
}
},
// 选择菜单回调函数
selectMenuCallback(name) {
if (name.includes('external-link')) return
this.gotoPage(name)
this.expandAside()
setTimeout(() => {
this.isShowAsideTitle = true
}, 200)
},
// 用户操作
userOperate(name) {
switch (name) {
case '1':
// 修改密码
this.gotoPage('password')
break
case '2':
// 基本资料
this.gotoPage('userinfo')
break
case '3':
resetTokenAndClearUser()
this.$router.push({ name: 'login' })
break
}
},
// 控制用户三角箭头显示状态
showArrow(flag) {
this.arrowUp = flag
this.arrowDown = !flag
},
// 判断
isShrinkAside() {
if (this.isShowAsideTitle) {
this.shrinkAside()
} else {
this.expandAside()
}
},
// 收缩
shrinkAside() {
for (let i = 0, len = this.asideArrowIcons.length; i < len; i++) {
this.asideArrowIcons[i].style.display = 'none'
}
this.isShowAsideTitle = false
this.openMenus = []
this.$nextTick(() => {
if (this.$refs.asideMenu) {
this.$refs.asideMenu.updateOpened()
}
})
setTimeout(() => {
this.asideClassName = ''
this.main.style.marginLeft = '90px'
}, 0)
},
// 展开
expandAside() {
setTimeout(() => {
this.isShowAsideTitle = true
for (let i = 0, len = this.asideArrowIcons.length; i < len; i++) {
this.asideArrowIcons[i].style.display = 'block'
}
this.openMenus = this.menuCache
if (this.$refs.asideMenu) {
this.$refs.asideMenu.updateOpened()
}
}, 200)
this.asideClassName = 'aside-big'
this.main.style.marginLeft = '220px'
},
// 刷新当前标签页
reloadPage() {
let name = this.$route.name
let index = this.keepAliveData.indexOf(name)
this.$nextTick(() => {
if (this.tagsArry.length) {
this.isShowRouter = false
this.tagsArry.splice(index, 1)
this.$nextTick(() => {
this.tagsArry.splice(index, 0, { name, text: this.nameToTitle[name] })
this.gotoPage(name)
this.isShowRouter = true
})
} else {
this.isShowRouter = false
this.$nextTick(() => {
this.tagsArry.push({ name, text: this.nameToTitle[name] })
this.gotoPage(name)
this.isShowRouter = true
})
}
})
},
// 关闭单个标签
closeTag(i) {
let name = this.tagsArry[i].name
this.tagsArry.splice(i, 1)
window.event.stopPropagation()
// 如果关闭的是当前标签 则激活前一个标签
// 如果关闭的是第一个标签 则激活后一个标签
if (this.tagsArry.length) {
if (this.isActive(name)) {
if (i != 0) {
this.gotoPage(this.tagsArry[i - 1].name)
} else {
this.gotoPage(this.tagsArry[i].name)
}
}
} else if (name != this.home) {
// 如果没有标签则跳往首页
this.gotoPage(this.home)
} else {
this.reloadPage()
}
},
// 根据路由名称关闭页面
closeName(name) {
for (let i = 0, len = this.tagsArry.length; i < len; i++) {
if (this.tagsArry[i].name == name) {
this.closeTag(i)
break
}
}
},
// 批量关闭标签
closeTags(flag) {
if (flag == 1) {
// 关闭其他标签
this.tagsArry = []
this.gotoPage(this.$route.name)
} else {
// 关闭所有标签
this.tagsArry = []
this.gotoPage(this.home)
this.reloadPage()
}
},
// 激活标签
activeTag(i) {
this.gotoPage(this.tagsArry[i].name)
},
// 消息通知
info() {
const self = this
this.$Notice.info({
title: `您有${this.msgNum}条消息`,
render(h) {
return h('Button', {
attrs: {
type: 'info',
size: 'small',
},
on: {
click() {
// 点击查看跳转到消息页
self.gotoPage('msg')
self.hasNewMsg = false
self.msgNum = 0
},
},
}, [
'点击查看',
])
},
})
},
// 菜单栏改变事件
menuChange(data) {
this.menuCache = data
},
processNameToTitle(obj, data, text) {
if (data.name) {
obj[data.name] = data.text
this.paths[data.name] = text ? `${text} / ${data.text}` : data.text
}
if (data.children) {
data.children.forEach(e => {
this.processNameToTitle(obj, e, text ? `${text} / ${data.text}` : data.text)
})
}
},
},
}
</script>
<style scoped>
.index-vue {
height: 100%;
color: #666;
}
/* 侧边栏 */
aside {
position: fixed;
top: 0;
left: 0;
width: 90px;
background: #20222A;
height: 100%;
transition: width .3s;
overflow: auto;
}
.logo-c {
display: flex;
align-items: center;
color: rgba(255,255,255,.8);
font-size: 16px;
margin: 20px 0;
justify-content: center;
}
.logo {
width: 40px;
margin-right: 10px;
}
.aside-big {
width: 220px;
}
/* 主体页面 */
.sec-right {
height: 100%;
margin-left: 220px;
transition: margin-left .3s;
overflow: hidden;;
background: #f3f7fd;
}
/* 主体页面头部 */
header {
height: 50px;
border-bottom: none;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 40px;
padding-left: 10px;
font-size: 14px;
}
header .ivu-icon {
font-size: 24px;
}
.refresh-c {
margin: 0 30px;
cursor: pointer;
}
.h-right {
display: flex;
align-items: center;
}
.h-left {
display: flex;
align-items: center;
}
.user-img-c img {
width: 100%;
}
.notice-c {
cursor: pointer;
position: relative;
}
.newMsg {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #FF5722;
right: 0;
top: 0;
}
.user-img-c {
width: 34px;
height: 34px;
background: #ddd;
border-radius: 50%;
margin: 0 40px;
overflow: hidden;
}
.tag-options {
cursor: pointer;
position: relative;
}
.div-tags {
display: flex;
align-items: center;
justify-content: space-between;
margin: 4px 0;
}
.div-icons {
display: flex;
justify-content: flex-start;
align-items: center;
background: #fff;
height: 34px;
width: 160px;
font-size: 18px;
}
/* 标签栏 */
.ul-c {
height: 34px;
background: #fff;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 10px;
overflow: hidden;
width: calc(100% - 160px);
}
.ul-c li {
border-radius: 3px;
cursor: pointer;
font-size: 12px;
height: 24px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
margin: 3px 5px 2px 3px;
border: 1px solid #e6e6e6;
}
a {
color: #666;
transition: none;
}
.li-a {
max-width: 80px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ul-c .ivu-icon {
margin-left: 6px;
}
.active {
background: #409eff;
border: 1px solid #409eff;
}
.active a {
color: #fff;
}
.active .ivu-icon {
color: #fff;
}
/* 主要内容区域 */
.main-content {
height: calc(100% - 88px);
overflow: hidden;
}
.view-c {
position: relative;
height: 100%;
overflow: hidden;
}
.pointer {
cursor: pointer;
}
.crumbs {
margin-left: 10px;
color: #97a8be;
cursor: default;
}
.menu-level-3 .ivu-icon {
font-size: 18px;
}
.shrink {
text-align: center;
}
.external {
color: rgba(255,255,255,.7);
}
.external > i {
margin-right: 6px;
}
</style>

167
src/components/Login.vue Normal file
View File

@@ -0,0 +1,167 @@
<template>
<div class="login-vue" :style="bg">
<div class="container">
<p class="title">高新投绿源</p>
<div class="input-c">
<Input prefix="ios-contact" v-model="account" placeholder="用户名" clearable />
<p class="error">{{ accountError }}</p>
</div>
<div class="input-c">
<Input type="password" v-model="pwd" prefix="md-lock" placeholder="密码" clearable
@keyup.enter.native="submit" />
<p class="error">{{ pwdError }}</p>
</div>
<Button :loading="isShowLoading" class="submit" type="primary" @click="submit">登陆</Button>
<!-- <p class="account"><span @click="register">注册账号</span> | <span @click="forgetPwd">忘记密码</span></p> -->
<p>@重庆爱京数字科技有限公司</p>
<div>
<a href=" " rel="noreferrer" target="_blank">渝公网安备50011502001173号</a >
</div>
</div>
</div>
</template>
<script>
import { login } from '@/api'
export default {
name: 'login',
data() {
return {
account: '',
pwd: '',
accountError: '',
pwdError: '',
isShowLoading: false,
bg: {},
}
},
created() {
this.bg.backgroundImage = 'url(' + require('../assets/imgs/bg0' + new Date().getDay() + '.jpg') + ')'
},
watch: {
$route: {
handler(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true,
},
},
methods: {
register() {
},
forgetPwd() {
},
async submit() {
this.accountError = ''
this.pwdError = ''
if (this.account == '') {
this.accountError = '请输入用户名'
return
}
if (this.pwd == '') {
this.pwdError = '请输入密码'
return
}
await login({ username: this.account, password: this.pwd }).then((res) => {
if (res.code == 200) {
localStorage.setItem('permissions', res.permissions)
localStorage.setItem('authority', res.authority)
localStorage.setItem('adminId', res.adminId)
localStorage.setItem('token', res.token)
this.$router.push({ path: this.redirect || '/' })
} else {
this.$Message.error(res.message[0])
}
})
},
},
}
</script>
<style>
.login-vue {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
.login-vue .container {
background: rgba(255, 255, 255, .5);
width: 300px;
text-align: center;
border-radius: 10px;
padding: 30px;
}
.login-vue .ivu-input {
background-color: transparent;
color: #fff;
outline: #fff;
border-color: #fff;
}
.login-vue ::-webkit-input-placeholder {
/* WebKit, Blink, Edge */
color: rgba(255, 255, 255, .8);
}
.login-vue :-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: rgba(255, 255, 255, .8);
}
.login-vue ::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: rgba(255, 255, 255, .8);
}
.login-vue :-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: rgba(255, 255, 255, .8);
}
.login-vue .title {
font-size: 16px;
margin-bottom: 20px;
}
.login-vue .input-c {
margin: auto;
width: 200px;
}
.login-vue .error {
color: red;
text-align: left;
margin: 5px auto;
font-size: 12px;
padding-left: 30px;
height: 20px;
}
.login-vue .submit {
width: 200px;
}
.login-vue .account {
margin-top: 30px;
}
.login-vue .account span {
cursor: pointer;
}
.login-vue .ivu-icon {
color: #eee;
}
.login-vue .ivu-icon-ios-close-circle {
color: #777;
}
</style>

20
src/main.js Normal file
View File

@@ -0,0 +1,20 @@
import Vue from 'vue'
import axios from 'axios'
import ViewUI from 'view-design'
import App from './App'
import store from './store'
import router from './router'
import 'view-design/dist/styles/iview.css'
import './permission'
Vue.config.productionTip = false
Vue.use(ViewUI)
Vue.prototype.$axios = axios
new Vue({
el: '#app',
router,
store,
render: h => h(App),
})

44
src/permission.js Normal file
View File

@@ -0,0 +1,44 @@
import { LoadingBar } from 'view-design'
import router from './router'
import store from './store'
import createRoutes from '@/utils/createRoutes'
import { getDocumentTitle, resetTokenAndClearUser } from './utils'
// 是否有菜单数据
let hasMenus = false
router.beforeEach(async (to, from, next) => {
document.title = getDocumentTitle(to.meta.title)
LoadingBar.start()
if (localStorage.getItem('token')) {
if (to.path === '/login') {
next({ path: '/' })
} else if (hasMenus) {
next()
} else {
try {
// 这里可以用 await 配合请求后台数据来生成路由
// const data = await axios.get('xxx')
// const routes = createRoutes(data)
const routes = createRoutes(store.state.menuItems)
// 动态添加路由
router.addRoutes(routes)
hasMenus = true
next({ path: to.path || '/' })
} catch (error) {
resetTokenAndClearUser()
next(`/login?redirect=${to.path}`)
}
}
} else {
hasMenus = false
if (to.path === '/login') {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
router.afterEach(() => {
LoadingBar.finish()
})

290
src/router/index.js Normal file
View File

@@ -0,0 +1,290 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const commonRoutes = [{
path: '/login',
name: 'login',
meta: {
title: '登录',
},
component: () => import('../components/Login.vue'),
},
{
path: '/other', // 点击侧边栏跳到一个单独的路由页面,需要定义,层级和其他顶级路由一样
name: 'other',
meta: {
title: '单独的路由',
},
component: () => import('../views/Other.vue'),
},
{
path: '/404',
name: '404',
meta: {
title: '404',
},
component: () => import('../components/404.vue'),
},
{
path: '/',
redirect: '/charging_station',
},
]
// 本地所有的页面 需要配合后台返回的数据生成页面
export const asyncRoutes = {
// 充电桩
charging_station: {
path: 'charging_station',
name: 'charging_station',
meta: {
title: '站点管理',
},
component: () => import('../views/charging_pile/Station.vue'),
},
charging_pile: {
path: 'charging_pile',
name: 'charging_pile',
meta: {
title: '充电桩管理',
},
component: () => import('../views/charging_pile/Pile.vue'),
},
charging_price: {
path: 'charging_price',
name: 'charging_price',
meta: {
title: '电价管理',
},
component: () => import('../views/charging_pile/Price.vue'),
},
charging_fault: {
path: 'charging_fault',
name: 'charging_fault',
meta: {
title: '故障运维',
},
component: () => import('../views/charging_pile/Fault.vue'),
},
// 客户
corporate_client: {
path: 'corporate_client',
name: 'corporate_client',
meta: {
title: '企业客户',
},
component: () => import('../views/customer_center/CorporateClient.vue'),
},
wx_user: {
path: 'wx_user',
name: 'wx_user',
meta: {
title: '用户管理',
},
component: () => import('../views/customer_center/WxUser.vue'),
},
// 运营中心
activity: {
path: 'activity',
name: 'activity',
meta: {
title: '活动发布',
},
component: () => import('../views/operations_center/activity.vue'),
},
coupon: {
path: 'coupon',
name: 'coupon',
meta: {
title: '优惠券',
},
component: () => import('../views/operations_center/coupon.vue'),
},
user_vip: {
path: 'user_vip',
name: 'user_vip',
meta: {
title: '会员',
},
component: () => import('../views/operations_center/user_vip.vue'),
},
invoice: {
path: 'invoice',
name: 'invoice',
meta: {
title: '发票管理',
},
component: () => import('../views/operations_center/invoice.vue'),
},
// 订单
charge_order: {
path: 'charge_order',
name: 'charge_order',
meta: {
title: '充电订单',
},
component: () => import('../views/order/charge_order.vue'),
},
recharge_order: {
path: 'recharge_order',
name: 'recharge_order',
meta: {
title: '充值订单',
},
component: () => import('../views/order/recharge_order.vue'),
},
refund_order: {
path: 'refund_order',
name: 'refund_order',
meta: {
title: '充电退款订单',
},
component: () => import('../views/order/refund_order.vue'),
},
order_total: {
path: 'order_total',
name: 'order_total',
meta: {
title: '订单统计',
},
component: () => import('../views/order/order_total.vue'),
},
// 数据中心
yunying_reports: {
path: 'yunying_reports',
name: 'yunying_reports',
meta: {
title: '运营报表',
},
component: () => import('../views/reports/yunying_reports.vue'),
},
caiwu_reports: {
path: 'caiwu_reports',
name: 'caiwu_reports',
meta: {
title: '财务报表',
},
component: () => import('../views/reports/caiwu_reports.vue'),
},
yunwei_reports: {
path: 'yunwei_reports',
name: 'yunwei_reports',
meta: {
title: '运维报表',
},
component: () => import('../views/reports/yunwei_reports.vue'),
},
// 系统配置
system: {
path: 'system',
name: 'system',
meta: {
title: '参数设置',
},
component: () => import('../views/system/system.vue'),
},
admin: {
path: 'admin',
name: 'admin',
meta: {
title: '系统用户',
},
component: () => import('../views/system/admin.vue'),
},
auth: {
path: 'auth',
name: 'auth',
meta: {
title: '权限管理',
},
component: () => import('../views/system/auth.vue'),
},
log: {
path: 'log',
name: 'log',
meta: {
title: '日志管理',
},
component: () => import('../views/system/log.vue'),
},
// 报表分析
rate: {
path: 'rate',
name: 'rate',
meta: {
title: '日志管理',
},
component: () => import('../views/reports/rate.vue'),
},
backup_and_repair: {
path: 'backup_and_repair',
name: 'backup_and_repair',
meta: {
title: '日志管理',
},
component: () => import('../views/reports/backup_and_repair.vue'),
},
activation: {
path: 'activation',
name: 'activation',
meta: {
title: '日志管理',
},
component: () => import('../views/reports/activation.vue'),
},
income_analysis: {
path: 'income_analysis',
name: 'income_analysis',
meta: {
title: '日志管理',
},
component: () => import('../views/reports/income_analysis.vue'),
},
t1: {
path: 't1',
name: 't1',
meta: {
title: '表格',
},
component: () => import('../views/T1.vue'),
},
password: {
path: 'password',
name: 'password',
meta: {
title: '修改密码',
},
component: () => import('../views/Password.vue'),
},
msg: {
path: 'msg',
name: 'msg',
meta: {
title: '通知消息',
},
component: () => import('../views/Msg.vue'),
},
userinfo: {
path: 'userinfo',
name: 'userinfo',
meta: {
title: '用户信息',
},
component: () => import('../views/UserInfo.vue'),
},
}
const createRouter = () => new Router({
routes: commonRoutes,
})
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
export default router

252
src/store/index.js Normal file
View File

@@ -0,0 +1,252 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isShowLoading: false, // 全局 loading
// 左侧菜单栏数据
menuItems: [{
text: '充电桩管理',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'msg',
text: '文档',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
hidden: true,
},
{
type: 'ios-grid',
name: 'charging_station',
text: '站点管理',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'charging_pile',
text: '充电桩管理',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
size: 18,
text: '电价管理',
name: 'charging_price',
type: 'ios-paper',
},
{
size: 18,
text: '故障运维',
name: 'charging_fault',
type: 'ios-paper',
},
],
},
{
size: 18, // icon大小
type: 'md-home', // icon类型
text: '客户中心', // 文本内容
children: [
{
type: 'ios-grid',
name: 'corporate_client',
text: '企业客户',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'wx_user',
text: '用户管理',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
size: 18,
text: '钱包账户',
type: 'ios-paper',
},
{
size: 18,
text: '车辆管理',
type: 'ios-paper',
},
],
},
{
size: 18, // icon大小
type: 'ios-egg-outline', // icon类型
text: '运营中心', // 点击侧边栏跳到一个单独的路由页面,需要提前在 router.js 定义
children: [
{
type: 'ios-grid',
name: 'activity',
text: '活动发布',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'coupon',
text: '优惠券',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
size: 18,
text: '会员',
name: 'user_vip',
type: 'ios-paper',
},
{
size: 18,
text: '发票管理',
name: 'invoice',
type: 'ios-paper',
},
],
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
text: '订单管理',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
children: [
{
type: 'ios-grid',
name: 'charge_order',
text: '充电订单',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'recharge_order',
text: '充值订单',
// url: 'https://www.baidu.com',
// isExternal: true, // 外链 跳到一个外部的 URL 页面
},
{
size: 18,
text: '充电退款订单',
name: 'refund_order',
type: 'ios-paper',
},
{
size: 18,
text: '订单统计',
name: 'order_total',
type: 'ios-paper',
},
],
},
{
text: '数据中心',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'yunying_reports',
text: '运营报表',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'caiwu_reports',
text: '财务报表',
},
{
size: 18,
text: '运维报表',
name: 'yunwei_reports',
type: 'ios-paper',
},
],
},
{
text: '系统设置',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'system',
text: '参数设置',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'admin',
text: '系统用户',
},
{
size: 18,
text: '权限管理',
name: 'auth',
type: 'ios-paper',
},
{
size: 18,
text: '日志管理',
name: 'log',
type: 'ios-paper',
},
],
},
{
text: '报表分析',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'rate',
text: '充电桩使用率',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
type: 'ios-grid',
name: 'backup_and_repair',
text: '数据备份与修复',
// hidden 属性 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
// hidden: true,
},
{
size: 18, // icon大小
type: 'md-arrow-forward', // icon类型
name: 'activation',
text: '用户活跃度',
},
{
size: 18,
text: '收入分析',
name: 'income_analysis',
type: 'ios-paper',
},
],
},
],
},
mutations: {
setMenus(state, items) {
state.menuItems = [...items]
},
setLoading(state, isShowLoading) {
state.isShowLoading = isShowLoading
},
},
})
export default store

34
src/utils/createRoutes.js Normal file
View File

@@ -0,0 +1,34 @@
import { asyncRoutes } from '@/router'
// 将菜单信息转成对应的路由信息 动态添加
export default function createRoutes(data) {
const result = []
const children = []
result.push({
path: '/',
component: () => import('../components/Index.vue'),
children,
})
data.forEach(item => {
generateRoutes(children, item)
})
// 最后添加404页面 否则会在登陆成功后跳到404页面
result.push(
{ path: '*', redirect: '/404' },
)
return result
}
function generateRoutes(children, item) {
if (item.name) {
if (asyncRoutes[item.name]) children.push(asyncRoutes[item.name])
} else if (item.children) {
item.children.forEach(e => {
generateRoutes(children, e)
})
}
}

16
src/utils/index.js Normal file
View File

@@ -0,0 +1,16 @@
import { resetRouter } from '@/router'
export function resetTokenAndClearUser() {
// 退出登陆 清除用户资料
localStorage.setItem('token', '')
localStorage.setItem('userImg', '')
localStorage.setItem('userName', '')
// 重设路由
resetRouter()
}
export const defaultDocumentTitle = '爱京科技'
export function getDocumentTitle(pageTitle) {
if (pageTitle) return `${defaultDocumentTitle} - ${pageTitle}`
return `${defaultDocumentTitle}`
}

19
src/utils/loading.js Normal file
View File

@@ -0,0 +1,19 @@
import store from '@/store'
let loadingCounter = 0
export function showLoading() {
if (loadingCounter === 0) {
store.commit('setLoading', true)
}
loadingCounter++
}
export function closeLoading() {
loadingCounter--
if (loadingCounter <= 0) {
loadingCounter = 0
store.commit('setLoading', false)
}
}

56
src/utils/request.js Normal file
View File

@@ -0,0 +1,56 @@
import axios from 'axios'
import { Message } from 'view-design'
import router from '@/router'
import { showLoading, closeLoading } from '@/utils/loading'
import { resetTokenAndClearUser } from '@/utils'
const service = axios.create({
baseURL: '/chargingapi/',
timeout: 60000,
})
service.interceptors.request.use(config => {
showLoading()
if (localStorage.getItem('token')) {
config.headers.Authorization = localStorage.getItem('token')
}
return config
}, (error) => Promise.reject(error))
service.interceptors.response.use(response => {
closeLoading()
const res = response.data
// 这里是接口处理的一个示范,可以根据自己的项目需求更改
// 错误处理
if (res.code != 0 && res.msg) {
Message.error({
content: res.msg,
})
// token 无效,清空路由,退出登录
if (res.code == 2) {
resetTokenAndClearUser()
router.push('login')
}
return Promise.reject()
}
// 如果接口正常,直接返回数据
return res
}, (error) => {
closeLoading()
if (error.name == 'Error') {
Message.error({
content: error.msg,
})
} else {
Message.error({
content: error.response.data.data || error.message,
})
}
return Promise.reject(error)
})
export default service

41
src/views/Home.vue Normal file
View File

@@ -0,0 +1,41 @@
<template>
<div class="home-container">
<div class="home-content">
<Button @click="getUserData">ajax 测试</Button>
<Input :rows="30" style="margin-top: 20px" v-model="userInfo" type="textarea" />
</div>
</div>
</template>
<script>
import { fetchUserData } from '@/api'
export default {
name: 'home',
data() {
return {
userInfo: '',
}
},
methods: {
getUserData() {
fetchUserData().then(res => {
this.userInfo = JSON.stringify(res, null, 4)
})
},
},
}
</script>
<style scoped>
.home-container {
padding: 10px;
padding-top: 5px;
}
.home-content {
padding: 10px;
border-radius: 5px;
background: #fff;
}
</style>

86
src/views/Msg.vue Normal file
View File

@@ -0,0 +1,86 @@
<template>
<div>
<div>按钮</div>
<Space direction="vertical">
<Space wrap>
<Button>Default</Button>
<Button type="primary">Primary</Button>
<Button type="dashed">Dashed</Button>
<Button type="text">Text</Button>
</Space>
<Space wrap>
<Button type="info">Info</Button>
<Button type="success">Success</Button>
<Button type="warning">Warning</Button>
<Button type="error">Error</Button>
</Space>
</Space>
<div>图标</div>
<Icon type="ios-checkmark" />
<div>
<Title>h1. View Design</Title>
<Title :level="2">h2. View Design</Title>
<Title :level="3">h3. View Design</Title>
<Title :level="4">h4. View Design</Title>
<Title :level="5">h5. View Design</Title>
</div>
<div>分页</div>
<Table border :columns="columns" :data="data"></Table>
<Page :total="100" show-sizer />
</div>
</template>
<script>
export default {
name: 'msg',
data() {
return {
columns: [
{
title: 'Name',
key: 'name',
},
{
title: 'Age',
key: 'age',
},
{
title: 'Address',
key: 'address',
},
],
data: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
],
}
},
}
</script>
<style scoped></style>

23
src/views/Other.vue Normal file
View File

@@ -0,0 +1,23 @@
<template>
<div>
单独的路由
<div>
<Button @click="reback" type="primary" style="margin-left: 100px">返回</Button>
</div>
</div>
</template>
<script>
export default {
name: 'other',
methods: {
reback() {
this.$router.back()
},
},
}
</script>
<style>
</style>

21
src/views/Password.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<div>
修改密码
<input type="text">
</div>
</template>
<script>
export default {
name: 'password',
data() {
return {
}
},
}
</script>
<style scoped>
</style>

284
src/views/T1.vue Normal file
View File

@@ -0,0 +1,284 @@
<template>
<div style="padding: 10px">
<div style="background: #fff; border-radius: 8px; padding: 20px;">
<div class="query-c">
查询
<Input search placeholder="请输入查询内容" style="width: auto" />
</div>
<br>
<Table max-height="670" border stripe :columns="columns1" :data="data1"></Table>
<br>
<Page :total="100" show-sizer show-elevator/>
</div>
</div>
</template>
<script>
export default {
name: 't1',
data() {
return {
columns1: [
{
title: 'Name',
key: 'name',
},
{
title: 'Age',
key: 'age',
},
{
title: 'Address',
key: 'address',
},
],
data1: [
{
name: 'John Brown',
age: 20,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03',
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01',
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02',
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04',
},
],
}
},
}
</script>
<style scoped>
</style>

22
src/views/UserInfo.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<div>
基本资料
<input type="text">
</div>
</template>
<script>
export default {
name: 'userinfo',
data() {
return {
}
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,261 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { GetChargeZ } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '站点名称',
key: 'charge_station_id',
},
{
title: '充电桩编号',
key: 'ConnectorID',
},
{
title: '充电桩类型',
key: 'EquipmentType',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetChargeZ({ page: 1, status: 'fault' }).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!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { GetChargeZ } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '站点名称',
key: 'charge_station_name',
},
{
title: '充电桩编号',
key: 'ConnectorID',
},
{
title: '充电桩状态',
key: 'status_text',
},
{
title: '位号',
key: 'no',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetChargeZ({ 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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { GetPerPrice } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '站点名称',
key: 'charge_station_name',
},
{
title: '电费',
key: 'ElectricityFee',
},
{
title: '服务费',
key: 'ServiceFee',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetPerPrice({ page: 1 }).then((res) => {
this.total = res.total
this.data = res.yuan
this.page = res.current_page
})
},
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$Message.success('Success!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</div>
<!-- 添加编辑 -->
<Modal v-model="show_modal" title="添加站点" :mask-closable="false">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="80">
<FormItem label="站点名称" prop="charge_station_name">
<Input v-model="formValidate.charge_station_name" placeholder="站点名称" />
</FormItem>
<FormItem label="站点编号" prop="charge_station_number">
<Input v-model="formValidate.charge_station_number" placeholder="站点编号" />
</FormItem>
<FormItem label="城市" 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="地址" prop="address">
<Input v-model="formValidate.address" placeholder="地址" />
</FormItem>
<FormItem label="联系电话" prop="charge_station_phone">
<Input v-model="formValidate.charge_station_phone" placeholder="联系电话" />
</FormItem>
<FormItem label="精度" prop="longitude">
<Input v-model="formValidate.longitude" placeholder="精度" />
</FormItem>
<FormItem label="纬度" prop="latitude">
<Input v-model="formValidate.latitude" placeholder="纬度" />
</FormItem>
<FormItem label="电费" prop="ElectricityFee">
<Input v-model="formValidate.ElectricityFee" placeholder="电费" />
</FormItem>
<FormItem label="服务费" prop="ServiceFee">
<Input v-model="formValidate.ServiceFee" placeholder="服务费" />
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
GetPosition,
} from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [{
title: '站点名称',
key: 'charge_station_name',
},
{
title: '站点编号',
key: 'charge_station_number',
},
{
title: '站点地址',
key: 'address',
},
{
title: '精度',
key: 'longitude',
},
{
title: '纬度',
key: 'latitude',
},
{
title: '电价',
key: 'ElectricityFee',
},
{
title: '服务费',
key: 'ServiceFee',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
charge_station_name: '',
charge_station_number: '',
city: '',
charge_station_phone: '',
longitude: [],
latitude: '',
ElectricityFee: '',
ServiceFee: '',
},
ruleValidate: {
charge_station_name: [{
required: true,
message: 'The name cannot be empty',
trigger: 'blur',
}],
charge_station_number: [{
required: true,
message: 'Mailbox cannot be empty',
trigger: 'blur',
}],
city: [{
required: true,
message: 'Please select the city',
trigger: 'change',
}],
address: [{
required: true,
message: 'Please select gender',
trigger: 'change',
}],
longitude: [{
required: true,
message: 'Please select the date',
trigger: 'change',
}],
latitude: [{
required: true,
message: 'Please select time',
trigger: 'change',
}],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetPosition({
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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,702 @@
<template>
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="name" label="企业名称:">
<Input type="text" v-model="formInline.name" placeholder="" />
</FormItem>
<FormItem prop="credit_code" label="企业代码:">
<Input type="text" v-model="formInline.credit_code" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
<Button type="primary" @click="resetSubmit('formInline')" style="margin-left: 8px">重置</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="add">添加</Button>
<!-- <Button type="error">删除</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_user_list(row.id)">员工账号</Button>
<Button type="primary" size="small" style="margin-right: 5px" @click="show_car_list(row.id)">企业车辆</Button>
<Button type="primary" size="small" style="margin-right: 5px" @click="show_rechage_list(row.id)">充值记录</Button>
<Button type="primary" size="small" style="margin-right: 5px" @click="show_chage_list(row.id)">充电记录</Button>
<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" @on-change="onChangePage"
@on-page-size-change="onChangePageSize" />
</div>
<!-- 添加编辑 -->
<Modal v-model="show_modal" :title="formValidate.id ? '编辑企业' : '新增企业'" :mask-closable="false">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="80">
<FormItem label="企业名称" prop="name">
<Input v-model="formValidate.name" placeholder="请输入企业名称" />
</FormItem>
<FormItem label="企业统一社会信用代码" prop="credit_code">
<Input v-model="formValidate.credit_code" placeholder="请输入企业统一社会信用代码" />
</FormItem>
<div style="display: flex;">
<FormItem label="联系人" prop="username">
<Input v-model="formValidate.username" placeholder="请输入联系人" />
</FormItem>
<FormItem label="联系电话" prop="phone">
<Input v-model="formValidate.phone" placeholder="请输入联系电话" />
</FormItem>
</div>
<FormItem label="折扣" prop="discount">
<RadioGroup v-model="formValidate.discount">
<Radio label="1">电费和服务费</Radio>
<Radio label="2">仅服务费</Radio>
</RadioGroup>
</FormItem>
<FormItem label="折扣范围" prop="range">
<Input v-model="formValidate.range" placeholder="请输入折扣">
<template #suffix>
<div style="line-height:32px;border-left:1px solid #c3b8b8;">%</div>
</template>
</Input>
</FormItem>
<FormItem label="扣款账户" prop="account">
<RadioGroup v-model="formValidate.account">
<Radio label="1">企业账户</Radio>
<Radio label="2">个人钱包</Radio>
</RadioGroup>
</FormItem>
<FormItem label="备注" prop="marks">
<Input v-model="formValidate.marks" type="textarea" :autosize="{ minRows: 2, maxRows: 5 }"
placeholder="请输入备注信息..." />
</FormItem>
</Form>
<!--解决弹框自动关闭-->
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" @click="ok">确定</Button>
</div>
</Modal>
<!-- 员工账号 -->
<Modal v-model="user_list_show_modal" title="员工账号" :mask-closable="false" :fullscreen="true"
:footer-hide="true">
<div class="action-btn">
<Button type="primary" @click="addUser">添加</Button>
</div>
<div class="table-container">
<Table border :columns="user_columns" stripe :height="tableHeight" :data="user_data">
<template #group_id="{ row }">
<strong v-if="row.group_id == 1">管理员</strong>
</template>
<template #action="{ row, index }">
<Button type="primary" size="small" style="margin-right: 5px"
@click="showUser(row.id)">编辑</Button>
<Button type="error" size="small" @click="removeUser(row.id)">删除</Button>
</template>
</Table>
<Page :total="user_total" show-total show-sizer class="page" @on-change="onChangeUserPage"
@on-page-size-change="onChangeUserPageSize" />
</div>
</Modal>
<!-- 员工账号 添加编辑 -->
<Modal v-model="user_show_modal" :title="formUserValidate.id ? '编辑员工账号' : '新增员工账号'" :mask-closable="false">
<Form ref="formUserValidate" :model="formUserValidate" :rules="ruleUserValidate" :label-width="80">
<FormItem label="联系电话" prop="phone">
<Input v-model="formUserValidate.phone" placeholder="请输入联系电话" />
</FormItem>
<FormItem label="管理员" prop="group_id">
<RadioGroup v-model="formUserValidate.group_id">
<Radio label="1"></Radio>
<Radio label="0"></Radio>
</RadioGroup>
</FormItem>
</Form>
<!--解决弹框自动关闭-->
<div slot="footer">
<Button type="text" @click="user_cancel">取消</Button>
<Button type="primary" @click="user_ok">确定</Button>
</div>
</Modal>
<!-- 企业车辆 -->
<Modal v-model="car_list_show_modal" title="企业车辆" :mask-closable="false" :fullscreen="true"
:footer-hide="true">
<div class="action-btn">
<Button type="primary" @click="addCar">添加</Button>
</div>
<div class="table-container">
<Table border :columns="car_columns" stripe :height="tableHeight" :data="car_data">
<template #vin_type="{ row }">
{{ row.vin_type == 2 ? '关闭' : '开启' }}
</template>
<template #action="{ row, index }">
<Button type="primary" size="small" style="margin-right: 5px"
@click="showCar(row.id)">编辑</Button>
<Button type="error" size="small" @click="removeCar(row.id)">删除</Button>
</template>
</Table>
<Page :total="user_total" show-total show-sizer class="page" @on-change="onChangeUserPage"
@on-page-size-change="onChangeUserPageSize" />
</div>
</Modal>
<!-- 企业车辆 添加编辑 -->
<Modal v-model="car_show_modal" :title="formCarValidate.id ? '编辑车辆信息' : '新增企业车辆'" :mask-closable="false">
<Form ref="formCarValidate" :model="formCarValidate" :rules="ruleCarValidate" :label-width="80">
<FormItem label="车辆" prop="car_number">
<Input v-model="formCarValidate.car_number" placeholder="请输入车辆车牌号" />
</FormItem>
<FormItem label="VIN码" prop="car_vin">
<Input v-model="formCarValidate.car_vin" placeholder="请输入VIN码" />
</FormItem>
<FormItem label="内部编号" prop="car_no">
<Input v-model="formCarValidate.car_no" placeholder="请输入内部编号" />
</FormItem>
<FormItem label="车辆所有人" prop="car_user">
<Input v-model="formCarValidate.car_user" placeholder="请输入车辆所有人" />
</FormItem>
<FormItem label="VIN充电类型" prop="vin_type">
<RadioGroup v-model="formCarValidate.vin_type">
<Radio label="1">开启</Radio>
<Radio label="2">关闭</Radio>
</RadioGroup>
</FormItem>
</Form>
<!--解决弹框自动关闭-->
<div slot="footer">
<Button type="text" @click="car_cancel">取消</Button>
<Button type="primary" @click="car_ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
getEnterpriseList, getEnterpriseSave, getEnterpriseDelete, getEnterpriseRead, getEnterpriseUpdate,
getEnterpriseUserList, getEnterpriseUserUpdate, getEnterpriseUserSave, getEnterpriseUserRead, getEnterpriseUserDelete,
getEnterpriseCarList, getEnterpriseCarUpdate, getEnterpriseCarSave, getEnterpriseCarRead, getEnterpriseCarDelete } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
page: 1,
pageSize: 10,
tableHeight: 500,
formInline: {
name: '',
credit_code: '',
},
ruleInline: {
},
columns: [{
title: 'ID',
key: 'id',
},
{
title: '企业名称',
key: 'name',
},
{
title: '企业统一社会信用代码',
key: 'credit_code',
},
{
title: '账户余额',
key: 'money',
},
{
title: '联系人',
key: 'username',
},
{
title: '联系电话',
key: 'phone',
},
{
title: '操作',
slot: 'action',
width: 300,
align: 'center',
},
],
data: [],
formValidate: {
id: null,
name: '',
credit_code: '',
username: '',
phone: '',
discount: '1',
range: '',
account: '1',
marks: '',
},
ruleValidate: {
name: [{
required: true,
message: '请输入企业名称',
trigger: 'blur',
}],
credit_code: [{
required: true,
message: '请输入企业统一社会信用代码',
trigger: 'blur',
}],
username: [{
required: true,
message: '请输入联系人',
trigger: 'change',
}],
phone: [{
required: true,
message: '请输入联系电话',
trigger: 'change',
}],
},
// 用户
user_list_show_modal: false,
user_show_modal: false,
user_columns: [{
title: 'ID',
key: 'id',
},
{
title: '用户',
key: 'phone',
},
{
title: '管理员',
slot: 'group_id',
},
{
title: '操作',
slot: 'action',
width: 300,
align: 'center',
}],
formUserValidate: {
id: null,
phone: '',
group_id: '0',
enterprise_id: '',
},
ruleUserValidate: {
phone: [{
required: true,
message: '请输入联系电话',
trigger: 'blur',
}],
},
user_data: [],
user_total: 0,
userPage: 1,
userPageSize: 10,
// 车辆
car_list_show_modal: false,
car_show_modal: false,
car_columns: [{
title: 'ID',
key: 'id',
},
{
title: '车辆',
key: 'car_number',
},
{
title: 'VIN码',
key: 'car_vin',
},
{
title: '内部编号',
key: 'car_no',
},
{
title: '车辆所有人',
key: 'car_user',
},
{
title: 'VIN充电类型',
slot: 'vin_type',
},
{
title: '操作',
slot: 'action',
width: 300,
align: 'center',
}],
formCarValidate: {
id: null,
car_number: '',
car_vin: '',
enterprise_id: '',
car_no: '',
car_user: '',
vin_type: '1',
},
ruleCarValidate: {
car_number: [{
required: true,
message: '请输入车辆车牌号',
trigger: 'blur',
}],
car_vin: [{
required: true,
message: '请输入车辆VIN码',
trigger: 'blur',
}],
},
car_data: [],
car_total: 0,
carPage: 1,
carPageSize: 10,
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
// 列表
async getList() {
await getEnterpriseList({
page: this.page,
pageSize: this.pageSize,
...this.formInline,
}).then((res) => {
this.total = res.total
this.data = res.data
})
},
handleSubmit() {
this.getList()
},
resetSubmit() {
this.$refs.formInline.resetFields()
this.getList()
},
// 详情
async show(id) {
await getEnterpriseRead({ id }).then(res => {
this.formValidate = { ...res.data }
this.formValidate.discount = String(this.formValidate.discount)
this.formValidate.account = String(this.formValidate.account)
this.show_modal = true
})
},
// 删除
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 getEnterpriseDelete({ id }).then(res => {
this.$Message.success(res.msg)
this.getList()
})
},
// 新增
async ok() {
const valid = await new Promise(resolve => this.$refs.formValidate.validate(resolve))
if (!valid) {
this.$Message.error('请正确填写表单')
return
}
if (this.formValidate.id) {
await getEnterpriseUpdate(this.formValidate).then(res => {
this.$Message.success(res.msg)
this.getList()
this.cancel()
})
} else {
await getEnterpriseSave(this.formValidate).then(res => {
this.$Message.success(res.msg)
this.getList()
this.cancel()
})
}
},
add() {
this.show_modal = true
this.$refs.formValidate.resetFields()
this.formValidate.id = ''
},
cancel() {
this.$refs.formValidate.resetFields()
this.show_modal = false
},
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()
}
},
// 员工账号列表
show_user_list(id) {
this.user_list_show_modal = true
this.formUserValidate.enterprise_id = id
this.getEnterpriseUserList()
},
async getEnterpriseUserList() {
await getEnterpriseUserList({
enterprise_id: this.formUserValidate.enterprise_id,
page: this.userPage,
pageSize: this.userPageSize,
}).then((res) => {
this.user_data = res.data
this.user_total = res.total
})
},
// 添加员工账号 弹窗显示
addUser() {
this.$refs.formUserValidate.resetFields()
this.formUserValidate.id = ''
this.user_show_modal = true
},
// 关闭新增弹窗
user_cancel() {
this.user_show_modal = false
this.$refs.formUserValidate.resetFields()
},
// 提交员工账号数据
async user_ok() {
const valid = await new Promise(resolve => this.$refs.formUserValidate.validate(resolve))
if (!valid) {
this.$Message.error('请正确填写表单')
return
}
if (this.formUserValidate.id) {
await getEnterpriseUserUpdate(this.formUserValidate).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseUserList()
this.user_cancel()
})
} else {
await getEnterpriseUserSave(this.formUserValidate).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseUserList()
this.user_cancel()
})
}
},
// 编辑员工账号信息
async showUser(id) {
await getEnterpriseUserRead({ id }).then(res => {
this.formUserValidate = { ...res.data }
this.formUserValidate.group_id = String(this.formUserValidate.group_id)
this.user_show_modal = true
})
},
// 删除员工账号信息
async removeUser(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 getEnterpriseUserDelete({ id }).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseUserList()
})
},
onChangeUserPage(e) {
if (this.userPage != e) {
this.userPage = e
this.getEnterpriseUserList()
}
},
onChangeUserPageSize(e) {
if (this.userPageSize != e) {
this.userPage = 1
this.userPageSize = e
this.getEnterpriseUserList()
}
},
// 企业车辆列表
show_car_list(id) {
this.car_list_show_modal = true
this.formCarValidate.enterprise_id = id
this.getEnterpriseCarList()
},
async getEnterpriseCarList() {
await getEnterpriseCarList({
enterprise_id: this.formCarValidate.enterprise_id,
page: this.carPage,
pageSize: this.carPageSize,
}).then((res) => {
this.car_data = res.data
this.car_total = res.total
})
},
// 添加车辆信息 弹窗显示
addCar() {
this.$refs.formCarValidate.resetFields()
this.formCarValidate.id = ''
this.car_show_modal = true
},
// 关闭新增弹窗
car_cancel() {
this.car_show_modal = false
this.$refs.formCarValidate.resetFields()
},
// 提交车辆数据
async car_ok() {
const valid = await new Promise(resolve => this.$refs.formCarValidate.validate(resolve))
if (!valid) {
this.$Message.error('请正确填写表单')
return
}
if (this.formCarValidate.id) {
await getEnterpriseCarUpdate(this.formCarValidate).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseCarList()
this.car_cancel()
})
} else {
await getEnterpriseCarSave(this.formCarValidate).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseCarList()
this.car_cancel()
})
}
},
// 编辑车辆信息
async showCar(id) {
await getEnterpriseCarRead({ id }).then(res => {
this.formCarValidate = { ...res.data }
this.formCarValidate.vin_type = String(this.formCarValidate.vin_type)
this.car_show_modal = true
})
},
// 删除车辆信息
async removeCar(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 getEnterpriseCarDelete({ id }).then(res => {
this.$Message.success(res.msg)
this.getEnterpriseCarList()
})
},
onChangeCarPage(e) {
if (this.carPage != e) {
this.carPage = e
this.getEnterpriseCarList()
}
},
onChangeCarPageSize(e) {
if (this.carPageSize != e) {
this.carPage = 1
this.carPageSize = e
this.getEnterpriseCarList()
}
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { GetUser } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '区域',
key: 'area',
},
{
title: '用户名称',
key: 'username',
},
{
title: 'openid',
key: 'openid',
},
{
title: '手机号',
key: 'phone',
},
{
title: '余额',
key: 'account',
},
{
title: '冻结余额',
key: 'FrozenAccount',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetUser({ type: '', 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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,261 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { getEventList } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '活动名称',
key: 'title',
},
{
title: '活动状态',
key: 'statusMsg',
},
{
title: '创建时间',
key: 'create_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,261 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { getEventList } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: '优惠券名称',
key: 'title',
},
{
title: '状态',
key: 'statusMsg',
},
{
title: '创建时间',
key: 'create_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
// this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>safsa</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>safsa</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,201 @@
<template>
<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.phone" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #order="{ row, index }">
订单编号: {{ row.order_number }}<br>
开始时间: {{ row.start_time }}<br>
结束时间: {{ row.end_time }}
</template>
<template #pile="{ row, index }">
充电站名称: {{ row.charge_station_name }}<br>
充电桩编号: {{ row.ConnectorID}}
</template>
<template #user="{ row, index }">
openId: {{ row.openid }}<br>
手机号: {{ row.phone}}<br>
VIN: {{ row.vin }}
</template>
<template #power="{ row, index }">
电量: {{ row.TotalPower }}<br>
电费: {{ row.ElecMoney}}<br>
服务费: {{ row.SeviceMoney }}<br>
总计: {{ row.TotalMoney }}<br>
</template>
<template #type="{ row, index }">
订单类型: {{ row.type_text }}<br>
<div v-if="row.type == 1">
充值金额: {{ row.WithholdingMoney }}<br>
退还金额: {{ row.FeedbackMoney }}<br>
{{row.is_wind == 0 ? '未结算' : '已结算'}}
</div>
</template>
<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 { getChargeOrder } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: 'id',
key: 'order_id',
width: '80',
},
{
title: '订单信息',
slot: 'order',
width: '280',
},
{
title: '充电站信息',
slot: 'pile',
width: '240',
},
{
title: '用户信息',
slot: 'user',
width: '310',
},
{
title: '充电信息',
slot: 'power',
width: 200,
},
{
title: '充电类型',
slot: 'type',
width: 180,
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
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
})
},
handleSubmit() {
this.getList()
},
handleReset(name) {
this.$refs[name].resetFields()
},
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}`,
})
},
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>
<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;
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<div>
<div class="search-area">
<Form ref="formInline" :model="formInline" :rules="ruleInline" inline :label-width="120">
<FormItem prop="user" label="时间">
<Input type="text" v-model="formInline.user" placeholder="Username"></Input>
</FormItem>
<FormItem prop="password" label="充电桩">
<Input type="password" v-model="formInline.password" placeholder="Password"></Input>
</FormItem>
<FormItem prop="password" label="充电站">
<Input type="password" v-model="formInline.password" placeholder="Password"></Input>
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div id="main" ref="chartContainer" style="width: 100%; height: 400px;"></div>
</div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'Total',
data() {
return {
data: [],
chartInstance: null,
formInline: {
user: '',
password: '',
},
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (this.chartInstance) {
this.chartInstance.dispose()
}
window.removeEventListener('resize', this.handleResize)
},
methods: {
initChart() {
if (!this.$refs.chartContainer) return
// 销毁旧实例
if (this.chartInstance) {
this.chartInstance.dispose()
}
this.chartInstance = echarts.init(this.$refs.chartContainer)
// 模拟数据
const xData = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
const orderData = [120, 132, 101, 134, 90, 230, 210]
const revenueData = [220, 182, 191, 234, 290, 330, 310]
const option = {
title: {
text: '充电订单趋势',
left: 'center',
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['订单量', '收入'],
top: 30,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xData,
},
yAxis: {
type: 'value',
},
series: [{
name: '订单量',
type: 'line',
data: orderData,
smooth: true,
itemStyle: {
color: '#5470c6',
},
},
{
name: '收入',
type: 'line',
data: revenueData,
smooth: true,
itemStyle: {
color: '#91cc75',
},
},
],
}
this.chartInstance.setOption(option)
// 响应式调整
window.addEventListener('resize', this.handleResize)
},
handleResize() {
if (this.chartInstance) {
this.chartInstance.resize()
}
},
},
}
</script>
<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;
padding-top: 20px;
border: 1px solid #F3F7FD;
border-radius: 15px;
}
</style>

View File

@@ -0,0 +1,177 @@
<template>
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="openid">
<Input type="text" v-model="formInline.openid" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</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 { getReChargeOrder } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
openid: '',
},
ruleInline: {},
columns: [{
title: 'id',
key: 'id',
},
{
title: '订单编号',
key: 'out_trade_no',
},
{
title: '用户',
key: 'openid',
},
{
title: '充值金额',
key: 'total',
},
{
title: '已用金额',
key: 'total_used',
},
{
title: '充值状态',
key: 'trade_state',
},
{
title: '充值时间',
key: 'success_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
await getReChargeOrder({
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.getList()
},
handleReset(name) {
this.$refs[name].resetFields()
},
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}`,
})
},
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>
<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;
}
</style>

View File

@@ -0,0 +1,190 @@
<template>
<div class="container">
<div class="search-area">
<Form ref="formInline" inline :label-width="120" :model="formInline" :rules="ruleInline">
<FormItem prop="user" label="openid">
<Input type="text" v-model="formInline.openid" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</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 { getRefundOrder } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
openid: '',
},
ruleInline: {},
columns: [{
title: 'id',
key: 'id',
},
{
title: '订单编号',
key: 'out_trade_no',
},
{
title: '用户',
key: 'openid',
},
{
title: '用户手机号',
key: 'phone',
},
{
title: '退款金额',
key: 'refund_total',
},
{
title: '退还账户',
key: 'user_received_account',
},
{
title: '退款状态',
key: 'status',
},
{
title: '退款时间',
key: 'create_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
await getRefundOrder({
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!')
}
})
},
handleReset(name) {
this.$refs[name].resetFields()
},
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)
},
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>
<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;
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>11</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>11</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,183 @@
<template>
<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>
<FormItem prop="user" label="类型">
<Input type="text" v-model="formInline.user" placeholder="" />
</FormItem>
<FormItem prop="user" label="起止时间">
<Input type="text" v-model="formInline.user" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</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 { getCaiWuData } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [{
title: 'Id',
key: 'id',
},
{
title: '用户',
key: 'openid',
},
{
title: '类型',
key: 'type_text',
},
{
title: '金额',
key: 'money',
},
{
title: '说明',
key: 'mark',
},
{
title: '时间',
key: 'createtime',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
await getCaiWuData({
page: this.page,
pageSize: this.pageSize,
}).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!')
}
})
},
handleReset(name) {
this.$refs[name].resetFields()
},
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}`,
})
},
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>
<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;
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>11</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>11</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,9 @@
<template>
<view>322534</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,203 @@
<template>
<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>
<FormItem prop="user" label="充电桩">
<Input type="text" v-model="formInline.user" placeholder="" />
</FormItem>
<FormItem prop="user" label="起止时间">
<Input type="text" v-model="formInline.user" placeholder="" />
</FormItem>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</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 { getYunYingData } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
page: 1,
pageSize: 10,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [{
title: '充电站名称',
key: 'id',
},
{
title: '桩类型',
key: 'out_trade_no',
},
{
title: '桩数量',
key: 'openid',
},
{
title: '枪数量',
key: 'total',
},
{
title: '充电次数',
key: 'total_used',
},
{
title: '充电电量(度)',
key: 'trade_state',
},
{
title: '充电金额(元)',
key: 'success_time',
},
{
title: '电费',
key: 'success_time',
},
{
title: '服务费',
key: 'success_time',
},
{
title: '总时长',
key: 'success_time',
},
{
title: '尖峰平谷电量',
key: 'success_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
calculateTableHeight() {
// 计算表格高度 = 窗口高度 - 搜索区域高度 - 分页高度 - 其他间距
const searchHeight = document.querySelector('.search-area').offsetHeight
const pageHeight = 32 // 分页组件大约高度
const margins = 40 // 上下边距总和
this.tableHeight = window.innerHeight - searchHeight - pageHeight - margins - 130
},
async getList() {
await getYunYingData({
page: this.page,
pageSize: this.pageSize,
}).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!')
}
})
},
handleReset(name) {
this.$refs[name].resetFields()
},
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}`,
})
},
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>
<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;
}
</style>

273
src/views/system/admin.vue Normal file
View File

@@ -0,0 +1,273 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="action-btn">
<Button type="primary" @click="show_modal = true">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</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" />
</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>
</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>
</Form>
</Modal>
</div>
</template>
<script>
import { GetAdmin } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: 'ID',
key: 'id',
},
{
title: '用户名',
key: 'username',
},
{
title: '账号',
key: 'nickname',
},
{
title: '手机号',
key: 'phone',
},
{
title: '岗位',
key: 'position',
},
{
title: '角色名',
key: 'roles',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
mail: '',
city: '',
gender: '',
interest: [],
date: '',
time: '',
desc: '',
},
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' },
],
},
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetAdmin({ 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!')
} else {
this.$Message.error('Fail!')
}
})
},
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')
},
},
}
</script>
<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;
}
</style>

247
src/views/system/auth.vue Normal file
View File

@@ -0,0 +1,247 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div> -->
<div class="action-btn">
<Button type="primary" @click="add">添加</Button>
<!-- <Button type="error">删除</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>
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" />
</div>
<!-- 添加编辑 -->
<Modal v-model="show_modal" title="添加角色" :mask-closable="false">
<Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="80">
<FormItem label="角色名" prop="name">
<Input v-model="formValidate.name" placeholder="请输入角色名" />
</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>
</Select>
</FormItem>
<FormItem label="权限菜单" prop="roles">
<Tree :data="menu_data" ref="tree" show-checkbox style="height: 300px;overflow-y: auto;"></Tree>
</FormItem>
</Form>
<div slot="footer">
<Button type="text" @click="cancel">取消</Button>
<Button type="primary" @click="ok">确定</Button>
</div>
</Modal>
</div>
</template>
<script>
import {
GetRole,
getMenuList,
} from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [{
title: 'ID',
key: 'id',
},
{
title: '角色名称',
key: 'name',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
formValidate: {
name: '',
pid: 0,
roles: '',
},
ruleValidate: {
name: [{
required: true,
message: '请输入角色名',
trigger: 'blur',
}],
roles: [{
required: true,
message: '请选择权限菜单',
}],
},
cityList: [{
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',
},
],
},
],
}],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await GetRole({
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!')
} 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)
},
async ok() {
let menuIds = this.$refs.tree.getCheckedAndIndeterminateNodes()
this.formValidate.roles = menuIds.map(item => item.id)
const valid = await new Promise(resolve => this.$refs.formValidate.validate(resolve))
if (!valid) {
this.$Message.error('请正确填写表单')
return
}
// if (this.formValidate.id) { } else { }
this.$Message.info('Clicked ok')
},
cancel() {
this.show_modal = false
this.$refs.formValidate.resetFields()
},
async add() {
this.show_modal = true
await this.getMenuList()
},
async getMenuList() {
await getMenuList().then((res) => {
this.menu_data = res
})
},
},
}
</script>
<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;
}
</style>

188
src/views/system/log.vue Normal file
View File

@@ -0,0 +1,188 @@
<template>
<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>
<FormItem>
<Button type="primary" @click="handleSubmit('formInline')">搜索</Button>
</FormItem>
</Form>
</div>
<div class="table-container">
<Table border :columns="columns" stripe :height="tableHeight" :data="data">
<template #action="{ row, index }">
</template>
</Table>
<Page :total="total" show-total show-sizer class="page" @on-change="onChangePage"
@on-page-size-change="onChangePageSize"/>
</div>
</div>
</template>
<script>
import { getLogList } from '@/api'
export default {
name: 'charging_station',
data() {
return {
show_modal: false,
total: 100,
tableHeight: 500,
formInline: {
user: '',
password: '',
},
ruleInline: {},
columns: [
{
title: 'ID',
key: 'id',
},
{
title: 'ip',
key: 'ip',
},
{
title: '类型',
key: 'method',
},
{
title: '参数',
key: 'params',
},
{
title: '地址',
key: 'url',
},
{
title: '时间',
key: 'create_time',
},
{
title: '操作',
slot: 'action',
width: 150,
align: 'center',
},
],
data: [],
}
},
mounted() {
this.getList()
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
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
},
async getList() {
await getLogList({ page: this.page, pageSize: this.pageSize }).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!')
}
})
},
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')
},
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>
<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;
}
</style>

152
src/views/system/system.vue Normal file
View File

@@ -0,0 +1,152 @@
<template>
<div class="container">
<!-- tab切换 -->
<div class="tab" ref="tabHeader">
<div v-for="(item, index) in tab" @click="changeTab(index)" :key="index" class="tab_children"
:class="{ active: item.check }">
{{ item.name }}
</div>
</div>
<!-- 内容区域动态高度 -->
<div class="content" :style="{ height: tableHeight + 'px', overflow: 'auto' }">
<div v-if="currentTab === 'system'">系统参数内容</div>
<div v-else-if="currentTab === 'charge_sate'">充电费率内容</div>
<div v-else-if="currentTab === 'invoice'">发票配置内容</div>
</div>
<!-- 固定在底部的提交按钮 -->
<div class="footer">
<Button type="primary" @click="submitForm">提交</Button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
tableHeight: 500, // 初始高度单位px
currentTab: 'system',
tab: [{
type: 'system',
name: '系统参数',
check: true,
},
{
type: 'charge_sate',
name: '充电费率',
check: false,
},
{
type: 'invoice',
name: '发票配置',
check: false,
},
],
}
},
mounted() {
this.calculateTableHeight()
window.addEventListener('resize', this.calculateTableHeight)
// 添加防抖处理,避免频繁计算
this.debouncedCalculate = this.$utils.debounce(this.calculateTableHeight, 100)
},
beforeDestroy() {
window.removeEventListener('resize', this.debouncedCalculate)
},
methods: {
calculateTableHeight() {
try {
const container = this.$el
const tabHeader = this.$refs.tabHeader
const footer = container.querySelector('.footer')
if (!container || !tabHeader || !footer) return
// 计算可用高度 = 容器高度 - tab高度 - footer高度 - 内边距
const containerHeight = container.clientHeight
const tabHeight = tabHeader.offsetHeight
const footerHeight = footer.offsetHeight
const padding = 40 // 上下内边距总和
this.tableHeight = containerHeight - tabHeight - footerHeight - padding
// 设置最小高度,避免内容区域太小
this.tableHeight = Math.max(this.tableHeight, 200)
} catch (e) {
// console.error('计算高度出错:', e)
}
},
handleClose() {
this.show = false
},
changeTab(index) {
this.tab.forEach((item, i) => {
item.check = i === index
})
this.currentTab = this.tab[index].type
},
submitForm() {
// console.log('提交表单')
},
},
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
height: 100vh;
padding: 20px;
box-sizing: border-box;
position: relative;
}
.tab {
display: flex;
align-items: center;
flex-direction: row;
gap: 10px;
margin-bottom: 20px;
}
.tab_children {
padding: 8px 16px;
background: #ccc;
color: #000;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.tab_children:hover {
background: #bbb;
}
.active {
color: #fff;
background-color: #409eff;
}
.content {
padding: 20px;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 20px;
flex-shrink: 0;
/* 防止内容区域被压缩 */
}
.footer {
position: sticky;
bottom: 0;
background: white;
padding: 15px 0;
text-align: center;
border-top: 1px solid #eee;
z-index: 10;
}
</style>

6
tests/unit/utils.spec.js Normal file
View File

@@ -0,0 +1,6 @@
import { defaultDocumentTitle, getDocumentTitle } from '@/utils'
it('getDocumentTitle test', () => {
const title = '这是一个测试'
expect(getDocumentTitle(title)).toMatch(`${defaultDocumentTitle} - ${title}`)
})

124
update.md Normal file
View File

@@ -0,0 +1,124 @@
## 更新日志
### 2020.8.30 更新
* build: 打包后的文件从绝对路径改成相对路径,也就是说打包后的文件不能放在服务器根目录下。
### 2020.8.14 更新
* new: loading 从 `components/Index.vue` 挪到了 `App.vue`。axios 从 `components/Index.vue` 挪到了 `utils/request.js`,并对其进行了封装,方便复用。
* new: 重构了 loading 和 axios 拦截器使用方式,并提供了一个 ajax DEMO 放在首页。
### 2020.6.5 更新
* new: 新增外链功能,点击菜单可以跳到一个新页面,地址为指定的 URL。
* new: 新增独立路由页面功能,点击侧边栏可以跳转到单独的路由页面(铺满屏幕,顶级路由)。
具体示例请查看源码 `src/store/index` 和 [demo](https://woai3c.github.io/)
### 2019.12.21 更新
* refactor: 将 `404` 页面独立出来,单独展示(占满屏幕)
* new: [新增 eslint配合 vscode 可以自动格式化代码](https://github.com/woai3c/Front-end-articles/blob/master/eslint-vscode-format.md)
* new: 新增 jest 单元测试
* new: 页面标题 `document.title`,在 `src/utils/index` 下可设置默认的 `title`,在每个路由配置项上可设置对应的 `title`,具体示例请看代码
### 2019.12.13 更新
* fix: 修复在IE下关闭标签栏时页面抖动的问题
* refactor: 同时将左右两栏的布局方式从 flex 布局更改为 fixed + margin 的方式
### 2019.10.30 更新
* new: 在对应的菜单项上添加 `hidden` 属性,即可隐藏对应的菜单项,但还是可以在地址栏上输入对应的 URL 来访问页面。
使用方法
```js
{
type: 'ios-grid',
name: 't1',
text: '表格',
hidden: true, // 隐藏此菜单 可以通过在地址栏上输入对应的 URL 来显示页面
}
```
### 2019.10.14 更新
* fix: 修复窗口宽度过小不会收缩侧边栏的问题
* new: 打开页面时,默认展开和路由对应的菜单栏
### 2019.8.19 更新
* fix: `components/Index.vue` 文件第 31 行代码的 `v-show="isShowAsideTitle"` 会造成侧边栏收缩时二级菜单隐藏,目前已修复。
### 2019.7.24 更新
* new: 增加页面进度条,跳转时显示
### 2019.6.25 更新
* fix: 修复路由表冲突问题
退出当前用户,换账号重新登陆时,上个账号和现在的账号路由表会有冲突的问题,解决办法是在退出登陆时重置路由表。
具体实现请查看 `router/index.js``Login.vue``Index.vue` 的退出登陆回调方法。
### 2019.6.18 更新
* new: 优化动态添加路由功能
以前的动态路由功能并不完善,首先要将所有的路由都添加到路由表里,然后根据后台返回的菜单栏数据来生成菜单。
导致的问题就是,虽然有页面在菜单栏上不显示,但由于已经添加到路由表里了,所以可以在地址栏上手动输入在菜单栏上不存在(但在路由表存在)的页面,进而可以越权访问。
现在除了必要的页面需要在一开始添加到路由表里,其他的页面都可以根据后台数据来自动生成。而且菜单栏上没有的页面,在地址栏上输入地址也是访问不了的。
另外,如果在未登陆时要访问某一指定页面,会重定向到登陆页,登陆成功后会自动跳到这个指定页面。
具体实现请看 `permission.js``util` 目录下的 `index.js` 文件
### 2019.3.14 更新
* new: 增加404页面
假如跳转到一个不存在的页面时会重定向到404页面
### 2019.3.8 更新
* new: 增加面包屑功能 用于展示当前页面的路径
* new: 增加权限控制功能,如果未登陆,访问所有页面都重定向到登陆页
### 2019.3.1 更新
* new: 增加动态菜单栏功能
菜单项中的 `icon` 使用的是 `iview` 组件的 `icon` 组件。
数据格式:
```js
// 左侧菜单栏数据
menuItems: [
{
name: 'Home', // 要跳转的路由名称 不是路径
size: 18, // icon大小 非必填
type: 'md-home', // icon类型 非必填
text: '主页' // 文本内容
},
{
text: '二级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-grid',
name: 'T1',
text: '表格',
hidden: true, // 可以在菜单中隐藏此菜单项,但还是可以访问此页面,只是不能在菜单栏中看见。
},
{
text: '三级菜单',
type: 'ios-paper',
children: [
{
type: 'ios-notifications-outline',
name: 'Msg',
text: '查看消息'
},
{
type: 'md-lock',
name: 'Password',
text: '修改密码'
},
{
type: 'md-person',
name: 'UserInfo',
text: '基本资料',
}
]
}
]
}
]
```

15
vue.config.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
devServer: {
proxy: {
'/chargingapi': {
target: 'https://www.cqaijing.top', // 对应自己的接口
changeOrigin: true,
ws: true,
pathRewrite: {
'^/chargingapi': '',
},
},
},
},
publicPath: './',
}