Compare commits

...

21 Commits

Author SHA1 Message Date
liangbin 2dc36e8dd4 fix: 移除项目列表请求中的状态参数
删除项目列表请求中的status参数,以获取所有状态的项目数据
2026-02-04 08:31:20 +08:00
liangbin d6fd635dc8 refactor(WorkOrderEdit): 统一作业负责人字段命名并修复相关逻辑
将workResponsible字段统一重命名为supervisorName,并添加supervisorId字段
修复保存草稿和下一步逻辑中作业负责人相关字段的赋值逻辑
2026-02-03 10:40:16 +08:00
liangbin 8c73637b79 refactor(WorkOrderEdit): 重命名成员选择器变量并优化负责人选择逻辑
将 showMemberPicker1 重命名为 showMemberPickerA 以提高可读性
优化 HandleMemberConfirmA 方法,直接从数组取值并关闭选择器
将 UpdatePeriod 方法提前定义以提升代码组织性
2026-02-03 10:22:05 +08:00
liangbin d18f0f4c5e feat(WorkOrderEdit): 添加作业负责人选择器并更新相关逻辑
- 将作业负责人输入框改为只读并添加选择器功能
- 新增作业负责人ID字段存储
- 更新表单数据恢复和保存逻辑以适配新字段
- 修改验证逻辑检查负责人ID而非名称
2026-02-03 10:12:48 +08:00
liangbin e19ba8e145 fix(用户页面): 修复退出登录功能并移除项目存档入口
添加退出登录功能实现,包括确认弹窗和清除token操作
移除不再使用的项目存档入口代码
2026-02-03 10:00:56 +08:00
liangbin dcb0ab1377 feat(用户中心): 实现个人中心页面布局及功能
添加用户信息展示、消息中心、项目存档和退出登录功能
实现跳转到项目存档页面的导航逻辑
添加样式布局和响应式设计
2026-02-03 09:53:40 +08:00
lixiaobang 7959a0bd7d 代码提交 2026-02-02 10:14:00 +08:00
lixiaobang 476b9f79cb 代码提交 2026-01-29 10:37:28 +08:00
liangbin 005ce674db feat(项目详情): 添加今日检查项和问题整改反馈功能
- 新增TodayExamine和ReformFeedback页面
- 在ControlView组件中添加跳转逻辑
- 完善项目详情页的tab切换功能
- 添加BasicView和RecordView组件展示项目基本信息和整改记录
- 更新pages.json配置路由
2026-01-20 11:29:45 +08:00
liangbin 8821c01725 feat(项目详情): 添加项目详情页面及组件
实现项目详情页面的基础结构,包括:
- 新增BasicView、RecordView、ControlView三个组件
- 配置项目详情路由
- 完善项目列表到详情的跳转功能
- 实现风险管控实施页面的主要交互和样式
2026-01-19 17:58:04 +08:00
liangbin ab8e574c5e refactor(WorkOrderEdit): 重构导航逻辑并修复步骤切换问题
将返回上一页和返回主页的逻辑分离为独立的函数
修复步骤切换时的条件判断错误
移除未使用的参数并统一函数命名
2026-01-19 15:03:01 +08:00
liangbin 478c5cfc1a feat(工单编辑): 添加移动球机申领功能
- 在WorkNote组件中添加申领按钮并绑定openApplyForMachine事件
- 新增ApplyForMachine组件用于球机申领表单
- 修改index.vue实现球机申领流程跳转
- 调整步骤导航逻辑支持子步骤
2026-01-19 14:32:27 +08:00
liangbin 9efe368f79 feat(WorkOrderEdit): 添加风险控制卡组件并调整表单样式
- 新增RiskControl.vue组件用于风险控制卡填写
- 修改BasicsInfo.vue中人员选择器的columns参数格式
- 移除WorkNote.vue和GatePassInfo.vue中表单背景色
- 更新index.vue添加风险控制步骤和组件引用
2026-01-19 13:54:47 +08:00
liangbin 485f35157b feat(WorkOrderEdit): 新增工作票组件并调整样式
添加WorkNote组件用于工作票信息填写,包含风险类型选择、安全措施配置等功能
调整GatePassInfo组件样式,优化文件上传列表的显示
2026-01-19 10:58:07 +08:00
liangbin 6a66ff4422 feat(WorkOrderEdit): 添加出入证申请表单数据收集功能
在下一步按钮点击事件中增加对第二步表单数据的收集处理,通过gatePassInfoRef获取出入证申请表单数据并存储到allData中
2026-01-16 18:01:02 +08:00
liangbin c239c25fa4 feat(文件上传): 添加上传组件功能及样式优化
- 新增上传图标资源文件
- 在utils中添加生成唯一标识符方法
- 重构GatePassInfo组件文件上传功能,支持图片和PDF格式
- 优化BasicsInfo和GatePassInfo组件滚动行为
- 添加上传文件大小和类型校验
- 完善文件列表展示和删除功能
2026-01-16 16:59:47 +08:00
liangbin 7880cb1051 feat(WorkOrderEdit): 实现工单编辑多步骤表单功能
添加工单编辑的多步骤表单功能,包括基础信息和出入证申请两个步骤:
1. 在基础信息步骤中添加作业负责人和作业班成员选择功能
2. 创建新的出入证申请组件,处理车辆信息和有效日期选择
3. 实现步骤间数据共享和导航功能
2026-01-16 16:12:39 +08:00
liangbin 158a652db7 feat(WorkOrderEdit): 将作业周期选择改为日期范围选择器
重构日期选择组件,将单个日期输入改为开始和结束日期分开选择
添加日期范围验证逻辑,确保结束日期不小于开始日期
更新表单数据结构以支持日期范围存储
2026-01-16 14:06:19 +08:00
liangbin 7d1b906aae feat(WorkOrderEdit): 添加作业周期选择功能并暴露表单数据方法
- 在基础信息组件中添加日期范围选择器
- 暴露getFormData方法供父组件获取表单数据
- 优化样式和代码结构
2026-01-16 13:44:25 +08:00
liangbin b55f605d6d feat(工单管理): 新增工单编辑和详情页面及功能
添加工单编辑页面和工单详情页面,包括基础信息表单和地图功能
在工单审批页面增加新建工单按钮跳转功能
引入天地图API用于地图展示
更新页面路由配置
2026-01-16 11:30:52 +08:00
liangbin a67654d9f4 feat: 重构页面结构并添加工单审批和项目实施功能
- 删除旧版首页、注册页和演示页
- 新增工单审批和项目实施页面及功能
- 更新底部导航栏图标和路由配置
- 修改登录成功后的跳转逻辑
2026-01-15 19:00:36 +08:00
38 changed files with 8180 additions and 69 deletions

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>uniapp-vue3-template</title>
<link rel="icon" href="/favicon.ico">
<script type="text/javascript" src="https://api.tianditu.gov.cn/api?v=4.0&tk=627214536ffef28d296fab6a9578679e"></script>
</head>
<body>
<div id="app"></div>

View File

@ -1,2 +1,4 @@
export * from './auth'
export * from './user'
export * from './modules/auth'
export * from './modules/user'
export * from './modules/workTicket'
export * from './modules/projectImplementation'

View File

@ -0,0 +1,33 @@
import { get } from '../request'
/**
* 查询项目实施列表
* @param {Object} params - 查询参数
* @param {Number} params.pageNum - 页码可选
* @param {Number} params.pageSize - 每页数量可选
* @param {String} params.status - 状态可选
* @param {String} params.projectName - 项目名称可选模糊查询
* @param {Number} params.projectId - 项目ID可选
* @returns {Promise} 返回项目实施列表 { total: number, rows: array }
*/
export function getProjectImplementationList(params = {}) {
return get('/manage/contractor/projectImplementation/list', params)
}
/**
* 查询项目详情
* @param {Number} projectId - 项目ID
* @returns {Promise} 返回项目详情数据
*/
export function getProjectImplementationDetail(projectId) {
return get(`/manage/contractor/projectImplementation/${projectId}`)
}
/**
* 查询当天合格数量和检查项总数
* @param {Number} projectId - 项目ID
* @returns {Promise} 返回当天检查项汇总数据 { qualifiedCount: number, totalCount: number, submissionDate: string, submitted: boolean }
*/
export function getTodayInspectionSummary(projectId) {
return get('/manage/contractor/dailyInspection/todaySummary', { projectId })
}

View File

@ -17,9 +17,13 @@ export function bindPhone(phone, smsCode) {
}
export function getUserList(params) {
return get('/user/list', params)
return get('/system/user/list', params)
}
export function getUserDetail(id) {
return get(`/user/detail/${id}`)
}
export function getUserInfo() {
return get('/system/user/getInfo')
}

View File

@ -0,0 +1,260 @@
import { post, get, del, put, uploadFile } from '../request'
/**
* 新增工作票申请
* @param {Object} data - 工作票数据
* @param {Number} data.projectId - 所属项目ID必填
* @param {String} data.workLocation - 作业地点
* @param {Number} data.supervisorId - 作业负责人ID
* @param {String} data.supervisorName - 作业负责人姓名
* @param {String} data.supervisorPosition - 作业负责人职位
* @param {String} data.workContent - 作业内容
* @param {String} data.riskType - 风险类型高风险中风险低风险
* @param {String} data.workStartTime - 作业开始时间格式yyyy-MM-dd HH:mm:ss
* @param {String} data.workEndTime - 作业结束时间格式yyyy-MM-dd HH:mm:ss
* @param {Array<String>} data.safetyMeasuresList - 安全措施列表
* @param {Array<Object>} data.qualificationList - 作业班成员资质列表
* @param {String} data.needMonitoringCamera - 是否需申领移动监控球机"0""1"
* @param {Number} data.cameraApplicationId - 监控球机申请ID如果需要监控球机时填写
* @param {String} data.remark - 备注
* @returns {Promise} 返回工作票ID
*/
export function createWorkTicket(data) {
return post('/manage/contractor/workTicket', data)
}
/**
* 出入证申请
* @param {Object} data - 出入证申请数据
* @returns {Promise}
*/
export function applyAccessPermit(data) {
return post('/manage/contractor/accessPermit', data)
}
/**
* 新增工作计划
* @param {Object} data - 工作计划数据
* @returns {Promise}
*/
export function createWorkPlan(data) {
return post('/manage/contractor/workPlan', data)
}
/**
* 球机申领新增接口
* @param {Object} data - 球机申领数据
* @param {Number} data.projectId - 项目ID必填
* @param {Number} data.needCameraCnt - 申领数量必填必须大于0
* @param {String} data.installationLocation - 安装位置必填
* @param {String} data.contactPerson - 联系人必填
* @param {String} data.contactPhone - 联系方式必填
* @param {String} data.remark - 备注可选
* @returns {Promise} 返回申领单ID
*/
export function createCameraApplication(data) {
return post('/manage/project/cameraApplication', data)
}
/**
* 查询工作计划列表
* @param {Object} params - 查询参数
* @param {Number} params.pageNum - 页码可选
* @param {Number} params.pageSize - 每页数量可选
* @param {String} params.status - 状态可选
* @param {Number} params.projectId - 项目ID可选
* @returns {Promise} 返回工作计划列表 { total: number, rows: array }
*/
export function getWorkPlanList(params) {
return get('/manage/contractor/workPlan/list', params)
}
/**
* 删除工作计划
* @param {Number|String|Array<Number>|Array<String>} projectIds - 项目ID可以是单个IDID数组或逗号分隔的字符串
* @returns {Promise}
*/
export function deleteWorkPlan(projectIds) {
// 处理不同的输入格式
let ids = projectIds
if (Array.isArray(projectIds)) {
ids = projectIds.join(',')
}
return del(`/manage/contractor/workPlan/${ids}`)
}
/**
* 提交工作计划
* @param {Number|String} projectId - 项目ID必填
* @returns {Promise}
*/
export function submitWorkPlan(projectId) {
return put(`/manage/contractor/workPlan/submit/${projectId}`)
}
/**
* 撤回工作计划
* @param {Number|String} projectId - 项目ID必填
* @returns {Promise}
*/
export function withdrawWorkPlan(projectId) {
return put(`/manage/contractor/workPlan/withdraw/${projectId}`)
}
/**
* 上传文件
* @param {String} filePath - 文件路径本地临时文件路径
* @param {String} name - 文件对应的 key默认为 'file'
* @param {Object} formData - 额外的表单数据
* @param {Object} options - 其他配置选项
* @returns {Promise<{name: string, url: string}>} 返回上传后的文件信息包含 name文件名 url文件访问地址
*/
export function uploadFileApi(filePath, name = 'file', formData = {}, options = {}) {
return uploadFile('/file/upload', filePath, name, formData, options)
}
/**
* 查询风险管控卡模板列表
* @param {Object} params - 查询参数可选
* @returns {Promise} 返回风险管控卡模板列表
*/
export function getRiskCardTemplateList(params = {}) {
return get('/manage/logistics/riskCard/template/list', params)
}
/**
* 获取风险管控卡模板详细信息
* @param {Number|String} templateId - 模板ID必填
* @returns {Promise} 返回风险管控卡模板详细信息
*/
export function getRiskCardTemplateDetail(templateId) {
return get(`/manage/logistics/riskCard/template/${templateId}`)
}
/**
* 获取工作计划详细信息
* @param {Number|String} projectId - 项目ID必填
* @returns {Promise} 返回工作计划详细信息
*/
export function getWorkPlanDetail(projectId) {
return get(`/manage/contractor/workPlan/${projectId}`)
}
/**
* 获取工作票详细信息
* @param {Number|String} ticketId - 工作票ID必填
* @returns {Promise} 返回工作票详细信息
*/
export function getWorkTicketDetail(ticketId) {
return get(`/manage/contractor/workTicket/${ticketId}`)
}
/**
* 提交风险管控卡监理审核
* @param {Object} data - 风险管控卡数据
* @param {Number} data.ticketId - 工作票ID必填
* @param {Number} data.templateId - 模板ID必填
* @param {String} data.ticketNumber - 票证编号必填
* @param {String} data.operatingUnit - 所属单位必填
* @param {String} data.workContent - 工作内容必填
* @param {String} data.supervisorName - 工作负责人姓名必填
* @param {String} data.supervisorPosition - 工作负责人职位
* @param {String} data.contactMethod - 联系方式
* @param {String} data.inspectionTime - 检查时间格式yyyy-MM-ddTHH:mm
* @param {Array<Object>} data.checkItems - 检查项列表
* @param {Number} data.checkItems[].templateItemId - 模板项ID
* @param {String} data.checkItems[].itemDescription - 检查项描述
* @param {String} data.checkItems[].checkResult - 检查结果
* @param {Number} data.checkItems[].sortOrder - 排序顺序
* @param {Array<String>} data.attachmentList - 附件URL列表
* @returns {Promise} 返回风险管控卡IDcardId
*/
export function submitRiskControlCard(data) {
return post('/manage/contractor/riskControlCard', data)
}
/**
* 提交工作票
* @param {Number|String} ticketId - 工作票ID必填
* @param {Number|String} cardId - 风险管控卡ID可选高等风险时必填
* @returns {Promise}
*/
export function submitWorkTicket(ticketId, cardId = null) {
let url = `/manage/contractor/workTicket/submit/${ticketId}`;
if (cardId) {
url += `?cardId=${cardId}`;
}
return put(url)
}
/**
* 提交工作票承包商接口
* @param {Number|String} ticketId - 工作票ID必填
* @returns {Promise}
*/
export function submitWorkTicketForContractor(ticketId) {
return put(`/manage/contractor/workTicket/submit/${ticketId}`)
}
/**
* 提交实施
* @param {Number|String} projectId - 项目ID必填
* @returns {Promise}
*/
export function submitImplementation(projectId) {
return put(`/manage/contractor/projectImplementation/submitImplementation?projectId=${projectId}`)
}
/**
* 修改工作计划
* @param {Object} data - 工作计划数据
* @param {Number|String} data.id - 工作计划ID必填
* @returns {Promise}
*/
export function updateWorkPlan(data) {
return put('/manage/contractor/workPlan', data)
}
/**
* 获取出入证详情根据项目ID
* @param {Number|String} projectId - 项目ID必填
* @returns {Promise} 返回出入证详情
*/
export function getAccessPermitDetail(projectId) {
return get(`/manage/contractor/accessPermit/project/${projectId}`)
}
/**
* 修改出入证申请
* @param {Object} data - 出入证申请数据
* @param {Number|String} data.id - 出入证申请ID必填
* @returns {Promise}
*/
export function updateAccessPermit(data) {
return put('/manage/contractor/accessPermit', data)
}
/**
* 修改工作票申请
* @param {Object} data - 工作票数据
* @param {Number|String} data.id - 工作票ID必填
* @param {Number} data.projectId - 所属项目ID必填
* @param {String} data.workLocation - 作业地点
* @param {Number} data.supervisorId - 作业负责人ID
* @param {String} data.supervisorName - 作业负责人姓名
* @param {String} data.supervisorPosition - 作业负责人职位
* @param {String} data.workContent - 作业内容
* @param {String} data.riskType - 风险类型高风险中风险低风险
* @param {String} data.workStartTime - 作业开始时间格式yyyy-MM-dd HH:mm:ss
* @param {String} data.workEndTime - 作业结束时间格式yyyy-MM-dd HH:mm:ss
* @param {Array<String>} data.safetyMeasuresList - 安全措施列表
* @param {Array<Object>} data.qualificationList - 作业班成员资质列表
* @param {String} data.needMonitoringCamera - 是否需申领移动监控球机"0""1"
* @param {Number} data.cameraApplicationId - 监控球机申请ID如果需要监控球机时填写
* @param {String} data.remark - 备注
* @returns {Promise}
*/
export function updateWorkTicket(data) {
return put('/manage/contractor/workTicket', data)
}

