index.vue 27 KB

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