index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. <template>
  2. <page-meta root-font-size="system" />
  3. <view class="process_container">
  4. <!-- 主表单 -->
  5. <uni-card spacing="0">
  6. <view class="main_container">
  7. <uni-forms ref="$mainForm" :modelValue="mainFormValue" :rules="$mainFormRules" label-position="left"
  8. :label-width="125" :border="true">
  9. <view v-for="(elem, index) in formElements" :key="index">
  10. <uni-forms-item v-if="'0' != elem.canEdit && '8' != elem.type" :name="elem.elementId"
  11. :label="elem.elementName">
  12. <!-- 关联变量输入框 -->
  13. <uni-easyinput v-if="undefined != elem.bindTimeRange" placeholder=""
  14. :value="calculateTimeDifference(elem, formElements)"></uni-easyinput>
  15. <!-- 金额转大写 -->
  16. <uni-easyinput v-else-if="elem.elementName.endsWith('大写')" placeholder=""
  17. :value="computedNumberToChineseCurrency(elem, formElements)"></uni-easyinput>
  18. <!-- 主表关联重复表输入框 -->
  19. <uni-easyinput v-else-if="elem.BddzText.startsWith('re:')" placeholder=""
  20. :value="computedValueToRepeatingForm(elem)"></uni-easyinput>
  21. <!-- 输入框 -->
  22. <uni-easyinput v-else-if="'0' == elem.type" :disabled="'0' == elem.canEdit"
  23. :type="fieldTypeDict[elem.fieldType] || 'text'"
  24. :placeholder="'0' == elem.canEdit ? '' : '请输入内容'"
  25. v-model="elem.defaultValue"></uni-easyinput>
  26. <!-- 富文本输入框 -->
  27. <uni-easyinput v-else-if="'1' == elem.type" :disabled="'0' == elem.canEdit"
  28. :placeholder="'0' == elem.canEdit ? '' : '请输入内容'" v-model="elem.defaultValue"
  29. type="textarea"></uni-easyinput>
  30. <!-- 下拉框 -->
  31. <picker class="picker_container" v-else-if="'2' == elem.type"
  32. @change="bindPickerChange($event, elem)" :value="elem.defaultValue"
  33. :range="formatDict(elem.typeDetail.enum)">
  34. <view class="uni-input input_text">
  35. <!-- 设置默认值为第一个选项 -->
  36. {{ elem.defaultValue ? elem.defaultValue : elem.defaultValue =
  37. elem.typeDetail.enum[0].enumVname }}
  38. </view>
  39. </picker>
  40. <!-- 开始时间选择器 -->
  41. <uni-datetime-picker :end="formElements[elem.endElemIndex].defaultValue"
  42. @change="setTimeRange(elem)"
  43. v-else-if="'9' == elem.type && undefined != elem.endElemIndex"
  44. v-model="elem.defaultValue" :clear-icon="false" type="datetime" />
  45. <!-- 结束时间选择器 -->
  46. <uni-datetime-picker :start="formElements[elem.startElemIndex].defaultValue"
  47. @change="setTimeRange(elem)"
  48. v-else-if="'9' == elem.type && undefined != elem.startElemIndex"
  49. v-model="elem.defaultValue" :clear-icon="false" type="datetime" />
  50. <!-- 其他时间选择器 -->
  51. <!-- 年月日 时分秒 -->
  52. <uni-datetime-picker v-else-if="'9' == elem.type" v-model="elem.defaultValue"
  53. type="datetime" />
  54. <!-- 年月日 -->
  55. <uni-datetime-picker v-else-if="'3' == elem.type" v-model="elem.defaultValue" type="date" />
  56. </uni-forms-item>
  57. </view>
  58. </uni-forms>
  59. </view>
  60. </uni-card>
  61. <!-- 重复表单 -->
  62. <view v-if="repeatingForm.elementItem.length > 0" class="repeating_table">
  63. <uni-card v-for="(form, tableIndex) in repeatingForm.elements" spacing="0" :key="tableIndex">
  64. <uni-forms :ref="(el) => $repeatingForms[tableIndex] = el" :modelValue="repeatingFormsValue[tableIndex]"
  65. :rules="$repFormRules" label-position="left" :label-width="125" :border="true">
  66. <uni-forms-item :name="elem.tableField" v-for="(elem, itemIndex) in form"
  67. :label="repeatingForm.elementItem[itemIndex].elementName.slice(3)" :key="itemIndex">
  68. <!-- 自定义关联变量 -->
  69. <uni-easyinput v-if="repeatingForm.elementItem[itemIndex].bddzText" :placeholder="''"
  70. :value="calculateRepeatingFormExpression(elem, form)"></uni-easyinput>
  71. <uni-easyinput v-else placeholder="请输入内容" v-model="elem.defaultValue"
  72. :type="fieldTypeDict[repeatingForm.elementItem[itemIndex].fieldType.value] || 'text'"></uni-easyinput>
  73. </uni-forms-item>
  74. </uni-forms>
  75. <view class="repeating_table_button_container">
  76. <uni-row>
  77. <uni-col :span="10" :offset="1">
  78. <button @click="addRepeatingFormItem(tableIndex)" type="primary">新增</button>
  79. </uni-col>
  80. <uni-col :span="10" :offset="2">
  81. <button @click="delRepeatingFormItem(tableIndex)"
  82. :disabled="repeatingForm.elements.length <= 1" type="warn">删除</button>
  83. </uni-col>
  84. </uni-row>
  85. </view>
  86. </uni-card>
  87. </view>
  88. <!-- 上传附件 -->
  89. <view class="file_picker_container">
  90. <uni-card title="上传附件" :extra="`${fileList.length}/15`" spacing="0">
  91. <uni-file-picker ref="filePicker" v-model="fileList" :auto-upload="true" mode="list" limit="10"
  92. file-mediatype="all" @select="handleFileSelect" @progress="handleFileProgress"
  93. @success="handleFileSuccess" @fail="handleFileFail" @delete="handleFileDelete" />
  94. </uni-card>
  95. </view>
  96. <view class="submit_button_container">
  97. <uni-card spacing="0" padding="0">
  98. <button :disabled="!button_state" :loading="!button_state" type="primary" class="submit_button"
  99. @click="submitProcess">提交</button>
  100. </uni-card>
  101. </view>
  102. </view>
  103. <view style="height: 5px; margin-top: 10px;"></view>
  104. </template>
  105. <script setup lang="ts">
  106. import { onMounted, reactive, ref, computed } from 'vue'
  107. import { onLoad } from '@dcloudio/uni-app'
  108. import { useUserStore } from '@/store/user.js'
  109. import $modal from '@/plugins/modal.js'
  110. import $tab from '@/plugins/tab.js'
  111. import { convertToChineseCurrency } from '@/utils/ygoa.js'
  112. import { calCommonExp } from '@/utils/rpn.js'
  113. import { getProcessInfo, getProcessForm, submitProcessForm, uploadFile, getAttendanceSegment } from '@/api/work.js'
  114. const fieldTypeDict = {
  115. '0': 'text',
  116. '1': 'number'
  117. }
  118. let processInfo = reactive({
  119. modelName: '流程申请',
  120. reqOffice: 0,
  121. control: '',
  122. formId: '',
  123. modelId: '',
  124. tmodelId: '',
  125. isMoreIns: '',
  126. pathJudgeType: '',
  127. form: []
  128. })
  129. const userStore = useUserStore()
  130. const title = ref('')
  131. onLoad((options) => {
  132. const { modelName, modelId, control } = options
  133. processInfo.modelName = modelName
  134. processInfo.modelId = modelId
  135. processInfo.control = control
  136. title.value = userStore.user.name + '的' + processInfo.modelName
  137. // 设置导航栏标题
  138. uni.setNavigationBarTitle({
  139. title: title.value
  140. });
  141. })
  142. onMounted(() => {
  143. initProcessInfo().then(() => {
  144. initProcessForm()
  145. })
  146. })
  147. function initProcessInfo() {
  148. return new Promise<void>((resolve, reject) => {
  149. getProcessInfo(processInfo)
  150. .then(({ returnParams }) => {
  151. const { formId, tmodelId, isMoreIns, pathJudgeType, reqOffice } = returnParams.flow[0]
  152. processInfo.formId = formId
  153. processInfo.tmodelId = tmodelId
  154. processInfo.isMoreIns = isMoreIns
  155. processInfo.pathJudgeType = pathJudgeType
  156. processInfo.reqOffice = reqOffice
  157. resolve()
  158. })
  159. })
  160. }
  161. const formElements = ref([])
  162. const repeatingForm = ref({
  163. elementItem: [],
  164. elements: []
  165. })
  166. function initProcessForm() {
  167. getProcessForm(userStore.user, processInfo).then(({ returnParams }) => {
  168. formElements.value = returnParams.formElements
  169. repeatingForm.value = returnParams.repeatingForm
  170. getValidateRules()
  171. // 生成第一个重复表数据
  172. if (repeatingForm.value) {
  173. addRepeatingFormItem(0)
  174. }
  175. bindTimeRangeData()
  176. })
  177. }
  178. const timeRangeItems = ref([
  179. ['开始时间', '结束时间', '多少小时'],
  180. ['出发时间', '预计返回时间'],
  181. ['出差时间', '返回时间'],
  182. ['出门时间', '预计返回时间'],
  183. ['开始时间', '结束时间', '合计小时'],
  184. ])
  185. // 关联时间变量
  186. function bindTimeRangeData() {
  187. return new Promise<void>((resolve) => {
  188. timeRangeItems.value.forEach((range) => {
  189. const [startName, endName, bindName] = range;
  190. // 找到 startName 和 endName 的索引
  191. const startIndex = formElements.value.findIndex((item) => item.elementName === startName);
  192. const endIndex = formElements.value.findIndex((item) => item.elementName === endName);
  193. // 只有找到 startName 和 endName 后,才检查 bindName
  194. if (startIndex !== -1 && endIndex !== -1) {
  195. formElements.value[startIndex].defaultValue = new Date(Date.now()).toLocaleString('zh-CN', {
  196. year: 'numeric',
  197. month: '2-digit',
  198. day: '2-digit',
  199. hour: '2-digit',
  200. minute: '2-digit',
  201. second: '2-digit',
  202. hour12: false
  203. }).replace(/\//g, '-').replace(/, /g, ' ');
  204. if (bindName) {
  205. const bindIndex = formElements.value.findIndex((item) => item.elementName === bindName);
  206. if (bindIndex !== -1) {
  207. // 所有匹配项都存在,保存索引
  208. formElements.value[startIndex].endElemIndex = endIndex;
  209. formElements.value[endIndex].startElemIndex = startIndex;
  210. formElements.value[bindIndex].bindTimeRange = {
  211. startIndex,
  212. endIndex
  213. };
  214. setAttendanceSegment() // 获取班次信息
  215. }
  216. } else {
  217. // 没有 bindName,仅保存 start 和 end 的索引
  218. formElements.value[startIndex].endElemIndex = endIndex;
  219. formElements.value[endIndex].startElemIndex = startIndex;
  220. }
  221. // TODO 加班开始时间默认当天下班时间 请假时间默认当天上班时间
  222. }
  223. });
  224. resolve(); // 返回一个 resolved 状态的 Promise
  225. });
  226. }
  227. // 计算时间差
  228. function calculateTimeDifference(item, form) {
  229. // 获取 开始时间 和 结束时间
  230. const { startIndex, endIndex } = item.bindTimeRange;
  231. const startTime = new Date(form[startIndex].defaultValue);
  232. const endTime = new Date(form[endIndex].defaultValue);
  233. // 检查时间是否合法
  234. if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
  235. return item.defaultValue = 0
  236. }
  237. // 改为配置选择
  238. const type = item.elementName == '多少小时'?'请假':'加班'
  239. // 计算时间差
  240. const timeDifferenceInHours = calculateWorkingHours(startTime, endTime, type);
  241. // const timeDifferenceInMs = endTime - startTime;
  242. // let timeDifferenceInHours = (timeDifferenceInMs / (1000 * 60 * 60)).toFixed(1);
  243. // let timeDifferenceInHours = (timeDifferenceInMs / (1000 * 60 * 60))
  244. // // 计算小数部分
  245. // const decimalPart = timeDifferenceInHours - Math.floor(timeDifferenceInHours)
  246. // if (decimalPart >= 0.5) {
  247. // // 如果小数部分大于等于 0.5,向上取整到 0.5 的倍数
  248. // timeDifferenceInHours = Math.floor(timeDifferenceInHours) + 0.5;
  249. // } else {
  250. // // 如果小数部分小于 0.5,向下取整到 0.5 的倍数
  251. // timeDifferenceInHours = Math.floor(timeDifferenceInHours);
  252. // }
  253. // 保存到 defaultValue
  254. return item.defaultValue = timeDifferenceInHours;
  255. }
  256. let workingPeriods = []
  257. let workDays = [] // 0为周日
  258. function setAttendanceSegment() {
  259. getAttendanceSegment(userStore.user.unitId).then(({returnParams}) => {
  260. const workTime = returnParams[0].work_time.split(';')
  261. for (const time of workTime) {
  262. if (time == '') continue
  263. const times = time.split(',')
  264. const obj = {
  265. start: times[0],
  266. end: times[1]
  267. }
  268. workingPeriods.push(obj)
  269. }
  270. if (workingPeriods.length == 0) {
  271. workingPeriods = [
  272. { start: "09:00", end: "12:00" }, // 上午
  273. { start: "13:30", end: "17:30" }, // 下午
  274. ]
  275. }
  276. if (workDays.length == 0) {
  277. workDays = [1,2,3,4,5,6] // 0为周日
  278. }
  279. })
  280. }
  281. function calculateWorkingHours(startTime, endTime, type) {
  282. // 将时间字符串解析为分钟数
  283. const formatTime = (timeString) => {
  284. const [hours, minutes] = timeString.split(":").map(Number)
  285. return hours * 60 + minutes
  286. };
  287. // 计算两个时间段的重叠部分
  288. const calculateOverlap = (start1, end1, start2, end2) => {
  289. const overlapStart = Math.max(start1, start2)
  290. const overlapEnd = Math.min(end1, end2)
  291. return Math.max(0, overlapEnd - overlapStart) // 如果无重叠返回 0
  292. };
  293. // 将 Date 转化为当天的分钟数
  294. const dateToMinutes = (date) => date.getHours() * 60 + date.getMinutes()
  295. // 判断日期是否是工作日
  296. const isRestdays = (date) => !workDays.includes(date.getDay())
  297. // 确保 startTime 和 endTime 是 Date 类型
  298. if (!(startTime instanceof Date) || !(endTime instanceof Date)) {
  299. throw new Error("startTime 和 endTime 必须是 Date 类型")
  300. }
  301. // 将时间范围分解为每天的计算
  302. const startDate = new Date(startTime)
  303. const endDate = new Date(endTime)
  304. startDate.setHours(0, 0, 0, 0)
  305. endDate.setHours(0, 0, 0, 0)
  306. let totalMinutes = 0
  307. // 遍历时间范围的每一天
  308. for (
  309. let currentDate = new Date(startDate);
  310. currentDate <= endDate;
  311. currentDate.setDate(currentDate.getDate() + 1)
  312. ) {
  313. // 如果当前日期不是工作日,跳过
  314. if (isRestdays(currentDate)) continue;
  315. const isStartDay = currentDate.getTime() === startDate.getTime();
  316. const isEndDay = currentDate.getTime() === endDate.getTime();
  317. // 当天的起始和结束时间
  318. const dayStart = isStartDay ? dateToMinutes(startTime) : 0;
  319. const dayEnd = isEndDay ? dateToMinutes(endTime) : 1440; // 1440 = 24 * 60
  320. // TODO 改为配置选择
  321. if (type === "请假") {
  322. // 计算工作时间段内的时间
  323. workingPeriods.forEach((period) => {
  324. const periodStart = formatTime(period.start);
  325. const periodEnd = formatTime(period.end);
  326. totalMinutes += calculateOverlap(dayStart, dayEnd, periodStart, periodEnd);
  327. });
  328. } else if (type === "加班") {
  329. // 计算非工作时间段的时间
  330. let nonWorkingMinutes = 0;
  331. let current = dayStart;
  332. for (const period of workingPeriods) {
  333. const periodStart = formatTime(period.start);
  334. const periodEnd = formatTime(period.end);
  335. if (current < periodStart) {
  336. nonWorkingMinutes += calculateOverlap(current, dayEnd, current, periodStart);
  337. }
  338. current = Math.max(current, periodEnd);
  339. }
  340. if (current < dayEnd) {
  341. nonWorkingMinutes += dayEnd - current;
  342. }
  343. totalMinutes += nonWorkingMinutes;
  344. }
  345. }
  346. // 转换为小时
  347. return totalMinutes / 60;
  348. }
  349. function setTimeRange(e) {
  350. // console.log('setTimeRange', e)
  351. }
  352. // 生成人民币大写
  353. function computedNumberToChineseCurrency(item, form) {
  354. const elem = form.find(elem => elem.elementName == item.BddzText.slice(3))
  355. return item.defaultValue = convertToChineseCurrency(elem.defaultValue)
  356. }
  357. function computedValueToRepeatingForm(item) {
  358. const index = repeatingForm.value.elementItem.findIndex(({elementName}) => elementName.slice(3) == item.BddzText.slice(3))
  359. let result = 0
  360. for(const formItem of repeatingForm.value.elements) {
  361. result += Number(formItem[index].defaultValue) || 0
  362. }
  363. return item.defaultValue = result
  364. }
  365. // 下拉框
  366. function bindPickerChange(e, item) {
  367. const index = e.detail.value;
  368. item.defaultValue = item.typeDetail.enum[index].enumVname
  369. }
  370. function formatDict(dict) {
  371. return dict.map(({ enumVname }) => enumVname)
  372. }
  373. // 新增重复表表单
  374. function addRepeatingFormItem(index) {
  375. const form = repeatingForm.value.elementItem.map(({ tableField, bddzText }) => {
  376. const item = {
  377. name: tableField,
  378. defaultValue: '',
  379. bddzText
  380. }
  381. return item
  382. })
  383. repeatingForm.value.elements.splice(index + 1, 0, form)
  384. }
  385. // 删除重复表表单
  386. function delRepeatingFormItem(index) {
  387. $modal.confirm('', '确认删除该重复表数据')
  388. .then(() => {
  389. repeatingForm.value.elements.splice(index, 1)
  390. $repeatingForms.value.splice(index, 1)
  391. })
  392. .catch(() => { })
  393. }
  394. // 计算重复表关联变量表达式结果值
  395. function calculateRepeatingFormExpression(item, form) {
  396. // 提取表达式中的变量名
  397. const variablePattern = /my:[\u4e00-\u9fa5]+/g;
  398. let match;
  399. let expression = item.bddzText
  400. // 替换表达式中的变量
  401. while ((match = variablePattern.exec(item.bddzText)) !== null) {
  402. const variableName = match[0]; // 完整变量名
  403. // 找到 重复表 中对应的索引
  404. const index = repeatingForm.value.elementItem.findIndex(item => item.elementName === variableName);
  405. if (index !== -1) {
  406. const value = form[index]?.defaultValue || 0;
  407. expression = expression.replace(match[0], value);
  408. } else {
  409. // 未匹配到的变量替换为 0
  410. expression = expression.replace(match[0], 0);
  411. }
  412. }
  413. if (/[^0-9\+\-\*\/\(\)\.]/.test(expression)) {
  414. console.error('错误的表达式:', expression);
  415. return expression;
  416. }
  417. return item.defaultValue = calCommonExp(expression); // 返回填充后的表达式
  418. }
  419. const fileList = ref([]) // 文件列表
  420. const fileSeqs = ref([])
  421. async function handleFileSelect(files) { // 新增文件
  422. // console.log('handleFileSelect', files.tempFiles)
  423. files.tempFiles.forEach(file => {
  424. const data = {
  425. name: file.name,
  426. filePath: file.path,
  427. }
  428. uploadFile(data)
  429. .then(res => {
  430. file.seq = res.returnParams
  431. fileSeqs.value.push({ 'seq': res.returnParams, 'path': file.path })
  432. fileList.value.push(file)
  433. // console.log('handleFileSelect fileList: ',fileList);
  434. // file.seq = res.returnParams
  435. })
  436. .catch(err => {
  437. $modal.msgError('文件' + data.name + '上传失败,请删除重新上传')
  438. switch (err) {
  439. case -201:
  440. console.log('文件上传失败 未找到该文件');
  441. break;
  442. case -20201:
  443. console.log('文件上传失败 返回值不是JSON字符串');
  444. break;
  445. }
  446. })
  447. })
  448. // console.log('UploadFiles', files.tempFiles);
  449. }
  450. function handleFileProgress(file, progress) {
  451. // console.log('handleFileProgress', file, progress);
  452. }
  453. function handleFileSuccess(file, res) {
  454. // console.log('handleFileSuccess', file, res);
  455. }
  456. function handleFileFail(file, err) {
  457. // console.log('handleFileFail', file, err);
  458. }
  459. function handleFileDelete(file) { // 移除文件
  460. // console.log('handleDelete', file)
  461. fileSeqs.value.splice(fileSeqs.value.findIndex(({ path }) => path == file.tempFilePath))
  462. }
  463. //表单校验
  464. const $mainForm = ref(null)
  465. const mainFormValue = ref({})
  466. const $repeatingForms = ref([])
  467. const repeatingFormsValue = ref([])
  468. const formatTypeDict = {
  469. '0': 'string',
  470. '1': 'number'
  471. }
  472. // 校验重复表
  473. function validateRepeatingForm() {
  474. // 设置重复表校验数据
  475. repeatingFormsValue.value = repeatingForm.value.elements.map((item, index) => {
  476. return computed(() => {
  477. const obj = {};
  478. item.forEach(({ name, defaultValue }) => {
  479. obj[name] = defaultValue;
  480. });
  481. return obj;
  482. }).value
  483. })
  484. // console.log('$repeatingForms: ',$repeatingForms.value);
  485. // console.log('repeatingFormsValue: ',repeatingFormsValue.value);
  486. // 重复表数据校验
  487. // BUG validate()校验无效
  488. $repeatingForms.value.forEach((form) => {
  489. // console.log('form: ',form);
  490. form.validate().then(res => {
  491. console.log('验证成功:', res);
  492. }).catch(err => {
  493. console.log('验证失败:', err);
  494. });
  495. })
  496. }
  497. function validateRepeatingForm2() {
  498. // 设置重复表校验数据
  499. repeatingFormsValue.value = repeatingForm.value.elements.map((item, index) => {
  500. return computed(() => {
  501. const obj = {};
  502. item.forEach(({ name, defaultValue }) => {
  503. obj[name] = defaultValue;
  504. });
  505. return obj;
  506. }).value
  507. })
  508. let flag = false
  509. repeatingFormsValue.value.forEach((item, index) => {
  510. for (const elem in item) {
  511. if (item[elem] == '') {
  512. $modal.msgError(`重复表 ${index + 1} 数据填写错误`)
  513. flag = false
  514. return
  515. }
  516. }
  517. flag = true
  518. })
  519. return flag
  520. }
  521. // 设置表单校验规则
  522. const $mainFormRules = ref({})
  523. const $repFormRules = ref({})
  524. function getValidateRules() {
  525. $repFormRules.value = computed(() => {
  526. const obj = {};
  527. repeatingForm.value.elementItem.forEach(elem => {
  528. obj[elem.tableField] = {
  529. rules: [
  530. {
  531. required: true,
  532. },
  533. // {
  534. // 类型判断 数值型使用string会出错
  535. // formatTypeDict: formatTypeDict[elem.fieldType.value] || 'string',
  536. // }
  537. ],
  538. label: elem.elementName.slice(3)
  539. };
  540. });
  541. return obj;
  542. }).value
  543. $mainFormRules.value = computed(() => {
  544. const obj = {};
  545. formElements.value.forEach(elem => {
  546. if (!('0' != elem.canEdit && '8' != elem.type)) return
  547. obj[elem.elementId] = {
  548. rules: [
  549. {
  550. required: '1' == elem.noNull,
  551. },
  552. // {
  553. // format: formatTypeDict[elem.fieldType] || 'string',
  554. // }
  555. ],
  556. label: elem.elementName
  557. };
  558. });
  559. return obj;
  560. }).value
  561. }
  562. const button_state = ref(true)
  563. function submitProcess() { // 提交表单
  564. // 禁用提交按钮
  565. button_state.value = false
  566. // validateMainForm()
  567. // 设置主表校验数据
  568. mainFormValue.value = computed(() => {
  569. const obj = {};
  570. formElements.value.forEach(elem => {
  571. if (!('0' != elem.canEdit && '8' != elem.type)) return
  572. obj[elem.elementId] = elem.defaultValue;
  573. });
  574. return obj;
  575. }).value
  576. // 主表数据校验
  577. $mainForm.value.validate().then(res => {
  578. // 重复表数据校验
  579. // validateRepeatingForm()
  580. if (!validateRepeatingForm2()) {
  581. button_state.value = true
  582. return
  583. }
  584. // 保存表单数据
  585. processInfo.form = formElements.value.map(({ tableField, defaultValue }) => {
  586. return { name: tableField, value: defaultValue }
  587. })
  588. repeatingForm.value.elements.forEach((item, index) => {
  589. const newItem = item.map(({ name, defaultValue }) => {
  590. return {
  591. name: name + '_' + (index + 1),
  592. value: defaultValue
  593. }
  594. })
  595. processInfo.form.push(...newItem)
  596. })
  597. // 保存附件ID
  598. const seqs = fileSeqs.value.map(({ seq }) => seq)
  599. if (processInfo.reqOffice == 1 && seqs.length == 0) {
  600. button_state.value = true
  601. $modal.msgError('请上传附件')
  602. return
  603. }
  604. submitProcessForm(userStore.user, processInfo, seqs).then(res => {
  605. if (res.returnMsg.includes('提交失败')) {
  606. // 启用提交按钮
  607. button_state.value = true
  608. $modal.msgError(res.returnMsg)
  609. } else {
  610. $modal.msgSuccess(res.returnMsg)
  611. // 通知列表刷新数据
  612. uni.$emit('ReloadProcessData');
  613. setTimeout(() => {
  614. // 返回上一页
  615. $tab.navigateBack();
  616. }, 1000)
  617. }
  618. })
  619. }).catch(err => {
  620. button_state.value = true
  621. $modal.msgError('表单填写错误')
  622. });
  623. }
  624. </script>
  625. <style lang="scss" scoped>
  626. .process_container {
  627. position: relative;
  628. ::v-deep .uni-forms {
  629. .uni-forms-item__label {
  630. font-size: calc(1rem + 0px) !important;
  631. line-height: calc(1rem + 0px) !important;
  632. font-weight: 600;
  633. }
  634. .uni-forms-item__content {
  635. .uni-easyinput__content-textarea {
  636. font-size: calc(14px + .5*(1rem - 16px)) !important;
  637. }
  638. .uni-easyinput__content-input {
  639. font-size: calc(14px + .5*(1rem - 16px)) !important;
  640. color: #333;
  641. }
  642. .uni-date {
  643. .uni-icons {
  644. font-size: calc(22px + .5*(1rem - 16px)) !important;
  645. }
  646. .uni-date__x-input {
  647. font-size: calc(14px + .5*(1rem - 16px)) !important;
  648. }
  649. }
  650. }
  651. }
  652. ::v-deep button {
  653. font-size: calc(18px + .5*(1rem - 16px));
  654. }
  655. .main_container {
  656. min-height: 30vh;
  657. }
  658. .picker_container {
  659. border: 1px solid #e5e5e5;
  660. background-color: #fff;
  661. border-radius: 4px;
  662. // box-sizing: border-box;
  663. .input_text {
  664. color: #333;
  665. font-size: calc(14px + .5*(1rem - 16px));
  666. line-height: 35px;
  667. height: 35px;
  668. padding: 0 0 0 10px;
  669. }
  670. }
  671. .repeating_table {
  672. ::v-deep .uni-forms-item--border {
  673. padding: 5px 0 !important;
  674. }
  675. .repeating_table_button_container {
  676. margin-top: 5px;
  677. button {
  678. height: 36px;
  679. line-height: 36px;
  680. color: #fff;
  681. }
  682. .add {
  683. background-color: #1ca035;
  684. }
  685. .del {
  686. background-color: #e64340;
  687. }
  688. }
  689. }
  690. .file_picker_container {
  691. ::v-deep .uni-card {
  692. .uni-card__header-content-title {
  693. font-size: calc(15px + .5*(1rem - 16px));
  694. }
  695. .uni-card__header-extra-text {
  696. font-size: calc(15px + .5*(1rem - 16px));
  697. }
  698. }
  699. }
  700. .is-disabled {
  701. color: #666 !important;
  702. }
  703. .submit_button_container {
  704. position: sticky;
  705. z-index: 10;
  706. width: 100%;
  707. bottom: 10px;
  708. .submit_button {
  709. background-color: #007aff !important;
  710. color: #fff !important;
  711. }
  712. .submit_button::after {
  713. border: 1px solid rgba(0, 0, 0, .2);
  714. border-radius: 10px;
  715. box-sizing: border-box;
  716. content: " ";
  717. height: 200%;
  718. left: 0;
  719. position: absolute;
  720. top: 0;
  721. -webkit-transform: scale(.5);
  722. transform: scale(.5);
  723. -webkit-transform-origin: 0 0;
  724. transform-origin: 0 0;
  725. width: 200%;
  726. border-top-width: 1px;
  727. border-right-width: 1px;
  728. border-bottom-width: 1px;
  729. border-left-width: 1px;
  730. border-top-style: solid;
  731. border-right-style: solid;
  732. border-bottom-style: solid;
  733. border-left-style: solid;
  734. border-top-color: rgba(0, 0, 0, 0.2);
  735. border-right-color: rgba(0, 0, 0, 0.2);
  736. border-bottom-color: rgba(0, 0, 0, 0.2);
  737. border-left-color: rgba(0, 0, 0, 0.2);
  738. border-image-source: initial;
  739. border-image-slice: initial;
  740. border-image-width: initial;
  741. border-image-outset: initial;
  742. border-image-repeat: initial;
  743. border-top-left-radius: 10px;
  744. border-top-right-radius: 10px;
  745. border-bottom-right-radius: 10px;
  746. border-bottom-left-radius: 10px;
  747. }
  748. }
  749. }
  750. </style>