index.vue 28 KB

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