approvalCenter.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. <template>
  2. <view class="container">
  3. <!-- 搜索+筛选栏 -->
  4. <view class="top-box">
  5. <view class="input-item">
  6. <image class="icon search-icon" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/search.png" mode="widthFix"></image>
  7. <input v-model="searchKeywords" @input="handleSearchInput" placeholder="请输入发起人姓名、手机号" placeholder-class="placeholder" />
  8. </view>
  9. </view>
  10. <!-- 顶部选项卡 -->
  11. <view class="top-tabs">
  12. <view class="top-tab-item" :class="{ active: currentTopTab === index }" @click="switchTopTab(index)"
  13. v-for="(item, index) in topTabs" :key="index">
  14. {{ item.label }}·{{ item.badge }}
  15. </view>
  16. </view>
  17. <!-- 核心修改:新增子标签容器,作为popup-box的定位参考 -->
  18. <view class="sub-tabs-wrapper">
  19. <!-- 动态子标签 -->
  20. <view class="sub-tabs">
  21. <view class="sub-tab-item" :class="{ active: currentSubTab === 'all' || currentSubTab === 'taskCreate' }" @click="openSubTabPopup('all')">
  22. <text>{{ selectedSubTabLabels['all'] || '全部审核' }}</text>
  23. <image class="icon" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_expand.png" mode="widthFix"></image>
  24. </view>
  25. <view class="sub-tab-item" :class="{ active: currentSubTab === 'unfinished' || currentSubTab === 'finished' }" @click="openSubTabPopup('unfinished')">
  26. <text>{{ selectedSubTabLabels['unfinished'] || '最新发起' }}</text>
  27. <image class="icon" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_expand.png" mode="widthFix"></image>
  28. </view>
  29. </view>
  30. <!-- 子标签弹窗 -->
  31. <view class="sub-tab-popup-overlay" v-if="showSubTabPopup" @click="showSubTabPopup = false"></view>
  32. <view class="sub-tab-popup" v-if="showSubTabPopup">
  33. <view class="sub-tab-options">
  34. <!-- 全部审核选项 -->
  35. <template v-if="currentPopupType === 'all'">
  36. <view class="sub-tab-option" :class="{ active: selectedSubTabOptions['all'] === 'all' }" @click="selectSubTabOption('all', '全部审核')">
  37. <text>全部审核</text>
  38. </view>
  39. <view class="sub-tab-option" :class="{ active: selectedSubTabOptions['all'] === 'taskCreate' }" @click="selectSubTabOption('taskCreate', '任务创建')">
  40. <text>任务创建</text>
  41. </view>
  42. </template>
  43. <!-- 最新发起选项 -->
  44. <template v-else-if="currentPopupType === 'unfinished'">
  45. <view class="sub-tab-option" :class="{ active: selectedSubTabOptions['unfinished'] === 'unfinished' }" @click="selectSubTabOption('unfinished', '最新发起')">
  46. <text>最新发起</text>
  47. </view>
  48. <view class="sub-tab-option" :class="{ active: selectedSubTabOptions['unfinished'] === 'finished' }" @click="selectSubTabOption('finished', '最早发起')">
  49. <text>最早发起</text>
  50. </view>
  51. </template>
  52. </view>
  53. </view>
  54. </view>
  55. <!-- 列表内容 -->
  56. <scroll-view class="content" scroll-y refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
  57. <view class="task-card" v-for="(item, index) in currentList" :key="index" @click="goDetails(item)">
  58. <!-- 任务/审核标题+状态 -->
  59. <view class="card-top">
  60. <text class="card-task-title">{{ getAuditNameDisplay(item.auditName) }}</text>
  61. <view class="status-tag" :class="item.statusClass">{{ item.statusText }}</view>
  62. </view>
  63. <view class="card-content">
  64. <!-- 时间信息 -->
  65. <view class="time-info">
  66. <view class="time-item">
  67. <text class="title">任务名称:</text>
  68. <text>{{ item.auditName || '未设置' }}</text>
  69. </view>
  70. <view class="time-item">
  71. <text class="title">任务类型:</text>
  72. <text>{{ getBusinessTypeLabel(item.businessType) }}</text>
  73. </view>
  74. </view>
  75. <!-- 操作按钮 -->
  76. <view class="operate-btn-group">
  77. <view class="share-btn" @click="handleShare(item)">
  78. <image class="share-icon" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_user.png" mode="widthFix"></image>
  79. <text>{{ item.initiatorName || '未命名' }}</text>
  80. </view>
  81. <view class="date">
  82. {{ formatTime(item.createTime) }}
  83. </view>
  84. </view>
  85. </view>
  86. </view>
  87. </scroll-view>
  88. <!-- 筛选弹窗 -->
  89. <view class="filter-popup" v-if="showFilter" @click="closeFilter">
  90. <view class="filter-content" @click.stop>
  91. <view class="filter-header">
  92. <view class="filter-title">筛选</view>
  93. <image class="filter-close-btn" src="https://ysrw-1395926010.cos.ap-chengdu.myqcloud.com/image/icon_close.png" @click="closeFilter"
  94. mode="widthFix"></image>
  95. </view>
  96. <view class="filter-form">
  97. <!-- 任务申请时间 -->
  98. <view class="filter-section">
  99. <view class="section-label">任务申请时间</view>
  100. <view class="time-range">
  101. <input class="time-input" placeholder="开始时间" v-model="filters.applyTimeStart" />
  102. <view class="time-separator">-</view>
  103. <input class="time-input" placeholder="结束时间" v-model="filters.applyTimeEnd" />
  104. </view>
  105. </view>
  106. <!-- 任务完成时间:改为判断index:1对应已办 -->
  107. <view class="filter-section" v-if="currentTopTab === 1">
  108. <view class="section-label">任务完成时间</view>
  109. <view class="time-range">
  110. <input class="time-input" placeholder="开始时间" v-model="filters.finishTimeStart" />
  111. <view class="time-separator">-</view>
  112. <input class="time-input" placeholder="结束时间" v-model="filters.finishTimeEnd" />
  113. </view>
  114. </view>
  115. <!-- 完成审核时间:改为判断index:1对应已办 -->
  116. <view class="filter-section" v-if="currentTopTab === 1">
  117. <view class="section-label">完成审核时间</view>
  118. <view class="time-range">
  119. <input class="time-input" placeholder="开始时间" v-model="filters.auditTimeStart" />
  120. <view class="time-separator">-</view>
  121. <input class="time-input" placeholder="结束时间" v-model="filters.auditTimeEnd" />
  122. </view>
  123. </view>
  124. <!-- 任务归属 -->
  125. <view class="filter-section">
  126. <view class="section-label">任务归属</view>
  127. <view class="btn-group">
  128. <view class="filter-btn" :class="{ active: filters.taskBelong === 'my' }"
  129. @click="filters.taskBelong = 'my'">我的任务</view>
  130. <view class="filter-btn" :class="{ active: filters.taskBelong === 'dept' }"
  131. @click="filters.taskBelong = 'dept'">部门任务</view>
  132. </view>
  133. </view>
  134. <!-- 完结状态:改为判断index:1对应已办 -->
  135. <view class="filter-section" v-if="currentTopTab === 1">
  136. <view class="section-label">完结状态</view>
  137. <view class="btn-group">
  138. <view class="filter-btn" :class="{ active: filters.finishStatus === 'unfinished' }"
  139. @click="filters.finishStatus = 'unfinished'">未完结</view>
  140. <view class="filter-btn" :class="{ active: filters.finishStatus === 'finished' }"
  141. @click="filters.finishStatus = 'finished'">已完结</view>
  142. </view>
  143. </view>
  144. <!-- 归属类型 -->
  145. <view class="filter-section">
  146. <view class="section-label">归属类型</view>
  147. <view class="btn-group">
  148. <view class="filter-btn" :class="{ active: filters.belongType === 'inner' }"
  149. @click="filters.belongType = 'inner'">院内</view>
  150. <view class="filter-btn" :class="{ active: filters.belongType === 'outer' }"
  151. @click="filters.belongType = 'outer'">院外</view>
  152. </view>
  153. </view>
  154. </view>
  155. <view class="filter-actions">
  156. <view class="reset-btn" @click="resetFilters">重置</view>
  157. <view class="confirm-btn" @click="confirmFilters">确定</view>
  158. </view>
  159. </view>
  160. </view>
  161. </view>
  162. </template>
  163. <script>
  164. import utils from '@/utils/common.js'
  165. import TabPopup from '@/components/tab-popup.vue'
  166. import { getPendingAuditList, searchCompanyUser } from '@/api/audit.js'
  167. export default {
  168. components: {
  169. TabPopup
  170. },
  171. data() {
  172. return {
  173. processTemplate: null,
  174. popShow: false,
  175. showFilter: false,
  176. selectedTabItem: '',
  177. searchKeywords: '',
  178. searchTimer: null,
  179. filters: {
  180. applyTimeStart: '',
  181. applyTimeEnd: '',
  182. finishTimeStart: '',
  183. finishTimeEnd: '',
  184. auditTimeStart: '',
  185. auditTimeEnd: '',
  186. taskBelong: 'my',
  187. finishStatus: '',
  188. belongType: ''
  189. },
  190. refreshing: false,
  191. showSubTabPopup: false,
  192. currentPopupType: 'all',
  193. // 为每种类型的弹窗分别存储选中的选项
  194. selectedSubTabOptions: {
  195. all: 'all',
  196. unfinished: 'unfinished'
  197. },
  198. selectedSubTabLabels: {
  199. all: '全部审核',
  200. unfinished: '最新发起'
  201. },
  202. // 顶部选项卡数组
  203. topTabs: [{
  204. label: '待办',
  205. value: 'todo',
  206. badge: 0
  207. },
  208. {
  209. label: '已办',
  210. value: 'done',
  211. badge: 0
  212. },
  213. {
  214. label: '我发起的',
  215. value: 'myInitiate',
  216. badge: 0
  217. }
  218. ],
  219. currentTopTab: 0,
  220. currentSubTab: 'all',
  221. // 子标签数组
  222. taskSubTabs: [{
  223. label: '全部审批',
  224. value: 'all'
  225. },
  226. {
  227. label: '最新发起',
  228. value: 'unfinished'
  229. }
  230. ],
  231. tabsList: ['全部审批', '任务创建'],
  232. tabsList2: ['最新发起', '最早发起'],
  233. // auditSubTabs: [{
  234. // label: '全部',
  235. // value: 'all'
  236. // },
  237. // {
  238. // label: '驳回',
  239. // value: 'rejected'
  240. // }
  241. // ],
  242. // 加载状态
  243. loading: {
  244. todo: false,
  245. done: false,
  246. myInitiate: false
  247. },
  248. // 列表数据
  249. todoList: [], // 待办列表(index=0)
  250. doneList: [], // 已办列表(index=1)
  251. myInitiateList: [] // 我发起的列表(index=2)
  252. }
  253. },
  254. computed: {
  255. currentSubTabsList() {
  256. return this.taskSubTabs;
  257. // if (this.currentTopTab === 0) return this.taskSubTabs;
  258. // if (this.currentTopTab === 1) return this.auditSubTabs;
  259. // if (this.currentTopTab === 2) return this.myInitiateList;
  260. },
  261. currentList() {
  262. if (this.currentTopTab === 0) return this.todoList;
  263. if (this.currentTopTab === 1) return this.doneList;
  264. if (this.currentTopTab === 2) return this.myInitiateList;
  265. }
  266. },
  267. onLoad() {
  268. // 页面加载时的初始化操作
  269. this.loadData();
  270. utils.getDicts("FLOW_TEMPLATE").then(res => {
  271. this.processTemplate = res;
  272. console.log("流程模板", this.processTemplate)
  273. });
  274. },
  275. methods: {
  276. onRefresh() {
  277. this.refreshing = true;
  278. this.loadData().finally(() => {
  279. setTimeout(() => {
  280. this.refreshing = false;
  281. }, 500);
  282. });
  283. },
  284. onRestore() {
  285. this.refreshing = false;
  286. },
  287. openSubTabPopup(type) {
  288. this.currentPopupType = type;
  289. this.showSubTabPopup = true;
  290. },
  291. selectSubTabOption(value, label) {
  292. // 根据当前弹窗类型更新对应类型的选中选项
  293. this.selectedSubTabOptions[this.currentPopupType] = value;
  294. this.selectedSubTabLabels[this.currentPopupType] = label;
  295. this.currentSubTab = value;
  296. this.showSubTabPopup = false;
  297. // 这里可以添加根据选择的子标签过滤数据的逻辑
  298. this.loadData();
  299. },
  300. selectTabItem(item) {
  301. this.selectedTabItem = item;
  302. this.popShow = false;
  303. },
  304. goDetails(item) {
  305. console.log("跳转参数",item)
  306. uni.navigateTo({
  307. url: `/pages_task/approvalTaskDetail?taskId=${item.id}&businessType=${item.businessType}`
  308. })
  309. },
  310. switchTopTab(index) {
  311. this.currentTopTab = index;
  312. this.currentSubTab = 'all';
  313. // 重置所有子标签选项
  314. this.selectedSubTabOptions = {
  315. all: 'all',
  316. unfinished: 'unfinished'
  317. };
  318. this.selectedSubTabLabels = {
  319. all: '全部审核',
  320. unfinished: '最新发起'
  321. };
  322. this.popShow = false;
  323. this.showSubTabPopup = false;
  324. // 切换标签时重新加载数据
  325. this.loadData();
  326. },
  327. closeFilter() {
  328. this.showFilter = false
  329. },
  330. resetFilters() {
  331. this.filters = {
  332. applyTimeStart: '',
  333. applyTimeEnd: '',
  334. finishTimeStart: '',
  335. finishTimeEnd: '',
  336. auditTimeStart: '',
  337. auditTimeEnd: '',
  338. taskBelong: 'my',
  339. finishStatus: '',
  340. belongType: ''
  341. }
  342. },
  343. confirmFilters() {
  344. this.showFilter = false
  345. console.log('筛选条件:', this.filters)
  346. // 筛选后重新加载数据
  347. this.loadData()
  348. },
  349. getAuditNameDisplay(auditName) {
  350. if (!auditName) return '未设置'
  351. switch (auditName) {
  352. case '完成任务审核':
  353. return '"完成任务"审核'
  354. case '定级审核':
  355. return '"讲者定级"审核'
  356. default:
  357. // 对于其他审核类型,在最开始和审核前都加双引号
  358. const prefix = auditName.replace('审核', '')
  359. return '"' + prefix + '"审核'
  360. }
  361. },
  362. handleShare(item) {
  363. uni.showToast({
  364. title: '分享功能待实现',
  365. icon: 'none'
  366. })
  367. },
  368. handleSearchInput() {
  369. // 防抖处理,避免频繁请求
  370. if (this.searchTimer) {
  371. clearTimeout(this.searchTimer)
  372. }
  373. this.searchTimer = setTimeout(async () => {
  374. if (this.searchKeywords.trim()) {
  375. try {
  376. // 调用搜索发起人接口
  377. const res = await searchCompanyUser({
  378. keywords: this.searchKeywords.trim(),
  379. pageNum: 1,
  380. pageSize: 10
  381. })
  382. if (res.code === 200 && res.rows) {
  383. // 根据当前标签页更新对应列表
  384. if (this.currentTopTab === 0) {
  385. this.todoList = res.rows.map(item => ({
  386. auditName: item.auditName || '未知审核',
  387. statusText: '待审核',
  388. statusClass: 'status-createPending',
  389. id: item.id,
  390. status: item.status,
  391. auditType: item.auditType,
  392. businessId: item.businessId,
  393. businessType: item.businessType,
  394. createTime: item.createTime,
  395. initiatorName: item.initiatorName
  396. }))
  397. } else if (this.currentTopTab === 1) {
  398. this.doneList = res.rows.map(item => ({
  399. auditName: item.auditName || '未知审核',
  400. statusText: item.status === 1 ? '已通过' : '已驳回',
  401. statusClass: item.status === 1 ? 'status-finish' : 'status-rejected',
  402. id: item.id,
  403. status: item.status,
  404. auditType: item.auditType,
  405. businessId: item.businessId,
  406. businessType: item.businessType,
  407. createTime: item.createTime,
  408. initiatorName: item.initiatorName
  409. }))
  410. } else if (this.currentTopTab === 2) {
  411. this.myInitiateList = res.rows.map(item => ({
  412. auditName: item.auditName || '未知审核',
  413. statusText: '待审核',
  414. statusClass: 'status-createPending',
  415. id: item.id,
  416. status: item.status,
  417. auditType: item.auditType,
  418. businessId: item.businessId,
  419. businessType: item.businessType,
  420. createTime: item.createTime,
  421. initiatorName: item.initiatorName
  422. }))
  423. }
  424. } else {
  425. // 搜索结果为空,清空对应列表
  426. if (this.currentTopTab === 0) {
  427. this.todoList = []
  428. } else if (this.currentTopTab === 1) {
  429. this.doneList = []
  430. } else if (this.currentTopTab === 2) {
  431. this.myInitiateList = []
  432. }
  433. }
  434. } catch (e) {
  435. console.error('搜索失败', e)
  436. uni.showToast({
  437. title: '搜索失败',
  438. icon: 'none'
  439. })
  440. }
  441. } else {
  442. // 搜索框为空,重新加载默认数据
  443. this.loadData()
  444. }
  445. }, 300)
  446. },
  447. // 加载审批数据
  448. async loadData() {
  449. try {
  450. // 无论当前标签是什么,都调用getPendingAuditList请求
  451. const userInfoStr = uni.getStorageSync('userInfo')
  452. const userInfo = userInfoStr ? (typeof userInfoStr === 'string' ? JSON.parse(userInfoStr) : userInfoStr) : {}
  453. // 获取不同status的数据total并更新badge
  454. const getBadgeData = async () => {
  455. // 获取待办数据(total)
  456. const todoParams = {
  457. initiatorName: userInfo.nickName || '',
  458. initiatorPhone: userInfo.phone || '',
  459. status: 1,
  460. sort: 0,
  461. userId: userInfo.userId || '',
  462. companyId: userInfo.companyId || 0
  463. }
  464. const todoRes = await getPendingAuditList(todoParams)
  465. this.topTabs[0].badge = todoRes.code === 200 ? (todoRes.total || 0) : 0
  466. // 获取已办数据(total)
  467. const doneParams = {
  468. initiatorName: userInfo.nickName || '',
  469. initiatorPhone: userInfo.phone || '',
  470. status: 2,
  471. sort: 0,
  472. userId: userInfo.userId || '',
  473. companyId: userInfo.companyId || 0
  474. }
  475. const doneRes = await getPendingAuditList(doneParams)
  476. this.topTabs[1].badge = doneRes.code === 200 ? (doneRes.total || 0) : 0
  477. // 获取我发起的数据(total)
  478. const myInitiateParams = {
  479. initiatorName: userInfo.nickName || '',
  480. initiatorPhone: userInfo.phone || '',
  481. status: '',
  482. sort: 0,
  483. userId: userInfo.userId || '',
  484. companyId: 1 || 0
  485. }
  486. const myInitiateRes = await getPendingAuditList(myInitiateParams)
  487. this.topTabs[2].badge = myInitiateRes.code === 200 ? (myInitiateRes.total || 0) : 0
  488. }
  489. // 先获取所有badge数据
  490. await getBadgeData()
  491. // 获取当前标签页的数据
  492. const params = {
  493. initiatorName: userInfo.nickName || '',
  494. initiatorPhone: userInfo.phone || '',
  495. status: this.currentTopTab === 0 ? 1 : this.currentTopTab === 1 ? 2 : '',
  496. sort: 0,
  497. userId: userInfo.userId || '',
  498. companyId: this.currentTopTab === 2 ? 1 : (userInfo.companyId || 0)
  499. }
  500. const res = await getPendingAuditList(params)
  501. if (res.code === 200) {
  502. // 处理返回的数据
  503. const responseData = res.rows || [];
  504. // 直接使用responseData作为对应列表的数据
  505. if (this.currentTopTab === 0) {
  506. // 待办列表
  507. this.loading.todo = true
  508. this.todoList = responseData.map(item => ({
  509. auditName: item.auditName || '未知审核',
  510. statusText: '待审核',
  511. statusClass: 'status-createPending',
  512. id: item.id,
  513. status: item.status,
  514. auditType: item.auditType,
  515. businessId: item.businessId,
  516. businessType: item.businessType,
  517. createTime: item.createTime,
  518. initiatorName: item.initiatorName
  519. }))
  520. this.loading.todo = false
  521. } else if (this.currentTopTab === 1) {
  522. // 已办列表
  523. this.loading.done = true
  524. this.doneList = responseData.map(item => ({
  525. auditName: item.auditName || '未知审核',
  526. statusText: item.status === 1 ? '已通过' : '已驳回',
  527. statusClass: item.status === 1 ? 'status-finish' : 'status-rejected',
  528. id: item.id,
  529. status: item.status,
  530. auditType: item.auditType,
  531. businessId: item.businessId,
  532. businessType: item.businessType,
  533. createTime: item.createTime,
  534. initiatorName: item.initiatorName
  535. }))
  536. this.loading.done = false
  537. } else if (this.currentTopTab === 2) {
  538. // 我发起的列表
  539. this.loading.myInitiate = true
  540. this.myInitiateList = responseData.map(item => ({
  541. auditName: item.auditName || '未知审核',
  542. statusText: '待审核',
  543. statusClass: 'status-createPending',
  544. id: item.id,
  545. status: item.status,
  546. auditType: item.auditType,
  547. businessId: item.businessId,
  548. businessType: item.businessType,
  549. createTime: item.createTime,
  550. initiatorName: item.initiatorName
  551. }))
  552. this.loading.myInitiate = false
  553. }
  554. // 这里不再需要更新badge,因为已经在getBadgeData中更新过了
  555. } else {
  556. uni.showToast({
  557. title: '获取审核列表失败',
  558. icon: 'none'
  559. })
  560. }
  561. } catch (e) {
  562. console.error('加载数据失败', e)
  563. uni.showToast({
  564. title: '加载数据失败',
  565. icon: 'none'
  566. })
  567. // 重置加载状态
  568. this.loading.todo = false
  569. this.loading.done = false
  570. this.loading.myInitiate = false
  571. }
  572. },
  573. // 格式化时间
  574. formatTime(timeStr) {
  575. if (!timeStr) return ''
  576. const date = new Date(timeStr)
  577. const year = date.getFullYear()
  578. const month = String(date.getMonth() + 1).padStart(2, '0')
  579. const day = String(date.getDate()).padStart(2, '0')
  580. const hours = String(date.getHours()).padStart(2, '0')
  581. const minutes = String(date.getMinutes()).padStart(2, '0')
  582. return `${year}-${month}-${day} ${hours}:${minutes}`
  583. },
  584. getBusinessTypeLabel(businessType) {
  585. if (!businessType || !this.processTemplate) {
  586. return '未设置'
  587. }
  588. const dictItem = this.processTemplate.find(item => item.dictValue === businessType)
  589. return dictItem ? dictItem.dictLabel : businessType
  590. }
  591. }
  592. }
  593. </script>
  594. <style lang="scss" scoped>
  595. .container {
  596. min-height: 100vh;
  597. background: #F7F8FA;
  598. position: relative;
  599. // 搜索栏
  600. .top-box {
  601. padding: 20rpx 32rpx;
  602. display: flex;
  603. align-items: center;
  604. background: #fff;
  605. position: relative;
  606. z-index: 101;
  607. .input-item {
  608. display: flex;
  609. align-items: center;
  610. flex: 1;
  611. height: 72rpx;
  612. background: #F7F8FA;
  613. border-radius: 38rpx;
  614. .search-icon {
  615. width: 26rpx;
  616. height: 26rpx;
  617. margin: 0 10rpx 0 28rpx;
  618. }
  619. input {
  620. flex: 1;
  621. font-size: 28rpx;
  622. color: #333;
  623. }
  624. }
  625. }
  626. // 顶部选项卡
  627. .top-tabs {
  628. display: flex;
  629. background: #fff;
  630. position: relative;
  631. z-index: 101;
  632. .top-tab-item {
  633. flex: 1;
  634. text-align: center;
  635. padding: 24rpx 0;
  636. font-size: 28rpx;
  637. color: #999;
  638. transition: all 0.2s;
  639. &.active {
  640. color: #333;
  641. font-weight: 500;
  642. position: relative;
  643. &::after {
  644. content: '';
  645. position: absolute;
  646. bottom: 0;
  647. left: 50%;
  648. transform: translateX(-50%);
  649. border-radius: 3rpx;
  650. width: 80rpx;
  651. height: 6rpx;
  652. background: #388BFF;
  653. }
  654. }
  655. }
  656. }
  657. // 核心修改:子标签容器(定位参考)
  658. .sub-tabs-wrapper {
  659. position: relative; // 作为popup-box的定位参考
  660. z-index: 101; // 确保在遮罩层之上
  661. // 子标签栏
  662. .sub-tabs {
  663. display: flex;
  664. justify-content: space-between;
  665. background: #fff;
  666. padding: 16rpx 32rpx;
  667. gap: 24rpx;
  668. .sub-tab-item {
  669. flex: 1;
  670. height: 64rpx;
  671. display: flex;
  672. border-radius: 70rpx;
  673. border: 2rpx solid #F2F2F2;
  674. justify-content: center;
  675. align-items: center;
  676. font-size: 28rpx;
  677. color: #666;
  678. transition: all 0.2s;
  679. .icon {
  680. width: 28rpx;
  681. height: 28rpx;
  682. margin-left: 12rpx;
  683. }
  684. }
  685. }
  686. }
  687. /* 子标签弹窗样式 */
  688. .sub-tab-popup-overlay {
  689. position: absolute;
  690. top: 100%;
  691. height: 100vh;
  692. left: 0;
  693. right: 0;
  694. bottom: -1000rpx;
  695. background: rgba(0, 0, 0, 0.5);
  696. z-index: 8;
  697. }
  698. .sub-tab-popup {
  699. position: absolute;
  700. top: 100%;
  701. left: 0;
  702. right: 0;
  703. background: #fff;
  704. border-radius: 0;
  705. padding: 32rpx;
  706. width: 100%;
  707. box-sizing: border-box;
  708. box-shadow: none;
  709. z-index: 9;
  710. }
  711. .sub-tab-options {
  712. display: flex;
  713. flex-wrap: wrap;
  714. gap: 24rpx;
  715. border-radius: 70rpx 70rpx 70rpx 70rpx;
  716. justify-content: flex-start;
  717. }
  718. .sub-tab-option {
  719. padding: 24rpx 14rpx;
  720. border-radius: 70rpx;
  721. font-size: 28rpx;
  722. color: #666666;
  723. background: #ffffff;
  724. border: 1rpx solid #e8e8e8;
  725. transition: all 0.2s ease;
  726. text-align: center;
  727. min-width: 214rpx;
  728. box-sizing: border-box;
  729. &.active {
  730. background: rgba(56,139,255,0.15);
  731. color: #ffffff;
  732. color: #388BFF;
  733. border: 1rpx solid #388BFF ;
  734. }
  735. }
  736. // 列表区域
  737. .content {
  738. padding: 24rpx;
  739. box-sizing: border-box;
  740. min-height: calc(100vh - 300rpx);
  741. position: relative;
  742. z-index: 98; // 低于遮罩层
  743. .task-card {
  744. background: #fff;
  745. border-radius: 24rpx;
  746. margin-bottom: 20rpx;
  747. border: 2rpx solid #E9F2FF;
  748. position: relative;
  749. padding-bottom: 24rpx;
  750. .card-top {
  751. display: flex;
  752. justify-content: space-between;
  753. align-items: center;
  754. background: linear-gradient(90deg, #E8F1FF 0%, #FFFFFF 100%);
  755. border-radius: 24rpx 24rpx 0 0;
  756. padding: 26rpx 24rpx;
  757. margin-bottom: 16rpx;
  758. .card-task-title {
  759. font-size: 32rpx;
  760. font-weight: 600;
  761. color: #333;
  762. }
  763. .status-tag {
  764. padding: 8rpx 16rpx;
  765. border-radius: 20rpx;
  766. font-size: 24rpx;
  767. &.status-wait {
  768. background: #E3F2FD;
  769. color: #2196F3;
  770. }
  771. &.status-finish {
  772. background: #E6FAEF;
  773. color: #07C160;
  774. }
  775. &.status-rejected {
  776. background: #FFF4F5;
  777. color: #CF3546;
  778. }
  779. &.status-createPending {
  780. background: #FFF8E6;
  781. color: #FF9500;
  782. }
  783. }
  784. }
  785. .card-content {
  786. padding: 0 24rpx;
  787. .time-info {
  788. font-size: 28rpx;
  789. color: #666;
  790. display: flex;
  791. flex-direction: column;
  792. gap: 8rpx;
  793. margin-bottom: 24rpx;
  794. .time-item {
  795. display: flex;
  796. font-size: 28rpx;
  797. color: #333;
  798. .title {
  799. color: #666;
  800. }
  801. }
  802. }
  803. .operate-btn-group {
  804. display: flex;
  805. align-items: center;
  806. justify-content: space-between;
  807. .share-btn {
  808. display: flex;
  809. align-items: center;
  810. font-size: 28rpx;
  811. color: #666;
  812. .share-icon {
  813. width: 36rpx;
  814. height: 36rpx;
  815. margin-right: 8rpx;
  816. }
  817. }
  818. .date {
  819. font-size: 24rpx;
  820. color: #999;
  821. }
  822. }
  823. }
  824. }
  825. }
  826. // 筛选弹窗
  827. .filter-popup {
  828. position: fixed;
  829. top: 0;
  830. left: 0;
  831. right: 0;
  832. bottom: 0;
  833. background: rgba(0, 0, 0, 0.5);
  834. z-index: 999;
  835. display: flex;
  836. align-items: flex-end;
  837. .filter-content {
  838. width: 100%;
  839. background: #fff;
  840. border-radius: 24rpx 24rpx 0 0;
  841. padding: 32rpx;
  842. .filter-header {
  843. display: flex;
  844. align-items: center;
  845. justify-content: center;
  846. margin-bottom: 32rpx;
  847. position: relative;
  848. .filter-title {
  849. font-size: 32rpx;
  850. font-weight: bold;
  851. color: #333;
  852. }
  853. .filter-close-btn {
  854. position: absolute;
  855. right: 0;
  856. width: 44rpx;
  857. height: 44rpx;
  858. }
  859. }
  860. .filter-form {
  861. padding: 0 0 24rpx 0;
  862. .filter-section {
  863. margin-bottom: 32rpx;
  864. .section-label {
  865. font-weight: 500;
  866. font-size: 28rpx;
  867. color: #333;
  868. margin-bottom: 24rpx;
  869. }
  870. .time-range {
  871. display: flex;
  872. align-items: center;
  873. gap: 16rpx;
  874. .time-input {
  875. flex: 1;
  876. height: 72rpx;
  877. text-align: center;
  878. background: #F7F8FA;
  879. border-radius: 8rpx;
  880. padding: 0 16rpx;
  881. font-size: 26rpx;
  882. color: #333;
  883. }
  884. .time-separator {
  885. font-size: 24rpx;
  886. color: #999;
  887. }
  888. }
  889. .btn-group {
  890. display: flex;
  891. gap: 16rpx;
  892. .filter-btn {
  893. width: 214rpx;
  894. height: 72rpx;
  895. background: #F7F8FA;
  896. border-radius: 70rpx;
  897. line-height: 72rpx;
  898. text-align: center;
  899. font-size: 28rpx;
  900. transition: all 0.2s;
  901. color: #333;
  902. &.active {
  903. background: rgba(56, 139, 255, 0.15);
  904. color: #388BFF;
  905. }
  906. }
  907. }
  908. }
  909. }
  910. .filter-actions {
  911. display: flex;
  912. gap: 24rpx;
  913. margin-top: 40rpx;
  914. .reset-btn,
  915. .confirm-btn {
  916. flex: 1;
  917. height: 80rpx;
  918. line-height: 80rpx;
  919. text-align: center;
  920. border-radius: 200rpx;
  921. font-size: 28rpx;
  922. transition: all 0.2s;
  923. }
  924. .reset-btn {
  925. background: #fff;
  926. color: #388BFF;
  927. border: 2rpx solid #388BFF;
  928. &:active {
  929. background: #EBF3FF;
  930. }
  931. }
  932. .confirm-btn {
  933. background: #388BFF;
  934. color: #fff;
  935. &:active {
  936. background: #2A78E5;
  937. }
  938. }
  939. }
  940. }
  941. }
  942. }
  943. </style>