Merge branch 'master' of http://47.109.37.87:3000/by2025/admin-vben5
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run

# Conflicts:
#	apps/web-antd/vite.config.mts
This commit is contained in:
lxj
2025-07-10 18:39:11 +08:00
94 changed files with 9470 additions and 64 deletions

View File

@@ -0,0 +1,61 @@
import type { InspectionItemVO, InspectionItemForm, InspectionItemQuery } from './model';
import type { ID, IDS } from '#/api/common';
import type { PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询巡检项目列表
* @param params
* @returns 巡检项目列表
*/
export function inspectionItemList(params?: InspectionItemQuery) {
return requestClient.get<PageResult<InspectionItemVO>>('/property/inspectionItem/list', { params });
}
/**
* 导出巡检项目列表
* @param params
* @returns 巡检项目列表
*/
export function inspectionItemExport(params?: InspectionItemQuery) {
return commonExport('/property/inspectionItem/export', params ?? {});
}
/**
* 查询巡检项目详情
* @param id id
* @returns 巡检项目详情
*/
export function inspectionItemInfo(id: ID) {
return requestClient.get<InspectionItemVO>(`/property/inspectionItem/${id}`);
}
/**
* 新增巡检项目
* @param data
* @returns void
*/
export function inspectionItemAdd(data: InspectionItemForm) {
return requestClient.postWithMsg<void>('/property/inspectionItem', data);
}
/**
* 更新巡检项目
* @param data
* @returns void
*/
export function inspectionItemUpdate(data: InspectionItemForm) {
return requestClient.putWithMsg<void>('/property/inspectionItem', data);
}
/**
* 删除巡检项目
* @param id id
* @returns void
*/
export function inspectionItemRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/inspectionItem/${id}`);
}

View File

@@ -0,0 +1,49 @@
import type { PageQuery, BaseEntity } from '#/api/common';
export interface InspectionItemVO {
/**
* 主键id
*/
id: string | number;
/**
* 项目名称
*/
itemName: string;
/**
* 备注
*/
remark: string;
}
export interface InspectionItemForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 项目名称
*/
itemName?: string;
/**
* 备注
*/
remark?: string;
}
export interface InspectionItemQuery extends PageQuery {
/**
* 项目名称
*/
itemName?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,61 @@
import type { InspectionPlanVO, InspectionPlanForm, InspectionPlanQuery } from './model';
import type { ID, IDS } from '#/api/common';
import type { PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询巡检计划列表
* @param params
* @returns 巡检计划列表
*/
export function inspectionPlanList(params?: InspectionPlanQuery) {
return requestClient.get<PageResult<InspectionPlanVO>>('/property/inspectionPlan/list', { params });
}
/**
* 导出巡检计划列表
* @param params
* @returns 巡检计划列表
*/
export function inspectionPlanExport(params?: InspectionPlanQuery) {
return commonExport('/property/inspectionPlan/export', params ?? {});
}
/**
* 查询巡检计划详情
* @param id id
* @returns 巡检计划详情
*/
export function inspectionPlanInfo(id: ID) {
return requestClient.get<InspectionPlanVO>(`/property/inspectionPlan/${id}`);
}
/**
* 新增巡检计划
* @param data
* @returns void
*/
export function inspectionPlanAdd(data: InspectionPlanForm) {
return requestClient.postWithMsg<void>('/property/inspectionPlan', data);
}
/**
* 更新巡检计划
* @param data
* @returns void
*/
export function inspectionPlanUpdate(data: InspectionPlanForm) {
return requestClient.putWithMsg<void>('/property/inspectionPlan', data);
}
/**
* 删除巡检计划
* @param id id
* @returns void
*/
export function inspectionPlanRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/inspectionPlan/${id}`);
}

View File

@@ -0,0 +1,199 @@
import type { PageQuery, BaseEntity } from '#/api/common';
export interface InspectionPlanVO {
/**
* 主键id
*/
id: string | number;
/**
* 巡检计划名称
*/
planName: string;
/**
* 巡检路线id
*/
inspectionRouteId: string | number;
/**
* 巡检周期
*/
inspectionPlanPeriod: number;
/**
* 任务提前分组
*/
beforeTime: number;
/**
* 开始日期
*/
startDate: string;
/**
* 结束日期
*/
endDate: string;
/**
* 开始时间
*/
startTime: string;
/**
* 结束时间
*/
endTime: string;
/**
* 签到方式(0现场定位,1现场定位)
*/
signType: number;
/**
* 允许补检(0允许1不允许)
*/
canReexamine: number;
/**
* 选择员工
*/
userId: string | number;
/**
* 备注
*/
remark: string;
}
export interface InspectionPlanForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 巡检计划名称
*/
planName?: string;
/**
* 巡检路线id
*/
inspectionRouteId?: string | number;
/**
* 巡检周期
*/
inspectionPlanPeriod?: number;
/**
* 任务提前分组
*/
beforeTime?: number;
/**
* 开始日期
*/
startDate?: string;
/**
* 结束日期
*/
endDate?: string;
/**
* 开始时间
*/
startTime?: string;
/**
* 结束时间
*/
endTime?: string;
/**
* 签到方式(0现场定位,1现场定位)
*/
signType?: number;
/**
* 允许补检(0允许1不允许)
*/
canReexamine?: number;
/**
* 选择员工
*/
userId?: string | number;
/**
* 备注
*/
remark?: string;
}
export interface InspectionPlanQuery extends PageQuery {
/**
* 巡检计划名称
*/
planName?: string;
/**
* 巡检路线id
*/
inspectionRouteId?: string | number;
/**
* 巡检周期
*/
inspectionPlanPeriod?: number;
/**
* 任务提前分组
*/
beforeTime?: number;
/**
* 开始日期
*/
startDate?: string;
/**
* 结束日期
*/
endDate?: string;
/**
* 开始时间
*/
startTime?: string;
/**
* 结束时间
*/
endTime?: string;
/**
* 签到方式(0现场定位,1现场定位)
*/
signType?: number;
/**
* 允许补检(0允许1不允许)
*/
canReexamine?: number;
/**
* 选择员工
*/
userId?: string | number;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,61 @@
import type { InspectionPointVO, InspectionPointForm, InspectionPointQuery } from './model';
import type { ID, IDS } from '#/api/common';
import type { PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询巡检点列表
* @param params
* @returns 巡检点列表
*/
export function inspectionPointList(params?: InspectionPointQuery) {
return requestClient.get<PageResult<InspectionPointVO>>('/property/inspectionPoint/list', { params });
}
/**
* 导出巡检点列表
* @param params
* @returns 巡检点列表
*/
export function inspectionPointExport(params?: InspectionPointQuery) {
return commonExport('/property/inspectionPoint/export', params ?? {});
}
/**
* 查询巡检点详情
* @param id id
* @returns 巡检点详情
*/
export function inspectionPointInfo(id: ID) {
return requestClient.get<InspectionPointVO>(`/property/inspectionPoint/${id}`);
}
/**
* 新增巡检点
* @param data
* @returns void
*/
export function inspectionPointAdd(data: InspectionPointForm) {
return requestClient.postWithMsg<void>('/property/inspectionPoint', data);
}
/**
* 更新巡检点
* @param data
* @returns void
*/
export function inspectionPointUpdate(data: InspectionPointForm) {
return requestClient.putWithMsg<void>('/property/inspectionPoint', data);
}
/**
* 删除巡检点
* @param id id
* @returns void
*/
export function inspectionPointRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/inspectionPoint/${id}`);
}

View File

@@ -0,0 +1,139 @@
import type { PageQuery, BaseEntity } from '#/api/common';
export interface InspectionPointVO {
/**
* 主键id
*/
id: string | number;
/**
* 巡检项目id
*/
itemId: string | number;
/**
* 巡检点名称
*/
pointName: string;
/**
* 巡检点类型
*/
pointType: number;
/**
* nfc编码
*/
nfcCode: string;
/**
* 备注
*/
remark: string;
/**
* 创建人id
*/
createById: string | number;
/**
* 更新人id
*/
updateById: string | number;
/**
* 搜索值
*/
searchValue: string;
}
export interface InspectionPointForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 巡检项目id
*/
itemId?: string | number;
/**
* 巡检点名称
*/
pointName?: string;
/**
* 巡检点类型
*/
pointType?: number;
/**
* nfc编码
*/
nfcCode?: string;
/**
* 备注
*/
remark?: string;
/**
* 创建人id
*/
createById?: string | number;
/**
* 更新人id
*/
updateById?: string | number;
/**
* 搜索值
*/
searchValue?: string;
}
export interface InspectionPointQuery extends PageQuery {
/**
* 巡检项目id
*/
itemId?: string | number;
/**
* 巡检点名称
*/
pointName?: string;
/**
* 巡检点类型
*/
pointType?: number;
/**
* nfc编码
*/
nfcCode?: string;
/**
* 创建人id
*/
createById?: string | number;
/**
* 更新人id
*/
updateById?: string | number;
/**
* 搜索值
*/
searchValue?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,61 @@
import type { InspectionRouteVO, InspectionRouteForm, InspectionRouteQuery } from './model';
import type { ID, IDS } from '#/api/common';
import type { PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询巡检路线列表
* @param params
* @returns 巡检路线列表
*/
export function inspectionRouteList(params?: InspectionRouteQuery) {
return requestClient.get<PageResult<InspectionRouteVO>>('/property/inspectionRoute/list', { params });
}
/**
* 导出巡检路线列表
* @param params
* @returns 巡检路线列表
*/
export function inspectionRouteExport(params?: InspectionRouteQuery) {
return commonExport('/property/inspectionRoute/export', params ?? {});
}
/**
* 查询巡检路线详情
* @param id id
* @returns 巡检路线详情
*/
export function inspectionRouteInfo(id: ID) {
return requestClient.get<InspectionRouteVO>(`/property/inspectionRoute/${id}`);
}
/**
* 新增巡检路线
* @param data
* @returns void
*/
export function inspectionRouteAdd(data: InspectionRouteForm) {
return requestClient.postWithMsg<void>('/property/inspectionRoute', data);
}
/**
* 更新巡检路线
* @param data
* @returns void
*/
export function inspectionRouteUpdate(data: InspectionRouteForm) {
return requestClient.putWithMsg<void>('/property/inspectionRoute', data);
}
/**
* 删除巡检路线
* @param id id
* @returns void
*/
export function inspectionRouteRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/inspectionRoute/${id}`);
}

View File