View File

@ -1,21 +1,28 @@
import { useUserStore } from '@/store/user'
const BASE_URL = 'https://api.example.com'
// 根据平台配置不同的基础URL
// H5 开发环境使用代理路径 /api通过 vite 代理解决跨域问题
// H5 生产环境和小程序、App 使用完整URL
let BASE_URL = '/api'
// #ifdef H5
// #endif
const requestInterceptor = (config) => {
const userStore = useUserStore()
if (config.header) {
config.header = {
...config.header,
'Content-Type': 'application/json',
'Authorization': userStore.token ? `Bearer ${userStore.token}` : ''
}
} else {
config.header = {
'Content-Type': 'application/json',
'Authorization': userStore.token ? `Bearer ${userStore.token}` : ''
}
// 初始化 header 对象
if (!config.header) {
config.header = {}
}
// 设置 Content-Type
config.header['Content-Type'] = config.header['Content-Type'] || 'application/json'
// 如果有 token添加 authorization header
if (userStore.token) {
config.header['authorization'] = `Bearer ${userStore.token}`
}
return config
@ -25,11 +32,11 @@ const responseInterceptor = (response) => {
const { statusCode, data } = response
if (statusCode === 200) {
if (data.code === 0 || data.success) {
return data.data || data.result
if (data.code === 0 || data.code === 200 || data.success) {
return data.data || data.result || data
} else {
uni.showToast({
title: data.message || '请求失败',
title: data.msg || data.message || '请求失败',
icon: 'none'
})
return Promise.reject(data)
@ -81,10 +88,13 @@ function request(options) {
url: url.startsWith('http') ? url : `${BASE_URL}${url}`,
method,
data,
header: requestInterceptor({ header }),
header,
timeout: 15000
}
// 通过拦截器处理 header
requestInterceptor(config)
return new Promise((resolve, reject) => {
if (loading) {
uni.showLoading({
@ -96,8 +106,17 @@ function request(options) {
uni.request({
...config,
success: (res) => {
const result = responseInterceptor(res)
resolve(result)
try {
const result = responseInterceptor(res)
// 如果 result 是 Promise需要正确处理
if (result instanceof Promise) {
result.then(resolve).catch(reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
},
fail: (err) => {
uni.showToast({
@ -160,4 +179,112 @@ export function patch(url, data, options) {
})
}
/**
* 文件上传
* @param {String} url - 上传接口地址
* @param {String} filePath - 文件路径本地临时文件路径
* @param {String} name - 文件对应的 key开发者在服务端可以通过这个 key 获取文件的二进制内容
* @param {Object} formData - HTTP 请求中其他额外的 form data
* @param {Object} options - 其他配置选项
* @param {Boolean} options.loading - 是否显示加载提示默认 true
* @param {String} options.loadingText - 加载提示文字默认 '上传中...'
* @returns {Promise}
*/
export function uploadFile(url, filePath, name = 'file', formData = {}, options = {}) {
const { loading = true, loadingText = '上传中...' } = options
const userStore = useUserStore()
// 构建完整的 URL
const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url}`
// 构建请求头
const header = {}
if (userStore.token) {
header['authorization'] = `Bearer ${userStore.token}`
}
return new Promise((resolve, reject) => {
if (loading) {
uni.showLoading({
title: loadingText,
mask: true
})
}
uni.uploadFile({
url: fullUrl,
filePath: filePath,
name: name,
formData: formData,
header: header,
success: (res) => {
try {
// uni.uploadFile 返回的是字符串,需要解析
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
if (res.statusCode === 200) {
if (data.code === 0 || data.code === 200 || data.success) {
resolve(data.data || data.result || data)
} else {
uni.showToast({
title: data.msg || data.message || '上传失败',
icon: 'none'
})
reject(data)
}
} else if (res.statusCode === 401) {
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
})
userStore.clearUser()
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/index'
})
}, 1500)
reject(res)
} else if (res.statusCode === 403) {
uni.showToast({
title: '没有权限访问',
icon: 'none'
})
reject(res)
} else if (res.statusCode >= 500) {
uni.showToast({
title: '服务器错误,请稍后重试',
icon: 'none'
})
reject(res)
} else {
uni.showToast({
title: '上传失败',
icon: 'none'
})
reject(res)
}
} catch (error) {
uni.showToast({
title: '上传响应解析失败',
icon: 'none'
})
reject(error)
}
},
fail: (err) => {
uni.showToast({
title: '上传失败,请检查网络',
icon: 'none'
})
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading()
}
}
})
})
}
export default request

View File

@ -43,7 +43,13 @@
"minified": true,
"postcss": true
},
"usingComponents": true
"usingComponents": true,
"requiredPrivateInfos": [],
"permission": {
"scope.userLocation": {
"desc": "您的位置信息将用于小程序位置接口的效果展示"
}
}
},
"mp-alipay": {
"usingComponents": true

View File

@ -1,12 +1,5 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/login/index",
"style": {
@ -15,9 +8,51 @@
}
},
{
"path": "pages/register/index",
"path": "pages/WorkOrderApproval/index",
"style": {
"navigationBarTitleText": "注册",
"navigationBarTitleText": "工单审批",
"enablePullDownRefresh": true
}
},
{
"path": "pages/WorkOrderEdit/index",
"style": {
"navigationBarTitleText": "工单编辑",
"navigationStyle": "custom"
}
},
{
"path": "pages/WorkOrderDetails/index",
"style": {
"navigationBarTitleText": "工单详情",
"navigationStyle": "custom"
}
},
{
"path": "pages/ProjectList/index",
"style": {
"navigationBarTitleText": "项目实施",
"enablePullDownRefresh": true
}
},
{
"path": "pages/ProgectDetails/index",
"style": {
"navigationBarTitleText": "项目详情",
"navigationStyle": "custom"
}
},
{
"path": "pages/TodayExamine/index",
"style": {
"navigationBarTitleText": "今日检查项",
"navigationStyle": "custom"
}
},
{
"path": "pages/ReformFeedback/index",
"style": {
"navigationBarTitleText": "问题整改反馈",
"navigationStyle": "custom"
}
},
@ -27,13 +62,6 @@
"navigationBarTitleText": "个人中心",
"enablePullDownRefresh": false
}
},
{
"path": "pages/demo/index",
"style": {
"navigationBarTitleText": "组件演示",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
@ -56,22 +84,22 @@
"iconWidth": "24px",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
"pagePath": "pages/WorkOrderApproval/index",
"text": "工单审批",
"iconPath": "static/tabbar/gd-icon1.png",
"selectedIconPath": "static/tabbar/gd-icon2.png"
},
{
"pagePath": "pages/demo/index",
"text": "演示",
"iconPath": "static/tabbar/demo.png",
"selectedIconPath": "static/tabbar/demo-active.png"
"pagePath": "pages/ProjectList/index",
"text": "项目实施",
"iconPath": "static/tabbar/xm-icon1.png",
"selectedIconPath": "static/tabbar/xm-icon2.png"
},
{
"pagePath": "pages/user/index",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png"
"iconPath": "static/tabbar/gr-icon1.png",
"selectedIconPath": "static/tabbar/gr-icon2.png"
}
]
},

View File

@ -0,0 +1,167 @@
<!-- 项目详情-基本项目信息 -->
<template>
<view class="MainBox">
<view class="labebBox">申请单位</view>
<view class="fw-600">{{ applyUnit }}</view>
<view class="labebBox">施工单位</view>
<view class="fw-600">{{ constructUnit }}</view>
<view class="labebBox">施工地点</view>
<view class="fw-600">{{ site }}</view>
<view class="labebBox">附件资料</view>
<view class="ListBox">
<u-virtual-list v-if="dataSource.length > 0" :list-data="dataSource" :item-height="80" height="100%">
<template #default="{ item, index }">
<view class="CardBox" :key="item.id">
<view class="FlexBox">
<img class="CardImg" src="@/static/icon/file-icon.png">
<view class="CardTxt">{{ item.name }}</view>
</view>
<view>
<u-icon name="download" size="30"></u-icon>
</view>
</view>
</template>
</u-virtual-list>
<view v-else class="empty-tip">暂无附件资料</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
projectData: {
type: Object,
default: () => null
}
})
//
const applyUnit = computed(() => {
return props.projectData?.constructionUnitName || '未设置'
})
//
const constructUnit = computed(() => {
return props.projectData?.constructionUnitName || '未设置'
})
//
const site = computed(() => {
const location = props.projectData?.workLocation || ''
const detail = props.projectData?.workLocationDetail || ''
if (location && detail) {
return `${location} ${detail}`
}
return location || detail || '未设置'
})
// projectData
const dataSource = ref([
//
//
])
//
const getProjectStatusText = (status) => {
const statusMap = {
'0': '未开始',
'1': '进行中',
'2': '已完成',
'3': '已暂停',
'4': '已取消'
}
return statusMap[status] || '未知状态'
}
//
const getImplementationStatusText = (status) => {
const statusMap = {
'1': '实施中',
'2': '已完工',
'3': '整改中',
'4': '待完工确认'
}
return statusMap[status] || '未知状态'
}
//
const getApprovalStatusText = (status) => {
const statusMap = {
'0': '待审批',
'1': '已审批',
'2': '已拒绝'
}
return statusMap[status] || '未知状态'
}
</script>
<style lang="scss" scoped>
.labebBox {
font-size: 28rpx;
color: #666;
margin-top: 20rpx;
}
.mt-20 {
margin-top: 20rpx;
}
.fw-600 {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.FlexBox {
display: flex;
align-items: center;
gap: 20rpx;
}
.MainBox {
height: 100%;
overflow: hidden;
padding: 20rpx;
background-color: #fff;
.ListBox {
margin-top: 20rpx;
height: calc(100% - 384rpx);
padding: 20rpx;
.empty-tip {
text-align: center;
color: #999;
font-size: 28rpx;
padding: 40rpx 0;
}
.CardBox {
width: 100%;
height: 100rpx;
background-color: #fff;
padding: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
border: 1rpx solid #c7c7c7;
border-radius: 10rpx;
.CardImg {
width: 50rpx;
height: 50rpx;
}
.CardTxt {
width: 400rpx;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
</style>

View File

@ -0,0 +1,189 @@
<!-- 项目详情-风险管控实施 -->
<template>
<view class="MainBox">
<view class="BoxA">
<u-row>
<u-col :span="6">
<view>今日检查项完成度</view>
<view class="blcakFont">{{ completedCount }}/{{ totalCheckItems }}</view>
</u-col>
<u-col :span="6">
<view>待整改问题数</view>
<view class="yellowFont">10</view>
</u-col>
</u-row>
<view style="margin-top: 20rpx;">
<u-button type="primary" size="medium" @click="submitCheck">提交今日风险检查项</u-button>
</view>
</view>
<view class="BoxB">
<view class="h2Box">问题反馈列表</view>
<view class="ListBox">
<u-virtual-list :list-data="dataSource" :item-height="130" height="100%">
<template #default="{ item, index }">
<view class="CardBox">
<view class="CardItem">
<view class="CardName">{{ item.name }}</view>
<view class="CardTime">{{ item.time }}</view>
</view>
<view class="CardItem">
<view>
<u-tag type="warning" plain text="待整改" shape="circle"></u-tag>
</view>
<view>
<u-button text="整改反馈" color="#2979ff" size="small" @click="goFeedback(index)"></u-button>
</view>
</view>
</view>
</template>
</u-virtual-list>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
projectData: {
type: Object,
default: () => null
},
todaySummary: {
type: Object,
default: () => ({
qualifiedCount: 0,
totalCount: 0,
submissionDate: '',
submitted: false
})
}
})
// 使
const totalCheckItems = computed(() => {
return props.todaySummary?.totalCount || 0
})
// 使
const completedCount = computed(() => {
return props.todaySummary?.qualifiedCount || 0
})
const dataSource = ref([
{ id: 1, name: 'AI返现安全帽佩戴不规范', time: '2023-08-01 10:00:00' },
{ id: 2, name: 'AI返现安全帽佩戴不规范', time: '2023-08-02 10:00:00' },
{ id: 3, name: 'AI返现安全帽佩戴不规范', time: '2023-08-03 10:00:00' },
{ id: 4, name: 'AI返现安全帽佩戴不规范', time: '2023-08-04 10:00:00' },
{ id: 5, name: 'AI返现安全帽佩戴不规范', time: '2023-08-05 10:00:00' },
{ id: 6, name: 'AI返现安全帽佩戴不规范', time: '2023-08-06 10:00:00' },
{ id: 7, name: 'AI返现安全帽佩戴不规范', time: '2023-08-07 10:00:00' },
])
//
const submitCheck = () => {
// ID
const projectId = props.projectData?.projectId
if (!projectId) {
uni.showToast({
title: '项目ID不存在',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages/TodayExamine/index?projectId=${projectId}`,
})
}
//
const goFeedback = (index) => {
uni.navigateTo({
url: '/pages/ReformFeedback/index',
})
}
</script>
<style lang="scss" scoped>
.h2Box {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.blcakFont {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.yellowFont {
font-size: 36rpx;
font-weight: bold;
color: #FFD600;
}
.MainBox {
height: 100%;
overflow: hidden;
padding: 20rpx;
background-color: #fff;
.BoxA {
padding: 20rpx;
border-radius: 10rpx;
background-color: #EFF6FF;
}
.BoxB {
padding: 20rpx;
border-radius: 10rpx;
height: calc(100% - 210rpx);
.ListBox {
height: calc(100% - 10rpx);
margin-top: 20rpx;
overflow: hidden;
.CardBox {
width: calc(100% - 30rpx);
height: 180rpx;
margin: 0 auto;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08), 0 2rpx 4rpx rgba(0, 0, 0, 0.04);
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 10rpx;
overflow: auto;
.CardItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 20rpx;
margin-bottom: 20rpx;
}
.CardName {
flex: 1;
font-size: 26rpx;
font-weight: bold;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.CardTime {
white-space: nowrap;
flex-shrink: 0;
}
}
}
}
}
</style>

View File

@ -0,0 +1,129 @@
<!-- 项目详情-整改记录 -->
<template>
<view class="MainBox">
<u-virtual-list :list-data="dataSource" :item-height="260" height="100%">
<template #default="{ item }">
<view class="CardBox">
<view class="fw-600">{{ item.name }}</view>
<view class="FlexBox">
<view class="LableBox">整改内容</view>
<view class="TxtBox">{{ item.details }}</view>
</view>
<view class="FlexBox">
<view class="imgBox" v-for="(img, index) in item.imgUrl" :key="index">
<image :src="img" mode="aspectFill"></image>
</view>
</view>
<view class="FlexBox">
<view>审核意见</view>
<view>{{ item.details }}</view>
</view>
<view class="fb-gray">{{ item.time }}</view>
</view>
</template>
</u-virtual-list>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const dataSource = ref([
{
id: 1,
name: '消防器械摆放不当',
time: '2023-08-01 10:00:00',
details: '已重新规划消防器械摆区域,并设置标识',
opinion: '整改到位,符合项目要求',
imgUrl: ['https://img95.699pic.com/photo/40225/7755.jpg_wh860.jpg', 'https://img95.699pic.com/photo/40225/7755.jpg_wh860.jpg']
},
{
id: 2,
name: '项目风险管控类型',
time: '2023-08-02 10:00:00',
details: '已重新规划消防器械摆区域,并设置标识',
opinion: '整改到位,符合项目要求',
imgUrl: ['https://img95.699pic.com/photo/40225/7755.jpg_wh860.jpg']
},
{
id: 3,
name: '项目风险管控类型',
time: '2023-08-02 10:00:00',
details: '已重新规划消防器械摆区域,并设置标识',
opinion: '整改到位,符合项目要求',
imgUrl: ['https://img95.699pic.com/photo/40225/7755.jpg_wh860.jpg']
},
])
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
// align-items: flex-start;
gap: 20rpx;
}
.fb-gray {
color: #999;
}
.fw-600 {
font-weight: bold;
color: #333;
}
.MainBox {
height: 100%;
overflow: hidden;
padding: 20rpx;
background-color: #fff;
padding: 20rpx;
.CardBox {
width: 100%;
height: 380rpx;
background-color: #fff;
padding: 20rpx;
display: flex;
flex-direction: column;
border: 1rpx solid #c7c7c7;
// box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
border-radius: 10rpx;
gap: 20rpx;
.LableBox {
width: 160rpx;
font-size: 24rpx;
color: #666;
}
.TxtBox {
width: calc(100% - 160rpx);
font-size: 24rpx;
color: #333;
font-weight: bold;
overflow: hidden;
/* 隐藏溢出内容 */
white-space: nowrap;
/* 强制不换行 */
text-overflow: ellipsis;
/* 显示省略号 */
}
.imgBox {
width: 100rpx;
height: 100rpx;
border-radius: 10rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
}
}
</style>

View File

@ -0,0 +1,335 @@
<!-- 项目详情 -->
<template>
<view class="PageBox">
<view class="TopBox FlexBox">
<view class="iconBox FlexBox">
<!-- 返回列表页 -->
<u-icon name="arrow-leftward" size="24" @click="goBack"></u-icon>
</view>
<view class="Title">{{ progectInfo.name }}</view>
<view class="informBox">
<u-icon name="bell-fill" size="24"></u-icon>
<view class="MsgTxt"></view>
</view>
</view>
<view class="ContentBox">
<view class="PBasicBox">
<u-row justify="space-between">
<u-col span="4">
<view class="LabelBox">项目编号</view>
<view class="LabelBoxTxt">{{ progectInfo.code }}</view>
</u-col>
<u-col span="3">
<view class="LabelBox">作业负责人</view>
<view class="LabelBoxTxt">{{ progectInfo.leader }}</view>
</u-col>
<u-col span="5">
<view class="LabelBox">作业周期</view>
<view class="LabelBoxTxt">{{ progectInfo.period }}</view>
</u-col>
</u-row>
<u-row>
<u-col span="12">当前进度<u-line-progress :percentage="progectInfo.percentage"
activeColor="#55AAFF"></u-line-progress></u-col>
</u-row>
<u-row>
<u-col span="12">
<view class="LabelBox">风险管控类型</view>
<view class="LabelBoxTxt">{{ progectInfo.riskControlType }}</view>
</u-col>
</u-row>
</view>
<view class="TabBox">
<up-tabs :list="list1" @click="click"></up-tabs>
</view>
<view class="displayCase">
<ControlView v-if="currentTab === 1" :project-data="progectInfo.detailData" :today-summary="todayInspectionSummary"></ControlView>
<BasicView v-if="currentTab === 2" :project-data="progectInfo.detailData"></BasicView>
<RecordView v-if="currentTab === 3" :project-data="progectInfo.detailData"></RecordView>
</view>
</view>
<view class="FootBox">
<u-button type="primary" :disabled="progectInfo.detailData?.implementationStatus !== '4'">项目完工确认</u-button>
<view class="FootTxt">
<view>复核进度</view>
<view class="yellow">{{ getReviewStatusText() }}</view>
</view>
</view>
</view>
</template>
<script setup>
import ControlView from './components/ControlView.vue'
import BasicView from './components/BasicView.vue'
import RecordView from './components/RecordView.vue'
import { ref, onMounted } from 'vue'
import { getProjectImplementationDetail, getTodayInspectionSummary } from '@/api'
const progectInfo = ref({
Id: null,
name: '',
code: '',
leader: '',
period: '',
riskControlType: '',
percentage: 0,
//
detailData: null
})
//
const todayInspectionSummary = ref({
qualifiedCount: 0,
totalCount: 0,
submissionDate: '',
submitted: false
})
// tabs
const currentTab = ref(1)
// tabs
const list1 = ref([
{
name: '风险管控实施',
id: 1
},
{
name: '基本项目信息',
id: 2
},
{
name: '整改记录',
id: 3
}
])
// tabs
const click = (e) => {
currentTab.value = e.id
}
//
const goBack = () => {
uni.navigateBack()
}
//
const formatDate = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}.${month}.${day}`
}
//
const fetchProjectDetail = async (projectId) => {
try {
const data = await getProjectImplementationDetail(projectId)
if (data) {
//
progectInfo.value.detailData = data
//
progectInfo.value.Id = data.projectId
progectInfo.value.name = data.projectName || '未命名项目'
progectInfo.value.code = data.projectCode || ''
//
progectInfo.value.leader = data.supervisorName || '未设置'
//
const startTime = formatDate(data.workStartTime)
const endTime = formatDate(data.workEndTime)
progectInfo.value.period = startTime && endTime ? `${startTime}-${endTime}` : '未设置'
//
progectInfo.value.percentage = data.currentProgress || 0
// riskControlCard.templateName
progectInfo.value.riskControlType = data.riskControlCard?.templateName || '未设置'
}
} catch (error) {
console.error('获取项目详情失败:', error)
uni.showToast({
title: '获取项目详情失败',
icon: 'none'
})
}
}
//
const fetchTodayInspectionSummary = async (projectId) => {
try {
const data = await getTodayInspectionSummary(projectId)
if (data) {
todayInspectionSummary.value = {
qualifiedCount: data.qualifiedCount || 0,
totalCount: data.totalCount || 0,
submissionDate: data.submissionDate || '',
submitted: data.submitted || false
}
}
} catch (error) {
console.error('获取今日检查项汇总失败:', error)
// 使
todayInspectionSummary.value = {
qualifiedCount: 0,
totalCount: 0,
submissionDate: '',
submitted: false
}
}
}
//
const getReviewStatusText = () => {
const data = progectInfo.value.detailData
if (!data) return '未知状态'
//
if (data.supervisionReviewStatus) {
const statusMap = {
'0': '待监理复核',
'1': '监理复核中',
'2': '监理复核通过',
'3': '监理复核拒绝'
}
return statusMap[data.supervisionReviewStatus] || '未知状态'
}
//
if (data.propertyReviewStatus) {
const statusMap = {
'0': '待物业复核',
'1': '物业复核中',
'2': '物业复核通过',
'3': '物业复核拒绝'
}
return statusMap[data.propertyReviewStatus] || '未知状态'
}
return '未开始复核'
}
//
onMounted(() => {
// URLID
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
const projectId = options.id || options.projectId
if (projectId) {
fetchProjectDetail(projectId)
fetchTodayInspectionSummary(projectId)
} else {
uni.showToast({
title: '缺少项目ID参数',
icon: 'none'
})
}
})
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.PBasicBox {
padding: 20rpx;
background-color: #fff;
.LabelBox {
font-size: 24rpx;
color: #666;
margin-top: 20rpx;
}
.LabelBoxTxt {
font-size: 24rpx;
color: #333;
font-weight: bold;
}
}
.PageBox {
background-color: #ff9900;
height: 100vh;
overflow: hidden;
.TopBox {
height: 100rpx;
padding: 20rpx;
background-color: #fff;
.iconBox {
font-size: 32rpx;
}
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.informBox {
.MsgTxt {
padding: 8rpx;
background-color: hsla(17, 100%, 50%, 0.849);
color: #fff;
position: absolute;
top: 16rpx;
right: 16rpx;
border-radius: 50%;
}
}
}
.ContentBox {
background-color: #c5d5d6;
height: calc(100vh - 250rpx);
overflow: hidden;
.TabBox {
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
background-color: #fff;
}
.displayCase {
height: calc(100% - 370rpx);
background-color: #ff9900;
overflow: hidden;
}
}
.FootBox {
height: 150rpx;
gap: 20rpx;
padding: 20rpx;
background-color: #fff;
font-weight: bold;
.FootTxt {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
font-size: 24rpx;
color: #666;
}
.yellow {
color: #ff9900;
}
}
}
</style>

View File

@ -0,0 +1,234 @@
<!-- 项目实施-列表页 -->
<template>
<view class="PageBox">
<view class="FlexBox TopBox">
<view class="SelectBox">
<u-select :current="TypeValue" :options="TypeList" placeholder="请选择工单类型" size="large" showOptionsLabel
@update:current="handleTypeChange($event)"></u-select>
</view>
</view>
<view class="ListBox">
<u-virtual-list :list-data="dataSource" :item-height="200" height="100%">
<template #default="{ item, index }">
<view class="CardBox">
<view class="FlexBox">
<view class="Title">{{ item.name }}</view>
<view class="DetailBtn">
<u-button text="详情" type="primary" size="small" @click="GoDetail(item.id)"></u-button>
</view>
</view>
<view class="CodeTxt">{{ item.site }}</view>
<view class="CodeTxt">剩余作业时长{{ item.remainingTime }}</view>
<view class="TagBox">
<u-tag :text="item.status" plain
:type="item.status === '实施中' ? 'primary' : item.status === '整改中' ? 'warning' : item.status === '待完工确认' ? 'info' : item.status === '已完工' ? 'success' : 'danger'"
shape="circle"></u-tag>
</view>
</view>
</template>
</u-virtual-list>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { getProjectImplementationList } from '@/api'
const TypeValue = ref('1') //
//
const TypeList = ref([
{ name: '全部实施中项目', id: '1' },
{ name: '我负责的项目', id: '2' },
{ name: '带整改', id: '3' },
{ name: '待完工确认', id: '4' },
])
const dataSource = ref([])
const total = ref(0) //
//
const fetchProjectImplementationList = async () => {
try {
const params = {
pageNum: 1,
pageSize: 1000
}
//
if (TypeValue.value === '2') {
// -
params.myProject = true
} else if (TypeValue.value === '3') {
//
params.status = '整改中'
} else if (TypeValue.value === '4') {
//
params.status = '待完工确认'
} else {
//
params.status = '实施中'
}
delete params.status
const result = await getProjectImplementationList(params)
// { total, rows, code, msg }
if (result && result.rows && Array.isArray(result.rows)) {
dataSource.value = result.rows.map(item => {
//
let remainingDays = 0
if (item.workEndTime) {
const endTime = new Date(item.workEndTime)
const now = new Date()
const diffTime = endTime - now
remainingDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
remainingDays = remainingDays > 0 ? remainingDays : 0
}
//
let statusText = '未知状态'
if (item.implementationStatus === '1') {
statusText = '实施中'
} else if (item.implementationStatus === '2') {
statusText = '已完工'
} else if (item.implementationStatus === '3') {
statusText = '整改中'
} else if (item.implementationStatus === '4') {
statusText = '待完工确认'
}
return {
id: item.projectId,
name: item.projectName || '未命名项目',
remainingTime: remainingDays,
site: item.workLocation || item.workLocationDetail || '未设置地址',
status: statusText,
// 便使
rawData: item
}
})
total.value = result.total || 0
} else {
dataSource.value = []
total.value = 0
}
} catch (error) {
console.error('查询项目实施列表失败:', error)
dataSource.value = []
total.value = 0
}
}
//
const handleTypeChange = (value) => {
TypeValue.value = value
fetchProjectImplementationList()
}
//
onMounted(() => {
fetchProjectImplementationList()
})
//
onPullDownRefresh(() => {
fetchProjectImplementationList().finally(() => {
uni.stopPullDownRefresh()
})
})
//
const GoDetail = (id) => {
uni.navigateTo({
url: '/pages/ProgectDetails/index?id=' + id
})
}
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
}
.PageBox {
padding: 20rpx;
background-color: #fff;
height: calc(100vh - 170rpx);
.TopBox {
.SelectBox {
width: calc(50% - 10rpx);
height: 80rpx;
border-radius: 10rpx;
border: 1rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx;
.u-select {
width: 100%;
}
}
.BtnBox {
width: 300rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.ListBox {
padding: 20rpx 0px;
margin-top: 20rpx;
height: calc(100vh - 300rpx);
overflow: hidden;
.CardBox {
width: calc(100% - 30rpx);
height: calc(100% - 30rpx);
margin: 0 auto;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08), 0 2rpx 4rpx rgba(0, 0, 0, 0.04);
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 10rpx;
overflow: auto;
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.DetailBtn {
font-size: 24rpx;
color: #666;
}
.CodeTxt {
margin-top: 20rpx;
font-size: 24rpx;
color: #666;
}
.TagBox {
margin-top: 20rpx;
}
.BtnList {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<!-- 问题整改反馈 -->
<template>
<view class="PageBox">
<view class="TopBox FlexBox">
<view class="iconBox FlexBox">
<u-icon name="arrow-leftward" size="24" @click="goBack"></u-icon>
</view>
<view class="Title">问题整改反馈</view>
<view class="informBox"> </view>
</view>
<view class="ContentBox">
<view class="ListBox">
<view class="h2Box">问题详情</view>
<view class="ListItem">
<view class="LabelBox">问题描述</view>
<view class="ValueBox">
<u-input v-model="problemDesc" placeholder="请输入问题描述" disabled></u-input>
</view>
</view>
<view class="ListItem">
<view class="LabelBox">反馈人</view>
<view class="ValueBox">
<u-input v-model="feedbackPerson" placeholder="请输入反馈人" disabled></u-input>
</view>
</view>
<view class="ListItem">
<view class="LabelBox">反馈时间</view>
<view class="ValueBox">
<u-input v-model="feedbackTime" placeholder="请输入反馈时间" disabled></u-input>
</view>
</view>
<view class="ListItem">
<view class="LabelBox">现场照片</view>
<view class="ValueBox FlexBox">
<u-image url="https://img.yzcdn.cn/vant/user-avatar.png" width="100rpx"
height="100rpx"></u-image>
</view>
</view>
<view class="ListItem">
<view class="LabelBox">反馈时间</view>
<view class="ValueBox">
<u-input v-model="feedbackTime" placeholder="请输入反馈时间" disabled></u-input>
</view>
</view>
<view class="h2Box">整改内容</view>
<view class="ListItem">
<view class="LabelBox mustBox">整改措施描述</view>
<view class="ValueBox">
<u-textarea v-model="reformDesc" placeholder="请输入整改措施描述"></u-textarea>
</view>
</view>
<view class="ListItem">
<view class="LabelBox mustBox">整改完成照片</view>
<view class="ValueBox">
<u-upload v-model="reformPhoto" :max-count="1" :auto-upload="false" :preview-size="100"
@change="handleChange"></u-upload>
</view>
</view>
<view class="ListItem">
<view class="LabelBox mustBox">整改完成时间</view>
<view class="ValueBox">
<up-datetime-picker hasInput :show="showTimePicker" v-model="reformTime"
mode="datetime" @change="handleTimePicker"></up-datetime-picker>
</view>
</view>
</view>
</view>
<view class="FootBox">
<u-button type="primary" plain>保存草稿</u-button>
<u-button type="primary">提交检查项</u-button>
</view>
</view>
</template>
<script setup>
import dayjs from 'dayjs'
import { ref } from 'vue'
//
const reformDesc = ref('')
//
const reformPhoto = ref([])
//
const reformTime = ref('')
//
const showTimePicker = ref(false)
//
const handleTimePicker = (e) => {
showTimePicker.value = false
reformTime.value = dayjs(e.detail).format('YYYY-MM-DD HH:mm')
}
//
const goBack = () => {
uni.navigateBack({
delta: 1
})
}
</script>
<style lang="scss" scoped>
.h2Box {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-top: 20rpx;
}
.mustBox::after {
content: '*';
color: red;
margin-left: 5rpx;
}
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.PageBox {
background-color: #ff9900;
height: 100vh;
overflow: hidden;
.TopBox {
height: 100rpx;
padding: 20rpx;
background-color: #fff;
.iconBox {
font-size: 32rpx;
}
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.informBox {
.MsgTxt {
padding: 8rpx;
background-color: hsla(17, 100%, 50%, 0.849);
color: #fff;
position: absolute;
top: 16rpx;
right: 16rpx;
border-radius: 50%;
}
}
}
.ContentBox {
background-color: #c5d5d6;
height: calc(100vh - 250rpx);
overflow: hidden;
}
.FootBox {
display: flex;
align-items: center;
justify-content: space-between;
height: 150rpx;
gap: 20rpx;
padding: 20rpx;
background-color: #fff;
font-weight: bold;
}
}
.ListBox {
height: 100%;
padding: 20rpx;
background-color: #fff;
overflow: auto;
.ListItem {
margin-bottom: 20rpx;
.LabelBox {
font-weight: bold;
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
}
}
}
</style>

View File

@ -0,0 +1,197 @@
<!-- 今日检查项 -->
<template>
<view class="PageBox">
<view class="TopBox FlexBox">
<view class="iconBox FlexBox">
<!-- 返回列表页 -->
<u-icon name="arrow-leftward" size="24" @click="goBack"></u-icon>
</view>
<view class="Title">今日风险检查项</view>
<view class="informBox">
<u-icon name="bell-fill" size="24"></u-icon>
<view class="MsgTxt"></view>
</view>
</view>
<view class="ContentBox">
<view class="ListBox">
<view class="ListItem" v-for="item in checkItemList" :key="item.id">
<view class="LabelBox">{{ item.checkItemName }}</view>
<view class="ValueBox">
<u-radio-group v-model="item.value" @change="change(item)">
<u-radio label="合格" :name="'合格'" :border-color="item.value === '合格' ? '#ff9900' : '#c5d5d6'"></u-radio>
<u-radio label="不合格" :name="'不合格'" :border-color="item.value === '不合格' ? '#ff9900' : '#c5d5d6'"></u-radio>
</u-radio-group>
</view>
</view>
</view>
</view>
<view class="FootBox">
<u-button type="primary" plain>保存草稿</u-button>
<u-button type="primary">提交检查项</u-button>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getProjectImplementationDetail } from '@/api'
//
const checkItemList = ref([])
//
const goBack = () => {
uni.navigateBack({
delta: 1
})
}
//
const fetchCheckItems = async (projectId) => {
try {
const data = await getProjectImplementationDetail(projectId)
if (data && data.riskControlCard && data.riskControlCard.checkItems && data.riskControlCard.checkItems.length > 0) {
// checkItemssortOrder
checkItemList.value = data.riskControlCard.checkItems
.map(item => ({
id: item.checkItemId,
checkItemName: item.itemDescription,
value: item.checkResult === '合格' ? '合格' : (item.checkResult || ''),
checkItemId: item.checkItemId,
templateItemId: item.templateItemId,
sortOrder: item.sortOrder || 0,
originalData: item // 便
}))
.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
} else {
uni.showToast({
title: '暂无检查项数据',
icon: 'none'
})
checkItemList.value = []
}
} catch (error) {
console.error('获取检查项数据失败:', error)
uni.showToast({
title: '获取检查项数据失败',
icon: 'none'
})
checkItemList.value = []
}
}
//
const change = (item) => {
console.log('检查项选择改变:', item)
}
//
onMounted(() => {
// URLID
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
const projectId = options.projectId || options.id
if (projectId) {
fetchCheckItems(projectId)
} else {
uni.showToast({
title: '缺少项目ID参数',
icon: 'none'
})
}
})
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.PBasicBox {
padding: 20rpx;
background-color: #fff;
.LabelBox {
font-size: 24rpx;
color: #666;
margin-top: 20rpx;
}
.LabelBoxTxt {
font-size: 24rpx;
color: #333;
font-weight: bold;
}
}
.PageBox {
background-color: #ff9900;
height: 100vh;
overflow: hidden;
.TopBox {
height: 100rpx;
padding: 20rpx;
background-color: #fff;
.iconBox {
font-size: 32rpx;
}
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.informBox {
.MsgTxt {
padding: 8rpx;
background-color: hsla(17, 100%, 50%, 0.849);
color: #fff;
position: absolute;
top: 16rpx;
right: 16rpx;
border-radius: 50%;
}
}
}
.ContentBox {
background-color: #c5d5d6;
height: calc(100vh - 250rpx);
overflow: hidden;
}
.FootBox {
display: flex;
align-items: center;
justify-content: space-between;
height: 150rpx;
gap: 20rpx;
padding: 20rpx;
background-color: #fff;
font-weight: bold;
}
}
.ListBox {
height: 100%;
padding: 20rpx;
background-color: #fff;
overflow: auto;
.ListItem {
margin-bottom: 20rpx;
.LabelBox {
font-weight: bold;
font-size: 28rpx;
color: #333;
margin-top: 20rpx;
}
}
}
</style>

View File

@ -0,0 +1,382 @@
<!-- 工单审批-列表页 -->
<template>
<view class="PageBox">
<view class="FlexBox TopBox">
<view class="SelectBox">
<u-select :current="TypeValue" :options="TypeList" placeholder="请选择工单类型" size="large" showOptionsLabel
@update:current="handleTypeChange($event)"></u-select>
</view>
<view class="BtnBox">
<u-button type="primary" text="查询" @click="HandleNewAdd">新建工单</u-button>
</view>
</view>
<scroll-view class="ListBox" scroll-y>
<view class="CardBox" v-for="(item, index) in dataSource" :key="index">
<view class="FlexBox">
<view class="Title">{{ item.projectName || '未命名项目' }}</view>
<view class="DetailBtn" @click="handleDetail(item)">详情</view>
</view>
<view class="CodeTxt">项目编号{{ item.projectCode }}</view>
<!-- <view class="CodeTxt">作业地点{{ item.workLocation }}</view>
<view class="CodeTxt">作业负责人{{ item.supervisorName || '未设置' }}</view> -->
<view class="TagBox">
<!-- <u-tag :text="getStatusText(item.projectStatus)" shape="circle"></u-tag> -->
<u-tag :text="item.draftStatusText" shape="circle"></u-tag>
</view>
<view class="BtnList">
<u-button text="修改" plain color="#2979ff" size="small" @click="handleEdit(item)"></u-button>
<u-button text="撤回" plain color="#ff9900" size="small" @click="handleWithdraw(item, index)"></u-button>
<u-button text="删除" plain color="#fa3534" size="small" @click="handleDelete(item, index)"></u-button>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getWorkPlanList, deleteWorkPlan, withdrawWorkPlan } from '@/api/modules/workTicket'
const TypeValue = ref('1') //
//
const TypeList = ref([
{ name: '全部项目', id: '1' },
{ name: '我负责的项目', id: '2' },
{ name: '草稿', id: '3' },
{ name: '待审核', id: '4' },
{ name: '已撤回', id: '5' },
])
const dataSource = ref([])
const total = ref(0) //
//
// 0稿1234
const getStatusText = (status) => {
const statusMap = {
'0': '草稿',
'1': '已提交',
'2': '进行中',
'3': '已完成',
'4': '已取消'
}
return statusMap[status] || '未知状态'
}
//
//
const shouldShowEditButton = (item) => {
const status = item.draftStatusText || ''
return status === '待填写工作票' || status === '已驳回'
}
//
//
const shouldShowWithdrawButton = (item) => {
const status = item.draftStatusText || ''
return status === '待申领监控球机' || status === '待审核'
}
//
//
const shouldShowDeleteButton = (item) => {
const status = item.draftStatusText || ''
return status === '待填写工作票' || status === '已驳回'
}
//
const fetchWorkPlanList = async () => {
try {
const params = {
pageNum: 1,
pageSize: 1000
}
//
if (TypeValue.value === '3') {
params.draftStatusText = '草稿' // 稿
} else if (TypeValue.value === '4') {
params.draftStatusText = '待审核' //
} else if (TypeValue.value === '5') {
params.draftStatusText = '已撤回' //
}
const result = await getWorkPlanList(params)
// { total, rows }
if (result && result.rows) {
dataSource.value = result.rows
total.value = result.total || 0
} else if (Array.isArray(result)) {
dataSource.value = result
total.value = result.length
} else if (result && result.list) {
dataSource.value = result.list
total.value = result.total || result.list.length
} else if (result && result.records) {
dataSource.value = result.records
total.value = result.total || result.records.length
} else if (result && result.data) {
dataSource.value = result.data
total.value = result.total || result.data.length
} else {
dataSource.value = []
total.value = 0
}
} catch (error) {
console.error('查询工作计划列表失败:', error)
dataSource.value = []
total.value = 0
}
}
//
const handleTypeChange = (value) => {
TypeValue.value = value
fetchWorkPlanList()
}
//
const HandleNewAdd = () => {
uni.navigateTo({
url: '/pages/WorkOrderEdit/index'
})
}
//
const handleEdit = (item) => {
const projectId = item.projectId || item.id
if (!projectId) {
uni.showToast({
title: '项目ID不存在',
icon: 'none'
})
return
}
// ID ticketIds ticketId
const ticketId = item.ticketIds || item.ticketId
// URL edit=1
let url = `/pages/WorkOrderEdit/index?projectId=${projectId}&edit=1`
if (ticketId) {
// ticketIds 使
const ticketIdValue = Array.isArray(ticketId) ? ticketId[0] : ticketId
url += `&workTicketId=${ticketIdValue}`
}
uni.navigateTo({
url: url
})
}
//
const handleDetail = (item) => {
const projectId = item.projectId || item.id
if (!projectId) {
uni.showToast({
title: '项目ID不存在',
icon: 'none'
})
return
}
// ID ticketIds ticketId
const ticketId = item.ticketIds || item.ticketId
// URL
let url = `/pages/WorkOrderDetails/index?projectId=${projectId}`
if (ticketId) {
// ticketIds 使
const ticketIdValue = Array.isArray(ticketId) ? ticketId[0] : ticketId
url += `&workTicketId=${ticketIdValue}`
}
uni.navigateTo({
url: url
})
}
//
const handleWithdraw = async (item, index) => {
try {
//
const res = await new Promise((resolve) => {
uni.showModal({
title: '提示',
content: `确定要撤回项目"${item.projectName || '未命名项目'}"吗?`,
confirmText: '撤回',
confirmColor: '#ff9900',
success: (result) => {
resolve(result.confirm)
},
fail: () => {
resolve(false)
}
})
})
if (!res) {
return
}
// ID使 projectId使 id
const projectId = item.projectId || item.id
if (!projectId) {
uni.showToast({
title: '项目ID不存在',
icon: 'none'
})
return
}
//
await withdrawWorkPlan(projectId)
uni.showToast({
title: '撤回成功',
icon: 'success'
})
//
await fetchWorkPlanList()
} catch (error) {
console.error('撤回失败:', error)
// request.js
}
}
//
const handleDelete = async (item, index) => {
try {
//
const res = await new Promise((resolve) => {
uni.showModal({
title: '提示',
content: `确定要删除项目"${item.projectName || '未命名项目'}"吗?`,
confirmText: '删除',
confirmColor: '#fa3534',
success: (result) => {
resolve(result.confirm)
},
fail: () => {
resolve(false)
}
})
})
if (!res) {
return
}
// ID使 projectId使 id
const projectId = item.projectId || item.id
if (!projectId) {
uni.showToast({
title: '项目ID不存在',
icon: 'none'
})
return
}
//
await deleteWorkPlan(projectId)
uni.showToast({
title: '删除成功',
icon: 'success'
})
//
dataSource.value.splice(index, 1)
total.value = total.value - 1
} catch (error) {
console.error('删除失败:', error)
// request.js
}
}
//
onMounted(() => {
fetchWorkPlanList()
})
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
}
.PageBox {
padding: 20rpx;
background-color: #fff;
height: calc(100vh - 170rpx);
.TopBox {
.SelectBox {
width: calc(50% - 10rpx);
height: 80rpx;
border-radius: 10rpx;
border: 1rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx;
.u-select {
width: 100%;
}
}
.BtnBox {
width: 300rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.ListBox {
padding: 20rpx 0px;
margin-top: 20rpx;
height: calc(100vh - 300rpx);
.CardBox {
width: calc(100% - 30rpx);
min-height: 200rpx;
margin: 0 auto;
padding: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08), 0 2rpx 4rpx rgba(0, 0, 0, 0.04);
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 10rpx;
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.DetailBtn {
font-size: 24rpx;
color: #666;
}
.CodeTxt {
margin-top: 20rpx;
font-size: 24rpx;
color: #666;
}
.TagBox {
margin-top: 20rpx;
}
.BtnList {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
:deep(.u-button) {
width: 80px !important;
min-width: 60px !important;
margin-left: 0 !important;
margin-right: auto !important;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,251 @@
<!-- 球机申领 -->
<template>
<view class="MainBox">
<view class="FormBox">
<view class="FormItem">
<view class="FormLableBox mustBox">作业地点</view>
<view class="FormValueBox">
<u-input v-model="displayData.workLocation" placeholder="请输入作业地点" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业周期</view>
<view class="FormValueBox">
<view class="date-range-box">
<view class="date-item">
<u-input v-model="displayData.startTime" placeholder="开始时间" readonly></u-input>
</view>
<view class="date-separator"></view>
<view class="date-item">
<u-input v-model="displayData.endTime" placeholder="结束时间" readonly></u-input>
</view>
</view>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">申领数量</view>
<view class="FormValueBox">
<up-number-box v-model="formData.applyQuantity" @change="valChange"></up-number-box>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">安装位置</view>
<view class="FormValueBox">
<u-input v-model="formData.installationSite" placeholder="请输入安装位置"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">联系人</view>
<view class="FormValueBox">
<u-input v-model="formData.contactPerson" placeholder="请输入联系人"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">联系方式</view>
<view class="FormValueBox">
<u-input v-model="formData.contactPhone" placeholder="请输入联系方式"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox">备注</view>
<view class="FormValueBox">
<u-textarea v-model="formData.remark" placeholder="请输入备注(可选)"></u-textarea>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { generateGuid } from '@/utils/index.js';
import dayjs from 'dayjs';
import { ref, onMounted, watch, defineExpose } from 'vue'
const props = defineProps({
allData: {
type: Object,
default: () => { },
},
})
//
const displayData = ref({
workLocation: '', //
startTime: '', //
endTime: '', //
})
const formData = ref({
applyQuantity: 1, //
installationSite: '', //
contactPerson: '', //
contactPhone: '', //
remark: '', //
})
// allData
const updateDisplayData = () => {
// WorkNote
const workNote = props.allData?.WorkNote;
const basicsInfo = props.allData?.BasicsInfo;
// WorkNote.workLocation BasicsInfo.SpecificAddress
if (workNote?.workLocation) {
displayData.value.workLocation = workNote.workLocation;
} else if (basicsInfo?.SpecificAddress) {
displayData.value.workLocation = basicsInfo.SpecificAddress;
} else {
displayData.value.workLocation = '';
}
// WorkNote.period BasicsInfo.period
let period = null;
if (workNote?.period && Array.isArray(workNote.period) && workNote.period.length >= 2) {
period = workNote.period;
} else if (basicsInfo?.period && Array.isArray(basicsInfo.period) && basicsInfo.period.length >= 2) {
period = basicsInfo.period;
}
if (period) {
displayData.value.startTime = period[0] || '';
displayData.value.endTime = period[1] || '';
} else {
displayData.value.startTime = '';
displayData.value.endTime = '';
}
}
// allData
const restoreFormData = (data) => {
if (!data || Object.keys(data).length === 0) return;
if (data.applyQuantity !== undefined) formData.value.applyQuantity = data.applyQuantity;
if (data.installationSite) formData.value.installationSite = data.installationSite;
if (data.contactPerson) formData.value.contactPerson = data.contactPerson;
if (data.contactPhone) formData.value.contactPhone = data.contactPhone;
if (data.remark) formData.value.remark = data.remark;
// linkman
if (data.linkman && !data.contactPerson) {
//
const phoneMatch = data.linkman.match(/(\d{11}|\d{3,4}-\d{7,8})/);
if (phoneMatch) {
const phone = phoneMatch[0];
formData.value.contactPerson = data.linkman.replace(phone, '').trim();
formData.value.contactPhone = phone;
} else {
formData.value.contactPerson = data.linkman;
}
}
};
// allData.ApplyForMachine
watch(() => props.allData?.ApplyForMachine, (newData) => {
if (newData && Object.keys(newData).length > 0) {
restoreFormData(newData);
}
}, { immediate: true, deep: true });
// allData.WorkNote allData.BasicsInfo
watch(() => [props.allData?.WorkNote, props.allData?.BasicsInfo], () => {
updateDisplayData();
}, { immediate: true, deep: true });
//
const valChange = (val) => {
console.log('申领数量变化:', val);
}
defineExpose({
getFormData() {
return formData.value;
}
})
//
onMounted(() => {
//
updateDisplayData();
// allData
if (props.allData?.ApplyForMachine && Object.keys(props.allData.ApplyForMachine).length > 0) {
restoreFormData(props.allData.ApplyForMachine);
}
});
</script>
<style lang="scss" scoped>
.BlueTxt {
font-size: 28rpx;
color: #2979ff;
margin: 10rpx 0;
}
.addBtn {
font-size: 30rpx;
color: #2979ff;
font-weight: bold;
}
.BorderBox {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
}
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.MainBox {
padding: 20rpx;
background-color: #fff;
height: 100%;
overflow: auto;
}
.mustBox::after {
content: "*";
color: red;
font-size: 30rpx;
margin-left: 10rpx;
}
.FormBox {
// background-color: #f5f5f5;
height: 100%;
.FormItem {
padding: 20rpx;
background-color: #fff;
.FormLableBox {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.FormValueBox {
font-size: 30rpx;
margin-top: 20rpx;
}
}
}
.date-range-box {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
.date-item {
flex: 1;
}
.date-separator {
color: #999;
font-size: 28rpx;
}
}
</style>

View File

@ -0,0 +1,492 @@
<!-- 工单基础信息 -->
<template>
<view class="MainBox">
<view class="FormBox">
<view class="FormItem">
<view class="FormLableBox mustBox">项目名称</view>
<view class="FormValueBox">
<u-input v-model="formData.ProjectName" placeholder="请输入项目名称"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业地点</view>
<view class="MapBox" id="tianditu-map"> </view>
<view class="FormValueBox">
<u-input v-model="formData.SpecificAddress" placeholder="请输入具体楼层或区域"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业周期</view>
<view class="FormValueBox">
<view class="date-range-box">
<view class="date-item">
<u-input v-model="startDateText" placeholder="开始日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showStartDatePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showStartDatePicker" v-model="startDate" mode="date"
:minDate="minTimestamp" @confirm="HandleStartDateConfirm"
@cancel="showStartDatePicker = false"></up-datetime-picker>
</view>
<view class="date-separator"></view>
<view class="date-item">
<u-input v-model="endDateText" placeholder="结束日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showEndDatePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showEndDatePicker" v-model="endDate" mode="date"
:minDate="minTimestamp" @confirm="HandleEndDateConfirm"
@cancel="showEndDatePicker = false"></up-datetime-picker>
</view>
</view>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox FlexBox">
<view class="mustBox">作业负责人</view>
<view class="addBtn" @click="showMemberPickerA = true">添加</view>
</view>
<view class="FormValueBox">
<u-input v-model="formData.supervisorName" placeholder="请输入作业负责人" readonly></u-input>
</view>
<u-picker :show="showMemberPickerA" :columns="[MemberArr]" keyName="name" @confirm="HandleMemberConfirmA"
@cancel="showMemberPickerA = false"></u-picker>
</view>
<view class="FormItem">
<view class="FormLableBox FlexBox">
<view class="mustBox">作业班成员</view>
<view class="addBtn" @click="showMemberPicker = true">添加</view>
</view>
<view class="FormValueBox">
<view class="MemberBox">
<u-tag v-for="(member, index) in formData.MemberList" :key="member.id" closable shape="circle"
@close="RemoveMember(index)">
{{ member.name }}
</u-tag>
</view>
</view>
<u-picker :show="showMemberPicker" :columns="[MemberArr]" keyName="name" @confirm="HandleMemberConfirm"
@cancel="showMemberPicker = false"></u-picker>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, watch, defineExpose } from "vue";
import dayjs from 'dayjs';
import { getUserList } from '@/api';
const props = defineProps({
allData: {
type: Object,
default: () => ({})
}
})
//
let mapEntity = null;
//
let currentMarker = null;
const showMemberPickerA = ref(false); // 1
const showStartDatePicker = ref(false); //
const showEndDatePicker = ref(false); //
const startDate = ref(null); //
const endDate = ref(null); //
const startDateText = ref(''); //
const endDateText = ref(''); //
const minTimestamp = ref(dayjs('2020-01-01').valueOf()); //
//
const endDateMinTimestamp = computed(() => {
if (startDate.value) {
return startDate.value;
}
return minTimestamp.value;
});
const showMemberPicker = ref(false); //
//
const MemberArr = ref([]); //
//
const formData = ref({
ProjectName: "", //
Location: "", //
SpecificAddress: "", //
period: [], // [, ]
supervisorName: "", //
supervisorId: "", // ID
MemberList: [], //
});
//
const UpdatePeriod = () => {
if (startDateText.value && endDateText.value) {
formData.value.period = [startDateText.value, endDateText.value];
} else if (startDateText.value) {
formData.value.period = [startDateText.value, ''];
} else if (endDateText.value) {
formData.value.period = ['', endDateText.value];
} else {
formData.value.period = [];
}
console.log('作业周期:', formData.value.period);
};
//
defineExpose({
getFormData: () => formData.value,
});
// allData
const restoreFormData = (data) => {
if (!data || Object.keys(data).length === 0) return;
if (data.ProjectName) formData.value.ProjectName = data.ProjectName;
if (data.Location) formData.value.Location = data.Location;
if (data.SpecificAddress) formData.value.SpecificAddress = data.SpecificAddress;
if (data.supervisorName) formData.value.supervisorName = data.supervisorName;
if (data.supervisorId) formData.value.supervisorId = data.supervisorId;
if (data.MemberList && Array.isArray(data.MemberList)) {
formData.value.MemberList = [...data.MemberList];
}
if (data.period && Array.isArray(data.period) && data.period.length >= 2) {
if (data.period[0]) {
// "2020-01-01 00:00:00"
const startDateStr = String(data.period[0]).split(' ')[0];
startDateText.value = startDateStr;
startDate.value = dayjs(startDateStr).isValid() ? dayjs(startDateStr).valueOf() : null;
}
if (data.period[1]) {
// "2020-01-01 00:00:00"
const endDateStr = String(data.period[1]).split(' ')[0];
endDateText.value = endDateStr;
endDate.value = dayjs(endDateStr).isValid() ? dayjs(endDateStr).valueOf() : null;
}
// formData.period
UpdatePeriod();
}
};
// allData.BasicsInfo
watch(() => props.allData?.BasicsInfo, (newData) => {
if (newData && Object.keys(newData).length > 0) {
restoreFormData(newData);
}
}, { immediate: true, deep: true });
//
const fetchUserList = async () => {
try {
const res = await getUserList();
console.log('获取用户列表成功:', res);
// { total: number, rows: [...], code: 200, msg: "" }
if (res && res.rows && Array.isArray(res.rows)) {
// { id: userId, name: nickName || userName }
MemberArr.value = res.rows.map(user => ({
id: user.userId,
name: user.nickName || user.userName || ''
}));
console.log('转换后的成员列表:', MemberArr.value);
} else if (res && res.data && Array.isArray(res.data)) {
//
MemberArr.value = res.data.map(user => ({
id: user.userId || user.id,
name: user.nickName || user.userName || user.name || ''
}));
} else if (Array.isArray(res)) {
//
MemberArr.value = res.map(user => ({
id: user.userId || user.id,
name: user.nickName || user.userName || user.name || ''
}));
} else {
console.warn('用户列表数据格式不正确:', res);
uni.showToast({
title: '获取用户列表失败',
icon: 'none',
duration: 2000
});
}
} catch (error) {
console.error('获取用户列表失败:', error);
uni.showToast({
title: error.message || '获取用户列表失败',
icon: 'none',
duration: 2000
});
}
};
//
onMounted(() => {
initTiandituMap();
//
fetchUserList();
// allData
if (props.allData?.BasicsInfo && Object.keys(props.allData.BasicsInfo).length > 0) {
restoreFormData(props.allData.BasicsInfo);
}
});
//
const HandleStartDateConfirm = (e) => {
console.log('选中的开始日期:', e);
// 使
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
startDateText.value = dayjs(selectedDate).format('YYYY-MM-DD');
startDate.value = selectedDate;
endDateText.value = '';
endDate.value = null;
showStartDatePicker.value = false;
UpdatePeriod();
};
//
const HandleEndDateConfirm = (e) => {
console.log('选中的结束日期:', e);
// 使
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
//
if (startDate.value && dayjs(selectedDate).isBefore(dayjs(startDate.value))) {
uni.showToast({
title: '结束日期不能小于开始日期',
icon: 'none',
duration: 2000
});
return;
}
endDateText.value = dayjs(selectedDate).format('YYYY-MM-DD');
endDate.value = selectedDate;
showEndDatePicker.value = false;
UpdatePeriod();
};
//
const HandleMemberConfirmA = (e) =>{
console.log('选中的作业负责人完整信息:', e.value);
if (e.value) {
formData.value.supervisorName = e.value[0].name;
formData.value.supervisorId = e.value[0].id;
showMemberPickerA.value = false;
}
}
//
const HandleMemberConfirm = (e) => {
console.log('选中的成员完整信息:', e);
console.log('e.value:', e.value);
console.log('e.value[0]:', e.value ? e.value[0] : 'undefined');
console.log('MemberArr:', MemberArr.value);
if (e.value && e.value.length > 0) {
const selectedMember = e.value[0];
console.log('选中的成员:', selectedMember);
console.log('selectedMember 的类型:', typeof selectedMember);
let memberInfo;
//
if (typeof selectedMember === 'object' && selectedMember !== null) {
// 使
memberInfo = selectedMember;
console.log('返回的是对象,直接使用:', memberInfo);
} else {
// MemberArr id
memberInfo = MemberArr.value.find(item => item.id === selectedMember);
console.log('返回的是字符串,从 MemberArr 根据id查找', memberInfo);
}
if (memberInfo && memberInfo.id) {
// id
const isExist = formData.value.MemberList.some(item => item.id === memberInfo.id);
console.log('成员是否已存在:', isExist);
console.log('当前成员列表:', formData.value.MemberList);
if (!isExist) {
formData.value.MemberList.push({
id: memberInfo.id,
name: memberInfo.name
});
console.log('添加成员成功:', memberInfo);
console.log('添加后的成员列表:', formData.value.MemberList);
} else {
uni.showToast({
title: '该成员已存在',
icon: 'none',
duration: 2000
});
}
} else {
console.log('未找到成员信息或成员信息无效');
}
} else {
console.log('e.value 为空或长度为0');
}
showMemberPicker.value = false;
};
//
const RemoveMember = (index) => {
formData.value.MemberList.splice(index, 1);
console.log('删除成员成功');
};
//
const initTiandituMap = () => {
try {
// 1.
const container = document.getElementById("tianditu-map");
if (!container) {
console.error("地图容器未找到");
return;
}
// 2. 12
mapEntity = new T.Map("tianditu-map");
const centerPoint = new T.LngLat(116.403874, 39.914885);
mapEntity.centerAndZoom(centerPoint, 12);
// 3.
mapEntity.enableDrag();
mapEntity.enableScrollWheelZoom();
// 4. +
const vecLayer = new T.TileLayer("vec_w", { key: "你的天地图Key" });
const cvaLayer = new T.TileLayer("cva_w", { key: "你的天地图Key" });
const layerGroup = new T.LayerGroup([vecLayer, cvaLayer]);
mapEntity.addLayer(layerGroup);
// 5.
mapEntity.addEventListener("click", (e) => {
const lng = e.lnglat.getLng();
const lat = e.lnglat.getLat();
//
if (currentMarker) {
mapEntity.removeOverLay(currentMarker);
}
//
const marker = new T.Marker(new T.LngLat(lng, lat));
mapEntity.addOverLay(marker);
currentMarker = marker;
// Location
formData.value.Location = `${lng},${lat}`;
console.log("标记点位置:", lng, lat);
});
console.log("天地图初始化成功");
} catch (error) {
console.error("天地图初始化失败:", error);
}
};
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.addBtn {
font-size: 30rpx;
color: #2979ff;
font-weight: bold;
}
.MainBox {
padding: 20rpx;
background-color: #fff;
height: 100%;
overflow: auto;
}
.mustBox::after {
content: "*";
color: red;
font-size: 30rpx;
margin-left: 10rpx;
}
.MapBox {
width: 100%;
height: 400rpx;
}
.MemberBox {
height: 100rpx;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
flex-wrap: wrap;
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
}
.FormBox {
// background-color: #f5f5f5;
height: 100%;
.FormItem {
padding: 20rpx;
background-color: #fff;
.FormLableBox {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.FormValueBox {
font-size: 30rpx;
margin-top: 20rpx;
.date-range-box {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
.date-item {
flex: 1;
}
.date-separator {
color: #999;
font-size: 28rpx;
}
}
}
}
}
</style>

View File

@ -0,0 +1,708 @@
<!-- 出入证申请 -->
<template>
<view class="MainBox">
<view class="FormBox">
<view class="FormItem">
<view class="FormLableBox">项目名称</view>
<view class="FormValueBox">
<u-input v-model="allData.BasicsInfo.ProjectName" placeholder="请输入项目名称" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox">作业地点</view>
<view class="FormValueBox">
<u-input v-model="allData.BasicsInfo.SpecificAddress" placeholder="请输入作业地点" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox">作业班成员</view>
<view class="FormValueBox">
<view class="MemberBox">
<u-tag v-for="(member, index) in memberList" :key="member.id" shape="circle">
{{ member.name }}
</u-tag>
<view v-if="!memberList || memberList.length === 0" class="empty-tip">暂无作业班成员</view>
</view>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">出入证有效日期</view>
<view class="FormValueBox">
<view class="date-range-box">
<view class="date-item">
<u-input v-model="startValidTimeText" placeholder="开始日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showStartValidTimePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showStartValidTimePicker" v-model="startValidTime" mode="date"
:minDate="minTimestamp" @confirm="HandleStartValidTimeConfirm"
@cancel="showStartValidTimePicker = false" format="YYYY-MM-DD"></up-datetime-picker>
</view>
<view class="date-separator"></view>
<view class="date-item">
<u-input v-model="endValidTimeText" placeholder="结束日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showEndValidTimePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showEndValidTimePicker" v-model="endValidTime" mode="date"
:minDate="endValidTimeMinTimestamp" @confirm="HandleEndValidTimeConfirm"
@cancel="showEndValidTimePicker = false" format="YYYY-MM-DD"></up-datetime-picker>
</view>
</view>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox FlexBox">
<view class="mustBox">随行车辆信息</view>
<view class="addBtn" @click="showCarInputPopup = true">添加</view>
</view>
<view class="FormValueBox">
<view class="CarListBox">
<view v-for="item in formData.CarList" :key="item.id" class="CarItem">
<view class="CarInfoBox">
<view class="CarTitleBox">{{ item.code }}</view>
<view class="CartypeBox">{{ item.type }}</view>
</view>
<view>
<u-button text="删除" type="primary" size="small"
@click="HandleDeleteCar(item.id)"></u-button>
</view>
</view>
</view>
</view>
<!-- 车辆信息输入弹窗 -->
<u-popup :show="showCarInputPopup" mode="center" :round="10" @close="showCarInputPopup = false">
<view class="car-input-popup">
<view class="popup-title">添加车辆信息</view>
<view class="popup-form">
<view class="form-item">
<view class="form-label">车辆类型</view>
<u-input v-model="carInputForm.type" placeholder="请输入车辆类型" :maxlength="50"></u-input>
</view>
<view class="form-item">
<view class="form-label">车牌号</view>
<u-input v-model="carInputForm.code" placeholder="请输入车牌号" :maxlength="20"></u-input>
</view>
</view>
<view class="popup-buttons">
<u-button text="取消" type="info" @click="showCarInputPopup = false"></u-button>
<u-button text="确定" type="primary" @click="HandleCarInputConfirm"></u-button>
</view>
</view>
</u-popup>
</view>
<view class="FormItem">
<view class="FormLableBox">附件上传</view>
<view class="FormValueBox">
<up-upload width="100%" @afterRead="afterRead" multiple accept="image/*,application/pdf">
<view class="UploadBox">
<view class="UpIcon">
<img src="@/static/icon/upload-icon.png" alt="">
</view>
<view class="UpTitle">点击上传</view>
<view class="UpDesc">支持JPG,PNG,PDF格式上传单个文件大小不超过10MB</view>
</view>
</up-upload>
</view>
<view class="UpFileListBox">
<view v-for="item in fileList1" :key="item.fid || item.url" class="UpFileItem">
<view>
<view class="UpFileName">{{ item.name }}</view>
<view class="UpFileSize">{{ item.size }}</view>
<view v-if="item.uploading" class="UpFileStatus">上传中...</view>
</view>
<u-button text="删除" type="primary" size="small" :disabled="item.uploading"
@click="deletePic(item.fid)">
</u-button>
</view>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox">备注</view>
<view class="FormValueBox">
<u-input v-model="formData.remark" type="textarea" :maxlength="500" placeholder="请输入备注信息"
:autoHeight="true"></u-input>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { generateGuid } from '@/utils/index.js';
import dayjs from "dayjs";
import { ref, computed, onMounted, watch, defineExpose } from "vue";
import { uploadFileApi } from '@/api/modules/workTicket';
const props = defineProps({
allData: {
type: Object,
default: () => { },
}
})
const showStartValidTimePicker = ref(false); //
const showEndValidTimePicker = ref(false); //
const startValidTime = ref(null); //
const endValidTime = ref(null); //
const startValidTimeText = ref(''); //
const endValidTimeText = ref(''); //
const minTimestamp = ref(dayjs('2020-01-01').valueOf()); //
const showCarInputPopup = ref(false); //
//
const endValidTimeMinTimestamp = computed(() => {
if (startValidTime.value) {
return startValidTime.value;
}
return minTimestamp.value;
});
//
const carInputForm = ref({
type: '', //
code: '', //
});
//
const formData = ref({
validTime: '', // YYYY-MM-DD YYYY-MM-DD YYYY-MM-DD
CarList: [], //
remark: '', //
})
const fileList1 = ref([]); //
//
const memberList = computed(() => {
return props.allData?.BasicsInfo?.MemberList || [];
});
//
const afterRead = async (e) => {
console.log('上传的文件:', e);
let fileInfo = e.file[0];
//
if (fileInfo.size > 10 * 1024 * 1024) {
uni.showToast({
title: '文件大小不能超过10MB',
icon: 'none'
});
return;
}
//
if (!fileInfo.name.match(/\.(jpg|png|pdf)$/i)) {
uni.showToast({
title: '请上传JPG,PNG,PDF格式的文件',
icon: 'none'
});
return;
}
// ID
const tempFid = generateGuid();
const tempFile = {
fid: tempFid,
name: fileInfo.name,
size: (fileInfo.size / 1024 / 1024).toFixed(2) + 'MB',
url: fileInfo.url,
uploading: true //
};
//
fileList1.value.push(tempFile);
try {
//
const uploadResult = await uploadFileApi(
fileInfo.url, //
'file', //
{}, //
{ loading: true, loadingText: '上传中...' }
);
//
// : { name: "", url: "URL" }
const serverUrl = uploadResult?.url || fileInfo.url;
const serverFileName = uploadResult?.name || fileInfo.name;
// 使使ID
const fileId = uploadResult?.name || tempFid;
//
const fileIndex = fileList1.value.findIndex(item => item.fid === tempFid);
if (fileIndex !== -1) {
fileList1.value[fileIndex] = {
fid: fileId,
name: serverFileName, // 使
size: (fileInfo.size / 1024 / 1024).toFixed(2) + 'MB',
url: serverUrl, // 使URL
uploading: false
};
}
uni.showToast({
title: '上传成功',
icon: 'success',
duration: 1500
});
} catch (error) {
console.error('文件上传失败:', error);
//
fileList1.value = fileList1.value.filter(item => item.fid !== tempFid);
uni.showToast({
title: '上传失败,请重试',
icon: 'none',
duration: 2000
});
}
}
//
const deletePic = (fid) => {
console.log('删除文件:', fid);
let arr = [...fileList1.value];
arr = arr.filter(item => item.fid !== fid);
fileList1.value = arr;
}
// allData
const restoreFormData = (data) => {
if (!data || Object.keys(data).length === 0) return;
if (data.validTime) {
formData.value.validTime = data.validTime;
//
const timeStr = data.validTime.trim();
if (timeStr.includes('至')) {
const parts = timeStr.split('至').map(s => s.trim());
if (parts[0]) {
startValidTimeText.value = parts[0];
startValidTime.value = dayjs(parts[0]).valueOf();
}
if (parts[1]) {
endValidTimeText.value = parts[1];
endValidTime.value = dayjs(parts[1]).valueOf();
}
} else {
startValidTimeText.value = timeStr;
startValidTime.value = dayjs(timeStr).valueOf();
}
}
if (data.CarList && Array.isArray(data.CarList)) {
formData.value.CarList = [...data.CarList];
}
if (data.remark) formData.value.remark = data.remark;
//
if (data.fileList && Array.isArray(data.fileList)) {
fileList1.value = [...data.fileList];
}
};
// allData.GatePassInfo
watch(() => props.allData?.GatePassInfo, (newData) => {
if (newData && Object.keys(newData).length > 0) {
restoreFormData(newData);
}
}, { immediate: true, deep: true });
//
defineExpose({
getFormData() {
// "YYYY-MM-DD" "YYYY-MM-DD YYYY-MM-DD"
let validityStartTime = '';
let validityEndTime = '';
if (formData.value.validTime) {
const timeStr = formData.value.validTime.trim();
if (timeStr.includes('至')) {
// ""
const parts = timeStr.split('至').map(s => s.trim());
validityStartTime = parts[0] ? `${parts[0]} 08:00:00` : '';
validityEndTime = parts[1] ? `${parts[1]} 18:00:00` : '';
} else {
//
validityStartTime = `${timeStr} 08:00:00`;
validityEndTime = `${timeStr} 18:00:00`;
}
}
//
const vehicleList = formData.value.CarList.map(car => ({
licensePlate: car.code || '',
vehicleType: car.type || ''
}));
// URL
const attachmentList = fileList1.value
.filter(file => !file.uploading && file.url) // URL
.map(file => file.url || '')
.filter(url => url);
// ID
const projectId = props.allData?.BasicsInfo?.projectId || null;
const workLocation = props.allData?.BasicsInfo?.SpecificAddress || '';
// projectId
console.log('GatePassInfo getFormData - projectId:', projectId);
console.log('GatePassInfo getFormData - allData.BasicsInfo:', props.allData?.BasicsInfo);
// ID
const memberList = props.allData?.BasicsInfo?.MemberList || [];
const sysUserIds = memberList.map(member => {
// ID
const id = member.id;
return typeof id === 'string' ? parseInt(id) || id : id;
});
// ID
const accessPermitId = props.allData?.GatePassInfo?.id || null;
return {
permitId: accessPermitId, // ID使permitId
projectId,
workLocation,
sysUserIds,
validityStartTime,
validityEndTime,
vehicleList,
attachmentList,
remark: formData.value.remark || ''
};
}
})
//
onMounted(() => {
// allData
if (props.allData?.GatePassInfo && Object.keys(props.allData.GatePassInfo).length > 0) {
restoreFormData(props.allData.GatePassInfo);
}
});
//
const HandleStartValidTimeConfirm = (e) => {
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
startValidTimeText.value = dayjs(selectedDate).format('YYYY-MM-DD');
startValidTime.value = selectedDate;
endValidTimeText.value = '';
endValidTime.value = null;
showStartValidTimePicker.value = false;
updateValidTime();
}
//
const HandleEndValidTimeConfirm = (e) => {
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
//
if (startValidTime.value && dayjs(selectedDate).isBefore(dayjs(startValidTime.value))) {
uni.showToast({
title: '结束日期不能小于开始日期',
icon: 'none',
duration: 2000
});
return;
}
endValidTimeText.value = dayjs(selectedDate).format('YYYY-MM-DD');
endValidTime.value = selectedDate;
showEndValidTimePicker.value = false;
updateValidTime();
}
//
const updateValidTime = () => {
if (startValidTimeText.value && endValidTimeText.value) {
formData.value.validTime = `${startValidTimeText.value}${endValidTimeText.value}`;
} else if (startValidTimeText.value) {
formData.value.validTime = startValidTimeText.value;
} else {
formData.value.validTime = '';
}
}
//
const HandleCarInputConfirm = () => {
//
if (!carInputForm.value.type || carInputForm.value.type.trim() === '') {
uni.showToast({
title: '请输入车辆类型',
icon: 'none',
duration: 2000
});
return;
}
if (!carInputForm.value.code || carInputForm.value.code.trim() === '') {
uni.showToast({
title: '请输入车牌号',
icon: 'none',
duration: 2000
});
return;
}
//
const isExist = formData.value.CarList.some(item => item.code === carInputForm.value.code.trim());
if (isExist) {
uni.showToast({
title: '该车牌号已存在',
icon: 'none',
duration: 2000
});
return;
}
//
const newCar = {
id: generateGuid(), // 使ID
type: carInputForm.value.type.trim(),
code: carInputForm.value.code.trim()
};
formData.value.CarList.push(newCar);
console.log('添加车辆成功:', newCar);
//
carInputForm.value.type = '';
carInputForm.value.code = '';
showCarInputPopup.value = false;
uni.showToast({
title: '添加成功',
icon: 'success',
duration: 1500
});
};
//
const HandleDeleteCar = (carId) => {
const index = formData.value.CarList.findIndex(item => item.id === carId);
if (index !== -1) {
formData.value.CarList.splice(index, 1);
console.log('删除车辆成功');
}
};
</script>
<style lang="scss" scoped>
.UploadBox {
width: 100%;
height: 300rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20rpx;
padding: 20rpx;
border: 1rpx dashed #e4e7ed;
border-radius: 10rpx;
.UpTitle {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.UpDesc {
font-size: 24rpx;
color: #666;
}
}
.UpFileListBox {
margin-top: 20rpx;
height: auto;
.UpFileItem {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.UpFileName {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.UpFileSize {
font-size: 24rpx;
color: #666;
}
.UpFileStatus {
font-size: 24rpx;
color: #2979ff;
margin-top: 10rpx;
}
}
}
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.addBtn {
font-size: 30rpx;
color: #2979ff;
font-weight: bold;
}
.MainBox {
padding: 20rpx;
background-color: #fff;
height: 100%;
overflow: auto;
}
.mustBox::after {
content: "*";
color: red;
font-size: 30rpx;
margin-left: 10rpx;
}
.CarListBox {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.CarItem {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
flex-wrap: wrap;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
padding: 20rpx;
border-radius: 10rpx;
.CarInfoBox {
.CarTitleBox {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.CartypeBox {
font-size: 24rpx;
color: #666;
}
}
}
}
.FormBox {
// background-color: #f5f5f5;
height: 100%;
.FormItem {
padding: 20rpx;
background-color: #fff;
.FormLableBox {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.FormValueBox {
font-size: 30rpx;
margin-top: 20rpx;
.MemberBox {
min-height: 60rpx;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
flex-wrap: wrap;
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
.empty-tip {
color: #999;
font-size: 28rpx;
}
}
.date-range-box {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
.date-item {
flex: 1;
}
.date-separator {
color: #999;
font-size: 28rpx;
}
}
}
}
}
//
.car-input-popup {
width: 600rpx;
padding: 40rpx;
background-color: #fff;
border-radius: 20rpx;
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 40rpx;
}
.popup-form {
display: flex;
flex-direction: column;
gap: 30rpx;
margin-bottom: 40rpx;
.form-item {
display: flex;
flex-direction: column;
gap: 20rpx;
.form-label {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
.popup-buttons {
display: flex;
gap: 20rpx;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,555 @@
<!-- 风险控制 -->
<template>
<view class="MainBox">
<view class="FormBox">
<view class="H2Box">基础信息</view>
<view class="FormItem">
<view class="FormLableBox mustBox">所属单位</view>
<view class="FormValueBox">
<u-input v-model="formData.unitName" placeholder="所属单位" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">票证编号</view>
<view class="FormValueBox">
<u-input v-model="formData.ticketNumber" placeholder="请输入票证编号" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">工作内容</view>
<view class="FormValueBox">
<u-input v-model="formData.workContent" placeholder="请输入工作内容" :readonly="isWorkTicketDetailLoaded"></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox FlexBox">
<view class="mustBox">工作负责人</view>
<view class="addBtn" v-if="!isWorkTicketDetailLoaded" @click="showWorkResponsiblePicker = true">选择</view>
</view>
<view class="FormValueBox">
<view class="BorderBox">
<view class="grayFont" v-if="!formData.workResponsible">请选择工作负责人</view>
<view v-else>{{ formData.workResponsible }}</view>
</view>
<u-picker v-if="!isWorkTicketDetailLoaded" v-model="formData.workResponsible" :columns="[workResponsibleList]"
:show="showWorkResponsiblePicker" keyName="name" @confirm="confirmWorkResponsiblePicker"
@cancel="showWorkResponsiblePicker = false"></u-picker>
</view>
</view>
<view class="H2Box">检查内容</view>
<view class="CardBox" v-if="checkContentList.length > 0">
<view class="CardBoxItem" v-for="(item, index) in checkContentList" :key="item.id || index">
{{ index + 1 }} {{ item.itemDescription }}
</view>
</view>
<view class="CardBox" v-else>
<view class="CardBoxItem grayFont">暂无检查内容</view>
</view>
<view class="H2Box">附件上传</view>
<view style="margin-top: 20rpx;">
<up-upload width="100%" @afterRead="afterRead" multiple accept="image/*,application/pdf">
<view class="UploadBox">
<view class="UpIcon">
<img src="@/static/icon/upload-icon.png" alt="">
</view>
<view class="UpTitle">点击上传</view>
<view class="UpDesc">上传现场照片或检测报告支持JPG,PNG,PDF格式上传单个文件大小不超过10MB</view>
</view>
</up-upload>
</view>
<view class="UpFileListBox">
<view v-for="item in fileList1" :key="item.fid || item.url" class="UpFileItem">
<view>
<view class="UpFileName">{{ item.name }}</view>
<view class="UpFileSize">{{ item.size }}</view>
<view v-if="item.uploading" class="UpFileStatus">上传中...</view>
</view>
<u-button text="删除" type="primary" size="small" :disabled="item.uploading"
@click="deletePic(item.fid)">
</u-button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { generateGuid } from '@/utils/index.js';
import dayjs from 'dayjs';
import { ref, onMounted, watch, defineExpose } from 'vue'
import { getRiskCardTemplateDetail, uploadFileApi } from '@/api/modules/workTicket'
import { getUserInfo } from '@/api/modules/user'
const props = defineProps({
allData: {
type: Object,
default: () => ({})
},
templateId: {
type: [Number, String],
default: null
}
})
//
const showWorkResponsiblePicker = ref(false);
//
const workResponsibleList = ref([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
]);
//
const checkContentList = ref([])
//
const templateDetail = ref(null)
//
const fileList1 = ref([]);
//
const isWorkTicketDetailLoaded = ref(false);
const formData = ref({
ticketNumber: generateGuid(), //
unitName: '', //
workContent: '', //
workResponsible: '', //
})
// allData
const restoreFormData = (data) => {
if (!data || Object.keys(data).length === 0) return;
if (data.unitName) formData.value.unitName = data.unitName;
if (data.ticketNumber) formData.value.ticketNumber = data.ticketNumber;
if (data.workContent) formData.value.workContent = data.workContent;
if (data.workResponsible) formData.value.workResponsible = data.workResponsible;
// data
// if (data.fileList && Array.isArray(data.fileList)) {
// fileList1.value = [...data.fileList];
// }
};
// allData.RiskControl
watch(() => props.allData?.RiskControl, (newData) => {
if (newData && Object.keys(newData).length > 0) {
restoreFormData(newData);
}
}, { immediate: true, deep: true });
//
watch(() => props.allData?.RiskControl?.workTicketDetail, (workTicketDetail) => {
if (workTicketDetail && Object.keys(workTicketDetail).length > 0) {
console.log('工作票详情数据:', workTicketDetail);
//
if (workTicketDetail.ticketNumber) {
formData.value.ticketNumber = workTicketDetail.ticketNumber;
}
//
if (workTicketDetail.workContent) {
formData.value.workContent = workTicketDetail.workContent;
}
//
if (workTicketDetail.supervisorName) {
formData.value.workResponsible = workTicketDetail.supervisorName;
}
// 使
isWorkTicketDetailLoaded.value = true;
}
}, { immediate: true, deep: true });
//
defineExpose({
getFormData() {
return {
...formData.value,
//
// fileList: fileList1.value
};
},
//
getSubmitData() {
//
const checkItems = checkContentList.value.map((item, index) => ({
templateItemId: item.id || item.templateItemId || (index + 1),
itemDescription: item.itemDescription || item.description || '',
checkResult: item.checkResult || '合格', // ""
sortOrder: item.sortOrder || (index + 1)
}));
// URL
const attachmentList = fileList1.value
.filter(file => !file.uploading && file.url)
.map(file => file.url);
//
const workTicketDetail = props.allData?.RiskControl?.workTicketDetail
|| props.allData?.workTicketDetail
|| {};
// 使yyyy-MM-ddTHH:mm
const inspectionTime = dayjs().format('YYYY-MM-DDTHH:mm');
return {
ticketId: props.allData?.workTicketId || null,
templateId: props.templateId || null,
ticketNumber: formData.value.ticketNumber || '',
operatingUnit: formData.value.unitName || '',
workContent: formData.value.workContent || '',
supervisorName: formData.value.workResponsible || workTicketDetail.supervisorName || '',
supervisorPosition: workTicketDetail.supervisorPosition || '',
contactMethod: workTicketDetail.contactMethod || workTicketDetail.contactPhone || '',
inspectionTime: inspectionTime,
checkItems: checkItems,
attachmentList: attachmentList
};
}
})
//
const loadTemplateDetail = async (templateId) => {
if (!templateId) {
console.warn('模板ID为空无法加载模板详情');
return;
}
try {
const res = await getRiskCardTemplateDetail(templateId);
templateDetail.value = res;
console.log('风险管控卡模板详情:', res);
//
// checkItemsitemscontentList
if (res) {
if (Array.isArray(res.checkItems)) {
checkContentList.value = res.checkItems;
} else if (Array.isArray(res.items)) {
checkContentList.value = res.items;
} else if (Array.isArray(res.contentList)) {
checkContentList.value = res.contentList;
} else if (Array.isArray(res.list)) {
checkContentList.value = res.list;
} else if (res.templateContent) {
//
//
try {
const parsed = typeof res.templateContent === 'string'
? JSON.parse(res.templateContent)
: res.templateContent;
if (Array.isArray(parsed)) {
checkContentList.value = parsed;
}
} catch (e) {
console.warn('模板内容解析失败:', e);
}
}
//
if (res.workContent && !formData.value.workContent) {
formData.value.workContent = res.workContent;
}
if (res.templateName && !formData.value.templateName) {
formData.value.templateName = res.templateName;
}
}
} catch (error) {
console.error('加载风险管控卡模板详情失败:', error);
uni.showToast({
title: '加载模板详情失败',
icon: 'none'
});
}
}
// templateId
watch(() => props.templateId, (newTemplateId) => {
if (newTemplateId) {
loadTemplateDetail(newTemplateId);
}
}, { immediate: true });
//
const loadUserInfo = async () => {
try {
const res = await getUserInfo();
console.log('用户信息:', res);
// deptName
if (res && res.user && res.user.dept && res.user.dept.deptName) {
formData.value.unitName = res.user.dept.deptName;
}
} catch (error) {
console.error('获取用户信息失败:', error);
uni.showToast({
title: '获取用户信息失败',
icon: 'none'
});
}
}
//
onMounted(() => {
// allData
if (props.allData?.RiskControl && Object.keys(props.allData.RiskControl).length > 0) {
restoreFormData(props.allData.RiskControl);
}
// ID
if (props.templateId) {
loadTemplateDetail(props.templateId);
}
//
loadUserInfo();
});
//
const confirmWorkResponsiblePicker = (e) => {
formData.value.workResponsible = e.value[0].name ? e.value[0].name : workResponsibleList.value[0].name;
showWorkResponsiblePicker.value = false;
}
//
const afterRead = async (e) => {
console.log('上传的文件:', e);
let fileInfo = e.file[0];
//
if (fileInfo.size > 10 * 1024 * 1024) {
uni.showToast({
title: '文件大小不能超过10MB',
icon: 'none'
});
return;
}
//
if (!fileInfo.name.match(/\.(jpg|png|pdf)$/i)) {
uni.showToast({
title: '请上传JPG,PNG,PDF格式的文件',
icon: 'none'
});
return;
}
// ID
const tempFid = generateGuid();
const tempFile = {
fid: tempFid,
name: fileInfo.name,
size: (fileInfo.size / 1024 / 1024).toFixed(2) + 'MB',
url: fileInfo.url,
uploading: true //
};
//
fileList1.value.push(tempFile);
try {
//
const uploadResult = await uploadFileApi(
fileInfo.url, //
'file', //
{}, //
{ loading: true, loadingText: '上传中...' }
);
//
// : { name: "", url: "URL" }
const serverUrl = uploadResult?.url || fileInfo.url;
const serverFileName = uploadResult?.name || fileInfo.name;
// 使使ID
const fileId = uploadResult?.name || tempFid;
//
const fileIndex = fileList1.value.findIndex(item => item.fid === tempFid);
if (fileIndex !== -1) {
fileList1.value[fileIndex] = {
fid: fileId,
name: serverFileName, // 使
size: (fileInfo.size / 1024 / 1024).toFixed(2) + 'MB',
url: serverUrl, // 使URL
uploading: false
};
}
uni.showToast({
title: '上传成功',
icon: 'success',
duration: 1500
});
} catch (error) {
console.error('文件上传失败:', error);
//
fileList1.value = fileList1.value.filter(item => item.fid !== tempFid);
uni.showToast({
title: '上传失败,请重试',
icon: 'none',
duration: 2000
});
}
}
//
const deletePic = (fid) => {
console.log('删除文件:', fid);
let arr = [...fileList1.value];
arr = arr.filter(item => item.fid !== fid);
fileList1.value = arr;
}
</script>
<style lang="scss" scoped>
.H2Box {
font-size: 34rpx;
font-weight: bold;
margin-top: 20rpx;
}
.BlueTxt {
font-size: 28rpx;
color: #2979ff;
margin: 10rpx 0;
}
.addBtn {
font-size: 30rpx;
color: #2979ff;
font-weight: bold;
}
.grayFont {
font-size: 28rpx;
color: #909399;
}
.BorderBox {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
}
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.MainBox {
padding: 20rpx;
background-color: #fff;
height: 100%;
overflow: auto;
}
.mustBox::after {
content: "*";
color: red;
font-size: 30rpx;
margin-left: 10rpx;
}
.FormBox {
height: 100%;
.FormItem {
padding: 20rpx;
background-color: #fff;
.FormLableBox {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.FormValueBox {
font-size: 30rpx;
margin-top: 20rpx;
}
}
}
.CardBox {
height: auto;
padding: 20rpx;
overflow: hidden;
background-color: #fff;
border-radius: 10rpx;
margin-top: 20rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.CardBoxItem {
padding: 20rpx;
font-size: 30rpx;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
}
}
.UploadBox {
width: 100%;
height: 300rpx;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20rpx;
padding: 20rpx;
border: 1rpx dashed #e4e7ed;
border-radius: 10rpx;
.UpTitle {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.UpDesc {
font-size: 24rpx;
color: #666;
}
}
.UpFileListBox {
margin-top: 20rpx;
height: auto;
.UpFileItem {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
display: flex;
flex-direction: column;
gap: 20rpx;
.UpFileName {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.UpFileSize {
font-size: 24rpx;
color: #666;
}
.UpFileStatus {
font-size: 24rpx;
color: #2979ff;
margin-top: 10rpx;
}
}
}
</style>

View File

@ -0,0 +1,829 @@
<!-- 工作票 -->
<template>
<view class="MainBox">
<view class="FormBox">
<view class="FormItem">
<view class="FormLableBox mustBox">票证编号</view>
<view class="FormValueBox">
<u-input v-model="formData.ticketNumber" placeholder="请输入票证编号" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">所属项目</view>
<view class="FormValueBox">
<u-input v-model="formData.projectName" placeholder="请输入所属项目" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业地点</view>
<view class="FormValueBox">
<u-input v-model="formData.workLocation" placeholder="请输入作业地点" readonly></u-input>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业负责人</view>
<view class="FormValueBox">
<u-input v-model="formData.supervisorName" placeholder="请输入作业负责人" readonly></u-input>
</view>
</view>
<view class="H2Box">核心信息</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业内容</view>
<view class="FormValueBox">
<u-textarea v-model="formData.workContent" placeholder="请输入作业内容" readonly></u-textarea>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox FlexBox">
<view class="mustBox">风险类型</view>
<view class="addBtn" @click="showPicker = true">选择</view>
</view>
<view class="FormValueBox">
<view class="BorderBox">{{ formData.riskType }}</view>
<u-picker v-model="formData.riskType" :columns="[riskTypeList]" :show="showPicker" keyName="name"
@confirm="confirmRiskTypePicker" @cancel="showPicker = false"></u-picker>
</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业起止时间</view>
<view class="FormValueBox">
<view class="date-range-box">
<view class="date-item">
<u-input v-model="startDateText" placeholder="开始日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showStartDatePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showStartDatePicker" v-model="startDate" mode="date"
:minDate="minTimestamp" @confirm="HandleStartDateConfirm"
@cancel="showStartDatePicker = false"></up-datetime-picker>
</view>
<view class="date-separator"></view>
<view class="date-item">
<u-input v-model="endDateText" placeholder="结束日期" readonly>
<template #suffix>
<u-button type="primary" size="small"
@click="showEndDatePicker = true">选择日期</u-button>
</template>
</u-input>
<up-datetime-picker :show="showEndDatePicker" v-model="endDate" mode="date"
:minDate="minTimestamp" @confirm="HandleEndDateConfirm"
@cancel="showEndDatePicker = false"></up-datetime-picker>
</view>
</view>
</view>
</view>
<view class="H2Box">安全措施</view>
<view class="FormItem">
<view class="FormLableBox mustBox">通用安全措施</view>
<view class="FormValueBox">
<up-checkbox-group v-model="checkboxValue1" placement="column" @change="checkboxChange">
<up-checkbox :customStyle="{ marginBottom: '8px' }" v-for="(item, index) in safeMeasureList"
:key="item.id" :label="item.name" :name="item.name">
</up-checkbox>
</up-checkbox-group>
</view>
<view class="FlexBox" v-if="showMeasureBox">
<u-input v-model="customSafeMeasure" placeholder="请输入自定义安全措施"></u-input>
<view class="FlexBox">
<u-button type="info" size="small" @click="showMeasureBox = false">取消</u-button>
<u-button type="primary" size="small" @click="addCustomSafeMeasure">确认</u-button>
</view>
</view>
<view class="BlueTxt" @click="showMeasureBox = true">添加自定义措施</view>
</view>
<view class="FormItem">
<view class="FormLableBox mustBox">作业班成员资质确认</view>
<view class="FormValueBox">
<view class="MemberBox">
<view class="MemberItem" v-for="(item, index) in memberList" :key="item.id">
<view class="FlexBox">
<view class="MemberName">{{ item.name }}</view>
<view class="">
<u-tag type="primary" v-if="item.status == '持证'">{{ item.status }}</u-tag>
<u-tag type="warning" plain v-else>{{ item.status }}</u-tag>
</view>
</view>
<view class="FlexBox">
<u-checkbox v-model="item.checked" label="已经确认特种作业资格"></u-checkbox>
</view>
</view>
</view>
</view>
</view>
<view class="H2Box" v-if="isHighRiskType">高风险作业专项</view>
<view class="FormItem" v-if="isHighRiskType">
<view class="FormLableBox FlexBox">
<view class="mustBox">风险管控卡模板</view>
<view class="addBtn" @click="showTemplatePicker = true">选择</view>
</view>
<view class="FormValueBox">
<view class="BorderBox">
<view class="grayFont" v-if="!selectedTemplate">请选择风险管控卡模板</view>
<view v-else>{{ getTemplateDisplayName(selectedTemplate) }}</view>
</view>
<u-picker v-model="selectedTemplate" :columns="[riskCardTemplateList]" :show="showTemplatePicker"
keyName="name" @confirm="confirmTemplatePicker" @cancel="showTemplatePicker = false"></u-picker>
</view>
</view>
<view class="FormItem" v-if="isHighRiskType">
<view class="FormLableBox FlexBox">
<view class="mustBox">是否需要申领移动球机</view>
<view class="FlexBox">
<span></span>
<up-switch v-model="needApplyBallMachine" @change="change"></up-switch>
<span></span>
</view>
</view>
<view class="FormValueBox">
<u-button type="primary" :disabled="!needApplyBallMachine" @click="openApplyForMachine">申领移动球机</u-button>
</view>
</view>
</view>
</view>
</template>·
<script setup>
import { generateGuid } from '@/utils/index.js';
import dayjs from 'dayjs';
import { ref, onMounted, watch, defineExpose, computed, nextTick } from 'vue'
import { getRiskCardTemplateList } from '@/api/modules/workTicket'
// props
const props = defineProps({
allData: {
type: Object,
default: () => { },
},
openApplyForMachine: {
type: Function,
default: () => { },
},
})
//
const riskTypeList = ref([
{
id: 1,
name: '电气作业-低等风险',
value: '1',
},
{
id: 2,
name: '电气作业-中等风险',
value: '2',
},
{
id: 3,
name: '电气作业-高等风险',
value: '3',
},
])
//
const safeMeasureList = ref([
{
id: 1,
name: '断电并挂牌上锁',
},
{
id: 2,
name: '验电确认无电压',
},
{
id: 3,
name: '设置临时接地线',
},
{
id: 4,
name: '穿戴绝缘手套和防护服',
},
{
id: 5,
name: '安排专人监护',
},
])
const showMeasureBox = ref(false); //
const customSafeMeasure = ref(''); //
//
const memberList = ref([])
//
const updateMemberListFromBasicsInfo = (preserveCheckedState = true) => {
const basicsInfo = props.allData?.BasicsInfo;
if (basicsInfo && basicsInfo.MemberList && Array.isArray(basicsInfo.MemberList) && basicsInfo.MemberList.length > 0) {
//
const savedCheckedList = props.allData?.WorkNote?.memberCheckedList || [];
// preserveCheckedState true memberList
const existingCheckedMap = new Map();
if (preserveCheckedState && memberList.value.length > 0) {
memberList.value.forEach(member => {
existingCheckedMap.set(member.id, member.checked);
});
}
memberList.value = basicsInfo.MemberList.map(member => {
// 使
let checked = false;
// 使
const savedMember = savedCheckedList.find(m =>
(m.id == member.id) || (m.memberId == member.id)
);
if (savedMember !== undefined) {
// checked 使 true
checked = savedMember.checked === true || savedMember.checked === 'true' || savedMember.checked === 1;
} else if (preserveCheckedState && existingCheckedMap.has(member.id)) {
// 使
checked = existingCheckedMap.get(member.id);
}
return {
id: member.id,
name: member.name,
status: member.status || '持证', //
checked: checked
};
});
} else if (props.allData?.WorkNote?.memberCheckedList && Array.isArray(props.allData.WorkNote.memberCheckedList) && props.allData.WorkNote.memberCheckedList.length > 0) {
// workTicketId
// 使 memberCheckedListqualificationList
const savedCheckedList = props.allData.WorkNote.memberCheckedList || []
// memberList > savedCheckedList
const existingCheckedMap = new Map();
if (preserveCheckedState && memberList.value.length > 0) {
memberList.value.forEach(member => {
existingCheckedMap.set(member.id, member.checked);
});
}
memberList.value = savedCheckedList.map((m) => {
const id = m.id || m.memberId || m.userId
const name = m.name || m.memberName || m.userName
let checked = false
if (m.checked !== undefined) {
checked = m.checked === true || m.checked === 'true' || m.checked === 1
} else if (preserveCheckedState && existingCheckedMap.has(id)) {
checked = existingCheckedMap.get(id)
} else {
//
checked = true
}
return {
id,
name,
status: m.status || '持证',
checked
}
})
} else {
// 使
//
const existingCheckedMap = new Map();
if (preserveCheckedState && memberList.value.length > 0) {
memberList.value.forEach(member => {
existingCheckedMap.set(member.id, member.checked);
});
}
//
const savedCheckedList = props.allData?.WorkNote?.memberCheckedList || [];
memberList.value = [
{
id: 1,
name: '张三',
status: '持证',
checked: (() => {
const saved = savedCheckedList.find(m => (m.id == 1) || (m.memberId == 1));
if (saved !== undefined) {
return saved.checked === true || saved.checked === 'true' || saved.checked === 1;
}
return preserveCheckedState && existingCheckedMap.has(1) ? existingCheckedMap.get(1) : false;
})(),
},
{
id: 2,
name: '李四',
status: '未持证',
checked: (() => {
const saved = savedCheckedList.find(m => (m.id == 2) || (m.memberId == 2));
if (saved !== undefined) {
return saved.checked === true || saved.checked === 'true' || saved.checked === 1;
}
return preserveCheckedState && existingCheckedMap.has(2) ? existingCheckedMap.get(2) : false;
})(),
},
{
id: 3,
name: '王五',
status: '持证',
checked: (() => {
const saved = savedCheckedList.find(m => (m.id == 3) || (m.memberId == 3));
if (saved !== undefined) {
return saved.checked === true || saved.checked === 'true' || saved.checked === 1;
}
return preserveCheckedState && existingCheckedMap.has(3) ? existingCheckedMap.get(3) : false;
})(),
},
];
}
}
//
const needApplyBallMachine = ref(false);
//
const isHighRiskType = computed(() => {
return formData.value.riskType && formData.value.riskType.includes('高等风险');
});
//
const showTemplatePicker = ref(false); //
const riskCardTemplateList = ref([]); //
const selectedTemplate = ref(null); //
const selectedTemplateId = ref(null); // ID
//
const checkboxValue1 = ref([]);
//
const checkboxChange = (e) => {
console.log('安全措施选择变化:', e);
};
//
const formData = ref({
ticketNumber: '', //
projectName: '', //
workLocation: '', //
supervisorName: '', //
supervisorId: '', // ID
workContent: '', //
riskType: '', //
})
//
const showPicker = ref(false);
const showStartDatePicker = ref(false); //
const showEndDatePicker = ref(false); //
const startDate = ref(null); //
const endDate = ref(null); //
const startDateText = ref(''); //
const endDateText = ref(''); //
const minTimestamp = ref(dayjs('2020-01-01').valueOf()); //
// allData
const restoreFormData = (data) => {
if (!data || Object.keys(data).length === 0) return;
if (data.ticketNumber) formData.value.ticketNumber = data.ticketNumber;
if (data.projectName) formData.value.projectName = data.projectName;
if (data.workLocation) formData.value.workLocation = data.workLocation;
if (data.supervisorName) formData.value.supervisorName = data.supervisorName;
if (data.supervisorId) formData.value.supervisorId = data.supervisorId;
if (data.workContent) formData.value.workContent = data.workContent;
if (data.riskType) formData.value.riskType = data.riskType;
if (data.period && Array.isArray(data.period) && data.period.length >= 2) {
if (data.period[0]) {
startDateText.value = data.period[0];
startDate.value = dayjs(data.period[0]).valueOf();
}
if (data.period[1]) {
endDateText.value = data.period[1];
endDate.value = dayjs(data.period[1]).valueOf();
}
}
//
if (data.safeMeasures && Array.isArray(data.safeMeasures)) {
checkboxValue1.value = [...data.safeMeasures];
}
if (data.needApplyBallMachine !== undefined) {
needApplyBallMachine.value = data.needApplyBallMachine;
}
//
//
// updateMemberListFromBasicsInfo allData.WorkNote.memberCheckedList
updateMemberListFromBasicsInfo(true);
// data.memberCheckedList
if (data.memberCheckedList && Array.isArray(data.memberCheckedList) && data.memberCheckedList.length > 0) {
// ID
data.memberCheckedList.forEach(checkedMember => {
const member = memberList.value.find(m =>
(m.id == checkedMember.id) || (m.id == checkedMember.memberId)
);
if (member) {
// checked true true
member.checked = checkedMember.checked === true || checkedMember.checked === 'true' || checkedMember.checked === 1;
}
});
}
//
if (data.riskCardTemplateId) {
selectedTemplateId.value = data.riskCardTemplateId;
//
if (riskCardTemplateList.value.length > 0) {
restoreSelectedTemplate();
}
}
};
//
const restoreSelectedTemplate = () => {
if (selectedTemplateId.value && riskCardTemplateList.value.length > 0) {
const template = riskCardTemplateList.value.find(t =>
(t.id || t.templateId || t.template_id) == selectedTemplateId.value
);
if (template) {
selectedTemplate.value = template;
}
}
};
// allData.WorkNote
watch(() => props.allData?.WorkNote, (newData) => {
if (newData && Object.keys(newData).length > 0) {
restoreFormData(newData);
}
}, { immediate: true, deep: true });
//
defineExpose({
getFormData() {
//
const memberCheckedList = memberList.value.map(member => ({
id: member.id,
name: member.name,
checked: member.checked || false
}));
return {
...formData.value,
period: formData.value.period || [startDateText.value, endDateText.value],
safeMeasures: checkboxValue1.value,
needApplyBallMachine: needApplyBallMachine.value,
riskCardTemplateId: selectedTemplateId.value, // ID
memberCheckedList: memberCheckedList //
};
}
})
//
const initFromBasicsInfo = () => {
const basicsInfo = props.allData?.BasicsInfo;
if (basicsInfo) {
if (basicsInfo.ProjectName && !formData.value.projectName) {
formData.value.projectName = basicsInfo.ProjectName;
}
if (basicsInfo.SpecificAddress && !formData.value.workLocation) {
formData.value.workLocation = basicsInfo.SpecificAddress;
}
if (basicsInfo.supervisorName && !formData.value.supervisorName) {
formData.value.supervisorName = basicsInfo.supervisorName;
}
if (basicsInfo.supervisorId && !formData.value.supervisorId) {
formData.value.supervisorId = basicsInfo.supervisorId;
}
//
updateMemberListFromBasicsInfo(true);
}
};
//
watch(() => props.allData?.BasicsInfo, (newData) => {
if (newData && Object.keys(newData).length > 0) {
initFromBasicsInfo();
}
}, { immediate: true, deep: true });
//
const generateticketNumber = () => {
//
if (formData.value.ticketNumber) {
return;
}
// YYYYMM
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const yearMonth = `${year}${month}`;
// 40001-9999
const randomNum = Math.floor(Math.random() * 9999) + 1;
const serialNum = String(randomNum).padStart(4, '0');
// HQ-GZP-YYYYMM-XXXX
formData.value.ticketNumber = `HQ-GZP-${yearMonth}-${serialNum}`;
};
//
onMounted(() => {
//
initFromBasicsInfo();
// allData
if (props.allData?.WorkNote && Object.keys(props.allData.WorkNote).length > 0) {
restoreFormData(props.allData.WorkNote);
}
//
if (!formData.value.ticketNumber) {
generateticketNumber();
}
//
if (memberList.value.length === 0) {
updateMemberListFromBasicsInfo(true);
}
// 使 nextTick DOM
nextTick(() => {
if (props.allData?.WorkNote?.memberCheckedList && Array.isArray(props.allData.WorkNote.memberCheckedList)) {
props.allData.WorkNote.memberCheckedList.forEach(checkedMember => {
const member = memberList.value.find(m =>
(m.id == checkedMember.id) || (m.id == checkedMember.memberId)
);
if (member) {
member.checked = checkedMember.checked === true || checkedMember.checked === 'true' || checkedMember.checked === 1;
}
});
}
});
});
//
const confirmRiskTypePicker = (e) => {
formData.value.riskType = e.value[0].name;
showPicker.value = false;
}
//
const HandleStartDateConfirm = (e) => {
console.log('选中的开始日期:', e);
// 使
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
startDateText.value = dayjs(selectedDate).format('YYYY-MM-DD');
startDate.value = selectedDate;
endDateText.value = '';
endDate.value = null;
showStartDatePicker.value = false;
UpdatePeriod();
};
//
const HandleEndDateConfirm = (e) => {
console.log('选中的结束日期:', e);
// 使
const selectedDate = (e.value && dayjs(e.value).isValid()) ? e.value : minTimestamp.value;
//
if (startDate.value && dayjs(selectedDate).isBefore(dayjs(startDate.value))) {
uni.showToast({
title: '结束日期不能小于开始日期',
icon: 'none',
duration: 2000
});
return;
}
endDateText.value = dayjs(selectedDate).format('YYYY-MM-DD');
endDate.value = selectedDate;
showEndDatePicker.value = false;
UpdatePeriod();
};
//
const UpdatePeriod = () => {
if (startDateText.value && endDateText.value) {
formData.value.period = [startDateText.value, endDateText.value];
} else if (startDateText.value) {
formData.value.period = [startDateText.value, ''];
} else if (endDateText.value) {
formData.value.period = ['', endDateText.value];
} else {
formData.value.period = [];
}
console.log('作业周期:', formData.value.period);
};
//
const addCustomSafeMeasure = () => {
if (customSafeMeasure.value.trim() !== '') {
safeMeasureList.value.push({
id: generateGuid(8),
name: customSafeMeasure.value,
});
customSafeMeasure.value = '';
showMeasureBox.value = false;
}
}
//
const change = (e) => {
console.log('移动球机开关状态变化:', e);
}
//
const loadRiskCardTemplateList = async () => {
try {
const res = await getRiskCardTemplateList({status: '1'});
//
let templateList = [];
if (Array.isArray(res)) {
templateList = res;
} else if (res && Array.isArray(res.list)) {
templateList = res.list;
} else if (res && Array.isArray(res.rows)) {
templateList = res.rows;
} else if (res && Array.isArray(res.data)) {
templateList = res.data;
}
// name
riskCardTemplateList.value = templateList.map(template => ({
...template,
name: template.templateName || template.name || template.title || `模板${template.id || template.templateId || ''}`
}));
console.log('风险管控卡模板列表:', riskCardTemplateList.value);
//
restoreSelectedTemplate();
} catch (error) {
console.error('加载风险管控卡模板列表失败:', error);
uni.showToast({
title: '加载模板列表失败',
icon: 'none'
});
}
}
//
const getTemplateDisplayName = (template) => {
if (!template) return '';
return template.templateName || template.name || template.title || `模板${template.id || template.templateId || ''}`;
}
//
const confirmTemplatePicker = (e) => {
if (e.value && e.value.length > 0) {
const template = e.value[0];
selectedTemplate.value = template;
// ID
selectedTemplateId.value = template.id || template.templateId || template.template_id;
console.log('选中的模板:', template, '模板ID', selectedTemplateId.value);
}
showTemplatePicker.value = false;
}
//
watch(() => formData.value.riskType, (newRiskType) => {
if (newRiskType && newRiskType.includes('高等风险')) {
//
if (riskCardTemplateList.value.length === 0) {
loadRiskCardTemplateList().then(() => {
//
restoreSelectedTemplate();
});
} else {
//
restoreSelectedTemplate();
}
} else {
//
selectedTemplate.value = null;
selectedTemplateId.value = null;
}
}, { immediate: true });
//
watch(() => riskCardTemplateList.value, (newList) => {
if (newList.length > 0 && selectedTemplateId.value) {
restoreSelectedTemplate();
}
}, { deep: true });
</script>
<style lang="scss" scoped>
.BlueTxt {
font-size: 28rpx;
color: #2979ff;
margin: 10rpx 0;
}
.addBtn {
font-size: 30rpx;
color: #2979ff;
font-weight: bold;
}
.BorderBox {
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
}
.grayFont {
font-size: 28rpx;
color: #909399;
}
.FlexBox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.MainBox {
padding: 20rpx;
background-color: #fff;
height: 100%;
overflow: auto;
}
.mustBox::after {
content: "*";
color: red;
font-size: 30rpx;
margin-left: 10rpx;
}
.FormBox {
// background-color: #f5f5f5;
height: 100%;
.FormItem {
padding: 20rpx;
background-color: #fff;
.FormLableBox {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.FormValueBox {
font-size: 30rpx;
margin-top: 20rpx;
}
}
}
.MemberBox{
display: flex;
flex-direction: column;
gap: 20rpx;
.MemberItem{
border: 1rpx solid #e4e7ed;
padding: 20rpx;
border-radius: 10rpx;
.MemberName{
font-size: 30rpx;
}
}
}
.date-range-box {
display: flex;
align-items: center;
gap: 20rpx;
flex: 1;
.date-item {
flex: 1;
}
.date-separator {
color: #999;
font-size: 28rpx;
}
}
.H2Box {
font-size: 34rpx;
font-weight: bold;
margin-top: 20rpx;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
<template>
<div>这是一个演示页面</div>
</template>

View File

@ -1,12 +0,0 @@
<template>
<div class="container">首页</div>
</template>
<style lang="scss" scoped>
.container {
font-size: 80rpx;
font-weight: bold;
color: #2979ff;
margin-bottom: 20rpx;
}
</style>

View File

@ -1,3 +1,4 @@
<!-- 登录页 -->
<template>
<view class="login-page">
<view class="login-container">
@ -34,6 +35,8 @@
<script setup>
import { ref, reactive } from 'vue'
import { login } from '@/api'
import { useUserStore } from '@/store/user'
const formData = reactive({
username: '',
@ -47,14 +50,54 @@ const roleList = ref([
])
const loading = ref(false)
const userStore = useUserStore()
async function handleLogin() {
//
if (!formData.username) {
uni.showToast({ title: '请输入账号', icon: 'none' })
return
}
if (!formData.password) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
if (!formData.role) {
uni.showToast({ title: '请选择角色', icon: 'none' })
return
}
loading.value = true
try {
//
//
const response = await login({
username: formData.username,
password: formData.password,
role: formData.role
})
// token
if (response) {
userStore.setUserInfo(response)
if (response.access_token) {
userStore.setToken(response.access_token)
}
if (response.refreshToken) {
userStore.setRefreshToken(response.refreshToken)
}
}
uni.showToast({ title: '登录成功', icon: 'success' })
//
setTimeout(() => {
uni.switchTab({ url: '/pages/WorkOrderApproval/index' })
}, 1500)
} catch (error) {
console.error('登录失败:', error)
uni.showToast({
title: error?.message || '登录失败,请重试',
icon: 'none'
})
} finally {
loading.value = false
}

View File

@ -1,3 +0,0 @@
<template>
<div>注册</div>
</template>

View File

@ -1,3 +1,115 @@
<!-- 个人中心 -->
<template>
<div>用户中心</div>
<view class="PageBox">
<view class="CardBox">
<view class="imgBox">
<u-image src="https://img95.699pic.com/photo/60054/8564.jpg_wh300.jpg" shape="circle" width="100rpx"
height="100rpx" />
</view>
<view class="usrInfoBox">
<view class="FlexBox Title">{{ userInfo.name }}-{{ userInfo.position }} </view>
<view class="FlexBox">所属单位{{ userInfo.unit }} </view>
<view class="FlexBox">联系方式{{ userInfo.phone }} </view>
</view>
</view>
<view class="CardBox">
<u-icon name="bell-fill" size="32rpx" color="#0088FF" />
<view class="TxtBox">消息中心</view>
</view>
<!-- <view class="CardBox" @click="toArchivesRoom">
<u-icon name="file-text-fill" size="32rpx" color="#0088FF" />
<view class="TxtBox">项目存档</view>
</view> -->
<view class="CardBox">
<u-icon name="arrow-leftward" size="32rpx" color="#0088FF" />
<view class="TxtBox" @click="logout">退出登录</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const userInfo = ref({
id: '123456', // ID
name: '张三', //
phone: '13800138000', //
email: 'zhangsan@example.com', //
position: '工程师', //
unit: '中国建筑集团有限公司', //
})
//
const toArchivesRoom = () => {
uni.navigateTo({
url: '/pages/ArchivesRoom/index',
})
}
// 退
const logout = () => {
uni.showModal({
title: '提示',
content: '确定退出登录吗?',
success: (res) => {
if (res.confirm) {
// 退
uni.removeStorageSync('token')
uni.showToast({
title: '退出登录成功',
icon: 'success',
})
uni.reLaunch({
url: '/pages/login/index',
})
}
}
})
}
//
</script>
<style lang="scss" scoped>
.FlexBox {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
}
.Title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.PageBox {
background-color: #fff;
height: calc(100vh - 120rpx);
overflow: hidden;
padding: 20rpx;
box-sizing: border-box;
.CardBox {
width: 100%;
padding: 20rpx;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
background-color: #F9FAFB;
border-radius: 10rpx;
margin-bottom: 40rpx;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.2);
}
.imgBox {
width: 100rpx;
height: 100rpx;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,3 +1,15 @@
export * from './storage'
export * from './validate'
export * from './format'
// 生成指定长度的唯一标识符
export function generateGuid(length = 8) {
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
let guid = '';
for (let i = 0; i < length; i++) {
let randomIndex = Math.floor(Math.random() * arr.length);
guid += arr[randomIndex];
}
return guid;
}

View File

@ -22,5 +22,17 @@ export default defineConfig({
},
optimizeDeps: {
include: ['uview-plus']
},
// H5 开发环境代理配置,解决跨域问题
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://172.16.1.146:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
secure: false
}
}
}
})