index.vue 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. <template>
  2. <view class="page_publish">
  3. <commonHeader :title="isEdit ? '编辑任务' : '新建任务'" @back="goBack"></commonHeader>
  4. <scroll-view scroll-y class="publish_content" :style="{ paddingTop: statusBarHeight + 160 + 'rpx', paddingBottom: safeAreaBottom + 'rpx' }">
  5. <view class="form_item">
  6. <view class="form_row form_row_type">
  7. <text class="form_label">学年学期</text>
  8. <view class="form_content">
  9. <picker mode="selector" :range="academicYearOptions" :value="academicYearIndex" @change="handleAcademicYearChange">
  10. <view class="filter_item">
  11. <text class="filter_text">{{ academicYearOptions[academicYearIndex] }}</text>
  12. <uni-icons type="down" size="16" color="#999999"></uni-icons>
  13. </view>
  14. </picker>
  15. </view>
  16. </view>
  17. </view>
  18. <view class="form_item">
  19. <view class="form_row form_row_type">
  20. <text class="form_label">归属部门</text>
  21. <view class="form_content">
  22. <picker mode="selector" :range="categoryOptions" :value="categoryIndex" @change="handleCategoryChange">
  23. <view class="filter_item">
  24. <text class="filter_text">{{ categoryOptions[categoryIndex] }}</text>
  25. <uni-icons type="down" size="16" color="#999999"></uni-icons>
  26. </view>
  27. </picker>
  28. </view>
  29. </view>
  30. </view>
  31. <view class="form_item">
  32. <view class="form_row form_row_type">
  33. <text class="form_label">材料类目</text>
  34. <view class="form_content">
  35. <picker mode="selector" :range="materialTypeOptions" :value="materialTypeIndex" @change="handleMaterialTypeChange">
  36. <view class="filter_item">
  37. <text class="filter_text">{{ materialTypeOptions[materialTypeIndex] }}</text>
  38. <uni-icons type="down" size="16" color="#999999"></uni-icons>
  39. </view>
  40. </picker>
  41. </view>
  42. </view>
  43. </view>
  44. <view class="form_item">
  45. <view class="form_row form_row_type">
  46. <text class="form_label">任务名称</text>
  47. <view class="form_content">
  48. <input type="text" class="form_input" v-model="formData.taskName" placeholder="请输入任务名称" />
  49. </view>
  50. </view>
  51. </view>
  52. <view class="form_item">
  53. <view class="form_row">
  54. <text class="form_label">采集要求</text>
  55. <view class="form_content">
  56. <view class="textarea_container">
  57. <textarea class="form_textarea" v-model="formData.collectRequirement" placeholder="请输入通知内容"
  58. :maxlength="-1"></textarea>
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. <view class="form_item">
  64. <view class="form_row form_row_type">
  65. <text class="form_label">采集周期</text>
  66. <view class="form_content date_range">
  67. <picker mode="date" :value="formData.startTime" @change="onStartDateChange">
  68. <view class="date_range_item">
  69. <text class="date_text" :class="{ placeholder: !formData.startTime }">{{ formData.startTime || '开始日期' }}</text>
  70. <uni-icons type="calendar" size="16" color="#999999"></uni-icons>
  71. </view>
  72. </picker>
  73. <text class="date_separator">—</text>
  74. <picker mode="date" :value="formData.endTime" @change="onEndDateChange">
  75. <view class="date_range_item">
  76. <text class="date_text" :class="{ placeholder: !formData.endTime }">{{ formData.endTime || '结束日期' }}</text>
  77. <uni-icons type="calendar" size="16" color="#999999"></uni-icons>
  78. </view>
  79. </picker>
  80. </view>
  81. </view>
  82. </view>
  83. <view class="form_item">
  84. <view class="form_row form_row_type">
  85. <text class="form_label">审核机制</text>
  86. <view class="form_content">
  87. <customRadio v-model="formData.auditMechanism" :options="auditOptions" />
  88. </view>
  89. </view>
  90. </view>
  91. <view class="form_item" v-if="formData.auditMechanism === 1">
  92. <view class="form_row form_row_type">
  93. <text class="form_label">审核人</text>
  94. <view class="form_content">
  95. <view class="auditor_row" @click="openAuditorDialog">
  96. <text class="auditor_name" :class="{ placeholder: !formData.auditorName }">{{ formData.auditorName || '请选择审核人' }}</text>
  97. <uni-icons type="down" size="16" color="#999999"></uni-icons>
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. <view class="form_item">
  103. <view class="form_row">
  104. <text class="form_label">采集对象</text>
  105. <view class="form_content">
  106. <view class="object_row">
  107. <view class="object_info" @click="goPersonList">
  108. <text class="object_count">{{ objectTypeLabel || '请选择' }}</text>
  109. <text class="object_count">已选{{ selectedCount }}人</text>
  110. </view>
  111. <view class="object_action" @click="selectObject">
  112. <text class="action_text">选择采集对象</text>
  113. <text class="action_arrow">></text>
  114. </view>
  115. </view>
  116. </view>
  117. </view>
  118. </view>
  119. </scroll-view>
  120. <view class="publish_footer" :style="{ paddingBottom: safeAreaBottom + 24 + 'rpx' }">
  121. <view class="confirm_btn" @click="handleSave">
  122. <text class="btn_text">确定</text>
  123. </view>
  124. </view>
  125. <!-- 审核人搜索选择弹窗 -->
  126. <customPopupDialog ref="auditorDialogRef" title="选择审核人" :showDialogFooter="false" @DialogConfirm="confirmAuditorSelection">
  127. <view class="auditor_search_wrap">
  128. <view class="auditor_search_box">
  129. <uni-icons type="search" size="20" color="#999999"></uni-icons>
  130. <input type="text" class="auditor_search_input" v-model="auditorSearchKey" placeholder="请输入搜索关键字"
  131. @confirm="searchAuditors" @input="handleAuditorSearchInput" />
  132. <text v-if="auditorSearchKey" class="auditor_search_clear" @click="clearAuditorSearch">
  133. <uni-icons type="clear" size="18" color="#999999"></uni-icons>
  134. </text>
  135. </view>
  136. <scroll-view scroll-y class="auditor_list">
  137. <view v-if="auditorSearchLoading" class="auditor_loading">
  138. <text>加载中...</text>
  139. </view>
  140. <view v-else-if="auditorListFiltered.length === 0" class="auditor_empty">
  141. <noData :tipText="'暂无搜索结果'" />
  142. </view>
  143. <view v-else class="auditor_list_content">
  144. <view v-for="(item, idx) in auditorListFiltered" :key="idx"
  145. class="auditor_list_item"
  146. :class="{ active: selectedAuditor && selectedAuditor.teacherId === item.teacherId }"
  147. @click="selectAuditor(item)">
  148. <view class="auditor_item_info">
  149. <text class="auditor_item_name">{{ item.teacherName }}</text>
  150. <text v-if="item.teacherAccount" class="auditor_item_account">账号:{{ item.teacherAccount
  151. }}</text>
  152. </view>
  153. <view v-if="selectedAuditor && selectedAuditor.teacherId === item.teacherId"
  154. class="auditor_item_check">
  155. <uni-icons type="checkmarkempty" size="22" color="#2E64FA"></uni-icons>
  156. </view>
  157. </view>
  158. </view>
  159. </scroll-view>
  160. <view class="auditor_dialog_footer">
  161. <view class="auditor_dialog_btn cancel_btn" @click="closeAuditorDialog">
  162. <text>取消</text>
  163. </view>
  164. <view class="auditor_dialog_btn confirm_btn" @click="confirmAuditorSelection">
  165. <text>确定</text>
  166. </view>
  167. </view>
  168. </view>
  169. </customPopupDialog>
  170. </view>
  171. </template>
  172. <script setup>
  173. import { ref, reactive, onMounted, computed } from 'vue';
  174. import { onLoad, onShow } from '@dcloudio/uni-app';
  175. import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
  176. import {getSchoolYearList,getCategoryList,getAuditorList,find_collection_task_target_list,save_collection_task} from '@/reqApi/notification.js';
  177. import CustomRadio from '@/components/customRadio.vue';
  178. import CommonHeader from '@/components/commonHeader.vue';
  179. import CustomPopupDialog from '@/components/customPopupDialog.vue';
  180. import NoData from '@/components/noData.vue';
  181. import { useSafeArea } from '@/common/safeArea';
  182. const { statusBarHeight, safeAreaBottom, initSafeArea } = useSafeArea();
  183. const isEdit = ref(false);
  184. const editId = ref('');
  185. const academicYearIndex = ref(0);
  186. const academicYearOptions = ref(['']);
  187. const academicYearIds = ref(['']);
  188. const academicYearCodes = ref(['']);
  189. const categoryIndex = ref(0);
  190. const categoryOptions = ref(['全部分类']);
  191. const categoryData = ref([]);
  192. const materialTypeIndex = ref(0);
  193. const materialTypeOptions = ref(['全部类目']);
  194. // 审核人弹窗相关
  195. const auditorDialogRef = ref(null);
  196. const auditorSearchKey = ref('');
  197. const auditorSearchLoading = ref(false);
  198. const auditorListFull = ref([]);
  199. const selectedAuditor = ref(null);
  200. let auditorSearchTimer = null;
  201. const auditorListFiltered = computed(() => {
  202. return auditorListFull.value;
  203. });
  204. const auditOptions = [
  205. { label: '需审核', value: 1 },
  206. { label: '无审核', value: 0 }
  207. ];
  208. const formData = reactive({
  209. schoolYearName: '',
  210. schoolYearId: '',
  211. schoolYearCode: '',
  212. departmentName: '',
  213. departmentId: '',
  214. materialCategoryName: '',
  215. materialCategoryId: '',
  216. taskName: '',
  217. collectRequirement: '',
  218. startTime: '',
  219. endTime: '',
  220. auditMechanism: 0,
  221. auditorName: '',
  222. auditorId: ''
  223. });
  224. const selectedCount = ref(0);
  225. const selectedUsers = ref([]);
  226. const objectTypeLabel = ref('');
  227. const originalNoticeType = ref(0);
  228. const fetchAcademicYearList = async () => {
  229. try {
  230. const res = await getSchoolYearList();
  231. if (res.data && Array.isArray(res.data)) {
  232. const names = res.data.map(item => item.schoolYearName);
  233. const ids = res.data.map(item => item.id);
  234. const codes = res.data.map(item => item.schoolYearCode);
  235. academicYearOptions.value = names;
  236. academicYearIds.value = ids;
  237. academicYearCodes.value = codes;
  238. if (res.data.length > 0) {
  239. academicYearIndex.value = 0;
  240. formData.schoolYearName = res.data[0].schoolYearName;
  241. formData.schoolYearId = res.data[0].id;
  242. formData.schoolYearCode = res.data[0].schoolYearCode;
  243. }
  244. }
  245. } catch (error) {
  246. console.error('获取学年学期列表失败:', error);
  247. }
  248. };
  249. const fetchCategoryList = async () => {
  250. try {
  251. const res = await getCategoryList();
  252. if (res.data && Array.isArray(res.data)) {
  253. categoryData.value = res.data;
  254. const categoryNames = res.data.map(item => item.categoryName || item.name);
  255. categoryOptions.value = ['全部分类', ...categoryNames];
  256. categoryIndex.value = 0;
  257. materialTypeOptions.value = ['全部类目'];
  258. formData.departmentName = '';
  259. formData.departmentId = '';
  260. formData.materialCategoryName = '';
  261. formData.materialCategoryId = '';
  262. }
  263. } catch (error) {
  264. console.error('获取归属部门列表失败:', error);
  265. }
  266. };
  267. const searchAuditors = async () => {
  268. auditorSearchLoading.value = true;
  269. try {
  270. const res = await getAuditorList(auditorSearchKey.value || '');
  271. if (res.data && Array.isArray(res.data)) {
  272. auditorListFull.value = res.data.map(item => ({
  273. teacherId: item.teacherId || item.id,
  274. teacherName: item.teacherName || item.name,
  275. teacherAccount: item.teacherAccount || item.accountName || item.userAccount || ''
  276. }));
  277. } else {
  278. auditorListFull.value = [];
  279. }
  280. } catch (error) {
  281. console.error('搜索审核人失败:', error);
  282. auditorListFull.value = [];
  283. } finally {
  284. auditorSearchLoading.value = false;
  285. }
  286. };
  287. const handleAuditorSearchInput = () => {
  288. if (auditorSearchTimer) {
  289. clearTimeout(auditorSearchTimer);
  290. }
  291. auditorSearchTimer = setTimeout(() => {
  292. searchAuditors();
  293. }, 500);
  294. };
  295. const clearAuditorSearch = () => {
  296. auditorSearchKey.value = '';
  297. searchAuditors();
  298. };
  299. const openAuditorDialog = () => {
  300. auditorDialogRef.value.OpenDialog('bottom');
  301. // 初始化选中状态
  302. if (formData.auditorId) {
  303. selectedAuditor.value = {
  304. teacherId: formData.auditorId,
  305. teacherName: formData.auditorName
  306. };
  307. } else {
  308. selectedAuditor.value = null;
  309. }
  310. auditorSearchKey.value = '';
  311. auditorListFull.value = [];
  312. searchAuditors();
  313. };
  314. const closeAuditorDialog = () => {
  315. auditorDialogRef.value.CloseDialog();
  316. };
  317. const selectAuditor = (item) => {
  318. selectedAuditor.value = item;
  319. };
  320. const confirmAuditorSelection = () => {
  321. if (!selectedAuditor.value) {
  322. uni.showToast({ title: '请选择审核人', icon: 'none' });
  323. return;
  324. }
  325. formData.auditorName = selectedAuditor.value.teacherName;
  326. formData.auditorId = selectedAuditor.value.teacherId;
  327. closeAuditorDialog();
  328. };
  329. onMounted(() => {
  330. initSafeArea();
  331. });
  332. onLoad(async (options) => {
  333. await Promise.all([
  334. fetchAcademicYearList(),
  335. fetchCategoryList()
  336. ]);
  337. if (options && options.id) {
  338. isEdit.value = true;
  339. editId.value = options.id;
  340. fetchTaskDetail(options.id);
  341. }
  342. });
  343. const fetchTaskDetail = async (id) => {
  344. try {
  345. const res = await find_collection_task_target_list({ id });
  346. if (res.data) {
  347. const data = res.data;
  348. formData.taskName = data.taskName || '';
  349. const content = data.noticeContent || data.content || '';
  350. formData.collectRequirement = content.replace(/<[^>]+>/g, '');
  351. formData.startTime = data.startTime || '';
  352. formData.endTime = data.endTime || '';
  353. formData.auditMechanism = data.auditMechanism ? 1 : 0;
  354. formData.auditorId = data.auditorId || '';
  355. formData.auditorName = data.auditorName || '';
  356. formData.departmentName = data.departmentName || '';
  357. formData.materialCategoryName = data.materialCategoryName || '';
  358. formData.schoolYearName = data.schoolYearName || '';
  359. if (data.departmentId) {
  360. formData.departmentId = data.departmentId;
  361. const idx = categoryData.value.findIndex(item => item.id === data.departmentId);
  362. if (idx >= 0) {
  363. categoryIndex.value = idx + 1;
  364. const selectedCategory = categoryData.value[idx];
  365. const childList = selectedCategory?.childList || [];
  366. materialTypeOptions.value = ['全部类目', ...childList.map(child => child.categoryName || child.name)];
  367. }
  368. }
  369. if (data.materialCategory) {
  370. formData.materialCategoryId = data.materialCategory;
  371. if (categoryIndex.value > 0) {
  372. const selectedCategory = categoryData.value[categoryIndex.value - 1];
  373. const childList = selectedCategory?.childList || [];
  374. const materialIdx = childList.findIndex(item => item.id === data.materialCategory);
  375. if (materialIdx >= 0) {
  376. materialTypeIndex.value = materialIdx + 1;
  377. formData.materialCategoryName = childList[materialIdx].categoryName || childList[materialIdx].name || '';
  378. }
  379. }
  380. }
  381. if (data.noticeType) {
  382. const typeMap = {
  383. 1: '按部门',
  384. 2: '按学科',
  385. 3: '按年级',
  386. 4: '按权限',
  387. 5: '按学生'
  388. };
  389. objectTypeLabel.value = typeMap[data.noticeType] || '';
  390. originalNoticeType.value = data.noticeType;
  391. }
  392. if (data.userList && Array.isArray(data.userList)) {
  393. selectedUsers.value = data.userList.map(user => ({
  394. id: user.userId || user.id,
  395. name: user.thirdName || user.name,
  396. userId: user.userId,
  397. thirdCode: user.thirdCode || user.userAccount || '',
  398. userAccount: user.userAccount || user.thirdCode,
  399. departmentName: user.departmentName || '',
  400. groupName: user.groupName || '',
  401. subjectName: user.subjectName || '',
  402. gradeName: user.gradeName || '',
  403. classTypeName: user.classTypeName || '',
  404. className: user.className || '',
  405. roleName: user.roleName || ''
  406. }));
  407. selectedCount.value = selectedUsers.value.length;
  408. }
  409. }
  410. } catch (error) {
  411. console.error('获取任务详情失败:', error);
  412. }
  413. };
  414. onShow(() => {
  415. const result = uni.getStorageSync('selectObjectResult');
  416. if (result) {
  417. try {
  418. const data = JSON.parse(result);
  419. if (data) {
  420. objectTypeLabel.value = data.tab;
  421. selectedCount.value = data.count;
  422. selectedUsers.value = data.users;
  423. }
  424. } catch (e) {
  425. console.error('解析选择结果失败:', e);
  426. }
  427. uni.removeStorageSync('selectObjectResult');
  428. }
  429. const personResult = uni.getStorageSync('personListResult');
  430. if (personResult) {
  431. try {
  432. const data = JSON.parse(personResult);
  433. if (data && data.users) {
  434. selectedUsers.value = data.users;
  435. selectedCount.value = data.users.length;
  436. }
  437. } catch (e) {
  438. console.error('解析人员列表结果失败:', e);
  439. }
  440. uni.removeStorageSync('personListResult');
  441. }
  442. });
  443. const handleAcademicYearChange = (e) => {
  444. const idx = e.detail.value;
  445. academicYearIndex.value = idx;
  446. formData.schoolYearName = academicYearOptions.value[idx];
  447. formData.schoolYearId = academicYearIds.value[idx];
  448. formData.schoolYearCode = academicYearCodes.value[idx];
  449. };
  450. const handleCategoryChange = (e) => {
  451. categoryIndex.value = e.detail.value;
  452. materialTypeIndex.value = 0;
  453. if (categoryIndex.value === 0) {
  454. materialTypeOptions.value = ['全部类目'];
  455. formData.departmentName = '';
  456. formData.departmentId = '';
  457. formData.materialCategoryName = '';
  458. formData.materialCategoryId = '';
  459. } else {
  460. const selectedCategory = categoryData.value[categoryIndex.value - 1];
  461. const categoryName = selectedCategory?.categoryName || selectedCategory?.name || '';
  462. formData.departmentName = categoryName;
  463. formData.departmentId = selectedCategory?.id || '';
  464. const childList = selectedCategory?.childList || [];
  465. materialTypeOptions.value = ['全部类目', ...childList.map(child => child.categoryName || child.name)];
  466. formData.materialCategoryName = '';
  467. formData.materialCategoryId = '';
  468. }
  469. };
  470. const handleMaterialTypeChange = (e) => {
  471. materialTypeIndex.value = e.detail.value;
  472. if (categoryIndex.value === 0) {
  473. formData.materialCategoryName = '';
  474. formData.materialCategoryId = '';
  475. } else {
  476. const selectedCategory = categoryData.value[categoryIndex.value - 1];
  477. const childList = selectedCategory?.childList || [];
  478. if (materialTypeIndex.value === 0) {
  479. formData.materialCategoryName = '';
  480. formData.materialCategoryId = '';
  481. } else {
  482. const selectedChild = childList[materialTypeIndex.value - 1];
  483. formData.materialCategoryName = selectedChild?.categoryName || selectedChild?.name || '';
  484. formData.materialCategoryId = selectedChild?.id || '';
  485. }
  486. }
  487. };
  488. const onStartDateChange = (e) => {
  489. formData.startTime = e.detail.value;
  490. };
  491. const onEndDateChange = (e) => {
  492. formData.endTime = e.detail.value;
  493. };
  494. const goBack = () => {
  495. uni.navigateBack();
  496. };
  497. const selectObject = () => {
  498. const lastSelection = {
  499. tab: objectTypeLabel.value,
  500. users: selectedUsers.value,
  501. noticeType: originalNoticeType.value || getNoticeTypeValue(objectTypeLabel.value),
  502. isEdit: isEdit.value
  503. };
  504. uni.setStorageSync('selectObjectLastSelection', JSON.stringify(lastSelection));
  505. uni.navigateTo({ url: '/pages/materialCollection/taskOverview/components/selectObjectNoDedup' });
  506. };
  507. const goPersonList = () => {
  508. if (selectedUsers.value.length === 0) {
  509. return;
  510. }
  511. const data = {
  512. tab: objectTypeLabel.value,
  513. users: selectedUsers.value
  514. };
  515. uni.setStorageSync('personListData', JSON.stringify(data));
  516. uni.navigateTo({ url: '/pages/materialCollection/taskOverview/components/personList' });
  517. };
  518. const getNoticeTypeValue = (tab) => {
  519. const map = {
  520. '按部门': 1,
  521. '按学科': 2,
  522. '按年级': 3,
  523. '按权限': 4,
  524. '按学生': 5
  525. };
  526. return map[tab] || 1;
  527. };
  528. const handleSave = async () => {
  529. if (!formData.schoolYearId) {
  530. uni.showToast({ title: '请选择学年学期', icon: 'none' });
  531. return;
  532. }
  533. if (!formData.departmentId) {
  534. uni.showToast({ title: '请选择归属部门', icon: 'none' });
  535. return;
  536. }
  537. if (!formData.materialCategoryId) {
  538. uni.showToast({ title: '请选择材料类目', icon: 'none' });
  539. return;
  540. }
  541. if (!formData.taskName) {
  542. uni.showToast({ title: '请输入任务名称', icon: 'none' });
  543. return;
  544. }
  545. if (!formData.collectRequirement) {
  546. uni.showToast({ title: '请输入采集要求', icon: 'none' });
  547. return;
  548. }
  549. if (!formData.startTime || !formData.endTime) {
  550. uni.showToast({ title: '请选择采集周期', icon: 'none' });
  551. return;
  552. }
  553. if (formData.auditMechanism === 1 && !formData.auditorId) {
  554. uni.showToast({ title: '请选择审核人', icon: 'none' });
  555. return;
  556. }
  557. if (!objectTypeLabel.value) {
  558. uni.showToast({ title: '请选择采集对象', icon: 'none' });
  559. return;
  560. }
  561. if (selectedUsers.value.length === 0) {
  562. uni.showToast({ title: '请选择采集人员', icon: 'none' });
  563. return;
  564. }
  565. uni.showLoading({ title: '保存中...' });
  566. try {
  567. const objectType = getNoticeTypeValue(objectTypeLabel.value);
  568. const params = {
  569. schoolYearCode: formData.schoolYearCode,
  570. schoolYearId: formData.schoolYearId,
  571. departmentId: formData.departmentId,
  572. materialCategory: formData.materialCategoryId,
  573. taskName: formData.taskName,
  574. noticeContent: btoa(unescape(encodeURIComponent(formData.collectRequirement))),
  575. startTime: formData.startTime,
  576. endTime: formData.endTime,
  577. auditMechanism: formData.auditMechanism,
  578. auditorId: formData.auditorId,
  579. auditorName: formData.auditorName,
  580. noticeType: objectType,
  581. fileList: []
  582. };
  583. if (isEdit.value && editId.value) {
  584. params.id = editId.value;
  585. }
  586. const userList = selectedUsers.value.map(user => {
  587. return {
  588. userType: objectType === 5 ? 2 : 1,
  589. userId: user.userId || user.id,
  590. thirdName: user.name,
  591. thirdCode: user.thirdCode || '',
  592. gradeName: user.gradeName || '',
  593. classTypeName: user.classTypeName || '',
  594. className: user.className || '',
  595. departmentName: user.departmentName || '',
  596. groupName: user.groupName || '',
  597. subjectName: user.subjectName || '',
  598. roleName: user.roleName || ''
  599. };
  600. });
  601. if (userList.length > 0) {
  602. params.userList = userList;
  603. }
  604. const res = await save_collection_task(params);
  605. if (res.code === 200) {
  606. uni.showToast({ title: '保存成功', icon: 'success' });
  607. setTimeout(() => {
  608. uni.navigateBack();
  609. }, 1500);
  610. } else {
  611. uni.showToast({ title: res.message || '保存失败', icon: 'none' });
  612. }
  613. } catch (error) {
  614. console.error('保存失败:', error);
  615. uni.showToast({ title: '保存失败', icon: 'none' });
  616. } finally {
  617. uni.hideLoading();
  618. }
  619. };
  620. </script>
  621. <style lang="scss" scoped>
  622. .page_publish {
  623. min-height: 100vh;
  624. background-color: #FFFFFF;
  625. display: flex;
  626. flex-direction: column;
  627. box-sizing: border-box;
  628. }
  629. .publish_content {
  630. flex: 1;
  631. box-sizing: border-box;
  632. padding-top: 36rpx;
  633. padding-left: 24rpx;
  634. padding-right: 24rpx;
  635. }
  636. .form_item {
  637. background-color: #FFFFFF;
  638. border-radius: 8rpx;
  639. padding-bottom: 24rpx;
  640. margin-bottom: 24rpx;
  641. border-bottom: 2rpx solid #F3F3F3;
  642. &:last-child {
  643. border-bottom: none;
  644. }
  645. .form_label {
  646. font-size: 28rpx;
  647. color: #666666;
  648. font-weight: 400;
  649. white-space: nowrap;
  650. flex-shrink: 0;
  651. margin-right: 16rpx;
  652. &::after {
  653. content: ':';
  654. margin-left: 8rpx;
  655. }
  656. }
  657. .form_row {
  658. display: flex;
  659. align-items: flex-start;
  660. .form_content {
  661. flex: 1;
  662. }
  663. }
  664. .form_row_type {
  665. align-items: center;
  666. .filter_item {
  667. display: flex;
  668. align-items: center;
  669. justify-content: space-between;
  670. padding: 0 16rpx;
  671. height: 72rpx;
  672. line-height: 72rpx;
  673. background-color: #FFFFFF;
  674. border-radius: 8rpx;
  675. width: 320rpx;
  676. border: 2rpx solid #DCDFE6;
  677. .filter_text {
  678. font-size: 28rpx;
  679. color: #333333;
  680. margin-right: 8rpx;
  681. overflow: hidden;
  682. text-overflow: ellipsis;
  683. white-space: nowrap;
  684. &.placeholder {
  685. color: #909399;
  686. }
  687. }
  688. }
  689. }
  690. .form_input {
  691. font-size: 28rpx;
  692. color: #333333;
  693. padding: 0 24rpx;
  694. background-color: #FFFFFF;
  695. border-radius: 8rpx;
  696. width: 100%;
  697. height: 72rpx;
  698. box-sizing: border-box;
  699. border: 2rpx solid #E9E9E9;
  700. }
  701. .textarea_container {
  702. position: relative;
  703. .form_textarea {
  704. width: 100%;
  705. height: 240rpx;
  706. font-size: 28rpx;
  707. color: #333333;
  708. line-height: 1.6;
  709. padding: 14rpx 24rpx;
  710. background-color: #FFFFFF;
  711. border-radius: 8rpx;
  712. box-sizing: border-box;
  713. border: 2rpx solid #E9E9E9;
  714. }
  715. }
  716. .date_range {
  717. display: flex;
  718. align-items: center;
  719. gap: 16rpx;
  720. .date_range_item {
  721. display: flex;
  722. align-items: center;
  723. gap: 8rpx;
  724. padding: 0 16rpx;
  725. height: 72rpx;
  726. background-color: #FFFFFF;
  727. border-radius: 8rpx;
  728. border: 2rpx solid #DCDFE6;
  729. .date_text {
  730. font-size: 28rpx;
  731. color: #333333;
  732. &.placeholder {
  733. color: #909399;
  734. }
  735. }
  736. }
  737. .date_separator {
  738. font-size: 28rpx;
  739. color: #999999;
  740. }
  741. }
  742. .object_row {
  743. display: flex;
  744. align-items: center;
  745. justify-content: space-between;
  746. .object_info {
  747. display: flex;
  748. align-items: center;
  749. gap: 12rpx;
  750. .object_count {
  751. font-size: 28rpx;
  752. color: #2E64FA;
  753. }
  754. }
  755. .object_action {
  756. display: flex;
  757. align-items: center;
  758. gap: 8rpx;
  759. .action_text {
  760. font-size: 28rpx;
  761. color: #2E64FA;
  762. }
  763. .action_arrow {
  764. font-size: 28rpx;
  765. color: #2E64FA;
  766. }
  767. }
  768. }
  769. .auditor_row {
  770. display: flex;
  771. align-items: center;
  772. justify-content: space-between;
  773. padding: 0 16rpx;
  774. height: 72rpx;
  775. background-color: #FFFFFF;
  776. border-radius: 8rpx;
  777. border: 2rpx solid #E9E9E9;
  778. box-sizing: border-box;
  779. .auditor_name {
  780. font-size: 28rpx;
  781. color: #333333;
  782. flex: 1;
  783. overflow: hidden;
  784. text-overflow: ellipsis;
  785. white-space: nowrap;
  786. &.placeholder {
  787. color: #909399;
  788. }
  789. }
  790. }
  791. }
  792. // 审核人搜索弹窗样式
  793. .auditor_search_wrap {
  794. padding: 24rpx;
  795. box-sizing: border-box;
  796. .auditor_search_box {
  797. display: flex;
  798. align-items: center;
  799. height: 72rpx;
  800. background: #F5F7FA;
  801. border-radius: 36rpx;
  802. padding: 0 24rpx;
  803. margin-bottom: 24rpx;
  804. gap: 16rpx;
  805. .auditor_search_input {
  806. flex: 1;
  807. font-size: 28rpx;
  808. color: #333333;
  809. background: transparent;
  810. height: 72rpx;
  811. line-height: 72rpx;
  812. }
  813. .auditor_search_clear {
  814. display: flex;
  815. align-items: center;
  816. justify-content: center;
  817. width: 40rpx;
  818. height: 40rpx;
  819. }
  820. }
  821. .auditor_list {
  822. height: 600rpx;
  823. .auditor_loading {
  824. text-align: center;
  825. padding: 60rpx 0;
  826. font-size: 28rpx;
  827. color: #999999;
  828. }
  829. .auditor_empty {
  830. padding: 60rpx 0;
  831. }
  832. .auditor_list_content {
  833. .auditor_list_item {
  834. display: flex;
  835. align-items: center;
  836. justify-content: space-between;
  837. padding: 24rpx 16rpx;
  838. border-bottom: 2rpx solid #F3F3F3;
  839. &:last-child {
  840. border-bottom: none;
  841. }
  842. &.active {
  843. background-color: rgba(46, 100, 250, 0.05);
  844. }
  845. .auditor_item_info {
  846. flex: 1;
  847. display: flex;
  848. flex-direction: column;
  849. gap: 8rpx;
  850. .auditor_item_name {
  851. font-size: 30rpx;
  852. color: #333333;
  853. font-weight: 500;
  854. }
  855. .auditor_item_account {
  856. font-size: 24rpx;
  857. color: #999999;
  858. }
  859. }
  860. .auditor_item_check {
  861. width: 44rpx;
  862. height: 44rpx;
  863. display: flex;
  864. align-items: center;
  865. justify-content: center;
  866. }
  867. }
  868. }
  869. }
  870. .auditor_dialog_footer {
  871. display: flex;
  872. gap: 24rpx;
  873. padding-top: 24rpx;
  874. margin-top: 12rpx;
  875. border-top: 2rpx solid #F3F3F3;
  876. .auditor_dialog_btn {
  877. flex: 1;
  878. height: 88rpx;
  879. display: flex;
  880. align-items: center;
  881. justify-content: center;
  882. border-radius: 12rpx;
  883. font-size: 30rpx;
  884. font-weight: 500;
  885. }
  886. .cancel_btn {
  887. background: #F0F2F9;
  888. color: #606266;
  889. }
  890. .confirm_btn {
  891. background: #2E64FA;
  892. color: #FFFFFF;
  893. }
  894. }
  895. }
  896. .publish_footer {
  897. padding: 24rpx;
  898. background-color: #FFFFFF;
  899. .confirm_btn {
  900. width: 702rpx;
  901. height: 90rpx;
  902. background: #2E64FA;
  903. border-radius: 8rpx 8rpx 8rpx 8rpx;
  904. text-align: center;
  905. line-height: 90rpx;
  906. .btn_text {
  907. font-size: 32rpx;
  908. font-weight: 500;
  909. color: #FFFFFF;
  910. }
  911. }
  912. }
  913. </style>