日报等

This commit is contained in:
邱贞招 2025-01-27 00:20:30 +08:00
parent 46a3d45f97
commit 800899ab70
11 changed files with 766 additions and 358 deletions

View File

@ -10,10 +10,11 @@ export function listStore(query) {
} }
// 查询店铺详细 // 查询店铺详细
export function getStore(storeId) { export function getStore(storeId,data) {
return request({ return request({
url: '/system/store/' + storeId, url: '/system/store/' + storeId,
method: 'get' method: 'get',
params: data
}) })
} }

View File

@ -21,10 +21,11 @@ export function fastSearch(query) {
// 查询用户详细 // 查询用户详细
export function getUser(userId) { export function getUser(userId, query) {
return request({ return request({
url: '/user/user/' + parseStrEmpty(userId), url: '/user/user/' + parseStrEmpty(userId),
method: 'get' method: 'get',
params: query
}) })
} }

View File

@ -166,8 +166,7 @@
<router-link <router-link
v-if="deviceData.userId" v-if="deviceData.userId"
:to="'/user/detail/' + deviceData.userId" :to="'/user/detail/' + deviceData.userId"
class="link-type" class="link-type">
>
<span>{{ deviceData.userName }}</span> <span>{{ deviceData.userName }}</span>
</router-link> </router-link>
<span v-else>--</span> <span v-else>--</span>

View File

@ -323,6 +323,7 @@ import {listStore} from "@/api/system/store";
import {listUser} from "@/api/system/user"; import {listUser} from "@/api/system/user";
import QrCode from "@/components/QrCode/index.vue"; import QrCode from "@/components/QrCode/index.vue";
import { getDomain } from "@/api/common/common"; import { getDomain } from "@/api/common/common";
import { deviceSwitch } from "@/api/system/device";
// //
const defaultSort = { const defaultSort = {
@ -415,6 +416,7 @@ export default {
rules: { rules: {
}, },
domain: '', // domain: '', //
oldForm: null
}; };
}, },
created() { created() {
@ -532,6 +534,7 @@ export default {
const equipmentId = row.equipmentId || this.ids const equipmentId = row.equipmentId || this.ids
getEquipment(equipmentId).then(response => { getEquipment(equipmentId).then(response => {
this.form = response.data; this.form = response.data;
this.oldForm = { ...response.data };
this.open = true; this.open = true;
this.title = "修改设施"; this.title = "修改设施";
}); });
@ -540,22 +543,43 @@ export default {
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.equipmentId != null) { if (this.form.deviceId &&
updateEquipment(this.form).then(response => { this.form.unlockMode !== this.oldForm?.unlockMode) {
this.$modal.msgSuccess("修改成功"); const open = this.form.unlockMode === "2";
this.open = false; deviceSwitch({
this.getList(); deviceId: this.form.deviceId,
open: open
}).then(() => {
this.saveForm();
}).catch(() => {
this.$modal.msgError("设备开关操作失败");
}); });
} else { } else {
addEquipment(this.form).then(response => { this.saveForm();
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
} }
} }
}); });
}, },
/** 保存表单 */
saveForm() {
let data = {
...this.form
};
if (this.form.equipmentId != null) {
updateEquipment(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addEquipment(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
},
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const equipmentIds = row.equipmentId || this.ids; const equipmentIds = row.equipmentId || this.ids;

View File

@ -574,9 +574,14 @@ export default {
} }
return feeRules.map(rule => rule.explain).filter(Boolean).join(''); return feeRules.map(rule => rule.explain).filter(Boolean).join('');
}, },
/** 跳转到详情页面 */ /** 查看详情按钮操作 */
handleDetail(row) { handleDetail(row) {
this.$router.push(`/system/equipmentDetail/index/${row.roomId}`); this.$router.push({
path: `/system/equipmentDetail/index/${row.roomId}`,
query: {
type: 'equipment'
}
});
}, },
/** 获取套餐选项 */ /** 获取套餐选项 */
getRuleOptions(merchantId) { getRuleOptions(merchantId) {

View File

@ -458,8 +458,8 @@ export default {
/** 查询订单列表 */ /** 查询订单列表 */
getList() { getList() {
this.loading = true; this.loading = true;
console.log(this.userId,'this.userId'); // console.log(this.userId,'this.userId');
console.log(this.merchantId,'this.merchantId'); // console.log(this.merchantId,'this.merchantId');
listOrder(this.queryParams).then(response => { listOrder(this.queryParams).then(response => {
this.orderList = response.rows; this.orderList = response.rows;
this.total = response.total; this.total = response.total;

View File

@ -1,171 +1,193 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-card class="box-card" shadow="hover"> <div class="detail-container">
<!-- 标题和操作按钮 --> <el-card class="box-card" shadow="hover">
<div class="card-header"> <!-- 标题和操作按钮 -->
<div class="header-title"> <div class="card-header">
<span class="title">订单详情</span> <div class="header-title">
<dict-tag :options="dict.type.ss_order_status" :value="order.status"/> <span class="title">订单详情</span>
</div> <dict-tag :options="dict.type.ss_order_status" :value="order.status"/>
<div class="operation-buttons"> </div>
<el-button <div class="operation-buttons">
type="primary" <el-button
icon="el-icon-card" type="primary"
size="small" icon="el-icon-card"
@click="handleRefund" size="small"
v-has-permi="['system:room:refund']" @click="handleRefund"
>退款</el-button> v-has-permi="['system:room:refund']"
<el-button >退款</el-button>
type="danger" <el-button
icon="el-icon-close" type="danger"
size="small" icon="el-icon-close"
@click="handleClose" size="small"
v-has-permi="['system:room:close']" @click="handleClose"
>结束订单</el-button> v-has-permi="['system:room:close']"
>结束订单</el-button>
</div>
</div> </div>
<!-- 左侧详情信息 -->
<el-row :span="24">
<el-col :span="20">
<div class="detail-left">
<!-- 所有info-section -->
<div class="info-sections">
<!-- 基本信息 -->
<div class="info-section">
<h3>
<i class="el-icon-info"></i>
基本信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="订单号">{{ order.orderNo }}</el-descriptions-item>
<el-descriptions-item label="用户">
<router-link :to="`/user/detail/${order.userId}`" class="link-type">
{{ order.userName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="预约时间">
<template v-if="order.reserveStartTime && order.reserveEndTime">
{{ parseTime(order.reserveStartTime) }} {{ parseTime(order.reserveEndTime) }}
</template>
<template v-else>
--
</template>
</el-descriptions-item>
<el-descriptions-item label="房间/设施">
<router-link :to="`/system/roomDetail/index/${order.roomId}`" class="link-type">
{{ order.roomName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="店铺">
<router-link :to="`/system/storeDetail/index/${order.storeId}`" class="link-type">
{{ order.storeName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="大门">
<router-link
v-if="order.gateSn"
:to="`/system/deviceDetail/index/${order.gateId}`"
class="link-type">
{{ order.gateSn }}
</router-link>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 套餐信息 -->
<div class="info-section">
<h3>
<i class="el-icon-goods"></i>
套餐信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="收费模式">
<dict-tag :options="dict.type.ss_fee_rule_mode" :value="order.mode"/>
</el-descriptions-item>
<el-descriptions-item label="套餐名称">{{ order.ruleName }}</el-descriptions-item>
<el-descriptions-item label="套餐时长">{{ order.duration }} 小时</el-descriptions-item>
</el-descriptions>
</div>
<!-- 商户信息 -->
<div class="info-section">
<h3>
<i class="el-icon-office-building"></i>
商户信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="商户名称">
<router-link :to="`/user/detail/${order.merchantId}`" class="link-type">
{{ order.merchantName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="商户ID">{{ order.merchantId }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 支付信息 -->
<div class="info-section">
<h3>
<i class="el-icon-money"></i>
支付信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.ss_pay_type" :value="order.payType"/>
</el-descriptions-item>
<el-descriptions-item label="支付状态">
<dict-tag :options="dict.type.et_order_pay_status" :value="order.paid"/>
</el-descriptions-item>
<el-descriptions-item label="支付时间">
{{ parseTime(order.payTime) }}
</el-descriptions-item>
<el-descriptions-item label="订单金额">{{ order.totalFee | dv }} </el-descriptions-item>
<el-descriptions-item label="实付金额">{{ order.payFee | dv }} </el-descriptions-item>
<el-descriptions-item label="手续费">{{ order.handlingCharge | dv }} </el-descriptions-item>
<el-descriptions-item label="平台服务费">{{ order.platformServiceFee | dv }} </el-descriptions-item>
<el-descriptions-item label="押金扣除">{{ order.depositDeduction | dv }} </el-descriptions-item>
<el-descriptions-item label="扣除金额">{{ order.deductionAmount | dv }} </el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
</el-col>
<el-col :span="4">
<!-- 右侧操作履历 -->
<div class="detail-right">
<div class="info-sections history-section">
<h3>
<i class="el-icon-time" style="color: #E6A23C;"></i>
操作履历
</h3>
<el-timeline class="padding0">
<el-timeline-item
v-for="activity in order.orderOpers"
:key="activity.operId"
:type="getTimelineItemType(activity.operType)"
:timestamp="activity.createTime"
:color="getTimelineItemColor(activity.operType)"
>
<div class="timeline-content">
<span class="operation-type" :style="{ color: getTimelineItemColor(activity.operType) }">
{{ dict.type.ss_order_oper_type[activity.operType].label }}
</span>
<el-tooltip placement="right" effect="light" popper-class="operation-tooltip">
<div slot="content">
<div>{{ activity.details }}</div>
<div v-if="activity.beforeAmount !== activity.afterAmount">
金额变更: {{ activity.beforeAmount }} -> {{ activity.afterAmount }}
</div>
<div>操作人: {{ activity.operPhone }}</div>
</div>
<i
class="el-icon-info tooltip-icon"
:style="{ color: getTimelineItemColor(activity.operType) }"
></i>
</el-tooltip>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</el-col>
</el-row>
</el-card>
<!-- 分账明细标签页 -->
<el-card class="box-card detail-tabs" shadow="hover">
<el-tabs v-model="activeTab">
<el-tab-pane label="分账明细" name="detail" :lazy="true">
<detail v-if="order.orderNo != null"
:query="{
orderNo: order.orderNo
}"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</div> </div>
<!-- 基本信息 -->
<div class="info-section">
<h3>
<i class="el-icon-info"></i>
基本信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="订单号">{{ order.orderNo }}</el-descriptions-item>
<el-descriptions-item label="用户">
<router-link :to="`/user/detail/${order.userId}`" class="link-type">
{{ order.userName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="预约时间">
<template v-if="order.reserveStartTime && order.reserveEndTime">
{{ parseTime(order.reserveStartTime) }} {{ parseTime(order.reserveEndTime) }}
</template>
<template v-else>
--
</template>
</el-descriptions-item>
<el-descriptions-item label="房间/设施">
<router-link :to="`/system/roomDetail/index/${order.roomId}`" class="link-type">
{{ order.roomName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="店铺">
<router-link :to="`/system/storeDetail/index/${order.storeId}`" class="link-type">
{{ order.storeName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="大门">
<router-link
v-if="order.gateSn"
:to="`/system/deviceDetail/index/${order.gateId}`"
class="link-type">
{{ order.gateSn }}
</router-link>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 用户信息 -->
<!-- <div class="info-section">-->
<!-- <h3>-->
<!-- <i class="el-icon-user"></i>-->
<!-- 用户信息-->
<!-- </h3>-->
<!-- <el-descriptions :column="3" border>-->
<!-- <el-descriptions-item label="用户名">-->
<!-- <router-link :to="`/user/detail/${order.userId}`" class="link-type">-->
<!-- {{ order.userName }}-->
<!-- </router-link>-->
<!-- </el-descriptions-item>-->
<!-- <el-descriptions-item label="用户ID">{{ order.userId }}</el-descriptions-item>-->
<!-- </el-descriptions>-->
<!-- </div>-->
<!-- 套餐信息 -->
<div class="info-section">
<h3>
<i class="el-icon-goods"></i>
套餐信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="收费模式">
<dict-tag :options="dict.type.ss_fee_rule_mode" :value="order.mode"/>
</el-descriptions-item>
<el-descriptions-item label="套餐名称">{{ order.ruleName }}</el-descriptions-item>
<el-descriptions-item label="套餐时长">{{ order.duration }} 小时</el-descriptions-item>
<!-- <el-descriptions-item label="套餐ID">{{ order.ruleId }}</el-descriptions-item>-->
</el-descriptions>
</div>
<!-- 设备信息 -->
<!-- <div class="info-section">-->
<!-- <h3>-->
<!-- <i class="el-icon-cpu"></i>-->
<!-- 设备信息-->
<!-- </h3>-->
<!-- <el-descriptions :column="3" border>-->
<!-- <el-descriptions-item label="设备MAC">{{ order.deviceMac }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="设备SN">{{ order.deviceSn }}</el-descriptions-item>-->
<!-- <el-descriptions-item label="设备ID">{{ order.deviceId }}</el-descriptions-item>-->
<!-- </el-descriptions>-->
<!-- </div>-->
<!-- 商户信息 -->
<div class="info-section">
<h3>
<i class="el-icon-office-building"></i>
商户信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="商户名称">
<router-link :to="`/user/detail/${order.merchantId}`" class="link-type">
{{ order.merchantName }}
</router-link>
</el-descriptions-item>
<el-descriptions-item label="商户ID">{{ order.merchantId }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 支付信息 -->
<div class="info-section">
<h3>
<i class="el-icon-money"></i>
支付信息
</h3>
<el-descriptions :column="3" border>
<el-descriptions-item label="支付方式">
<dict-tag :options="dict.type.ss_pay_type" :value="order.payType"/>
</el-descriptions-item>
<el-descriptions-item label="支付状态">
<dict-tag :options="dict.type.et_order_pay_status" :value="order.paid"/>
</el-descriptions-item>
<el-descriptions-item label="支付时间">
{{ parseTime(order.payTime) }}
</el-descriptions-item>
<el-descriptions-item label="订单金额">{{ order.totalFee | dv }} </el-descriptions-item>
<el-descriptions-item label="实付金额">{{ order.payFee | dv }} </el-descriptions-item>
<el-descriptions-item label="手续费">{{ order.handlingCharge | dv }} </el-descriptions-item>
<el-descriptions-item label="平台服务费">{{ order.platformServiceFee | dv }} </el-descriptions-item>
<el-descriptions-item label="押金扣除">{{ order.depositDeduction | dv }} </el-descriptions-item>
<el-descriptions-item label="扣除金额">{{ order.deductionAmount | dv }} </el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- 标签页 -->
<el-card class="box-card detail-tabs" shadow="hover">
<el-tabs v-model="activeTab">
<el-tab-pane label="分账明细" name="detail" :lazy="true">
<detail v-if="order.orderNo != null"
:query="{
orderNo: order.orderNo
}"/>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 退款弹窗 --> <!-- 退款弹窗 -->
<refund-dialog :show.sync="showRefund" :pay-id="order.payId" :amount="order.payFee" @success="getDetail(orderId)" /> <refund-dialog :show.sync="showRefund" :pay-id="order.payId" :amount="order.payFee" @success="getDetail(orderId)" />
</div> </div>
@ -180,7 +202,7 @@ import Log from "@/views/system/commandLog/index.vue";
export default { export default {
name: "OrderDetail", name: "OrderDetail",
dicts: ['ss_order_status', 'ss_pay_type', 'et_order_pay_status', 'ss_fee_rule_mode'], dicts: ['ss_order_status', 'ss_pay_type', 'et_order_pay_status', 'ss_fee_rule_mode', 'ss_order_oper_type'],
components: { components: {
Log, Log,
RefundDialog, RefundDialog,
@ -238,7 +260,69 @@ export default {
}).catch(() => {}) }).catch(() => {})
}, },
parseTime /** 获取时间轴节点的类型 */
getTimelineItemType(operType) {
const typeMap = {
'1': 'primary', //
'2': 'success', //
'3': 'warning', //
'4': 'info', //
'6': 'warning', // 退
'7': 'danger', //
'8': 'danger' //
};
return typeMap[operType] || 'info';
},
/** 获取时间轴节点的颜色 */
getTimelineItemColor(operType) {
const colorMap = {
'1': '#409EFF', // -
'2': '#67C23A', // - 绿
'3': '#E6A23C', // -
'4': '#909399', // -
'6': '#E6A23C', // 退 -
'7': '#F56C6C', // -
'8': '#F56C6C' // -
};
return colorMap[operType] || '#909399';
},
/** 获取操作图标 */
getOperationIcon(operType) {
const iconMap = {
'1': 'el-icon-plus', // -
'2': 'el-icon-check', // -
'3': 'el-icon-refresh', // -
'4': 'el-icon-circle-close', // -
'6': 'el-icon-back', // 退 -
'7': 'el-icon-close', // -
'8': 'el-icon-warning' // -
};
return iconMap[operType] || 'el-icon-info';
},
/** 获取提示内容 */
getTooltipContent(activity) {
let content = [];
//
if (activity.details) {
content.push(activity.details);
}
//
if (activity.beforeAmount !== activity.afterAmount) {
content.push(`金额变更: ${activity.beforeAmount}元 -> ${activity.afterAmount}`);
}
//
if (activity.operPhone) {
content.push(`操作人: ${activity.operPhone}`);
}
return content.join('\n');
}
} }
} }
</script> </script>
@ -251,6 +335,9 @@ export default {
.box-card { .box-card {
margin-bottom: 24px; margin-bottom: 24px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
} }
} }
@ -258,6 +345,8 @@ export default {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px;
border-bottom: 1px solid #ebeef5;
.header-title { .header-title {
display: flex; display: flex;
@ -278,11 +367,20 @@ export default {
gap: 8px; gap: 8px;
} }
.info-section { .info-sections {
padding-top: 20px; padding: 0 20px;
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 1px 4px rgba(0,0,0,0.05); }
.info-section {
padding: 20px 0;
background-color: #fff;
border-radius: 4px;
& + .info-section {
border-top: 1px solid #ebeef5;
}
h3 { h3 {
margin: 0 0 16px; margin: 0 0 16px;
@ -298,10 +396,6 @@ export default {
color: #409EFF; color: #409EFF;
} }
} }
&:first-of-type {
margin-top: 0;
}
} }
.detail-tabs { .detail-tabs {
@ -329,4 +423,102 @@ export default {
padding: 12px 16px; padding: 12px 16px;
} }
} }
.detail-container {
width: 100%;
min-height: calc(100vh - 132px);
}
.detail-left {
.box-card {
margin-bottom: 0;
}
}
.detail-right {
.history-section {
h3 {
margin-bottom: 20px;
}
}
}
.padding0{
padding: 0 !important;
}
.history-section {
background-color: #fff;
border-radius: 4px;
:deep(.el-timeline) {
padding: 20px;
margin: 0 -20px;
.el-timeline-item__content {
.timeline-content {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.operation-type {
font-size: 14px;
font-weight: 500;
}
.tooltip-icon {
font-size: 16px;
cursor: help;
opacity: 0.8;
transition: opacity 0.2s;
display: inline-block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
&:hover {
opacity: 1;
}
}
}
.el-timeline-item__timestamp {
color: #909399;
font-size: 12px;
line-height: 1.4;
padding-top: 4px;
}
.el-timeline-item__node {
background-color: #fff;
border: 2px solid currentColor;
}
.el-timeline-item__tail {
border-left: 2px solid #e4e7ed;
}
}
}
/* 添加全局样式 */
:deep(.operation-tooltip) {
.el-tooltip__popper {
max-width: 300px;
line-height: 1.5;
div {
margin: 4px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
</style> </style>

View File

@ -7,8 +7,9 @@
type="success" type="success"
icon="el-icon-unlock" icon="el-icon-unlock"
size="small" size="small"
v-if="!isEquipment"
@click="handleOpenDoor" @click="handleOpenDoor"
>房间</el-button> ></el-button>
<el-button <el-button
type="primary" type="primary"
icon="el-icon-unlock" icon="el-icon-unlock"
@ -342,6 +343,7 @@ import room from "@/views/system/room/index.vue";
import QrCode from '@/components/QrCode' import QrCode from '@/components/QrCode'
import { getDomain } from "@/api/common/common"; import { getDomain } from "@/api/common/common";
import Log from "@/views/system/commandLog/index.vue"; import Log from "@/views/system/commandLog/index.vue";
import { deviceSwitch } from "@/api/system/device";
export default { export default {
name: 'RoomDetail', name: 'RoomDetail',
@ -402,6 +404,7 @@ export default {
explain: undefined, explain: undefined,
mode: undefined mode: undefined
}, },
isEquipment: false, //
} }
}, },
@ -411,6 +414,7 @@ export default {
this.getDictData() this.getDictData()
this.getStoreOptions() this.getStoreOptions()
this.getDomain2() this.getDomain2()
this.isEquipment = this.$route.query.type === 'equipment';
}, },
methods: { methods: {
@ -487,18 +491,12 @@ export default {
/** 修改按钮操作 */ /** 修改按钮操作 */
handleUpdate() { handleUpdate() {
this.reset();
//
this.form = JSON.parse(JSON.stringify(this.room));
//
if (Array.isArray(this.form.pictures) && this.form.pictures.length > 0) {
this.form.picture = this.form.pictures.join(',');
} else {
this.form.picture = '';
this.form.pictures = [];
}
this.open = true; this.open = true;
this.title = "修改房间/设施"; this.title = "修改" + (this.isEquipment ? "设施" : "房间");
this.form = {
...this.room,
pictures: this.room.pictures || []
};
}, },
// //
@ -532,38 +530,45 @@ export default {
this.loadRuleList(); this.loadRuleList();
}, },
/** 提交按钮 */ /** 表单提交 */
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
const submitData = { ...this.form }; //
// if (this.isEquipment &&
if (submitData.picture) { this.form.deviceId &&
// this.form.unlockMode !== this.room.unlockMode) {
submitData.pictures = submitData.picture.split(',').filter(Boolean); // (1)(false)(2)(true)
submitData.picture = submitData.pictures.join(','); const open = this.form.unlockMode === "2";
} else { deviceSwitch({
submitData.pictures = []; deviceId: this.form.deviceId,
submitData.picture = ''; open: open
} }).then(() => {
//
updateRoom(submitData).then(response => { this.saveForm();
this.$modal.msgSuccess("修改成功"); }).catch(() => {
this.open = false; this.$modal.msgError("设备开关操作失败");
//
this.getDetail().then(() => {
//
this.$nextTick(() => {
if (this.$refs.carousel) {
this.$refs.carousel.setActiveItem(0);
}
});
}); });
}); } else {
//
this.saveForm();
}
} }
}); });
}, },
/** 保存表单 */
saveForm() {
let data = {
...this.form
};
updateRoom(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getDetail();
});
},
/** 获取状态对应的类型 */ /** 获取状态对应的类型 */
getStatusType(status) { getStatusType(status) {
const statusMap = { const statusMap = {

View File

@ -213,15 +213,17 @@
<!-- 图表和地图区域 --> <!-- 图表和地图区域 -->
<el-row :gutter="20" class="chart-section"> <el-row :gutter="20" class="chart-section">
<el-col :span="16"> <el-col :span="16">
<el-card class="chart-card" shadow="hover"> <el-card class="box-card" shadow="hover">
<div slot="header" class="card-header"> <div slot="header" class="card-header">
<span class="title">营收趋势</span> <span>营收趋势</span>
<el-radio-group v-model="chartTimeRange" size="small" @change="handleChartRangeChange"> <div class="date-range">
<el-radio-button label="week">近7天</el-radio-button> <el-radio-group v-model="dateRange" size="small" @change="handleDateRangeChange">
<el-radio-button label="month">近30天</el-radio-button> <el-radio-button label="7">近7天</el-radio-button>
</el-radio-group> <el-radio-button label="30">近30天</el-radio-button>
</el-radio-group>
</div>
</div> </div>
<div class="chart-container" ref="incomeChart"></div> <div id="incomeChart" style="width: 100%; height: 400px;"></div>
</el-card> </el-card>
</el-col> </el-col>
@ -333,7 +335,8 @@ import {
bindGateApi, bindGateApi,
unbindDevice, unbindDevice,
gateAuth, gateAuth,
openGate openGate,
updateStore
} from "@/api/system/store"; } from "@/api/system/store";
import {getDevice} from "@/api/system/device"; import {getDevice} from "@/api/system/device";
import * as echarts from 'echarts'; import * as echarts from 'echarts';
@ -348,6 +351,7 @@ import toilet from '@/views/system/toilet/index.vue';
import BindGateDialog from './components/BindGateDialog.vue'; import BindGateDialog from './components/BindGateDialog.vue';
import QrCode from '@/components/QrCode'; import QrCode from '@/components/QrCode';
import { getDomain } from "@/api/common/common"; import { getDomain } from "@/api/common/common";
import { deviceSwitch } from "@/api/system/device";
export default { export default {
name: 'StoreDetail', name: 'StoreDetail',
@ -400,7 +404,8 @@ export default {
unlockTime: [ unlockTime: [
{ required: true, message: "请输入开锁时长", trigger: "blur" } { required: true, message: "请输入开锁时长", trigger: "blur" }
] ]
} },
dateRange: '7', // 7
} }
}, },
created() { created() {
@ -418,6 +423,12 @@ export default {
this.$refs.incomeChart.addEventListener('scroll', this.handleScroll); this.$refs.incomeChart.addEventListener('scroll', this.handleScroll);
} }
}); });
//
window.addEventListener('resize', () => {
if (this.incomeChart) {
this.incomeChart.resize();
}
});
}, },
beforeDestroy() { beforeDestroy() {
if (this.incomeChart) { if (this.incomeChart) {
@ -428,7 +439,7 @@ export default {
this.map.destroy(); this.map.destroy();
this.map = null; this.map = null;
} }
window.removeEventListener('resize', this.resizeChart); window.removeEventListener('resize', this.handleResize);
if (this.$refs.incomeChart) { if (this.$refs.incomeChart) {
this.$refs.incomeChart.removeEventListener('scroll', this.handleScroll); this.$refs.incomeChart.removeEventListener('scroll', this.handleScroll);
} }
@ -529,21 +540,30 @@ export default {
} }
}, },
getStoreData() { getStoreData() {
getStore(this.storeId).then(response => { //
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
//
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - Number(this.dateRange));
//
const params = {
startTime: formatDate(start),
endTime: formatDate(end)
};
getStore(this.storeId, params).then(response => {
this.storeData = response.data; this.storeData = response.data;
this.gateForm = { //
unlockMode: response.data.unlockMode || "1",
unlockCondition: response.data.unlockCondition || "3",
unlockTime: response.data.unlockTime || 0
};
//
if (this.map) {
this.map.clearMap();
this.map.destroy();
this.map = null;
}
this.$nextTick(() => { this.$nextTick(() => {
this.initMap(); this.initIncomeChart();
}); });
}); });
}, },
@ -698,26 +718,141 @@ export default {
submitGateConfig() { submitGateConfig() {
this.$refs["gateForm"].validate(valid => { this.$refs["gateForm"].validate(valid => {
if (valid) { if (valid) {
gateAuth({ //
storeId: this.storeData.storeId, if (this.storeData.gateId) {
unlockMode: this.gateForm.unlockMode, // (1)(false)(2)(true)
unlockCondition: this.gateForm.unlockCondition, const open = this.gateForm.unlockMode === "2";
unlockTime: this.gateForm.unlockTime deviceSwitch({
}).then(() => { deviceId: this.storeData.gateId,
this.$modal.msgSuccess("配置成功"); open: open
this.gateConfigVisible = false; }).then(() => {
this.getStoreData(); // //
}); this.saveGateConfig();
}).catch(() => {
this.$modal.msgError("设备开关操作失败");
});
} else {
this.$modal.msgError("未找到大门设备");
}
} }
}); });
}, },
/** 保存大门配置 */
saveGateConfig() {
gateAuth({
storeId: this.storeData.storeId,
unlockMode: this.gateForm.unlockMode,
unlockCondition: this.gateForm.unlockCondition,
unlockTime: this.gateForm.unlockTime
}).then(() => {
this.$modal.msgSuccess("配置成功");
this.gateConfigVisible = false;
this.getStoreData(); //
});
},
handleOpenGate() { handleOpenGate() {
this.$modal.confirm('是否确认为"' + this.storeData.name + '"开门?').then(() => { this.$modal.confirm('是否确认为"' + this.storeData.name + '"开门?').then(() => {
return openGate(this.storeData.storeId); return openGate(this.storeData.storeId);
}).then(() => { }).then(() => {
this.$modal.msgSuccess("开门成功"); this.$modal.msgSuccess("开门成功");
}).catch(() => {}); }).catch(() => {});
} },
/** 初始化收入趋势图表 */
initIncomeChart() {
if (this.incomeChart) {
this.incomeChart.dispose();
}
this.incomeChart = echarts.init(document.getElementById('incomeChart'));
//
const days = this.storeData.inComeList.map(item => {
// "2025-01-19" "01-19"
return item.day.substring(5); // 5
});
const incomes = this.storeData.inComeList.map(item => item.orderIncome);
const orderNums = this.storeData.inComeList.map(item => item.orderNum || 0);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['营收金额', '订单数量']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: days,
axisLabel: {
rotate: 45
}
},
yAxis: [
{
type: 'value',
name: '营收金额(元)',
position: 'left'
},
{
type: 'value',
name: '订单数量',
position: 'right'
}
],
series: [
{
name: '营收金额',
type: 'line',
smooth: true,
data: incomes,
itemStyle: {
color: '#409EFF'
}
},
{
name: '订单数量',
type: 'bar',
yAxisIndex: 1,
data: orderNums,
itemStyle: {
color: '#67C23A'
}
}
]
};
this.incomeChart.setOption(option);
},
/** 切换日期范围 */
handleDateRangeChange(days) {
this.dateRange = days;
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - days);
//
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 使 getStoreData
this.getStoreData({
startTime: formatDate(start),
endTime: formatDate(end)
});
},
} }
} }
</script> </script>
@ -836,42 +971,6 @@ export default {
} }
} }
:deep(.el-descriptions) {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
.el-descriptions__label {
font-weight: 500;
color: #606266;
min-width: 90px;
}
.el-descriptions__content {
padding: 16px;
}
.el-descriptions__body {
background-color: #fff;
}
.el-descriptions-item__label {
background-color: #f5f7fa;
font-weight: 500;
}
.el-descriptions-item__content {
display: flex;
align-items: center;
}
}
.info-text {
color: #606266;
font-size: 14px;
}
.info-card { .info-card {
margin-bottom: 20px; margin-bottom: 20px;
@ -1120,4 +1219,16 @@ export default {
} }
} }
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.date-range {
.el-radio-group {
margin-left: 16px;
}
}
</style> </style>

