临时提交

This commit is contained in:
墨大叔 2024-07-29 18:05:24 +08:00
parent 10f6e7351e
commit 1e2022d9f9
20 changed files with 2424 additions and 20 deletions

10
src/api/mch/bill.js Normal file
View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
// 商户查询充值记录统计
export function mchCountBill(query) {
return request({
url: '/mch/bill/count',
method: 'get',
params: query
})
}

43
src/api/mch/device.js Normal file
View File

@ -0,0 +1,43 @@
import request from '@/utils/request'
// 商户查询设备列表
export function mchListDevice(query) {
return request({
url: '/app/device/list',
method: 'get',
params: query
})
}
// 查询设备详细
export function mchGetDevice(deviceId) {
return request({
url: '/app/device/' + deviceId,
method: 'get'
})
}
// 商户解绑设备
export function mchUnbindDevice(deviceId) {
return request({
url: `/app/device/mch/unbind/${deviceId}`,
method: 'delete'
})
}
// 商户修改设备
export function mchUpdateDevice(data) {
return request({
url: `/app/device`,
method: 'put',
data
})
}
// 商户刷新物联网设备信息
export function mchRefreshIot(deviceId) {
return request({
url: `/app/device/${deviceId}/refreshIot`,
method: 'get'
})
}

46
src/api/mch/store.js Normal file
View File

@ -0,0 +1,46 @@
import request from '@/utils/request'
// 查询本人店铺
export function mchListStore(params) {
return request({
url: '/app/store/list',
method: 'get',
params
})
}
// 查询本人店铺详情
export function mchGetStore(storeId) {
return request({
url: `/app/store/mch/${storeId}`,
method: 'get'
})
}
// 新增店铺
export function mchAddStore(data) {
return request({
url: '/app/store',
method: 'post',
data
})
}
// 修改店铺
export function mchUpdateStore(data) {
return request({
url: '/app/store',
method: 'put',
data
})
}
// 删除店铺
export function mchDelStore(data) {
return request({
url: '/app/store',
method: 'delete',
data
})
}

View File

@ -69,7 +69,7 @@ export function logicDelDevice(deviceId) {
}
// 逻辑删除设备
// 刷新物联网设备信息
export function refreshIot(deviceId) {
return request({
url: `/system/device/${deviceId}/refreshIot`,

View File

@ -2,14 +2,15 @@
<base-link
:id="id"
:text="text"
path="/smDevice/detail"
:query="{deviceId: id}"
:path="path"
:query="query"
/>
</template>
<script>
import BaseLink from '@/components/Business/BaseLink.vue'
import { UserType } from '@/utils/constants'
export default {
components: { BaseLink },
@ -22,6 +23,22 @@ export default {
type: String,
default: null
}
},
computed: {
path() {
if (this.$store.getters.userType === UserType.APP) {
return `/device/${this.id}`
} else {
return '/smDevice/detail'
}
},
query() {
if (this.$store.getters.userType === UserType.APP) {
return null;
} else {
return {deviceId: this.id}
}
}
}
}
</script>

View File

@ -3,6 +3,8 @@
</template>
<script>
import { UserType } from '@/utils/constants'
export default {
name: 'StoreLink',
props: {
@ -13,11 +15,15 @@ export default {
name: {
type: String,
default: null,
}
},
},
methods: {
handleClick() {
this.$router.push({path: '/mch/storeDetail', query: {storeId: this.id}})
if (this.$store.getters.userType === UserType.APP) {
this.$router.push(`/business/store/${this.id}`)
} else {
this.$router.push({path: '/mch/storeDetail', query: {storeId: this.id}})
}
}
}
}

149
src/router/mch.js Normal file
View File

@ -0,0 +1,149 @@
import Layout from '@/layout/index.vue'
/**
* 商户中心路由
*/
export const mchRoutes = [
{
path: '/business',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '经营管理'
},
children: [
{
path: 'store',
component: () => import('../views/mch/store'),
hidden: false,
name: "MyStore",
meta: {
noCache: false,
title: '我的店铺',
}
},
{
path: 'store/:storeId',
component: () => import('../views/mch/store/detail.vue'),
hidden: true,
name: "MyStoreDetail",
meta: {
noCache: false,
title: '店铺详情',
}
},
{
path: 'suit',
component: () => import('@/views/index.vue'),
hidden: false,
name: "MyStore",
meta: {
noCache: false,
title: '套餐管理',
}
},
]
},
{
path: '/device',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '设备管理'
},
children: [
{
path: '',
component: () => import('@/views/mch/device'),
hidden: false,
name: "MyDevice",
meta: {
noCache: false,
title: '我的设备',
}
},
{
path: ':deviceId',
component: () => import('@/views/mch/device/detail.vue'),
hidden: true,
name: "MyDeviceDetail",
meta: {
noCache: false,
title: '设备详情',
}
},
]
},
{
path: '/bill',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '订单管理'
},
children: [
{
path: '',
component: () => import('@/views/index.vue'),
hidden: false,
name: "MyBill",
meta: {
noCache: false,
title: '套餐订单',
}
},
]
},
{
path: '/money',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '财务管理'
},
children: [
{
path: 'recordBalance',
component: () => import('@/views/index.vue'),
hidden: false,
name: "MyRecordBalance",
meta: {
noCache: false,
title: '收支明细',
}
},
]
},
{
path: '/dev',
component: Layout,
hidden: false,
alwaysShow: true,
meta: {
title: '开发管理'
},
children: [
{
path: 'access',
hidden: false,
component: () => import('@/views/index.vue'),
name: "MyAccess",
meta: {
noCache: false,
title: '秘钥管理'
},
},
{
path: 'https://www.baidu.com',
hidden: false,
meta: {
title: '开发文档'
},
}
]
},
]

View File

