CzRger hai 6 meses
pai
achega
4bdbd9cc7f

+ 10 - 0
src/api/modules/manage/global.ts

@@ -0,0 +1,10 @@
+import { handle } from '../../index'
+
+const suffix = 'api'
+
+// 文件上传
+export const frontUploadFile = (params: any) => handle({
+  url: `/${suffix}/front/uploadFile`,
+  method: 'post',
+  params
+})

BIN=BIN
src/assets/images/cus/cus-tab_type2-active.png


BIN=BIN
src/assets/images/cus/cus-title_type2-icon.png


BIN=BIN
src/assets/images/cus/file-del.png


BIN=BIN
src/assets/images/cus/no-data.png


BIN=BIN
src/assets/images/cus/no-data_1.webp


BIN=BIN
src/assets/images/cus/no-data_2.png


BIN=BIN
src/assets/images/cus/no-data_3.png


BIN=BIN
src/assets/images/cus/to-bottom.png


BIN=BIN
src/assets/images/cus/to-right.png


BIN=BIN
src/assets/images/file-type/file-type_default.png


BIN=BIN
src/assets/images/file-type/file-type_excel.png


BIN=BIN
src/assets/images/file-type/file-type_pdf.png


BIN=BIN
src/assets/images/file-type/file-type_ppt.png


BIN=BIN
src/assets/images/file-type/file-type_rar.png


BIN=BIN
src/assets/images/file-type/file-type_txt.png


BIN=BIN
src/assets/images/file-type/file-type_word.png


+ 12 - 0
src/components/cus/CusFormColumn.vue

@@ -188,6 +188,16 @@
             </template>
           </TreeSelectCom>
         </template>
+        <template v-else-if="link === 'upload'">
+          <UploadCom
+            v-bind="$attrs"
+            :label="labelCpt"
+            :param="param"
+            @emitParam="val => {
+                $emit('update:param', val), handleValidate(val)
+              }"
+          />
+        </template>
       </slot>
       <div v-if="unit" class="unit">{{unit}}</div>
       <slot name="unit"/>
@@ -221,6 +231,7 @@ import RadioCom from './cus-form-link/radio.vue'
 import CheckboxCom from './cus-form-link/checkbox.vue'
 import NumberCom from './cus-form-link/number.vue'
 import TreeSelectCom from './cus-form-link/tree-select.vue'