@@ -0,0 +1,49 @@
import type { PageQuery, BaseEntity } from '#/api/common';
export interface InspectionRouteVO {
/**
* 主键id
*/
id: string | number;
/**
* 路线名称
*/
routeName: string;
/**
* 备注
*/
remark: string;
}
export interface InspectionRouteForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 路线名称
*/
routeName?: string;
/**
* 备注
*/
remark?: string;
}
export interface InspectionRouteQuery extends PageQuery {
/**
* 路线名称
*/
routeName?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,61 @@
import type { InspectionTaskVO, InspectionTaskForm, InspectionTaskQuery } from './model';
import type { ID, IDS } from '#/api/common';
import type { PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
/**
* 查询巡检任务列表
* @param params
* @returns 巡检任务列表
*/
export function inspectionTaskList(params?: InspectionTaskQuery) {
return requestClient.get<PageResult<InspectionTaskVO>>('/property/inspectionTask/list', { params });
}
/**
* 导出巡检任务列表
* @param params
* @returns 巡检任务列表
*/
export function inspectionTaskExport(params?: InspectionTaskQuery) {
return commonExport('/property/inspectionTask/export', params ?? {});
}
/**
* 查询巡检任务详情
* @param id id
* @returns 巡检任务详情
*/
export function inspectionTaskInfo(id: ID) {
return requestClient.get<InspectionTaskVO>(`/property/inspectionTask/${id}`);
}
/**
* 新增巡检任务
* @param data
* @returns void
*/
export function inspectionTaskAdd(data: InspectionTaskForm) {
return requestClient.postWithMsg<void>('/property/inspectionTask', data);
}
/**
* 更新巡检任务
* @param data
* @returns void
*/
export function inspectionTaskUpdate(data: InspectionTaskForm) {
return requestClient.putWithMsg<void>('/property/inspectionTask', data);
}
/**
* 删除巡检任务
* @param id id
* @returns void
*/
export function inspectionTaskRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/property/inspectionTask/${id}`);
}

View File

@@ -0,0 +1,169 @@
import type { PageQuery, BaseEntity } from '#/api/common';
export interface InspectionTaskVO {
/**
* 主键id
*/
id: string | number;
/**
* 巡检计划id
*/
inspectionPlanId: string | number;
/**
* 实际巡检时间
*/
actInsTime: string;
/**
* 当前巡检人
*/
actUserId: string | number;
/**
* 巡检方式
*/
taskType: string;
/**
* 转移描述
*/
transferDesc: string;
/**
* 巡检状态
*/
status: string;
/**
* 备注
*/
remark: string;
/**
* 创建人id
*/
createById: string | number;
/**
* 更新人id
*/
updateById: string | number;
/**
* 搜索值
*/
searchValue: string;
}
export interface InspectionTaskForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 巡检计划id
*/
inspectionPlanId?: string | number;
/**
* 实际巡检时间
*/
actInsTime?: string;
/**
* 当前巡检人
*/
actUserId?: string | number;
/**
* 巡检方式
*/
taskType?: string;
/**
* 转移描述
*/
transferDesc?: string;
/**
* 巡检状态
*/
status?: string;
/**
* 备注
*/
remark?: string;
/**
* 创建人id
*/
createById?: string | number;
/**
* 更新人id
*/
updateById?: string | number;
/**
* 搜索值
*/
searchValue?: string;
}
export interface InspectionTaskQuery extends PageQuery {
/**
* 巡检计划id
*/
inspectionPlanId?: string | number;
/**
* 实际巡检时间
*/
actInsTime?: string;
/**
* 当前巡检人
*/
actUserId?: string | number;
/**
* 巡检方式
*/
taskType?: string;
/**
* 转移描述
*/
transferDesc?: string;
/**
* 巡检状态
*/
status?: string;
/**
* 创建人id
*/
createById?: string | number;
/**
* 更新人id
*/
updateById?: string | number;
/**
* 搜索值
*/
searchValue?: string;
/**
* 日期范围参数
*/
params?: any;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -11,6 +11,7 @@ import {
GiteeIcon,
GitHubOutlined,
UserOutlined,
} from '@vben/icons';
import {
BasicLayout,
@@ -30,6 +31,7 @@ import { resetRoutes } from '#/router';
import { useAuthStore, useNotifyStore } from '#/store';
import { useTenantStore } from '#/store/tenant';
import LoginForm from '#/views/_core/authentication/login.vue';
// import { TagOutlined } from '@ant-design/icons-vue';
const userStore = useUserStore();
const authStore = useAuthStore();
@@ -83,6 +85,13 @@ const menus = computed(() => {
icon: CircleHelp,
text: $t('ui.widgets.qa'),
},
{
handler: () => {
router.push('/navigation');
},
// icon: TagOutlined,
text: '返回导航',
},
];
/**
* 租户选中状态 不显示个人中心

View File

@@ -1,7 +1,7 @@
import { initPreferences } from '@vben/preferences';
import { unmountGlobalLoading } from '@vben/utils';
import { overridesPreferences } from './preferences';
import './utils/flexible'
/**
* 应用初始化完成之后再进行页面加载渲染
*/

View File

@@ -100,6 +100,67 @@ const coreRoutes: RouteRecordRaw[] = [
},
],
},
{
component: () => import('#/views/screen/navigation/Navigation.vue'),
name: 'navigation',
path: '/navigation',
meta: {
title: '导航页面', // 或者用 $t('page.navigation.title')
requiresAuth: true, // 如果需要登录验证
},
},
{
component: () => import('#/views/screen/energyConsumptionAnalysis/index.vue'),
name: 'energyAnalysis',
path: '/energyAnalysis',
meta: {
title: '能耗大屏',
requiresAuth: true, // 如果需要登录验证
},
}, {
component: () => import('#/views/screen/property/index.vue'),
name: 'property',
path: '/property',
meta: {
title: '物业大屏',
requiresAuth: true, // 如果需要登录验证
},
}, {
component: () => import('#/views/screen/security/index.vue'),
name: 'security',
path: '/security',
meta: {
title: '安防大屏',
requiresAuth: true, // 如果需要登录验证
},
},
{
component: () => import('#/views/screen/monitor/index.vue'),
name: 'monitor',
path: '/monitor',
meta: {
title: '监控大屏',
requiresAuth: true, // 如果需要登录验证
},
},
{
component: () => import('#/views/screen/security/index.vue'),
name: 'security',
path: '/security',
meta: {
title: '安防大屏',
requiresAuth: true, // 如果需要登录验证
},
},
{
component: () => import('#/views/screen/digitalIntelligence/index.vue'),
name: 'digitalIntelligence',
path: '/digitalIntelligence',
meta: {
title: '商务中心数智管理平台',
requiresAuth: true, // 如果需要登录验证
},
},
];
export { coreRoutes, fallbackNotFoundRoute };

View File

@@ -58,7 +58,7 @@ export const useAuthStore = defineStore('auth', () => {
} else {
onSuccess
? await onSuccess?.()
: await router.push(preferences.app.defaultHomePath);
: await router.push('/navigation');
}
if (userInfo?.realName) {

View File

@@ -0,0 +1,119 @@
import type { ECharts } from 'echarts'
/**
* ECharts 图表自适应管理器
* 用于监听图表容器大小变化并自动调整图表大小
*/
export class EChartsResizeManager {
private charts: Map<ECharts, ResizeObserver> = new Map()
private resizeHandler: (() => void) | null = null
constructor() {
this.initResizeListener()
}
/**
* 添加图表到管理器
* @param chart ECharts 实例
*/
addChart(chart: ECharts): void {
if (chart && !this.charts.has(chart)) {
// 获取图表容器元素
const container = chart.getDom()
if (container) {
// 为每个图表创建独立的 ResizeObserver
const observer = new ResizeObserver(() => {
if (chart && !chart.isDisposed()) {
chart.resize()
}
})
// 监听图表容器的大小变化
observer.observe(container)
this.charts.set(chart, observer)
}
}
}
/**
* 移除图表
* @param chart ECharts 实例
*/
removeChart(chart: ECharts): void {
const observer = this.charts.get(chart)
if (observer) {
observer.disconnect()
this.charts.delete(chart)
}
}
/**
* 清空所有图表
*/
clearCharts(): void {
this.charts.forEach(observer => {
observer.disconnect()
})
this.charts.clear()
}
/**
* 重新调整所有图表大小
*/
resizeAllCharts(): void {
this.charts.forEach((observer, chart) => {
if (chart && !chart.isDisposed()) {
chart.resize()
}
})
}
/**
* 初始化窗口大小变化监听器
*/
private initResizeListener(): void {
// 监听窗口大小变化
this.resizeHandler = () => {
this.resizeAllCharts()
}
window.addEventListener('resize', this.resizeHandler)
}
/**
* 销毁监听器
*/
destroy(): void {
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler)
this.resizeHandler = null
}
this.clearCharts()
}
}
// 创建全局实例
export const echartsResizeManager = new EChartsResizeManager()
/**
* 便捷函数:添加图表到管理器
* @param chart ECharts 实例
*/
export function addChartToResizeManager(chart: ECharts): void {
echartsResizeManager.addChart(chart)
}
/**
* 便捷函数:移除图表
* @param chart ECharts 实例
*/
export function removeChartFromResizeManager(chart: ECharts): void {
echartsResizeManager.removeChart(chart)
}
/**
* 便捷函数:手动触发所有图表重新调整大小
*/
export function resizeAllCharts(): void {
echartsResizeManager.resizeAllCharts()
}

View File

@@ -0,0 +1,12 @@
function setRootFontSize(): void {
const baseWidth = 1920 // 设计稿宽度
const baseFontSize = 16 // 设计稿根字体
const screenWidth = window.innerWidth
const fontSize = (screenWidth / baseWidth) * baseFontSize
document.documentElement.style.fontSize = fontSize + 'px'
}
setRootFontSize()
window.addEventListener('resize', setRootFontSize)
export default setRootFontSize

View File

@@ -0,0 +1,264 @@
import * as echarts from 'echarts'
import 'echarts-gl'
interface Pie3DData {
name: string
value: number
itemStyle?: {
color?: string
opacity?: number
}
startRatio?: number
endRatio?: number
}
interface Pie3DOptions {
data: Pie3DData[]
height?: number
hoverHeightScale?: number
selectOffset?: number
distance?: number
boxHeight?: number
radius?: number
}
interface ParametricEquation {
u: { min: number; max: number; step: number }
v: { min: number; max: number; step: number }
x: (u: number, v: number) => number
y: (u: number, v: number) => number
z: (u: number, v: number) => number
}
interface SeriesItem {
name: string
type: string
parametric: boolean
wireframe: { show: boolean }
pieData?: Pie3DData
pieStatus?: { selected: boolean; hovered: boolean; k: number }
itemStyle?: any
parametricEquation?: ParametricEquation
}
/**
* 渲染3D饼图支持点击选中外移、鼠标悬停高亮升高、高亮修正无label/legend。
* @param dom - 容器DOM
* @param options - 配置项
* options.data: [{name, value, itemStyle}]
* options.height: 饼图厚度
* options.hoverHeightScale: 高亮时高度放大倍数
* options.selectOffset: 选中时外移距离默认0.1
* options.distance: 视角距离
* options.boxHeight: grid3D.boxHeight
* options.radius: 半径缩放
* @returns echartsInstance
*/
export function renderPie3DChart(dom: HTMLElement, options: Pie3DOptions): echarts.ECharts | undefined {
if (!dom) return
const myChart = echarts.init(dom)
const hoverHeightScale = options.hoverHeightScale || 1.2
const selectOffset = options.selectOffset || 0.1
const distance = options.distance || 120
const gridBoxHeight = options.boxHeight || 10
const R = options.radius || 0.8
const fixedH = 100
// 生成扇形的曲面参数方程
function getParametricEquation(startRatio: number, endRatio: number, isSelected: boolean, isHovered: boolean, k: number, h: number): ParametricEquation {
const midRatio = (startRatio + endRatio) / 2
const startRadian = startRatio * Math.PI * 2
const endRadian = endRatio * Math.PI * 2
const midRadian = midRatio * Math.PI * 2
if (startRatio === 0 && endRatio === 1) isSelected = false
k = typeof k !== 'undefined' ? k : 1
const offsetX = isSelected ? Math.cos(midRadian) * selectOffset : 0
const offsetY = isSelected ? Math.sin(midRadian) * selectOffset : 0
const hoverRate = isHovered ? 1.05 : 1
return {
u: { min: -Math.PI, max: Math.PI * 3, step: Math.PI / 32 },
v: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
x: function(u: number, v: number) {
if (u < startRadian) return offsetX + R * Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
if (u > endRadian) return offsetX + R * Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
return offsetX + R * Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
},
y: function(u: number, v: number) {
if (u < startRadian) return offsetY + R * Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
if (u > endRadian) return offsetY + R * Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
return offsetY + R * Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
},
z: function(u: number, v: number) {
if (u < -Math.PI * 0.5) return Math.sin(u)
if (u > Math.PI * 2.5) return Math.sin(u) * h * .1
return Math.sin(v) > 0 ? 1 * h * .1 : -1
}
}
}
// 生成series
function getPie3D(pieData: Pie3DData[]): SeriesItem[] {
const series: SeriesItem[] = []
let sumValue = 0
let startValue = 0
let endValue = 0
const k = 1 // 实心饼图
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value
const seriesItem: SeriesItem = {
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
parametric: true,
wireframe: { show: false },
pieData: pieData[i],
pieStatus: { selected: false, hovered: false, k: k }
}
if (pieData[i].itemStyle) {
const itemStyle: any = {}
const itemStyleObj: any = pieData[i].itemStyle
if (itemStyleObj.color) itemStyle.color = itemStyleObj.color
if (itemStyleObj.opacity !== undefined) itemStyle.opacity = itemStyleObj.opacity
seriesItem.itemStyle = itemStyle
}
series.push(seriesItem)
}
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData!.value
series[i].pieData!.startRatio = startValue / sumValue
series[i].pieData!.endRatio = endValue / sumValue
series[i].parametricEquation = getParametricEquation(
series[i].pieData!.startRatio!,
series[i].pieData!.endRatio!,
false,
false,
k,
fixedH // 所有块厚度一样
)
startValue = endValue
}
// 透明圆环用于高亮修正
series.push({
name: 'mouseoutSeries',
type: 'surface',
parametric: true,
wireframe: { show: false },
itemStyle: { opacity: 0 },
parametricEquation: {
u: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
v: { min: 0, max: Math.PI, step: Math.PI / 20 },
x: function(u: number, v: number) { return Math.sin(v) * Math.sin(u) + Math.sin(u) },
y: function(u: number, v: number) { return Math.sin(v) * Math.cos(u) + Math.cos(u) },
z: function(u: number, v: number) { return Math.cos(v) > 0 ? 0.1 : -0.1 }
}
})
return series
}
const series = getPie3D(options.data)
// 不加pie2d不加legend不加label
const option = {
backgroundColor: 'rgba(0,0,0,0)',
legend: undefined,
tooltip: undefined,
xAxis3D: { min: -1, max: 1 },
yAxis3D: { min: -1, max: 1 },
zAxis3D: { min: -1, max: 1 },
grid3D: {
show: false,
boxHeight: gridBoxHeight,
viewControl: {
alpha: 35,
distance: distance,
rotateSensitivity: 0,
zoomSensitivity: 0,
panSensitivity: 0,
autoRotate: false
}
},
series: series
}
myChart.setOption(option, true)
// 选中/高亮交互
let hoveredIndex = ''
// // 点击选中(外移)
// myChart.on('click', function(params) {
// if (!series[params.seriesIndex] || series[params.seriesIndex].type !== 'surface') return;
// let isSelected = !series[params.seriesIndex].pieStatus.selected;
// let isHovered = series[params.seriesIndex].pieStatus.hovered;
// let k = series[params.seriesIndex].pieStatus.k;
// let startRatio = series[params.seriesIndex].pieData.startRatio;
// let endRatio = series[params.seriesIndex].pieData.endRatio;
// // 取消之前选中
// if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
// let prev = series[selectedIndex];
// prev.parametricEquation = getParametricEquation(
// prev.pieData.startRatio, prev.pieData.endRatio, false, prev.pieStatus.hovered, k, prev.pieData.value
// );
// prev.pieStatus.selected = false;
// }
// // 当前选中/取消
// series[params.seriesIndex].parametricEquation = getParametricEquation(
// startRatio, endRatio, isSelected, isHovered, k, series[params.seriesIndex].pieData.value
// );
// series[params.seriesIndex].pieStatus.selected = isSelected;
// isSelected ? selectedIndex = params.seriesIndex : selectedIndex = '';
// myChart.setOption({ series });
// });
// 悬停高亮(升高)
myChart.on('mouseover', function(params: any) {
if (!series[params.seriesIndex] || !series[params.seriesIndex].pieData) return
if (hoveredIndex === params.seriesIndex) return
// 取消之前高亮
if (hoveredIndex !== '') {
const prev = series[parseInt(hoveredIndex)]
if (!prev || !prev.pieData) return
prev.parametricEquation = getParametricEquation(
prev.pieData.startRatio!,
prev.pieData.endRatio!,
prev.pieStatus!.selected,
false,
prev.pieStatus!.k,
fixedH
)
prev.pieStatus!.hovered = false
hoveredIndex = ''
}
// 当前高亮
const cur = series[params.seriesIndex]
cur.parametricEquation = getParametricEquation(
cur.pieData!.startRatio!,
cur.pieData!.endRatio!,
cur.pieStatus!.selected,
true,
cur.pieStatus!.k,
fixedH * hoverHeightScale
)
cur.pieStatus!.hovered = true
hoveredIndex = params.seriesIndex.toString()
myChart.setOption({ series })
})
// 全局移出,修正高亮残留
myChart.on('globalout', function() {
if (hoveredIndex !== '') {
const prev = series[parseInt(hoveredIndex)]
if (!prev || !prev.pieData) return
prev.parametricEquation = getParametricEquation(
prev.pieData.startRatio!,
prev.pieData.endRatio!,
prev.pieStatus!.selected,
false,
prev.pieStatus!.k,
fixedH
)
prev.pieStatus!.hovered = false
hoveredIndex = ''
myChart.setOption({ series })
}
})
return myChart
}

View File

@@ -0,0 +1,129 @@
import * as echarts from 'echarts'
interface ThreeDBarOptionConfig {
xData: string[]
yData: number[]
barWidth?: number
colorConfig?: {
left0?: string
left08?: string
top03?: string
top1?: string
}
}
export function getThreeDBarOption({ xData, yData, barWidth = 30, colorConfig = {} }: ThreeDBarOptionConfig) {
// 注册 shape 只需一次,防止多次注册报错
if (!(echarts.graphic as any).registered3DBar) {
const leftShape = echarts.graphic.extendShape({
buildPath(ctx: any, shape: any) {
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape
const WIDTH = 15
const OBLIQUE_ANGLE_HEIGHT = 8
const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]
const p2 = [basicsXAxis - WIDTH, bottomYAxis]
const p3 = [basicsXAxis, bottomYAxis]
const p4 = [basicsXAxis, topBasicsYAxis]
ctx.moveTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p4[0], p4[1])
}
})
const rightShape = echarts.graphic.extendShape({
buildPath(ctx: any, shape: any) {
const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape
const WIDTH = 15
const OBLIQUE_ANGLE_HEIGHT = 2
const p1 = [basicsXAxis, topBasicsYAxis]
const p2 = [basicsXAxis, bottomYAxis]
const p3 = [basicsXAxis + WIDTH, bottomYAxis]
const p4 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]
ctx.moveTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p4[0], p4[1])
}
})
const topShape = echarts.graphic.extendShape({
buildPath(ctx: any, shape: any) {
const { topBasicsYAxis, basicsXAxis } = shape
const WIDTH = 15
const OBLIQUE_ANGLE_HEIGHT = 8
const p1 = [basicsXAxis, topBasicsYAxis]
const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]
const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2]
const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT]
ctx.moveTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p4[0], p4[1])
}
})
echarts.graphic.registerShape('leftShape', leftShape)
echarts.graphic.registerShape('rightShape', rightShape)
echarts.graphic.registerShape('topShape', topShape)
;(echarts.graphic as any).registered3DBar = true
}
return {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '4%', bottom: '3%', top: "12%", containLabel: true },
xAxis: [{
type: 'category',
data: xData,
axisTick: { alignWithLabel: true },
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
}],
yAxis: [{
type: 'value',
axisLine: { show: false },
splitLine: { lineStyle: { color: '#1b4a7a' } },
axisLabel: { color: '#fff' }
}],
series: [{
type: 'custom',
data: yData,
barWidth,
renderItem(params: any, api: any) {
const basicsCoord = api.coord([api.value(0), api.value(1)])
const topBasicsYAxis = basicsCoord[1]
const basicsXAxis = basicsCoord[0]
const bottomYAxis = api.coord([api.value(0), 0])[1]
return {
type: 'group',
children: [
{
type: 'leftShape',
shape: { topBasicsYAxis, basicsXAxis, bottomYAxis },
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colorConfig.left0 || '#2ADBEF' },
{ offset: 1, color: colorConfig.left08 || '#2677F9 ' },
])
}
},
{
type: 'rightShape',
shape: { topBasicsYAxis, basicsXAxis, bottomYAxis },
style: {
fill: '#114EAD '
}
},
{
type: 'topShape',
shape: { topBasicsYAxis, basicsXAxis, bottomYAxis },
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0.3, color: colorConfig.top03 || '#6DF0FF' },
{ offset: 1, color: colorConfig.top1 || '#6DF0FF' }
])
}
}
]
}
}
}]
}
}

View File

@@ -4,6 +4,8 @@ import {renderDict} from "#/utils/render";
import {getDictOptions} from "#/utils/dict";
import {h} from "vue";
import {Rate} from "ant-design-vue";
import type {Dayjs} from "dayjs";
import dayjs from "dayjs";
export const querySchema: FormSchemaGetter = () => [
@@ -39,6 +41,11 @@ export const columns: VxeGridProps['columns'] = [
field: 'orderName',
minWidth: 180,
},
{
title: '工单类型',
field: 'typeName',
minWidth: 150,
},
{
title: '派单时间',
field: 'dispatchTime',
@@ -73,8 +80,8 @@ export const columns: VxeGridProps['columns'] = [
title: '评价',
field: 'serviceEvalua',
width: 180,
slots:{
default: ({ row }) => {
slots: {
default: ({row}) => {
return h(Rate, {
value: row.serviceEvalua || 0,
disabled: true,
@@ -143,6 +150,7 @@ export const modalSchema: FormSchemaGetter = () => [
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
disabledDate: disabledDate
},
rules: 'selectRequired',
},
@@ -208,3 +216,6 @@ export const modalSchema: FormSchemaGetter = () => [
rules: 'selectRequired',
},
];
const disabledDate = (current: Dayjs) => {
return current && current < dayjs().endOf('day');
};

View File

@@ -35,6 +35,14 @@ const formOptions: VbenFormProps = {
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
handleReset: async () => {
ordersType.value = '0';
const { formApi, reload } = tableApi;
await formApi.resetForm();
const formValues = formApi.form.values;
formApi.setLatestSubmissionValues(formValues);
await reload(formValues);
},
};
const gridOptions: VxeGridProps = {
@@ -53,6 +61,7 @@ const gridOptions: VxeGridProps = {
proxyConfig: {
ajax: {
query: async ({page}, formValues = {}) => {
formValues.type = ordersType.value=='0'?undefined:ordersType.value;
return await workOrdersList({
pageNum: page.currentPage,
pageSize: page.pageSize,
@@ -85,7 +94,7 @@ function handleAdd() {
modalApi.setData({});
modalApi.open();
}
function handleInfo(row) {
function handleInfo(row:any) {
detailApi.setData({id:row.id});
detailApi.open();
}
@@ -139,21 +148,14 @@ async function queryOrderType() {
onMounted(async () => {
await queryOrderType()
});
async function changeOrdersType(val: string) {
await tableApi.formApi.setValues({
type: val === '0' ? undefined : val, // '0' 表示全部工单,传 undefined 或清除该字段
});
console.log(tableApi.formApi.getValues(),'==================')
await tableApi.query()
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="工单处理列表">
<BasicTable table-title="工单处理列表" class="order-work-left-radio">
<template #table-title>
<RadioGroup v-model:value="ordersType" button-style="solid" @change="changeOrdersType">
<RadioGroup v-model:value="ordersType" button-style="solid"
@change="() => tableApi.reload()">
<RadioButton v-for="item in ordersTypeList"
:value="item.value">{{ item.label }}
</RadioButton>
@@ -219,3 +221,18 @@ async function changeOrdersType(val: string) {
<WorkOrdersDetail/>
</Page>
</template>
<style lang="scss" scoped>
:where(.css-dev-only-do-not-override-aza1th).ant-radio-group {
white-space: nowrap;
overflow-y: hidden;
margin-right: 10px;
}
</style>
<style lang="scss">
.order-work-left-radio{
.vxe-toolbar .vxe-buttons--wrapper, .vxe-toolbar .vxe-tools--wrapper {
max-width: 70% !important;
}
}
</style>

View File

@@ -24,7 +24,7 @@ const [BasicModal, modalApi] = useVbenModal({
});
const orderDetail = shallowRef<null | WorkOrdersVO>(null);
const handleRecords=ref<any[]>([])
async function handleOpenChange(open: boolean) {
if (!open) {
return null;
@@ -34,6 +34,13 @@ async function handleOpenChange(open: boolean) {
const {id} = modalApi.getData() as { id: number | string };
// 赋值
orderDetail.value = await workOrdersInfo(id);
if(orderDetail.value){
handleRecords.value=[
{type:orderDetail.value.typeName,time:orderDetail.value.compleTime,personName:orderDetail.value.handlerText},
{type:'跟进',time:orderDetail.value.dispatchTime,personName:orderDetail.value.initiatorNameText},
{type:'创建工单',time:orderDetail.value.createTime,personName:orderDetail.value.initiatorNameText},
]
}
modalApi.modalLoading(false);
}
@@ -52,18 +59,16 @@ async function handleOpenChange(open: boolean) {
{{ orderDetail.orderName }}
</DescriptionsItem>
<DescriptionsItem label="工单类型">
<component
:is="renderDict(orderDetail.customerType,'wy_khlx')"
/>
{{orderDetail.typeName}}
</DescriptionsItem>
<DescriptionsItem label="发起人">
{{ orderDetail.rentalPeriod }}
{{ orderDetail.initiatorNameText+'-'+orderDetail.initiatorPhone}}
</DescriptionsItem>
<DescriptionsItem label="派单时间">
{{ orderDetail.dispatchTime }}
</DescriptionsItem>
<DescriptionsItem label="处理人">
{{ orderDetail.totalAmount + "元" }}
{{ orderDetail.handlerText }}
</DescriptionsItem>
<DescriptionsItem label="具体位置" :span="2">
{{ orderDetail.location }}
@@ -74,7 +79,7 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="完成时间">
{{ orderDetail.compleTime }}
</DescriptionsItem>
<DescriptionsItem label="评价">
<DescriptionsItem label="服务评价">
<Rate :value="orderDetail.serviceEvalua" disabled/>
</DescriptionsItem>
<DescriptionsItem label="是否超时">
@@ -86,11 +91,11 @@ async function handleOpenChange(open: boolean) {
<Divider orientation="left" orientation-margin="0px">
处理记录
</Divider>
<Timeline>
<TimelineItem>
<p>类型</p>
<p>时间</p>
<p>处理人</p>
<Timeline v-if="handleRecords.length">
<TimelineItem v-for="item in handleRecords">
<p>类型{{item.type}}</p>
<p>时间{{item.time}}</p>
<p>处理人{{item.personName}}</p>
</TimelineItem>
</Timeline>
</div>

View File

@@ -98,7 +98,7 @@ export const modalSchema: FormSchemaGetter = () => [
},
{
label: '完成时效(小时)',
fieldName: 'isTransfers',
fieldName: 'completionNumber',
component: 'Input',
rules: 'required',
},

View File

@@ -41,11 +41,6 @@ async function handleOpenChange(open: boolean) {
:is="renderDict(workOrdersTypeInfoDetail.operationMode,'pro_operation_pattern')"
/>
</DescriptionsItem>
<DescriptionsItem label="运作模式" v-if="workOrdersTypeInfoDetail.operationMode!=null">
<component
:is="renderDict(workOrdersTypeInfoDetail.operationMode,'pro_operation_pattern')"
/>
</DescriptionsItem>
<DescriptionsItem label="排序值">
{{ workOrdersTypeInfoDetail.sort }}
</DescriptionsItem>

View File

@@ -189,6 +189,6 @@ export const modalSchema: FormSchemaGetter = () => [
{
label: '备注',
fieldName: 'remark',
component: 'Input',
component: 'Textarea',
},
];

View File

@@ -120,6 +120,7 @@ export const modalSchema: FormSchemaGetter = () => [
label: '价格体系',
fieldName: 'price',
component: 'Input',
rules: 'required',
},
{
label: '状态',
@@ -134,6 +135,6 @@ export const modalSchema: FormSchemaGetter = () => [
{
label: '备注',
fieldName: 'remarks',
component: 'Input',
component: 'Textarea',
},
];

View File

@@ -12,6 +12,7 @@ import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import productDetailModal from './rentalPlan-detial-modal.vue';
import { getDictOptions } from '#/utils/dict';
import { Item } from 'ant-design-vue/es/menu';
const emit = defineEmits<{ reload: [] }>();
@@ -69,7 +70,10 @@ const [BasicModal, modalApi] = useVbenModal({
if ((isUpdate.value || isReadonly.value) && id) {
const record = await rentalPlanInfo(id);
// 后端返回绿植产品包列表结构处理
detailTable.value = record.productList.map((item:any) => item.product);
detailTable.value = record.productList.map((item:any) => {...item.product,Item.productNum});
console.log(detailTable.value);
await formApi.setValues(record);
}
await markInitialized();

View File

@@ -87,10 +87,14 @@ onMounted(async () => {
data: xAxisData.value,
boundaryGap: false,
},
yAxis: { type: 'value' },
yAxis: { type: 'value',
axisLabel: {
formatter: (value) => `${value * 100}%`
},
},
series: [
{
name: '订单',
name: '订单趋势',
type: 'line',
data: seriesData.value ||[],
smooth: true,
@@ -123,17 +127,22 @@ onMounted(async () => {
],
});
renderCustomerTypesBar({
title: { text: '客户类型分' },
title: { text: '客户类型分' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: countByCusTypeData.type || [],
data: ['企业客户','个人客户','政府机构','商业地产','其他'],
boundaryGap: true,
},
yAxis: { type: 'value' },
yAxis: { type: 'value',
axisLabel: {
// formatter: (value: number) => `${parseInt(value.toString())}`
},
// interval: 0,
},
series: [
{
name: '订单数',
name: '客户数',
type: 'bar',
data: countByCusTypeData.counts || [],
},
@@ -141,16 +150,28 @@ onMounted(async () => {
});
renderCustomerRenewalLine({
title: { text: '客户续租率趋势' },
tooltip: { trigger: 'axis' },
tooltip: { trigger: 'axis',
formatter: function(params:any) {
let result = params[0].axisValue + '<br/>';
params.forEach((item:any) => {
result += item.marker + item.seriesName + '' + item.data + '%<br/>';
});
return result;
}
},
xAxis: {
type: 'category',
data: countRenewRateData.month || [],
boundaryGap: false,
},
yAxis: { type: 'value' },
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}%',
}, },
series: [
{
name: '订单数',
name: '续租率',
type: 'line',
data: countRenewRateData.rate || [],
smooth: true,
@@ -162,6 +183,19 @@ onMounted(async () => {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: function(params:any) {
// params 是一个数组,包含每个系列的当前项
let result = params[0].axisValue + '<br/>';
params.forEach((item:any) => {
if (item.seriesName === '完成率') {
// 假设原始数据是 80显示为 80%
result += item.marker + item.seriesName + '' + item.data + '%<br/>';
} else {
result += item.marker + item.seriesName + '' + item.data + '<br/>';
}
});
return result;
}
},
legend: {
data: ['计划任务数', '已完成数', '完成率'],
@@ -169,7 +203,7 @@ onMounted(async () => {
xAxis: [
{
type: 'category',
data: countAchievedData.type || [],
data: ['修剪整形','肥水管理','中耕除草','病虫害防治','越冬防寒'],
},
],
yAxis: [
@@ -195,7 +229,7 @@ onMounted(async () => {
{
name: '计划任务数',
type: 'bar',
data: countAchievedData.toral || [],
data: countAchievedData.total || [],
},
{
name: '已完成数',
@@ -220,7 +254,7 @@ onMounted(async () => {
orient: 'horizontal',
left: 'center',
bottom: 10,
data: ['一星', '星', '三星', '四星', '五星'],
data: ['一星', '星', '三星', '四星', '五星'],
},
series: [
{
@@ -283,7 +317,7 @@ function formatNumber(num: number | string) {
<div class="title">
<div class="title-text">绿植租赁业务统计报表</div>
<div class="title-operate">
<div class="export">
<div class="export" style="display: none;">
<Button size="large" style="color: #fff; background-color: #22c55e">
导出数据
</Button>

View File

@@ -0,0 +1,68 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'id',
label: '编号',
},
{
component: 'Input',
fieldName: 'itemName',
label: '巡检项目',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '编号',
field: 'id',
},
{
title: '巡检项目',
field: 'itemName',
},
{
title: '创建时间',
field: 'createTime',
},
{
title: '备注',
field: 'remark',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键id',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '巡检项目',
fieldName: 'itemName',
component: 'Input',
rules: 'required',
},
{
label: '备注',
fieldName: 'remark',
component: 'Textarea',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
inspectionItemExport,
inspectionItemList,
inspectionItemRemove,
} from '#/api/property/inspectionManagement/inspectionItem';
import type { InspectionItemForm } from '#/api/property/inspectionManagement/inspectionItem/model';
import { commonDownloadExcel } from '#/utils/file/download';
import inspectionItemModal from './inspectionItem-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await inspectionItemList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-inspectionItem-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [InspectionItemModal, modalApi] = useVbenModal({
connectedComponent: inspectionItemModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<InspectionItemForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<InspectionItemForm>) {
await inspectionItemRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<InspectionItemForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await inspectionItemRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(inspectionItemExport, '巡检项目数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="巡检项目列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:inspectionItem:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:inspectionItem:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:inspectionItem:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:inspectionItem:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:inspectionItem:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<InspectionItemModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { inspectionItemAdd, inspectionItemInfo, inspectionItemUpdate } from '#/api/property/inspectionManagement/inspectionItem';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await inspectionItemInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? inspectionItemUpdate(data) : inspectionItemAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,220 @@
import type {FormSchemaGetter} from '#/adapter/form';
import type {VxeGridProps} from '#/adapter/vxe-table';
import {getDictOptions} from "#/utils/dict";
import {renderDict} from "#/utils/render";
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'planName',
label: '计划名称',
},
{
component: 'Select',
componentProps: {
options: getDictOptions('wy_xjzq'),
},
fieldName: 'inspectionPlanPeriod',
label: '巡检周期',
},
{
component: 'Select',
componentProps: {
options: getDictOptions('wy_xjqdfs'),
},
fieldName: 'signType',
label: '签到方式',
},
];
export const columns: VxeGridProps['columns'] = [
{type: 'checkbox', width: 60},
{
title: '主键id',
field: 'id',
},
{
title: '巡检计划名称',
field: 'planName',
},
{
title: '巡检路线',
field: 'inspectionRouteId',
},
{
title: '巡检周期',
field: 'inspectionPlanPeriod',
slots: {
default: ({ row }) => {
return renderDict(row.inspectionPlanPeriod, 'wy_xjzq');
},
},
},
{
title: '任务提前分组',
field: 'beforeTime',
},
{
title: '开始日期',
field: 'startDate',
},
{
title: '结束日期',
field: 'endDate',
},
{
title: '开始时间',
field: 'startTime',
},
{
title: '结束时间',
field: 'endTime',
},
{
title: '签到方式',
field: 'signType',
slots: {
default: ({ row }) => {
return renderDict(row.signType, 'wy_xjqdfs');
},
},
},
{
title: '是否允许补检',
field: 'canReexamine',
slots: {
default: ({ row }) => {
return renderDict(row.canReexamine, 'wy_sf');
},
},
},
{
title: '选择员工',
field: 'userId',
},
{
title: '备注',
field: 'remark',
},
{
field: 'action',
fixed: 'right',
slots: {default: 'action'},
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键id',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '巡检计划名称',
fieldName: 'planName',
component: 'Input',
rules: 'required',
},
{
label: '巡检路线',
fieldName: 'inspectionRouteId',
component: 'Input',
rules: 'required',
},
{
label: '巡检周期',
fieldName: 'inspectionPlanPeriod',
component: 'Input',
rules: 'required',
componentProps: {
options: getDictOptions('wy_xjzq'),
},
},
{
label: '任务提前分组',
fieldName: 'beforeTime',
component: 'Input',
rules: 'required',
},
{
label: '开始日期',
fieldName: 'startDate',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '结束日期',
fieldName: 'endDate',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '开始时间',
fieldName: 'startTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '结束时间',
fieldName: 'endTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
rules: 'required',
},
{
label: '签到方式',
fieldName: 'signType',
component: 'Select',
componentProps: {
options: getDictOptions('wy_xjqdfs'),
},
rules: 'selectRequired',
},
{
label: '允许补检',
fieldName: 'canReexamine',
component: 'Select',
componentProps: {
options: getDictOptions('wy_sf'),
},
rules: 'selectRequired',
},
{
label: '选择员工',
fieldName: 'userId',
component: 'Input',
rules: 'required',
},
{
label: '备注',
fieldName: 'remark',
component: 'Input',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
inspectionPlanExport,
inspectionPlanList,
inspectionPlanRemove,
} from '#/api/property/inspectionManagement/inspectionPlan';
import type { InspectionPlanForm } from '#/api/property/inspectionManagement/inspectionPlan/model';
import { commonDownloadExcel } from '#/utils/file/download';
import inspectionPlanModal from './inspectionPlan-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await inspectionPlanList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-inspectionPlan-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [InspectionPlanModal, modalApi] = useVbenModal({
connectedComponent: inspectionPlanModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<InspectionPlanForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<InspectionPlanForm>) {
await inspectionPlanRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<InspectionPlanForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await inspectionPlanRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(inspectionPlanExport, '巡检计划数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="巡检计划列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:inspectionPlan:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:inspectionPlan:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:inspectionPlan:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:inspectionPlan:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:inspectionPlan:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<InspectionPlanModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { inspectionPlanAdd, inspectionPlanInfo, inspectionPlanUpdate } from '#/api/property/inspectionManagement/inspectionPlan';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await inspectionPlanInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? inspectionPlanUpdate(data) : inspectionPlanAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,149 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'itemId',
label: '巡检项目id',
},
{
component: 'Input',
fieldName: 'pointName',
label: '巡检点名称',
},
{
component: 'Select',
componentProps: {
},
fieldName: 'pointType',
label: '巡检点类型',
},
{
component: 'Input',
fieldName: 'nfcCode',
label: 'nfc编码',
},
{
component: 'Input',
fieldName: 'createById',
label: '创建人id',
},
{
component: 'Input',
fieldName: 'updateById',
label: '更新人id',
},
{
component: 'Input',
fieldName: 'searchValue',
label: '搜索值',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '主键id',
field: 'id',
},
{
title: '巡检项目id',
field: 'itemId',
},
{
title: '巡检点名称',
field: 'pointName',
},
{
title: '巡检点类型',
field: 'pointType',
},
{
title: 'nfc编码',
field: 'nfcCode',
},
{
title: '备注',
field: 'remark',
},
{
title: '创建人id',
field: 'createById',
},
{
title: '更新人id',
field: 'updateById',
},
{
title: '搜索值',
field: 'searchValue',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键id',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '巡检项目id',
fieldName: 'itemId',
component: 'Input',
rules: 'required',
},
{
label: '巡检点名称',
fieldName: 'pointName',
component: 'Input',
rules: 'required',
},
{
label: '巡检点类型',
fieldName: 'pointType',
component: 'Select',
componentProps: {
},
rules: 'selectRequired',
},
{
label: 'nfc编码',
fieldName: 'nfcCode',
component: 'Input',
},
{
label: '备注',
fieldName: 'remark',
component: 'Textarea',
},
{
label: '创建人id',
fieldName: 'createById',
component: 'Input',
},
{
label: '更新人id',
fieldName: 'updateById',
component: 'Input',
},
{
label: '搜索值',
fieldName: 'searchValue',
component: 'Input',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
inspectionPointExport,
inspectionPointList,
inspectionPointRemove,
} from '#/api/property/inspectionManagement/inspectionPoint';
import type { InspectionPointForm } from '#/api/property/inspectionManagement/inspectionPoint/model';
import { commonDownloadExcel } from '#/utils/file/download';
import inspectionPointModal from './inspectionPoint-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await inspectionPointList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-inspectionPoint-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [InspectionPointModal, modalApi] = useVbenModal({
connectedComponent: inspectionPointModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<InspectionPointForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<InspectionPointForm>) {
await inspectionPointRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<InspectionPointForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await inspectionPointRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(inspectionPointExport, '巡检点数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="巡检点列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:inspectionPoint:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:inspectionPoint:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:inspectionPoint:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:inspectionPoint:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:inspectionPoint:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<InspectionPointModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { inspectionPointAdd, inspectionPointInfo, inspectionPointUpdate } from '#/api/property/inspectionManagement/inspectionPoint';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await inspectionPointInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? inspectionPointUpdate(data) : inspectionPointAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,59 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'routeName',
label: '路线名称',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '主键id',
field: 'id',
},
{
title: '路线名称',
field: 'routeName',
},
{
title: '备注',
field: 'remark',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键id',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '路线名称',
fieldName: 'routeName',
component: 'Input',
rules: 'required',
},
{
label: '备注',
fieldName: 'remark',
component: 'Input',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
inspectionRouteExport,
inspectionRouteList,
inspectionRouteRemove,
} from '#/api/property/inspectionManagement/inspectionRoute';
import type { InspectionRouteForm } from '#/api/property/inspectionManagement/inspectionRoute/model';
import { commonDownloadExcel } from '#/utils/file/download';
import inspectionRouteModal from './inspectionRoute-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await inspectionRouteList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-inspectionRoute-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [InspectionRouteModal, modalApi] = useVbenModal({
connectedComponent: inspectionRouteModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<InspectionRouteForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<InspectionRouteForm>) {
await inspectionRouteRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<InspectionRouteForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await inspectionRouteRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(inspectionRouteExport, '巡检路线数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="巡检路线列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:inspectionRoute:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:inspectionRoute:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:inspectionRoute:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:inspectionRoute:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:inspectionRoute:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<InspectionRouteModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { inspectionRouteAdd, inspectionRouteInfo, inspectionRouteUpdate } from '#/api/property/inspectionManagement/inspectionRoute';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await inspectionRouteInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? inspectionRouteUpdate(data) : inspectionRouteAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -0,0 +1,192 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'inspectionPlanId',
label: '巡检计划id',
},
{
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
fieldName: 'actInsTime',
label: '实际巡检时间',
},
{
component: 'Input',
fieldName: 'actUserId',
label: '当前巡检人',
},
{
component: 'Select',
componentProps: {
},
fieldName: 'taskType',
label: '巡检方式',
},
{
component: 'Textarea',
fieldName: 'transferDesc',
label: '转移描述',
},
{
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
optionType: 'button',
},
fieldName: 'status',
label: '巡检状态',
},
{
component: 'Input',
fieldName: 'createById',
label: '创建人id',
},
{
component: 'Input',
fieldName: 'updateById',
label: '更新人id',
},
{
component: 'Input',
fieldName: 'searchValue',
label: '搜索值',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// export const columns: () => VxeGridProps['columns'] = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '主键id',
field: 'id',
},
{
title: '巡检计划id',
field: 'inspectionPlanId',
},
{
title: '实际巡检时间',
field: 'actInsTime',
},
{
title: '当前巡检人',
field: 'actUserId',
},
{
title: '巡检方式',
field: 'taskType',
},
{
title: '转移描述',
field: 'transferDesc',
},
{
title: '巡检状态',
field: 'status',
},
{
title: '备注',
field: 'remark',
},
{
title: '创建人id',
field: 'createById',
},
{
title: '更新人id',
field: 'updateById',
},
{
title: '搜索值',
field: 'searchValue',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
},
];
export const modalSchema: FormSchemaGetter = () => [
{
label: '主键id',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
label: '巡检计划id',
fieldName: 'inspectionPlanId',
component: 'Input',
},
{
label: '实际巡检时间',
fieldName: 'actInsTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
},
{
label: '当前巡检人',
fieldName: 'actUserId',
component: 'Input',
},
{
label: '巡检方式',
fieldName: 'taskType',
component: 'Select',
componentProps: {
},
},
{
label: '转移描述',
fieldName: 'transferDesc',
component: 'Textarea',
},
{
label: '巡检状态',
fieldName: 'status',
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
optionType: 'button',
},
},
{
label: '备注',
fieldName: 'remark',
component: 'Input',
},
{
label: '创建人id',
fieldName: 'createById',
component: 'Input',
},
{
label: '更新人id',
fieldName: 'updateById',
component: 'Input',
},
{
label: '搜索值',
fieldName: 'searchValue',
component: 'Input',
},
];

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
useVbenVxeGrid,
vxeCheckboxChecked,
type VxeGridProps
} from '#/adapter/vxe-table';
import {
inspectionTaskExport,
inspectionTaskList,
inspectionTaskRemove,
} from '#/api/property/inspectionManagement/inspectionTask';
import type { InspectionTaskForm } from '#/api/property/inspectionManagement/inspectionTask/model';
import { commonDownloadExcel } from '#/utils/file/download';
import inspectionTaskModal from './inspectionTask-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
// 处理区间选择器RangePicker时间格式 将一个字段映射为两个字段 搜索/导出会用到
// 不需要直接删除
// fieldMappingTime: [
// [
// 'createTime',
// ['params[beginTime]', 'params[endTime]'],
// ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
// ],
// ],
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
// columns: columns(),
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await inspectionTaskList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'property-inspectionTask-index'
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [InspectionTaskModal, modalApi] = useVbenModal({
connectedComponent: inspectionTaskModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<InspectionTaskForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
async function handleDelete(row: Required<InspectionTaskForm>) {
await inspectionTaskRemove(row.id);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<InspectionTaskForm>) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await inspectionTaskRemove(ids);
await tableApi.query();
},
});
}
function handleDownloadExcel() {
commonDownloadExcel(inspectionTaskExport, '巡检任务数据', tableApi.formApi.form.values, {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="巡检任务列表">
<template #toolbar-tools>
<Space>
<a-button
v-access:code="['property:inspectionTask:export']"
@click="handleDownloadExcel"
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['property:inspectionTask:remove']"
@click="handleMultiDelete">
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['property:inspectionTask:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['property:inspectionTask:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['property:inspectionTask:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<InspectionTaskModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { inspectionTaskAdd, inspectionTaskInfo, inspectionTaskUpdate } from '#/api/property/inspectionManagement/inspectionTask';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
}
},
schema: modalSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const record = await inspectionTaskInfo(id);
await formApi.setValues(record);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? inspectionTaskUpdate(data) : inspectionTaskAdd(data));
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -118,6 +118,7 @@ async function queryAddServices() {
unit: item.unit,
quantity: 0
}))
console.log(res,addServiceList.value);
}
async function queryPersonData() {

View File

@@ -4,8 +4,6 @@
<a-form
:model="formState"
layout="inline"
@finish="onFinish"
@finishFailed="onFinishFailed"
class="form-box"
>
<a-form-item label="会议日期">
@@ -80,6 +78,7 @@ const formState = reactive<FormState>({
});
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
const meetingList = ref<MeetVO[]>([])
async function handleSearch() {
let hours = '';
if (formState.openHours && formState.openHours.length) {
@@ -107,15 +106,6 @@ function handleAdd(id:string) {
modalApi.setData({id});
modalApi.open();
}
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const meetingList = ref<MeetVO[]>([])
</script>
<style lang="scss">

View File

@@ -60,7 +60,8 @@ function generateWeekDates(): void {
return startOfWeek.add(i, 'day').format('YYYY-MM-DD');
});
weekDates.value = dates;
selectedDate.value = dates[0] ?? '';
// 默认选中今天
selectedDate.value = today.format('YYYY-MM-DD');
}
// 获取预约数据
@@ -137,7 +138,6 @@ async function fetchBookings(): Promise<void> {
});
bookingTable.value = table;
}
console.log(bookingTable.value,'bookingTable.value');
} catch (error) {
console.error('获取预约数据失败:', error);
@@ -150,7 +150,8 @@ async function fetchBookings(): Promise<void> {
function handleViewModeChange(e: RadioChangeEvent): void {
viewMode.value = e.target.value;
if (viewMode.value === 'date') {
selectedDate.value = weekDates.value[0] ?? '';
// 默认选中今天
selectedDate.value = dayjs().format('YYYY-MM-DD');
} else {
selectedRoom.value = roomList.value[0]?.id ?? '';
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,890 @@
<template>
<div class="mian">
<div class="title">
<div class="left">
<div class="left-first" id="time">--:--:--</div>
<div class="left-second" id="date">----</div>
</div>
<div class="center">南川区综合服务中心数智管理平台能耗大屏</div>
<div class="right">
<div>{{ weekDay }}</div>
<div></div>
<div>40</div>
<div class="logout" @click="logout">
<img src="../../../assets/return.png" style="width: 1.5rem; height: 1.5rem;">
<div>
退出
</div>
</div>
</div>
</div>
<div class="header">
<div class="header-item">
<span class="header-label">今年用电量</span>
<span class="header-value orange">180</span>
<span class="header-unit">亿kwh</span>
</div>
<div class="header-item">
<span class="header-label">本月用电总量</span>
<span class="header-value green">1.8</span>
<span class="header-unit">亿kwh</span>
</div>
<div class="header-item">
<span class="header-label">今年用水总量</span>
<span class="header-value blue">2600</span>
<span class="header-unit">万吨</span>
</div>
<div class="header-item">
<span class="header-label">本月用水总量</span>
<span class="header-value purple">30</span>
<span class="header-unit">万吨</span>
</div>
<div class="header-item">
<span class="header-label">设备总数</span>
<span class="header-value green">500</span>
<span class="header-unit"></span>
</div>
</div>
<div class="contents">
<div class="content">
<div class="content-left">
<div class="first">
<div class="first-total">
<div class="first-total-title">今日用电量</div>
<div class="first-total-value">
<div class="first-total-value-number">1</div>
<div class="first-total-value-number">2</div>
<div class="first-total-value-number">3</div>
</div>
<div class="first-total-unit">kwh</div>
</div>
<div ref="barChart" class="bar-chart" style="width:100%;height:80%;"></div>
</div>
<div class="second">
<div ref="powerChart" class="power-chart"></div>
</div>
<div class="third">
<div class="env-cards">
<div class="env-card">
<div class="env-card-content">
<div class="env-title">PM2.5(μg/)</div>
<div class="env-value">5</div>
</div>
</div>
<div class="env-card">
<div class="env-card-content">
<div class="env-title">PM10(μg/)</div>
<div class="env-value">120</div>
</div>
</div>
<div class="env-card">
<div class="env-card-content">
<div class="env-title">噪声(dB(A))</div>
<div class="env-value">50</div>
</div>
</div>
<div class="env-card">
<div class="env-card-content">
<div class="env-title">一氧化碳(ppm)</div>
<div class="env-value">10</div>
</div>
</div>
</div>
<div ref="envChart" class="env-chart"></div>
</div>
</div>
<div class="content-center">
<div class="content-center-first">
<div class="first-item">
<div class="item"></div>
</div>
</div>
<div class="content-center-second">
<div class="second-item">
<div class="second-item-text">645</div>
<div class="second-item-text">729</div>
<div class="second-item-text">648</div>
<div class="second-item-text">786</div>
</div>
</div>
</div>
<div class="content-right">
<div class="first">
<div class="first-total">
<div class="first-total-title">今日用水量</div>
<div class="first-total-value">
<div class="first-total-value-number">1</div>
<div class="first-total-value-number">2</div>
<div class="first-total-value-number">3</div>
</div>
<div class="first-total-unit"></div>
</div>
<div ref="pie3dChart" class="pie3d-chart"></div>
</div>
<div class="second">
<div ref="waterChart" class="water-chart"></div>
</div>
<div class="third">
<div class="device-cards">
<div class="device-card1">
<div>
<img src="../../../assets/energyConsumptionAnalysis/devices-number-icon.png" style="width: 1.75rem;height: 1.75rem;" alt="">
</div>
<div class="device-card-text-box">
<div class="device-card-text">设备总数</div>
<div class="device-card1-value">650</div>
</div>
</div>
<div class="device-card2">
<div>
<img src="../../../assets/energyConsumptionAnalysis/devices-online-icon.png" style="width: 1.75rem;height: 1.75rem;" alt="">
</div>
<div>
<div class="device-card-text">设备在线数</div>
<div class="device-card2-value">632</div>
</div>
</div>
<div class="device-card3">
<div>
<img src="../../../assets/energyConsumptionAnalysis/devices-offline-icon.png" style="width: 1.75rem;height: 1.75rem;" alt="">
</div>
<div>
<div class="device-card-text">设备离线数</div>
<div class="device-card3-value">18</div>
</div>
</div>
</div>
<div ref="deviceChart" class="device-chart"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
import 'echarts-gl'
import { getThreeDBarOption } from '#/utils/threeDBarOption'
import { renderPie3DChart } from '#/utils/pie3d'
import { addChartToResizeManager, removeChartFromResizeManager } from '#/utils/echartsResize'
// 路由
const router = useRouter()
// 图表实例
const barChart = ref<HTMLElement>()
const powerChart = ref<HTMLElement>()
const envChart = ref<HTMLElement>()
const waterChart = ref<HTMLElement>()
const deviceChart = ref<HTMLElement>()
const pie3dChart = ref<HTMLElement>()
// 定时器
let timer: number | null = null
// 星期几
const weekDay = ref('')
// 图表实例
let barChartInstance: echarts.ECharts | null = null
let powerChartInstance: echarts.ECharts | null = null
let envChartInstance: echarts.ECharts | null = null
let waterChartInstance: echarts.ECharts | null = null
let deviceChartInstance: echarts.ECharts | null = null
let pie3dChartInstance: any = null
// 退出方法
const logout = () => {
router.push('/navigation')
}
// 更新时间
const updateTime = () => {
const now = new Date()
const time = now.toLocaleTimeString('zh-CN', { hour12: false })
const date = now.getFullYear() + '.' +
String(now.getMonth() + 1).padStart(2, '0') + '.' +
String(now.getDate()).padStart(2, '0')
// 获取星期几
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
weekDay.value = '星期' + weekDays[now.getDay()]
const timeElement = document.getElementById('time')
const dateElement = document.getElementById('date')
if (timeElement) timeElement.innerText = time
if (dateElement) dateElement.innerText = date
}
// 初始化柱状图
const initBarChart = () => {
if (!barChart.value) return
const chart = echarts.init(barChart.value)
const option = getThreeDBarOption({
xData: ['A区', 'B区', 'C区', 'D区'],
yData: [320, 452, 688, 400]
})
chart.setOption(option)
barChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化电力图表
const initPowerChart = () => {
if (!powerChart.value) return
const chart = echarts.init(powerChart.value)
const option = {
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 40, bottom: 30 },
xAxis: {
type: 'category',
data: Array.from({length: 19}, (_, i) => i + 6),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
name: '',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [{
data: [80, 120, 100, 130, 150, 180, 200, 220, 250, 285, 230, 200, 180, 150, 120, 100, 80, 60, 40],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 0,
itemStyle: { color: '#3ec6ff' },
lineStyle: { width: 1 },
areaStyle: { color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#32B7E9' },
{ offset: 1, color: 'rgba(50,183,233,0)' }
]
), }
}]
}
chart.setOption(option)
powerChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化环境图表
const initEnvChart = () => {
if (!envChart.value) return
const chart = echarts.init(envChart.value)
const option = {
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 10, bottom: 20 },
xAxis: {
type: 'category',
data: Array.from({length: 8}, (_, i) => (i+1)*3),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [
{
name: '今年',
data: [100, 200, 150, 300, 250, 200, 100, 80],
type: 'line',
smooth: true,
itemStyle: { color: '#b388ff' },
lineStyle: { width: 3 },
areaStyle: { color: 'rgba(179,136,255,0.2)' },
symbol: 'circle',
symbolSize: 0,
},
{
name: '去年',
data: [80, 120, 100, 180, 150, 120, 60, 40],
type: 'line',
smooth: true,
itemStyle: { color: '#ffb300' },
lineStyle: { width: 3 },
areaStyle: { color: 'rgba(255,179,0,0.15)' },
symbol: 'circle',
symbolSize: 0,
}
]
}
chart.setOption(option)
envChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化水表图表
const initWaterChart = () => {
if (!waterChart.value) return
const chart = echarts.init(waterChart.value)
const option = {
tooltip: { trigger: 'axis' },
legend: {
data: ['今日', '平均'],
textStyle: { color: '#fff' },
right: 20,
top: 10,
bottom: 10
},
grid: { left: 40, right: 20, top: 40, bottom: 30 },
xAxis: {
type: 'category',
data: Array.from({length: 19}, (_, i) => i + 6),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [
{
name: '今日',
data: [1.2, 2.1, 1.8, 2.5, 3.0, 3.8, 4.2, 4.5, 4.8, 4.8, 4.0, 3.2, 2.5, 2.0, 1.5, 1.0, 0.8, 0.5, 0.2],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 0,
itemStyle: { color: '#3ec6ff' },
lineStyle: { width: 1 },
areaStyle: { color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#32B7E9' },
{ offset: 1, color: 'rgba(50,183,233,0)' }
]
), }
},
{
name: '平均',
data: Array(19).fill(2.5),
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: { color: '#ff6b00' },
lineStyle: { width: 1, type: 'solid', color: '#ff6b00' }
}
]
}
chart.setOption(option)
waterChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化设备图表
const initDeviceChart = () => {
if (!deviceChart.value) return
const chart = echarts.init(deviceChart.value)
const option = {
grid: { left: 60, right: 40, top: 10, bottom: 30 },
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#8697BA' } },
axisLabel: { color: '#fff' },
splitLine: { show: false }
},
yAxis: {
type: 'category',
data: ['门禁', '监控', '水表'],
axisLine: { lineStyle: { color: '#8697BA' } },
axisLabel: { color: '#A1B2C2' }
},
series: [
{
name: '设备数',
type: 'bar',
data: [650, 120, 40],
barWidth: 12,
itemStyle: {
color: function(params: any) {
const colors = [
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#2986B1' },
{ offset: 1, color: '#6941FF' }
]
),
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#33FF99' },
{ offset: 1, color: '#00908E' }
]
),
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#01B4FF' },
{ offset: 1, color: '#0336FF' }
]
),
];
return colors[params.dataIndex]
}
},
label: {
show: true,
position: 'right',
color: '#fff'
}
}
]
}
chart.setOption(option)
deviceChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化3D饼图
const initPie3DChart = () => {
if (!pie3dChart.value) return
pie3dChartInstance = renderPie3DChart(pie3dChart.value, {
data: [
{ name: 'A区域', value: 20, itemStyle: { color: '#3ffbff' } },
{ name: 'D区域', value: 20, itemStyle: { color: '#b388ff' } },
{ name: 'C区域', value: 45, itemStyle: { color: '#ff9900' } },
{ name: 'B区域', value: 15, itemStyle: { color: '#3f6bff' } },
],
hoverHeightScale: 2,
selectOffset: 0.1,
distance: 220,
boxHeight: 5
})
if (pie3dChartInstance) {
addChartToResizeManager(pie3dChartInstance)
}
}
// 组件挂载时初始化
onMounted(() => {
updateTime()
timer = setInterval(updateTime, 1000)
initBarChart()
initPowerChart()
initEnvChart()
initWaterChart()
initDeviceChart()
initPie3DChart()
})
// 组件卸载时清理
onBeforeUnmount(() => {
if (timer) {
clearInterval(timer)
}
// 从管理器中移除图表
if (barChartInstance) {
removeChartFromResizeManager(barChartInstance)
barChartInstance.dispose()
}
if (powerChartInstance) {
removeChartFromResizeManager(powerChartInstance)
powerChartInstance.dispose()
}
if (envChartInstance) {
removeChartFromResizeManager(envChartInstance)
envChartInstance.dispose()
}
if (waterChartInstance) {
removeChartFromResizeManager(waterChartInstance)
waterChartInstance.dispose()
}
if (deviceChartInstance) {
removeChartFromResizeManager(deviceChartInstance)
deviceChartInstance.dispose()
}
if (pie3dChartInstance) {
removeChartFromResizeManager(pie3dChartInstance)
pie3dChartInstance.dispose()
}
})
</script>
<style scoped>
.mian{
height: 100vh;
background: url("../../../assets/energyConsumptionAnalysis/bg.png");
background-size: 100% 100%;
background-color: #081b3a;
display: flex;
flex-direction: column;
}
.title {
height: 5.375rem;
align-items: center;
display: flex;
justify-content: space-between;
.left{
display: flex;
width: 14.3125rem;
.left-first{
padding-left: 2.3125rem;
padding-right: 3.5rem;
font-size: 1.875rem;
color: #FFFFFF;
}
.left-second{
width: 6.5rem;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.25rem;
color: #FFFFFF;
}
}
.center{
font-size: 1.9rem;
color: #fff;
text-align: center;
font-weight: bold;
letter-spacing: 0.1em;
text-shadow: 0 0 10px #1e90ff, 0 0 20px #1e90ff;
}
.right{
width: 17.3125rem;
display: flex;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.25rem;
color: #FFFFFF;
gap: .75rem;
.logout{
display: flex;
cursor: pointer;
align-items: center;
gap: .2rem;
}
}
}
.header{
/* margin-top: 1.125rem; */
margin-left: 4.625rem;
margin-right: 4.25rem;
height: 4.4rem;
display: flex;
justify-content: space-between;
.header-item {
display: flex;
align-items: center;
justify-content: center;
padding: 0 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 0 10px #0ff2, 0 0 20px #0ff2 inset;
}
.header-label {
color: #fff;
font-size: 1rem;
margin-right: 0.5rem;
letter-spacing: 0.05em;
}
.header-value {
font-size: 1.5rem;
font-weight: bold;
margin: 0 0.2rem;
}
.header-value.orange { color: #ffb300; }
.header-value.green { color: #00ffb0; }
.header-value.blue { color: #3ec6ff; }
.header-value.purple { color: #b388ff; }
.header-unit {
color: #fff;
font-size: 1rem;
margin-left: 0.2rem;
}
}
.header div{
width: 20.125rem;
}
.content{
flex:1;
height: 100%;
display: flex;
justify-content: space-between;
margin: 0 2.25rem 2.25rem 2.25rem;
.content-left{
width: 32.6875rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.first{
height: 13.9rem;
.first-total{
display: flex;
justify-content: flex-end;
align-items: center;
padding: 1rem 1rem 0 0;
font-size: 1.12rem;
color: #89EAFF;
.first-total-title{
font-family: Microsoft YaHei;
font-weight: 400;
font-size: 1.12rem;
line-height: 1rem;
}
.first-total-value{
display: flex;
align-items: center;
.first-total-value-number{
font-family: Microsoft YaHei;
font-weight: bold;
font-size: 1.3rem;
color: #89EAFF;
line-height: 1rem;
padding: .3rem;
margin: 0 .1rem;
border: .02rem solid rgba(99,145,180,0.59);
}
}
}
}
.second{
height: 12.5rem;
/* margin-top:2.25rem;
margin-bottom: 2rem; */
.power-chart {
height: 100%;
margin-top: 0.2rem;
}
}
.third{
height: 16rem;
display: flex;
flex-direction: column;
/* justify-content: space-around; */
.env-cards {
display: flex;
justify-content: space-between;
margin-top: 5rem;
margin-left: 1rem;
margin-right: 1.1rem;
.env-card {
width: 6.9rem;
height: 3.2rem;
border-radius: 0.5rem;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
.env-card-content{
.env-title {
font-size: .9rem;
font-weight: 400;
color: #4CE2D1;
}
.env-value {
font-size: .8rem;
font-weight: bold;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
color: #FFFFFF;
}
}
}
}
.env-chart {
height: 8rem;
}
}
}
.content-center{
width: 47.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.content-center-first{
height: 31.9rem;
.first-item{
height: 100%;
width: 100%;
background: url("../../../assets/energyConsumptionAnalysis/center-bg.png");
background-size: 95% 95%;
background-repeat: no-repeat;
background-position: center;
}
}
.content-center-second{
height: 12.5rem;
.second-item{
margin-top: 6rem;
margin-left: 3.68rem;
margin-bottom: 6.5rem;
margin-right: 4.9rem;
display: flex;
justify-content: space-between;
.second-item-text{
width: 5.37rem;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
text-align: center;
}
}
}
}
.content-right{
width: 32.68rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.first{
height: 14.9rem;
.first-total{
display: flex;
justify-content: flex-end;
align-items: center;
padding: 1rem 1rem 0 0;
font-size: 1.12rem;
color: #89EAFF;
.first-total-title{
font-family: Microsoft YaHei;
font-weight: 400;
font-size: 1.12rem;
line-height: 1rem;
}
.first-total-value{
display: flex;
align-items: center;
.first-total-value-number{
font-family: Microsoft YaHei;
font-weight: bold;
font-size: 1.3rem;
color: #89EAFF;
line-height: 1rem;
padding: .3rem;
margin: 0 .1rem;
border: .02rem solid rgba(99,145,180,0.59);
}
}
}
}
.second{
height: 12.5rem;
/* margin-top:2.25rem;
margin-bottom: 2rem; */
.water-chart {
height: 100%;
margin-top: 0.2rem;
}
}
.third{
height: 14rem;
.device-cards {
display: flex;
justify-content: space-between;
margin-top: 2rem;
margin-right: 1.06rem;
/* margin-bottom: 0.5rem; */
margin-left: 1.31rem;
.device-card1{
height: 3.2rem;
width: 7.21rem;
background: url("../../../assets/energyConsumptionAnalysis/devices-number.png");
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
.device-card1-value{
font-family: Arial;
font-weight: bold;
font-size: 1.25rem;
color: #02B3F4;
}
}
.device-card2{
height: 3.2rem;
width: 7.21rem;
background: url("../../../assets/energyConsumptionAnalysis/devices-online.png");
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
.device-card2-value{
font-family: Arial;
font-weight: bold;
font-size: 1.25rem;
color: #1DE39D;
}
}
.device-card3{
height: 3.2rem;
width: 7.21rem;
background: url("../../../assets/energyConsumptionAnalysis/devices-offline.png");
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
.device-card3-value{
font-family: Arial;
font-weight: bold;
font-size: 1.25rem;
color: #F19315;
}
}
.device-card-text{
font-family: Microsoft YaHei UI;
font-weight: 400;
font-size: 0.75rem;
color: #A1B2C2;
line-height: 0.75rem;
}
}
.device-chart {
height: 8rem;
/* margin-top: 0.2rem; */
}
}
}
}
.pie3d-title {
color: #fff;
font-size: 1.1rem;
font-weight: bold;
margin-bottom: 0.3rem;
}
.pie3d-water {
color: #fff;
font-size: 0.95rem;
margin-bottom: 0.2rem;
}
.pie3d-water-num {
font-size: 1.3rem;
color: #3ec6ff;
background: #0a1e3a;
border-radius: 0.2rem;
padding: 0 0.2rem;
margin: 0 0.2rem;
letter-spacing: 0.1em;
}
.pie3d-chart {
width: 100%;
height: 176px;
min-height: unset;
margin: 0 auto;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
<template>
<div class="navigation-bg">
<div class="navigation-head">
<div class="navigation-head-left">超级管理员 欢迎你</div>
<div class="navigation-head-center">南川区综合服务中心数智平台</div>
<div class="navigation-head-right" @click="logout">
<span>
<!-- <img src="../../../assets/return.png" alt="退出"> -->
退出
</span>
</div>
</div>
<div class="navigation-body">
<div class="navigation-body-left">
<div @click="goToProperty()">基础物业</div>
<div @click="goToEnergyAnalysis()">能耗分析</div>
<div @click="goToSecurity()">安防大屏</div>
<div @click="goToMonitor()">监控大屏</div>
</div>
<div class="navigation-body-center">
<div class="top-content">
<div class="top-content-text1"><div class="text1">数据管理</div></div>
<div class="top-content-text2"><div class="text1">运行监控</div></div>
<div class="top-content-text3"><div class="text1">信息管理</div></div>
<div class="top-content-text4"><div class="text1">配置管理</div></div>
<div class="top-content-text5"><div class="text1">登录管理</div></div>
<div class="top-content-text6"><div class="text1">驾驶舱</div></div>
</div>
<div class="bottom-content">
<div class="bottom-content-text1"><div class="text">数字建模</div></div>
<div class="bottom-content-text2"><div class="text">指挥中心</div></div>
<div class="bottom-content-text3"><div class="text">软件支撑</div></div>
<div class="bottom-content-text4"><div class="text">应用管理</div></div>
<div class="bottom-content-text5"><div class="text">数据管理</div></div>
</div>
</div>
<div class="navigation-body-right">
<div @click="goToDigitalIntelligence()">商务中心</div>
<div @click="goToHome()">后台管理</div>
<div>能源管理</div>
<div>能耗分析</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { preferences } from '@vben/preferences';
const router = useRouter()
// 退出方法
const logout = () => {
// 这里可以添加清除 token、重定向登录页等逻辑
console.log('用户退出')
router.push('/login') // 假设登录页面路径为 /login
}
//物业
const goToProperty = () => {
router.push('/property')
}
//能耗分析
const goToEnergyAnalysis = () => {
router.push('/energyAnalysis')
}
// 监控大屏
const goToSecurity = () => {
router.push('/security')
}
// 监控大屏
const goToMonitor = () => {
router.push('/monitor')
}
// 商务中心数智管理平台
const goToDigitalIntelligence = () => {
router.push('/digitalIntelligence')
}
//
const goToHome = () => {
router.push(preferences.app.defaultHomePath);
}
</script>
<style scoped>
.navigation-bg {
height: 100vh;
width: 100vw;
text-align: center;
background: url("../../../assets/navigation.png");
background-size: 100% 100%;
color: #fff;
text-shadow: 0 0.125rem 0.5rem #000;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.navigation-head {
display: flex;
align-items: center;
flex-wrap: wrap;
height: 3.5rem;
}
.navigation-head-center{
font-size: 1.9rem;
color: #fff;
text-align: center;
font-weight: bold;
letter-spacing: 0.1em;
}
.navigation-head-left,
.navigation-head-right {
flex: 1 1 100%;
text-align: center;
cursor: pointer;
font-size: 1rem;
padding-left: 1.3rem;
padding-right: 1.3rem;
}
@media (min-width: 48em) {
.navigation-head-left {
flex: 1;
text-align: left;
font-size: 0.875rem;
padding-left: 1.3rem;
}
.navigation-head-center {
flex: 3;
font-size: 1.5rem;
text-align: center;
}
.navigation-head-right {
flex: 1;
text-align: right;
font-size: 0.875rem;
padding-right: 1.3rem;
}
}
.navigation-body {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
}
.navigation-body-left {
width: 12%;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 2.25rem 1.25rem;
height: 35rem;
}
.navigation-body-left div {
background: url("../../../assets/shine.png") no-repeat center;
background-size: contain;
font-size: 1rem;
padding: 0.625rem 1.25rem;
min-width: 7.5rem;
max-width: 100%;
transition: transform 0.2s ease;
cursor: pointer;
color: #77DAFF;
}
.navigation-body-left div:hover {
color: #ffffff;
background-image: url("../../../assets/shine-lines.png");
}
.navigation-body-center{
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.top-content{
height: 70%;
padding-top: 26rem;
padding-left: 19.5rem;
padding-right: 20rem;
display: flex;
justify-content: space-between;
.top-content-text1{
font-size: 1.0625rem;
width: 4.6875rem;
height: 7.5rem;
padding-top: 0.5rem;
color: #86B6E6;
cursor: pointer;
}
.top-content-text2{
font-size: 1.0625rem;
font-weight: 600;
width: 4.6875rem;
height: 7.5rem;
padding-top: 1.25rem;
color: #86ECDE;
cursor: pointer;
}
.top-content-text3{
font-size: 1.0625rem;
font-weight: 600;
width: 4.6875rem;
height: 7.5rem;
padding-top: 2.5rem;
color: #FECFB4;
cursor: pointer;
}
.top-content-text4{
font-size: 1.0625rem;
font-weight: 600;
width: 4.6875rem;
height: 7.5rem;
padding-top: 2.625rem;
color: #CDADF4;
cursor: pointer;
}
.top-content-text5{
font-size: 1.0625rem;
font-weight: 600;
width: 4.6875rem;
height: 7.5rem;
padding-top: 1.5625rem;
color: #86ECDE;
cursor: pointer;
}
.top-content-text6{
font-size: 1.0625rem;
font-weight: 600;
width: 4.6875rem;
height: 7.5rem;
padding-top: 1.25rem;
color: #86B6E6;
cursor: pointer;
}
.text1{
padding: 0.3125rem 1.875rem;
}
}
.bottom-content{
height: 50%;
display: flex;
justify-content: space-between;
padding-top: 12.5rem;
padding-left: 23.625rem;
padding-right: 24.25rem;
font-size: 1rem;
color: #00EEFD;
.bottom-content-text1{
width: 6.875rem;
cursor: pointer;
}
.bottom-content-text2{
padding-top: 0.75rem;
width: 6.875rem;
cursor: pointer;
}
.bottom-content-text3{
padding-top: 1.125rem;
width: 6.875rem;
cursor: pointer;
}
.bottom-content-text4{
padding-top: 0.8125rem;
width: 6.875rem;
cursor: pointer;
}
.bottom-content-text5{
width: 6.875rem;
cursor: pointer;
}
.text{
align-items: center;
}
}
.navigation-body-right{
width: 12%;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 2.25rem 1.25rem;
height: 35rem;
}
.navigation-body-right div {
background: url("../../../assets/shine.png") no-repeat center;
background-size: contain;
font-size: 1rem;
padding: 0.625rem 1.25rem;
min-width: 7.5rem;
max-width: 100%;
transition: transform 0.2s ease;
cursor: pointer;
color: #77DAFF;
}
.navigation-body-right div:hover {
background-image: url("../../../assets/shine-lines.png");
color: #ffffff;
}
</style>

View File

@@ -0,0 +1,804 @@
<template>
<div class="mian">
<div class="title">
<div class="left">
<div class="left-first" id="time">--:--:--</div>
<div class="left-second" id="date">----</div>
</div>
<div class="center">南川区综合服务中心数智管理平台物业大屏</div>
<div class="right">
<div>{{ weekDay }}</div>
<div></div>
<div>40</div>
<div class="logout" @click="logout">
<img src="../../../assets/return.png" style="width: 1.5rem; height: 1.5rem;">
<div>
退出
</div>
</div>
</div>
</div>
<div class="header">
<div class="header-item">
<span class="header-label">今年用电量</span>
<span class="header-value orange">180</span>
<span class="header-unit">亿kwh</span>
</div>
<div class="header-item">
<span class="header-label">本月用电总量</span>
<span class="header-value green">1.8</span>
<span class="header-unit">亿kwh</span>
</div>
<div class="header-item">
<span class="header-label">今年用水总量</span>
<span class="header-value blue">2600</span>
<span class="header-unit">万吨</span>
</div>
<div class="header-item">
<span class="header-label">本月用水总量</span>
<span class="header-value purple">30</span>
<span class="header-unit">万吨</span>
</div>
<div class="header-item">
<span class="header-label">设备总数</span>
<span class="header-value green">500</span>
<span class="header-unit"></span>
</div>
</div>
<div class="contents">
<div class="content">
<div class="content-left">
<div class="first">
<div ref="barChart" class="bar-chart" style="width:100%;height:100%;"></div>
</div>
<div class="second">
<div ref="powerChart" class="power-chart"></div>
</div>
<div class="third">
<div ref="envChart" class="env-chart"></div>
</div>
</div>
<div class="content-center">
<div class="content-center-first">
<div class="first-item">
<div class="item"></div>
</div>
</div>
<div class="content-center-second">
<div class="second-item">
<div class="second-item-box1">645</div>
<div class="second-item-box2">729</div>
<div class="second-item-box3">648</div>
<div class="second-item-box4">786</div>
<div class="second-item-box5">645</div>
</div>
</div>
</div>
<div class="content-right">
<div class="first">
<div ref="waterChart" class="water-chart"></div>
<!-- <div ref="pie3dChart" class="pie3d-chart"></div> -->
</div>
<div class="second">
<div class="second-box">
<div class="box-content">
<div class="box-content-label">服务数量</div>
<div class="box-content-num">132</div>
</div>
<div class="box-content">
<div class="box-content-label">服务数量</div>
<div class="box-content-num">862</div>
</div>
<div class="box-content">
<div class="box-content-label">服务数量</div>
<div class="box-content-num">272</div>
</div>
</div>
<!-- <div ref="waterChart" class="water-chart"></div> -->
</div>
<div class="third">
<div ref="deviceChart" class="device-chart"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import * as echarts from 'echarts'
import 'echarts-gl'
import { getThreeDBarOption } from '#/utils/threeDBarOption'
import { renderPie3DChart } from '#/utils/pie3d'
import { addChartToResizeManager, removeChartFromResizeManager } from '#/utils/echartsResize'
// 路由
const router = useRouter()
// 图表实例
const barChart = ref<HTMLElement>()
const powerChart = ref<HTMLElement>()
const envChart = ref<HTMLElement>()
const waterChart = ref<HTMLElement>()
const deviceChart = ref<HTMLElement>()
const pie3dChart = ref<HTMLElement>()
// 定时器
let timer: number | null = null
// 星期几
const weekDay = ref('')
// 图表实例
let barChartInstance: echarts.ECharts | null = null
let powerChartInstance: echarts.ECharts | null = null
let envChartInstance: echarts.ECharts | null = null
let waterChartInstance: echarts.ECharts | null = null
let deviceChartInstance: echarts.ECharts | null = null
let pie3dChartInstance: any = null
// 退出方法
const logout = () => {
router.push('/navigation')
}
// 更新时间
const updateTime = () => {
const now = new Date()
const time = now.toLocaleTimeString('zh-CN', { hour12: false })
const date = now.getFullYear() + '.' +
String(now.getMonth() + 1).padStart(2, '0') + '.' +
String(now.getDate()).padStart(2, '0')
// 获取星期几
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
weekDay.value = '星期' + weekDays[now.getDay()]
const timeElement = document.getElementById('time')
const dateElement = document.getElementById('date')
if (timeElement) timeElement.innerText = time
if (dateElement) dateElement.innerText = date
}
// 初始化柱状图
const initBarChart = () => {
if (!barChart.value) return
const chart = echarts.init(barChart.value)
const option = getThreeDBarOption({
xData: ['A区', 'B区', 'C区', 'D区'],
yData: [320, 452, 688, 400]
})
chart.setOption(option)
barChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化电力图表
const initPowerChart = () => {
if (!powerChart.value) return
const chart = echarts.init(powerChart.value)
const option = {
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 32, bottom: 20 },
xAxis: {
type: 'category',
data: Array.from({length: 19}, (_, i) => i + 6),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
name: '',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [{
data: [80, 120, 100, 130, 150, 180, 200, 220, 250, 285, 230, 200, 180, 150, 120, 100, 80, 60, 40],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 0,
itemStyle: { color: '#3ec6ff' },
lineStyle: { width: 1 },
areaStyle: { color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#32B7E9' },
{ offset: 1, color: 'rgba(50,183,233,0)' }
]
), }
}]
}
chart.setOption(option)
powerChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化环境图表
const initEnvChart = () => {
if (!envChart.value) return
const chart = echarts.init(envChart.value)
const option = {
tooltip: { trigger: 'axis' },
grid: { left: 40, right: 20, top: 40, bottom: 30 },
xAxis: {
type: 'category',
data: Array.from({length: 8}, (_, i) => (i+1)*3),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [
{
name: '今年',
data: [100, 200, 150, 300, 250, 200, 100, 80],
type: 'line',
smooth: true,
itemStyle: { color: '#b388ff' },
lineStyle: { width: 3 },
areaStyle: { color: 'rgba(179,136,255,0.2)' },
symbol: 'circle',
symbolSize: 0,
},
{
name: '去年',
data: [80, 120, 100, 180, 150, 120, 60, 40],
type: 'line',
smooth: true,
itemStyle: { color: '#ffb300' },
lineStyle: { width: 3 },
areaStyle: { color: 'rgba(255,179,0,0.15)' },
symbol: 'circle',
symbolSize: 0,
}
]
}
chart.setOption(option)
envChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化水表图表
const initWaterChart = () => {
if (!waterChart.value) return
const chart = echarts.init(waterChart.value)
const option = {
tooltip: { trigger: 'axis' },
legend: {
data: ['今日', '平均'],
textStyle: { color: '#fff' },
right: 20,
top: 10
},
grid: { left: 40, right: 20, top: 40, bottom: 30 },
xAxis: {
type: 'category',
data: Array.from({length: 19}, (_, i) => i + 6),
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#3ec6ff' } },
axisLabel: { color: '#fff' },
splitLine: { lineStyle: { color: '#1e90ff22' } }
},
series: [
{
name: '今日',
data: [1.2, 2.1, 1.8, 2.5, 3.0, 3.8, 4.2, 4.5, 4.8, 4.8, 4.0, 3.2, 2.5, 2.0, 1.5, 1.0, 0.8, 0.5, 0.2],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 0,
itemStyle: { color: '#3ec6ff' },
lineStyle: { width: 1 },
areaStyle: { color: new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#32B7E9' },
{ offset: 1, color: 'rgba(50,183,233,0)' }
]
), }
},
{
name: '平均',
data: Array(19).fill(2.5),
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: { color: '#ff6b00' },
lineStyle: { width: 1, type: 'solid', color: '#ff6b00' }
}
]
}
chart.setOption(option)
waterChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化设备图表
const initDeviceChart = () => {
if (!deviceChart.value) return
const chart = echarts.init(deviceChart.value)
const option = {
grid: { left: 60, right: 40, top: 10, bottom: 30 },
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#8697BA' } },
axisLabel: { color: '#fff' },
splitLine: { show: false }
},
yAxis: {
type: 'category',
data: ['门禁', '监控', '水表'],
axisLine: { lineStyle: { color: '#8697BA' } },
axisLabel: { color: '#A1B2C2' }
},
series: [
{
name: '设备数',
type: 'bar',
data: [650, 120, 40],
barWidth: 12,
itemStyle: {
color: function(params: any) {
const colors = [
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#2986B1' },
{ offset: 1, color: '#6941FF' }
]
),
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#33FF99' },
{ offset: 1, color: '#00908E' }
]
),
new echarts.graphic.LinearGradient(
0, 0, 0, 1, // 上→下
[
{ offset: 0, color: '#01B4FF' },
{ offset: 1, color: '#0336FF' }
]
),
];
return colors[params.dataIndex]
}
},
label: {
show: true,
position: 'right',
color: '#fff'
}
}
]
}
chart.setOption(option)
deviceChartInstance = chart
addChartToResizeManager(chart)
}
// 初始化3D饼图
const initPie3DChart = () => {
if (!pie3dChart.value) return
pie3dChartInstance = renderPie3DChart(pie3dChart.value, {
data: [
{ name: 'A区域', value: 20, itemStyle: { color: '#3ffbff' } },
{ name: 'D区域', value: 20, itemStyle: { color: '#b388ff' } },
{ name: 'C区域', value: 45, itemStyle: { color: '#ff9900' } },
{ name: 'B区域', value: 15, itemStyle: { color: '#3f6bff' } },
],
hoverHeightScale: 2,
selectOffset: 0.1,
distance: 220,
boxHeight: 5
})
if (pie3dChartInstance) {
addChartToResizeManager(pie3dChartInstance)
}
}
// 组件挂载时初始化
onMounted(() => {
updateTime()
timer = setInterval(updateTime, 1000)
initBarChart()
initPowerChart()
initEnvChart()
initWaterChart()
initDeviceChart()
initPie3DChart()
})
// 组件卸载时清理
onBeforeUnmount(() => {
if (timer) {
clearInterval(timer)
}
// 从管理器中移除图表
if (barChartInstance) {
removeChartFromResizeManager(barChartInstance)
barChartInstance.dispose()
}
if (powerChartInstance) {
removeChartFromResizeManager(powerChartInstance)
powerChartInstance.dispose()
}
if (envChartInstance) {
removeChartFromResizeManager(envChartInstance)
envChartInstance.dispose()
}
if (waterChartInstance) {
removeChartFromResizeManager(waterChartInstance)
waterChartInstance.dispose()
}
if (deviceChartInstance) {
removeChartFromResizeManager(deviceChartInstance)
deviceChartInstance.dispose()
}
if (pie3dChartInstance) {
removeChartFromResizeManager(pie3dChartInstance)
pie3dChartInstance.dispose()
}
})
</script>
<style scoped>
.mian{
height: 100vh;
background: url("../../../assets/property/bg.png");
background-size: 100% 100%;
background-color: #081b3a;
display: flex;
flex-direction: column;
}
.title {
height: 5.375rem;
align-items: center;
display: flex;
justify-content: space-between;
.left{
display: flex;
width: 14.3125rem;
.left-first{
padding-left: 2.3125rem;
padding-right: 3.5rem;
font-size: 1.875rem;
color: #FFFFFF;
}
.left-second{
width: 6.5rem;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.25rem;
color: #FFFFFF;
}
}
.center{
font-size: 1.9rem;
color: #fff;
text-align: center;
font-weight: bold;
letter-spacing: 0.1em;
text-shadow: 0 0 10px #1e90ff, 0 0 20px #1e90ff;
}
.right{
width: 17.3125rem;
display: flex;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.25rem;
color: #FFFFFF;
gap: .75rem;
.logout{
display: flex;
cursor: pointer;
align-items: center;
gap: .2rem;
}
}
}
.header{
margin-top: 1.125rem;
margin-left: 4.625rem;
margin-right: 4.25rem;
height: 6rem;
display: flex;
justify-content: space-between;
.header-item {
display: flex;
align-items: center;
justify-content: center;
padding: 0 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 0 10px #0ff2, 0 0 20px #0ff2 inset;
}
.header-label {
color: #fff;
font-size: 1rem;
margin-right: 0.5rem;
letter-spacing: 0.05em;
}
.header-value {
font-size: 1.5rem;
font-weight: bold;
margin: 0 0.2rem;
}
.header-value.orange { color: #ffb300; }
.header-value.green { color: #00ffb0; }
.header-value.blue { color: #3ec6ff; }
.header-value.purple { color: #b388ff; }
.header-unit {
color: #fff;
font-size: 1rem;
margin-left: 0.2rem;
}
}
.header div{
width: 20.125rem;
}
.contents{
flex: 1;
.content{
height: 100%;
display: flex;
justify-content: space-between;
margin: 0 2.25rem 2.25rem 2.25rem;
.content-left{
width: 32.6875rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.first{
height: 16.81rem;
.pie3d-title {
color: #fff;
font-size: 1.1rem;
font-weight: bold;
margin-bottom: 0.3rem;
}
.pie3d-water {
color: #fff;
font-size: 0.95rem;
margin-bottom: 0.2rem;
}
.pie3d-water-num {
font-size: 1.3rem;
color: #3ec6ff;
background: #0a1e3a;
border-radius: 0.2rem;
padding: 0 0.2rem;
margin: 0 0.2rem;
letter-spacing: 0.1em;
}
.pie3d-chart {
width: 100%;
height: 176px;
min-height: unset;
margin: 0 auto;
}
}
.second{
height: 12rem;
/* margin-top:2.25rem; */
/* margin-bottom: 2rem; */
.power-chart {
height: 100%;
/* margin-top: 0.2rem; */
}
}
.third{
height: 12.12rem;
.env-chart {
height: 12rem;
}
}
}
.content-center{
width: 47.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.content-center-first{
height: 30.9rem;
.first-item{
height: 100%;
width: 100%;
background: url("../../../assets/property/center-bg.png");
background-size: 95% 95%;
background-repeat: no-repeat;
background-position: center;
}
}
.content-center-second{
display: flex;
justify-content: center;
align-items: center;
height: 12.5rem;
.second-item{
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
.second-item-box1{
width: 7rem;
height: 7rem;
display: flex;
justify-content: center;
align-items: center;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
background: url("../../../assets/property/personnel-duty-circle1.png");
background-size: 100% 100%;
}
.second-item-box2{
width: 7rem;
height: 7rem;
display: flex;
justify-content: center;
align-items: center;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
background: url("../../../assets/property/personnel-duty-circle2.png");
background-size: 100% 100%;
}
.second-item-box3{
width: 7rem;
height: 7rem;
display: flex;
justify-content: center;
align-items: center;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
background: url("../../../assets/property/personnel-duty-circle3.png");
background-size: 100% 100%;
}
.second-item-box4{
width: 7rem;
height: 7rem;
display: flex;
justify-content: center;
align-items: center;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
background: url("../../../assets/property/personnel-duty-circle1.png");
background-size: 100% 100%;
}
.second-item-box5{
width: 7rem;
height: 7rem;
display: flex;
justify-content: center;
align-items: center;
font-family: ShiShangZhongHeiJianTi;
font-weight: 400;
font-size: 1.37rem;
color: #FFFFFF;
background: url("../../../assets/property/personnel-duty-circle4.png");
background-size: 100% 100%;
}
}
}
}
.content-right{
width: 32.68rem;
display: flex;
flex-direction: column;
justify-content: space-between;
.first{
height: 16.81rem;
.water-chart {
height: 100%;
/* margin-top: 0.2rem; */
}
}
.second{
height: 11rem;
margin-top:2rem;
margin-bottom: 2rem;
.second-box{
margin-top: 2rem;
display: flex;
justify-content: space-around;
align-items: center;
.box-content{
height: 8rem;
width: 8rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.box-content-label{
font-family: Microsoft YaHei;
font-weight: 400;
font-size: .8rem;
color: #C4D6FF;
line-height: 2rem;
}
.box-content-num{
display: flex;
justify-content: center;
align-items: center;
width: 6rem;
height: 6rem;
background: url("../../../assets/property/customer-circle.png");
background-size: 100% 100%;
font-family: Microsoft YaHei;
font-weight: 400;
font-size: 1.2rem;
color: #FFFFFF;
}
}
}
}
.third{
height: 13.12rem;
.device-chart {
height: 10rem;
margin-top: 2rem;
}
}
}
}
}
.pie3d-title {
color: #fff;
font-size: 1.1rem;
font-weight: bold;
margin-bottom: 0.3rem;
}
.pie3d-water {
color: #fff;
font-size: 0.95rem;
margin-bottom: 0.2rem;
}
.pie3d-water-num {
font-size: 1.3rem;
color: #3ec6ff;
background: #0a1e3a;
border-radius: 0.2rem;
padding: 0 0.2rem;
margin: 0 0.2rem;
letter-spacing: 0.1em;
}
.pie3d-chart {
width: 100%;
height: 176px;
min-height: unset;
margin: 0 auto;
}
</style>

File diff suppressed because it is too large Load Diff