View File

@ -247,6 +247,7 @@ import { listStore } from "@/api/system/store";
import { $showColumns } from '@/utils/mixins'; import { $showColumns } from '@/utils/mixins';
import QrCode from "@/components/QrCode/index.vue"; import QrCode from "@/components/QrCode/index.vue";
import { getDomain } from "@/api/common/common"; import { getDomain } from "@/api/common/common";
import { deviceSwitch } from "@/api/system/device";
// //
const defaultSort = { const defaultSort = {
@ -326,7 +327,9 @@ export default {
rules: { rules: {
}, },
// //
storeOptions: [] storeOptions: [],
//
oldForm: null
}; };
}, },
created() { created() {
@ -414,6 +417,8 @@ export default {
const toiletId = row.toiletId || this.ids const toiletId = row.toiletId || this.ids
getToilet(toiletId).then(response => { getToilet(toiletId).then(response => {
this.form = response.data; this.form = response.data;
//
this.oldForm = { ...response.data };
this.open = true; this.open = true;
this.title = "修改卫生间"; this.title = "修改卫生间";
}); });
@ -422,22 +427,47 @@ export default {
submitForm() { submitForm() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
if (this.form.toiletId != null) { //
updateToilet(this.form).then(response => { if (this.form.deviceId &&
this.$modal.msgSuccess("修改成功"); this.form.unlockMode !== this.oldForm?.unlockMode) {
this.open = false; // (1)(false)(2)(true)
this.getList(); const open = this.form.unlockMode === "2";
deviceSwitch({
deviceId: this.form.deviceId,
open: open
}).then(() => {
//
this.saveForm();
}).catch(() => {
this.$modal.msgError("设备开关操作失败");
}); });
} else { } else {
addToilet(this.form).then(response => { //
this.$modal.msgSuccess("新增成功"); this.saveForm();
this.open = false;
this.getList();
});
} }
} }
}); });
}, },
/** 保存表单 */
saveForm() {
let data = {
...this.form
};
if (this.form.toiletId != null) {
updateToilet(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addToilet(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
},
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { handleDelete(row) {
const toiletIds = row.toiletId || this.ids; const toiletIds = row.toiletId || this.ids;

View File

@ -31,9 +31,9 @@
<div>用户详情</div> <div>用户详情</div>
</el-row> </el-row>
<el-row type="flex" justify="end" class="mb-2"> <el-row type="flex" justify="end" class="mb-2">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="handleUpdate" <!-- <el-button type="primary" icon="el-icon-edit" size="mini" @click="handleUpdate"
v-hasPermi="['system:smUser:edit']">修改</el-button> v-hasPermi="['system:smUser:edit']">修改</el-button>-->
<el-button type="primary" icon="el-icon-setting" size="mini" @click="handleUpdateRisk" <el-button type="text" icon="el-icon-setting" size="mini" @click="handleUpdateRisk"
v-hasPermi="['system:smUser:edit']">配置</el-button> v-hasPermi="['system:smUser:edit']">配置</el-button>
</el-row> </el-row>
</template> </template>
@ -93,7 +93,7 @@
<div class="filter-section"> <div class="filter-section">
<el-date-picker v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日期" <el-date-picker v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" :picker-options="pickerOptions" value-format="yyyy-MM-dd" end-placeholder="结束日期" :picker-options="pickerOptions" value-format="yyyy-MM-dd"
@change="loadDailyReport" /> @change="handleDateRangeChange" />
</div> </div>
<div class="chart-container"> <div class="chart-container">
<div ref="dailyChart" style="width: 100%; height: 300px"></div> <div ref="dailyChart" style="width: 100%; height: 300px"></div>
@ -461,10 +461,36 @@ export default {
}); });
}, },
handleDateRangeChange(dates) {
if (!dates) {
// 使7
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 7);
this.dateRange = [this.formatDate(start), this.formatDate(end)];
} else {
// dates [startDate, endDate]
this.dateRange = dates;
}
// 使 getDetail
this.getDetail();
},
getDetail() { getDetail() {
//
const params = {
startDate: this.dateRange[0],
endDate: this.dateRange[1]
};
this.loading = true; this.loading = true;
getUser(this.$route.params.userId).then(response => { getUser(this.$route.params.userId, params).then(response => {
this.detail = response.data; this.detail = response.data;
//
this.$nextTick(() => {
this.initDailyChart();
});
}).finally(() => { }).finally(() => {
this.loading = false; this.loading = false;
}); });
@ -534,20 +560,36 @@ export default {
}, },
loadDailyReport() { loadDailyReport() {
this.dailyReportData = Array.from({ length: 7 }, (_, i) => { if (!this.dateRange) return;
const date = new Date();
date.setDate(date.getDate() - i); //
return { const [startDate, endDate] = this.dateRange;
date: this.formatDate(date),
rechargeAmount: Math.floor(Math.random() * 10000), //
consumeAmount: Math.floor(Math.random() * 8000), getUser(this.$route.params.userId, {
orderCount: Math.floor(Math.random() * 100) startDate,
}; endDate
}).reverse(); }).then(response => {
this.detail = response.data;
this.$nextTick(() => {
this.initDailyChart();
});
});
},
initDailyChart() {
if (!this.dailyChart) {
this.dailyChart = echarts.init(this.$refs.dailyChart);
}
// 使 API
const days = this.detail.inComeList.map(item => item.day.substring(5)); // MM-dd
const incomes = this.detail.inComeList.map(item => item.orderIncome);
const orderNums = this.detail.inComeList.map(item => item.orderNum);
const option = { const option = {
title: { title: {
text: '日报表统计' text: '收入统计'
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
@ -556,7 +598,7 @@ export default {
} }
}, },
legend: { legend: {
data: ['充值金额', '消费金额', '订单数'] data: ['收入金额']
}, },
grid: { grid: {
left: '3%', left: '3%',
@ -566,47 +608,45 @@ export default {
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: this.dailyReportData.map(item => item.date) boundaryGap: true,
data: days,
axisLabel: {
rotate: 45
}
}, },
yAxis: [ yAxis: {
{ type: 'value',
type: 'value', axisLabel: {
name: '金额', formatter: '{value}元'
axisLabel: {
formatter: '{value} 元'
}
}, },
{ splitLine: {
type: 'value', show: true,
name: '订单数', lineStyle: {
axisLabel: { type: 'dashed'
formatter: '{value}'
} }
} }
], },
series: [ series: [
{ {
name: '充值金额', name: '收入金额',
type: 'bar', type: 'bar',
data: this.dailyReportData.map(item => item.rechargeAmount) itemStyle: {
}, normal: {
{ color: '#409EFF',
name: '消费金额', barBorderRadius: [4, 4, 0, 0]
type: 'bar', }
data: this.dailyReportData.map(item => item.consumeAmount) },
}, data: incomes,
{ label: {
name: '订单数', show: true,
type: 'line', position: 'top',
yAxisIndex: 1, formatter: '{c}元'
data: this.dailyReportData.map(item => item.orderCount) }
} }
] ]
}; };
if (this.dailyChart) { this.dailyChart.setOption(option);
this.dailyChart.setOption(option);
}
}, },
loadMonthlyReport() { loadMonthlyReport() {
@ -827,7 +867,7 @@ export default {
} }
.report-container { .report-container {
padding: 15px; padding: 15px 15px 15px 0;
} }
.filter-section { .filter-section {