+import UploadCom from './cus-form-link/upload.vue'
 import { v4 } from "uuid";
 
 export default defineComponent({
@@ -238,6 +249,7 @@ export default defineComponent({
     CheckboxCom,
     NumberCom,
     TreeSelectCom,
+    UploadCom
   },
   props: {
     span: {type: Number, default: 6},

+ 512 - 0
src/components/cus/cus-form-link/upload.vue

@@ -0,0 +1,512 @@
+<template>
+  <div class="cus-form-column-upload-com"
+       :class="{
+         'cus-form-column-upload-com_view': $util.isValue($attrs.disabled) ? $attrs.disabled : (isLimitCpt || isViewCpt),
+         'cus-form-column-upload-com_list': layout === 'list',
+         'cus-form-column-upload-com_card': layout === 'card',
+         'cus-form-column-upload-com_limit-no-upload': isLimitCpt && limitNoUpload !== false
+       }"
+       v-loading="loading"
+       :element-loading-background="elementLoadingBackground">
+    <el-upload
+        class="el-upload-com"
+        ref="ref_upload"
+        v-bind="$attrs"
+        :file-list="paramVal"
+        action="#"
+        :list-type="layout === 'card' ? 'picture-card' : 'text'"
+        :http-request="handleRequest"
+        :before-upload="handleBeforeUpload"
+        :accept="acceptTypeCpt"
+        :limit="limit > 0 ? limit : null"
+        :on-remove="handleRemove"
+        :disabled="$util.isValue($attrs.disabled) ? $attrs.disabled : (isLimitCpt || isViewCpt)"
+    >
+      <el-tooltip :content="acceptTooltipCpt" placement="top" popper-class="upload-tooltip-popper">
+        <template v-if="layout === 'card'">
+          <div class="upload-layout-card __hover" :class="{'limit-disabled': isLimitCpt || (isViewCpt && paramVal.length === 0)}">
+            <SvgIcon name="add" color="#0062E9" size="24"/>
+            <div>选择{{acceptTypeFormatCpt}}</div>
+            <div v-if="limit > 0">(最多{{ limit }}个)</div>
+          </div>
+        </template>
+        <template v-else>
+          <div class="upload-layout-list_button" :class="{'limit-disabled': isLimitCpt|| (isViewCpt && paramVal.length === 0)}">
+            <SvgIcon name="add" color="#ffffff" size="14"/>
+            选择{{acceptTypeFormatCpt}}
+            <template v-if="limit > 0">(最多{{ limit }}个)</template>
+          </div>
+        </template>
+      </el-tooltip>
+      <template #file="{file}">
+        <template v-if="layout === 'card'">
+          <el-tooltip placement="top" :content="file[nameKey] ?? file[urlKey]" :disabled="true">
+            <div class="upload-layout-card_item" :class="{'item-view': isViewCpt || !delRule(file)}" @mouseenter="file.hover = true" @mouseleave="file.hover = false">
+              <template v-if="validImgByUrl(file[urlKey])">
+                <img class="img __hover" :src="file[urlKey]" @click="viewImg(file[urlKey])"/>
+              </template>
+              <template v-else-if="validVideoByUrl(file[urlKey])">
+                <video class="video __hover" controls :src="file[urlKey]"/>
+              </template>
+              <template v-else>
+                <img class="file __hover" :src="getFileImgByUrl(file[urlKey])" @click="downloadFileByUrl(file[urlKey], file[nameKey])"/>
+              </template>
+              <img class="del __hover" src="@/assets/images/cus/file-del.png" v-if="file.hover && !isViewCpt && delRule(file)" @click.stop="ref_upload.handleRemove(file)"/>
+            </div>
+          </el-tooltip>
+        </template>
+        <template v-else>
+          <div class="upload-layout-list_item __hover" :class="{'item-view': isViewCpt || !delRule(file)}" @click="validImgByUrl(file[urlKey]) ? viewImg(file[urlKey]) : downloadFileByUrl(file[urlKey], file[nameKey])">
+            <img class="file-type-img" :src="getFileImgByUrl(file[urlKey])"/>
+            <CusEllipsis v-if="file[nameKey] ?? file[urlKey]" class="label" :value="file[nameKey] ?? file[urlKey]"/>
+            <SvgIcon v-if="!isViewCpt && delRule(file)" class="close" name="close_2" size="12" @click.stop="ref_upload.handleRemove(file)"/>
+          </div>
+        </template>
+      </template>
+    </el-upload>
+    <el-image-viewer
+        v-if="currentImg.show"
+        :zoom-rate="2"
+        :max-scale="10"
+        :min-scale="0.2"
+        :hide-on-click-modal="true"
+        :url-list="[currentImg.url]"
+        :initial-index="0"
+        @close="imageClose"
+        :teleported="true"
+        :crossorigin="null"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick, onBeforeMount, inject
+} from 'vue'
+import {useRouter, useRoute} from 'vue-router'
+import {ElMessage} from "element-plus";
+import FileDefaultImg from '@/assets/images/file-type/file-type_default.png'
+import FilePDFImg from '@/assets/images/file-type/file-type_pdf.png'
+import FilePPTImg from '@/assets/images/file-type/file-type_ppt.png'
+import FileRARImg from '@/assets/images/file-type/file-type_rar.png'
+import FileTXTImg from '@/assets/images/file-type/file-type_txt.png'
+import FileWORDImg from '@/assets/images/file-type/file-type_word.png'
+import FileEXCELImg from '@/assets/images/file-type/file-type_excel.png'
+import {frontUploadFile} from "@/api/modules/manage/global";
+
+export default defineComponent({
+  name: '',
+  components: {},
+  props: {
+    param: {},
+    label: {},
+    limit: {
+      default: 0
+    },
+    type: {default: 'all', validator(val: string) {
+      return ['all', 'img', 'file'].includes(val)
+    }},
+    layout: {default: 'list', validator(val: string) {
+      return ['list', 'card'].includes(val)
+    }},
+    acceptType: {default: '', type: String},
+    acceptMax: {default: 0, type: Number},
+    acceptFunc: {default: () => (options) => {}},
+    view: {default: null},
+    delRule: {default: () => () => false},
+    cardWidth: {default: '147px'},
+    cardHeight: {default: '128px'},
+    urlKey: {default: 'url'},
+    nameKey: {default: 'name'},
+    limitNoUpload: {default: false}
+  },
+  setup(props, { emit }) {
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({
+      paramVal: <any>props.param,
+      loading: false,
+      elementLoadingBackground: inject('element-loading-background', null),
+      currentImg: {
+        show: false,
+        url: ''
+      },
+      formView: inject('form-view', false),
+      config: {
+        fileMax: 50,
+        fileType: '.doc,.docx,.ppt,.pptx,.pdf,.xls,.xlsx,.txt,.rar,.zip',
+        imgMax: 50,
+        imgType: '.jpg,.jpeg,.png,.gif,.svg,.bmp,.webp',
+      }
+    })
+    watch(() => state.paramVal, (n) => {
+      emit('emitParam', n)
+    })
+    watch(() => props.param, (n) => {
+      state.paramVal = n
+    })
+    const isViewCpt = computed(() => {
+      return that.$util.isValue(props.view) ? props.view : state.formView
+    })
+    const ref_upload = ref()
+    const validImgByUrl = (url) => {
+      if (url) {
+        const imgList = ['bmp', 'jpg','jpeg', 'png', 'tif', 'gif', 'pcx', 'tga', 'exif', 'fpx', 'svg', 'psd', 'cdr', 'pcd', 'dxf', 'ufo', 'eps', 'ai', 'raw', 'WMF', 'webp', 'avif', 'apng', 'jfif'];
+        //进行图片匹配
+        let result = [...imgList.map(v => v.toLowerCase()), ...imgList.map(v => v.toUpperCase())].some((t) => url.includes('.' + t)) || url.includes('/ngx/proxy')  || url.includes('/pic?');
+        return result
+      }
+      return false
+    }
+    const validVideoByUrl = (url) => {
+      if (url) {
+        const videoList = ['mp4'];
+        //进行图片匹配
+        let result = [...videoList.map(v => v.toLowerCase()), ...videoList.map(v => v.toUpperCase())].some((t) => url.includes('.' + t));
+        return result
+      }
+      return false
+    }
+    const acceptTypeCpt = computed(() => {
+      let str = ''
+      if (that.$util.isValue(props.acceptType)) {
+        str += props.acceptType
+      } else {
+        if (props.type === 'all') {
+          str += state.config.imgType
+          str += ',' + state.config.fileType
+        } else if (props.type === 'img') {
+          str += state.config.imgType
+        } else if (props.type === 'file') {
+          str += state.config.fileType
+        }
+      }
+      return str
+    })
+    const acceptTypeFormatCpt = computed(() => {
+      let str = ''
+      if (that.$util.isValue(props.acceptType)) {
+        str += `自定义文件`
+      } else {
+        if (props.type === 'all') {
+          str += `图片、文件`
+        } else if (props.type === 'img') {
+          str += `图片`
+        } else if (props.type === 'file') {
+          str += `文件`
+        }
+      }
+      return str
+    })
+    const acceptTooltipCpt = computed(() => {
+      let str = ''
+      if (that.$util.isValue(props.acceptType)) {
+        str += `只支持大小在0~${props.acceptMax}M内,格式为${props.acceptType}的自定义文件`
+      } else {
+        if (props.type === 'all') {
+          str += `只支持大小在0~${state.config.imgMax}M内,格式为${state.config.imgType}的图片,`
+          str += `或大小在0~${state.config.fileMax}M内,格式为${state.config.fileType}的文件`
+        } else if (props.type === 'img') {
+          str += `只支持大小在0~${state.config.imgMax}M内,格式为${state.config.imgType}的图片`
+        } else if (props.type === 'file') {
+          str += `只支持大小在0~${state.config.fileMax}M内,格式为${state.config.fileType}的文件`
+        }
+      }
+      if (isLimitCpt.value) {
+        str = `最多上传${props.limit}个${acceptTypeFormatCpt.value}`
+      }
+      return str
+    })
+    const isLimitCpt = computed(() => {
+      return props.limit > 0 && props.limit <= state.paramVal.length
+    })
+    const handleBeforeUpload = (file) => {
+      if (that.$util.isValue(props.acceptType)) {
+        if (file.size / (1024 * 1024) > props.acceptMax) {
+          ElMessage.warning(`自定义文件大小不可超过${props.acceptMax}M`)
+          return false
+        } else if (!props.acceptType.split(',').some(v => file.name.includes(v))) {
+          ElMessage.warning(`自定义文件类型仅支持${props.acceptType}`)
+          return false
+        }
+      } else {
+        if (props.type === 'img') {
+          if (file.size / (1024 * 1024) > state.config.imgMax) {
+            ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
+            return false
+          } else if (!state.config.imgType.split(',').some(v => file.name.includes(v))) {
+            ElMessage.warning(`图片类型仅支持${state.config.imgType}`)
+            return false
+          }
+        } else if (props.type === 'file') {
+          if (file.size / (1024 * 1024) > state.config.fileMax) {
+            ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
+            return false
+          } else if (!state.config.fileType.split(',').some(v => file.name.includes(v))) {
+            ElMessage.warning(`文件类型仅支持${state.config.fileType}`)
+            return false
+          }
+        } else if (props.type === 'all') {
+          if (!acceptTypeCpt.value.split(',').some(v => file.name.includes(v))) {
+            ElMessage.warning(`文件或图片类型仅支持${acceptTypeCpt.value}`)
+            return false
+          } else {
+            if (state.config.imgType.split(',').some(v => file.name.includes(v))) {
+              if (file.size / (1024 * 1024) > state.config.imgMax) {
+                ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
+                return false
+              }
+            } else if (state.config.fileType.split(',').some(v => file.name.includes(v))) {
+              if (file.size / (1024 * 1024) > state.config.fileMax) {
+                ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
+                return false
+              }
+            }
+          }
+        }
+      }
+      return file
+    }
+    const handleRequest = async (options) => {
+      state.loading = true
+      if (that.$util.isValue(props.acceptType)) {
+        const result: any = await props.acceptFunc(options)
+        if (result?.[props.urlKey] && result?.[props.nameKey]) {
+          state.paramVal = [...state.paramVal, result]
+        } else {
+          state.paramVal = [...state.paramVal]
+        }
+        state.loading = false
+      } else if ([...state.config.imgType.split(','), ...state.config.fileType.split(',')].some(v => options.file.name.includes(v))) {
+        const formData = new FormData();
+        formData.append("file", options.file);
+        frontUploadFile(formData).then(res => {
+          if (res?.code === 200) {
+            state.paramVal = [...state.paramVal, {
+              [props.urlKey]: res.data.path,
+              [props.nameKey]: res.data.filename.replace(/,/g, '_')
+            }]
+          }
+          state.loading = false
+        }).catch(() => {
+          state.loading = false
+        })
+      }
+    }
+    const fileTypeMapper = new Map([
+      ['doc', FileWORDImg],
+      ['docx', FileWORDImg],
+      ['ppt', FilePPTImg],
+      ['pptx', FilePPTImg],
+      ['pdf', FilePDFImg],
+      ['xls', FileEXCELImg],
+      ['xlsx', FileEXCELImg],
+      ['txt', FileTXTImg],
+      ['rar', FileRARImg],
+      ['zip', FileRARImg],
+    ])
+    const getFileImgByUrl = (url) => {
+      if (url) {
+        const regex = /\.([^.]*)$/;
+        const match = url.match(regex);
+        const t = match ? match[1] : '';
+        if (t && fileTypeMapper.has(t)) {
+          return fileTypeMapper.get(t)
+        }
+      }
+      return FileDefaultImg
+    }
+    const handleRemove = (file, files) => {
+      state.paramVal = files
+    }
+    const downloadFileByUrl = (url, name) => {
+      ElMessage.info('开始下载!')
+      const xhr = new XMLHttpRequest();
+      xhr.open('GET', url, true);
+      xhr.responseType = 'blob';
+      xhr.onload = function () {
+        if (xhr.status === 200) {
+          const blob = xhr.response;
+          const url = window.URL.createObjectURL(blob);
+          const a = document.createElement('a');
+          a.href = url;
+          a.download = name || 'default';
+          a.click();
+          ElMessage.success('下载成功!')
+          window.URL.revokeObjectURL(url);
+        }
+      };
+      xhr.send();
+    }
+    const viewImg = (url) => {
+      state.currentImg.url = url
+      state.currentImg.show = true
+    }
+    const imageClose = () => {
+      state.currentImg.show = false
+      state.currentImg.url = ''
+    }
+    onMounted(() => {
+    })
+    return {
+      ...toRefs(state),
+      acceptTypeCpt,
+      handleBeforeUpload,
+      handleRequest,
+      handleRemove,
+      validImgByUrl,
+      validVideoByUrl,
+      acceptTooltipCpt,
+      acceptTypeFormatCpt,
+      getFileImgByUrl,
+      ref_upload,
+      downloadFileByUrl,
+      isLimitCpt,
+      viewImg,
+      imageClose,
+      isViewCpt
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+.cus-form-column-upload-com {
+  $uploadWidth: v-bind(cardWidth);
+  $uploadHeight: v-bind(cardHeight);
+  width: 100%;
+  position: relative;
+  .el-upload-com {
+    :deep(.el-upload) {
+      .upload-layout-list_button {
+        height: 32px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 0 18px;
+        background-color: #0062E9;
+        border-radius: 4px;
+        font-size: 14px;
+        font-family: Microsoft YaHei;
+        font-weight: 400;
+        color: #FFFFFF;
+        .svg-icon {
+          margin-right: 7px;
+        }
+        &.limit-disabled {
+          cursor: no-drop;
+          opacity: 0.5;
+        }
+      }
+
+      .upload-layout-card {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        line-height: 1;
+        align-items: center;
+        justify-content: center;
+        background-color: rgba(46, 129, 255, 0.04);
+        .svg-icon {
+          margin-bottom: 10px;
+          opacity: 0.5;
+        }
+        > div {
+          font-size: 14px;
+          font-family: PingFang SC-Regular, PingFang SC;
+          font-weight: 400;
+          color: rgba(0,98,233,0.5);
+        }
+        &.limit-disabled {
+          cursor: no-drop;
+          opacity: 0.5;
+        }
+      }
+    }
+    :deep(.el-upload-list) {
+      margin: 0;
+      .el-upload-list__item {
+        transition: none;
+        .upload-layout-list_item {
+          display: flex;
+          align-items: center;
+          padding: 0 6px;
+          .file-type-img {
+            width: 14px;
+            margin: 0 6px 0 6px;
+          }
+          .label {
+            width: calc(100% - 6px - 6px - 14px - 30px);
+          }
+          .close {
+            margin-left: auto;
+          }
+          &.item-view {
+            .label {
+              width: calc(100% - 6px - 6px - 14px);
+            }
+          }
+        }
+        .upload-layout-card_item {
+          width: 100%;
+          height: 100%;
+          position: relative;
+          .img {
+            width: 100%;
+            height: 100%;
+          }
+          .video {
+            width: 100%;
+            height: 100%;
+            object-fit: fill;
+          }
+          .file {
+            width: 100%;
+            height: 100%;
+          }
+          .del {
+            position: absolute;
+            top: 0;
+            right: 0;
+            z-index: 2;
+            width: 20px;
+            height: 20px;
+          }
+        }
+      }
+    }
+  }
+  &.cus-form-column-upload-com_view, &.cus-form-column-upload-com_limit-no-upload {
+    .el-upload-com {
+      :deep(.el-upload) {
+        display: none;
+      }
+    }
+  }
+  &.cus-form-column-upload-com_card {
+    .el-upload-com {
+      :deep(.el-upload) {
+        width: $uploadWidth;
+        height: $uploadHeight;
+      }
+      :deep(.el-upload-list) {
+        .el-upload-list__item {
+          width: $uploadWidth;
+          height: $uploadHeight;
+        }
+      }
+    }
+  }
+}
+</style>

+ 55 - 4
src/views/manage/theme/style.vue

@@ -37,21 +37,45 @@
           :span="8"
           label="logo(图标)"
           v-model:param="state.form.logo"
+          link="upload"
+          layout="card"
+          type="img"
+          :limit="1"
+          limitNoUpload
+          :delRule="(file) => true"
         />
         <CusFormColumn
           :span="8"
           label="logo(标题)"
           v-model:param="state.form.titleLogo"
+          link="upload"
+          layout="card"
+          type="img"
+          :limit="1"
+          limitNoUpload
+          :delRule="(file) => true"
         />
         <CusFormColumn
           :span="8"
           label="搜索背景图"
           v-model:param="state.form.webBgImg"
+          link="upload"
+          layout="card"
+          type="img"
+          :limit="1"
+          limitNoUpload
+          :delRule="(file) => true"
         />
         <CusFormColumn
           :span="8"
           label="登录图片"
           v-model:param="state.form.loginImg"
+          link="upload"
+          layout="card"
+          type="img"
+          :limit="1"
+          limitNoUpload
+          :delRule="(file) => true"
         />
       </CusForm>
     </div>
@@ -73,7 +97,15 @@ const props = defineProps({
  })
 const state: any = reactive({
   detail: {},
-  form: {},
+  form: {
+    mainColor: '#2e81ff',
+    title: '',
+    subTitle: '',
+    logo: [],
+    titleLogo: [],
+    webBgImg: [],
+    loginImg: [],
+  },
   loading: false
 })
 const ref_form = ref()
@@ -82,6 +114,7 @@ const titleCpt = computed(() => {
   return t
 })
 const onSubmit = () => {
+  console.log(state.form)
   ref_form.value.submit().then(() => {
     ElMessageBox.confirm("是否提交?", "提示", {
       confirmButtonText: "确定",
@@ -90,7 +123,12 @@ const onSubmit = () => {
     } as any).then(() => {
       state.loading = true
       const params = JSON.parse(JSON.stringify(state.detail))
-      params.themeStyle = JSON.stringify(state.form)
+      const f = {...state.form}
+      f.logo = f.logo.length > 0 ? f.logo[0].url : ''
+      f.titleLogo = f.titleLogo.length > 0 ? f.titleLogo[0].url : ''
+      f.webBgImg = f.webBgImg.length > 0 ? f.webBgImg[0].url : ''
+      f.loginImg = f.loginImg.length > 0 ? f.loginImg[0].url : ''
+      params.themeStyle = JSON.stringify(f)
       sysThemeStyleConfig(params).then(res => {
         if (res.code === 200) {
           ElMessage.success('配置成功!')
@@ -115,8 +153,12 @@ const initDetail = () => {
   sysThemeFind(props.transfer.id).then(res => {
     if (res.code === 200) {
       state.detail = res.data
-      state.form = state.detail.themeStyle ? JSON.parse(state.detail.themeStyle) : {
-        mainColor: '#2e81ff',
+      if (state.detail.themeStyle) {
+        state.form = JSON.parse(state.detail.themeStyle)
+        state.form.logo = state.form.logo ? [{url: state.form.logo, name: state.form.logo}] : []
+        state.form.titleLogo = state.form.titleLogo ? [{url: state.form.titleLogo, name: state.form.titleLogo}] : []
+        state.form.webBgImg = state.form.webBgImg ? [{url: state.form.webBgImg, name: state.form.webBgImg}] : []
+        state.form.loginImg = state.form.loginImg ? [{url: state.form.loginImg, name: state.form.loginImg}] : []
       }
       state.loading = false
     } else {
@@ -126,6 +168,15 @@ const initDetail = () => {
 }
 watch(() => props.show, (n) => {
   if (n) {
+    state.form = {
+      mainColor: '#2e81ff',
+      title: '',
+      subTitle: '',
+      logo: [],
+      titleLogo: [],
+      webBgImg: [],
+      loginImg: [],
+    }
     initDetail()
     nextTick(() => {
       ref_form.value.reset()