approvalCenter.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  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>{{ selectedSubTabOption === 'all' || selectedSubTabOption === 'taskCreate' ? (selectedSubTabOption === '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>{{ selectedSubTabOption === 'unfinished' || selectedSubTabOption === 'finished' ? (selectedSubTabOption === '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: selectedSubTabOption === 'all' }" @click="selectSubTabOption('all', '全部审核')">
  37. <text>全部审核</text>
  38. </view>
  39. <view class="sub-tab-option" :class="{ active: selectedSubTabOption === '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: selectedSubTabOption === 'unfinished' }" @click="selectSubTabOption('unfinished', '最新发起')">
  46. <text>最新发起</text>
  47. </view>
  48. <view class="sub-tab-option" :class="{ active: selectedSubTabOption === '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. selectedSubTabOption: 'all',
  194. selectedSubTabLabel: '全部审核',
  195. // 顶部选项卡数组
  196. topTabs: [{
  197. label: '待办',
  198. value: 'todo',
  199. badge: 0
  200. },
  201. {
  202. label: '已办',
  203. value: 'done',
  204. badge: 0
  205. },
  206. {
  207. label: '我发起的',
  208. value: 'myInitiate',
  209. badge: 0
  210. }
  211. ],
  212. currentTopTab: 0,
  213. currentSubTab: 'all',
  214. // 子标签数组
  215. taskSubTabs: [{
  216. label: '全部审批',
  217. value: 'all'
  218. },
  219. {
  220. label: '最新发起',
  221. value: 'unfinished'
  222. }
  223. ],
  224. tabsList: ['全部审批', '任务创建'],
  225. tabsList2: ['最新发起', '最早发起'],
  226. // auditSubTabs: [{
  227. // label: '全部',
  228. // value: 'all'
  229. // },
  230. // {
  231. // label: '驳回',
  232. // value: 'rejected'
  233. // }
  234. // ],
  235. // 加载状态
  236. loading: {
  237. todo: false,
  238. done: false,
  239. myInitiate: false
  240. },
  241. // 列表数据
  242. todoList: [], // 待办列表(index=0)
  243. doneList: [], // 已办列表(index=1)
  244. myInitiateList: [] // 我发起的列表(index=2)
  245. }
  246. },
  247. computed: {
  248. currentSubTabsList() {
  249. return this.taskSubTabs;
  250. // if (this.currentTopTab === 0) return this.taskSubTabs;
  251. // if (this.currentTopTab === 1) return this.auditSubTabs;
  252. // if (this.currentTopTab === 2) return this.myInitiateList;
  253. },
  254. currentList() {
  255. if (this.currentTopTab === 0) return this.todoList;
  256. if (this.currentTopTab === 1) return this.doneList;
  257. if (this.currentTopTab === 2) return this.myInitiateList;
  258. }
  259. },
  260. onLoad() {
  261. // 页面加载时的初始化操作
  262. this.loadData();
  263. utils.getDicts("FLOW_TEMPLATE").then(res => {
  264. this.processTemplate = res;
  265. console.log("流程模板", this.processTemplate)
  266. });
  267. },
  268. methods: {
  269. onRefresh() {
  270. this.refreshing = true;
  271. this.loadData().finally(() => {
  272. setTimeout(() => {
  273. this.refreshing = false;
  274. }, 500);
  275. });
  276. },
  277. onRestore() {
  278. this.refreshing = false;
  279. },
  280. openSubTabPopup(type) {
  281. this.currentPopupType = type;
  282. this.showSubTabPopup = true;
  283. },
  284. selectSubTabOption(value, label) {
  285. this.selectedSubTabOption = value;
  286. this.selectedSubTabLabel = label;
  287. this.currentSubTab = value;
  288. this.showSubTabPopup = false;
  289. // 这里可以添加根据选择的子标签过滤数据的逻辑
  290. this.loadData();
  291. },
  292. selectTabItem(item) {
  293. this.selectedTabItem = item;
  294. this.popShow = false;
  295. },
  296. goDetails(item) {
  297. console.log("跳转参数",item)
  298. uni.navigateTo({
  299. url: `/pages_task/approvalTaskDetail?taskId=${item.id}&businessType=${item.businessType}`
  300. })
  301. },
  302. switchTopTab(index) {
  303. this.currentTopTab = index;
  304. this.currentSubTab = 'all';
  305. this.selectedSubTabOption = 'all';
  306. this.selectedSubTabLabel = '全部审核';
  307. this.popShow = false;
  308. this.showSubTabPopup = false;
  309. // 切换标签时重新加载数据
  310. this.loadData();
  311. },
  312. closeFilter() {
  313. this.showFilter = false
  314. },
  315. resetFilters() {
  316. this.filters = {
  317. applyTimeStart: '',
  318. applyTimeEnd: '',
  319. finishTimeStart: '',
  320. finishTimeEnd: '',
  321. auditTimeStart: '',
  322. auditTimeEnd: '',
  323. taskBelong: 'my',
  324. finishStatus: '',
  325. belongType: ''
  326. }
  327. },
  328. confirmFilters() {
  329. this.showFilter = false
  330. console.log('筛选条件:', this.filters)
  331. // 筛选后重新加载数据
  332. this.loadData()
  333. },
  334. getAuditNameDisplay(auditName) {
  335. if (!auditName) return '未设置'
  336. switch (auditName) {
  337. case '完成任务审核':
  338. return '"完成任务"审核'
  339. case '定级审核':
  340. return '"讲者定级"审核'
  341. default:
  342. // 对于其他审核类型,在最开始和审核前都加双引号
  343. const prefix = auditName.replace('审核', '')
  344. return '"' + prefix + '"审核'
  345. }
  346. },
  347. handleShare(item) {
  348. uni.showToast({
  349. title: '分享功能待实现',
  350. icon: 'none'
  351. })
  352. },
  353. handleSearchInput() {
  354. // 防抖处理,避免频繁请求
  355. if (this.searchTimer) {
  356. clearTimeout(this.searchTimer)
  357. }
  358. this.searchTimer = setTimeout(async () => {
  359. if (this.searchKeywords.trim()) {
  360. try {
  361. // 调用搜索发起人接口
  362. const res = await searchCompanyUser({
  363. keywords: this.searchKeywords.trim(),
  364. pageNum: 1,
  365. pageSize: 10
  366. })
  367. if (res.code === 200 && res.rows) {
  368. // 根据当前标签页更新对应列表
  369. if (this.currentTopTab === 0) {
  370. this.todoList = res.rows.map(item => ({
  371. auditName: item.auditName || '未知审核',
  372. statusText: '待审核',
  373. statusClass: 'status-createPending',
  374. id: item.id,
  375. status: item.status,
  376. auditType: item.auditType,
  377. businessId: item.businessId,
  378. businessType: item.businessType,
  379. createTime: item.createTime,
  380. initiatorName: item.initiatorName
  381. }))
  382. } else if (this.currentTopTab === 1) {
  383. this.doneList = res.rows.map(item => ({
  384. auditName: item.auditName || '未知审核',
  385. statusText: item.status === 1 ? '已通过' : '已驳回',
  386. statusClass: item.status === 1 ? 'status-finish' : 'status-rejected',
  387. id: item.id,
  388. status: item.status,
  389. auditType: item.auditType,
  390. businessId: item.businessId,
  391. businessType: item.businessType,
  392. createTime: item.createTime,
  393. initiatorName: item.initiatorName
  394. }))
  395. } else if (this.currentTopTab === 2) {
  396. this.myInitiateList = res.rows.map(item => ({
  397. auditName: item.auditName || '未知审核',
  398. statusText: '待审核',
  399. statusClass: 'status-createPending',
  400. id: item.id,
  401. status: item.status,
  402. auditType: item.auditType,
  403. businessId: item.businessId,
  404. businessType: item.businessType,
  405. createTime: item.createTime,
  406. initiatorName: item.initiatorName
  407. }))
  408. }
  409. } else {
  410. // 搜索结果为空,清空对应列表
  411. if (this.currentTopTab === 0) {
  412. this.todoList = []
  413. } else if (this.currentTopTab === 1) {
  414. this.doneList = []
  415. } else if (this.currentTopTab === 2) {
  416. this.myInitiateList = []
  417. }
  418. }
  419. } catch (e) {
  420. console.error('搜索失败', e)
  421. uni.showToast({
  422. title: '搜索失败',
  423. icon: 'none'
  424. })
  425. }
  426. } else {
  427. // 搜索框为空,重新加载默认数据
  428. this.loadData()
  429. }
  430. }, 300)
  431. },
  432. // 加载审批数据
  433. async loadData() {
  434. try {
  435. // 无论当前标签是什么,都调用getPendingAuditList请求
  436. const userInfoStr = uni.getStorageSync('userInfo')
  437. const userInfo = userInfoStr ? (typeof userInfoStr === 'string' ? JSON.parse(userInfoStr) : userInfoStr) : {}
  438. const params = {
  439. initiatorName: userInfo.nickName || '',
  440. initiatorPhone: userInfo.phone || '',
  441. // initiatorId: 0,
  442. status: userInfo.status|| '',
  443. // tempCode: userInfo.productCode || '',
  444. sort: 0,
  445. userId:userInfo.userId|| '',
  446. companyId: userInfo.companyId || 0
  447. }
  448. const res = await getPendingAuditList(params)
  449. if (res.code === 200) {
  450. // 处理返回的数据
  451. const responseData = res.rows|| [];
  452. // 根据status分类数据
  453. const todoData = responseData.filter(item => item.status === 1);
  454. const doneData = responseData.filter(item => item.status === 2);
  455. const myInitiateData = responseData.filter(item => item.status === 3);
  456. // 更新对应列表
  457. if (this.currentTopTab === 0) {
  458. // 待办列表
  459. this.loading.todo = true
  460. this.todoList = todoData.map(item => ({
  461. auditName: item.auditName || '未知审核',
  462. statusText: '待审核',
  463. statusClass: 'status-createPending',
  464. id: item.id,
  465. status: item.status,
  466. auditType: item.auditType,
  467. businessId: item.businessId,
  468. businessType: item.businessType,
  469. createTime: item.createTime,
  470. initiatorName:item.initiatorName
  471. }))
  472. this.loading.todo = false
  473. } else if (this.currentTopTab === 1) {
  474. // 已办列表
  475. this.loading.done = true
  476. this.doneList = doneData.map(item => ({
  477. auditName: item.auditName || '未知审核',
  478. statusText: item.status === 1 ? '已通过' : '已驳回',
  479. statusClass: item.status === 1 ? 'status-finish' : 'status-rejected',
  480. id: item.id,
  481. status: item.status,
  482. auditType: item.auditType,
  483. businessId: item.businessId,
  484. businessType: item.businessType,
  485. createTime: item.createTime,
  486. initiatorName:item.initiatorName
  487. }))
  488. this.loading.done = false
  489. } else if (this.currentTopTab === 2) {
  490. // 我发起的列表
  491. this.loading.myInitiate = true
  492. this.myInitiateList = myInitiateData.map(item => ({
  493. auditName: item.auditName || '未知审核',
  494. statusText: '待审核',
  495. statusClass: 'status-createPending',
  496. id: item.id,
  497. status: item.status,
  498. auditType: item.auditType,
  499. businessId: item.businessId,
  500. businessType: item.businessType,
  501. createTime: item.createTime,
  502. initiatorName:item.initiatorName
  503. }))
  504. this.loading.myInitiate = false
  505. }
  506. // 更新badge数量
  507. this.topTabs[0].badge = todoData.length
  508. this.topTabs[1].badge = doneData.length
  509. this.topTabs[2].badge = myInitiateData.length
  510. } else {
  511. uni.showToast({
  512. title: '获取审核列表失败',
  513. icon: 'none'
  514. })
  515. }
  516. } catch (e) {
  517. console.error('加载数据失败', e)
  518. uni.showToast({
  519. title: '加载数据失败',
  520. icon: 'none'
  521. })
  522. // 重置加载状态
  523. this.loading.todo = false
  524. this.loading.done = false
  525. this.loading.myInitiate = false
  526. }
  527. },
  528. // 格式化时间
  529. formatTime(timeStr) {
  530. if (!timeStr) return ''
  531. const date = new Date(timeStr)
  532. const year = date.getFullYear()
  533. const month = String(date.getMonth() + 1).padStart(2, '0')
  534. const day = String(date.getDate()).padStart(2, '0')
  535. const hours = String(date.getHours()).padStart(2, '0')
  536. const minutes = String(date.getMinutes()).padStart(2, '0')
  537. return `${year}-${month}-${day} ${hours}:${minutes}`
  538. },
  539. getBusinessTypeLabel(businessType) {
  540. if (!businessType || !this.processTemplate) {
  541. return '未设置'
  542. }
  543. const dictItem = this.processTemplate.find(item => item.dictValue === businessType)
  544. return dictItem ? dictItem.dictLabel : businessType
  545. }
  546. }
  547. }
  548. </script>
  549. <style lang="scss" scoped>
  550. .container {
  551. min-height: 100vh;
  552. background: #F7F8FA;
  553. position: relative;
  554. // 搜索栏
  555. .top-box {
  556. padding: 20rpx 32rpx;
  557. display: flex;
  558. align-items: center;
  559. background: #fff;
  560. position: relative;
  561. z-index: 101;
  562. .input-item {
  563. display: flex;
  564. align-items: center;
  565. flex: 1;
  566. height: 72rpx;
  567. background: #F7F8FA;
  568. border-radius: 38rpx;
  569. .search-icon {
  570. width: 26rpx;
  571. height: 26rpx;
  572. margin: 0 10rpx 0 28rpx;
  573. }
  574. input {
  575. flex: 1;
  576. font-size: 28rpx;
  577. color: #333;
  578. }
  579. }
  580. }
  581. // 顶部选项卡
  582. .top-tabs {
  583. display: flex;
  584. background: #fff;
  585. position: relative;
  586. z-index: 101;
  587. .top-tab-item {
  588. flex: 1;
  589. text-align: center;
  590. padding: 24rpx 0;
  591. font-size: 28rpx;
  592. color: #999;
  593. transition: all 0.2s;
  594. &.active {
  595. color: #333;
  596. font-weight: 500;
  597. position: relative;
  598. &::after {
  599. content: '';
  600. position: absolute;
  601. bottom: 0;
  602. left: 50%;
  603. transform: translateX(-50%);
  604. border-radius: 3rpx;
  605. width: 80rpx;
  606. height: 6rpx;
  607. background: #388BFF;
  608. }
  609. }
  610. }
  611. }
  612. // 核心修改:子标签容器(定位参考)
  613. .sub-tabs-wrapper {
  614. position: relative; // 作为popup-box的定位参考
  615. z-index: 101; // 确保在遮罩层之上
  616. // 子标签栏
  617. .sub-tabs {
  618. display: flex;
  619. justify-content: space-between;
  620. background: #fff;
  621. padding: 16rpx 32rpx;
  622. gap: 24rpx;
  623. .sub-tab-item {
  624. flex: 1;
  625. height: 64rpx;
  626. display: flex;
  627. border-radius: 70rpx;
  628. border: 2rpx solid #F2F2F2;
  629. justify-content: center;
  630. align-items: center;
  631. font-size: 28rpx;
  632. color: #666;
  633. transition: all 0.2s;
  634. .icon {
  635. width: 28rpx;
  636. height: 28rpx;
  637. margin-left: 12rpx;
  638. }
  639. }
  640. }
  641. }
  642. /* 子标签弹窗样式 */
  643. .sub-tab-popup-overlay {
  644. position: absolute;
  645. top: 100%;
  646. height: 100vh;
  647. left: 0;
  648. right: 0;
  649. bottom: -1000rpx;
  650. background: rgba(0, 0, 0, 0.5);
  651. z-index: 8;
  652. }
  653. .sub-tab-popup {
  654. position: absolute;
  655. top: 100%;
  656. left: 0;
  657. right: 0;
  658. background: #fff;
  659. border-radius: 0;
  660. padding: 32rpx;
  661. width: 100%;
  662. box-sizing: border-box;
  663. box-shadow: none;
  664. z-index: 9;
  665. }
  666. .sub-tab-options {
  667. display: flex;
  668. flex-wrap: wrap;
  669. gap: 24rpx;
  670. border-radius: 70rpx 70rpx 70rpx 70rpx;
  671. justify-content: flex-start;
  672. }
  673. .sub-tab-option {
  674. padding: 24rpx 14rpx;
  675. border-radius: 70rpx;
  676. font-size: 28rpx;
  677. color: #666666;
  678. background: #ffffff;
  679. border: 1rpx solid #e8e8e8;
  680. transition: all 0.2s ease;
  681. text-align: center;
  682. min-width: 214rpx;
  683. box-sizing: border-box;
  684. &.active {
  685. background: rgba(56,139,255,0.15);
  686. color: #ffffff;
  687. color: #388BFF;
  688. border: 1rpx solid #388BFF ;
  689. }
  690. }
  691. // 列表区域
  692. .content {
  693. padding: 24rpx;
  694. box-sizing: border-box;
  695. min-height: calc(100vh - 300rpx);
  696. position: relative;
  697. z-index: 98; // 低于遮罩层
  698. .task-card {
  699. background: #fff;
  700. border-radius: 24rpx;
  701. margin-bottom: 20rpx;
  702. border: 2rpx solid #E9F2FF;
  703. position: relative;
  704. padding-bottom: 24rpx;
  705. .card-top {
  706. display: flex;
  707. justify-content: space-between;
  708. align-items: center;
  709. background: linear-gradient(90deg, #E8F1FF 0%, #FFFFFF 100%);
  710. border-radius: 24rpx 24rpx 0 0;
  711. padding: 26rpx 24rpx;
  712. margin-bottom: 16rpx;
  713. .card-task-title {
  714. font-size: 32rpx;
  715. font-weight: 600;
  716. color: #333;
  717. }
  718. .status-tag {
  719. padding: 8rpx 16rpx;
  720. border-radius: 20rpx;
  721. font-size: 24rpx;
  722. &.status-wait {
  723. background: #E3F2FD;
  724. color: #2196F3;
  725. }
  726. &.status-finish {
  727. background: #E6FAEF;
  728. color: #07C160;
  729. }
  730. &.status-rejected {
  731. background: #FFF4F5;
  732. color: #CF3546;
  733. }
  734. &.status-createPending {
  735. background: #FFF8E6;
  736. color: #FF9500;
  737. }
  738. }
  739. }
  740. .card-content {
  741. padding: 0 24rpx;
  742. .time-info {
  743. font-size: 28rpx;
  744. color: #666;
  745. display: flex;
  746. flex-direction: column;
  747. gap: 8rpx;
  748. margin-bottom: 24rpx;
  749. .time-item {
  750. display: flex;
  751. font-size: 28rpx;
  752. color: #333;
  753. .title {
  754. color: #666;
  755. }
  756. }
  757. }
  758. .operate-btn-group {
  759. display: flex;
  760. align-items: center;
  761. justify-content: space-between;
  762. .share-btn {
  763. display: flex;
  764. align-items: center;
  765. font-size: 28rpx;
  766. color: #666;
  767. .share-icon {
  768. width: 36rpx;
  769. height: 36rpx;
  770. margin-right: 8rpx;
  771. }
  772. }
  773. .date {
  774. font-size: 24rpx;
  775. color: #999;
  776. }
  777. }
  778. }
  779. }
  780. }
  781. // 筛选弹窗
  782. .filter-popup {
  783. position: fixed;
  784. top: 0;
  785. left: 0;
  786. right: 0;
  787. bottom: 0;
  788. background: rgba(0, 0, 0, 0.5);
  789. z-index: 999;
  790. display: flex;
  791. align-items: flex-end;
  792. .filter-content {
  793. width: 100%;
  794. background: #fff;
  795. border-radius: 24rpx 24rpx 0 0;
  796. padding: 32rpx;
  797. .filter-header {
  798. display: flex;
  799. align-items: center;
  800. justify-content: center;
  801. margin-bottom: 32rpx;
  802. position: relative;
  803. .filter-title {
  804. font-size: 32rpx;
  805. font-weight: bold;
  806. color: #333;
  807. }
  808. .filter-close-btn {
  809. position: absolute;
  810. right: 0;
  811. width: 44rpx;
  812. height: 44rpx;
  813. }
  814. }
  815. .filter-form {
  816. padding: 0 0 24rpx 0;
  817. .filter-section {
  818. margin-bottom: 32rpx;
  819. .section-label {
  820. font-weight: 500;
  821. font-size: 28rpx;
  822. color: #333;
  823. margin-bottom: 24rpx;
  824. }
  825. .time-range {
  826. display: flex;
  827. align-items: center;
  828. gap: 16rpx;
  829. .time-input {
  830. flex: 1;
  831. height: 72rpx;
  832. text-align: center;
  833. background: #F7F8FA;
  834. border-radius: 8rpx;
  835. padding: 0 16rpx;
  836. font-size: 26rpx;
  837. color: #333;
  838. }
  839. .time-separator {
  840. font-size: 24rpx;
  841. color: #999;
  842. }
  843. }
  844. .btn-group {
  845. display: flex;
  846. gap: 16rpx;
  847. .filter-btn {
  848. width: 214rpx;
  849. height: 72rpx;
  850. background: #F7F8FA;
  851. border-radius: 70rpx;
  852. line-height: 72rpx;
  853. text-align: center;
  854. font-size: 28rpx;
  855. transition: all 0.2s;
  856. color: #333;
  857. &.active {
  858. background: rgba(56, 139, 255, 0.15);
  859. color: #388BFF;
  860. }
  861. }
  862. }
  863. }
  864. }
  865. .filter-actions {
  866. display: flex;
  867. gap: 24rpx;
  868. margin-top: 40rpx;
  869. .reset-btn,
  870. .confirm-btn {
  871. flex: 1;
  872. height: 80rpx;
  873. line-height: 80rpx;
  874. text-align: center;
  875. border-radius: 200rpx;
  876. font-size: 28rpx;
  877. transition: all 0.2s;
  878. }
  879. .reset-btn {
  880. background: #fff;
  881. color: #388BFF;
  882. border: 2rpx solid #388BFF;
  883. &:active {
  884. background: #EBF3FF;
  885. }
  886. }
  887. .confirm-btn {
  888. background: #388BFF;
  889. color: #fff;
  890. &:active {
  891. background: #2A78E5;
  892. }
  893. }
  894. }
  895. }
  896. }
  897. }
  898. </style>