@ -4,6 +4,9 @@ import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
import { mchRoutes } from '@/router/mch'
import store from '@/store'
import { UserType } from '@/utils/constants'
const permission = {
state: {
@ -32,26 +35,42 @@ const permission = {
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
// 商户
if (store.getters.userType === UserType.APP) {
handleRoutes(mchRoutes, mchRoutes, resolve, commit);
}
// 管理员
else {
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
handleRoutes(rewriteRoutes, sidebarRoutes, resolve, commit);
})
}
})
}
}
}
function handleRoutes(rewriteRoutes, sidebarRoutes, resolve, commit) {
console.log(rewriteRoutes, sidebarRoutes);
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {

View File

@ -0,0 +1,71 @@
<template>
<div>
<el-table v-loading="loading" :data="recordList">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="时间" prop="createTime"></el-table-column>
<el-table-column align="center" label="用户名称" prop="userName"></el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getRecordList"
/>
</div>
</template>
<script>
import {listBindRecord} from "@/api/system/bindRecord";
export default {
name: 'bindRecord',
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
recordList: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
deviceId: null,
}
}
},
watch: {
deviceId(nv, ov) {
this.getRecordList(nv);
}
},
created() {
this.getRecordList(this.deviceId);
},
methods: {
//
getRecordList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listBindRecord(this.queryParams).then(response => {
this.recordList = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,153 @@
<template>
<div>
<el-row type="flex">
<el-tabs v-model="queryParams.groupBy" style="width: 100%" >
<el-tab-pane label="日报表" name="create_date">
<el-row type="flex">
<el-col :span="16">
<range-picker :end="new Date().getFullYear()" v-model="queryParams.year" @change="onChangeYear" suffix="年" />
<range-picker :start='1' :end="reportMonthEnd" v-model="queryParams.month" @change="onChangeMonth" suffix="月"/>
</el-col>
<el-col :span="8" class="used">
本月用电量
<span class="used-num">{{groupTotalUsed | money}}</span>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="月报表" name="create_month">
<el-row type="flex">
<el-col :span="16">
<range-picker :end="new Date().getFullYear()" v-model="queryParams.year" @change="onChangeYear" suffix="年"/>
</el-col>
<el-col :span="8" class="used">
本年用电量
<span class="used-num">{{groupTotalUsed | money}}</span>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-row>
<single-line-chart v-if="records && records.length > 0" v-loading="loading" :labels="labels" :chart-data="chartData" name="用电量" />
<el-empty v-else description="暂无数据" />
</div>
</template>
<script>
import SingleLineChart from "@/components/SingleLineChart/index.vue";
import {listCountRecord,} from "@/api/system/record";
import RangePicker from "@/components/RangePicker/index.vue";
export default {
name: 'meterRecordReport',
components: {RangePicker, SingleLineChart},
props: {
// id
deviceId: {
type: String,
default: null,
}
},
data() {
return {
loading: false,
records: [], //
queryParams: {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1,
groupBy: "create_date",
}
}
},
watch: {
deviceId(nv, ov) {
this.getReportData(nv);
},
"queryParams.groupBy": {
handler(nv, ov) {
if (nv === 'create_month') { //
this.queryParams.month = null;
}
if (nv === 'create_date') {//
this.queryParams.month = new Date().getMonth() + 1;
}
this.getReportData(this.deviceId);
}
}
},
computed: {
labels() {
if (this.queryParams.groupBy === 'create_date') {
return this.records.map(item => item.createDate);
} else if (this.queryParams.groupBy === 'create_month') {
return this.records.map(item => item.createMonth + '月');
}
return [];
},
chartData() {
return this.records.map(item => item.groupUsedElectriQuantity);
},
//
groupTotalUsed() {
let sum = 0;
this.records.forEach(item => sum += item.groupUsedElectriQuantity);
return sum;
},
//
reportMonthEnd() {
let now = new Date();
let nowYear = new Date().getFullYear()
let paramYear = this.queryParams.year;
if (paramYear < nowYear) {
return 12;
}
if (paramYear === nowYear) {
return now.getMonth() + 1;
}
return 0;
},
},
created() {
this.getReportData(this.deviceId);
},
methods: {
//
onChangeMonth(month) {
this.queryParams.month = month;
this.getReportData(this.deviceId);
},
//
onChangeYear(year) {
this.queryParams.year = year;
let now = new Date();
if (year === now.getFullYear()) {
if (this.queryParams.month > now.getMonth() + 1) {
this.queryParams.month = new Date().getMonth() + 1;
}
}
this.getReportData(this.deviceId);
},
//
getReportData(deviceId) {
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listCountRecord(this.queryParams).then(response => {
this.records = response.data;
}).finally(() => {
this.loading = false;
})
},
}
}
</script>
<style scoped type="scss">
.used {
line-height: 36px;
text-align: right;
}
.used-num {
font-size: 24px;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div>
<el-table v-loading="loading" :data="tableData">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="时间" prop="createTime"></el-table-column>
<el-table-column align="center" label="总用电量" prop="totalElectriQuantity">
<template slot-scope="d">{{d.row.totalElectriQuantity | money}} </template>
</el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getChargeList"
/>
</div>
</template>
<script>
import {listRecord} from "@/api/system/record";
export default {
name: 'readingRecord',
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
tableData: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
type: 1,
deviceId: null,
}
}
},
watch: {
deviceId(nv, ov) {
this.getChargeList(nv);
}
},
created() {
this.getChargeList(this.deviceId);
},
methods: {
//
getChargeList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listRecord(this.queryParams).then(response => {
this.tableData = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,91 @@
<template>
<div>
<el-table v-loading="loading" :data="rechargeList">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="时间" prop="createTime"></el-table-column>
<el-table-column align="center" label="充值用户" prop="userName"></el-table-column>
<el-table-column align="center" label="套餐名称" prop="suitName"></el-table-column>
<el-table-column align="center" label="金额" prop="money">
<template slot-scope="d">{{d.row.money | money}} </template>
</el-table-column>
<el-table-column align="center" label="时长">
<template slot-scope="d">
<span>{{d.row.suitTime}} {{suitTimeUnit(d.row.suitTimeUnit)}}</span>
</template>
</el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getChargeList"
/>
</div>
</template>
<script>
import {listBill} from "@/api/system/recharge";
import { findLabel } from '@/utils'
export default {
name: 'rechargeRecord',
dicts: ['time_unit'],
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
rechargeList: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
type: 1,
deviceId: null,
}
}
},
computed: {
//
suitTimeUnit() {
return (unit) => {
return findLabel(this.dict.type.time_unit, unit);
}
}
},
watch: {
deviceId(nv, ov) {
this.getChargeList(nv);
}
},
created() {
this.getChargeList(this.deviceId);
},
methods: {
//
getChargeList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listBill(this.queryParams).then(response => {
this.rechargeList = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<div>
<el-table v-loading="loading" :data="recordList">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="时间" prop="createTime"></el-table-column>
<el-table-column align="center" label="商户名称" prop="mchName"></el-table-column>
<el-table-column align="center" label="归零前剩余时长" prop="surplusTime">
<template slot-scope="d">{{d.row.surplusTime | money}} 分钟</template>
</el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getRecordList"
/>
</div>
</template>
<script>
import {listResetRecord} from "@/api/system/resetRecord";
export default {
name: 'resetRecord',
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
recordList: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
deviceId: null,
}
}
},
watch: {
deviceId(nv, ov) {
this.getRecordList(nv);
}
},
created() {
this.getRecordList(this.deviceId);
},
methods: {
//
getRecordList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listResetRecord(this.queryParams).then(response => {
this.recordList = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,71 @@
<template>
<div>
<el-table v-loading="loading" :data="recordList">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="时间" prop="createTime"></el-table-column>
<el-table-column align="center" label="用户名称" prop="userName"></el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getRecordList"
/>
</div>
</template>
<script>
import {listBindRecord} from "@/api/system/bindRecord";
export default {
name: 'suitList',
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
recordList: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
deviceId: null,
}
}
},
watch: {
deviceId(nv, ov) {
this.getRecordList(nv);
}
},
created() {
this.getRecordList(this.deviceId);
},
methods: {
//
getRecordList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.deviceId = deviceId | this.deviceId;
listBindRecord(this.queryParams).then(response => {
this.recordList = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,75 @@
<template>
<div>
<el-table v-loading="loading" :data="tenantList">
<el-table-column align="center" type="index" label="#"></el-table-column>
<el-table-column align="center" label="头像" prop="avatar">
<template slot-scope="d">
<image-preview :src="d.row.avatar" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column align="center" label="用户名称" prop="userName"></el-table-column>
</el-table>
<pagination
:auto-scroll="false"
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getUserList"
/>
</div>
</template>
<script>
import {listSmUser} from "@/api/system/smUser";
export default {
name: 'tenantList',
props: {
// id
deviceId: {
type: String,
default: null
}
},
data() {
return {
tenantList: [], //
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
tenantDeviceId: null,
}
}
},
watch: {
deviceId(nv, ov) {
this.getUserList(nv);
}
},
created() {
this.getUserList(this.deviceId);
},
methods: {
//
getUserList(deviceId) {
if(deviceId == null) {
this.recordList = [];
this.total = 0;
return;
}
this.loading = true;
this.queryParams.tenantDeviceId = deviceId | this.deviceId;
listSmUser(this.queryParams).then(response => {
this.tenantList = response.rows;
this.total = response.total;
}).finally(() => {
this.loading = false;
});
}
}
}
</script>

View File

@ -0,0 +1,341 @@
<template>
<div class="app-container" v-loading="loading" >
<div v-if="deviceData">
<el-card class="box-card" >
<el-descriptions title="设备详情">
<template slot="extra">
<el-dropdown style="margin-right: 1em">
<el-button>
更多操作<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<el-link :underline="false" icon="el-icon-plus" @click="handleAddElectricity">增加时长</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link :underline="false" icon="el-icon-refresh" @click="handleReset">时长归零</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link :underline="false" icon="el-icon-switch-button" v-if="!isOpen" @click="handleSwitch(true)">强制开启</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link :underline="false" icon="el-icon-switch-button" v-if="isOpen" @click="handleSwitch(false)">强制关闭</el-link>
</el-dropdown-item>
<el-dropdown-item>
<el-link :underline="false" icon="el-icon-link" type="danger" @click="handleUnbind" :disabled="deviceData.userId == null">强制解绑</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button icon="el-icon-refresh" @click="refreshIot(deviceId, true)" style="margin-right: 1em">刷新设备信息</el-button>
<el-popover
placement="left"
width="180"
trigger="click">
<div class="qr-code-box">
<qr-code :text="qrCodeText(deviceData)" :width="150" :height="150" />
<p>扫描二维码进行设备绑定</p>
</div>
<el-button slot="reference" type="primary" icon="el-icon-picture">设备二维码</el-button>
</el-popover>
</template>
<el-descriptions-item label="MAC">{{deviceData.mac | defaultValue}}
<dict-tag :options="dict.type.sm_device_status" :value="deviceData.status" size="mini"/>
</el-descriptions-item>
<el-descriptions-item label="SN">{{deviceData.deviceNo | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="在线状态">
<dict-tag :options="dict.type.sm_device_online_status" :value="deviceData.onlineStatus" size="mini"/>
</el-descriptions-item>
<el-descriptions-item label="型号">{{deviceData.model | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="型号功能">
<dict-tag :options="dict.type.sm_model_tag" :value="deviceData.modelTags" size="mini"/>
</el-descriptions-item>
<el-descriptions-item label="开关状态">
<el-tag :type="isOpen ? 'success' : 'danger'" size="mini">{{isOpen ? '已开启' : '已关闭'}}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="WIFI">{{deviceData.wifi | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="剩余时长">{{surplusTimeDesc(surplusTime).text}}</el-descriptions-item>
<el-descriptions-item label="设备剩余时长">
{{surplusTimeDesc(deviceData.remainTime).text}}
<span class="remark-text">最近更新时间{{deviceData.lastPullTime}}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card class="box-card">
<el-descriptions title="所属人信息">
<el-descriptions-item label="所属用户">
<user-link :name="deviceData.userName" :id="deviceData.userId"/>
</el-descriptions-item>
<el-descriptions-item label="设备名称">{{deviceData.deviceName | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="店铺名称">
<store-link :name="deviceData.storeName" :id="deviceData.storeId"/>
</el-descriptions-item>
<el-descriptions-item label="服务费">
<template v-if="deviceData.serviceRate == null || deviceData.serviceType == null">跟随用户</template>
<template v-else>
<dict-tag :options="dict.type.service_type" :value="deviceData.serviceType" size="mini"/>
{{deviceData.serviceRate}} {{serviceUnit(deviceData.serviceType)}}
</template>
</el-descriptions-item>
<el-descriptions-item label="备注">{{deviceData.remark | defaultValue}}</el-descriptions-item>
</el-descriptions>
</el-card>
<!-- <el-card class="box-card">-->
<!-- <el-row type="flex">-->
<!-- <el-statistic class="statistic" group-separator=",">-->
<!-- <template #title>-->
<!-- <svg-icon icon-class="time" />剩余时长-->
<!-- </template>-->
<!-- <template #formatter>-->
<!-- {{surplusTimeDesc(surplusTime).text}}-->
<!-- </template>-->
<!-- </el-statistic>-->
<!-- <el-statistic class="statistic" group-separator=",">-->
<!-- <template #title>-->
<!-- <svg-icon icon-class="time" />在线状态-->
<!-- </template>-->
<!-- <template #formatter>-->
<!-- {{findLabel(dict.type.sm_device_online_status, deviceData.onlineStatus)}}-->
<!-- </template>-->
<!-- </el-statistic>-->
<!-- <el-statistic class="statistic" group-separator=",">-->
<!-- <template #title>-->
<!-- <svg-icon icon-class="time" />开关状态-->
<!-- </template>-->
<!-- <template #formatter>-->
<!-- <el-tag :type="isOpen ? 'success' : 'danger'" size="mini">{{isOpen ? '已开启' : '已关闭'}}</el-tag>-->
<!-- </template>-->
<!-- </el-statistic>-->
<!-- </el-row>-->
<!-- </el-card>-->
<el-card class="box-card">
<el-tabs>
<el-tab-pane label="套餐列表" :lazy="true">
<suit v-if="deviceData.deviceId != null" :device-id="deviceData.deviceId"/>
</el-tab-pane>
<el-tab-pane label="用户充值记录" :lazy="true">
<recharge-record :device-id="deviceData.deviceId"/>
</el-tab-pane>
<el-tab-pane label="时长变化记录" :lazy="true">
<record-time :query="{deviceId: deviceData.deviceId}" view="device"/>
</el-tab-pane>
<el-tab-pane label="抄表记录" :lazy="true">
<reading-record :device-id="deviceData.deviceId"/>
</el-tab-pane>
<el-tab-pane label="绑定记录" :lazy="true">
<bind-record :device-id="deviceData.deviceId"/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
<el-empty v-else description="设备已被删除或不存在"/>
<!--添加时长-->
<el-dialog title="增加时长" :visible.sync="showAddElectricity" center width="400px">
<el-form :model="addElectricityForm" :rules="addRules">
<el-form-item label="时长" prop="amount">
<el-input-number v-model="addElectricityForm.amount" style="width: 250px" controls-position="right" :step="1" step-strictly :min="0"/> 分钟
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" plain @click="submitAddElectricity">确认</el-button>
<el-button plain @click="showAddElectricity = false">取消</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { addTime, getDevice, refreshIot, resetDevice, switchDevice, unbind } from '@/api/system/device'
import LineChart from "@/views/dashboard/LineChart.vue";
import RechargeRecord from "@/views/system/device/components/rechargeRecord.vue";
import { findLabel, formatDate } from '@/utils'
import QrCode from "@/components/QrCode/index.vue";
import MeterRecordReport from "@/views/system/device/components/meterRecordReport.vue";
import ReadingRecord from "@/views/system/device/components/readingRecord.vue";
import {getWxIndexUrl} from "@/utils/wx";
import BindRecord from "@/views/system/device/components/bindRecord.vue";
import ResetRecord from "@/views/system/device/components/resetRecord.vue";
import TenantList from "@/views/system/device/components/tenantList.vue";
import SuitList from '@/views/system/device/components/suitList.vue'
import Suit from '@/views/ss/suit/index.vue'
import RecordTime from '@/views/ss/time/index.vue'
import { toDescriptionFromSecond } from '@/utils/date'
import StoreLink from '@/components/Business/Store/StoreLink.vue'
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import { $serviceType } from '@/utils/mixins'
import { mchGetDevice, mchRefreshIot } from '@/api/mch/device'
export default {
name: 'MyDeviceDetail',
mixins: [$serviceType],
components: {
UserLink,
StoreLink,
RecordTime,
Suit,
SuitList,
TenantList, ResetRecord, BindRecord, ReadingRecord, MeterRecordReport, QrCode, RechargeRecord, LineChart},
dicts: ['sm_device_status', 'sm_device_outage_way', 'sm_device_notice_way', 'sm_model_tag', 'sm_device_online_status', 'service_type'],
data() {
return {
loading: false,
deviceData: {},
timer: null,
surplusTime: 0, //
addElectricityForm: {
amount: 0
},
showAddElectricity: false,
addRules: {
amount: [
{ required: true, message: '请输入电量', trigger: 'blur' }
]
},
// ID
deviceId: null,
}
},
computed: {
qrCodeText() {
return (device) => {
return getWxIndexUrl({ s: device.deviceNo});
}
},
surplusTimeDesc() {
return (second) => {
return toDescriptionFromSecond(second);
}
},
isOpen() {
return this.deviceData != null && this.deviceData.powerStatus === '1';
}
},
created() {
this.deviceId = this.$route.params.deviceId;
this.refreshIot(this.deviceId);
},
beforeDestroy() {
clearInterval(this.timer);
},
methods: {
handleUnbind() {
this.$confirm('是否强制解绑该设备?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true;
unbind(this.deviceData.deviceId).then(res => {
if (res.code === 200) {
this.$message.success("操作成功");
}
}).finally(() => {
this.getDevice();
})
})
},
handleSwitch(open) {
this.$confirm(`是否确认强制${open ? '开启' : '关闭'}设备?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true;
switchDevice(this.deviceData.deviceId, open).then(res => {
if (res.code === 200) {
this.$message.success("操作成功");
this.deviceData.powerStatus = open ? '1' : '0';
}
}).finally(() => {
this.loading = false;
})
})
},
//
computeSurplusTime() {
if (this.deviceData.expireTime == null) {
return 0;
}
let expireTime = new Date(this.deviceData.expireTime).getTime();
let now = new Date().getTime();
if (expireTime < now) {
return 0;
}
return ((expireTime - now) / 1000).toFixed(2);
},
handleReset() {
this.$confirm('是否确认归零设备?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
resetDevice(this.deviceData.deviceId).then(res => {
if (res.code === 200) {
this.$message.success('操作成功');
this.getDevice();
}
})
})
},
handleAddElectricity() {
this.resetAddElectricityForm();
this.showAddElectricity = true;
},
submitAddElectricity() {
addTime(this.deviceData.deviceId, this.addElectricityForm.amount).then(res => {
if (res.code === 200) {
this.$message.success('操作成功');
this.showAddElectricity = false;
this.getDevice();
}
})
},
resetAddElectricityForm() {
this.addElectricityForm = {
amount: 0
}
},
//
refreshIot(deviceId, notice = false) {
this.loading = true;
mchRefreshIot(deviceId).then(res => {
if (res.code !== 200) {
return this.$message.error(res.msg);
}
if (notice) {
this.$message.success('操作成功');
}
}).finally(() => {
this.getDevice();
})
},
getDevice() {
this.loading = true;
mchGetDevice(this.deviceId).then(response => {
this.deviceData = response.data;
this.surplusTime = this.computeSurplusTime();
if (this.timer == null) {
this.timer = setInterval(() => {
this.surplusTime = this.computeSurplusTime();
}, 1000)
}
}).finally(() => {
this.loading = false;
})
},
}
}
</script>
<style scoped>
.remark-text {
color: #ccc;
margin-left: 1em;
}
</style>

View File

@ -0,0 +1,498 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="SN" prop="deviceNo">
<el-input
v-model="queryParams.deviceNo"
placeholder="请输入设备SN"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="MAC" prop="mac">
<el-input
v-model="queryParams.mac"
placeholder="请输入设备Mac"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="型号" prop="model">
<el-input
v-model="queryParams.model"
placeholder="请输入型号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input
v-model="queryParams.deviceName"
placeholder="请输入设备名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="店铺名称" prop="storeName">
<el-input
v-model="queryParams.storeName"
placeholder="请输入店铺名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="在线状态" prop="onlineStatus">
<el-select v-model="queryParams.onlineStatus" placeholder="请选择在线状态" clearable @change="handleQuery">
<el-option
v-for="dict in dict.type.sm_device_online_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable @change="handleQuery">
<el-option
v-for="dict in dict.type.sm_device_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="deviceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="设备ID" align="center" prop="deviceId" width="100"/>
<el-table-column label="图片" align="center" prop="picture" width="100">
<template slot-scope="scope">
<image-preview v-if="isEmpty(scope.row.customPicture)" :src="scope.row.picture" :width="50" :height="50"/>
<image-preview v-else :src="scope.row.customPicture" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="二维码" align="center" width="80">
<template slot-scope="d">
<el-popover
placement="top"
width="180"
trigger="hover">
<div class="qr-code-box">
<qr-code :text="qrCodeText(d.row)" :width="150" :height="150" />
<p>扫描二维码进行设备绑定</p>
</div>
<el-button slot="reference" type="text" icon="el-icon-picture">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column label="设备Mac" align="center" prop="mac" min-width="100">
<device-link slot-scope="d" :text="d.row.mac" :id="d.row.deviceId"/>
</el-table-column>
<el-table-column label="设备SN" align="center" prop="deviceNo" min-width="60">
<template slot-scope="d">
<device-link :text="d.row.deviceNo" :id="d.row.deviceId"/>
</template>
</el-table-column>
<el-table-column label="设备名称" align="center" prop="deviceName" >
<device-link slot-scope="d" :text="d.row.deviceName" :id="d.row.deviceId"/>
</el-table-column>
<el-table-column label="型号" align="center" prop="model" />
<el-table-column label="在线状态" align="center" prop="onlineStatus" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.sm_device_online_status" :value="scope.row.onlineStatus"/>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.sm_device_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="所属店铺" align="center" prop="storeName" >
<store-link slot-scope="d" :id="d.row.storeId" :name="d.row.storeName"/>
</el-table-column>
<el-table-column label="服务费" align="center" prop="serviceRate" >
<template slot-scope="d">
<template v-if="d.row.serviceRate == null || d.row.serviceType == null">跟随用户</template>
<template v-else>
<dict-tag :options="dict.type.service_type" :value="d.row.serviceType"/>
{{d.row.serviceRate}} {{serviceUnit(d.row.serviceType)}}
</template>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleSee(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleUnbind(scope.row)"
>解绑</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<sn-input :show.sync="showBindSn" @submit="onSubmitSn"></sn-input>
<!-- 添加或修改设备对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="6em">
<el-row :gutter="gutter">
<form-col :span="span" label="图片" prop="customPicture">
<image-upload v-model="form.customPicture" :limit="1"/>
</form-col>
<form-col :span="span" label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</form-col>
<form-col :span="span * 2" label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</form-col>
<!--计时配置-->
<form-col :span="span" label="起步时长" prop="startTime">
<el-input v-model="form.startTime" type="number" placeholder="请输入起步时长" >
<template #append>
<el-select v-model="form.startUnit" style="width: 6em">
<el-option v-for="dict in dict.type.time_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</template>
</el-input>
</form-col>
<form-col :span="span" label="起步价" prop="startPrice">
<el-input v-model="form.startPrice" type="number" placeholder="请输入起步价" >
<template #append></template>
</el-input>
</form-col>
<form-col :span="span" label="续期时长" prop="overTime">
<el-input v-model="form.overTime" type="number" placeholder="请输入续期时长" >
<template #append>
<el-select v-model="form.overUnit" style="width: 6em">
<el-option v-for="dict in dict.type.time_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</template>
</el-input>
</form-col>
<form-col :span="span" label="续期价" prop="overPrice">
<el-input v-model="form.overPrice" type="number" placeholder="请输入续期价" >
<template #append></template>
</el-input>
</form-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!--型号选择弹窗-->
<model-dialog
:show.sync="showCheckModel"
@select="onSubmitBatchModel"
/>
</div>
</template>
<script>
import {
listDevice,
getDevice,
addDevice,
updateDevice,
logicDelDevice,
updateDeviceSn,
batchUpdateModel, updateDeviceServiceRate
} from '@/api/system/device'
import ModelSelect from "@/components/Business/Model/modelSelect.vue";
import SmUserSelect from "@/components/Business/SmUser/smUserSelect.vue";
import QrCode from "@/components/QrCode/index.vue";
import {getWxIndexUrl} from "@/utils/wx";
import SnInput from '@/components/SnInput/index.vue'
import StoreInput from '@/components/Business/Store/StoreInput.vue'
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import { isEmpty } from '@/utils'
import ModelDialog from '@/components/Business/Model/modelDialog.vue'
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import StoreLink from '@/components/Business/Store/StoreLink.vue'
import DeviceLink from '@/components/Business/Device/DeviceLink.vue'
import { $serviceType } from '@/utils/mixins'
import { mchGetDevice, mchListDevice, mchUnbindDevice, mchUpdateDevice } from '@/api/mch/device'
export default {
name: "MyDevice",
mixins: [$serviceType],
components: { DeviceLink, StoreLink, UserLink, ModelDialog, UserInput, StoreInput, SnInput, QrCode, SmUserSelect, ModelSelect},
dicts: ['sm_device_online_status', 'sm_device_status', 'sm_device_outage_way','sm_device_notice_way', 'service_type', 'time_unit'],
data() {
return {
//
showCheckModel: false,
span: 12,
gutter: 8,
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
deviceList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
storeName: null,
deviceName: null,
model: null,
mac: null,
deviceNo: null,
onlineStatus: null,
status: null,
userName: null,
tenantName: null,
},
//
form: {},
//
rules: {
modelId: [
{ required: true, message: "型号不能为空", trigger: "change" }
],
mac: [
{ required: true, message: "设备MAC不能为空", trigger: "change" }
],
},
//
expireDatePickerOptions: {
disabledDate(date) {
return date.getTime() <= Date.now();
}
},
showBindSn: false,
snLoading: false,
};
},
computed: {
isEdit() {
return this.title === "修改设备";
},
//
qrCodeText() {
return (device) => {
return getWxIndexUrl({ s: device.deviceNo});
}
},
},
created() {
this.getList();
},
methods: {
//
onSubmitBatchModel(model) {
if (model == null) {
return;
}
batchUpdateModel(this.ids, model.modelId).then(res => {
if (res.code === 200) {
this.$message.success("操作成功");
this.showCheckModel = false;
this.getList();
}
})
},
isEmpty,
handleBindSn(row) {
this.form.deviceId = row.deviceId;
this.showBindSn = true;
},
// SN
onSubmitSn(data) {
this.snLoading = true;
updateDeviceSn(this.form.deviceId, data.sn).then(res => {
if (res.code !== 200) {
return this.$message.error(res.msg);
}
this.$message.success("操作成功");
this.showBindSn = false;
this.getList();
}).finally(()=> {
this.snLoading = false;
})
},
//
onSubmitGroup(group) {
this.form.groupId = group?.groupId;
this.form.groupName = group?.groupName;
},
//
onSubmitModel(model) {
this.form.model = model?.modelName;
//
if (this.form.deviceId == null) {
this.form.serviceType = model?.serviceType;
this.form.serviceRate = model?.serviceRate;
}
},
/** 查询设备列表 */
getList() {
this.loading = true;
mchListDevice(this.queryParams).then(response => {
this.deviceList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
deviceId: null,
picture: null,
modelId: null,
mac: null,
deviceNo: null,
deviceName: null,
serviceType: '1',
serviceRate: null,
remark: null,
startTime: null,
startUnit: "3",
startPrice: null,
overTime: null,
overUnit: "3",
overPrice: null
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.deviceId)
this.single = selection.length!==1
this.multiple = !selection.length
},
//
handleSee(row) {
this.$router.push({path: `/device/${row.deviceId}`})
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加设备";
},
//
handleResetService(row) {
this.$confirm(`确定重置设备${row.deviceNo}的服务费吗?`, '重置服务费', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loading = true;
updateDeviceServiceRate(row.deviceId, null).then(res => {
if (res.code === 200) {
this.$message.success("重置成功")
}
}).finally(() => {
this.getList();
})
})
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const deviceId = row.deviceId || this.ids
mchGetDevice(deviceId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改设备";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.deviceId != null) {
mchUpdateDevice(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
}
}
});
},
handleBatchModel() {
this.showCheckModel = true;
},
/** 解绑按钮操作 */
handleUnbind(row) {
this.$modal.confirm(`是否确认解绑设备"${row.deviceName}"(SN:${row.deviceNo})`).then(function() {
return mchUnbindDevice(row.deviceId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("解绑成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/device/export', {
...this.queryParams
}, `device_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,104 @@
<template>
<div>
<el-row type="flex">
<el-tabs v-model="queryParams.groupBy" style="width: 100%">
<el-tab-pane label="月报表" name="create_month">
<range-picker v-model="queryParams.year" @change="onChangeYear" suffix="年"/>
</el-tab-pane>
</el-tabs>
</el-row>
<single-line-chart v-loading="loading" :labels="labels" :chart-data="chartData" name="收入" />
</div>
</template>
<script>
import SingleLineChart from "@/components/SingleLineChart/index.vue";
import RangePicker from "@/components/RangePicker/index.vue";
import {countBill, listBill} from "@/api/system/recharge";
import { mchCountBill } from '@/api/mch/bill'
export default {
name: 'MyStoreRechargeReport',
components: {RangePicker, SingleLineChart},
props: {
// id
storeId: {
type: String,
default: null,
}
},
data() {
return {
labels: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], // x
chartData: [], //
queryParams: {
type: 1,
year: new Date().getFullYear(),
status: "2",
groupBy: "create_month",
storeId: this.storeId,
},
loading: false,
}
},
watch: {
storeId(nv, ov) {
this.getReportData(nv);
}
},
methods: {
//
onChangeYear(year) {
console.log(year);
this.queryParams.year = year;
this.getReportData(this.storeId);
},
//
getReportData(storeId) {
this.loading = true;
this.queryParams.storeId = storeId | this.storeId;
mchCountBill(this.queryParams).then(response => {
let data = response.data;
//
let list = [];
if (data != null) {
for (let i = 1; i <= this.getReportMonth(); i ++) {
let monthData = data.find(item => item.createMonth === i);
list[i - 1] = monthData == null ? 0 : monthData.recharge;
}
}
this.chartData = list;
}).finally(() => {
this.loading = false;
})
},
//
getReportMonth() {
let now = new Date();
let nowYear = new Date().getFullYear()
let paramYear = this.queryParams.year;
if (paramYear < nowYear) {
return 12;
}
if (paramYear === nowYear) {
return now.getMonth() + 1;
}
return 0;
},
//
getMonth(str) {
if (str == null) {
return null;
}
return new Date(str).getMonth() + 1;
}
}
}
</script>
<style scoped>
.title {
line-height: 36px;
}
</style>

View File

@ -0,0 +1,151 @@
<template>
<div class="app-container" v-loading="loading">
<el-card class="box-card" header="店铺详情">
<el-row type="flex">
<image-preview :src="store.picture" :width="80" :height="80"/>
<div style="flex: 1;margin-left: 2em">
<el-descriptions>
<el-descriptions-item label="店铺名称">{{store.name | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="店铺类型">
<dict-tag :value="store.type" :options="dict.type.ss_store_type" size="small"/>
</el-descriptions-item>
<el-descriptions-item label="所属商户">{{store.userName | defaultValue}}</el-descriptions-item>
<el-descriptions-item label="营业时间">
{{store.businessTimeStart | defaultValue}} {{store.businessTimeEnd | defaultValue}}
</el-descriptions-item>
<el-descriptions-item label="店铺地址" :span="2">
{{store.province}}{{store.city}}{{store.county}}{{store.address}}
</el-descriptions-item>
<el-descriptions-item label="联系方式">{{store.contactName | defaultValue}}({{store.contactMobile | defaultValue}})</el-descriptions-item>
<el-descriptions-item label="创建时间">{{store.createTime | defaultValue}}</el-descriptions-item>
</el-descriptions>
</div>
</el-row>
</el-card>
<el-row :gutter="16">
<el-col :span="18">
<el-card class="box-card" >
<el-row type="flex">
<el-statistic
group-separator=","
:precision="2"
:value="store.todayIncome"
suffix="¥"
title="今日收入"
></el-statistic>
<el-statistic
group-separator=","
:precision="2"
:value="store.monthIncome"
suffix="¥"
title="本月收入"
></el-statistic>
<el-statistic
group-separator=","
:precision="2"
:value="store.lastMonthIncome"
suffix="¥"
title="上月收入"
></el-statistic>
<el-statistic
group-separator=","
:value="store.deviceCount"
suffix="台"
title="设备总数"
></el-statistic>
<el-statistic
group-separator=","
:value="store.onlineCount"
suffix="台"
title="在线设备"
></el-statistic>
<el-statistic
group-separator=","
:value="store.offlineCount"
suffix="台"
title="离线设备"
></el-statistic>
</el-row>
</el-card>
<el-card class="box-card">
<my-store-recharge-report :store-id="store.storeId"/>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<place-search-map
v-if="showMap"
height="534px"
:init-lng="store.lng"
:init-lat="store.lat"
enable-geo
marker-type="store"
/>
</el-card>
</el-col>
</el-row>
<el-card class="box-card">
<el-tabs>
<el-tab-pane label="设备列表" :lazy="true">
<my-device :store-id="store.storeId"/>
</el-tab-pane>
<el-tab-pane label="订单列表" :lazy="true">
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import PlaceSearchMap from '@/components/Map/PlaceSearch/PlaceSearchMap.vue'
import { mchGetStore } from '@/api/mch/store'
import MyStoreRechargeReport from '@/views/mch/store/components/storeRechargeReport.vue'
import MyDevice from '@/views/mch/device/index.vue'
export default {
name: 'MyStoreDetail',
components: { MyDevice, MyStoreRechargeReport, PlaceSearchMap },
dicts: ['ss_store_type'],
data() {
return {
loading: false,
store: {},
showMap: false,
}
},
created() {
this.getDetail();
},
beforeDestroy() {
clearInterval(this.timer);
},
methods: {
getDetail() {
this.loading = true;
mchGetStore(this.$route.params.storeId).then(res => {
this.store = res.data;
this.showMap = true;
}).finally(() => {
this.loading = false;
})
},
}
}
</script>
<style scoped>
.remark-text {
color: #ccc;
margin-left: 1em;
}
.app-container .box-card:nth-child(n + 1) {
margin-top: 1em;
}
</style>

View File

@ -0,0 +1,410 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="店铺名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入店铺名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button-->
<!-- type="danger"-->
<!-- plain-->
<!-- icon="el-icon-delete"-->
<!-- size="mini"-->
<!-- :disabled="multiple"-->
<!-- @click="handleDelete"-->
<!-- >删除</el-button>-->
<!-- </el-col>-->
<!-- <el-col :span="1.5">-->
<!-- <el-button-->
<!-- type="warning"-->
<!-- plain-->
<!-- icon="el-icon-download"-->
<!-- size="mini"-->
<!-- @click="handleExport"-->
<!-- >导出</el-button>-->
<!-- </el-col>-->
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="storeList" @selection-change="handleSelectionChange">
<!-- <el-table-column type="selection" width="55" align="center" />-->
<el-table-column type="index" width="55" align="center" label="#" />
<el-table-column label="店铺图片" align="center" prop="picture" width="100">
<template slot-scope="scope">
<image-preview :src="scope.row.picture" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="店铺名称" align="center" prop="name" >
<store-link slot-scope="d" :id="d.row.storeId" :name="d.row.name"/>
</el-table-column>
<el-table-column label="店铺类型" align="center" prop="type" width="100">
<template slot-scope="scope">
<dict-tag :value="scope.row.type" :options="dict.type.ss_store_type"/>
</template>
</el-table-column>
<el-table-column label="营业时间" align="center" min-width="100">
<template slot-scope="d">
{{d.row.businessTimeStart}} {{d.row.businessTimeEnd}}
</template>
</el-table-column>
<el-table-column label="门店地址" align="center">
<template slot-scope="d">
{{d.row.province}}{{d.row.city}}{{d.row.county}}{{d.row.address}}
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row)"
>详情</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改店铺列表对话框 -->
<el-dialog :title="title" :visible.sync="open" width="50%" append-to-body :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="8">
<form-col label="店铺图片" prop="picture" :span="span * 2">
<image-upload v-model="form.picture" :limit="9"/>
</form-col>
<form-col label="店铺名称" prop="name" :span="span">
<el-input v-model="form.name" placeholder="请输入店铺名称" />
</form-col>
<form-col label="店铺类型" prop="type" :span="span">
<el-select v-model="form.type" placeholder="请选择店铺类型" style="width: 100%">
<el-option
v-for="opt of dict.type.ss_store_type"
:label="opt.label"
:key="opt.value"
:value="opt.value"
/>
</el-select>
</form-col>
<form-col label="营业时间" prop="businessTimeStart" :span="span">
<el-time-picker
is-range
v-model="formatBusinessTime"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
:clearable="false"
style="width: 100%"
placeholder="请选择营业时间范围">
</el-time-picker>
</form-col>
<form-col label="是否在地图展示" prop="show" :span="span" label-width="9em">
<el-switch v-model="form.show"/>
</form-col>
<form-col label="定位地址" prop="address" :span="span * 2">
<el-input v-model="form.address" placeholder="请输入店铺地址">
<template #prepend>
<div>
{{form.province}}{{form.city}}{{form.county}}
</div>
</template>
<template #append>
<el-button @click="showPlaceSearchMap = true" icon="el-icon-location">选择定位</el-button>
</template>
</el-input>
</form-col>
<form-col label="联系人" prop="contactName" :span="span">
<el-input v-model="form.contactName" placeholder="请输入联系人"/>
</form-col>
<form-col label="联系电话" prop="contactMobile" :span="span">
<el-input v-model="form.contactMobile" placeholder="请输入联系电话" maxlength="11" show-word-limit/>
</form-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<place-search-dialog
:show.sync="showPlaceSearchMap"
:init-lat="form.lat"
:init-lng="form.lng"
@submit="onSubmitAddress"
marker-type="store"
/>
</div>
</template>
<script>
import UserInput from '@/components/Business/SmUser/UserInput.vue'
import PlaceSearchDialog from '@/components/Map/PlaceSearch/PlaceSearchDialog.vue'
import AreaTextSelect from '@/components/AreaTextSelect/index.vue'
import { parseTime } from '@/utils/ruoyi'
import { $view } from '@/utils/mixins'
import UserLink from '@/components/Business/SmUser/UserLink.vue'
import StoreLink from '@/components/Business/Store/StoreLink.vue'
import { mchAddStore, mchDelStore, mchGetStore, mchListStore, mchUpdateStore } from '@/api/mch/store'
export default {
name: "MyStore",
mixins: [$view],
dicts: ['ss_store_type'],
components: { StoreLink, UserLink, AreaTextSelect, PlaceSearchDialog, UserInput },
props: {
query: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
storeList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
storeId: null,
userId: null,
name: null,
address: null,
deleted: null
},
//
form: {},
//
rules: {
userId: [
{ required: true, message: "用户不能为空", trigger: "change" }
],
name: [
{ required: true, message: "店铺名称不能为空", trigger: "change" }
],
address: [
{ required: true, message: "定位地址不能为空", trigger: "change" }
],
type: [
{ required: true, message: "店铺类型不能为空", trigger: "change" }
],
businessTimeStart: [
{ required: true, message: "营业时间不允许为空", trigger: "change" }
],
contactName: [
{ required: true, message: "联系人不允许为空", trigger: "change" }
],
contactMobile: [
{ required: true, message: "联系电话不允许为空", trigger: "change" }
],
},
showPlaceSearchMap: false,
span: 12,
};
},
computed: {
formatBusinessTime: {
set(val) {
this.form.businessTimeStart = val[0];
this.form.businessTimeEnd = val[1];
},
get() {
if (this.form.businessTimeStart == null || this.form.businessTimeEnd == null) {
return null;
}
return [this.form.businessTimeStart, this.form.businessTimeEnd]
}
}
},
created() {
this.queryParams = {
...this.queryParams,
...this.query
}
this.getList();
},
methods: {
parseTime,
onSubmitAddress(addr) {
this.form.address = addr.name;
this.form.lat = addr.lat;
this.form.lng = addr.lng;
this.form.province = addr.province;
this.form.city = addr.city;
this.form.county = addr.county;
this.form.specificAddress = addr.name;
},
/** 查询店铺列表列表 */
getList() {
this.loading = true;
mchListStore(this.queryParams).then(response => {
this.storeList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
storeId: null,
userId: this.query.userId,
picture: null,
name: null,
address: null,
specificAddress: null,
businessTimeStart: "08:00",
businessTimeEnd: "18:00",
province: "福建省",
city: "宁德市",
county: "福鼎市",
lng: null,
lat: null,
createTime: null,
createBy: null,
updateTime: null,
updateBy: null,
deleted: null,
show: true
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.storeId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加店铺列表";
},
handleView(row) {
this.$router.push({
path: `/business/store/${row.storeId}`,
})
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const storeId = row.storeId || this.ids
mchGetStore(storeId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改店铺列表";
});
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.storeId != null) {
mchUpdateStore(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
mchAddStore(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal.confirm(`是否确认删除店铺${row.storeName}`).then(function() {
return mchDelStore(row.storeId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('ss/store/export', {
...this.queryParams
}, `store_${new Date().getTime()}.xlsx`)
}
}
};
</script>