選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

DialogCollapse.operation.vue 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <template>
  2. <!-- div 包裹突出 -->
  3. <el-collapse v-model="activeNames" class="bg-content">
  4. <template v-for="(dialogArray, i) in editCollapseArray">
  5. <el-collapse-item
  6. :name="i + 1 + ''"
  7. v-if="!dialogArray.hide"
  8. :disabled="dialogArray.disabled"
  9. >
  10. <template #title>
  11. <div class="t-title">
  12. {{ dialogArray.label }}
  13. </div>
  14. </template>
  15. <div class="bg-dialog">
  16. <div
  17. v-for="(item, index) in dialogArray.children"
  18. :style="`width: ${item.form?.width ? item.form.width : '100%'}`"
  19. :key="index"
  20. >
  21. <div
  22. v-if="item.form?.type === 'title' && !item.form?.slotSetEdit"
  23. style="
  24. padding: 20px 0;
  25. margin-left: 8%;
  26. display: flex;
  27. flex-direction: row;
  28. "
  29. class="as-gravity-center"
  30. >
  31. <div style="background-color: #999; height: 1px; flex: 1"></div>
  32. <el-text class="as-bold" size="large" style="padding: 0 20px">
  33. {{ item.label }}
  34. </el-text>
  35. <div style="background-color: #999; height: 1px; flex: 1"></div>
  36. </div>
  37. <!-- 此处的width决定了是否要换行 v-show="!item.form.hide"-->
  38. {{ form[item.form.prop ? item.form.prop : item.prop] }}
  39. <el-form-item
  40. style="position: relative"
  41. v-if="
  42. !item.form?.hideEdit &&
  43. !item.form?.hide &&
  44. item.form?.type != 'title'
  45. "
  46. class="as-bold"
  47. :label="item.label"
  48. :prop="item.prop"
  49. :label-width="
  50. item.form?.formLabelWidth
  51. ? item.form.formLabelWidth
  52. : formLabelWidth
  53. "
  54. :error="item.form?.error"
  55. :rules="rules[item.prop]"
  56. >
  57. <!-- 输入框 -->
  58. <div style="width: 100%" v-if="item.form.type === 'input'">
  59. <!-- :readonly="true" 只读 -->
  60. <el-input
  61. v-trim
  62. clearable
  63. :disabled="item.form.disabled"
  64. v-model="form[item.prop]"
  65. :type="item.form.itemType"
  66. :min="item.form.min"
  67. :max="item.form.max"
  68. :placeholder="item.form.placeholder"
  69. :rows="item.form.rows"
  70. @change="
  71. customSelectorSelection(
  72. $event,
  73. item.form.prop ? item.form.prop : item.prop,
  74. form,
  75. item,
  76. dialogArray.children
  77. )
  78. "
  79. :maxlength="item.form.maxlength ?? 60"
  80. @input="
  81. handleBeforeInput($event, item.form.checkContent, item)
  82. "
  83. >
  84. <!-- 输入框单位 -->
  85. <template v-if="item.unit" #append>
  86. <el-button
  87. @click="unitClick($event, item, form[item.prop])"
  88. >
  89. {{ item.unit }}
  90. </el-button>
  91. </template>
  92. </el-input>
  93. </div>
  94. <!-- 区间输入框 -->
  95. <div style="width: 100%" v-if="item.form.type === 'section'">
  96. <div class="num-input">
  97. <el-input
  98. v-trim
  99. clearable
  100. :disabled="item.form.disabled"
  101. v-model="form[item.props].split(',')[0]"
  102. :type="item.form.itemType"
  103. :placeholder="item.form.placeholder"
  104. :rows="item.form.rows"
  105. :maxlength="item.form.maxlength ?? 60"
  106. >
  107. <!-- 输入框单位 -->
  108. <template v-if="item.unit" #append>
  109. {{ item.unit }}
  110. </template>
  111. </el-input>
  112. <span>-</span>
  113. <el-input
  114. v-trim
  115. clearable
  116. :disabled="item.form.disabled"
  117. v-model="form[item.props].split(',')[1]"
  118. :type="item.form.itemType"
  119. :placeholder="item.form.placeholder"
  120. :rows="item.form.rows"
  121. :maxlength="item.form.maxlength ?? 60"
  122. >
  123. <!-- 输入框单位 -->
  124. <template v-if="item.unit" #append>
  125. {{ item.unit }}
  126. </template>
  127. </el-input>
  128. </div>
  129. </div>
  130. <!-- 级联选择 -->
  131. <div style="width: 100%" v-if="item.form.type === 'cascader'">
  132. <el-cascader
  133. style="width: 100%"
  134. clearable
  135. v-model="form[item.prop]"
  136. :disabled="item.form.disabled"
  137. :placeholder="item.form.placeholder"
  138. :options="item.form.options"
  139. :props="{ multiple: item.form.multiple }"
  140. :collapse-tags="item.form.collapseTags"
  141. :collapse-tags-tooltip="item.form.collapseTags"
  142. />
  143. </div>
  144. <!-- 单选框 -->
  145. <div style="width: 100%" v-if="item.form.type === 'radio'">
  146. <el-radio-group v-model="form[item.prop]" class="ml-4">
  147. <el-radio
  148. v-for="(itemData, index) in item.form.listData"
  149. :key="index"
  150. :label="itemData.value"
  151. >
  152. {{ itemData.label }}
  153. </el-radio>
  154. </el-radio-group>
  155. </div>
  156. <!-- 选择框 -->
  157. <div style="width: 100%" v-else-if="item.form.type === 'select'">
  158. <!-- {{ form[item.form.prop ? item.form.prop : item.prop] }} -->
  159. <el-select
  160. filterable
  161. clearable
  162. style="width: 100%"
  163. value-key="lable"
  164. v-model="form[item.form.prop ? item.form.prop : item.prop]"
  165. @change="
  166. customSelectorSelection(
  167. $event,
  168. item.form.prop ? item.form.prop : item.prop,
  169. form,
  170. item,
  171. dialogArray.children
  172. )
  173. "
  174. :disabled="item.form.disabled"
  175. :placeholder="item.form.placeholder"
  176. :multiple="item.form.multiple"
  177. >
  178. <el-option
  179. v-for="(itemData, index) in item.form.listData"
  180. :key="index"
  181. :label="itemData.label"
  182. :value="itemData.value"
  183. />
  184. </el-select>
  185. </div>
  186. <!-- 异步选择框 -->
  187. <div
  188. style="width: 100%"
  189. v-else-if="item.form.type === 'lazySelect'"
  190. >
  191. <lazy-select
  192. @lazySelectorSelection="lazySelectorSelection"
  193. :item="item"
  194. :form="form"
  195. :disabled="item.form.disabled"
  196. :prop="item.form.prop ? item.form.prop : item.prop"
  197. :api="item.form.api"
  198. :returnSuffixName="item.form.returnSuffixName"
  199. :parameter="item.form.parameter"
  200. :name="item.form.name"
  201. :id="item.form.id"
  202. :multiple="item.form.multiple"
  203. ></lazy-select>
  204. </div>
  205. <!-- 日期框 -->
  206. <div style="width: 100%" v-else-if="item.form.type === 'date'">
  207. <el-date-picker
  208. unlink-panels
  209. @change="
  210. customSelectorSelection(
  211. $event,
  212. item.form.prop ? item.form.prop : item.prop,
  213. form,
  214. item,
  215. dialogArray.children
  216. )
  217. "
  218. style="width: 100%"
  219. v-model="form[item.prop]"
  220. :disabled="item.form.disabled"
  221. :format="form.format"
  222. :value-format="item.form.valueFormat || form.valueFormat"
  223. :type="item.form.itemType"
  224. :placeholder="item.form.placeholder"
  225. />
  226. </div>
  227. <!-- 图片文件 -->
  228. <div
  229. style="width: 100%"
  230. v-else-if="item.form.type === 'uploadImg'"
  231. >
  232. <el-upload
  233. ref="uploadImg"
  234. class="avatar-uploader"
  235. :style="
  236. (item.form.width
  237. ? 'width: ' + item.form.width + 'px;'
  238. : '') +
  239. (item.form.height
  240. ? 'height:' + item.form.height + 'px'
  241. : '')
  242. "
  243. :limit="1"
  244. :on-exceed="handleImg"
  245. :data="data"
  246. accept=".png,.jpg,jpeg"
  247. :before-upload="beforeAvatarImgUpload"
  248. :show-file-list="false"
  249. :action="uploadUrl"
  250. @success="onSuccess($event, item)"
  251. >
  252. <img
  253. v-if="form[item.prop]"
  254. :src="changeAddress(form[item.prop])"
  255. class="avatar"
  256. :style="
  257. (item.form.width
  258. ? 'width: ' + item.form.width + 'px;'
  259. : '') +
  260. (item.form.height
  261. ? 'height:' + item.form.height + 'px'
  262. : '')
  263. "
  264. />
  265. <el-icon
  266. v-else
  267. class="avatar-uploader-icon"
  268. :style="
  269. (item.form.width
  270. ? 'width: ' + item.form.width + 'px;'
  271. : '') +
  272. (item.form.height
  273. ? 'height:' + item.form.height + 'px'
  274. : '')
  275. "
  276. >
  277. <Plus />
  278. </el-icon>
  279. <template #tip>
  280. <div class="el-upload__tip">
  281. {{ item.form.placeholder }}
  282. </div>
  283. </template>
  284. </el-upload>
  285. </div>
  286. <!-- 文件上传 drag(是否拖拽上传) multiple(是否支持多文件) action(请求URL) auto-upload(是否自动上传文件)-->
  287. <div style="width: 100%" v-else-if="item.form.type === 'upload'">
  288. <el-upload
  289. ref="upload"
  290. class="upload-demo"
  291. :file-list="fileList"
  292. drag
  293. :limit="item.form.limit || 1"
  294. :on-exceed="(files) => handleExceed(files, item.form.limit)"
  295. :before-upload="
  296. (el) =>
  297. beforeAvatarUpload(
  298. el,
  299. item.form.suffixType,
  300. item.form.typeHint
  301. )
  302. "
  303. :data="data"
  304. :action="uploadUrl"
  305. :on-remove="(el) => onRemove(el, item)"
  306. @success="onSuccess($event, item)"
  307. :accept="item.form.accept"
  308. >
  309. <el-icon class="el-icon--upload">
  310. <UploadFilled />
  311. </el-icon>
  312. <!-- <el-icon class="el-icon--upload"><el-image :src="Android" /></el-icon> -->
  313. <div class="el-upload__text">
  314. 在此提交文件或
  315. <em>点击上传</em>
  316. </div>
  317. <template #tip>
  318. <div class="el-upload__tip">
  319. <!-- 提示内容 -->
  320. {{ item.form.placeholder }}
  321. </div>
  322. </template>
  323. </el-upload>
  324. </div>
  325. <!-- 提示 -->
  326. <el-popover
  327. v-if="item.form.isPopover"
  328. placement="top-start"
  329. title="提示"
  330. :width="200"
  331. trigger="hover"
  332. :content="item.form.popoverContent"
  333. >
  334. <template #reference>
  335. <QuestionFilled
  336. style="
  337. width: 20px;
  338. height: 20px;
  339. color: #dc362e;
  340. position: absolute;
  341. right: -30px;
  342. z-index: 2;
  343. "
  344. />
  345. </template>
  346. </el-popover>
  347. <!-- 自定义插槽 -->
  348. <div
  349. v-else-if="!item.form.type && item.form.slotSetEdit"
  350. style="width: 100%"
  351. >
  352. <slot :name="item.form.slotSetNameEdit" />
  353. </div>
  354. </el-form-item>
  355. </div>
  356. </div>
  357. </el-collapse-item>
  358. </template>
  359. </el-collapse>
  360. </template>
  361. <script setup lang="ts">
  362. import { onMounted, ref, watch, nextTick } from 'vue'
  363. import { genFileId, ElMessage } from 'element-plus'
  364. import type { UploadInstance, UploadProps, UploadRawFile } from 'element-plus'
  365. import Android from '@/assets/image/android.png' //默认头像资源
  366. // @ts-ignore lazySelect模板
  367. import lazySelect from '@/components/lazySelect/lazySelect.vue'
  368. import { changeAddress } from '@/utils/utils'
  369. import { rule } from 'postcss'
  370. const props = defineProps({
  371. form: {
  372. //表单数据内容
  373. type: Object,
  374. default: function () {
  375. return {}
  376. },
  377. required: true,
  378. },
  379. editCollapseArray: {
  380. //表单展示内容数据 tableFrom.dialogArray
  381. type: Array as () => any[],
  382. default: () => [],
  383. required: true,
  384. },
  385. formLabelWidth: {
  386. //表单宽度
  387. type: String,
  388. default: '80px',
  389. },
  390. dialogFormVisible: {
  391. type: Boolean,
  392. },
  393. rules: {
  394. type: Object,
  395. default: function () {
  396. return {}
  397. },
  398. },
  399. defaultActiveNames: {
  400. type: Array as () => any[],
  401. default: function () {
  402. return []
  403. },
  404. },
  405. })
  406. const activeNames = ref(props.defaultActiveNames)
  407. const uploadUrl = '/minIo/upload'
  408. const data = { bucket: '' }
  409. const upload = ref<UploadInstance>()
  410. const uploadImg = ref<UploadInstance>()
  411. const emit = defineEmits([
  412. 'customSelectorSelection',
  413. 'handleBeforeInput',
  414. 'lazySelectorSelection',
  415. 'unitClick',
  416. 'uploadSuccess',
  417. ])
  418. //选择组件变化监听
  419. function customSelectorSelection(value, title, form, item, dialogArray) {
  420. console.log('选择内容', value, title)
  421. emit('customSelectorSelection', value, title, form, item, dialogArray)
  422. }
  423. //文件上传成功
  424. const fileList = ref<any>([])
  425. function onSuccess(response: any, item: any) {
  426. // import.meta.env.VITE_APP_UPLOAD_URL +
  427. const url = response.data.ossFilePath
  428. fileList.value.push(url)
  429. props.form[item.prop] = fileList.value.join(';')
  430. console.log(props.form[item.prop], fileList.value)
  431. emit('uploadSuccess', response, item)
  432. }
  433. function onRemove(response: any, item: any) {
  434. const index = fileList.value.indexOf(
  435. response.response.data.ossFilePath
  436. )
  437. if (index !== -1) {
  438. fileList.value.splice(index, 1)
  439. }
  440. console.log(index)
  441. props.form[item.prop] = fileList.value.join(';')
  442. console.log(fileList.value, response, props.form[item.prop])
  443. }
  444. //单位点击事件
  445. function unitClick($event, item, value) {
  446. emit('unitClick', $event, item, value)
  447. }
  448. //覆盖替换上一个文件
  449. const handleImg: UploadProps['onExceed'] = (files) => {
  450. uploadImg.value![0].clearFiles()
  451. const file = files[0] as UploadRawFile
  452. file.uid = genFileId()
  453. uploadImg.value![0].handleStart(file)
  454. uploadImg.value![0].submit()
  455. }
  456. //覆盖替换上一个文件
  457. const handleExceed: UploadProps['onExceed'] = (files, limit) => {
  458. console.log(files, limit)
  459. if (limit) {
  460. return ElMessage.error(`限制最多上传${limit}个文件`)
  461. } else {
  462. fileList.value.splice(0, 1)
  463. upload.value![0].clearFiles()
  464. const file = files[0] as UploadRawFile
  465. file.uid = genFileId()
  466. upload.value![0].handleStart(file)
  467. nextTick(() => {
  468. upload.value![0].submit()
  469. })
  470. }
  471. }
  472. const beforeAvatarImgUpload: UploadProps['beforeUpload'] = (rawFile) => {
  473. if (rawFile.size / 1024 / 1024 > 5) {
  474. ElMessage.error('文件大小不能超过 5MB!')
  475. return false
  476. }
  477. return true
  478. }
  479. // 清除文件
  480. watch(
  481. () => props.dialogFormVisible,
  482. (newValue) => {
  483. if (newValue) {
  484. fileList.value = []
  485. if (upload.value && upload.value![0]) upload.value![0].clearFiles()
  486. }
  487. }
  488. )
  489. watch(
  490. () => props.form,
  491. (newValue) => {
  492. if (newValue) {
  493. console.log(newValue, 'newValue')
  494. }
  495. },
  496. {
  497. deep: true,
  498. }
  499. )
  500. //限制上传文件 application/vnd.android.package-archive(应用) image/jpeg(图片文件)
  501. const beforeAvatarUpload: any = (rawFile, suffixType, typeHint) => {
  502. if (suffixType && rawFile) {
  503. const suffix = rawFile.name?.slice(
  504. rawFile.name?.indexOf('.') + 1,
  505. rawFile.name?.length
  506. )
  507. if (suffixType.indexOf(suffix) == -1) {
  508. ElMessage.error(typeHint ?? '请上传正确文件格式')
  509. return false
  510. } else {
  511. return true
  512. }
  513. } else {
  514. return true
  515. }
  516. // if (rawFile.type !== 'application/vnd.android.package-archive') {
  517. // ElMessage.error('请上传正确的应用文件')
  518. // return false
  519. // } else if (rawFile.size / 1024 / 1024 > 200) {
  520. // ElMessage.error('文件大小不能超过 200MB!')
  521. // return false
  522. // }
  523. }
  524. //APP版本号校验
  525. function handleBeforeInput(value, checkContent, item) {
  526. if (checkContent) {
  527. if (checkContent == 'version') {
  528. //限制输入版本号
  529. // 匹配形如 x.y.z 的版本号
  530. const regex = /[^\d\.]/g
  531. if (!regex.test(value)) {
  532. item.form.error = ''
  533. } else {
  534. props.form[item.prop] = ''
  535. item.form.error = '版本号格式不正确,请输入形如 x.y.z 的版本号'
  536. }
  537. } else if (checkContent == 'money') {
  538. //金额校验
  539. const regex = /^\d*(\.\d{0,2})?$/
  540. if (regex.test(value)) {
  541. item.form.error = ''
  542. props.form[item.prop] = value.trim()
  543. } else {
  544. props.form[item.prop] = ''
  545. item.form.error = '请输入正确的数据格式'
  546. }
  547. //限制输入金额
  548. if (value.length > 15) {
  549. props.form[item.prop] = value.slice(0, 15)
  550. item.form.error = '最多只能输入15位'
  551. }
  552. } else if (checkContent == 'percentage') {
  553. //限制输入金额
  554. //金额校验
  555. const regex = /^\d*(\.\d{0,2})?$/
  556. if (regex.test(value) && value <= 100) {
  557. item.form.error = ''
  558. props.form[item.prop] = value.trim()
  559. } else {
  560. props.form[item.prop] = ''
  561. item.form.error = '请输入正确的数据格式且最大值为100'
  562. }
  563. } else if (checkContent == 'integer') {
  564. //数字校验
  565. const regex = /^[0-9]\d*$/
  566. if (regex.test(value)) {
  567. item.form.error = ''
  568. props.form[item.prop] = value.trim()
  569. } else {
  570. props.form[item.prop] = ''
  571. item.form.error = '只能输入整数'
  572. if(item.form.min === 3){
  573. item.form.error = '产品编号最少输入三位'
  574. }
  575. }
  576. if (
  577. item?.form?.integerlength &&
  578. value.toString().length > item.form.integerlength
  579. ) {
  580. props.form[item.prop] = value.slice(0, item.form.integerlength)
  581. item.form.error = `最多只能输入${item.form.integerlength}位`
  582. }
  583. if (
  584. item?.form?.min &&
  585. value.toString().length < item.form.min
  586. ) {
  587. // props.form[item.prop] = value.slice(0, item.form.min)
  588. item.form.error = `最少输入${item.form.min}位`
  589. }
  590. }
  591. } else {
  592. item.form.error = ''
  593. }
  594. if (item?.form?.digit && value.length > item.form.digit) {
  595. props.form[item.prop] = value.slice(0, item.form.digit)
  596. item.form.error = `最多只能输入${item.form.digit}位`
  597. }
  598. emit('handleBeforeInput', value, item, props.form)
  599. }
  600. function lazySelectorSelection(value, title, form) {
  601. emit('lazySelectorSelection', value, title, form)
  602. }
  603. </script>
  604. <style lang="scss" scoped>
  605. .bg-content {
  606. width: 100%;
  607. }
  608. .t-title {
  609. font-size: 20px;
  610. }
  611. .avatar-uploader .avatar {
  612. width: 375px;
  613. height: 280px;
  614. display: block;
  615. }
  616. .avatar-uploader .el-upload {
  617. border: 1px dashed var(--el-border-color);
  618. border-radius: 6px;
  619. cursor: pointer;
  620. position: relative;
  621. overflow: hidden;
  622. transition: var(--el-transition-duration-fast);
  623. }
  624. .avatar-uploader .el-upload:hover {
  625. border-color: var(--el-color-primary);
  626. }
  627. .el-icon.avatar-uploader-icon {
  628. font-size: 28px;
  629. color: #8c939d;
  630. border: 1px dashed #8c939d;
  631. border-radius: 6px;
  632. width: 375px;
  633. height: 280px;
  634. text-align: center;
  635. }
  636. ::v-deep .el-form-item__content {
  637. flex-wrap: nowrap;
  638. }
  639. :deep(.el-input-number .el-input__inner) {
  640. text-align: left;
  641. }
  642. </style>