index.vue 29 KB

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