feat: 移动端开发规范重大更新
- 🔥 完全重写 .cursor/cursorrules-mobile.md (940行) * 基于实际mobile工程Vue 3.4.21+uniapp+uview-plus架构 * 移除所有JeeSite相关内容,精准匹配实际项目 * 新增11个完整章节:技术栈、项目结构、页面开发、组件规范等 - 📚 创建完整规范文档 docs/开发文档/移动端开发规范总结.md (800+行) * 涵盖从项目架构到部署的全流程指导 * 提供可直接使用的页面和组件模板 * 包含现代CSS变量设计系统和用户体验优化 - 🔄 同步更新主配置文件 .cursorrules * 更新移动端技术栈描述 * 调整为Vue 3.4.21+uniapp+uview-plus 3.4.45架构 - 📝 完善变更记录 * 详细记录规范更新背景和技术价值 * 为后续维护提供完整历史记录 核心价值:解决规范与实际项目不匹配问题,为Cursor AI提供准确的移动端开发上下文,确保生成代码完全符合实际项目架构。
This commit is contained in:
1 parent
e8ece1b612
commit
28232d5d46
131 files changed
+17170
-5479
No files matched your search
@@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './info';
|
||||
export * from './reservation';
|
||||
@@ -0,0 +1,77 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { VenueInfoVO, VenueInfoForm, VenueInfoQuery } from './types';
|
||||
|
||||
/**
|
||||
* 查询场地信息列表
|
||||
*/
|
||||
export const listVenueInfo = (query?: VenueInfoQuery): AxiosPromise<VenueInfoVO[]> => {
|
||||
return request({
|
||||
url: '/venue/info/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询场地信息详细
|
||||
*/
|
||||
export const getVenueInfo = (id: string | number): AxiosPromise<VenueInfoVO> => {
|
||||
return request({
|
||||
url: `/venue/info/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增场地信息
|
||||
*/
|
||||
export const addVenueInfo = (data: VenueInfoForm) => {
|
||||
return request({
|
||||
url: '/venue/info',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改场地信息
|
||||
*/
|
||||
export const updateVenueInfo = (data: VenueInfoForm) => {
|
||||
return request({
|
||||
url: '/venue/info',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除场地信息
|
||||
*/
|
||||
export const delVenueInfo = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: `/venue/info/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取可用场地列表
|
||||
*/
|
||||
export const getAvailableVenues = (): AxiosPromise<VenueInfoVO[]> => {
|
||||
return request({
|
||||
url: '/venue/info/available',
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出场地信息
|
||||
*/
|
||||
export const exportVenueInfo = (query: VenueInfoQuery) => {
|
||||
return request({
|
||||
url: '/venue/info/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {
|
||||
VenueReservationVO,
|
||||
VenueReservationForm,
|
||||
VenueReservationQuery,
|
||||
ReservationApproveForm,
|
||||
ConflictCheckQuery,
|
||||
ConflictReservationVO
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 查询场地预约列表
|
||||
*/
|
||||
export const listVenueReservation = (query?: VenueReservationQuery) => {
|
||||
return request({
|
||||
url: '/venue/reservation/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询我的预约列表
|
||||
*/
|
||||
export const listMyReservations = (query?: VenueReservationQuery) => {
|
||||
return request({
|
||||
url: '/venue/reservation/my',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询待审批预约列表
|
||||
*/
|
||||
export const listPendingReservations = (query?: VenueReservationQuery) => {
|
||||
return request({
|
||||
url: '/venue/reservation/pending',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询场地预约详细
|
||||
*/
|
||||
export const getVenueReservation = (id: string | number): AxiosPromise<VenueReservationVO> => {
|
||||
return request({
|
||||
url: `/venue/reservation/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增场地预约
|
||||
*/
|
||||
export const addVenueReservation = (data: VenueReservationForm) => {
|
||||
return request({
|
||||
url: '/venue/reservation',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改场地预约
|
||||
*/
|
||||
export const updateVenueReservation = (data: VenueReservationForm) => {
|
||||
return request({
|
||||
url: '/venue/reservation',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除场地预约
|
||||
*/
|
||||
export const delVenueReservation = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: `/venue/reservation/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查时间冲突
|
||||
*/
|
||||
export const checkTimeConflict = (query: ConflictCheckQuery): AxiosPromise<ConflictReservationVO[]> => {
|
||||
return request({
|
||||
url: '/venue/reservation/conflict',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审批场地预约
|
||||
*/
|
||||
export const approveReservation = (data: ReservationApproveForm) => {
|
||||
return request({
|
||||
url: '/venue/reservation/approve',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出场地预约
|
||||
*/
|
||||
export const exportVenueReservation = (query: VenueReservationQuery) => {
|
||||
return request({
|
||||
url: '/venue/reservation/export',
|
||||
method: 'post',
|
||||
data: query
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 场地信息查询对象类型
|
||||
*/
|
||||
export interface VenueInfoQuery extends PageQuery {
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 场地编码 */
|
||||
venueCode?: string;
|
||||
/** 场地位置 */
|
||||
venueLocation?: string;
|
||||
/** 场地类型 */
|
||||
venueType?: string;
|
||||
/** 场地状态 */
|
||||
venueStatus?: string;
|
||||
/** 管理员姓名 */
|
||||
venueManagerName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地信息视图对象
|
||||
*/
|
||||
export interface VenueInfoVO {
|
||||
/** 主键ID */
|
||||
id?: string | number;
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 场地编码 */
|
||||
venueCode?: string;
|
||||
/** 场地位置 */
|
||||
venueLocation?: string;
|
||||
/** 场地容量 */
|
||||
venueCapacity?: number;
|
||||
/** 场地类型 */
|
||||
venueType?: string;
|
||||
/** 场地状态 */
|
||||
venueStatus?: string;
|
||||
/** 场地描述 */
|
||||
venueDescription?: string;
|
||||
/** 场地图片 */
|
||||
venueImages?: string;
|
||||
/** 管理员ID */
|
||||
venueManagerId?: string | number;
|
||||
/** 管理员姓名 */
|
||||
venueManagerName?: string;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地信息表单类型
|
||||
*/
|
||||
export interface VenueInfoForm {
|
||||
/** 主键ID */
|
||||
id?: string | number;
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 场地编码 */
|
||||
venueCode?: string;
|
||||
/** 场地位置 */
|
||||
venueLocation?: string;
|
||||
/** 场地容量 */
|
||||
venueCapacity?: number;
|
||||
/** 场地类型 */
|
||||
venueType?: string;
|
||||
/** 场地状态 */
|
||||
venueStatus?: string;
|
||||
/** 场地描述 */
|
||||
venueDescription?: string;
|
||||
/** 场地图片 */
|
||||
venueImages?: string;
|
||||
/** 管理员ID */
|
||||
venueManagerId?: string | number;
|
||||
/** 管理员姓名 */
|
||||
venueManagerName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地预约查询对象类型
|
||||
*/
|
||||
export interface VenueReservationQuery extends PageQuery {
|
||||
/** 场地ID */
|
||||
venueId?: string | number;
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 申请人姓名 */
|
||||
applicantName?: string;
|
||||
/** 预约日期 */
|
||||
reservationDate?: string;
|
||||
/** 预约状态 */
|
||||
reservationStatus?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地预约视图对象
|
||||
*/
|
||||
export interface VenueReservationVO {
|
||||
/** 主键ID */
|
||||
id?: string | number;
|
||||
/** 场地ID */
|
||||
venueId?: string | number;
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 申请人ID */
|
||||
applicantId?: string | number;
|
||||
/** 申请人姓名 */
|
||||
applicantName?: string;
|
||||
/** 申请人电话 */
|
||||
applicantPhone?: string;
|
||||
/** 预约日期 */
|
||||
reservationDate?: string;
|
||||
/** 开始时间 */
|
||||
startTime?: string;
|
||||
/** 结束时间 */
|
||||
endTime?: string;
|
||||
/** 预约目的 */
|
||||
reservationPurpose?: string;
|
||||
/** 参与人数 */
|
||||
participantCount?: number;
|
||||
/** 特殊要求 */
|
||||
specialRequirements?: string;
|
||||
/** 预约状态 */
|
||||
reservationStatus?: string;
|
||||
/** 审批备注 */
|
||||
approveRemark?: string;
|
||||
/** 审批人ID */
|
||||
approverId?: string | number;
|
||||
/** 审批人姓名 */
|
||||
approverName?: string;
|
||||
/** 审批时间 */
|
||||
approveTime?: string;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地预约表单类型
|
||||
*/
|
||||
export interface VenueReservationForm {
|
||||
/** 主键ID */
|
||||
id?: string | number;
|
||||
/** 场地ID */
|
||||
venueId?: string | number;
|
||||
/** 申请人电话 */
|
||||
applicantPhone?: string;
|
||||
/** 预约日期 */
|
||||
reservationDate?: string;
|
||||
/** 开始时间 */
|
||||
startTime?: string;
|
||||
/** 结束时间 */
|
||||
endTime?: string;
|
||||
/** 预约目的 */
|
||||
reservationPurpose?: string;
|
||||
/** 参与人数 */
|
||||
participantCount?: number;
|
||||
/** 特殊要求 */
|
||||
specialRequirements?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预约审批表单类型
|
||||
*/
|
||||
export interface ReservationApproveForm {
|
||||
/** 预约ID */
|
||||
id?: string | number;
|
||||
/** 预约状态 */
|
||||
reservationStatus?: string;
|
||||
/** 审批备注 */
|
||||
approveRemark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间冲突检查参数
|
||||
*/
|
||||
export interface ConflictCheckQuery {
|
||||
/** 场地ID */
|
||||
venueId?: string | number;
|
||||
/** 预约日期 */
|
||||
reservationDate?: string;
|
||||
/** 开始时间 */
|
||||
startTime?: string;
|
||||
/** 结束时间 */
|
||||
endTime?: string;
|
||||
/** 排除的预约ID */
|
||||
excludeId?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 冲突预约信息
|
||||
*/
|
||||
export interface ConflictReservationVO {
|
||||
/** 预约ID */
|
||||
id?: string | number;
|
||||
/** 场地名称 */
|
||||
venueName?: string;
|
||||
/** 申请人姓名 */
|
||||
applicantName?: string;
|
||||
/** 开始时间 */
|
||||
startTime?: string;
|
||||
/** 结束时间 */
|
||||
endTime?: string;
|
||||
/** 预约目的 */
|
||||
reservationPurpose?: string;
|
||||
/** 预约状态 */
|
||||
reservationStatus?: string;
|
||||
}
|
||||
Whitespace-only changes.
@@ -60,7 +60,7 @@ function addIframe() {
|
||||
min-height: calc(100vh - 50px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地名称" prop="venueName">
|
||||
<el-input v-model="queryParams.venueName" placeholder="请输入场地名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地编码" prop="venueCode">
|
||||
<el-input v-model="queryParams.venueCode" placeholder="请输入场地编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地位置" prop="venueLocation">
|
||||
<el-input v-model="queryParams.venueLocation" placeholder="请输入场地位置" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地类型" prop="venueType">
|
||||
<el-select v-model="queryParams.venueType" placeholder="请选择场地类型" clearable style="width: 100%">
|
||||
<el-option v-for="dict in hq_venue_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地状态" prop="venueStatus">
|
||||
<el-select v-model="queryParams.venueStatus" placeholder="请选择场地状态" clearable style="width: 100%">
|
||||
<el-option label="维修中" value="0" />
|
||||
<el-option label="可用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="管理员" prop="venueManagerName">
|
||||
<el-input v-model="queryParams.venueManagerName" placeholder="请输入管理员姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:venue:add']">新增场地</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:venue:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:venue:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:venue:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="venueList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="场地编码" align="center" prop="venueCode" width="120" />
|
||||
<el-table-column label="场地名称" align="center" prop="venueName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="场地位置" align="center" prop="venueLocation" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="容量" align="center" prop="venueCapacity" width="80" />
|
||||
<el-table-column label="场地类型" align="center" prop="venueType" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="hq_venue_type" :value="scope.row.venueType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="venueStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.venueStatus === '1' ? 'success' : 'danger'">
|
||||
{{ scope.row.venueStatus === '1' ? '可用' : '维修中' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="管理员" align="center" prop="venueManagerName" width="120" />
|
||||
<el-table-column label="场地描述" align="center" prop="venueDescription" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:venue:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:venue:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="handleDetail(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改场地信息对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="venueFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地名称" prop="venueName">
|
||||
<el-input v-model="form.venueName" placeholder="请输入场地名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地编码" prop="venueCode">
|
||||
<el-input v-model="form.venueCode" placeholder="请输入场地编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地位置" prop="venueLocation">
|
||||
<el-input v-model="form.venueLocation" placeholder="请输入场地位置" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地容量" prop="venueCapacity">
|
||||
<el-input-number v-model="form.venueCapacity" :min="1" :max="9999" placeholder="请输入场地容量" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地类型" prop="venueType">
|
||||
<el-select v-model="form.venueType" placeholder="请选择场地类型" style="width: 100%">
|
||||
<el-option v-for="dict in hq_venue_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地状态" prop="venueStatus">
|
||||
<el-radio-group v-model="form.venueStatus">
|
||||
<el-radio value="0">维修中</el-radio>
|
||||
<el-radio value="1">可用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="管理员" prop="venueManagerId">
|
||||
<el-select v-model="form.venueManagerId" placeholder="请选择管理员" clearable style="width: 100%">
|
||||
<el-option v-for="user in userOptions" :key="user.userId" :label="user.nickName" :value="user.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="场地图片" prop="venueImages">
|
||||
<image-upload v-model="form.venueImages" :limit="3" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="场地描述" prop="venueDescription">
|
||||
<el-input v-model="form.venueDescription" type="textarea" :rows="4" placeholder="请输入场地描述" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 场地详情对话框 -->
|
||||
<el-dialog title="场地详情" v-model="detailDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="场地编码">{{ detailData.venueCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地名称">{{ detailData.venueName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地位置">{{ detailData.venueLocation }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地容量">{{ detailData.venueCapacity }}人</el-descriptions-item>
|
||||
<el-descriptions-item label="场地类型">
|
||||
<dict-tag :options="hq_venue_type" :value="detailData.venueType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="场地状态">
|
||||
<el-tag :type="detailData.venueStatus === '1' ? 'success' : 'danger'">
|
||||
{{ detailData.venueStatus === '1' ? '可用' : '维修中' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="管理员">{{ detailData.venueManagerName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(detailData.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地描述" :span="2">{{ detailData.venueDescription }}</el-descriptions-item>
|
||||
<el-descriptions-item label="场地图片" :span="2" v-if="detailData.venueImages">
|
||||
<image-preview :src="detailData.venueImages" :z-index="3000" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="VenueInfo" lang="ts">
|
||||
import { listVenueInfo, getVenueInfo, delVenueInfo, addVenueInfo, updateVenueInfo } from '@/api/hq/venue/info';
|
||||
import { listUser } from '@/api/system/user';
|
||||
import { VenueInfoVO, VenueInfoQuery, VenueInfoForm } from '@/api/hq/venue/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { hq_venue_type } = toRefs<any>(proxy?.useDict('hq_venue_type'));
|
||||
|
||||
const venueList = ref<VenueInfoVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const userOptions = ref<any[]>([]);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<VenueInfoQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
venueName: undefined,
|
||||
venueCode: undefined,
|
||||
venueLocation: undefined,
|
||||
venueType: undefined,
|
||||
venueStatus: undefined,
|
||||
venueManagerName: undefined
|
||||
});
|
||||
|
||||
// 表单参数
|
||||
const form = ref<VenueInfoForm>({});
|
||||
const rules = reactive<ElFormRules>({
|
||||
venueName: [{ required: true, message: '场地名称不能为空', trigger: 'blur' }],
|
||||
venueCode: [{ required: true, message: '场地编码不能为空', trigger: 'blur' }],
|
||||
venueLocation: [{ required: true, message: '场地位置不能为空', trigger: 'blur' }],
|
||||
venueCapacity: [{ required: true, message: '场地容量不能为空', trigger: 'blur' }],
|
||||
venueType: [{ required: true, message: '场地类型不能为空', trigger: 'change' }],
|
||||
venueStatus: [{ required: true, message: '场地状态不能为空', trigger: 'change' }],
|
||||
venueManagerId: [{ required: true, message: '管理员不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 对话框
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 详情对话框
|
||||
const detailDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
const detailData = ref<VenueInfoVO>({});
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const venueFormRef = ref<ElFormInstance>();
|
||||
|
||||
/** 查询场地信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listVenueInfo(queryParams.value);
|
||||
venueList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: VenueInfoVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
getUserList();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加场地信息';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: VenueInfoVO) => {
|
||||
reset();
|
||||
await getUserList();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getVenueInfo(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改场地信息';
|
||||
};
|
||||
|
||||
/** 详情按钮操作 */
|
||||
const handleDetail = async (row: VenueInfoVO) => {
|
||||
const res = await getVenueInfo(row.id!);
|
||||
detailData.value = res.data;
|
||||
detailDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
venueFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
if (form.value.id) {
|
||||
await updateVenueInfo(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addVenueInfo(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: VenueInfoVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除场地编号为"' + _ids + '"的数据项?');
|
||||
await delVenueInfo(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'venue/info/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`venue_info_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
venueName: undefined,
|
||||
venueCode: undefined,
|
||||
venueLocation: undefined,
|
||||
venueCapacity: undefined,
|
||||
venueType: undefined,
|
||||
venueStatus: '1',
|
||||
venueDescription: undefined,
|
||||
venueImages: undefined,
|
||||
venueManagerId: undefined,
|
||||
venueManagerName: undefined
|
||||
};
|
||||
venueFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 获取用户列表 */
|
||||
const getUserList = async () => {
|
||||
const res = await listUser({ pageNum: 1, pageSize: 1000 });
|
||||
userOptions.value = res.rows;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button-card .el-card__header {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.button-card .el-card__body {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,785 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="场地名称" prop="venueName">
|
||||
<el-input v-model="queryParams.venueName" placeholder="请输入场地名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请人" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申请人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约日期" prop="reservationDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.reservationDate"
|
||||
type="date"
|
||||
placeholder="请选择预约日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约状态" prop="reservationStatus">
|
||||
<el-select v-model="queryParams.reservationStatus" placeholder="请选择预约状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in hq_reservation_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 快捷功能按钮 -->
|
||||
<el-row :gutter="10" class="mb-[10px]">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="handleAdd">
|
||||
<el-icon class="quick-icon"><Plus /></el-icon>
|
||||
<span>新增预约</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showMyReservations">
|
||||
<el-icon class="quick-icon"><User /></el-icon>
|
||||
<span>我的预约</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showPendingReservations">
|
||||
<el-icon class="quick-icon"><Clock /></el-icon>
|
||||
<span>待审批</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="quick-action-card">
|
||||
<div class="quick-action" @click="showConflictCheck">
|
||||
<el-icon class="quick-icon"><Warning /></el-icon>
|
||||
<span>冲突检查</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:reservation:add']">新增预约</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:reservation:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:reservation:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:reservation:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="reservationList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="场地名称" align="center" prop="venueName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" width="100" />
|
||||
<el-table-column label="申请人电话" align="center" prop="applicantPhone" width="120" />
|
||||
<el-table-column label="预约日期" align="center" prop="reservationDate" width="120" />
|
||||
<el-table-column label="预约时间" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ formatTime(scope.row.startTime) }} - {{ formatTime(scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预约目的" align="center" prop="reservationPurpose" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="参与人数" align="center" prop="participantCount" width="100" />
|
||||
<el-table-column label="预约状态" align="center" prop="reservationStatus" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="hq_reservation_status" :value="scope.row.reservationStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批人" align="center" prop="approverName" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="info" icon="View" @click="handleDetail(scope.row)"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top" v-if="canEdit(scope.row)">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:reservation:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="canDelete(scope.row)">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:reservation:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审批" placement="top" v-if="canApprove(scope.row)">
|
||||
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['hq:reservation:approve']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/修改预约对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="reservationFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择场地" prop="venueId">
|
||||
<el-select v-model="form.venueId" placeholder="请选择场地" style="width: 100%" @change="handleVenueChange">
|
||||
<el-option v-for="venue in availableVenues" :key="venue.id" :label="`${venue.venueName}(${venue.venueLocation})`" :value="venue.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请人电话" prop="applicantPhone">
|
||||
<el-input v-model="form.applicantPhone" placeholder="请输入申请人电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预约日期" prop="reservationDate">
|
||||
<el-date-picker
|
||||
v-model="form.reservationDate"
|
||||
type="date"
|
||||
placeholder="请选择预约日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
:disabled-date="disabledDate"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="参与人数" prop="participantCount">
|
||||
<el-input-number v-model="form.participantCount" :min="1" :max="9999" placeholder="请输入参与人数" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-time-picker v-model="form.startTime" placeholder="请选择开始时间" format="HH:mm" value-format="HH:mm" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-time-picker v-model="form.endTime" placeholder="请选择结束时间" format="HH:mm" value-format="HH:mm" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="预约目的" prop="reservationPurpose">
|
||||
<el-input v-model="form.reservationPurpose" type="textarea" :rows="3" placeholder="请输入预约目的" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="特殊要求">
|
||||
<el-input v-model="form.specialRequirements" type="textarea" :rows="3" placeholder="请输入特殊要求(如设备需求等)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="checkConflictAndSubmit">检查冲突并提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<el-dialog title="预约审批" v-model="approveDialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="approveFormRef" :model="approveForm" :rules="approveRules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="预约信息">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="场地名称">{{ currentReservation.venueName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{ currentReservation.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时间">
|
||||
{{ currentReservation.reservationDate }} {{ formatTime(currentReservation.startTime) }}-{{ formatTime(currentReservation.endTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预约目的">{{ currentReservation.reservationPurpose }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="审批结果" prop="reservationStatus">
|
||||
<el-radio-group v-model="approveForm.reservationStatus">
|
||||
<el-radio value="1">通过</el-radio>
|
||||
<el-radio value="2">拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="审批备注" prop="approveRemark">
|
||||
<el-input v-model="approveForm.approveRemark" type="textarea" :rows="4" placeholder="请输入审批备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="approveDialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitApprove">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog title="预约详情" v-model="detailDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="场地名称">{{ detailData.venueName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人">{{ detailData.applicantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请人电话">{{ detailData.applicantPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约日期">{{ detailData.reservationDate }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ formatTime(detailData.startTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ formatTime(detailData.endTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参与人数">{{ detailData.participantCount }}人</el-descriptions-item>
|
||||
<el-descriptions-item label="预约状态">
|
||||
<dict-tag :options="hq_reservation_status" :value="detailData.reservationStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预约目的" :span="2">{{ detailData.reservationPurpose }}</el-descriptions-item>
|
||||
<el-descriptions-item label="特殊要求" :span="2">{{ detailData.specialRequirements || '无' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人" v-if="detailData.approverName">{{ detailData.approverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间" v-if="detailData.approveTime">{{ parseTime(detailData.approveTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批备注" :span="2" v-if="detailData.approveRemark">{{ detailData.approveRemark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">{{ parseTime(detailData.createTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 冲突检查对话框 -->
|
||||
<el-dialog title="时间冲突检查" v-model="conflictDialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="conflictFormRef" :model="conflictForm" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="选择场地" prop="venueId">
|
||||
<el-select v-model="conflictForm.venueId" placeholder="请选择场地" style="width: 100%">
|
||||
<el-option v-for="venue in availableVenues" :key="venue.id" :label="`${venue.venueName}(${venue.venueLocation})`" :value="venue.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预约日期" prop="reservationDate">
|
||||
<el-date-picker
|
||||
v-model="conflictForm.reservationDate"
|
||||
type="date"
|
||||
placeholder="请选择预约日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-time-picker v-model="conflictForm.startTime" placeholder="请选择开始时间" format="HH:mm" value-format="HH:mm" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-time-picker v-model="conflictForm.endTime" placeholder="请选择结束时间" format="HH:mm" value-format="HH:mm" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-button type="primary" @click="handleConflictCheck">检查冲突</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 冲突检查结果 -->
|
||||
<div v-if="conflictCheckResult.length > 0" class="mt-4">
|
||||
<el-alert title="发现时间冲突" type="error" :closable="false" show-icon class="mb-4" />
|
||||
<el-table :data="conflictCheckResult" border>
|
||||
<el-table-column label="申请人" prop="applicantName" />
|
||||
<el-table-column label="预约目的" prop="reservationPurpose" />
|
||||
<el-table-column label="时间段">
|
||||
<template #default="scope"> {{ formatTime(scope.row.startTime) }} - {{ formatTime(scope.row.endTime) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="reservationStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="hq_reservation_status" :value="scope.row.reservationStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else-if="conflictChecked" class="mt-4">
|
||||
<el-alert title="无时间冲突" type="success" :closable="false" show-icon />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="VenueReservation" lang="ts">
|
||||
import {
|
||||
listVenueReservation,
|
||||
getVenueReservation,
|
||||
delVenueReservation,
|
||||
addVenueReservation,
|
||||
updateVenueReservation,
|
||||
listMyReservations,
|
||||
listPendingReservations,
|
||||
checkTimeConflict,
|
||||
approveReservation
|
||||
} from '@/api/hq/venue/reservation';
|
||||
import { getAvailableVenues } from '@/api/hq/venue/info';
|
||||
import { VenueReservationVO, VenueReservationQuery, VenueReservationForm, ReservationApproveForm, ConflictCheckQuery } from '@/api/hq/venue/types';
|
||||
import { checkPermi } from '@/utils/permission';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { hq_reservation_status } = toRefs<any>(proxy?.useDict('hq_reservation_status'));
|
||||
|
||||
const reservationList = ref<VenueReservationVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const availableVenues = ref<any[]>([]);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<VenueReservationQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
venueId: undefined,
|
||||
venueName: undefined,
|
||||
applicantName: undefined,
|
||||
reservationDate: undefined,
|
||||
reservationStatus: undefined
|
||||
});
|
||||
|
||||
// 表单参数
|
||||
const form = ref<VenueReservationForm>({});
|
||||
const rules = reactive<ElFormRules>({
|
||||
venueId: [{ required: true, message: '请选择场地', trigger: 'change' }],
|
||||
applicantPhone: [
|
||||
{ required: true, message: '申请人电话不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
reservationDate: [{ required: true, message: '请选择预约日期', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
reservationPurpose: [{ required: true, message: '预约目的不能为空', trigger: 'blur' }],
|
||||
participantCount: [{ required: true, message: '参与人数不能为空', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
// 审批表单
|
||||
const approveForm = ref<ReservationApproveForm>({});
|
||||
const approveRules = reactive<ElFormRules>({
|
||||
reservationStatus: [{ required: true, message: '请选择审批结果', trigger: 'change' }],
|
||||
approveRemark: [{ required: true, message: '审批备注不能为空', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
// 冲突检查表单
|
||||
const conflictForm = ref<ConflictCheckQuery>({});
|
||||
const conflictCheckResult = ref<any[]>([]);
|
||||
const conflictChecked = ref(false);
|
||||
|
||||
// 对话框
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const approveDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
|
||||
const detailDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
|
||||
const conflictDialog = reactive({
|
||||
visible: false
|
||||
});
|
||||
|
||||
const detailData = ref<VenueReservationVO>({});
|
||||
const currentReservation = ref<VenueReservationVO>({});
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const reservationFormRef = ref<ElFormInstance>();
|
||||
const approveFormRef = ref<ElFormInstance>();
|
||||
const conflictFormRef = ref<ElFormInstance>();
|
||||
|
||||
/** 查询预约列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listVenueReservation(queryParams.value);
|
||||
reservationList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: VenueReservationVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
getAvailableVenueList();
|
||||
dialog.visible = true;
|
||||
dialog.title = '新增预约';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: VenueReservationVO) => {
|
||||
reset();
|
||||
await getAvailableVenueList();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getVenueReservation(_id);
|
||||
const data = res.data;
|
||||
|
||||
// 处理时间格式
|
||||
if (data.startTime) {
|
||||
const startTime = new Date(data.startTime);
|
||||
form.value.startTime = `${startTime.getHours().toString().padStart(2, '0')}:${startTime.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
if (data.endTime) {
|
||||
const endTime = new Date(data.endTime);
|
||||
form.value.endTime = `${endTime.getHours().toString().padStart(2, '0')}:${endTime.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
Object.assign(form.value, {
|
||||
...data,
|
||||
startTime: form.value.startTime,
|
||||
endTime: form.value.endTime
|
||||
});
|
||||
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改预约';
|
||||
};
|
||||
|
||||
/** 详情按钮操作 */
|
||||
const handleDetail = async (row: VenueReservationVO) => {
|
||||
const res = await getVenueReservation(row.id!);
|
||||
detailData.value = res.data;
|
||||
detailDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 审批按钮操作 */
|
||||
const handleApprove = (row: VenueReservationVO) => {
|
||||
currentReservation.value = row;
|
||||
approveForm.value = {
|
||||
id: row.id,
|
||||
reservationStatus: undefined,
|
||||
approveRemark: undefined
|
||||
};
|
||||
approveDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 检查冲突并提交 */
|
||||
const checkConflictAndSubmit = async () => {
|
||||
reservationFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
// 验证时间
|
||||
if (form.value.startTime && form.value.endTime && form.value.startTime >= form.value.endTime) {
|
||||
proxy?.$modal.msgError('开始时间必须早于结束时间');
|
||||
return;
|
||||
}
|
||||
|
||||
// 组装完整的时间字符串
|
||||
const fullStartTime = `${form.value.reservationDate} ${form.value.startTime}:00`;
|
||||
const fullEndTime = `${form.value.reservationDate} ${form.value.endTime}:00`;
|
||||
|
||||
// 检查冲突
|
||||
try {
|
||||
const conflictRes = await checkTimeConflict({
|
||||
venueId: form.value.venueId,
|
||||
startTime: fullStartTime,
|
||||
endTime: fullEndTime,
|
||||
excludeId: form.value.id
|
||||
});
|
||||
|
||||
if (conflictRes.data && conflictRes.data.length > 0) {
|
||||
await proxy?.$modal.confirm('检测到时间冲突,是否继续提交?');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('冲突检查失败:', error);
|
||||
}
|
||||
|
||||
const submitData = {
|
||||
...form.value,
|
||||
startTime: fullStartTime,
|
||||
endTime: fullEndTime
|
||||
};
|
||||
|
||||
if (form.value.id) {
|
||||
await updateVenueReservation(submitData);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addVenueReservation(submitData);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 提交审批 */
|
||||
const submitApprove = () => {
|
||||
approveFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
await approveReservation(approveForm.value);
|
||||
proxy?.$modal.msgSuccess('审批成功');
|
||||
approveDialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: VenueReservationVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的预约数据?');
|
||||
await delVenueReservation(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'venue/reservation/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`venue_reservation_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 显示我的预约 */
|
||||
const showMyReservations = async () => {
|
||||
loading.value = true;
|
||||
const res = await listMyReservations(queryParams.value);
|
||||
reservationList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 显示待审批预约 */
|
||||
const showPendingReservations = async () => {
|
||||
loading.value = true;
|
||||
queryParams.value.reservationStatus = '0';
|
||||
const res = await listPendingReservations(queryParams.value);
|
||||
reservationList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
/** 显示冲突检查对话框 */
|
||||
const showConflictCheck = () => {
|
||||
getAvailableVenueList();
|
||||
conflictForm.value = {};
|
||||
conflictCheckResult.value = [];
|
||||
conflictChecked.value = false;
|
||||
conflictDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 处理冲突检查 */
|
||||
const handleConflictCheck = async () => {
|
||||
if (!conflictForm.value.venueId || !conflictForm.value.reservationDate || !conflictForm.value.startTime || !conflictForm.value.endTime) {
|
||||
proxy?.$modal.msgError('请填写完整的检查信息');
|
||||
return;
|
||||
}
|
||||
|
||||
const fullStartTime = `${conflictForm.value.reservationDate} ${conflictForm.value.startTime}:00`;
|
||||
const fullEndTime = `${conflictForm.value.reservationDate} ${conflictForm.value.endTime}:00`;
|
||||
|
||||
try {
|
||||
const res = await checkTimeConflict({
|
||||
venueId: conflictForm.value.venueId,
|
||||
startTime: fullStartTime,
|
||||
endTime: fullEndTime
|
||||
});
|
||||
|
||||
conflictCheckResult.value = res.data || [];
|
||||
conflictChecked.value = true;
|
||||
} catch (error) {
|
||||
proxy?.$modal.msgError('冲突检查失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
venueId: undefined,
|
||||
applicantPhone: undefined,
|
||||
reservationDate: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
reservationPurpose: undefined,
|
||||
participantCount: undefined,
|
||||
specialRequirements: undefined
|
||||
};
|
||||
reservationFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 获取可用场地列表 */
|
||||
const getAvailableVenueList = async () => {
|
||||
const res = await getAvailableVenues();
|
||||
availableVenues.value = res.data;
|
||||
};
|
||||
|
||||
/** 场地变更处理 */
|
||||
const handleVenueChange = () => {
|
||||
// 场地变更时清空相关信息
|
||||
};
|
||||
|
||||
/** 禁用过去日期 */
|
||||
const disabledDate = (time: Date) => {
|
||||
return time.getTime() < Date.now() - 24 * 60 * 60 * 1000;
|
||||
};
|
||||
|
||||
/** 时间格式化 */
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return '';
|
||||
const time = new Date(timeStr);
|
||||
return `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
/** 权限判断 */
|
||||
const canEdit = (row: VenueReservationVO) => {
|
||||
return row.reservationStatus === '0' && checkPermi(['hq:reservation:edit']);
|
||||
};
|
||||
|
||||
const canDelete = (row: VenueReservationVO) => {
|
||||
return row.reservationStatus === '0' && checkPermi(['hq:reservation:remove']);
|
||||
};
|
||||
|
||||
const canApprove = (row: VenueReservationVO) => {
|
||||
return row.reservationStatus === '0' && checkPermi(['hq:reservation:approve']);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.quick-action-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.quick-action-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.quick-action span {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quick-action-card:hover .quick-icon {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.quick-action-card:hover span {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,548 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header mb-4">
|
||||
<h2 class="text-xl font-bold text-gray-800">场地使用统计</h2>
|
||||
<p class="text-gray-600 mt-2">查看场地预约和使用情况的统计数据</p>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<el-row :gutter="20" class="mb-6">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-blue-100">
|
||||
<el-icon class="text-blue-600" size="24"><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ statistics.totalReservations }}</div>
|
||||
<div class="stat-label">总预约数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-green-100">
|
||||
<el-icon class="text-green-600" size="24"><Check /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ statistics.approvedReservations }}</div>
|
||||
<div class="stat-label">已批准预约</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-yellow-100">
|
||||
<el-icon class="text-yellow-600" size="24"><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ statistics.pendingReservations }}</div>
|
||||
<div class="stat-label">待审批预约</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-red-100">
|
||||
<el-icon class="text-red-600" size="24"><CloseBold /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ statistics.rejectedReservations }}</div>
|
||||
<div class="stat-label">已拒绝预约</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
<el-form :model="queryParams" label-width="80px" inline>
|
||||
<el-form-item label="统计类型">
|
||||
<el-select v-model="queryParams.statisticType" placeholder="请选择统计类型" @change="handleStatisticTypeChange">
|
||||
<el-option label="预约量统计" value="reservation" />
|
||||
<el-option label="场地使用率" value="usage" />
|
||||
<el-option label="时间段分析" value="timeSlot" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间范围">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="场地类型">
|
||||
<el-select v-model="queryParams.venueType" placeholder="请选择场地类型" clearable>
|
||||
<el-option v-for="dict in hq_venue_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadStatisticsData">
|
||||
<template #icon><Search /></template>
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="exportReport">
|
||||
<template #icon><Download /></template>
|
||||
导出报表
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 图表展示区域 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>预约量趋势图</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="reservationTrendChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>场地类型分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="venueTypeChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="mt-4">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" class="chart-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>热门场地排行榜</span>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="popularVenuesChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 详细数据表格 -->
|
||||
<el-card shadow="hover" class="mt-4">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>详细统计数据</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-table v-loading="loading" :data="detailData" style="width: 100%">
|
||||
<el-table-column prop="venueName" label="场地名称" width="150" />
|
||||
<el-table-column prop="venueType" label="场地类型" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="hq_venue_type" :value="scope.row.venueType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalReservations" label="预约总数" width="100" />
|
||||
<el-table-column prop="approvedCount" label="已批准" width="100" />
|
||||
<el-table-column prop="pendingCount" label="待审批" width="100" />
|
||||
<el-table-column prop="rejectedCount" label="已拒绝" width="100" />
|
||||
<el-table-column prop="usageRate" label="使用率" width="100">
|
||||
<template #default="scope"> {{ scope.row.usageRate }}% </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="avgDuration" label="平均时长(小时)" />
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
:total="total"
|
||||
@pagination="loadDetailData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="VenueStatistics">
|
||||
import { getCurrentInstance, ref, reactive, onMounted, nextTick, onUnmounted, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Calendar, Check, Clock, CloseBold, Search, Download } from '@element-plus/icons-vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
// 如果有对应的API,可以引入
|
||||
// import { getVenueStatistics, getDetailStatistics } from '@/api/hq/venue/statistics';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const { hq_venue_type } = toRefs<any>(proxy?.useDict('hq_venue_type'));
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
// 统计概览数据
|
||||
const statistics = ref({
|
||||
totalReservations: 156,
|
||||
approvedReservations: 128,
|
||||
pendingReservations: 18,
|
||||
rejectedReservations: 10
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
statisticType: 'reservation',
|
||||
dateRange: [] as string[],
|
||||
venueType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
// 详细数据
|
||||
const detailData = ref([
|
||||
{
|
||||
venueName: '第一会议室',
|
||||
venueType: 'meeting_room',
|
||||
totalReservations: 45,
|
||||
approvedCount: 38,
|
||||
pendingCount: 4,
|
||||
rejectedCount: 3,
|
||||
usageRate: 85.2,
|
||||
avgDuration: 2.5
|
||||
},
|
||||
{
|
||||
venueName: '多媒体教室A',
|
||||
venueType: 'classroom',
|
||||
totalReservations: 32,
|
||||
approvedCount: 28,
|
||||
pendingCount: 2,
|
||||
rejectedCount: 2,
|
||||
usageRate: 76.8,
|
||||
avgDuration: 3.2
|
||||
},
|
||||
{
|
||||
venueName: '体育馆',
|
||||
venueType: 'sports_field',
|
||||
totalReservations: 28,
|
||||
approvedCount: 25,
|
||||
pendingCount: 2,
|
||||
rejectedCount: 1,
|
||||
usageRate: 68.5,
|
||||
avgDuration: 4.0
|
||||
}
|
||||
]);
|
||||
|
||||
// 图表引用
|
||||
const reservationTrendChart = ref();
|
||||
const venueTypeChart = ref();
|
||||
const popularVenuesChart = ref();
|
||||
|
||||
// 图表实例
|
||||
let trendChartInstance: echarts.ECharts;
|
||||
let typeChartInstance: echarts.ECharts;
|
||||
let popularChartInstance: echarts.ECharts;
|
||||
|
||||
// 初始化预约趋势图
|
||||
const initReservationTrendChart = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '预约量趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '预约量',
|
||||
type: 'line',
|
||||
data: [25, 30, 28, 35, 40, 38],
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: '#409EFF'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
trendChartInstance = echarts.init(reservationTrendChart.value);
|
||||
trendChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 初始化场地类型分布图
|
||||
const initVenueTypeChart = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '场地类型分布',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '场地类型',
|
||||
type: 'pie',
|
||||
radius: '60%',
|
||||
data: [
|
||||
{ value: 45, name: '会议室' },
|
||||
{ value: 32, name: '教室' },
|
||||
{ value: 28, name: '运动场' },
|
||||
{ value: 15, name: '礼堂' },
|
||||
{ value: 8, name: '实验室' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
typeChartInstance = echarts.init(venueTypeChart.value);
|
||||
typeChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 初始化热门场地排行图
|
||||
const initPopularVenuesChart = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '热门场地排行榜',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['第一会议室', '多媒体教室A', '体育馆', '大礼堂', '实验室B']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '预约次数',
|
||||
type: 'bar',
|
||||
data: [45, 32, 28, 22, 18],
|
||||
itemStyle: {
|
||||
color: '#67C23A'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
popularChartInstance = echarts.init(popularVenuesChart.value);
|
||||
popularChartInstance.setOption(option);
|
||||
};
|
||||
|
||||
// 处理统计类型变化
|
||||
const handleStatisticTypeChange = () => {
|
||||
loadStatisticsData();
|
||||
};
|
||||
|
||||
// 处理时间范围变化
|
||||
const handleDateRangeChange = () => {
|
||||
loadStatisticsData();
|
||||
};
|
||||
|
||||
// 加载统计数据
|
||||
const loadStatisticsData = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 这里应该调用实际的API
|
||||
// const response = await getVenueStatistics(queryParams.value);
|
||||
// statistics.value = response.data;
|
||||
|
||||
// 更新图表数据
|
||||
updateCharts();
|
||||
|
||||
ElMessage.success('统计数据加载成功');
|
||||
} catch (error) {
|
||||
ElMessage.error('加载统计数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载详细数据
|
||||
const loadDetailData = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// 这里应该调用实际的API
|
||||
// const response = await getDetailStatistics(queryParams.value);
|
||||
// detailData.value = response.rows;
|
||||
// total.value = response.total;
|
||||
|
||||
total.value = detailData.value.length;
|
||||
} catch (error) {
|
||||
ElMessage.error('加载详细数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 更新图表
|
||||
const updateCharts = () => {
|
||||
// 更新图表数据,这里可以根据实际API返回的数据来更新
|
||||
// 当前使用模拟数据
|
||||
};
|
||||
|
||||
// 导出报表
|
||||
const exportReport = () => {
|
||||
ElMessage.info('报表导出功能开发中...');
|
||||
};
|
||||
|
||||
// 窗口大小变化时重新调整图表
|
||||
const handleResize = () => {
|
||||
if (trendChartInstance) trendChartInstance.resize();
|
||||
if (typeChartInstance) typeChartInstance.resize();
|
||||
if (popularChartInstance) popularChartInstance.resize();
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 设置默认时间范围为近30天
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setDate(start.getDate() - 30);
|
||||
|
||||
queryParams.value.dateRange = [start.toISOString().split('T')[0], end.toISOString().split('T')[0]];
|
||||
|
||||
nextTick(() => {
|
||||
initReservationTrendChart();
|
||||
initVenueTypeChart();
|
||||
initPopularVenuesChart();
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
loadStatisticsData();
|
||||
loadDetailData();
|
||||
});
|
||||
|
||||
// 组件卸载时清理事件监听
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (trendChartInstance) trendChartInstance.dispose();
|
||||
if (typeChartInstance) typeChartInstance.dispose();
|
||||
if (popularChartInstance) popularChartInstance.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user