share-space-vue/src/views/system/store/store_detail.vue
2025-03-03 15:29:30 +08:00

1269 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="store-detail">
<!-- 基本信息卡片 -->
<el-card class="info-card" shadow="hover">
<div slot="header" class="card-header">
<div class="header-left">
<span class="title">店铺详情</span>
<el-tag size="small" :type="getStatusType(storeData.status)">
{{ getStatusText(storeData.status) }}
</el-tag>
</div>
<div class="action-buttons">
<el-button
type="success"
size="small"
icon="el-icon-unlock"
v-if="storeData.gateSn"
@click="handleOpenGate"
>开大门</el-button>
<el-button
type="primary"
size="small"
icon="el-icon-setting"
v-if="storeData.gateSn"
@click="handleGateConfig"
>大门配置</el-button>
<el-button type="success" size="small" @click="handleUpdate">修改</el-button>
<el-button type="danger" size="small" @click="handleDelete">删除</el-button>
</div>
</div>
<el-row :gutter="20">
<el-col :span="6">
<div class="store-image">
<el-carousel height="200px" indicator-position="outside" :autoplay="true" trigger="click" arrow="always">
<el-carousel-item v-for="(url, index) in storeData.pictures" :key="index">
<el-image
:src="url"
fit="fill"
style="width: 100%; height: 100%"
>
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</el-carousel-item>
</el-carousel>
</div>
</el-col>
<el-col :span="18">
<div class="store-title">
<h2>{{ storeData.name }}</h2>
<!-- <el-tag :type="getStatusType(storeData.status)" class="status-tag" effect="dark">
<dict-tag :options="dict.type.ss_store_status" :value="storeData.status" />
</el-tag> -->
</div>
<div class="info-content">
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="联系人">
<span class="info-text">{{ storeData.contactName || '--' }}</span>
</el-descriptions-item>
<el-descriptions-item label="联系电话">
<span class="info-text">{{ storeData.contactMobile || '--' }}</span>
</el-descriptions-item>
<el-descriptions-item label="二维码">
<el-popover placement="top" trigger="hover">
<div class="qr-code-box">
<qr-code :text="getCodeText(storeData)" :width="150" :height="150" />
<p>扫描二维码进入店铺</p>
</div>
<el-button slot="reference" type="text" icon="el-icon-picture">查看</el-button>
</el-popover>
</el-descriptions-item>
<el-descriptions-item label="商户" class="device-detail2">
<router-link
v-if="storeData.merchantId"
:to="'/user/detail/' + storeData.merchantId"
class="link-type">
<span >{{ storeData.merchantName || '--' }}</span>
</router-link>
</el-descriptions-item>
<el-descriptions-item label="客服电话">
<span class="info-text">{{ storeData.serverPhone || '--' }}</span>
</el-descriptions-item>
<el-descriptions-item label="营业时间">
<span class="info-text">{{ formatBusinessTime(storeData.businessTimeStart, storeData.businessTimeEnd) }}</span>
</el-descriptions-item>
<el-descriptions-item label="身份证号">
<span class="info-text">{{ storeData.idcard || '--' }}</span>
</el-descriptions-item>
<el-descriptions-item label="大门" >
<router-link
v-if="storeData.gateSn"
:to="`/system/deviceDetail/index/${storeData.gateId}`"
class="link-type"
>
{{ storeData.gateSn }}
</router-link>
<el-tag
v-if="storeData.gateSn"
type="text"
size="mini"
effect="dark"
@click="handleUnbindGate"
style="cursor: pointer; margin-left: 10px;">解绑
</el-tag>
<el-button v-else type="primary" @click="showBindGateDialog = true">去绑定</el-button>
</el-descriptions-item>
<el-descriptions-item label="地址" :span="3">
<span class="info-text">{{ storeData.address || '--' }}</span>
</el-descriptions-item>
<el-descriptions-item label="标签" :span="2">
<dict-tag :options="dict.type.ss_store_tags" :value="storeData.tags" />
</el-descriptions-item>
<el-descriptions-item label="类型">
<dict-tag :options="dict.type.ss_room_type" :value="storeData.typeTags" />
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-row>
</el-card>
<!-- 顶部统计卡片 -->
<el-row :gutter="20" class="stat-cards">
<!-- 今日营收 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon income">
<i class="el-icon-money"></i>
</div>
<div class="stat-content">
<div class="stat-label">今日营收</div>
<div class="stat-value">¥ {{ storeData.todayIncome || '0.00' }}</div>
</div>
</el-card>
</el-col>
<!-- 本月营收 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon income-month">
<i class="el-icon-wallet"></i>
</div>
<div class="stat-content">
<div class="stat-label">本月营收</div>
<div class="stat-value">¥ {{ storeData.monthIncome || '0.00' }}</div>
</div>
</el-card>
</el-col>
<!-- 总营收 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon total-income">
<i class="el-icon-bank-card"></i>
</div>
<div class="stat-content">
<div class="stat-label">总营收</div>
<div class="stat-value">¥ {{ storeData.totalIncome || '0.00' }}</div>
</div>
</el-card>
</el-col>
<!-- 房间统计 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon room">
<i class="el-icon-office-building"></i>
</div>
<div class="stat-content">
<div class="stat-label">房间使用</div>
<div class="stat-numbers">
<span class="current">{{ storeData.usingRoomNum || 0 }}</span>
<span class="divider">/</span>
<span class="total">{{ storeData.totalRoomNum || 0 }}</span>
</div>
</div>
</el-card>
</el-col>
<!-- 设施统计 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon facility">
<i class="el-icon-s-platform"></i>
</div>
<div class="stat-content">
<div class="stat-label">设施使用</div>
<div class="stat-numbers">
<span class="current">{{ storeData.usingFacilityNum || 0 }}</span>
<span class="divider">/</span>
<span class="total">{{ storeData.totalFacilities || 0 }}</span>
</div>
</div>
</el-card>
</el-col>
<!-- 设备数量 -->
<el-col :span="4">
<el-card shadow="hover" class="stat-card">
<div class="stat-icon device">
<i class="el-icon-cpu"></i>
</div>
<div class="stat-content">
<div class="stat-label">设备数量</div>
<div class="stat-numbers">
<span class="total">{{ storeData.totalDeviceNum || 0 }}</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 图表和地图区域 -->
<el-row :gutter="20" class="chart-section">
<el-col :span="16">
<el-card class="box-card" shadow="hover">
<div slot="header" class="card-header">
<span>营收趋势</span>
<div class="date-range">
<el-radio-group v-model="dateRange" size="small" @change="handleDateRangeChange">
<el-radio-button label="7">近7天</el-radio-button>
<el-radio-button label="30">近30天</el-radio-button>
</el-radio-group>
</div>
</div>
<div id="incomeChart" style="width: 100%; height: 400px;"></div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="map-card" shadow="hover">
<div slot="header" class="card-header">
<span class="title">店铺位置</span>
</div>
<div class="map-container" id="mapContainer"></div>
</el-card>
</el-col>
</el-row>
<el-tabs v-model="activeTab" class="detail-tabs">
<el-tab-pane label="订单列表" name="orders" :lazy="true">
<order v-if="storeId != null"
:query="{
storeId: Number(storeId)
}"/>
</el-tab-pane>
<el-tab-pane label="房间列表" name="rooms" :lazy="true">
<room :query="{
storeId: Number(storeId),
merchantId: storeData.merchantId
}"/>
</el-tab-pane>
<el-tab-pane label="大厅设施" name="equipments" :lazy="true">
<equipment :query="{
storeId: Number(storeId),
merchantId: storeData.merchantId
}"/>
</el-tab-pane>
<el-tab-pane label="设备列表" name="devices" :lazy="true">
<device :query="{
storeId: Number(storeId)
}"/>
</el-tab-pane>
<el-tab-pane label="员工列表" name="users" :lazy="true">
<user :query="{
storeId: Number(storeId)
}"/>
</el-tab-pane>
<el-tab-pane label="卫生间列表" name="toilets" :lazy="true">
<toilet :query="{
storeId: Number(storeId)
}"/>
</el-tab-pane>
</el-tabs>
<BindGateDialog
:visible.sync="showBindGateDialog"
:query="{
merchantId: storeData.merchantId,
storeName: storeData.storeName,
storeId: storeData.storeId
}"
@bind-success="getStoreData"
/>
<!-- 添加或修改店铺对话框 -->
<store-form
:visible.sync="showStoreForm"
:query="{
merchantId: storeData.merchantId,
isAdmin: isAdmin,
storeId: currentStoreId
}"
@success="handleFormSuccess"
@cancel="handleFormCancel"
/>
<!-- 大门配置弹窗 -->
<el-dialog
title="大门配置"
:visible.sync="gateConfigVisible"
width="800px"
append-to-body
>
<el-form ref="gateForm" :model="gateForm" :rules="gateRules" label-width="80px">
<el-form-item label="开锁方式" prop="unlockMode">
<el-radio-group v-model="gateForm.unlockMode">
<el-radio
v-for="dict in dict.type.ss_unlock_mode"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="开锁条件" prop="unlockCondition">
<el-radio-group v-model="gateForm.unlockCondition">
<el-radio
v-for="dict in dict.type.ss_unlock_condition"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="开锁时长" prop="unlockTime">
<el-input-number
v-model="gateForm.unlockTime"
:min="0"
:precision="0"
:step="1"
controls-position="right"
style="width: 200px">
<template slot="append">秒</template>
</el-input-number>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="gateConfigVisible = false">取 消</el-button>
<el-button type="primary" @click="submitGateConfig">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getStore,
delStore,
bindGateApi,
unbindDevice,
gateAuth,
openGate,
updateStore
} from "@/api/system/store";
import {getDevice} from "@/api/system/device";
import * as echarts from 'echarts';
import AMapLoader from "@amap/amap-jsapi-loader";
import globalConfig from '@/utils/config/globalConfig';
import order from '@/views/system/order/index.vue';
import room from '@/views/system/room/index.vue';
import equipment from '@/views/system/hallEqu/index.vue';
import device from '@/views/system/device/index.vue';
import user from '@/views/user/user/index.vue';
import toilet from '@/views/system/toilet/index.vue';
import BindGateDialog from './components/BindGateDialog.vue';
import QrCode from '@/components/QrCode';
import { getDomain } from "@/api/common/common";
import { deviceSwitch } from "@/api/system/device";
import StoreForm from "@/views/system/store/components/StoreForm.vue";
export default {
name: 'StoreDetail',
dicts: ['ss_store_status', 'ss_store_tags', 'ss_room_type', 'ss_unlock_mode', 'ss_unlock_condition'],
components: {
StoreForm,
order,
room,
equipment,
device,
user,
toilet,
BindGateDialog,
QrCode
},
computed: {
isAdmin() {
return this.$store.state.user.name === 'admin';
}
},
data() {
return {
storeId: Number(this.$route.params.id),
storeData: {},
map: null,
marker: null,
stats: {
todayIncome: 0,
monthIncome: 0,
totalIncome: 0,
totalWithdraw: 0,
totalRoomNum: 0,
usingRoomNum: 0,
totalFacilities: 0,
usingFacilityNum: 0
},
chartTimeRange: 'week',
incomeChart: null,
activeTab: 'orders',
showBindGateDialog: false,
equipmentList: [],
domain: '',
gateConfigVisible: false,
gateForm: {
unlockMode: "1",
unlockCondition: "3",
unlockTime: 0
},
gateRules: {
unlockMode: [
{ required: true, message: "请选择开锁方式", trigger: "change" }
],
unlockCondition: [
{ required: true, message: "请选择开锁条件", trigger: "change" }
],
unlockTime: [
{ required: true, message: "请输入开锁时长", trigger: "blur" }
]
},
dateRange: '7', // 默认7天
showStoreForm: false,
currentStoreId: null
}
},
created() {
this.storeId = this.$route.params.storeId;
this.getStoreData();
this.loadEquipmentList();
this.getDicts();
this.getDomain();
},
mounted() {
this.$nextTick(() => {
this.initMap();
this.initChart();
if (this.$refs.incomeChart) {
this.$refs.incomeChart.addEventListener('scroll', this.handleScroll);
}
});
// 监听窗口大小变化,重绘图表
window.addEventListener('resize', () => {
if (this.incomeChart) {
this.incomeChart.resize();
}
});
},
beforeDestroy() {
if (this.incomeChart) {
this.incomeChart.dispose();
}
if (this.map) {
this.map.clearMap();
this.map.destroy();
this.map = null;
}
window.removeEventListener('resize', this.handleResize);
if (this.$refs.incomeChart) {
this.$refs.incomeChart.removeEventListener('scroll', this.handleScroll);
}
},
methods: {
handleScroll() {
if (!this.$refs.incomeChart || !this.incomeChart) return;
const scrollTop = this.$refs.incomeChart.scrollTop;
const scrollHeight = this.$refs.incomeChart.scrollHeight;
const clientHeight = this.$refs.incomeChart.clientHeight;
if (scrollTop + clientHeight >= scrollHeight) {
console.log('Reached bottom');
}
},
getStatusType(status) {
const statusMap = {
0: 'info',
1: 'success',
2: 'danger'
};
return statusMap[status] || 'info';
},
getStatusText(status) {
const statusMap = {
0: '停用',
1: '正常',
2: '禁用'
};
return statusMap[status] || '未知';
},
initChart() {
if (!this.$refs.incomeChart) return;
this.incomeChart = echarts.init(this.$refs.incomeChart);
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}<br />{a}: ¥{c}'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
axisLine: {
lineStyle: {
color: '#DCDFE6'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '¥{value}'
},
axisLine: {
show: false
},
splitLine: {
lineStyle: {
color: '#EBEEF5'
}
}
},
series: [{
name: '营收',
type: 'line',
smooth: true,
data: [820, 932, 901, 934, 1290, 1330, 1320],
itemStyle: {
color: '#409EFF'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(64,158,255,0.3)'
}, {
offset: 1,
color: 'rgba(64,158,255,0.1)'
}])
}
}]
};
this.incomeChart.setOption(option);
window.addEventListener('resize', this.resizeChart);
},
resizeChart() {
if (this.incomeChart) {
this.incomeChart.resize();
}
},
getStoreData() {
// 格式化日期
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.$nextTick(() => {
this.initIncomeChart();
});
});
},
getStoreStats() {
// Mock数据实际项目中替换为API调用
// this.stats = {
// todayIncome: 1234.56,
// monthIncome: 45678.90,
// totalDeviceNum: 100,
// onlineDevices: 85
// };
},
handleChartRangeChange(range) {
// Mock数据实际项目中替换为API调用
const mockData = {
week: {
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
series: [820, 932, 901, 934, 1290, 1330, 1320]
},
month: {
xAxis: Array.from({ length: 30 }, (_, i) => `${i + 1}日`),
series: Array.from({ length: 30 }, () => Math.floor(Math.random() * 2000 + 500))
}
};
const data = mockData[range];
this.incomeChart.setOption({
xAxis: { data: data.xAxis },
series: [{ data: data.series }]
});
},
formatBusinessTime(start, end) {
if (!start && !end) return '--';
return `${start || '--'} - ${end || '--'}`;
},
async initMap() {
try {
if (!window._AMapSecurityConfig) {
window._AMapSecurityConfig = {
securityJsCode: process.env.VUE_APP_AMAP_SECURITY_CODE,
}
}
const AMap = await AMapLoader.load({
key: globalConfig.aMap.key,
version: "2.0",
plugins: []
});
if (!document.getElementById('mapContainer')) {
console.error('地图容器不存在');
return;
}
this.map = new AMap.Map("mapContainer", {
zoom: 15,
center: [
parseFloat(this.storeData.lng) || 116.397428,
parseFloat(this.storeData.lat) || 39.90923
],
resizeEnable: true,
viewMode: '2D'
});
// 如果有经纬度,添加标记点
if (parseFloat(this.storeData.lng) && parseFloat(this.storeData.lat)) {
// 创建标记点
this.marker = new AMap.Marker({
position: [parseFloat(this.storeData.lng), parseFloat(this.storeData.lat)],
title: this.storeData.name
});
// 将标记点添加到地图
this.map.add(this.marker);
// 创建信息窗体
const infoWindow = new AMap.InfoWindow({
content: `
<div class="info-window">
<h4>${this.storeData.name || '未命名店铺'}</h4>
<p>${this.storeData.address || '暂无地址'}</p>
<p>经度${this.storeData.lng}</p>
<p>纬度${this.storeData.lat}</p>
</div>
`,
offset: new AMap.Pixel(0, -30)
});
// 点击标记点时打开信息窗体
this.marker.on('click', () => {
infoWindow.open(this.map, this.marker.getPosition());
});
// 自动适应标记点位置
this.map.setFitView();
}
} catch (e) {
console.error("地图加载失败", e);
}
},
handleEdit() {
this.$router.push(`/system/store/edit/${this.storeId}`);
},
handleDelete() {
this.$modal.confirm('是否确认删除该店铺?').then(() => {
return delStore(this.storeId);
}).then(() => {
this.$modal.msgSuccess("删除成功");
this.$router.push('/system/store/list');
}).catch(() => { });
},
bindGate() {
// 调用绑定大门的接口
bindGateApi().then(response => {
this.$modal.msgSuccess("绑定成功");
this.fetchStoreDetail(); // 重新获取店铺详情
}).catch(error => {
this.$modal.msgError("绑定失败");
});
},
loadEquipmentList() {
// 实现加载设施列表的逻辑
// 例如this.equipmentList = response.data;
},
handleUnbindGate() {
this.$modal.confirm('确定要解除该店铺的大门绑定吗?').then(() => {
// 调用解绑接口
unbindDevice(this.storeData.gateId).then(() => {
this.$modal.msgSuccess("解绑成功");
this.getStoreData(); // 重新获取店铺数据
}).catch(() => {
this.$modal.msgError("解绑失败");
});
}).catch(() => {});
},
getDomain() {
getDomain().then(response => {
if (response.data) {
this.domain = response.data;
}
}).catch(() => {
this.$message.error("获取全局域名失败")
})
},
getCodeText(store) {
let url = this.domain + `?d=` + store.qrText;
return this.domain ? url : '';
},
handleGateConfig() {
this.gateConfigVisible = true;
},
submitGateConfig() {
this.$refs["gateForm"].validate(valid => {
if (valid) {
// 先发送开关命令
if (this.storeData.gateId) {
// 通电开锁(1)时发送断电命令(false),断电开锁(2)时发送通电命令(true)
const open = this.gateForm.unlockMode === "2";
deviceSwitch({
deviceId: this.storeData.gateId,
open: open
}).then(() => {
// 开关命令成功后,保存配置
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() {
this.$modal.confirm('是否确认为"' + this.storeData.name + '"开门?').then(() => {
return openGate(this.storeData.storeId);
}).then(() => {
this.$modal.msgSuccess("开门成功");
}).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)
});
},
handleFormSuccess() {
this.getList();
},
handleFormCancel() {
this.showStoreForm = false;
},
/** 修改按钮操作 */
handleUpdate() {
this.currentStoreId = this.storeId;
console.log("this.currentStoreId----------"+this.currentStoreId)
this.showStoreForm = true;
},
}
}
</script>
<style lang="scss" scoped>
.store-detail {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 84px);
.el-row {
height: 100%;
.el-col {
height: 100%;
}
}
.store-image {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
height: 200px;
&:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
:deep(.el-carousel) {
.el-carousel__indicators {
bottom: -20px;
}
.el-carousel__arrow {
background-color: rgba(0, 0, 0, 0.3);
&:hover {
background-color: rgba(0, 0, 0, 0.5);
}
}
.el-carousel__item {
overflow: hidden;
.el-image {
width: 100%;
height: 100%;
display: block;
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 30px;
}
}
}
}
}
.info-content {
flex: 1;
display: flex;
flex-direction: column;
height: calc(100% - 80px); // 减去标题的高度
:deep(.el-descriptions) {
height: 100%;
display: flex;
flex-direction: column;
.el-descriptions__body {
flex: 1;
display: flex;
flex-direction: column;
.el-descriptions-item {
flex: 1;
display: flex;
.el-descriptions-item__container {
flex: 1;
display: flex;
}
}
}
}
}
.store-title {
display: flex;
align-items: center;
padding-bottom: 16px;
height: 40px;
h2 {
margin: 0;
margin-right: 15px;
font-size: 28px;
color: #303133;
font-weight: 600;
}
.status-tag {
margin-left: auto;
padding: 0 16px;
height: 28px;
line-height: 28px;
font-size: 12px;
}
}
.info-card {
margin-bottom: 20px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-left {
display: flex;
align-items: center;
.title {
font-size: 16px;
font-weight: bold;
margin-right: 12px;
}
.status-tag {
margin-left: 8px;
}
}
.action-buttons {
.el-button {
margin-left: 10px;
}
}
}
.info-item {
margin-bottom: 16px;
display: flex;
align-items: center;
.label {
color: #909399;
margin-right: 12px;
min-width: 80px;
font-size: 14px;
}
.value {
color: #303133;
font-size: 14px;
}
}
}
.detail-tabs {
margin-top: 20px;
.search-form {
margin-bottom: 20px;
.el-form-item {
margin-bottom: 10px;
}
}
}
.stat-cards {
margin-bottom: 20px;
.stat-card {
height: 120px;
display: flex;
padding: 0 10px;
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 16px;
i {
font-size: 24px;
color: #fff;
}
&.income {
background: linear-gradient(135deg, #36d1dc, #5b86e5);
}
&.income-month {
background: linear-gradient(135deg, #ff9a9e, #fad0c4);
}
&.total-income {
background: linear-gradient(135deg, #a8edea, #fed6e3);
}
&.withdraw {
background: linear-gradient(135deg, #84fab0, #8fd3f4);
}
&.room {
background: linear-gradient(135deg, #fbc2eb, #a6c1ee);
}
&.facility {
background: linear-gradient(135deg, #f6d365, #fda085);
}
&.device {
background: linear-gradient(135deg, #84fab0, #8fd3f4);
}
}
.stat-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.stat-value {
font-size: 22px;
font-weight: bold;
color: #303133;
margin-bottom: 8px;
line-height: 1.2;
}
.stat-numbers {
font-size: 22px;
font-weight: bold;
color: #303133;
margin-bottom: 8px;
line-height: 1.2;
.current {
color: #409EFF;
}
.divider {
margin: 0 4px;
color: #909399;
}
.total {
color: #606266;
}
}
.stat-label {
font-size: 14px;
color: #909399;
}
}
}
}
.chart-section {
margin-bottom: 20px;
.card-header {
.title {
font-size: 16px;
font-weight: bold;
}
}
.chart-card {
.chart-container {
height: 350px;
padding: 10px;
}
}
.map-card {
.map-container {
height: 350px;
width: 100%;
position: relative;
border: 1px solid #EBEEF5;
border-radius: 4px;
overflow: hidden;
}
}
}
}
:deep(.info-window) {
padding: 12px;
min-width: 200px;
h4 {
margin: 0 0 8px;
color: #303133;
font-size: 16px;
font-weight: bold;
}
p {
margin: 4px 0;
color: #606266;
font-size: 14px;
line-height: 1.4;
}
}
:deep(.el-tag) {
margin-right: 8px;
}
.link-type {
color: #409EFF;
text-decoration: underline;
cursor: pointer;
}
.link-type:hover {
color: #66b1ff;
}
.device-detail2 {
padding: 20px;
.link-type {
color: #11A983;
text-decoration: none;
cursor: pointer;
}
.link-type:hover {
text-decoration: underline;
}
}
.qr-code-box {
text-align: center;
padding: 10px;
p {
margin: 10px 0 0;
color: #666;
font-size: 14px;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.date-range {
.el-radio-group {
margin-left: 16px;
}
}
</style>