基本功能都已完成

This commit is contained in:
王利强
2026-02-08 09:30:43 +08:00
parent 1ad538f351
commit 721ef0ad54
494 changed files with 6837 additions and 42302 deletions

View File

@@ -1,23 +1,60 @@
<template>
<view class="page padding">
<view class="text-gray text-center" v-if="!hasData">暂无证件照记录</view>
<button class="cuIcon-add bg-blue round margin-top-xl" @click="showAddPopup = true">新增</button>
<!-- 证照列表 -->
<view class="license-list" v-if="licenseList.length > 0">
<view class="license-item" v-for="(item, index) in licenseList" :key="item.id">
<view class="license-info">
<view class="license-header">
<text class="license-type">{{ item.type || '未知类型' }}</text>
<view class="license-actions">
<text class="action-btn text-blue" @click="handleEdit(item)">编辑</text>
<text class="action-btn text-red" @click="handleDelete(item)">删除</text>
</view>
</view>
<view class="license-detail">
<view class="detail-row">
<text class="label">证件编号</text>
<text class="value">{{ item.code || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">有效期</text>
<text class="value">{{ item.startDate || '-' }} {{ item.endDate || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">法人</text>
<text class="value">{{ item.legalPerson || '-' }}</text>
</view>
</view>
<view class="license-photo" v-if="item.photo">
<image :src="getImageUrl(item.photo)" mode="aspectFill" @click="previewImage(item.photo)"></image>
</view>
</view>
</view>
</view>
<!-- 新增证件照弹窗 -->
<u-popup :show="showAddPopup" mode="center" round="20" @close="showAddPopup = false">
<!-- 空状态 -->
<view class="empty-state" v-else>
<text class="text-gray text-center">暂无证照记录</text>
</view>
<!-- 新增按钮 -->
<button class="add-btn cuIcon-add bg-blue round" @click="openAddPopup">+ 新增</button>
<!-- 新增/编辑证照弹窗 -->
<u-popup :show="showAddPopup" mode="center" round="20" @close="closePopup">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title text-bold">新增证照</view>
<view class="popup-close" @click="showAddPopup = false">×</view>
<view class="popup-title text-bold">{{ isEdit ? '编辑证照' : '新增证照' }}</view>
<view class="popup-close" @click="closePopup">×</view>
</view>
<view class="popup-body">
<scroll-view class="popup-body" scroll-y>
<!-- 部门 -->
<view class="form-item">
<view class="form-label">部门<text class="text-red">*</text></view>
<view class="form-input form-select" @click="showDeptPopup = true">
<text :class="formData.dept ? '' : 'text-gray'">
{{ formData.dept || '请选择部门' }}
<text :class="formData.enterpriseName ? '' : 'text-gray'">
{{ formData.enterpriseName || '请选择部门' }}
</text>
</view>
</view>
@@ -25,49 +62,60 @@
<!-- 证件类型 -->
<view class="form-item">
<view class="form-label">证件类型</view>
<input class="form-input" v-model="formData.idType" placeholder="请输入证件类型" />
<input class="form-input" v-model="formData.type" placeholder="请输入证件类型" />
</view>
<!-- 证件编号 -->
<view class="form-item">
<view class="form-label">证件编号</view>
<input class="form-input" v-model="formData.number" placeholder="请输入证件编号" />
<input class="form-input" v-model="formData.code" placeholder="请输入证件编号" />
</view>
<!-- 开始日期 -->
<view class="form-item">
<view class="form-label">开始日期</view>
<view class="form-input form-select" @click="showDatePicker = true">
<text :class="formData.expireDate ? '' : 'text-gray'">
{{ formData.expireDate || '请选择开始时间' }}
</text>
</view>
</view>
<!-- 结束日期 -->
<view class="form-item">
<view class="form-label">结束日期</view>
<view class="form-input form-select" @click="showDatePicker = true">
<text :class="formData.expireDate ? '' : 'text-gray'">
{{ formData.expireDate || '请选择结束时间' }}
<view class="form-input form-select" @click="openDatePicker('start')">
<text :class="formData.startDate ? '' : 'text-gray'">
{{ formData.startDate || '请选择开始日期' }}
</text>
</view>
</view>
<!-- 上传证件照 -->
<!-- 结束日期 -->
<view class="form-item">
<view class="form-label">结束日期</view>
<view class="form-input form-select" @click="openDatePicker('end')">
<text :class="formData.endDate ? '' : 'text-gray'">
{{ formData.endDate || '请选择结束日期' }}
</text>
</view>
</view>
<!-- 法人 -->
<view class="form-item">
<view class="form-label">法人</view>
<input class="form-input" v-model="formData.legalPerson" placeholder="请输入法人" />
</view>
<!-- 封面图片 -->
<view class="form-item">
<view class="form-label">封面图片</view>
<view class="upload-box" @click="chooseImage">
<view class="upload-add" v-if="!formData.image">
<view class="upload-add" v-if="!formData.photoPreview">
<text class="upload-icon">+</text>
<text class="upload-text">上传照片</text>
</view>
<image v-else :src="formData.image" mode="aspectFill" class="upload-img"></image>
<view class="upload-preview" v-else>
<image :src="formData.photoPreview" mode="aspectFill" class="upload-img"></image>
<view class="upload-delete" @click.stop="removeImage">×</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showAddPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleSubmit">确定</button>
<button class="btn-cancel" @click="closePopup">取消</button>
<button class="btn-confirm bg-blue" @click="handleSubmit" :loading="submitting">确定</button>
</view>
</view>
</u-popup>
@@ -75,9 +123,11 @@
<!-- 日期选择器 -->
<u-datetime-picker
:show="showDatePicker"
v-model="datePickerValue"
mode="date"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
@cancel="onDateCancel"
@close="onDateCancel"
></u-datetime-picker>
<!-- 选择部门弹窗 -->
@@ -88,60 +138,289 @@
<view class="popup-close" @click="showDeptPopup = false">×</view>
</view>
<view class="dept-list">
<scroll-view class="dept-list" scroll-y v-if="deptList.length > 0">
<view
class="dept-item"
v-for="(item, index) in deptList"
:key="index"
v-for="item in deptList"
:key="item.id"
@click="selectedDept = item"
>
<view class="dept-checkbox" :class="{ 'dept-checkbox-active': selectedDept === item }">
<text v-if="selectedDept === item"></text>
<view class="dept-checkbox" :class="{ 'dept-checkbox-active': selectedDept && selectedDept.id === item.id }">
<text v-if="selectedDept && selectedDept.id === item.id"></text>
</view>
<text class="dept-name">{{ item }}</text>
<text class="dept-name">{{ item.name }}</text>
</view>
</scroll-view>
<view class="text-gray text-center padding" v-else>
暂无部门数据
</view>
<button class="btn-dept-confirm bg-blue" @click="confirmDept">确定</button>
</view>
</u-popup>
<!-- 删除确认弹窗 -->
<u-modal
:show="showDeleteModal"
title="确认删除"
content="确定要删除这条证照记录吗?"
showCancelButton
@confirm="confirmDelete"
@cancel="showDeleteModal = false"
></u-modal>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import {
getLicenseEnterpriseSelect,
getLicenseList,
getLicenseDetail,
addLicense,
updateLicense,
deleteLicense
} from '@/request/three_one_api/license.js';
import { baseUrl, getToken } from '@/request/request.js';
const hasData = ref(false);
// 证照列表
const licenseList = ref([]);
// 部门列表
const deptList = ref([]);
const selectedDept = ref(null);
// 弹窗控制
const showAddPopup = ref(false);
const showDatePicker = ref(false);
const showDeptPopup = ref(false);
const showDeleteModal = ref(false);
// 部门列表
const deptList = ref([
'湘西自治州和谐网络科技有限公司',
'湘西自治州和谐云大数据科技有限公司',
'湘西网络有限公司'
]);
const selectedDept = ref('');
// 编辑状态
const isEdit = ref(false);
const currentEditId = ref(null);
const currentDeleteItem = ref(null);
const submitting = ref(false);
// 日期选择类型
const datePickerType = ref('start');
// 日期选择器的值(时间戳)
const datePickerValue = ref(Date.now());
// 表单数据
const formData = reactive({
dept: '',
enterpriseId: '',
enterpriseName: '',
type: '',
idType: '',
number: '',
code: '',
startDate: '',
expireDate: '',
image: ''
endDate: '',
legalPerson: '',
photo: '',
photoPreview: ''
});
// 页面加载
onMounted(() => {
loadLicenseList();
loadDeptList();
});
// 获取图片完整URL
const getImageUrl = (path) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return baseUrl + path;
};
// 预览图片
const previewImage = (path) => {
uni.previewImage({
urls: [getImageUrl(path)]
});
};
// 加载证照列表
const loadLicenseList = async () => {
try {
const res = await getLicenseList();
if (res.code === 0) {
licenseList.value = res.data.records || [];
}
} catch (err) {
console.error('获取证照列表失败:', err);
}
};
// 加载部门列表
const loadDeptList = async () => {
try {
const res = await getLicenseEnterpriseSelect();
if (res.code === 0) {
deptList.value = res.data || [];
}
} catch (err) {
console.error('获取部门列表失败:', err);
}
};
// 打开新增弹窗
const openAddPopup = () => {
resetForm();
isEdit.value = false;
currentEditId.value = null;
showAddPopup.value = true;
};
// 编辑证照
const handleEdit = async (item) => {
try {
// 先获取详情
const res = await getLicenseDetail({ id: item.id });
if (res.code === 0) {
const detail = res.data;
isEdit.value = true;
currentEditId.value = item.id;
// 填充表单数据
formData.enterpriseId = detail.enterpriseId || '';
formData.type = detail.type || '';
formData.code = detail.code || '';
formData.startDate = detail.startDate || '';
formData.endDate = detail.endDate || '';
formData.legalPerson = detail.legalPerson || '';
formData.photo = detail.photo || '';
formData.photoPreview = detail.photo ? getImageUrl(detail.photo) : '';
// 设置部门名称
const dept = deptList.value.find(d => d.id === detail.enterpriseId);
formData.enterpriseName = dept ? dept.name : '';
selectedDept.value = dept || null;
showAddPopup.value = true;
}
} catch (err) {
console.error('获取证照详情失败:', err);
uni.showToast({ title: '获取详情失败', icon: 'none' });
}
};
// 删除证照
const handleDelete = (item) => {
currentDeleteItem.value = item;
showDeleteModal.value = true;
};
// 确认删除
const confirmDelete = async () => {
if (!currentDeleteItem.value) return;
try {
const res = await deleteLicense({ id: currentDeleteItem.value.id });
if (res.code === 0) {
uni.showToast({ title: '删除成功', icon: 'success' });
loadLicenseList();
}
} catch (err) {
console.error('删除失败:', err);
uni.showToast({ title: '删除失败', icon: 'none' });
} finally {
showDeleteModal.value = false;
currentDeleteItem.value = null;
}
};
// 关闭弹窗
const closePopup = () => {
showAddPopup.value = false;
resetForm();
};
// 重置表单
const resetForm = () => {
formData.enterpriseId = '';
formData.enterpriseName = '';
formData.type = '';
formData.code = '';
formData.startDate = '';
formData.endDate = '';
formData.legalPerson = '';
formData.photo = '';
formData.photoPreview = '';
selectedDept.value = null;
isEdit.value = false;
currentEditId.value = null;
};
// 确认选择部门
const confirmDept = () => {
if (selectedDept.value) {
formData.dept = selectedDept.value;
formData.enterpriseId = selectedDept.value.id;
formData.enterpriseName = selectedDept.value.name;
}
showDeptPopup.value = false;
};
// 打开日期选择器
const openDatePicker = (type) => {
datePickerType.value = type;
// 根据已有的日期设置选择器初始值
let currentDate = '';
if (type === 'start' && formData.startDate) {
currentDate = formData.startDate;
} else if (type === 'end' && formData.endDate) {
currentDate = formData.endDate;
}
// 如果有已选择的日期,转换为时间戳
if (currentDate) {
datePickerValue.value = new Date(currentDate).getTime();
} else {
datePickerValue.value = Date.now();
}
showDatePicker.value = true;
};
// 日期选择取消/关闭
const onDateCancel = () => {
showDatePicker.value = false;
};
// 日期选择确认
const onDateConfirm = (e) => {
// 使用 v-model 绑定的 datePickerValue
const timestamp = datePickerValue.value;
// 验证时间戳
if (!timestamp || isNaN(timestamp)) {
console.error('无效的日期值:', timestamp);
showDatePicker.value = false;
return;
}
const date = new Date(timestamp);
// 验证日期是否有效
if (isNaN(date.getTime())) {
console.error('无效的日期:', date);
showDatePicker.value = false;
return;
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const dateStr = `${year}-${month}-${day}`;
if (datePickerType.value === 'start') {
formData.startDate = dateStr;
} else {
formData.endDate = dateStr;
}
showDatePicker.value = false;
};
// 选择图片
const chooseImage = () => {
uni.chooseImage({
@@ -149,40 +428,102 @@ const chooseImage = () => {
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.image = res.tempFilePaths[0];
const tempFilePath = res.tempFilePaths[0];
formData.photoPreview = tempFilePath;
// 上传图片
uploadImage(tempFilePath);
}
});
};
// 日期选择确认
const onDateConfirm = (e) => {
const date = new Date(e.value);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
formData.expireDate = `${year}-${month}-${day}`;
showDatePicker.value = false;
// 上传图片
const uploadImage = (filePath) => {
uni.showLoading({ title: '上传中...' });
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (uploadRes) => {
uni.hideLoading();
try {
const data = JSON.parse(uploadRes.data);
if (data.code === 0 && data.data) {
formData.photo = data.data.url || data.data;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
formData.photoPreview = '';
}
} catch (e) {
console.error('解析上传结果失败:', e);
uni.showToast({ title: '上传失败', icon: 'none' });
formData.photoPreview = '';
}
},
fail: (err) => {
uni.hideLoading();
console.error('上传失败:', err);
uni.showToast({ title: '上传失败', icon: 'none' });
formData.photoPreview = '';
}
});
};
// 移除图片
const removeImage = () => {
formData.photo = '';
formData.photoPreview = '';
};
// 提交表单
const handleSubmit = () => {
if (!formData.dept) {
const handleSubmit = async () => {
// 表单验证
if (!formData.enterpriseId) {
uni.showToast({ title: '请选择部门', icon: 'none' });
return;
}
console.log('提交数据:', formData);
uni.showToast({ title: '新增成功', icon: 'success' });
showAddPopup.value = false;
submitting.value = true;
// 重置表单
formData.dept = '';
formData.type = '';
formData.number = '';
formData.startDate = '';
formData.expireDate = '';
formData.image = '';
selectedDept.value = '';
try {
const submitData = {
enterpriseId: formData.enterpriseId,
type: formData.type,
code: formData.code,
startDate: formData.startDate,
endDate: formData.endDate,
legalPerson: formData.legalPerson,
photo: formData.photo
};
let res;
if (isEdit.value) {
// 编辑
submitData.id = currentEditId.value;
res = await updateLicense(submitData);
} else {
// 新增
res = await addLicense(submitData);
}
if (res.code === 0) {
uni.showToast({
title: isEdit.value ? '修改成功' : '新增成功',
icon: 'success'
});
closePopup();
loadLicenseList();
}
} catch (err) {
console.error('提交失败:', err);
uni.showToast({ title: '操作失败', icon: 'none' });
} finally {
submitting.value = false;
}
};
</script>
@@ -190,6 +531,96 @@ const handleSubmit = () => {
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 120rpx;
}
// 证照列表样式
.license-list {
padding-bottom: 20rpx;
}
.license-item {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.license-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.license-type {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.license-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
font-size: 28rpx;
padding: 8rpx 16rpx;
}
.license-detail {
margin-bottom: 16rpx;
}
.detail-row {
display: flex;
margin-bottom: 12rpx;
font-size: 28rpx;
.label {
color: #999;
width: 160rpx;
flex-shrink: 0;
}
.value {
color: #333;
flex: 1;
}
}
.license-photo {
width: 200rpx;
height: 150rpx;
border-radius: 8rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
// 空状态
.empty-state {
padding: 200rpx 0;
text-align: center;
}
// 新增按钮
.add-btn {
position: fixed;
bottom: 40rpx;
left: 30rpx;
right: 30rpx;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
}
// 弹出框样式
@@ -257,6 +688,7 @@ const handleSubmit = () => {
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.upload-add {
@@ -276,12 +708,33 @@ const handleSubmit = () => {
margin-top: 8rpx;
}
.upload-preview {
width: 100%;
height: 100%;
position: relative;
}
.upload-img {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.upload-delete {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 40rpx;
height: 40rpx;
background: #ff4d4f;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.popup-footer {
display: flex;
justify-content: center;

View File

@@ -1,5 +1,16 @@
<template>
<view class="page padding">
<!-- 进度显示 -->
<view class="progress-bar" v-if="checkData">
<view class="progress-text">
<text class="current-index"> {{ checkData.currentIndex || 1 }} 个问题</text>
<text class="total-count"> / {{ checkData.totalCount || 1 }} </text>
</view>
<view class="progress-line">
<view class="progress-inner" :style="{ width: progressPercent + '%' }"></view>
</view>
</view>
<view class="padding bg-white radius">
<view class="text-bold">{{ checkData?.name || '加载中...' }}</view>
<view class="margin-top">
@@ -22,18 +33,169 @@
</u-radio>
</u-radio-group>
</view>
<view>
<!-- 异常时显示隐患信息区域 -->
<view v-if="radiovalue1 === '异常'" class="hazard-section margin-top">
<view class="hazard-tip">
<text class="cuIcon-warn text-yellow margin-right-xs"></text>
<text class="text-orange">检查结果为异常需填写隐患信息</text>
</view>
<!-- 未填写时显示按钮 -->
<view v-if="!hasHazardData" class="hazard-btn" @click="openHazardPopup">
<text class="text-blue">填写隐患信息</text>
</view>
<!-- 已填写时显示卡片 -->
<view v-else class="hazard-card">
<view class="card-header">
<view class="text-bold text-black">{{ hazardFormData.title }}</view>
<view class="level-tag" :class="{
'level-minor': hazardFormData.level === 0,
'level-normal': hazardFormData.level === 1,
'level-major': hazardFormData.level === 2
}">{{ levelOptions[hazardFormData.level]?.title }}</view>
</view>
<view class="card-body">
<view class="info-row">
<text class="text-gray">隐患来源</text>
<text>{{ sourceOptions[hazardFormData.source]?.title || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患位置</text>
<text>{{ hazardAddress || '-' }}</text>
</view>
<view class="info-row">
<text class="text-gray">隐患描述</text>
<text class="description-text">{{ hazardFormData.description || '-' }}</text>
</view>
<view class="info-row" v-if="hazardFileList.length > 0">
<text class="text-gray">附件</text>
<text>{{ hazardFileList.length }}个文件</text>
</view>
</view>
<view class="card-footer">
<button class="btn-edit" @click="editHazard">修改</button>
<button class="btn-clear" @click="clearHazard">清除</button>
</view>
</view>
</view>
<view class="margin-top">
<up-textarea v-model="value1" placeholder="请输入备注信息" ></up-textarea>
</view>
</view>
</view>
<button class="bg-blue round margin-top-xl" @click="handleSubmit">提交</button>
<!-- 新增隐患弹窗 -->
<u-popup :show="showHazardPopup" mode="center" round="20" @close="showHazardPopup = false">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title text-bold">填写隐患信息</view>
<view class="popup-close" @click="showHazardPopup = false">×</view>
</view>
<scroll-view class="popup-body" scroll-y :style="{ height: '60vh' }">
<view class="flex margin-bottom">
<view class="text-gray">隐患图片/视频</view>
<view class="text-red">*</view>
</view>
<up-upload :fileList="hazardFileList" @afterRead="afterRead" @delete="deletePic" name="1" multiple
:maxCount="10"></up-upload>
<view class="text-gray text-sm">必填请上传现场照片或者视频作为隐患证据</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患标题</view>
<view class="text-red">*</view>
</view>
<up-input placeholder="请输入内容" border="surround" v-model="hazardFormData.title"></up-input>
<view class="text-sm text-gray margin-top-xs">请用简洁的语言概括隐患要点</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患等级</view>
<view class="text-red">*</view>
</view>
<up-choose v-model="hazardFormData.level" :options="levelOptions" :wrap="false" item-width="183rpx"
item-height="72rpx"></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患来源</view>
<view class="text-red">*</view>
</view>
<up-choose v-model="hazardFormData.source" :options="sourceOptions" :wrap="false" item-width="183rpx"
item-height="72rpx"></up-choose>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患位置</view>
<view class="text-red">*</view>
</view>
<view class="address-box">
<up-input class="address-input-wrapper" v-model="hazardAddress" placeholder="请输入地址" border="surround"></up-input>
<button class="btn-address bg-blue" @tap.stop="chooseLocation">选择地址</button>
</view>
<view class="text-gray text-sm margin-top-xs">办公楼3层东侧消防通道生产车间A区设备旁等</view>
<!-- 隐患区域选择 -->
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患区域</view>
</view>
<view class="select-trigger" @click="showAreaPicker = true">
<view class="select-value" :class="{ 'placeholder': !selectedAreaName }">
{{ selectedAreaName || '请选择隐患区域' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患描述</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="hazardFormData.description" placeholder="请输入内容"></up-textarea>
<view class="text-gray text-sm margin-top-xs">请详细说明隐患现状潜在风险及影响范围</view>
<view class="text-gray margin-bottom margin-top">隐患标签</view>
<up-choose v-model="hazardFormData.tagIndex" :options="tagOptions"></up-choose>
<view class="text-gray text-sm">可选择多个相关标签对隐患进行分类</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showHazardPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="confirmHazard">确定</button>
</view>
</view>
</u-popup>
<!-- 区域选择弹窗 -->
<u-popup :show="showAreaPicker" mode="bottom" round="20" @close="showAreaPicker = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showAreaPicker = false">取消</view>
<view class="picker-title">选择隐患区域</view>
<view class="picker-confirm" @click="confirmAreaSelect">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
v-for="item in areaList"
:key="item.id"
class="picker-item"
:class="{ 'picker-item-active': tempAreaId === item.id }"
@click="tempAreaId = item.id"
>
<view class="flex align-center">
<view class="area-color-dot" :style="{ backgroundColor: item.color }"></view>
<text>{{ item.name }}</text>
</view>
<text v-if="tempAreaId === item.id" class="cuIcon-check text-blue"></text>
</view>
<view v-if="areaList.length === 0" class="text-gray text-center padding">
暂无区域数据
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { enterCheckPlan,submitCheckResult } from '@/request/api.js';
import { enterCheckPlan, submitCheckResult, addHiddenDanger, getHiddenDangerLabelList } from '@/request/api.js';
import { baseUrl, getToken } from '@/request/request.js';
import { getAreaList } from '@/request/three_one_api/area.js';
// 页面参数
const oneTableId = ref('');
@@ -63,16 +225,246 @@
const radiovalue1 = ref('');
const groupChange = (n) => {
console.log('groupChange', n);
console.log('groupChange', n);
// 切换到非异常时清除隐患数据
if (n !== '异常') {
clearHazard();
}
};
const radioChange = (n) => {
console.log('radioChange', n);
console.log('radioChange', n);
};
// 备注
const value1 = ref('');
// ==================== 隐患信息相关 ====================
// 弹窗控制
const showHazardPopup = ref(false);
// 隐患表单数据(暂存)
const hazardFormData = reactive({
title: '',
level: 0,
source: 0,
description: '',
tagIndex: 0
});
// 隐患地址和经纬度
const hazardAddress = ref('');
const hazardLng = ref(0);
const hazardLat = ref(0);
// 区域选择相关
const showAreaPicker = ref(false);
const areaList = ref([]);
const selectedAreaId = ref('');
const selectedAreaName = ref('');
const tempAreaId = ref('');
// 获取区域列表
const fetchAreaList = async () => {
try {
const res = await getAreaList();
if (res.code === 0 && res.data && res.data.records) {
areaList.value = res.data.records;
}
} catch (error) {
console.error('获取区域列表失败:', error);
}
};
// 确认区域选择
const confirmAreaSelect = () => {
if (tempAreaId.value) {
selectedAreaId.value = tempAreaId.value;
const selected = areaList.value.find(item => item.id === tempAreaId.value);
selectedAreaName.value = selected ? selected.name : '';
}
showAreaPicker.value = false;
};
// 隐患附件列表
const hazardFileList = ref([]);
// 是否已填写隐患数据
const hasHazardData = computed(() => {
return hazardFormData.title && hazardFileList.value.length > 0;
});
// 进度百分比
const progressPercent = computed(() => {
if (!checkData.value) return 0;
const current = checkData.value.currentIndex || 1;
const total = checkData.value.totalCount || 1;
return Math.round((current / total) * 100);
});
// 隐患等级选项
const levelOptions = ref([
{ id: 1, title: '轻微隐患' },
{ id: 2, title: '一般隐患' },
{ id: 3, title: '重大隐患' }
]);
// 隐患来源选项
const sourceOptions = ref([
{ id: 1, title: '随手拍' },
{ id: 2, title: '企业自查' },
{ id: 3, title: '行业互查' },
{ id: 4, title: '专家诊查' }
]);
// 隐患标签选项
const tagOptions = ref([]);
// 获取隐患标签列表
const fetchTagOptions = async () => {
try {
const res = await getHiddenDangerLabelList();
if (res.code === 0) {
tagOptions.value = res.data.map(item => ({
id: item.id,
title: item.name
}));
}
} catch (error) {
console.error('获取标签列表失败:', error);
}
};
// 打开隐患信息弹窗
const openHazardPopup = () => {
showHazardPopup.value = true;
};
// 编辑隐患信息(回显数据)
const editHazard = () => {
showHazardPopup.value = true;
};
// 清除隐患信息
const clearHazard = () => {
hazardFormData.title = '';
hazardFormData.level = 0;
hazardFormData.source = 0;
hazardFormData.description = '';
hazardFormData.tagIndex = 0;
hazardAddress.value = '';
hazardLng.value = 0;
hazardLat.value = 0;
hazardFileList.value = [];
selectedAreaId.value = '';
selectedAreaName.value = '';
};
// 确认隐患信息(只暂存,不调接口)
const confirmHazard = () => {
// 表单验证
if (hazardFileList.value.length === 0) {
uni.showToast({ title: '请上传隐患图片/视频', icon: 'none' });
return;
}
if (!hazardFormData.title) {
uni.showToast({ title: '请输入隐患标题', icon: 'none' });
return;
}
if (!hazardAddress.value) {
uni.showToast({ title: '请输入隐患位置', icon: 'none' });
return;
}
if (!hazardFormData.description) {
uni.showToast({ title: '请输入隐患描述', icon: 'none' });
return;
}
// 验证通过,关闭弹窗(数据已在 reactive 中暂存)
showHazardPopup.value = false;
uni.showToast({ title: '隐患信息已暂存', icon: 'success' });
};
// 选择地址
const chooseLocation = () => {
showHazardPopup.value = false;
setTimeout(() => {
uni.chooseLocation({
success: (res) => {
hazardAddress.value = res.address + (res.name ? `(${res.name})` : '');
hazardLng.value = res.longitude;
hazardLat.value = res.latitude;
showHazardPopup.value = true;
},
fail: (err) => {
showHazardPopup.value = true;
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({ title: '选择位置失败', icon: 'none' });
}
}
});
}, 300);
};
// 删除图片
const deletePic = (event) => {
hazardFileList.value.splice(event.index, 1);
};
// 新增图片
const afterRead = async (event) => {
let lists = [].concat(event.file);
let fileListLen = hazardFileList.value.length;
lists.map((item) => {
hazardFileList.value.push({
...item,
status: 'uploading',
message: '上传中',
});
});
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url);
let item = hazardFileList.value[fileListLen];
hazardFileList.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: result,
});
fileListLen++;
}
};
// 上传文件
const uploadFilePromise = (filePath) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (res) => {
const data = JSON.parse(res.data);
if (data.code === 0) {
resolve(data.data);
} else {
reject(data.msg || '上传失败');
}
},
fail: (err) => {
console.error('上传失败:', err);
reject(err);
}
});
});
};
// ==================== 提交逻辑 ====================
// 提交检查结果
const handleSubmit = async () => {
// 验证是否选择了检查结果
@@ -90,32 +482,121 @@
return;
}
// 如果选择异常,验证是否填写了隐患信息
if (radiovalue1.value === '异常' && !hasHazardData.value) {
uni.showToast({ title: '请填写隐患信息', icon: 'none' });
return;
}
try {
const params = {
uni.showLoading({ title: '提交中...' });
// 如果选择异常,先调用新增隐患接口
if (radiovalue1.value === '异常' && hasHazardData.value) {
// 构建附件列表
const attachments = hazardFileList.value.map(file => {
let url = '';
if (typeof file.url === 'string') {
url = file.url;
} else if (file.url && typeof file.url === 'object') {
url = file.url.url || file.url.path || '';
}
const fileName = (typeof url === 'string' && url) ? url.split('/').pop() : (file.name || '');
return {
fileName: fileName || '',
filePath: url || '',
fileType: file.type || 'image/png',
fileSize: file.size || 0
};
});
// 获取隐患标签ID
const selectedTag = tagOptions.value[hazardFormData.tagIndex];
const tagId = selectedTag ? selectedTag.id : null;
// 构建隐患参数
const hazardParams = {
taskId: checkData.value?.taskId,
checkPointId: checkData.value?.checkPointId,
title: hazardFormData.title,
level: hazardFormData.level + 1,
lng: hazardLng.value || 0,
lat: hazardLat.value || 0,
address: hazardAddress.value || '',
areaId: selectedAreaId.value || null, // 隐患区域ID
description: hazardFormData.description || '',
source: sourceOptions.value[hazardFormData.source]?.title || '',
tagId: tagId,
attachments: attachments
};
console.log('隐患参数:', hazardParams);
// 调用新增隐患接口
const hazardRes = await addHiddenDanger(hazardParams);
if (hazardRes.code !== 0) {
uni.hideLoading();
uni.showToast({ title: hazardRes.msg || '新增隐患失败', icon: 'none' });
return;
}
console.log('新增隐患成功');
}
// 调用提交巡检结果接口
const submitParams = {
taskId: checkData.value?.taskId,
result: resultValue,
remark: value1.value
};
console.log('提交参数:', params);
console.log('提交参数:', submitParams);
const res = await submitCheckResult(params);
console.log('提交结果:', res);
const res = await submitCheckResult(submitParams);
uni.hideLoading();
if (res.code === 0) {
uni.showToast({ title: '提交成功', icon: 'success' });
// 提交成功后可以返回上一页或刷新数据
setTimeout(() => {
uni.navigateBack();
}, 1500);
// 判断是否全部完成
if (res.data && res.data.allFinished === true) {
// 全部完成,退出页面
uni.showToast({ title: '全部检查已完成', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else if (res.data && res.data.nextTask) {
// 还有下一个问题,更新数据继续
uni.showToast({ title: '提交成功,进入下一题', icon: 'success' });
// 重置表单状态
resetFormState();
// 更新为下一个检查项的数据
checkData.value = res.data.nextTask;
} else {
// 兜底处理:没有 allFinished 也没有 nextTask直接返回
uni.showToast({ title: '提交成功', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' });
}
} catch (error) {
uni.hideLoading();
console.error('提交失败:', error);
uni.showToast({ title: '提交失败', icon: 'none' });
}
};
// 重置表单状态(进入下一题时调用)
const resetFormState = () => {
// 重置检查结果选择
radiovalue1.value = '';
// 重置备注
value1.value = '';
// 清除隐患信息
clearHazard();
};
// 获取检查项数据
const getCheckData = async () => {
try {
@@ -136,6 +617,10 @@
oneTableId.value = options.id;
getCheckData();
}
// 获取隐患标签列表
fetchTagOptions();
// 获取区域列表
fetchAreaList();
});
</script>
@@ -144,4 +629,342 @@
min-height: 100vh;
background: #EBF2FC;
}
</style>
// 进度显示
.progress-bar {
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
.progress-text {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
.current-index {
font-size: 32rpx;
font-weight: bold;
color: #2667E9;
}
.total-count {
font-size: 28rpx;
color: #999;
}
}
.progress-line {
height: 12rpx;
background: #E5E5E5;
border-radius: 6rpx;
overflow: hidden;
}
.progress-inner {
height: 100%;
background: linear-gradient(90deg, #2667E9, #5B9BFF);
border-radius: 6rpx;
transition: width 0.3s ease;
}
// 隐患信息区域
.hazard-section {
border-top: 1rpx solid #eee;
padding-top: 20rpx;
}
// 提示文字
.hazard-tip {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
background: #FFF7E6;
border: 1rpx solid #FFE7BA;
border-radius: 8rpx;
margin-bottom: 20rpx;
.text-orange {
color: #FA8C16;
font-size: 26rpx;
}
}
// 填写隐患信息按钮
.hazard-btn {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
border: 2rpx dashed #2667E9;
border-radius: 12rpx;
background: #F5F9FF;
.text-blue {
color: #2667E9;
font-size: 28rpx;
}
}
// 隐患信息卡片
.hazard-card {
background: #F5F9FF;
border: 1rpx solid #D6E4FF;
border-radius: 12rpx;
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #E8E8E8;
background: #fff;
}
.card-body {
padding: 20rpx;
.info-row {
display: flex;
margin-bottom: 12rpx;
font-size: 26rpx;
&:last-child {
margin-bottom: 0;
}
.text-gray {
flex-shrink: 0;
color: #999;
}
.description-text {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.card-footer {
display: flex;
border-top: 1rpx solid #E8E8E8;
background: #fff;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 0;
&::after {
border: none;
}
}
.btn-edit {
background: #fff;
color: #2667E9;
border-right: 1rpx solid #E8E8E8;
}
.btn-clear {
background: #fff;
color: #F56C6C;
}
}
}
// 隐患等级标签
.level-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-size: 24rpx;
}
.level-minor {
background: #F6FFED;
border: 2rpx solid #B7EB8F;
color: #52C41A;
}
.level-normal {
background: #FFF7E6;
border: 2rpx solid #FFD591;
color: #FA8C16;
}
.level-major {
background: #FFF1F0;
border: 2rpx solid #FFA39E;
color: #F5222D;
}
// 弹窗样式
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
}
.popup-body {
padding: 30rpx;
}
.popup-footer {
display: flex;
border-top: 1rpx solid #eee;
button {
flex: 1;
height: 90rpx;
line-height: 90rpx;
border-radius: 0;
font-size: 30rpx;
&::after {
border: none;
}
}
.btn-cancel {
background: #fff;
color: #666;
}
.btn-confirm {
color: #fff;
}
}
.address-box {
display: flex;
align-items: center;
gap: 20rpx;
.address-input-wrapper {
flex: 1;
}
.btn-address {
flex-shrink: 0;
height: 70rpx;
line-height: 70rpx;
padding: 0 30rpx;
font-size: 26rpx;
border-radius: 8rpx;
color: #fff;
&::after {
border: none;
}
}
}
// 选择器触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
.select-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.placeholder {
color: #c0c4cc;
}
}
}
// 选择弹窗样式
.picker-popup {
background: #fff;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.picker-cancel {
font-size: 28rpx;
color: #999;
}
.picker-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.picker-confirm {
font-size: 28rpx;
color: #2667E9;
}
}
.picker-body {
max-height: 600rpx;
padding: 0 30rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.picker-item-active {
color: #2667E9;
}
}
}
// 区域颜色圆点
.area-color-dot {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
margin-right: 16rpx;
}
</style>

View File

@@ -9,173 +9,462 @@
</view>
</scroll-view>
<!-- 查询条件 -->
<view class="bg-white radius padding margin-top">
<view class="flex">
<!-- <view class="lg cuIcon-search bg-blue radius text-center align-center"></view> -->
<view class="bg-white radius padding margin-top search-card">
<view class="section-header">
<image class="section-icon" src="/static/yujin/yujin_sousuo.png" mode="aspectFit"></image>
<view class="text-black text-bold">查询条件</view>
</view>
<view class="margin-top margin-bottom">开始日期</view>
<up-datetime-picker hasInput :show="show" v-model="value1" mode="date"></up-datetime-picker>
<view class="margin-top margin-bottom">结束日期</view>
<up-datetime-picker hasInput :show="show" v-model="value1" mode="date"></up-datetime-picker>
<view class="margin-top margin-bottom">公司名称</view>
<up-input placeholder="请输入公司名称"></up-input>
<button class="bg-blue round margin-top">查询</button>
<!-- 日期选择行 -->
<view class="date-row margin-top">
<view class="date-item">
<view class="date-label">开始日期</view>
<view class="date-picker" @click="showStartDatePicker = true">
<text :class="searchForm.startDate ? 'date-value' : 'date-placeholder'">
{{ searchForm.startDate || '请选择' }}
</text>
<text class="cuIcon-unfold"></text>
</view>
<up-datetime-picker
:show="showStartDatePicker"
v-model="startDateValue"
mode="date"
@confirm="onStartDateConfirm"
@cancel="showStartDatePicker = false"
@close="showStartDatePicker = false"
></up-datetime-picker>
</view>
<view class="date-item">
<view class="date-label">结束日期</view>
<view class="date-picker" @click="showEndDatePicker = true">
<text :class="searchForm.endDate ? 'date-value' : 'date-placeholder'">
{{ searchForm.endDate || '请选择' }}
</text>
<text class="cuIcon-unfold"></text>
</view>
<up-datetime-picker
:show="showEndDatePicker"
v-model="endDateValue"
mode="date"
@confirm="onEndDateConfirm"
@cancel="showEndDatePicker = false"
@close="showEndDatePicker = false"
></up-datetime-picker>
</view>
</view>
<view class="margin-top">
<view class="date-label">公司名称</view>
<up-input v-model="searchForm.deptName" placeholder="请输入公司名称" border="surround"></up-input>
</view>
<button class="search-btn" @click="handleSearch">查询</button>
</view>
<!-- 统计概览 -->
<view class="padding bg-white radius margin-top">
<view class="flex">
<view></view>
<view class="section-header">
<image class="section-icon" src="/static/yujin/yujin_tongji.png" mode="aspectFit"></image>
<view class="text-bold text-black">统计概览</view>
</view>
<view class="flex col-4 grid margin-top " style="gap:20rpx">
<view class="text-center padding-top-sm " style="width: 142rpx;height: 124rpx;background:#628EFB; border-radius: 8rpx;color: #fff;">
<view>80</view>
<view>总计</view>
<view class="stat-grid margin-top">
<view class="stat-item stat-total">
<view class="stat-num">{{ statistics.total }}</view>
<view class="stat-label">总计</view>
</view>
<view class="text-center padding-top-sm " style="width: 142rpx;height: 124rpx;background:#32DCC7; border-radius: 8rpx;color: #fff;">
<view>70</view>
<view>逾期</view>
<view class="stat-item stat-overdue">
<view class="stat-num">{{ statistics.overdue }}</view>
<view class="stat-label">逾期</view>
</view>
<view class="text-center padding-top-sm " style="width: 142rpx;height: 124rpx;background:#32D1E9; border-radius: 8rpx;color: #fff;">
<view>20</view>
<view>已完成</view>
<view class="stat-item stat-completed">
<view class="stat-num">{{ statistics.completed }}</view>
<view class="stat-label">已完成</view>
</view>
<view class="text-center padding-top-sm " style="width: 142rpx;height: 124rpx;background:#A190F5; border-radius: 8rpx;color: #fff;">
<view>20</view>
<view>待处理</view>
<view class="stat-item stat-pending">
<view class="stat-num">{{ statistics.pending }}</view>
<view class="stat-label">待处理</view>
</view>
</view>
</view>
</view>
<!-- 数据列表 -->
<view class="bg-white radius padding margin-top margin-bottom flex">
<view class="list-title"></view>
<!-- 数据列表标题 -->
<view class="bg-white radius padding margin-top margin-bottom flex align-center">
<view class="list-title-bar"></view>
<view class="text-bold text-black">日常安全检查预警数据列表</view>
</view>
<view class="bg-white radius padding list-list">
<view class=" flex justify-between">
<view class="text-bold text-black">#1</view>
<view>严重逾期</view>
<!-- 数据列表 -->
<view v-for="(item, index) in dataList" :key="item.id" class="list-card">
<!-- 状态标签斜角 -->
<view class="status-tag" :class="getStatusClass(item.overdueDays)">
<text class="status-text">{{ getStatusText(item.overdueDays, item.statusName) }}</text>
</view>
<view class="flex margin-top">
<view class="text-gray" style="white-space: nowrap;">企业名称</view>
<view>湘西和谐云大数据产业发展有限公司</view>
<view class="card-header">
<view class="text-bold text-black">#{{ index + 1 }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">计划名称</view>
<view>检查计划</view>
<view class="card-row">
<view class="row-label">企业名称</view>
<view class="row-value">{{ item.deptName || '-' }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">计划周期</view>
<view>每天一次</view>
<view class="card-row">
<view class="row-label">计划名称</view>
<view class="row-value">{{ item.planName || '-' }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">预约检查日</view>
<view>2025-11-20</view>
<view class="card-row">
<view class="row-label">计划周</view>
<view class="row-value">{{ item.cycleName || '-' }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">实际完成时间</view>
<view>未完成</view>
<view class="card-row">
<view class="row-label">预约检查日期</view>
<view class="row-value">{{ item.taskDate || '-' }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">负责人</view>
<view>符友成</view>
<view class="card-row">
<view class="row-label">实际完成时间</view>
<view class="row-value">{{ item.finishTime || '未完成' }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">逾期天数</view>
<view>17 </view>
<view class="card-row">
<view class="row-label">负责人</view>
<view class="row-value">{{ item.executorName || '-' }}</view>
</view>
<view class="card-row">
<view class="row-label">逾期天数</view>
<view class="row-value">{{ item.overdueDays || '-' }}</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="dataList.length === 0" class="empty-tip">
<text>暂无数据</text>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getInspectionWarningList } from '@/request/api.js'
const activeIndex = ref(0)
// 搜索表单
const searchForm = reactive({
startDate: '',
endDate: '',
deptName: ''
})
const warningList = ref([{
id: 1,
name: '全部状态80',
},
{
id: 2,
name: '逾期未检16',
},
{
id: 3,
name: '严重逾期50',
},
{
id: 4,
name: '期限内待检4',
},
{
id: 5,
name: '逾期已完成8',
},
{
id: 6,
name: '按期已完成2',
}
])
// 日期选择器
const showStartDatePicker = ref(false)
const showEndDatePicker = ref(false)
const startDateValue = ref(Number(new Date()))
const endDateValue = ref(Number(new Date()))
const switchTab = (index) => {
activeIndex.value = index
// 统计数据
const statistics = reactive({
total: 0,
overdue: 0,
completed: 0,
pending: 0
})
// 列表数据
const dataList = ref([])
const pageNum = ref(1)
const pageSize = ref(20)
// 日期格式化
const formatDate = (timestamp) => {
const date = new Date(timestamp)
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 onStartDateConfirm = (e) => {
searchForm.startDate = formatDate(e.value)
showStartDatePicker.value = false
}
const onEndDateConfirm = (e) => {
searchForm.endDate = formatDate(e.value)
showEndDatePicker.value = false
}
// 获取状态样式类
const getStatusClass = (overdueDays) => {
if (!overdueDays || overdueDays === '按期') {
return 'status-normal' // 按期/期限内
}
// 日期
const value1 = ref(Date.now());
const days = parseInt(overdueDays)
if (days >= 7) {
return 'status-serious' // 严重逾期(红色)
} else if (days >= 1) {
return 'status-overdue' // 逾期(橙色)
}
return 'status-normal'
}
// 获取状态文本
const getStatusText = (overdueDays, statusName) => {
if (!overdueDays || overdueDays === '按期') {
return statusName === '已完成' ? '按期已完成' : '期限内待检'
}
const days = parseInt(overdueDays)
if (days >= 7) {
return '严重逾期'
} else if (days >= 1) {
return statusName === '已完成' ? '逾期已完成' : '逾期未检'
}
return '期限内待检'
}
// 获取数据
const fetchData = async () => {
try {
const params = {
pageNum: pageNum.value,
pageSize: pageSize.value
}
// 添加查询条件
if (searchForm.startDate) {
params.startDate = searchForm.startDate
}
if (searchForm.endDate) {
params.endDate = searchForm.endDate
}
if (searchForm.deptName && searchForm.deptName.trim()) {
params.deptName = searchForm.deptName.trim()
}
const res = await getInspectionWarningList(params)
if (res.code === 0) {
// 更新统计数据
if (res.data.statistics) {
statistics.total = res.data.statistics.total || 0
statistics.overdue = res.data.statistics.overdue || 0
statistics.completed = res.data.statistics.completed || 0
statistics.pending = res.data.statistics.pending || 0
}
// 更新列表数据
if (res.data.page && res.data.page.records) {
dataList.value = res.data.page.records
}
}
} catch (error) {
console.error('获取预警列表失败:', error)
}
}
// 搜索
const handleSearch = () => {
pageNum.value = 1
fetchData()
}
// 页面加载
onShow(() => {
fetchData()
})
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 40rpx;
}
.tab-scroll {
white-space: nowrap;
// 区块标题
.section-header {
display: flex;
align-items: center;
.section-icon {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
}
.tab-list {
display: inline-flex;
// 搜索卡片
.search-card {
.date-row {
display: flex;
gap: 20rpx;
}
.tab-item {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 16rpx 28rpx;
border-radius: 8rpx;
font-size: 28rpx;
background: #fff;
border: 1rpx solid #ddd;
color: #333;
flex-shrink: 0;
&.tab-active {
background: #2979ff;
border-color: #2979ff;
color: #fff;
.date-item {
flex: 1;
}
}
.list-title {
width: 10rpx;
height: 32rpx;
background: #2667E9;
border-radius: 10rpx;
line-height: 32rpx;
margin-right: 10rpx;
}
.list-list {
background: #FFFFFF;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-left: 5px solid #2667E9;
border-radius: 20rpx;
padding: 20rpx;
.date-label {
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.date-picker {
display: flex;
align-items: center;
justify-content: space-between;
height: 72rpx;
padding: 0 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
border: 1rpx solid #eee;
.date-value {
color: #333;
font-size: 28rpx;
}
.date-placeholder {
color: #999;
font-size: 28rpx;
}
}
.search-btn {
margin-top: 30rpx;
background: linear-gradient(135deg, #667eea 0%, #2667E9 100%);
color: #fff;
border-radius: 40rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
}
}
// 统计卡片
.stat-grid {
display: flex;
justify-content: space-between;
gap: 16rpx;
.stat-item {
flex: 1;
height: 124rpx;
border-radius: 12rpx;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.stat-num {
font-size: 40rpx;
font-weight: bold;
}
.stat-label {
font-size: 24rpx;
margin-top: 8rpx;
}
}
.stat-total {
background: linear-gradient(135deg, #628EFB 0%, #4A7CF7 100%);
}
.stat-overdue {
background: linear-gradient(135deg, #32DCC7 0%, #20C5B0 100%);
}
.stat-completed {
background: linear-gradient(135deg, #32D1E9 0%, #20B8D0 100%);
}
.stat-pending {
background: linear-gradient(135deg, #A190F5 0%, #8B78E8 100%);
}
}
// 列表标题
.list-title-bar {
width: 8rpx;
height: 32rpx;
background: #2667E9;
border-radius: 4rpx;
margin-right: 12rpx;
}
// 数据卡片
.list-card {
position: relative;
background: #FFFFFF;
box-shadow: 0rpx 2rpx 10rpx rgba(0, 0, 0, 0.08);
border-left: 8rpx solid #2667E9;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
overflow: hidden;
.card-header {
margin-bottom: 20rpx;
}
.card-row {
display: flex;
margin-top: 16rpx;
font-size: 28rpx;
line-height: 1.5;
.row-label {
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
.row-value {
color: #333;
word-break: break-all;
}
}
}
// 状态标签(斜角样式)
.status-tag {
position: absolute;
top: 0;
right: 0;
width: 160rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
transform: rotate(0deg);
border-radius: 0 16rpx 0 16rpx;
.status-text {
font-size: 22rpx;
color: #fff;
font-weight: 500;
}
}
// 严重逾期 - 红色
.status-serious {
background: linear-gradient(135deg, #FF6B6B 0%, #EE5A5A 100%);
}
// 逾期 - 橙色
.status-overdue {
background: linear-gradient(135deg, #FFA726 0%, #FF9800 100%);
}
// 按期/正常 - 绿色
.status-normal {
background: linear-gradient(135deg, #66BB6A 0%, #4CAF50 100%);
}
// 逾期已完成 - 蓝色
.status-completed {
background: linear-gradient(135deg, #42A5F5 0%, #2196F3 100%);
}
// 空状态
.empty-tip {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
</style>

View File

@@ -1,226 +1,213 @@
<template>
<view class="padding page">
<view class="padding bg-white radius">
<view class="flex justify-between">
<view>
<view class="text-bold text-black">{{ areaData.name || '区域名称' }}</view>
<view class="margin-top flex align-center">
<text>颜色</text>
<view class="color-dot" :style="{ backgroundColor: areaData.color }"></view>
<text class="margin-left-xs">{{ areaData.color }}</text>
<!-- 区域列表 -->
<view class="area-list" v-if="areaList.length > 0">
<view class="padding bg-white radius margin-bottom" v-for="item in areaList" :key="item.id">
<view class="flex justify-between">
<view>
<view class="text-bold text-black">{{ item.name || '区域名称' }}</view>
<view class="margin-top flex align-center">
<text>颜色</text>
<view class="color-dot" :style="{ backgroundColor: item.color }"></view>
<text class="margin-left-xs">{{ item.color }}</text>
</view>
</view>
<view>
<button class="bg-blue cu-btn" @click="openEditPopup(item)">编辑</button>
<button class="bg-red cu-btn margin-left" @click="handleDelete(item)">删除</button>
</view>
</view>
<view>
<button class="bg-blue cu-btn" @click="openEditPopup" >编辑</button>
<button class="bg-red cu-btn margin-left">删除</button>
</view>
</view>
</view>
<button class="cuIcon-add bg-blue round margin-top-xl">新增公司区域</button>
<!-- 编辑区域弹出框 -->
<u-popup :show="showEditPopup" mode="center" round="20" @close="showEditPopup = false">
<view class="popup-content">
<view class="popup-header">
<view class="popup-title text-bold">编辑区域</view>
<view class="popup-close" @click="showEditPopup = false">×</view>
</view>
<view class="popup-body">
<!-- 区域名称 -->
<view class="flex margin-bottom-sm">
<view>区域名称</view>
<view class="text-red">*</view>
</view>
<up-input v-model="formData.name" placeholder="请输入区域名称"></up-input>
<!-- 区域颜色 -->
<view class="flex margin-bottom-sm margin-top">
<view>区域颜色</view>
<view class="text-red">*</view>
</view>
<view class="flex align-center">
<up-input v-model="formData.color" placeholder="#ef4444" class="flex-sub"></up-input>
<view class="color-preview" :style="{ backgroundColor: formData.color }"></view>
</view>
<!-- 预设颜色 -->
<view class="margin-top margin-bottom-sm text-gray">预设颜色</view>
<view class="color-grid">
<view
v-for="(color, index) in presetColors"
:key="index"
class="color-item"
:style="{ backgroundColor: color }"
@click="selectColor(color)"
></view>
</view>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="showEditPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleEdit">确定</button>
</view>
</view>
</u-popup>
<!-- 空状态 -->
<view class="empty-state" v-else>
<text class="text-gray">暂无区域数据</text>
</view>
<!-- 新增按钮 -->
<button class="add-btn cuIcon-add bg-blue round" @click="openAddPopup">新增公司区域</button>
<!-- 新增/编辑弹窗组件 -->
<AreaFormPopup
v-model:visible="showPopup"
:isEdit="isEdit"
:editData="editData"
:loading="submitting"
@submit="handleSubmit"
@close="handlePopupClose"
/>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import AreaFormPopup from '@/components/AreaFormPopup.vue';
import {
getAreaList,
getAreaDetail,
addArea,
updateArea,
deleteArea
} from '@/request/three_one_api/area.js';
// 弹出框显示状态
const showEditPopup = ref(false);
// 区域列表
const areaList = ref([]);
// 当前区域数据(用于列表显示)
const areaData = reactive({
name: '区域名称',
color: '#ef4444'
// 弹窗控制
const showPopup = ref(false);
const isEdit = ref(false);
const currentEditId = ref(null);
const submitting = ref(false);
// 编辑时的数据
const editData = ref({});
// 页面加载
onMounted(() => {
loadAreaList();
});
// 表单数据
const formData = reactive({
name: '',
color: '#ef4444'
});
// 预设颜色
const presetColors = [
'#2563eb', '#ef4444', '#10b981', '#f59e0b', '#6366f1', '#ec4899', '#06b6d4',
'#84cc16', '#f97316', '#4f46e5', '#dc2626', '#f59e0b', '#d97706', '#8b5cf6',
'#db2777'
];
// 打开编辑弹出框
const openEditPopup = () => {
// 初始化表单数据为当前区域数据
formData.name = areaData.name;
formData.color = areaData.color;
showEditPopup.value = true;
// 加载区域列
const loadAreaList = async () => {
try {
const res = await getAreaList();
if (res.code === 0) {
areaList.value = res.data.records || [];
}
} catch (err) {
console.error('获取区域列表失败:', err);
}
};
// 选择预设颜色
const selectColor = (color) => {
formData.color = color;
// 打开新增弹窗
const openAddPopup = () => {
isEdit.value = false;
currentEditId.value = null;
editData.value = {};
showPopup.value = true;
};
// 确定编辑
const handleEdit = () => {
if (!formData.name) {
uni.showToast({ title: '请输入区域名称', icon: 'none' });
return;
// 打开编辑弹窗
const openEditPopup = async (item) => {
try {
const res = await getAreaDetail({ id: item.id });
if (res.code === 0) {
isEdit.value = true;
currentEditId.value = item.id;
editData.value = {
name: res.data.name || '',
color: res.data.color || '#FF5733'
};
showPopup.value = true;
}
} catch (err) {
console.error('获取区域详情失败:', err);
uni.showToast({ title: '获取详情失败', icon: 'none' });
}
if (!formData.color) {
uni.showToast({ title: '请选择区域颜色', icon: 'none' });
return;
};
// 弹窗关闭
const handlePopupClose = () => {
isEdit.value = false;
currentEditId.value = null;
editData.value = {};
};
// 提交表单
const handleSubmit = async (formData) => {
submitting.value = true;
try {
const submitData = {
name: formData.name,
color: formData.color
};
let res;
if (isEdit.value) {
submitData.id = currentEditId.value;
res = await updateArea(submitData);
} else {
res = await addArea(submitData);
}
if (res.code === 0) {
showPopup.value = false;
uni.showToast({
title: isEdit.value ? '修改成功' : '新增成功',
icon: 'success'
});
loadAreaList();
}
} catch (err) {
console.error('提交失败:', err);
uni.showToast({ title: '操作失败', icon: 'none' });
} finally {
submitting.value = false;
}
// 更新区域数据
areaData.name = formData.name;
areaData.color = formData.color;
showEditPopup.value = false;
uni.showToast({
title: '编辑成功',
icon: 'success'
};
// 删除区域
const handleDelete = (item) => {
uni.showModal({
title: '确认删除',
content: '确定要删除该区域吗?',
confirmColor: '#e54d42',
success: async (res) => {
if (res.confirm) {
try {
const result = await deleteArea({ id: item.id });
if (result.code === 0) {
uni.showToast({ title: '删除成功', icon: 'success' });
loadAreaList();
}
} catch (err) {
console.error('删除失败:', err);
uni.showToast({ title: '删除失败', icon: 'none' });
}
}
}
});
};
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 120rpx;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
// 区域列表
.area-list {
padding-bottom: 20rpx;
}
.popup-title {
font-size: 32rpx;
}
// 空状态
.empty-state {
padding: 200rpx 0;
text-align: center;
}
.popup-close {
font-size: 40rpx;
color: #999;
cursor: pointer;
}
// 新增按钮
.add-btn {
position: fixed;
bottom: 40rpx;
left: 30rpx;
right: 30rpx;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
}
.popup-body {
padding: 30rpx;
}
.popup-footer {
display: flex;
padding: 20rpx 30rpx 30rpx;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
margin: 0 10rpx;
&::after {
border: none;
}
}
.btn-cancel {
background: #f5f5f5;
color: #666;
}
.btn-confirm {
color: #fff;
}
}
// 颜色预览
.color-preview {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
margin-left: 20rpx;
flex-shrink: 0;
}
// 预设颜色网格
.color-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.color-item {
width: 70rpx;
height: 70rpx;
border-radius: 12rpx;
cursor: pointer;
transition: transform 0.2s;
&:active {
transform: scale(0.9);
}
}
// 颜色圆点
.color-dot {
width: 30rpx;
height: 30rpx;
border-radius: 6rpx;
flex-shrink: 0;
margin-left: 10rpx;
}
</style>
// 颜色圆点
.color-dot {
width: 30rpx;
height: 30rpx;
border-radius: 6rpx;
flex-shrink: 0;
margin-left: 10rpx;
}
</style>

View File

@@ -1,41 +1,101 @@
<template>
<view class="page padding">
<view class="padding bg-white radius flex justify-between margin-bottom" v-for="item in list" :key="item.id">
<view>{{ item.name }}</view>
<view>
<button class="bg-blue cu-btn margin-right-xs" @click="edit()">编辑</button>
<button class="bg-red cu-btn">删除</button>
</view>
<!-- 检查表列表 -->
<view class="checklist-card" v-for="item in list" :key="item.id">
<view class="card-name">{{ item.name }}</view>
</view>
<button class="lg cuIcon-add bg-blue round margin-top-xl" @click="edit()">新增检查表</button>
<!-- 空状态 -->
<view v-if="list.length === 0" class="empty-tip">
<text>暂无检查表</text>
</view>
<!-- 新增按钮 -->
<button class="add-btn" @click="goToAdd">
<text class="cuIcon-add"></text>
<text>新增检查表</text>
</button>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useRouter } from 'vue-router'
import { getCheckTableList } from '@/request/api.js'
const router = useRouter()
const list = ref([])
const edit = () => {
uni.navigateTo({
url: '/pages/editchecklist/editchecklist'
})
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getCheckTableList } from '@/request/api.js'
const list = ref([])
// 获取检查表列表
const fetchList = async () => {
try {
const res = await getCheckTableList({ pageNum: 1, pageSize: 100 });
if (res.code === 0) {
list.value = res.data.records || [];
}
} catch (error) {
console.error('获取检查表列表失败:', error);
}
onLoad(() => {
getCheckTableList().then(res => {
if (res.code === 0) {
list.value = res.data.records
}
})
}
// 跳转到新增页面
const goToAdd = () => {
uni.navigateTo({
url: '/pages/editchecklist/editchecklist'
})
}
// 每次显示页面时刷新数据
onShow(() => {
fetchList();
})
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 120rpx;
}
.checklist-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.card-name {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
}
.empty-tip {
text-align: center;
padding: 100rpx 0;
color: #999;
font-size: 28rpx;
}
.add-btn {
position: fixed;
bottom: 40rpx;
left: 30rpx;
right: 30rpx;
height: 90rpx;
background: linear-gradient(135deg, #667eea 0%, #2668EA 100%);
border-radius: 45rpx;
color: #fff;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(102, 126, 234, 0.4);
.cuIcon-add {
margin-right: 10rpx;
font-size: 36rpx;
}
}
</style>

View File

@@ -27,7 +27,7 @@
<view><button class="bg-blue round cu-btn lg" @click="editor()">查看详情</button></view>
</view>
</view>
<button class="cuIcon-add bg-blue round margin-top" @click="showAddPopup = true">新增</button>
<button class="cuIcon-add bg-blue round margin-top" @click="openAddPopup">新增</button>
<!-- 弹出框 -->
<u-popup :show="showAddPopup" mode="center" round="20" @close="showAddPopup = false">
<view class="popup-content">
@@ -35,7 +35,7 @@
<view class="popup-title text-bold">新增销号申请</view>
<view class="popup-close" @click="showAddPopup = false">×</view>
</view>
<view class="popup-body">
<scroll-view class="popup-body" scroll-y :style="{ height: '60vh' }">
<view class="flex margin-bottom">
<view>隐患</view>
<view class="text-red">*</view>
@@ -66,7 +66,16 @@
@close="showDatePicker = false"
></up-datetime-picker>
<view class="margin-bottom margin-top">隐患治理责任单位</view>
<up-input v-model="formData.responsibleDeptName" placeholder="请输入隐患治理责任单位"></up-input>
<view class="picker-input" @click="showDeptPicker = true">
<text :class="selectedDeptName ? '' : 'text-gray'">{{ selectedDeptName || '请选择隐患治理责任单位' }}</text>
</view>
<up-picker
:show="showDeptPicker"
:columns="deptColumns"
@confirm="onDeptConfirm"
@cancel="showDeptPicker = false"
@close="showDeptPicker = false"
></up-picker>
<view class="margin-bottom margin-top">主要负责人</view>
<up-input v-model="formData.responsiblePerson" placeholder="请输入主要负责人"></up-input>
<view class="margin-bottom margin-top">主要治理内容</view>
@@ -75,7 +84,7 @@
<up-textarea v-model="formData.treatmentResult" placeholder="请输入隐患治理完成情况"></up-textarea>
<view class="margin-bottom margin-top">隐患治理责任单位自行验收的情况</view>
<up-textarea v-model="formData.selfVerifyContent" placeholder="请输入隐患治理责任单位自行验收的情况"></up-textarea>
</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showAddPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleAdd">确定</button>
@@ -87,18 +96,26 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import {getMyWriteOffList, applyDelete } from '@/request/api.js';
import { getMyWriteOffList, applyDelete, getAcceptanceList, getDepartmentPersonUsers } from '@/request/api.js';
// 弹窗控制
const showAddPopup = ref(false);
const showHazardPicker = ref(false);
const showDatePicker = ref(false);
const showDeptPicker = ref(false);
// 隐患选择
const selectedHazard = ref('');
const selectedHazardId = ref('');
const hazardColumns = ref([['暂无数据']]);
const hazardList = ref([]); // 存储完整隐患数据
const acceptanceHazardList = ref([]); // 存储可申请销号的隐患数据
const hazardList = ref([]); // 存储销号申请列表
// 部门选择
const selectedDeptName = ref('');
const selectedDeptId = ref('');
const deptColumns = ref([['暂无数据']]);
const deptList = ref([]); // 存储部门列表
// 日期选择
const dateValue = ref(Date.now());
@@ -106,31 +123,100 @@
// 表单数据
const formData = reactive({
rectifyDeadline: '', // 整改时限
responsibleDeptName: '', // 隐患治理责任单位
responsibleDeptId: '', // 隐患治理责任单位ID
responsiblePerson: '', // 主要负责人
mainTreatmentContent: '', // 主要治理内容
treatmentResult: '', // 隐患治理完成内容
selfVerifyContent: '' // 责任单位自行验收情况
});
// 获取验收完成的隐患列表
const fetchHazardList = async () => {
// 获取销号申请列表(页面显示用)
const fetchWriteOffList = async () => {
try {
const res = await getMyWriteOffList();
if (res.code === 0 && res.data) {
const list = res.data;
hazardList.value = list;
// 转换为 picker 需要的格式
if (list.length > 0) {
hazardColumns.value = [list.map(item => item.hazardTitle || `隐患${item.hazardId}`)];
}
console.log('隐患列表:', list);
hazardList.value = res.data;
console.log('销号申请列表:', res.data);
}
} catch (error) {
console.error('获取隐患列表失败:', error);
console.error('获取销号申请列表失败:', error);
}
};
// 获取可申请销号的隐患列表(弹窗选择用)
const fetchAcceptanceList = async () => {
try {
const res = await getAcceptanceList();
if (res.code === 0 && res.data) {
const list = res.data.records || res.data || [];
acceptanceHazardList.value = list;
// 转换为 picker 需要的格式
if (list.length > 0) {
hazardColumns.value = [list.map(item => item.title || item.hazardTitle || `隐患${item.hazardId}`)];
} else {
hazardColumns.value = [['暂无可申请销号的隐患']];
}
console.log('可申请销号的隐患列表:', list);
}
} catch (error) {
console.error('获取可申请销号隐患列表失败:', error);
}
};
// 获取部门列表
const fetchDeptList = async () => {
try {
const res = await getDepartmentPersonUsers();
if (res.code === 0 && res.data) {
const users = [];
res.data.forEach(dept => {
if (dept.users && dept.users.length > 0) {
dept.users.forEach(user => {
users.push({
userId: user.userId,
deptId: dept.deptId,
name: `${user.nickName}${dept.deptName}`
});
});
}
});
deptList.value = users;
// 转换为 picker 需要的格式
if (users.length > 0) {
deptColumns.value = [users.map(item => item.name)];
} else {
deptColumns.value = [['暂无人员数据']];
}
console.log('部门人员列表:', users);
}
} catch (error) {
console.error('获取部门人员列表失败:', error);
}
};
// 部门选择确认
const onDeptConfirm = (e) => {
console.log('选择的人员:', e);
if (e.value && e.value.length > 0) {
selectedDeptName.value = e.value[0];
// 找到对应的用户ID和部门ID
const index = e.indexs[0];
if (deptList.value[index]) {
selectedDeptId.value = deptList.value[index].deptId;
formData.responsibleDeptId = deptList.value[index].deptId;
}
}
showDeptPicker.value = false;
};
// 打开新增弹窗
const openAddPopup = () => {
resetForm();
fetchAcceptanceList(); // 获取可申请销号的隐患列表
fetchDeptList(); // 获取部门列表
showAddPopup.value = true;
};
// 隐患选择确认
const onHazardConfirm = (e) => {
console.log('选择的隐患:', e);
@@ -138,8 +224,8 @@
selectedHazard.value = e.value[0];
// 找到对应的隐患ID
const index = e.indexs[0];
if (hazardList.value[index]) {
selectedHazardId.value = hazardList.value[index].hazardId;
if (acceptanceHazardList.value[index]) {
selectedHazardId.value = acceptanceHazardList.value[index].hazardId;
}
}
showHazardPicker.value = false;
@@ -163,8 +249,10 @@
const resetForm = () => {
selectedHazard.value = '';
selectedHazardId.value = '';
selectedDeptName.value = '';
selectedDeptId.value = '';
formData.rectifyDeadline = '';
formData.responsibleDeptName = '';
formData.responsibleDeptId = '';
formData.responsiblePerson = '';
formData.mainTreatmentContent = '';
formData.treatmentResult = '';
@@ -178,10 +266,11 @@
return;
}
// 构建请求参数
// 构建请求参数(与接口文档对应)
const params = {
hazardId: Number(selectedHazardId.value), // 隐患ID必需
rectifyDeadline: formData.rectifyDeadline || '', // 整改时限
responsibleDeptId: Number(formData.responsibleDeptId) || 0, // 隐患治理责任单位ID
responsiblePerson: formData.responsiblePerson || '', // 主要负责人
mainTreatmentContent: formData.mainTreatmentContent || '', // 主要治理内容
treatmentResult: formData.treatmentResult || '', // 隐患治理完成内容
@@ -196,6 +285,8 @@
uni.showToast({ title: '申请成功', icon: 'success' });
showAddPopup.value = false;
resetForm();
// 刷新销号申请列表
fetchWriteOffList();
} else {
uni.showToast({ title: res.msg || '申请失败', icon: 'none' });
}
@@ -211,9 +302,9 @@
})
};
// 页面加载时获取数据
// 页面加载时获取销号申请列表
onMounted(() => {
fetchHazardList();
fetchWriteOffList();
});
</script>
@@ -251,8 +342,6 @@
.popup-body {
padding: 30rpx;
max-height: 800rpx;
overflow-y: auto;
}
.popup-footer {

View File

@@ -17,7 +17,7 @@
<up-input v-model="formData.statusName" placeholder="" disabled></up-input>
<view class="flex justify-center margin-top-xl" style="gap: 30rpx;">
<button class="round cu-btn lg" @click="handleCancel">返回</button>
<button v-if="canEdit" class="bg-blue round cu-btn lg" @click="handleSubmit">保存</button>
<!-- <button v-if="canEdit" class="bg-blue round cu-btn lg" @click="handleSubmit">保存</button> -->
</view>
</view>
</view>

View File

@@ -1,49 +1,825 @@
<template>
<view class="page padding">
<view class="padding bg-white radius list">
<!-- 有企业信息时显示详情 -->
<view v-if="hasEnterpriseInfo" class="padding bg-white radius list">
<view class="flex justify-between">
<view class="text-bold">湘西自治州和谐网络科技有限公司</view>
<view class="lg text-blue cuIcon-edit over" @click="edit()">编辑</view>
<view class="text-bold text-lg">{{ enterpriseInfo.name }}</view>
<view class="lg text-blue cuIcon-edit over" @click="openEditPopup">编辑</view>
</view>
<view class="flex margin-top">
<view class="text-gray">所属部门</view>
<view>湘西自治州和谐网络科技有限公司</view>
<view class="info-item" v-if="enterpriseInfo.enterpriseTypeName">
<view class="text-gray">企业类型</view>
<view>{{ enterpriseInfo.enterpriseTypeName }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">企业代码</view>
<view>91433126MA4P8WWG20</view>
<view class="info-item" v-if="enterpriseInfo.industryName">
<view class="text-gray">行业类型</view>
<view>{{ enterpriseInfo.industryName }}</view>
</view>
<view class="flex margin-top">
<view class="info-item" v-if="enterpriseInfo.creditCode">
<view class="text-gray">统一社会信用代码</view>
<view>{{ enterpriseInfo.creditCode }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.registeredCapital">
<view class="text-gray">注册资本</view>
<view>{{ enterpriseInfo.registeredCapital }}万元</view>
</view>
<view class="info-item" v-if="enterpriseInfo.establishDate">
<view class="text-gray">成立时间</view>
<view>{{ enterpriseInfo.establishDate }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.employeeCount">
<view class="text-gray">员工总数</view>
<view>{{ enterpriseInfo.employeeCount }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.mainBusiness">
<view class="text-gray">主营行业</view>
<view>{{ enterpriseInfo.mainBusiness }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.mainProducts">
<view class="text-gray">主要产品/服务</view>
<view>{{ enterpriseInfo.mainProducts }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.annualOutput">
<view class="text-gray">年产值</view>
<view>{{ enterpriseInfo.annualOutput }}万元</view>
</view>
<view class="info-item" v-if="enterpriseInfo.province || enterpriseInfo.city">
<view class="text-gray">所在地区</view>
<view>{{ enterpriseInfo.province }}{{ enterpriseInfo.city }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.address">
<view class="text-gray">详细地址</view>
<view>{{ enterpriseInfo.address }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.phone">
<view class="text-gray">联系电话</view>
<view>13974356210</view>
<view>{{ enterpriseInfo.phone }}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">企业地址</view>
<view>湘西州文学艺术界联合会6楼</view>
<view class="info-item" v-if="enterpriseInfo.email">
<view class="text-gray">电子邮箱</view>
<view>{{ enterpriseInfo.email }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.legalPerson">
<view class="text-gray">法定代表人</view>
<view>{{ enterpriseInfo.legalPerson }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.legalPersonPhone">
<view class="text-gray">法人联系电话</view>
<view>{{ enterpriseInfo.legalPersonPhone }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.safetyManager">
<view class="text-gray">安全负责人</view>
<view>{{ enterpriseInfo.safetyManager }}</view>
</view>
<view class="info-item" v-if="enterpriseInfo.safetyManagerPhone">
<view class="text-gray">安全负责人电话</view>
<view>{{ enterpriseInfo.safetyManagerPhone }}</view>
</view>
</view>
<!-- 没有企业信息时显示新增按钮 -->
<view v-else class="empty-box">
<view class="text-gray text-center margin-bottom">暂无企业信息</view>
<button class="bg-blue round" @click="openAddPopup">新增企业信息</button>
</view>
<!-- 新增/编辑弹窗 - 使用 up-modal -->
<up-modal
:show="showPopup"
:title="isEdit ? '编辑企业信息' : '新增企业信息'"
:showConfirmButton="true"
:showCancelButton="true"
confirmText="确定"
cancelText="取消"
@confirm="handleSubmit"
@cancel="showPopup = false"
@close="showPopup = false"
:closeOnClickOverlay="false"
>
<scroll-view class="modal-scroll-body" scroll-y="true">
<!-- 企业名称 -->
<view class="form-label">
<view class="text-gray">企业名称</view>
<view class="text-red">*</view>
</view>
<input class="form-input" v-model="formData.name" placeholder="请输入企业名称" />
<!-- 企业类型 -->
<view class="form-label margin-top">
<view class="text-gray">企业类型</view>
<view class="text-red">*</view>
</view>
<view class="select-trigger" @click="openEnterpriseTypePopup">
<view class="select-value" :class="{ 'placeholder': !selectedEnterpriseTypeName }">
{{ selectedEnterpriseTypeName || '请选择企业类型' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<!-- 行业类型 -->
<view class="form-label margin-top">
<view class="text-gray">行业类型</view>
<view class="text-red">*</view>
</view>
<view class="select-trigger" @click="openIndustryPopup">
<view class="select-value" :class="{ 'placeholder': !selectedIndustryName }">
{{ selectedIndustryName || '请选择行业类型' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<!-- 统一社会信用代码 -->
<view class="form-label margin-top">
<view class="text-gray">统一社会信用代码</view>
</view>
<input class="form-input" v-model="formData.creditCode" placeholder="请输入统一社会信用代码" />
<!-- 注册资本 -->
<view class="form-label margin-top">
<view class="text-gray">注册资本万元</view>
</view>
<input class="form-input" v-model="formData.registeredCapital" placeholder="请输入注册资本" type="number" />
<!-- 成立时间 -->
<view class="form-label margin-top">
<view class="text-gray">成立时间</view>
</view>
<view class="select-trigger" @click="showDatePicker = true">
<view class="select-value" :class="{ 'placeholder': !formData.establishDate }">
{{ formData.establishDate || '请选择成立时间' }}
</view>
<text class="cuIcon-calendar"></text>
</view>
<!-- 员工总数 -->
<view class="form-label margin-top">
<view class="text-gray">员工总数</view>
</view>
<input class="form-input" v-model="formData.employeeCount" placeholder="请输入员工总数" type="number" />
<!-- 主营行业 -->
<view class="form-label margin-top">
<view class="text-gray">主营行业</view>
</view>
<input class="form-input" v-model="formData.mainBusiness" placeholder="请输入主营行业" />
<!-- 主要产品/服务 -->
<view class="form-label margin-top">
<view class="text-gray">主要产品/服务</view>
</view>
<textarea class="form-textarea" v-model="formData.mainProducts" placeholder="请输入主要产品/服务"></textarea>
<!-- 年产值 -->
<view class="form-label margin-top">
<view class="text-gray">年产值万元</view>
</view>
<input class="form-input" v-model="formData.annualOutput" placeholder="请输入年产值" type="number" />
<!-- 所在省份 -->
<view class="form-label margin-top">
<view class="text-gray">所在省份</view>
</view>
<input class="form-input" v-model="formData.province" placeholder="请输入所在省份" />
<!-- 所在城市 -->
<view class="form-label margin-top">
<view class="text-gray">所在城市</view>
</view>
<input class="form-input" v-model="formData.city" placeholder="请输入所在城市" />
<!-- 详细地址 -->
<view class="form-label margin-top">
<view class="text-gray">详细地址</view>
</view>
<textarea class="form-textarea" v-model="formData.address" placeholder="请输入详细地址"></textarea>
<!-- 联系电话 -->
<view class="form-label margin-top">
<view class="text-gray">联系电话</view>
</view>
<input class="form-input" v-model="formData.phone" placeholder="请输入联系电话" />
<!-- 电子邮箱 -->
<view class="form-label margin-top">
<view class="text-gray">电子邮箱</view>
</view>
<input class="form-input" v-model="formData.email" placeholder="请输入电子邮箱" />
<!-- 法定代表人 -->
<view class="form-label margin-top">
<view class="text-gray">法定代表人</view>
</view>
<input class="form-input" v-model="formData.legalPerson" placeholder="请输入法定代表人" />
<!-- 法人联系电话 -->
<view class="form-label margin-top">
<view class="text-gray">法人联系电话</view>
</view>
<input class="form-input" v-model="formData.legalPersonPhone" placeholder="请输入法人联系电话" />
<!-- 安全负责人 -->
<view class="form-label margin-top">
<view class="text-gray">安全负责人</view>
</view>
<input class="form-input" v-model="formData.safetyManager" placeholder="请输入安全负责人" />
<!-- 安全负责人电话 -->
<view class="form-label margin-top">
<view class="text-gray">安全负责人电话</view>
</view>
<input class="form-input" v-model="formData.safetyManagerPhone" placeholder="请输入安全负责人电话" />
<!-- 资质证书 -->
<view class="form-label margin-top">
<view class="text-gray">资质证书</view>
</view>
<up-upload :fileList="certificateFiles" @afterRead="afterRead" @delete="deleteCertificate" name="certificate" multiple :maxCount="10" accept="all"></up-upload>
<view class="text-gray text-sm margin-bottom">支持上传图片PDF等文件</view>
</scroll-view>
</up-modal>
<!-- 日期选择器单独放在外面 -->
<u-datetime-picker
:show="showDatePicker"
v-model="establishDateValue"
mode="date"
@confirm="onDateConfirm"
@cancel="showDatePicker = false"
@close="showDatePicker = false"
></u-datetime-picker>
<!-- 企业类型选择弹窗 -->
<u-popup :show="showEnterpriseTypePopup" mode="bottom" round="20" @close="showEnterpriseTypePopup = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showEnterpriseTypePopup = false">取消</view>
<view class="picker-title">选择企业类型</view>
<view class="picker-confirm" @click="confirmEnterpriseType">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
class="picker-item"
v-for="item in enterpriseTypeList"
:key="item.id"
:class="{ 'active': tempEnterpriseTypeId === item.id }"
@click="tempEnterpriseTypeId = item.id"
>
<text>{{ item.name }}</text>
<text v-if="tempEnterpriseTypeId === item.id" class="cuIcon-check text-blue"></text>
</view>
</scroll-view>
</view>
</u-popup>
<!-- 行业类型选择弹窗 -->
<u-popup :show="showIndustryPopup" mode="bottom" round="20" @close="showIndustryPopup = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showIndustryPopup = false">取消</view>
<view class="picker-title">选择行业类型</view>
<view class="picker-confirm" @click="confirmIndustry">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
class="picker-item"
v-for="item in industryList"
:key="item.id"
:class="{ 'active': tempIndustryId === item.id }"
@click="tempIndustryId = item.id"
>
<text>{{ item.name }}</text>
<text v-if="tempIndustryId === item.id" class="cuIcon-check text-blue"></text>
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref } from 'vue'
const edit = () => {
uni.navigateTo({
url: '/pages/editcompanInformation/editcompanInformation'
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getEnterpriseinfo, addEnterprise, updateEnterprise, getEnterprisetype, getindustry } from '@/request/api.js'
import { baseUrl, getToken } from '@/request/request.js'
// 企业信息
const enterpriseInfo = ref({})
const hasEnterpriseInfo = computed(() => {
return enterpriseInfo.value && enterpriseInfo.value.name
})
// 弹窗控制
const showPopup = ref(false)
const isEdit = ref(false)
const showDatePicker = ref(false)
const establishDateValue = ref(Date.now())
// 下拉选项
const enterpriseTypeList = ref([])
const industryList = ref([])
// 选择弹窗控制
const showEnterpriseTypePopup = ref(false)
const showIndustryPopup = ref(false)
const tempEnterpriseTypeId = ref('')
const tempIndustryId = ref('')
const selectedEnterpriseTypeName = ref('')
const selectedIndustryName = ref('')
// 表单数据
const formData = reactive({
deptId: '',
enterpriseTypeId: '',
industryId: '',
name: '',
creditCode: '',
registeredCapital: '',
establishDate: '',
employeeCount: '',
mainBusiness: '',
mainProducts: '',
annualOutput: '',
province: '',
city: '',
address: '',
phone: '',
email: '',
legalPerson: '',
legalPersonPhone: '',
safetyManager: '',
safetyManagerPhone: '',
certificates: ''
})
// 资质证书文件列表
const certificateFiles = ref([])
// 获取用户部门ID
const getDeptId = () => {
try {
const userInfoStr = uni.getStorageSync('userInfo')
if (userInfoStr) {
const userInfo = JSON.parse(userInfoStr)
return userInfo.deptId || ''
}
} catch (error) {
console.error('获取用户信息失败:', error)
}
return ''
}
// 获取企业信息
const fetchEnterpriseInfo = async () => {
try {
const res = await getEnterpriseinfo()
if (res.code === 0 && res.data) {
enterpriseInfo.value = res.data
console.log('企业信息:', res.data)
} else {
enterpriseInfo.value = {}
}
} catch (error) {
console.error('获取企业信息失败:', error)
enterpriseInfo.value = {}
}
}
// 获取企业类型列表
const fetchEnterpriseTypes = async () => {
try {
const res = await getEnterprisetype()
if (res.code === 0 && res.data) {
enterpriseTypeList.value = res.data.map(item => ({
id: String(item.id),
name: item.name
}))
}
} catch (error) {
console.error('获取企业类型失败:', error)
}
}
// 获取行业类型列表
const fetchIndustryTypes = async () => {
try {
const res = await getindustry({})
if (res.code === 0 && res.data) {
industryList.value = res.data.map(item => ({
id: String(item.id),
name: item.name
}))
}
} catch (error) {
console.error('获取行业类型失败:', error)
}
}
// 打开企业类型选择弹窗
const openEnterpriseTypePopup = () => {
tempEnterpriseTypeId.value = formData.enterpriseTypeId
showEnterpriseTypePopup.value = true
}
// 确认企业类型选择
const confirmEnterpriseType = () => {
if (tempEnterpriseTypeId.value) {
formData.enterpriseTypeId = tempEnterpriseTypeId.value
const selected = enterpriseTypeList.value.find(item => item.id === tempEnterpriseTypeId.value)
selectedEnterpriseTypeName.value = selected ? selected.name : ''
}
showEnterpriseTypePopup.value = false
}
// 打开行业类型选择弹窗
const openIndustryPopup = () => {
tempIndustryId.value = formData.industryId
showIndustryPopup.value = true
}
// 确认行业类型选择
const confirmIndustry = () => {
if (tempIndustryId.value) {
formData.industryId = tempIndustryId.value
const selected = industryList.value.find(item => item.id === tempIndustryId.value)
selectedIndustryName.value = selected ? selected.name : ''
}
showIndustryPopup.value = false
}
// 日期确认
const onDateConfirm = (e) => {
const date = new Date(e.value)
formData.establishDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
showDatePicker.value = false
}
// 重置表单
const resetForm = () => {
formData.deptId = getDeptId()
formData.enterpriseTypeId = ''
formData.industryId = ''
formData.name = ''
formData.creditCode = ''
formData.registeredCapital = ''
formData.establishDate = ''
formData.employeeCount = ''
formData.mainBusiness = ''
formData.mainProducts = ''
formData.annualOutput = ''
formData.province = ''
formData.city = ''
formData.address = ''
formData.phone = ''
formData.email = ''
formData.legalPerson = ''
formData.legalPersonPhone = ''
formData.safetyManager = ''
formData.safetyManagerPhone = ''
formData.certificates = ''
certificateFiles.value = []
selectedEnterpriseTypeName.value = ''
selectedIndustryName.value = ''
}
// 打开新增弹窗
const openAddPopup = () => {
isEdit.value = false
resetForm()
showPopup.value = true
}
// 打开编辑弹窗
const openEditPopup = () => {
isEdit.value = true
// 填充表单数据
formData.deptId = enterpriseInfo.value.deptId || getDeptId()
formData.enterpriseTypeId = String(enterpriseInfo.value.enterpriseTypeId || '')
formData.industryId = String(enterpriseInfo.value.industryId || '')
formData.name = enterpriseInfo.value.name || ''
formData.creditCode = enterpriseInfo.value.creditCode || ''
formData.registeredCapital = enterpriseInfo.value.registeredCapital || ''
formData.establishDate = enterpriseInfo.value.establishDate || ''
formData.employeeCount = enterpriseInfo.value.employeeCount || ''
formData.mainBusiness = enterpriseInfo.value.mainBusiness || ''
formData.mainProducts = enterpriseInfo.value.mainProducts || ''
formData.annualOutput = enterpriseInfo.value.annualOutput || ''
formData.province = enterpriseInfo.value.province || ''
formData.city = enterpriseInfo.value.city || ''
formData.address = enterpriseInfo.value.address || ''
formData.phone = enterpriseInfo.value.phone || ''
formData.email = enterpriseInfo.value.email || ''
formData.legalPerson = enterpriseInfo.value.legalPerson || ''
formData.legalPersonPhone = enterpriseInfo.value.legalPersonPhone || ''
formData.safetyManager = enterpriseInfo.value.safetyManager || ''
formData.safetyManagerPhone = enterpriseInfo.value.safetyManagerPhone || ''
// 同步显示已选择的类型名称
selectedEnterpriseTypeName.value = enterpriseInfo.value.enterpriseTypeName || ''
selectedIndustryName.value = enterpriseInfo.value.industryName || ''
// 处理资质证书
if (enterpriseInfo.value.certificates) {
try {
const certs = JSON.parse(enterpriseInfo.value.certificates)
certificateFiles.value = certs.map(cert => ({
url: cert.filePath || cert.url,
name: cert.fileName || cert.name,
status: 'success'
}))
} catch (e) {
certificateFiles.value = []
}
} else {
certificateFiles.value = []
}
showPopup.value = true
}
// 上传文件
const afterRead = async (event) => {
let lists = [].concat(event.file)
let fileListLen = certificateFiles.value.length
lists.forEach((item) => {
certificateFiles.value.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url)
let item = certificateFiles.value[fileListLen]
certificateFiles.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: result
})
fileListLen++
}
}
// 删除文件
const deleteCertificate = (event) => {
certificateFiles.value.splice(event.index, 1)
}
// 上传文件Promise
const uploadFilePromise = (filePath) => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (res) => {
const data = JSON.parse(res.data)
if (data.code === 0) {
resolve(data.data)
} else {
reject(data.msg || '上传失败')
}
},
fail: (err) => {
console.error('上传失败:', err)
reject(err)
}
})
})
}
// 提交表单
const handleSubmit = async () => {
// 表单验证
if (!formData.name) {
uni.showToast({ title: '请输入企业名称', icon: 'none' })
return
}
if (!formData.enterpriseTypeId) {
uni.showToast({ title: '请选择企业类型', icon: 'none' })
return
}
if (!formData.industryId) {
uni.showToast({ title: '请选择行业类型', icon: 'none' })
return
}
// 构建资质证书JSON
const certificates = certificateFiles.value.map(file => ({
fileName: file.name || file.url.split('/').pop(),
filePath: file.url
}))
const params = {
deptId: Number(formData.deptId) || Number(getDeptId()),
enterpriseTypeId: Number(formData.enterpriseTypeId),
industryId: Number(formData.industryId),
name: formData.name,
creditCode: formData.creditCode || '',
registeredCapital: Number(formData.registeredCapital) || 0,
establishDate: formData.establishDate || '',
employeeCount: Number(formData.employeeCount) || 0,
mainBusiness: formData.mainBusiness || '',
mainProducts: formData.mainProducts || '',
annualOutput: Number(formData.annualOutput) || 0,
province: formData.province || '',
city: formData.city || '',
address: formData.address || '',
phone: formData.phone || '',
email: formData.email || '',
legalPerson: formData.legalPerson || '',
legalPersonPhone: formData.legalPersonPhone || '',
safetyManager: formData.safetyManager || '',
safetyManagerPhone: formData.safetyManagerPhone || '',
certificates: JSON.stringify(certificates)
}
// 如果是编辑模式,添加 id 字段
if (isEdit.value && enterpriseInfo.value.id) {
params.id = enterpriseInfo.value.id
}
try {
let res
if (isEdit.value) {
res = await updateEnterprise(params)
} else {
res = await addEnterprise(params)
}
if (res.code === 0) {
uni.showToast({
title: isEdit.value ? '修改成功' : '新增成功',
icon: 'success'
})
showPopup.value = false
// 刷新数据
fetchEnterpriseInfo()
} else {
uni.showToast({
title: res.msg || '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('提交失败:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
// 页面显示时获取数据
onShow(() => {
fetchEnterpriseInfo()
fetchEnterpriseTypes()
fetchIndustryTypes()
})
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
.page {
min-height: 100vh;
background: #EBF2FC;
}
.list {
background: #FFFFFF;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-left: 5px solid #2667E9;
border-radius: 20rpx;
padding: 20rpx;
}
.info-item {
display: flex;
margin-top: 16rpx;
font-size: 28rpx;
.text-gray {
flex-shrink: 0;
color: #999;
}
.list {
background: #FFFFFF;
box-shadow: 0rpx 2rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-left: 5px solid #2667E9;
border-radius: 20rpx;
padding: 20rpx;
}
.empty-box {
padding: 100rpx 40rpx;
text-align: center;
}
// up-modal 内的滚动区域
.modal-scroll-body {
height: 60vh;
padding: 20rpx 0;
box-sizing: border-box;
}
// 表单输入框样式
.form-input {
width: 100%;
height: 80rpx;
padding: 0 24rpx;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
background: #fff;
}
.form-textarea {
width: 100%;
min-height: 160rpx;
padding: 20rpx 24rpx;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
background: #fff;
}
.form-label {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.text-red {
margin-left: 4rpx;
}
</style>
}
// 选择触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
.select-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.placeholder {
color: #c0c4cc;
}
}
}
// 选择弹窗样式
.picker-popup {
background: #fff;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.picker-cancel {
font-size: 28rpx;
color: #999;
}
.picker-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.picker-confirm {
font-size: 28rpx;
color: #2667E9;
}
}
.picker-body {
max-height: 600rpx;
padding: 0 30rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.active {
color: #2667E9;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
<view class="popup-title text-bold">新增设备</view>
<view class="popup-close" @click="showAddPopup = false">×</view>
</view>
<view class="popup-body">
<scroll-view class="popup-body" scroll-y :style="{ height: '60vh' }">
<!-- 在这里填写表单内容 -->
<view class="flex">
<view class=" margin-bottom">型号</view>
@@ -64,7 +64,7 @@
</view>
<view class="margin-top-sm margin-bottom margin-top">备注</view>
<up-textarea v-model="value1" placeholder="请输入备注"></up-textarea>
</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showAddPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleAdd">确定</button>
@@ -169,8 +169,6 @@
.popup-body {
padding: 30rpx;
max-height: 60vh;
overflow-y: auto;
}
.popup-footer {

View File

@@ -25,18 +25,27 @@
<view class="flex justify-end" style="gap: 10rpx;">
<!-- 所有状态都显示查看详情 -->
<button class="round cu-btn lg light bg-blue" @click="details(item)">查看详情</button>
<!-- 待整改待验收显示立即整改 -->
<button v-if="item.statusName === '待整改' || item.statusName === '待验收'"
class="round cu-btn lg light bg-blue" @click="Rectification(item)">立即整改</button>
<!-- 待验收显示立即验收 -->
<button v-if="item.statusName === '待验收'"
<!-- 待整改状态canEdit为true时显示隐患交办和立即整改为false时不显示 -->
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn lg light bg-blue" @click="assignHazard(item)">隐患交办</button>
<button v-if="item.statusName === '待整改' && item.canEdit"
class="round cu-btn lg bg-blue" @click="Rectification(item)">立即整改</button>
<!-- 待验收显示编辑整改信息和立即验收 -->
<button v-if="item.statusName === '待验收' && item.canEdit"
class="round cu-btn lg light bg-blue" @click="editRectification(item)">编辑整改信息</button>
<button v-if="item.statusName === '待验收' && canAcceptance"
class="round cu-btn lg bg-blue" @click="acceptance(item)">立即验收</button>
<!-- 待交办显示隐患交办 -->
<button v-if="item.statusName === '待交办'"
class="round cu-btn lg bg-blue" @click="assignHazard(item)">隐患交办</button>
</view>
</view>
<button class="cuIcon-add round bg-blue margin-top-xl" @click="showAddPopup = true">新增</button>
<!-- 固定在底部的悬浮新增按钮 -->
<view class="fixed-add-btn" @click="showAddPopup = true">
<text class="cuIcon-add"></text>
<text>新增</text>
</view>
<!-- 新增弹窗 -->
<u-popup :show="showAddPopup" mode="center" round="20" @close="showAddPopup = false">
@@ -45,7 +54,7 @@
<view class="popup-title text-bold">新增隐患排查</view>
<view class="popup-close" @click="showAddPopup = false">×</view>
</view>
<view class="popup-body">
<scroll-view class="popup-body" scroll-y>
<view class="flex margin-bottom">
<view class="text-gray">隐患图片/视频</view>
<view class="text-red">*</view>
@@ -77,12 +86,22 @@
</view>
<view class="address-box">
<view class="address-input" @tap.stop="chooseLocation">
<text :class="selectedAddress ? '' : 'text-gray'">{{ selectedAddress || '请选择地址' }}</text>
</view>
<up-input class="address-input-wrapper" v-model="selectedAddress" placeholder="请输入地址" border="surround"></up-input>
<button class="btn-address bg-blue" @tap.stop="chooseLocation">选择地址</button>
</view>
<view class="text-gray text-sm margin-top-xs">办公楼3层东侧消防通道生产车间A区设备旁等或点击"选择地址"按钮在地图上选择</view>
<!-- 隐患区域选择 -->
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患区域</view>
</view>
<view class="select-trigger" @click="showAreaPicker = true">
<view class="select-value" :class="{ 'placeholder': !selectedAreaName }">
{{ selectedAreaName || '请选择隐患区域' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患描述</view>
<view class="text-red">*</view>
@@ -92,13 +111,42 @@
<view class="text-gray margin-bottom margin-top">隐患标签</view>
<up-choose v-model="formData.tagIndex" :options="tagOptions"></up-choose>
<view class="text-gray text-sm">可选择多个相关标签对隐患进行分类</view>
</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showAddPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="handleAdd">确定</button>
</view>
</view>
</u-popup>
<!-- 区域选择弹窗 -->
<u-popup :show="showAreaPicker" mode="bottom" round="20" @close="showAreaPicker = false">
<view class="picker-popup">
<view class="picker-header">
<view class="picker-cancel" @click="showAreaPicker = false">取消</view>
<view class="picker-title">选择隐患区域</view>
<view class="picker-confirm" @click="confirmAreaSelect">确定</view>
</view>
<scroll-view class="picker-body" scroll-y>
<view
v-for="item in areaList"
:key="item.id"
class="picker-item"
:class="{ 'picker-item-active': tempAreaId === item.id }"
@click="tempAreaId = item.id"
>
<view class="flex align-center">
<view class="area-color-dot" :style="{ backgroundColor: item.color }"></view>
<text>{{ item.name }}</text>
</view>
<text v-if="tempAreaId === item.id" class="cuIcon-check text-blue"></text>
</view>
<view v-if="areaList.length === 0" class="text-gray text-center padding">
暂无区域数据
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
@@ -120,6 +168,7 @@
getMyHiddenDangerList,
getHiddenDangerLabelList
} from '@/request/api.js'
import { getAreaList } from '@/request/three_one_api/area.js'
import {
baseUrl,
getToken
@@ -127,6 +176,27 @@
// 弹窗控制
const showAddPopup = ref(false);
// 获取用户角色判断是否有验收权限admin或manage才能验收
const userRole = ref('');
const canAcceptance = computed(() => {
return userRole.value === 'admin' || userRole.value === 'manage';
});
// 从storage获取用户信息
const getUserRole = () => {
try {
const userInfoStr = uni.getStorageSync('userInfo');
if (userInfoStr) {
const userInfo = JSON.parse(userInfoStr);
userRole.value = userInfo.role || '';
console.log('当前用户角色:', userRole.value);
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
};
getUserRole();
// 任务相关ID从接口获取
const taskId = ref('');
@@ -173,6 +243,36 @@
// 地址选择 - 调用腾讯地图
const selectedAddress = ref('');
// 区域选择相关
const showAreaPicker = ref(false);
const areaList = ref([]);
const selectedAreaId = ref('');
const selectedAreaName = ref('');
const tempAreaId = ref('');
// 获取区域列表
const fetchAreaList = async () => {
try {
const res = await getAreaList();
if (res.code === 0 && res.data && res.data.records) {
areaList.value = res.data.records;
}
} catch (error) {
console.error('获取区域列表失败:', error);
}
};
fetchAreaList();
// 确认区域选择
const confirmAreaSelect = () => {
if (tempAreaId.value) {
selectedAreaId.value = tempAreaId.value;
const selected = areaList.value.find(item => item.id === tempAreaId.value);
selectedAreaName.value = selected ? selected.name : '';
}
showAreaPicker.value = false;
};
const chooseLocation = () => {
console.log('chooseLocation called');
@@ -180,27 +280,60 @@
showAddPopup.value = false;
setTimeout(() => {
uni.chooseLocation({
success: (res) => {
console.log('选择位置成功:', res);
// 获取选择的位置信息
selectedAddress.value = res.address + (res.name ? `(${res.name})` : '');
lng.value = res.longitude;
lat.value = res.latitude;
// 重新打开弹窗
showAddPopup.value = true;
// 先获取当前位置,再打开地图选择
uni.getLocation({
type: 'gcj02', // 使用国测局坐标系(腾讯地图使用)
success: (locationRes) => {
console.log('获取当前位置成功:', locationRes);
// 使用当前位置作为地图初始中心点
uni.chooseLocation({
latitude: locationRes.latitude,
longitude: locationRes.longitude,
success: (res) => {
console.log('选择位置成功:', res);
// 获取选择的位置信息
selectedAddress.value = res.address + (res.name ? `(${res.name})` : '');
lng.value = res.longitude;
lat.value = res.latitude;
// 重新打开弹窗
showAddPopup.value = true;
},
fail: (err) => {
console.error('选择位置失败:', err);
// 重新打开弹窗
showAddPopup.value = true;
// 用户取消选择不提示
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({
title: '选择位置失败',
icon: 'none'
});
}
}
});
},
fail: (err) => {
console.error('选择位置失败:', err);
// 重新打开弹窗
showAddPopup.value = true;
// 用户取消选择不提示
if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
uni.showToast({
title: '选择位置失败',
icon: 'none'
});
}
console.error('获取当前位置失败:', err);
// 即使获取位置失败,也打开地图选择(不带初始位置)
uni.chooseLocation({
success: (res) => {
console.log('选择位置成功:', res);
selectedAddress.value = res.address + (res.name ? `(${res.name})` : '');
lng.value = res.longitude;
lat.value = res.latitude;
showAddPopup.value = true;
},
fail: (chooseErr) => {
console.error('选择位置失败:', chooseErr);
showAddPopup.value = true;
if (chooseErr.errMsg && chooseErr.errMsg.indexOf('cancel') === -1) {
uni.showToast({
title: '选择位置失败',
icon: 'none'
});
}
}
});
}
});
}, 300);
@@ -250,19 +383,21 @@
// 构建请求参数
const params = {
title: formData.title, //标题
level: formData.level + 1, // 1.轻微隐患 2.一般隐患 3.重大隐患
lng: lng.value || 0, //经度
lat: lat.value || 0, //纬度
address: selectedAddress.value || '', //详细地址
areaId: selectedAreaId.value || null, //隐患区域ID
description: formData.description || '', //隐患描述
tagId: tagId, //隐患标签ID
taskId: taskId.value, //关联任务ID
checkPointId: checkPointId.value, //关联检查点ID
source: sourceOptions.value[formData.source]?.title || '', //隐患来源(随手拍、企业自查、行业互查、专家诊查)
attachments: attachments, //附件列表(图片/视频)
};
console.log('提交的参数:', params);
//
try {
const res = await addHiddenDanger(params);
@@ -278,6 +413,8 @@
formData.description = '';
formData.tagIndex = 0;
selectedAddress.value = '';
selectedAreaId.value = '';
selectedAreaName.value = '';
fileList1.value = [];
// 刷新隐患列表
fetchHiddenDangerList();
@@ -320,6 +457,7 @@
// 页面显示时刷新列表(从交办、验收页面返回时自动刷新)
onShow(() => {
fetchHiddenDangerList();
});
const details = (item) => {
@@ -332,6 +470,14 @@
url: `/pages/hiddendanger/rectification?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
// 编辑整改信息(待验收状态)
const editRectification = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/rectification?rectifyId=${item.rectifyId}&isEdit=1`
})
}
const acceptance = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/acceptance?hazardId=${item.hazardId}&assignId=${item.assignId}&rectifyId=${item.rectifyId}`
@@ -476,6 +622,31 @@
.page {
min-height: 100vh;
background: #EBF2FC;
padding-bottom: 120rpx; // 给底部按钮留出空间
}
// 固定在底部的悬浮新增按钮
.fixed-add-btn {
position: fixed;
bottom: 40rpx;
left: 30rpx;
right: 30rpx;
height: 88rpx;
background: linear-gradient(135deg, #667eea 0%, #2668EA 100%);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 32rpx;
font-weight: 500;
box-shadow: 0 8rpx 24rpx rgba(38, 104, 234, 0.4);
z-index: 100;
.cuIcon-add {
margin-right: 10rpx;
font-size: 36rpx;
}
}
.list-list {
@@ -576,18 +747,8 @@
align-items: center;
gap: 20rpx;
.address-input {
.address-input-wrapper {
flex: 1;
background: #fff;
border: 1rpx solid #F6F6F6;
;
border-radius: 8rpx;
padding: 20rpx;
font-size: 26rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.btn-address {
@@ -605,6 +766,87 @@
}
}
// 选择器触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
.select-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.placeholder {
color: #c0c4cc;
}
}
}
// 选择弹窗样式
.picker-popup {
background: #fff;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.picker-cancel {
font-size: 28rpx;
color: #999;
}
.picker-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.picker-confirm {
font-size: 28rpx;
color: #2667E9;
}
}
.picker-body {
max-height: 600rpx;
padding: 0 30rpx;
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
font-size: 30rpx;
color: #333;
&:last-child {
border-bottom: none;
}
&.picker-item-active {
color: #2667E9;
}
}
}
// 区域颜色圆点
.area-color-dot {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
margin-right: 16rpx;
}
.address-popup {
width: 600rpx;
background: #fff;

View File

@@ -1,58 +1,91 @@
<template>
<view class="page padding">
<view class="padding bg-white radius">
<view class="flex margin-bottom">
<view class="form-label margin-bottom">
<view class="text-gray">整改方案</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="formData.rectifyPlan" placeholder="请输入内容"></up-textarea>
<view class="flex margin-bottom margin-top">
<view class="form-label margin-bottom margin-top">
<view class="text-gray">整改完成情况</view>
<view class="text-red">*</view>
</view>
<up-textarea v-model="formData.rectifyResult" placeholder="请输入内容"></up-textarea>
<view class="flex margin-bottom">
<view class="text-gray margin-top">投资资金(计划)</view>
<view class="form-label margin-bottom margin-top">
<view class="text-gray">投资资金(计划)</view>
<view class="text-red">*</view>
</view>
<up-input v-model="formData.planCost" placeholder="请输入内容" type="number"></up-input>
<view class="flex margin-bottom">
<view class="text-gray margin-top">投资资金(实际)</view>
<view class="form-label margin-bottom margin-top">
<view class="text-gray">投资资金(实际)</view>
<view class="text-red">*</view>
</view>
<up-input v-model="formData.actualCost" placeholder="请输入内容" type="number"></up-input>
<view class="flex margin-bottom">
<view class="text-gray margin-top">限定整改时间</view>
<view class="form-label margin-bottom margin-top">
<view class="text-gray">限定整改时间</view>
<view class="text-red">*</view>
</view>
<up-datetime-picker hasInput :show="show" v-model="value1" mode="date"></up-datetime-picker>
<view class="flex margin-bottom margin-top">
<view class="form-label margin-bottom margin-top">
<view class="text-gray">整改人员</view>
<view class="text-red">*</view>
<view class="margin-left-sm text-black" v-if="selectedUserName">{{ selectedUserName }}</view>
</view>
<up-select v-model:current="cateId" :options="cateList" @select="selectItem"></up-select>
<!-- 点击打开人员选择弹窗 -->
<view class="select-trigger" @click="showUserPopup = true">
<view class="select-content" :class="{ 'text-gray': selectedUsers.length === 0 }">
{{ selectedUsers.length > 0 ? selectedUsersText : '请选择整改人员(可多选)' }}
</view>
<text class="cuIcon-unfold"></text>
</view>
<view class="flex margin-bottom">
<!-- 人员多选弹窗 -->
<u-popup :show="showUserPopup" mode="bottom" round="20" @close="showUserPopup = false">
<view class="user-popup">
<view class="popup-header">
<view class="popup-title text-bold">选择整改人员</view>
<view class="popup-close" @click="showUserPopup = false">×</view>
</view>
<view class="popup-body">
<up-checkbox-group v-model="selectedUserIds" placement="column" @change="onUserChange">
<view class="user-item" v-for="item in cateList" :key="item.id">
<up-checkbox
:label="item.name"
:name="item.id"
activeColor="#2667E9"
shape="square"
></up-checkbox>
</view>
</up-checkbox-group>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="showUserPopup = false">取消</button>
<button class="btn-confirm bg-blue" @click="confirmUserSelect">确定</button>
</view>
</view>
</u-popup>
<view class="form-label margin-bottom margin-top">
<view class="text-gray">整改图片/视频</view>
<view class="text-red">*</view>
</view>
<up-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple
:maxCount="10"></up-upload>
<button class="bg-blue round margin-top-xl" @click="handleSubmit">提交整改</button>
<button class="bg-blue round margin-top-xl" @click="handleSubmit">{{ isEdit ? '保存修改' : '提交整改' }}</button>
</view>
</view>
</template>
<script setup>
import {ref,reactive,onMounted} from 'vue'
import {ref,reactive,computed} from 'vue'
import {onLoad} from '@dcloudio/uni-app'
import {submitRectification,getDepartmentPersonUsers} from '@/request/api.js'
import {submitRectification,getDepartmentPersonUsers,getRectifyDetail,getDeptUsersWithSubordinates} from '@/request/api.js'
import {baseUrl,getToken} from '@/request/request.js'
// 从页面参数获取的ID
const hazardId = ref('');
const assignId = ref('');
const rectifyId = ref(''); // 整改ID编辑模式时使用
const isEdit = ref(false); // 是否为编辑模式
// 表单数据
const formData = reactive({
@@ -66,17 +99,42 @@
const value1 = ref(Date.now());
const radiovalue1 = ref('');
// 整改人员
const cateId = ref('')
// 整改人员(多选)
const cateList = ref([])
const selectedUserName = ref('') // 选中的人员名称
const showUserPopup = ref(false) // 人员选择弹窗
const selectedUserIds = ref([]) // 选中的用户ID数组
const selectedUsers = ref([]) // 选中的用户对象数组
// 选中人员的显示文本
const selectedUsersText = computed(() => {
if (selectedUsers.value.length === 0) return '';
if (selectedUsers.value.length <= 2) {
return selectedUsers.value.map(u => u.name).join('、');
}
return `${selectedUsers.value[0].name}${selectedUsers.value.length}`;
});
// checkbox变化事件
const onUserChange = (ids) => {
console.log('选中的ID:', ids);
};
// 确认选择人员
const confirmUserSelect = () => {
// 根据选中的ID获取用户对象
selectedUsers.value = cateList.value.filter(item => selectedUserIds.value.includes(item.id));
showUserPopup.value = false;
console.log('选中的整改人员:', selectedUsers.value);
};
// 获取部门人员列表
const fetchDeptUsers = async () => {
console.log('当前hazardId:', hazardId.value);
try {
const res = await getDepartmentPersonUsers();
const res = await getDeptUsersWithSubordinates({hazardId:hazardId.value});
if (res.code === 0 && res.data) {
// 将部门下的用户数据扁平化为 up-select 需要的格式
// 将部门下的用户数据扁平化
const userList = [];
res.data.forEach(dept => {
if (dept.users && dept.users.length > 0) {
@@ -95,9 +153,6 @@
console.error('获取部门人员失败:', error);
}
};
// 页面加载时获取人员列表
fetchDeptUsers();
// 上传图片
const fileList1 = ref([]);
@@ -173,6 +228,13 @@
});
return;
}
if (selectedUsers.value.length === 0) {
uni.showToast({
title: '请选择整改人员',
icon: 'none'
});
return;
}
// 构建附件列表
const attachments = fileList1.value.map(file => {
@@ -198,14 +260,21 @@
rectifyResult: formData.rectifyResult,
planCost: Number(formData.planCost) || 0,
actualCost: Number(formData.actualCost) || 0,
attachments: attachments
attachments: attachments,
// 整改人员ID数组
rectifyUserIds: selectedUserIds.value.map(id => Number(id))
};
// 编辑模式需要传递rectifyId
if (rectifyId.value) {
params.rectifyId = rectifyId.value;
}
try {
const res = await submitRectification(params);
if (res.code === 0) {
uni.showToast({
title: '提交成功',
title: isEdit.value ? '保存成功' : '提交成功',
icon: 'success'
});
setTimeout(() => {
@@ -213,19 +282,76 @@
}, 1500);
} else {
uni.showToast({
title: res.msg || '提交失败',
title: res.msg || (isEdit.value ? '保存失败' : '提交失败'),
icon: 'none'
});
}
} catch (error) {
console.error('提交整改失败:', error);
uni.showToast({
title: '您不是整改人员',
title: '操作失败',
icon: 'none'
});
}
};
// 获取整改详情(编辑模式)
const fetchRectifyDetail = async () => {
try {
uni.showLoading({ title: '加载中...' });
const res = await getRectifyDetail({ rectifyId: rectifyId.value });
uni.hideLoading();
if (res.code === 0 && res.data) {
const data = res.data;
// 回显表单数据
formData.rectifyPlan = data.rectifyPlan || '';
formData.rectifyResult = data.rectifyResult || '';
formData.planCost = data.planCost ? String(data.planCost) : '';
formData.actualCost = data.actualCost ? String(data.actualCost) : '';
// 保存hazardId和assignId
hazardId.value = data.hazardId || '';
assignId.value = data.assignId || '';
// 回显附件
if (data.attachments && data.attachments.length > 0) {
fileList1.value = data.attachments.map(att => ({
url: att.filePath.startsWith('http') ? att.filePath : (baseUrl.replace('/api', '') + att.filePath),
status: 'success',
message: '',
name: att.fileName,
type: att.fileType,
filePath: att.filePath // 保存原始路径用于提交
}));
}
// 回显整改人员(如果有)
if (data.memberIds) {
const memberIdArr = data.memberIds.split(',').map(id => String(id.trim()));
selectedUserIds.value = memberIdArr;
// 等人员列表加载完成后再匹配
setTimeout(() => {
selectedUsers.value = cateList.value.filter(item => memberIdArr.includes(item.id));
}, 500);
} else if (data.rectifierId) {
// 如果没有memberIds使用rectifierId
selectedUserIds.value = [String(data.rectifierId)];
setTimeout(() => {
selectedUsers.value = cateList.value.filter(item => item.id === String(data.rectifierId));
}, 500);
}
// 设置页面标题
uni.setNavigationBarTitle({ title: '编辑整改信息' });
}
} catch (error) {
uni.hideLoading();
console.error('获取整改详情失败:', error);
uni.showToast({ title: '获取详情失败', icon: 'none' });
}
};
onLoad((options) => {
if (options.hazardId) {
hazardId.value = options.hazardId;
@@ -233,13 +359,18 @@
if (options.assignId) {
assignId.value = options.assignId;
}
// 在hazardId赋值后调用确保有值
fetchDeptUsers();
// 编辑模式
if (options.rectifyId) {
rectifyId.value = options.rectifyId;
isEdit.value = options.isEdit === '1';
// 获取整改详情
fetchRectifyDetail();
}
});
// 选择整改人员
const selectItem = (item) => {
console.log('选择的整改人员:', item);
cateId.value = item.id;
selectedUserName.value = item.name; // 显示选中的人员名称
};
</script>
<style lang="scss" scoped>
@@ -247,6 +378,17 @@
min-height: 100vh;
background: #EBF2FC;
}
// 表单标签样式 - 让*号和文字对齐
.form-label {
display: flex;
align-items: center;
.text-red {
margin-left: 4rpx;
line-height: 1;
}
}
.date-input {
background: #fff;
@@ -260,4 +402,87 @@
color: #333;
}
}
// 选择触发器样式
.select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border: 1rpx solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx 24rpx;
margin-bottom: 20rpx;
.select-content {
flex: 1;
font-size: 28rpx;
color: #333;
}
}
// 人员选择弹窗
.user-popup {
background: #fff;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
.popup-title {
font-size: 32rpx;
color: #333;
}
.popup-close {
font-size: 40rpx;
color: #999;
line-height: 1;
}
}
.popup-body {
padding: 20rpx 30rpx;
max-height: 600rpx;
overflow-y: auto;
}
.user-item {
padding: 24rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
}
.popup-footer {
display: flex;
border-top: 1rpx solid #eee;
button {
flex: 1;
height: 90rpx;
line-height: 90rpx;
border-radius: 0;
font-size: 30rpx;
&::after {
border: none;
}
}
.btn-cancel {
background: #fff;
color: #666;
}
.btn-confirm {
color: #fff;
}
}
}
</style>

View File

@@ -6,9 +6,9 @@
<view class="text-red">*</view>
</view>
<view class="margin-bottom">
<view v-if="rectifyAttachments.length > 0" class="margin-top">
<view v-if="detailData.attachments && detailData.attachments.length > 0" class="margin-top">
<view class="flex" style="flex-wrap: wrap; gap: 10rpx;">
<image v-for="(img, idx) in rectifyAttachments" :key="idx" :src="getFullPath(img.filePath)" style="width: 136rpx;height: 136rpx;border-radius: 16rpx;" mode="aspectFill" @click="previewRectifyImage(idx)"></image>
<image v-for="(img, idx) in detailData.attachments" :key="idx" :src="getFullPath(img.filePath)" style="width: 136rpx;height: 136rpx;border-radius: 16rpx;" mode="aspectFill" @click="previewHazardImage(idx)"></image>
</view>
</view>
<view v-else class="text-gray text-sm">暂无图片</view>
@@ -44,7 +44,12 @@
<button class="address-btn bg-blue">选择地址</button>
</view>
<view class="text-gray text-sm">办公楼3层东侧消防通道生产车间A区设备旁等或点击"选择地址"按钮在地图上选择</view>
<view class="flex margin-bottom ">
<!-- 隐患区域 -->
<view class="text-gray margin-top margin-bottom">隐患区域</view>
<view class="bg-gray padding radius">{{ detailData.areaName || '暂无' }}</view>
<view class="flex margin-bottom margin-top">
<view class="text-gray">隐患描述</view>
<view class="text-red">*</view>
</view>
@@ -74,6 +79,7 @@
source: '',
description: '',
address: '',
areaName: '', // 隐患区域名称
createdAt: '',
attachments: []
});
@@ -93,8 +99,9 @@
};
// 图片预览 - 隐患图片
const previewImage = (attachments, index) => {
const urls = attachments.map(item => getFullPath(item.filePath));
const previewHazardImage = (index) => {
if (!detailData.attachments || detailData.attachments.length === 0) return;
const urls = detailData.attachments.map(item => getFullPath(item.filePath));
uni.previewImage({
current: index,
urls: urls
@@ -116,6 +123,8 @@
const res = await getHiddenDangerDetail({ hazardId, assignId });
if (res.code === 0 && res.data) {
Object.assign(detailData, res.data);
console.log('隐患详情数据:', res.data);
console.log('隐患附件:', res.data.attachments);
// 提取整改附件assigns[0].rectify.attachments
if (res.data.assigns && res.data.assigns.length > 0) {

View File

@@ -2,9 +2,9 @@
<view class="content">
<view class="flex padding-top-xl padding-bottom-xl text-white " style="background-color:#007aff ;">
<view class="cu-avatar xl round margin-left">
<image></image>
<image class="avatar-image" :src="getImageUrl(userInfo.avatar) || defaultAvatar" mode="aspectFill"></image>
</view>
<view class="padding-left">
<view class="padding-left" style="display: flex;flex-direction: column;gap: 10rpx;justify-content: center;align-items: center;">
<view class="text-bold">{{ userInfo.deptName || '未知部门' }}</view>
<view class="flex padding-top-xs">
<view>用户</view>
@@ -82,7 +82,8 @@
</view>
<view class="margin-top margin-bottom flex justify-end">
<button class="cu-btn round lg light bg-blue margin-right" @click.stop="ViewDetails(item)">查看详情</button>
<button class="cu-btn round lg bg-blue" @click.stop="goDetails(item)">开始检查</button>
<button v-if="item.finishedCount < item.totalCount" class="cu-btn round lg bg-blue" @click.stop="goDetails(item)">开始检查</button>
<view v-else class="cu-btn round lg bg-green">已完成</view>
</view>
</view>
</view>
@@ -92,10 +93,13 @@
<view class="border-tite"></view>
<view class="text-bold margin-left-xs">我的隐患排查</view>
</view>
<view class="list-list padding margin-bottom" v-for="(item,index) in hiddenDangerData" :key="item.id" @click="HazardList()">
<view class="flex text-bold">
<view>隐患</view>
<view class="text-bold margin-left">#15</view>
<view class="list-list padding margin-bottom" v-for="(item,index) in hiddenDangerData" :key="item.hazardId">
<view class="flex text-bold justify-between">
<view class="flex">
<view>隐患</view>
<view class="text-bold margin-left">#{{ index + 1 }}</view>
</view>
<view class="text-blue">{{item.statusName}}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">标题</view>
@@ -111,20 +115,32 @@
</view>
<view class="flex margin-top">
<view class="text-gray">隐患等级</view>
<view>{{item.levelName}}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">隐患状态</view>
<view>{{item.statusName}}</view>
<view class="level-tag" :class="{
'level-minor': item.levelName === '轻微隐患',
'level-normal': item.levelName === '一般隐患',
'level-major': item.levelName === '重大隐患'
}">{{item.levelName}}</view>
</view>
<view class="flex margin-top">
<view class="text-gray">发现时间</view>
<view>{{item.createdAt}}</view>
</view>
<view class="margin-top margin-bottom flex" style="gap: 5rpx;">
<button class="cu-btn round lg light bg-blue " style="white-space: nowrap;">查看详情</button>
<button class="cu-btn round lg light bg-blue " style="white-space: nowrap;">立即整改</button>
<button class="cu-btn round lg bg-blue " style="white-space: nowrap;">立即验收</button>
<view class="margin-top margin-bottom flex justify-end" style="gap: 10rpx;">
<!-- 所有状态都显示查看详情 -->
<button class="cu-btn round lg light bg-blue" @click.stop="viewHazardDetail(item)">查看详情</button>
<!-- 待整改状态canEdit为true时显示隐患交办和立即整改 -->
<button v-if="item.statusName === '待整改' && item.canEdit"
class="cu-btn round lg light bg-blue" @click.stop="assignHazard(item)">隐患交办</button>
<button v-if="item.statusName === '待整改' && item.canEdit"
class="cu-btn round lg bg-blue" @click.stop="goRectification(item)">立即整改</button>
<!-- 待验收显示编辑整改信息和立即验收 -->
<button v-if="item.statusName === '待验收' && item.canEdit"
class="cu-btn round lg light bg-blue" @click.stop="editRectification(item)">编辑整改信息</button>
<button v-if="item.statusName === '待验收' && canAcceptance"
class="cu-btn round lg bg-blue" @click.stop="goAcceptance(item)">立即验收</button>
<!-- 待交办显示隐患交办 -->
<button v-if="item.statusName === '待交办'"
class="cu-btn round lg bg-blue" @click.stop="assignHazard(item)">隐患交办</button>
</view>
</view>
</view>
@@ -133,35 +149,73 @@
</template>
<script setup>
import { ref, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { ref, reactive, computed } from 'vue';
// import { onLoad } from '@dcloudio/uni-app';
import {getCheckPlanList,getHiddenDangerList} from '@/request/api.js'
import { getProfileDetail } from '@/request/three_one_api/info.js';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { baseUrl } from '@/request/request.js';
const loading = ref(true);
const defaultAvatar = 'https://ossweb-img.qq.com/images/lol/web201310/skin/big99008.jpg';
// 用户信息
const userInfo = reactive({
userId: '',
username: '',
nickName: '',
deptId: '',
deptName: ''
deptName: '',
role: '',
avatar: ''
});
// 获取用户信息
const getUserInfo = () => {
// 获取用户角色判断是否有验收权限admin或manage才能验收
const canAcceptance = computed(() => {
return userInfo.role === 'admin' || userInfo.role === 'manage';
});
// 获取图片完整URL用于显示
const getImageUrl = (path) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return baseUrl + path;
};
// 获取用户信息(从接口获取)
const getUserInfo = async () => {
try {
const storedUserInfo = uni.getStorageSync('userInfo');
if (storedUserInfo) {
const info = JSON.parse(storedUserInfo);
userInfo.userId = info.userId || '';
userInfo.username = info.username || '';
userInfo.nickName = info.nickName || '';
userInfo.deptId = info.deptId || '';
userInfo.deptName = info.deptName || '';
const res = await getProfileDetail();
if (res.code === 0 && res.data) {
userInfo.userId = res.data.userId || '';
userInfo.username = res.data.userName || '';
userInfo.nickName = res.data.nickName || '';
userInfo.deptId = res.data.deptId || '';
userInfo.deptName = res.data.deptName || '';
userInfo.avatar = res.data.avatar || '';
// 获取角色信息
if (res.data.roles && res.data.roles.length > 0) {
userInfo.role = res.data.roles[0].roleKey || '';
}
}
} catch (e) {
console.error('获取用户信息失败:', e);
// 如果接口失败,尝试从本地存储获取
try {
const storedUserInfo = uni.getStorageSync('userInfo');
if (storedUserInfo) {
const info = JSON.parse(storedUserInfo);
userInfo.userId = info.userId || '';
userInfo.username = info.username || '';
userInfo.nickName = info.nickName || '';
userInfo.deptId = info.deptId || '';
userInfo.deptName = info.deptName || '';
userInfo.role = info.role || '';
userInfo.avatar = info.avatar || '';
}
} catch (storageError) {
console.error('从本地存储获取用户信息失败:', storageError);
}
}
};
const infoList = ref([{
@@ -264,9 +318,15 @@
};
// 页面加载时调用接口
onLoad(() => {
// onLoad(() => {
// getUserInfo();
// getCheckPlanLists();
// });
// 页面每次显示时都会加载数据
onShow(() => {
getUserInfo();
getCheckPlanLists();
getHiddenDangerLists();
});
//我的隐患排查
const hiddenDangerParams = ref({
@@ -296,6 +356,42 @@
getHiddenDangerLists();
});
// ========== 隐患排查相关跳转函数 ==========
// 查看隐患详情
const viewHazardDetail = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/view?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
// 立即整改(待整改状态)
const goRectification = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/rectification?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
// 编辑整改信息(待验收状态)
const editRectification = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/rectification?rectifyId=${item.rectifyId}&isEdit=1`
})
}
// 立即验收
const goAcceptance = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/acceptance?hazardId=${item.hazardId}&assignId=${item.assignId}&rectifyId=${item.rectifyId}`
})
}
// 隐患交办
const assignHazard = (item) => {
uni.navigateTo({
url: `/pages/hiddendanger/assignment?hazardId=${item.hazardId}&assignId=${item.assignId}`
})
}
</script>
<style lang="scss" scoped>
@@ -309,7 +405,13 @@
z-index: 10;
min-height: calc(100vh - 400rpx);
}
.content {}
// 头像图片样式
.avatar-image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.grid-list {
gap: 30rpx;
@@ -370,4 +472,31 @@
font-size: 28rpx;
color: #2667E9;
}
// 隐患等级标签样式
.level-tag {
padding: 4rpx 16rpx;
border-radius: 8rpx;
}
// 轻微隐患
.level-minor {
background: #F6FFED;
border: 2rpx solid #B7EB8F;
color: #52C41A;
}
// 一般隐患
.level-normal {
background: #FFF7E6;
border: 2rpx solid #FFD591;
color: #FA8C16;
}
// 重大隐患
.level-major {
background: #FFF1F0;
border: 2rpx solid #FFA39E;
color: #F5222D;
}
</style>

View File

@@ -18,10 +18,10 @@
<input class="sl-input" v-model="password" type="text" maxlength="32" placeholder="请输入密码" :password="showPassword"/>
<image class="eye-img" :src="showPassword ? '/static/index/cl.png' : '/static/index/op.png'" @click="changePassword"></image>
</view>
<view class="agreement">
<!-- <view class="agreement">
<navigator url="reg" open-type="navigate" class="link">注册成员账号</navigator>
<navigator url="forget" open-type="navigate" class="link">忘记密码?</navigator>
</view>
</view> -->
</view>
<view class="padding-lr">
@@ -32,10 +32,10 @@
<text>登录普通成员</text>
</view> -->
<view class="button-report margin-top" hover-class="button-hover" @tap="goToReport">
<!-- <view class="button-report margin-top" hover-class="button-hover" @tap="goToReport">
<image src="/static/index/photos.png" class="icon-image"></image>
<text>随手拍举报</text>
</view>
</view> -->
<!-- <view class="protocol-box">
<navigator url="agreement" open-type="navigate" class="protocol-link">用户协议</navigator>
@@ -112,7 +112,9 @@ const handleLogin = async () => {
username: res.data.username,
nickName: res.data.nickName,
deptId: res.data.deptId,
deptName: res.data.deptName
deptName: res.data.deptName,
role:res.data.role,
isDept:res.data.isDept
};
uni.setStorageSync('userInfo', JSON.stringify(userInfo));

View File

@@ -1,38 +1,50 @@
<template>
<view class=" page padding ">
<view class=" padding bg-white radius margin-bottom" v-for="(item,index) in list" :key="item.id">
<view class="flex justify-between align-center">
<view class="page padding">
<!-- 成员管理卡片 -->
<view class="member-card bg-white radius">
<!-- 卡片头部公司名称 + 角色标签 -->
<view class="card-header">
<view class="flex align-center">
<view class="border-tite"></view>
<view class="text-bold margin-left-xs" @click="show = true">湘西自治州和谐网络科技有限公司</view>
<up-picker :show="show" :columns="columns"></up-picker>
<view class="border-line"></view>
<view class="text-bold margin-left-sm">{{ userInfo.deptName || '未知部门' }}</view>
</view>
<view class="tag-outline">负责人</view>
<view class="role-tag">{{ roleText }}</view>
</view>
<view class="flex margin-top">
<view class="cu-avatar radius lg"
style="background-image:url(https://ossweb-img.qq.com/images/lol/web201310/skin/big81005.jpg);">
<!-- 成员列表 -->
<view class="member-list">
<view
class="member-item"
v-for="(item, index) in list"
:key="item.userId"
:class="{ 'border-bottom': index < list.length - 1 }"
>
<view class="cu-avatar radius lg bg-gray" style="background-image:url(https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png);"></view>
<view class="member-info">
<view class="flex align-center">
<text class="member-name">{{ item.nickName }}</text>
<view class="status-tag" :class="item.statusName === '正常' ? 'status-normal' : 'status-locked'">
{{ item.statusName }}
</view>
</view>
<view class="member-phone text-gray">
<text>手机{{ item.phonenumber || '未设置' }}</text>
</view>
</view>
<button class="btn-lock bg-blue" @click="Lock(item)">
{{ item.status === '1' ? '解锁' : '锁定' }}
</button>
</view>
<view class="margin-left">
<view class="flex">
<view>{{item.nickName}}</view>
<view class="margin-left-xs light bg-olive padding-left-xs padding-right-xs">{{item.statusName}}</view>
</view>
<view class="flex text-gray">
<view>手机设置</view>
<view>{{item.phonenumber}}</view>
</view>
<view class="flex text-gray">
<view>登录IP</view>
<view>45.135.228.172</view>
</view>
</view>
<button class="bg-blue btn-lock" @click="Lock(item)">{{ item.lockStatus === 1 ? '解锁' : '锁定' }}</button>
</view>
<!-- 添加成员按钮 -->
<view class="add-btn-wrapper">
<button class="add-btn" @click="showPopup = true">
<text class="cuIcon-add"></text>
<text>添加成员</text>
</button>
</view>
</view>
<button class="lg cuIcon-add bg-blue round margin-top-xl" @click="showPopup = true">添加成员</button>
<!-- 添加成员弹出框 -->
<u-popup :show="showPopup" mode="center" round="20" @close="showPopup = false">
@@ -42,43 +54,49 @@
<view class="popup-close" @click="showPopup = false">×</view>
</view>
<view class="popup-body">
<scroll-view class="popup-body" scroll-y>
<!-- 用户名 -->
<view class="form-item">
<view class="form-label">用户名<text class="text-red">*</text></view>
<input class="form-input" v-model="formData.username" placeholder="请输入用户名" />
<up-input v-model="formData.username" placeholder="请输入用户名" border="surround"></up-input>
</view>
<!-- 昵称 -->
<view class="form-item">
<view class="form-label">昵称</view>
<input class="form-input" v-model="formData.nickname" placeholder="请输入昵称" />
<up-input v-model="formData.nickname" placeholder="请输入昵称" border="surround"></up-input>
</view>
<!-- 手机号 -->
<view class="form-item">
<view class="form-label">手机号</view>
<input class="form-input" v-model="formData.phone" placeholder="请输入手机号" type="number" />
<up-input v-model="formData.phone" placeholder="请输入手机号" type="number" border="surround"></up-input>
</view>
<!-- 密码 -->
<view class="form-item">
<view class="form-label">密码<text class="text-red">*</text></view>
<input class="form-input" v-model="formData.password" placeholder="请输入密码6-16位" password />
<up-input v-model="formData.password" placeholder="请输入密码6-16位" password border="surround"></up-input>
</view>
<!-- 主部门 -->
<!-- 角色类型 -->
<view class="form-item">
<view class="form-label">主部门<text class="text-red">*</text></view>
<view class="form-input form-select" @click="showDeptPicker = true">
<text :class="formData.department ? '' : 'text-gray'">
{{ formData.department || '请选择主部门' }}
<view class="form-label">角色类型<text class="text-red">*</text></view>
<view class="form-select" @click="showRolePicker = true">
<text :class="selectedRoleName ? '' : 'text-gray'">
{{ selectedRoleName || '请选择角色类型' }}
</text>
<text class="cuIcon-unfold"></text>
</view>
<up-picker :show="showDeptPicker" :columns="deptColumns" @confirm="onDeptConfirm"
@cancel="showDeptPicker = false" @close="showDeptPicker = false"></up-picker>
<up-picker
:show="showRolePicker"
:columns="roleColumns"
@confirm="onRoleConfirm"
@cancel="showRolePicker = false"
@close="showRolePicker = false"
></up-picker>
</view>
</view>
</scroll-view>
<view class="popup-footer">
<button class="btn-cancel" @click="showPopup = false">取消</button>
@@ -91,259 +109,396 @@
</template>
<script setup>
import { ref,reactive } from 'vue';
import { addMember,getMemberList,lockOrUnlockMember} from '@/request/api.js';
//成员列表
const list = ref([]);
getMemberList().then(res => {
list.value = res.data;
});
const showPopup = ref(false);
const showDeptPicker = ref(false);
import { ref, reactive, computed, onMounted } from 'vue';
import { addMember, getMemberList, lockOrUnlockMember } from '@/request/api.js';
const formData = reactive({
username: '',
nickname: '',
phone: '',
password: '',
department: ''
});
const handleSubmit = async () => {
// 表单验证
if (!formData.username) {
uni.showToast({
title: '请输入用户名',
icon: 'none'
});
return;
}
if (!formData.password || formData.password.length < 6 || formData.password.length > 16) {
uni.showToast({
title: '请输入6-16位密码',
icon: 'none'
});
return;
}
// 用户信息从storage获取
const userInfo = ref({
deptId: '',
deptName: '',
nickName: '',
role: '',
userId: '',
username: ''
});
// 构建请求参数(根据接口要求的字段名)
const params = {
userName: formData.username,
nickName: formData.nickname || '',
phonenumber: formData.phone || '',
password: formData.password,
roleType: 'common'
};
// 角色显示文本
const roleText = computed(() => {
const role = userInfo.value.role;
if (role === 'manage' || role === 'admin') {
return '管理人员';
} else if (role === 'common') {
return '执行人员';
}
return '成员';
});
try {
const res = await addMember(params);
if (res.code === 0) {
uni.showToast({
title: '添加成功',
icon: 'success'
});
showPopup.value = false;
// 重置表单
formData.username = '';
formData.nickname = '';
formData.phone = '';
formData.password = '';
formData.department = '';
} else {
uni.showToast({
title: res.msg || '添加失败',
icon: 'none'
});
}
} catch (error) {
console.error('添加成员失败:', error);
uni.showToast({
title: '请求失败',
icon: 'none'
});
// 获取用户信息
const getUserInfo = () => {
try {
const userInfoStr = uni.getStorageSync('userInfo');
if (userInfoStr) {
userInfo.value = JSON.parse(userInfoStr);
console.log('用户信息:', userInfo.value);
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
};
// 成员列表
const list = ref([]);
// 获取成员列表
const fetchMemberList = async () => {
try {
const res = await getMemberList();
if (res.code === 0 && res.data) {
list.value = res.data;
console.log('成员列表:', res.data);
}
} catch (error) {
console.error('获取成员列表失败:', error);
}
};
// 弹窗控制
const showPopup = ref(false);
const showRolePicker = ref(false);
const selectedRoleName = ref('');
// 表单数据
const formData = reactive({
username: '',
nickname: '',
phone: '',
password: '',
roleType: ''
});
// 角色类型选择器数据
const roleColumns = reactive([
['管理员', '普通成员']
]);
// 角色名称与值的映射
const roleMap = {
'管理员': 'manage',
'普通成员': 'common'
};
// 角色类型选择确认
const onRoleConfirm = (e) => {
if (e.value && e.value.length > 0) {
selectedRoleName.value = e.value[0];
formData.roleType = roleMap[e.value[0]];
}
showRolePicker.value = false;
};
// 重置表单
const resetForm = () => {
formData.username = '';
formData.nickname = '';
formData.phone = '';
formData.password = '';
formData.roleType = '';
selectedRoleName.value = '';
};
// 提交表单
const handleSubmit = async () => {
if (!formData.username) {
uni.showToast({ title: '请输入用户名', icon: 'none' });
return;
}
if (!formData.password || formData.password.length < 6 || formData.password.length > 16) {
uni.showToast({ title: '请输入6-16位密码', icon: 'none' });
return;
}
if (!formData.roleType) {
uni.showToast({ title: '请选择角色类型', icon: 'none' });
return;
}
const params = {
userName: formData.username,
nickName: formData.nickname || '',
phonenumber: formData.phone || '',
password: formData.password,
roleType: formData.roleType
};
// 锁定/解锁成员
const Lock = (item) => {
// 当前是锁定状态则解锁,否则锁定
const isLocked = item.lockStatus === 1;
const actionText = isLocked ? '解锁' : '锁定';
const newLockStatus = isLocked ? 0 : 1;
uni.showModal({
title: '提示',
content: `确定要${actionText}该成员吗?`,
confirmColor: '#2667E9',
success: async (res) => {
if (res.confirm) {
try {
const result = await lockOrUnlockMember({
userId: item.userId,
lockStatus: newLockStatus
});
if (result.code === 0) {
uni.showToast({
title: `${actionText}成功`,
icon: 'success'
});
// 更新本地状态
item.lockStatus = newLockStatus;
item.statusName = newLockStatus === 1 ? '已锁定' : '正常';
} else {
uni.showToast({
title: result.msg || `${actionText}失败`,
icon: 'none'
});
}
} catch (error) {
console.error(`${actionText}成员失败:`, error);
uni.showToast({
title: '请求失败',
icon: 'none'
});
try {
const res = await addMember(params);
if (res.code === 0) {
uni.showToast({ title: '添加成功', icon: 'success' });
showPopup.value = false;
resetForm();
// 刷新成员列表
fetchMemberList();
} else {
uni.showToast({ title: res.msg || '添加失败', icon: 'none' });
}
} catch (error) {
console.error('添加成员失败:', error);
uni.showToast({ title: '请求失败', icon: 'none' });
}
};
// 锁定/解锁成员
const Lock = (item) => {
const isLocked = item.status === '1';
const actionText = isLocked ? '解锁' : '锁定';
const newStatus = isLocked ? '0' : '1';
uni.showModal({
title: '提示',
content: `确定要${actionText}该成员吗?`,
confirmColor: '#2667E9',
success: async (res) => {
if (res.confirm) {
try {
const result = await lockOrUnlockMember({
userId: item.userId,
lockStatus: Number(newStatus)
});
if (result.code === 0) {
uni.showToast({ title: `${actionText}成功`, icon: 'success' });
// 更新本地状态
item.status = newStatus;
item.statusName = newStatus === '1' ? '已锁定' : '正常';
} else {
uni.showToast({ title: result.msg || `${actionText}失败`, icon: 'none' });
}
} catch (error) {
console.error(`${actionText}成员失败:`, error);
uni.showToast({ title: '请求失败', icon: 'none' });
}
}
});
};
//选择部门(顶部切换用)
const show = ref(false);
const columns = reactive([
['湘西自治州和谐网络科技有限公司', '湘西自治州和谐云科技有限公司']
]);
// 主部门选择器数据
const deptColumns = reactive([
['湘西自治州和谐网络科技有限公司', '湘西自治州和谐云科技有限公司', '研发部门', '深圳总公司', '若依科技']
]);
// 主部门选择确认
const onDeptConfirm = (e) => {
console.log('选择的部门:', e);
// e.value 是选中的值数组
if (e.value && e.value.length > 0) {
formData.department = e.value[0];
}
showDeptPicker.value = false;
};
});
};
// 页面加载
onMounted(() => {
getUserInfo();
fetchMemberList();
});
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
.page {
min-height: 100vh;
background: #EBF2FC;
}
.border-tite {
// 成员卡片
.member-card {
padding: 0;
overflow: hidden;
}
// 卡片头部
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.border-line {
width: 8rpx;
height: 32rpx;
background: #2667E9;
border-radius: 8rpx;
}
}
.tag-outline {
padding: 4rpx 16rpx;
border-radius: 8rpx;
background: #EEF3FF;
color: #2E7CF3;
font-size: 24rpx;
// 角色标签
.role-tag {
padding: 8rpx 24rpx;
background: #EEF3FF;
color: #2667E9;
font-size: 24rpx;
border-radius: 24rpx 0 0 24rpx;
margin-right: -30rpx;
}
// 成员列表
.member-list {
padding: 0 30rpx;
}
// 成员项
.member-item {
display: flex;
align-items: center;
padding: 24rpx 0;
&.border-bottom {
border-bottom: 1rpx solid #f5f5f5;
}
.cu-avatar {
flex-shrink: 0;
margin-right: -30rpx;
border-radius: 24rpx 0rpx 0rpx 24rpx;
}
}
.btn-lock {
width: 112rpx;
height: 52rpx;
line-height: 52rpx;
padding: 0;
font-size: 26rpx;
display: flex;
align-items: center;
justify-content: center;
}
// 弹出框样式
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.popup-title {
font-size: 34rpx;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
line-height: 1;
}
.popup-body {
max-height: 700rpx;
overflow-y: auto;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
}
.form-input {
width: 100%;
height: 80rpx;
border: 2rpx solid #E5E5E5;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.form-select {
display: flex;
align-items: center;
line-height: 80rpx;
}
.popup-footer {
display: flex;
justify-content: center;
gap: 30rpx;
margin-top: 40rpx;
}
.btn-cancel {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border: 2rpx solid #2667E9;
border-radius: 40rpx;
background: #fff;
color: #2667E9;
// 成员信息
.member-info {
flex: 1;
margin-left: 20rpx;
overflow: hidden;
.member-name {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
.member-phone {
font-size: 24rpx;
margin-top: 8rpx;
}
}
.btn-confirm {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
color: #fff;
font-size: 30rpx;
// 状态标签
.status-tag {
margin-left: 12rpx;
padding: 4rpx 12rpx;
font-size: 22rpx;
border-radius: 6rpx;
&.status-normal {
background: #E8F5E9;
color: #4CAF50;
}
</style>
&.status-locked {
background: #FFEBEE;
color: #F44336;
}
}
// 锁定按钮
.btn-lock {
width: 120rpx;
height: 56rpx;
line-height: 56rpx;
padding: 0;
font-size: 26rpx;
border-radius: 28rpx;
flex-shrink: 0;
}
// 添加成员按钮
.add-btn-wrapper {
padding: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.add-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
background: #fff;
border: 2rpx dashed #2667E9;
border-radius: 12rpx;
color: #2667E9;
font-size: 30rpx;
.cuIcon-add {
margin-right: 10rpx;
font-size: 32rpx;
}
&::after {
border: none;
}
}
// 弹出框样式
.popup-content {
width: 600rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 34rpx;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
line-height: 1;
}
.popup-body {
padding: 30rpx;
max-height: 700rpx;
overflow-y: auto;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
display: flex;
align-items: center;
}
.form-select {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
border: 2rpx solid #dadbde;
border-radius: 8rpx;
padding: 0 24rpx;
font-size: 28rpx;
}
.popup-footer {
display: flex;
border-top: 1rpx solid #eee;
button {
flex: 1;
height: 90rpx;
line-height: 90rpx;
border-radius: 0;
font-size: 30rpx;
&::after {
border: none;
}
}
}
.btn-cancel {
background: #fff;
color: #666;
}
.btn-confirm {
color: #fff;
}
</style>

View File

@@ -1,24 +1,178 @@
<template>
<view class="padding page">
<view class="padding bg-white">
<view class="flex justify-between padding-bottom solid-bottom">
<view>修改登录密码</view>
<view class="lg text-gray cuIcon-right"></view>
<view class="page">
<view class="padding bg-white margin-top">
<!-- 旧密码 -->
<view class="form-item solid-bottom">
<view class="form-label">旧密码</view>
<view class="form-input-wrap">
<input
class="form-input"
v-model="formData.oldPassword"
:type="showOldPwd ? 'text' : 'password'"
placeholder="请输入旧密码"
/>
<view class="pwd-toggle" @click="showOldPwd = !showOldPwd">
<text :class="showOldPwd ? 'cuIcon-attention' : 'cuIcon-attentionforbid'"></text>
</view>
</view>
</view>
<view class="flex justify-between padding-top padding-bottom solid-bottom">
<view>注销账户</view>
<view class="lg text-gray cuIcon-right"></view>
<!-- 新密码 -->
<view class="form-item solid-bottom">
<view class="form-label">新密码</view>
<view class="form-input-wrap">
<input
class="form-input"
v-model="formData.newPassword"
:type="showNewPwd ? 'text' : 'password'"
placeholder="请输入新密码"
/>
<view class="pwd-toggle" @click="showNewPwd = !showNewPwd">
<text :class="showNewPwd ? 'cuIcon-attention' : 'cuIcon-attentionforbid'"></text>
</view>
</view>
</view>
<!-- 确认密码 -->
<view class="form-item">
<view class="form-label">确认密码</view>
<view class="form-input-wrap">
<input
class="form-input"
v-model="formData.confirmPassword"
:type="showConfirmPwd ? 'text' : 'password'"
placeholder="请再次输入新密码"
/>
<view class="pwd-toggle" @click="showConfirmPwd = !showConfirmPwd">
<text :class="showConfirmPwd ? 'cuIcon-attention' : 'cuIcon-attentionforbid'"></text>
</view>
</view>
</view>
</view>
<!-- 提示信息 -->
<view class="tips">
<text class="text-gray text-sm">密码长度至少6位建议包含字母和数字</text>
</view>
<!-- 保存按钮 -->
<view class="padding">
<button class="bg-blue round" @click="handleSave" :loading="saving">保存</button>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { updatePassword } from '@/request/three_one_api/info.js';
const saving = ref(false);
const showOldPwd = ref(false);
const showNewPwd = ref(false);
const showConfirmPwd = ref(false);
const formData = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
});
// 保存
const handleSave = async () => {
// 表单验证
if (!formData.oldPassword) {
uni.showToast({ title: '请输入旧密码', icon: 'none' });
return;
}
if (!formData.newPassword) {
uni.showToast({ title: '请输入新密码', icon: 'none' });
return;
}
if (formData.newPassword.length < 6) {
uni.showToast({ title: '新密码长度至少6位', icon: 'none' });
return;
}
if (!formData.confirmPassword) {
uni.showToast({ title: '请确认新密码', icon: 'none' });
return;
}
if (formData.newPassword !== formData.confirmPassword) {
uni.showToast({ title: '两次输入的密码不一致', icon: 'none' });
return;
}
if (formData.oldPassword === formData.newPassword) {
uni.showToast({ title: '新密码不能与旧密码相同', icon: 'none' });
return;
}
saving.value = true;
try {
const params = {
oldPassword: formData.oldPassword,
newPassword: formData.newPassword,
confirmPassword: formData.confirmPassword
};
const res = await updatePassword(params);
if (res.code === 0) {
uni.showToast({ title: '密码修改成功', icon: 'success' });
// 清空表单
formData.oldPassword = '';
formData.newPassword = '';
formData.confirmPassword = '';
// 延迟返回
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({ title: res.msg || '修改失败', icon: 'none' });
}
} catch (err) {
console.error('修改密码失败:', err);
uni.showToast({ title: '修改失败', icon: 'none' });
} finally {
saving.value = false;
}
};
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #EBF2FC;
}
</style>
.page {
min-height: 100vh;
background: #EBF2FC;
}
.form-item {
padding: 30rpx 0;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.form-input-wrap {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
}
.form-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
}
.pwd-toggle {
padding: 10rpx;
font-size: 36rpx;
color: #999;
}
.tips {
padding: 20rpx 30rpx;
}
</style>

View File

@@ -5,7 +5,7 @@
<view class="flex justify-between align-center padding-tb solid-bottom" @click="chooseAvatar">
<view class="text-black">头像</view>
<view class="flex align-center">
<image class="avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<image class="avatar" :src="avatarPreview || getImageUrl(userInfo.avatar) || defaultAvatar" mode="aspectFill"></image>
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
@@ -14,25 +14,25 @@
<view class="flex justify-between align-center padding-tb solid-bottom">
<view class="text-black label-text">昵称</view>
<view class="flex align-center flex-sub justify-end">
<input class="input-right" v-model="userInfo.nickname" placeholder="请输入昵称" />
<input class="input-right" v-model="userInfo.nickName" placeholder="请输入昵称" />
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
<!-- 用户名 -->
<!-- 电话号码 -->
<view class="flex justify-between align-center padding-tb solid-bottom">
<view class="text-black label-text">用户名</view>
<view class="flex align-center">
<text class="text-black">{{ userInfo.username }}</text>
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
<!-- 个性签名 -->
<view class="flex justify-between align-center padding-tb solid-bottom">
<view class="text-black label-text">个性签名</view>
<view class="text-black label-text">电话号码</view>
<view class="flex align-center flex-sub justify-end">
<input class="input-right" v-model="userInfo.signature" placeholder="请输入个性签名" />
<input class="input-right" v-model="userInfo.phonenumber" placeholder="请输入电话号码" type="number" />
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
<!-- 邮箱 -->
<view class="flex justify-between align-center padding-tb solid-bottom">
<view class="text-black label-text">邮箱</view>
<view class="flex align-center flex-sub justify-end">
<input class="input-right" v-model="userInfo.email" placeholder="请输入邮箱" />
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
@@ -44,15 +44,15 @@
<view class="gender-switch">
<view
class="gender-item"
:class="{ 'gender-active': userInfo.gender === 1, 'gender-male': userInfo.gender === 1 }"
@click="userInfo.gender = 1"
:class="{ 'gender-active': userInfo.sex === '0', 'gender-male': userInfo.sex === '0' }"
@click="userInfo.sex = '0'"
>
<text class="cuIcon-male"></text>
</view>
<view
class="gender-item"
:class="{ 'gender-active': userInfo.gender === 2, 'gender-female': userInfo.gender === 2 }"
@click="userInfo.gender = 2"
:class="{ 'gender-active': userInfo.sex === '1', 'gender-female': userInfo.sex === '1' }"
@click="userInfo.sex = '1'"
>
<text class="cuIcon-female"></text>
</view>
@@ -60,42 +60,59 @@
</view>
</view>
<!-- 日期选择 -->
<view class="flex justify-between align-center padding-tb solid-bottom" @click="showDatePicker = true">
<view class="text-black">日期选择</view>
<view class="flex align-center">
<text class="text-black">{{ userInfo.birthday || '请选择日期' }}</text>
<view class="lg text-gray cuIcon-right margin-left-xs"></view>
</view>
</view>
<up-calendar
:show="showDatePicker"
mode="single"
@confirm="confirmDate"
@close="showDatePicker = false"
></up-calendar>
<button class="bg-blue round margin-top-xl" @click="handleSave">保存</button>
<button class="round line-blue margin-top" @click="handleLogout">退出登录</button>
<button class="bg-blue round margin-top-xl" @click="handleSave" :loading="saving">保存</button>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue';
import { baseUrl, getToken } from '@/request/request.js';
import { getProfileDetail, updateProfile } from '@/request/three_one_api/info.js';
const showDatePicker = ref(false);
const saving = ref(false);
const defaultAvatar = 'https://ossweb-img.qq.com/images/lol/web201310/skin/big81005.jpg';
const avatarPreview = ref(''); // 用于显示选择的图片临时预览
const userInfo = reactive({
avatar: 'https://ossweb-img.qq.com/images/lol/web201310/skin/big81005.jpg',
nickname: '希缝弗斯',
username: '17374339800',
signature: '',
gender: 1, // 1-男 2-女
birthday: '2025-10-09'
avatar: '', // 保存相对路径,用于提交
nickName: '',
phonenumber: '',
email: '',
sex: '0' // '0'-男 '1'-女
});
// 获取图片完整URL用于显示
const getImageUrl = (path) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return baseUrl + path;
};
// 页面加载时获取个人信息
onMounted(() => {
loadProfileDetail();
});
// 获取个人信息
const loadProfileDetail = async () => {
try {
uni.showLoading({ title: '加载中...' });
const res = await getProfileDetail();
uni.hideLoading();
if (res.code === 0 && res.data) {
userInfo.avatar = res.data.avatar || '';
userInfo.nickName = res.data.nickName || '';
userInfo.phonenumber = res.data.phonenumber || '';
userInfo.email = res.data.email || '';
userInfo.sex = res.data.sex || '0';
}
} catch (err) {
uni.hideLoading();
console.error('获取个人信息失败:', err);
}
};
// 选择头像
const chooseAvatar = () => {
uni.chooseImage({
@@ -103,33 +120,84 @@ const chooseAvatar = () => {
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
userInfo.avatar = res.tempFilePaths[0];
const tempFilePath = res.tempFilePaths[0];
// 显示临时预览
avatarPreview.value = tempFilePath;
// 上传获取链接
uploadAvatar(tempFilePath);
}
});
};
// 日期选择确认
const confirmDate = (e) => {
userInfo.birthday = e[0];
showDatePicker.value = false;
// 上传头像获取链接
const uploadAvatar = (filePath) => {
uni.showLoading({ title: '上传中...' });
uni.uploadFile({
url: baseUrl + '/frontend/attachment/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': getToken()
},
success: (uploadRes) => {
uni.hideLoading();
try {
const data = JSON.parse(uploadRes.data);
if (data.code === 0 && data.data) {
// 上传成功,保存相对路径(用于提交)
userInfo.avatar = data.data.url || data.data;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
avatarPreview.value = ''; // 上传失败,清除预览
uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
}
} catch (e) {
avatarPreview.value = '';
uni.showToast({ title: '上传失败', icon: 'none' });
}
},
fail: () => {
uni.hideLoading();
avatarPreview.value = '';
uni.showToast({ title: '上传失败', icon: 'none' });
}
});
};
// 保存
const handleSave = () => {
uni.showToast({ title: '保存成功', icon: 'success' });
};
// 退出登录
const handleLogout = () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.reLaunch({ url: '/pages/login/login' });
}
const handleSave = async () => {
// 表单验证
if (!userInfo.nickName) {
uni.showToast({ title: '请输入昵称', icon: 'none' });
return;
}
saving.value = true;
try {
const params = {
nickName: userInfo.nickName,
phonenumber: userInfo.phonenumber,
email: userInfo.email,
sex: userInfo.sex,
avatar: userInfo.avatar // 提交相对路径
};
const res = await updateProfile(params);
if (res.code === 0) {
uni.showToast({ title: '保存成功', icon: 'success' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({ title: res.msg || '保存失败', icon: 'none' });
}
});
} catch (err) {
console.error('保存失败:', err);
uni.showToast({ title: '保存失败', icon: 'none' });
} finally {
saving.value = false;
}
};
</script>
@@ -197,4 +265,4 @@ const handleLogout = () => {
color: #2667E9;
background: #fff;
}
</style>
</style>

View File

@@ -1,101 +1,134 @@
<template>
<view class="title">
<view class="padding">
<view class="text-center margin-top-xl text-white text-bold">我的</view>
<view class="flex justify-between align-center" style="padding-top:60rpx;" >
<view class="flex align-center">
<view class="cu-avatar xl round" style="background-image:url(https://ossweb-img.qq.com/images/lol/web201310/skin/big99008.jpg);"></view>
<view class="margin-left">
<view class="text-white text-lg text-bold">hhhrrrr</view>
<view class="text-white" style="opacity: 0.8;">17374339800</view>
<view class="page-wrapper">
<!-- 顶部弥散渐变背景 -->
<view class="header-bg">
<!-- 弥散光斑效果 -->
<view class="blur-circle circle-1"></view>
<view class="blur-circle circle-2"></view>
<view class="blur-circle circle-3"></view>
<!-- 标题 -->
<!-- <view class="header-title">我的</view> -->
<!-- 用户信息区域 -->
<view class="user-info-section">
<view class="user-avatar">
<image class="avatar-img" :src="getImageUrl(userInfo.avatar) || defaultAvatar" mode="aspectFill"></image>
</view>
<view class="user-details">
<view class="user-name">{{ userInfo.nickName || '未设置昵称' }}</view>
<view class="user-phone">{{ userInfo.phonenumber || '未绑定手机' }}</view>
</view>
<button class="edit-btn" @click="editinfo()">编辑资料</button>
</view>
</view>
<!-- 内容区域 -->
<view class="content-area">
<!-- 菜单卡片 -->
<view class="menu-card">
<!-- <view class="menu-item" @click="Helpcenter()">
<view class="menu-left">
<image src="/static/my/Helpcenter.png" class="menu-icon"></image>
<text class="menu-text">帮助中心</text>
</view>
<text class="cuIcon-right menu-arrow"></text>
</view> -->
<!-- <view class="menu-item">
<view class="menu-left">
<image src="/static/my/CustomerService.png" class="menu-icon"></image>
<text class="menu-text">智能客服</text>
</view>
<text class="cuIcon-right menu-arrow"></text>
</view> -->
<view class="menu-item" @click="editinfo()">
<view class="menu-left">
<image src="/static/my/Notification.png" class="menu-icon"></image>
<text class="menu-text">编辑个人信息</text>
</view>
<text class="cuIcon-right menu-arrow"></text>
</view>
<view class="menu-item" @click="Account()">
<view class="menu-left">
<image src="/static/my/Account.png" class="menu-icon"></image>
<text class="menu-text">修改登录密码</text>
</view>
<text class="cuIcon-right menu-arrow"></text>
</view>
<!-- <view class="menu-item">
<view class="menu-left">
<image src="/static/my/Delete.png" class="menu-icon"></image>
<text class="menu-text">清除缓存</text>
</view>
<view class="menu-right">
<text class="menu-value">0B</text>
<text class="cuIcon-right menu-arrow"></text>
</view>
</view>
<button class="cu-btn round edit-btn bg-blue text-white ">编辑资料</button>
<view class="menu-item" @click="Settings()">
<view class="menu-left">
<image src="/static/my/Settings.png" class="menu-icon"></image>
<text class="menu-text">通用设置</text>
</view>
<text class="cuIcon-right menu-arrow"></text>
</view>
<view class="menu-item">
<view class="menu-left">
<image src="/static/my/Helpcenter.png" class="menu-icon"></image>
<text class="menu-text">关于</text>
</view>
<view class="menu-right">
<text class="menu-value">1.0.0</text>
<text class="cuIcon-right menu-arrow"></text>
</view>
</view> -->
</view>
<!-- 退出登录按钮 -->
<button class="logout-btn" @click="handleLogout()">退出登录</button>
</view>
</view>
<view class="page-content">
<view class="padding bg-white radius">
<view class="flex justify-between padding-bottom solid-bottom">
<view class="flex " @click="Helpcenter()">
<image src="/static/my/Helpcenter.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">帮助中心</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex">
<image src="/static/my/CustomerService.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">智能客服</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex" @click="Account()">
<image src="/static/my/Account.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">账号安全</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex" @click="notification()">
<image src="/static/my/Notification.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">新消息通知</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex">
<image src="/static/my/Delete.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">清除缓存</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex" @click="Settings()">
<image src="/static/my/Settings.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">通用设置</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
<view class="flex justify-between padding-bottom padding-top solid-bottom">
<view class="flex">
<image src="/static/my/Helpcenter.png" style="width:40rpx;height: 40rpx;"></image>
<view class="margin-left">关于</view>
</view>
<view class="lg text-gray cuIcon-right"></view>
</view>
</view>
<button class=" bg-blue round margin-top-xl " @click="handleLogout()">退出登录</button>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive } from 'vue'
import { onShow } from '@dcloudio/uni-app';
import { baseUrl } from '@/request/request.js';
import { getProfileDetail } from '@/request/three_one_api/info.js';
const defaultAvatar = 'https://ossweb-img.qq.com/images/lol/web201310/skin/big99008.jpg';
// 用户信息
const userInfo = reactive({
avatar: '',
nickName: '',
phonenumber: ''
});
// // 获取用户信息
// const getUserInfo = () => {
// try {
// const storedUserInfo = uni.getStorageSync('userInfo');
// if (storedUserInfo) {
// const info = JSON.parse(storedUserInfo);
// userInfo.nickName = info.nickName || '';
// userInfo.phonenumber = info.phonenumber || info.username || '';
// }
// } catch (e) {
// console.error('获取用户信息失败:', e);
// }
// };
// 获取图片完整URL用于显示
const getImageUrl = (path) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return baseUrl + path;
};
onMounted(() => {
getUserInfo();
// 获取用户信息
const loadUserInfo = async () => {
try {
const res = await getProfileDetail();
if (res.code === 0 && res.data) {
userInfo.avatar = res.data.avatar || '';
userInfo.nickName = res.data.nickName || '';
userInfo.phonenumber = res.data.phonenumber || '';
}
} catch (e) {
console.error('获取用户信息失败:', e);
}
};
// 每次页面显示时获取最新数据(包括从编辑页返回)
onShow(() => {
loadUserInfo();
});
//帮助中心
@@ -110,6 +143,12 @@
url:'/pages/personalcenter/notification'
})
}
//编辑个人信息
const editinfo = () => {
uni.navigateTo({
url:'/pages/personalcenter/edit'
})
}
//通用设置
const Settings = () => {
uni.navigateTo({
@@ -144,22 +183,190 @@
</script>
<style lang="scss" scoped>
page {
background: #EBF2FC;
.page-wrapper {
min-height: 100vh;
background: #EBF2FC;
}
// 顶部弥散渐变背景
.header-bg {
position: relative;
height: 400rpx;
background: linear-gradient(180deg, #3B7FED 0%, #007AFF 50%, #8BB8F8 100%);
padding-top: 60rpx;
overflow: hidden;
// 弥散光斑效果
.blur-circle {
position: absolute;
border-radius: 50%;
filter: blur(60rpx);
opacity: 0.5;
}
.page-content {
background: #EBF2FC;
border-radius: 40rpx 40rpx 0rpx 0rpx;
margin-top: -40rpx;
padding: 30rpx;
padding-bottom: 50rpx;
position: relative;
z-index: 10;
min-height: calc(100vh - 400rpx);
.circle-1 {
width: 300rpx;
height: 300rpx;
background: rgba(100, 180, 255, 0.6);
top: -50rpx;
left: -50rpx;
}
.title {
height: 440rpx;
background: linear-gradient(135deg, #2667E9 0%, #4A8AF4 50%, #719BF0 100%);
padding-top: 60rpx;
.circle-2 {
width: 250rpx;
height: 250rpx;
background: rgba(150, 200, 255, 0.5);
top: 100rpx;
right: -30rpx;
}
.circle-3 {
width: 200rpx;
height: 200rpx;
background: rgba(180, 220, 255, 0.4);
bottom: 50rpx;
left: 200rpx;
}
}
// 标题
.header-title {
text-align: center;
font-size: 36rpx;
font-weight: 600;
color: #fff;
position: relative;
z-index: 5;
}
// 用户信息区域
.user-info-section {
display: flex;
align-items: center;
padding: 60rpx 40rpx 0;
position: relative;
z-index: 5;
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
overflow: hidden;
border: 4rpx solid rgba(255, 255, 255, 0.5);
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15);
.avatar-img {
width: 100%;
height: 100%;
}
}
.user-details {
flex: 1;
margin-left: 24rpx;
.user-name {
font-size: 36rpx;
font-weight: 600;
color: #fff;
margin-bottom: 8rpx;
}
.user-phone {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.85);
}
}
.edit-btn {
// padding: 16rpx 32rpx;
background: rgba(255, 255, 255, 0.25);
border: 2rpx solid rgba(255, 255, 255, 0.5);
border-radius: 32rpx;
font-size: 26rpx;
color: #fff;
backdrop-filter: blur(10rpx);
&::after {
border: none;
}
}
}
// 内容区域
.content-area {
position: relative;
margin-top: -60rpx;
padding: 0 30rpx 50rpx;
z-index: 10;
}
// 菜单卡片
.menu-card {
background: #fff;
border-radius: 24rpx;
padding: 10rpx 0;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 36rpx;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.menu-left {
display: flex;
align-items: center;
.menu-icon {
width: 44rpx;
height: 44rpx;
margin-right: 24rpx;
}
.menu-text {
font-size: 30rpx;
color: #333;
}
}
.menu-right {
display: flex;
align-items: center;
.menu-value {
font-size: 28rpx;
color: #999;
margin-right: 8rpx;
}
}
.menu-arrow {
font-size: 28rpx;
color: #ccc;
}
}
// 退出登录按钮
.logout-btn {
margin-top: 60rpx;
height: 96rpx;
line-height: 96rpx;
background: linear-gradient(135deg, #3B7FED 0%, #5A9CF5 100%);
border-radius: 48rpx;
font-size: 32rpx;
font-weight: 500;
color: #fff;
box-shadow: 0 8rpx 24rpx rgba(59, 127, 237, 0.35);
&::after {
border: none;
}
}
</style>