approvalCenter.vue 25 KB

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