index.vue 31 KB

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