<template> <cs-page> <view class="detail-container" :style="{ paddingBottom: isSelect.length > 0 ? '140rpx' : '40rpx', }" > <view class="box detail"> <text class="wd-font-800 wd-text-16" style="color: #071437; padding-right: 40rpx" > {{ detail.title }} </text> <view class="tagList"> <view class="tag" v-if="detail.deptName" > {{ detail.deptName }} </view> <view class="tag"> {{ $dict.echoDicValue( dictMap.task_priority, detail.priority ) }} </view> <view v-for="(tag, index) in detail.tagList" :key="index" class="tag" > {{ tag }} </view> </view> <view class="wd-flex wd-text-13" style="justify-content: space-between" > <view class="wd-flex" style="align-items: center; gap: 8rpx" > <u-icon name="calendar" color="#17C653" size="16" /> <text class="wd-text-13" style="font-weight: bold; margin-right: 8rpx" > {{ `${$util.formatDate( detail.startDate, 'YYYY年M月D日' )} 至 ${$util.formatDate( detail.endDate, 'YYYY年M月D日' )}` }} </text> </view> </view> <cs-text-more :value="detail.description" /> <view class="file-list" v-if="files && files.length > 0" > <view class="file" v-for="(file, index) in files" :key="index" @click="openFile(file)" > <u-icon name="attach" color="#17C653" size="16" /> <text class="name" style=" color: var( --LightMode-Grey-Grey-900, #071437 ); " > {{ file.name }} </text> </view> </view> <view class="audit"> <cs-dict-tag :dict="dictMap.task_state" :value="detail.status" ></cs-dict-tag> </view> </view> <u-sticky> <view :class="[ 'options-container', OptionsOffset.isTop ? 'isTop' : '', ]" ref="optionRef" id="options" > <van-dropdown-menu active-color="#17C653" style="flex: 1" > <van-dropdown-item v-if="showItem(['director'])" :value="queryParams.departmentId" :options="dropOption.dept" @change=" v => { querySelect(v, 'departmentId') } " /> <van-dropdown-item v-if="showItem(['queue', 'director'])" :value="queryParams.userId" use-before-toggle @before-toggle="getUserOption" :options="userList" @change=" v => { querySelect(v, 'userId') } " /> <van-dropdown-item :value="queryParams.Inspections_status" :options="dropOption.status" @change=" v => { querySelect(v, 'inspectionsStatus') } " /> </van-dropdown-menu> <view style="flex: 0 0 120rpx"> <text style="color: #17c653">{{ list.length }}</text> 条记录 </view> </view> </u-sticky> <view class="record" v-for="record in list" :key="record.id" @tap="goRecord(record)" > <view :class="{ select: true, isSelect: isSelect.includes(record.id), }" @tap.native.stop="select(record.id)" v-if=" ['3', null].includes(record.inspectionStatus) " > <u-icon name="checkbox-mark" size="16" color="#fff" ></u-icon> </view> <view class="disable" v-else ></view> <view class="info"> <view class="name"> {{ record.enterpriseName }} </view> <view style=" font-size: 26rpx; padding-right: 3rem; margin-bottom: 4rpx; white-space: nowrap; text-overflow: ellipsis; width: 528rpx; overflow: hidden; " > {{ record.enterpriseAddress }} </view> <view class="tagList" style="margin-top: 16rpx" > <view v-for="(item, index) in record.tagList" :key="index" class="tag" > {{ item }} </view> </view> </view> <view class="audit" v-show="record.inspectionStatus" > <cs-dict-tag :dict="dictMap.Inspections_status" :value="record.inspectionStatus" ></cs-dict-tag> </view> </view> <u-loadmore :status="load" marginTop="12" marginBottom="12" v-if="load != 'nomore'" /> <view class="btn-box" v-if="isSelect.length > 0" > <view class="confirm-btn" @tap="replay" > 已选择({{ isSelect.length }}) </view> </view> <u-modal :show="model.show" closeOnClickOverlay @close="closeModel" borderRadius="32rpx" > <view class="wd-flex wd-flex-col" style="gap: 20px" > <view class="header"> <view class="row-1"> 已选择 <text class="num" >{{ isSelect.length }}项记录</text > 转移给 </view> <view class="row-2">每次只能选择一人</view> </view> <view class="staff-list"> <view class="staff" v-for="staff in model.list" :key="staff.userId" :style="{ '--select-color': model.isSelect == staff.userId ? '#17c653' : '#f1f1f4', }" @click="selectUser(staff.userId)" > <u-avatar size="40" shape="circle" :src="staff.avatar" ></u-avatar> <view class="info"> <view class="name">{{ staff.realName }}</view> <view class="dept">{{ staff.roleName[0] }}</view> </view> <view class="isSelect"> <u-icon name="checkbox-mark" size="16px" color="#fff" ></u-icon> </view> </view> </view> </view> <template #confirmButton> <view class="wd-flex wd-flex-center"> <view class="confirm-btn" @tap="submitReplay" >转移给TA</view > </view> </template> </u-modal> </view> </cs-page> </template> <script> import { getDeptTree, getDictBatchByType, } from '@/api/system/dict.js' import { TaskApi } from '@/api/task/index.js' import { InspectionsApi } from '@/api/inspections/index.js' import { getUserList } from '@/api/system/user.js' export default { data() { return { // 任务详情 detail: { id: '', description: '', }, // 文件列表 files: [], list: [], // 是否展开文本 isShowAllText: false, // 已选中 isSelect: [], dictMap: {}, //u-sticky 样式开关 OptionsOffset: { top: 0, isTop: false, }, // 查询条件 queryParams: { departmentId: '', inspectionsStatus: '', pageSize: 8, pageNo: 1, userId: '', }, load: 'loadmore', model: { show: false, list: [], isSelect: '', }, userList: [ { value: '', text: '按人员', }, ], // 下拉选项 dropOption: { dept: [], userList: [], status: [], }, } }, async onLoad(res) { this.detail.id = res.id await this.getDict() }, onPageScroll(e) { this.OptionsOffset.isTop = e.scrollTop + 10 > this.OptionsOffset.top }, onReady() { this.getOptionOffset() }, onReachBottom() { this.loadMore() }, onPullDownRefresh() { this.reset() }, onShow() { this.init() this.reset() }, methods: { openFile(file) { uni.downloadFile({ url: file.url, success: function (res) { const filePath = res.tempFilePath uni.openDocument({ filePath: filePath, showMenu: true, fail(err) { uni.showToast({ title: '文件打开失败', icon: 'none', }) }, }) }, fail(err) { uni.showToast({ title: '文件下载失败', icon: 'none', }) }, }) }, getOptionOffset() { const query = uni.createSelectorQuery().in(this) query .select('#options') .boundingClientRect(data => { this.OptionsOffset.top = data.top }) .exec() }, showItem(query) { return this.$roles.checkRole(query) }, /** * 获取字典 */ async getDict() { const dict = await getDictBatchByType({ type: [ 'task_state', 'Inspections_status', 'task_priority', ].join(','), }) const dept = await getDeptTree() this.dictMap = { ...dict.data, dept: dept.data, } this.dropOption = { dept: [ { value: '', text: '按部门', }, ...dept.data.map(d => { return { value: d.id, text: d.name, } }), ], status: [ { value: '', text: '按状态', }, ...dict.data.task_state.map(d => { return { ...d, value: d.value, text: d.label, } }), ], } }, getDropdownOption(key) { const keyMap = { dept: '按部门', Inspections_status: '按状态', } if (!this.dictMap[key]) return [] if (key == 'dept') { return [ ...this.dictMap[key].map(d => { return { ...d, value: d.id, text: d.name, } }), { value: '', text: keyMap[key], }, ] } return [ ...this.dictMap[key].map(d => { return { value: d.value, text: d.label, } }), { value: '', text: keyMap[key], }, ] }, querySelect(v, key) { if (key == 'departmentId') { this.queryParams.userId = '' } this.queryParams[key] = v.detail this.queryList() }, async init() { const res = await TaskApi.getDetail(this.detail.id) this.detail = res.data this.files = res.data.fileList }, async getUser() { const res = await getUserList( this.queryParams.departmentId ) return res.data }, async getList() { if (!this.$roles.checkRole(['director'])) { this.queryParams.departmentId = this.$store.getters.deptId } uni.showToast({ title: '加载中', mask: true, icon: 'loading', }) this.load = 'loading' const { data } = await InspectionsApi.getList({ ...this.queryParams, taskId: this.detail.id, }) this.list.push(...data.list) this.load = 'loadmore' if (this.list.length == data.total) { this.load = 'nomore' } uni.stopPullDownRefresh() uni.hideToast() }, getUserOption({ detail }) { this.getUser().then(res => { this.userList = [ { value: '', text: '按人员', }, ...res.map(i => { return { ...i, value: i.userId, text: i.realName, } }), ] detail.callback(true) }) }, goBack() { uni.switchTab({ url: '/pages/task', }) }, loadMore() { if (this.load == 'nomore') { uni.showToast({ title: '没有更多了', icon: 'none', }) return } this.queryParams.pageNo++ this.getList() }, select(id) { if (this.isSelect.includes(id)) { this.isSelect = this.isSelect.filter(i => i != id) } else { this.isSelect.push(id) } }, async reset() { this.queryParams = { departmentId: '', Inspections_status: '', pageSize: 8, pageNo: 1, userId: '', } await this.queryList() }, queryList() { this.queryParams.pageNo = 1 this.load = 'loadmore' this.list = [] this.getList() }, replay() { this.getUser().then(res => { this.model.list = res this.model.show = true }) }, closeModel() { this.model = { show: false, list: [], isSelect: '', } }, selectUser(id) { if (this.model.isSelect == id) { this.model.isSelect = null } else { this.model.isSelect = id } }, goRecord(record) { uni.navigateTo({ url: `/sub/task/record?taskId=${record.taskId}&recordId=${record.id}&enterpriseId=${record.enterpriseId}`, }) }, submitReplay() { InspectionsApi.replay({ inspectionsId: this.isSelect, userId: this.model.isSelect, }).then(res => { uni.showToast({ icon: 'none', title: '任务转移成功', }) this.closeModel() this.reset() }) }, }, } </script> <style lang="scss" scoped> .detail-container { position: relative; color: $cs-color-grey; .box { margin: 12px; background-color: #fff; border-radius: $cs-border-radius; padding: 16px; } .detail { display: flex; flex-flow: column nowrap; gap: 12px; position: relative; overflow: hidden; .audit { position: absolute; right: 0; top: 0; transform: translateX(58rpx) translateY(24rpx) rotateZ(45deg); transform-origin: 50% 50%; } .file-list { border-radius: 4px; background: var(--LightMode-Grey-Grey-100, #f9f9f9); padding: 8rpx 16rpx; display: flex; flex-wrap: wrap; gap: 24rpx; width: 100%; .file { display: flex; flex-flow: row nowarp; gap: 8rpx; cursor: pointer; align-items: center; .name { color: var(--LightMode-Grey-Grey-900, #071437); font-family: 'PingFang SC'; font-size: 26rpx; font-style: normal; font-weight: 400; line-height: 160%; text-decoration-line: underline; text-decoration-style: solid; text-decoration-skip-ink: auto; text-decoration-thickness: auto; text-underline-offset: auto; text-underline-position: from-font; } } } } .record { background-color: #fff; display: flex; padding: 24rpx 32rpx; align-items: center; border-radius: var(--Number-8px, 8px); border: 2rpx solid var(--LightMode-Grey-Grey-100, #f9f9f9); margin: 24rpx; margin-top: 0; position: relative; overflow: hidden; .audit { position: absolute; right: 0; top: 0; transform-origin: 50% 50%; transform: translateX(64rpx) translateY(18rpx) rotateZ(45deg); } .select { width: 44rpx; height: 44rpx; min-width: 44rpx; border: 4rpx solid $cs-color-main; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; transition: 0.2s all; margin-right: 32rpx; } .disable { width: 44rpx; height: 44rpx; border: 4rpx solid #dbdfe9; min-width: 44rpx; margin-right: 32rpx; border-radius: 100%; position: relative; &::after { content: ''; position: absolute; inset: 0; border-left: 4rpx solid #dbdfe9; transform-origin: 0 50%; transform: translateX(calc(50% - 2rpx)) rotateZ(45deg); } } .isSelect { background-color: $cs-color-main; } .info { font-size: 26rpx; overflow: hidden; .name { font-size: 32rpx; font-weight: 600; color: #071437; margin-bottom: 8rpx; white-space: nowrap; text-overflow: ellipsis; width: 480rpx; overflow: hidden; } } &:active { background-color: $cs-color-touch; } } .btn-box { width: 100%; display: flex; align-items: center; justify-content: center; padding-bottom: 30px; position: fixed; bottom: 0; left: 0; } .confirm-btn { background-color: $cs-color-main; padding: 12px 40px; color: #fff; font-size: 16px; font-weight: 600; border-radius: 120px; } .header { display: flex; flex-flow: column nowrap; justify-content: center; align-items: center; gap: 4px; .row-1 { font-size: 16px; font-weight: 600; color: #071437; .num { color: $cs-color-main; padding: 0 4px; } } .row-2 { color: $cs-color-grey; } } .staff-list { width: 70vw; display: flex; flex-flow: row wrap; gap: 12px; max-height: 30vh; overflow-y: scroll; .staff { width: calc(50% - 6px); padding: 12px; border-radius: 6px; border: 1px solid var(--select-color, #dbdfe9); position: relative; gap: 8px; display: flex; .info { .name { font-size: 30rpx; font-weight: bold; } .dept { color: $cs-color-grey; } } .isSelect { position: absolute; right: 0; top: 0; width: 30px; height: 30px; background-image: linear-gradient( 45deg, rgba(255, 255, 255, 0) 50%, var(--select-color, #f1f1f4) 50% ); display: flex; justify-content: flex-end; align-items: flex-start; } } } } .options-container { overflow: hidden; margin: 12px; border-radius: 8px; transition: 0.1s all; display: flex; padding: 0 12px; flex-flow: row nowrap; justify-content: center; align-items: center; background-color: #fff; border-bottom: 1px solid #f1f1f4; color: #071437; ::v-deep .van-dropdown-menu { background-color: transparent; box-shadow: none; --dropdown-menu-title-font-size: 13px; --dropdown-menu-title-text-color: #071437; .van-dropdown-menu__item { flex: 0; .van-dropdown-menu__title { width: 80px; } } } } .isTop { margin: 12px 0; border-radius: 0px; } ::v-deep .u-popup__content { border-radius: 32rpx; } </style>