Переглянути джерело

管理后台用户新增、编辑

zuy 2 роки тому
батько
коміт
64599e46a2
32 змінених файлів з 242 додано та 3181 видалено
  1. 1 1
      admin-web/.env.development
  2. 1 1
      admin-web/.env.production
  3. 0 103
      admin-web/src/components/editor/index.vue
  4. 0 175
      admin-web/src/components/form/ExtCaptcha.vue
  5. 0 64
      admin-web/src/components/form/ExtColor.vue
  6. 0 966
      admin-web/src/components/form/ExtCron.vue
  7. 0 185
      admin-web/src/components/form/ExtForm.vue
  8. 0 87
      admin-web/src/components/form/ExtIcon.vue
  9. 0 119
      admin-web/src/components/form/ExtImport.vue
  10. 0 57
      admin-web/src/components/form/ExtProgress.vue
  11. 0 19
      admin-web/src/components/form/ExtRender.vue
  12. 78 526
      admin-web/src/components/form/ExtUpload.vue
  13. 0 241
      admin-web/src/components/iconSelector/index.vue
  14. 0 84
      admin-web/src/components/iconSelector/list.vue
  15. 0 158
      admin-web/src/components/preview/Preview.vue
  16. 0 21
      admin-web/src/components/rich/index.vue
  17. 0 323
      admin-web/src/components/section/ExtQuerySection.vue
  18. 1 1
      admin-web/src/layout/navBars/breadcrumb/toolbar.vue
  19. 1 1
      admin-web/src/router/frontEnd.ts
  20. 9 5
      admin-web/src/router/route.ts
  21. 2 2
      admin-web/src/utils/u.ts
  22. 39 21
      admin-web/src/views/admin/user/dialog.vue
  23. 9 3
      admin-web/src/views/admin/user/index.vue
  24. 6 0
      admin/src/main/java/com/kym/admin/controller/AdminUserController.java
  25. 58 0
      admin/src/main/java/com/kym/admin/controller/FileController.java
  26. 7 7
      admin/src/main/java/com/kym/admin/utils/MybatisPlusGeneratorForAdmin.java
  27. 4 3
      admin/src/main/resources/application.yml
  28. 1 2
      entity/src/main/java/com/kym/entity/admin/vo/AdminUserVo.java
  29. 2 0
      service/src/main/java/com/kym/service/admin/AdminUserService.java
  30. 20 5
      service/src/main/java/com/kym/service/admin/impl/AdminUserServiceImpl.java
  31. 2 0
      service/src/main/java/com/kym/service/miniapp/impl/AttachmentServiceImpl.java
  32. 1 1
      service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

+ 1 - 1
admin-web/.env.development

@@ -3,4 +3,4 @@ ENV = development
 
 # 本地环境接口地址
 VITE_API_URL = http://localhost:8080/admin/
-VITE_FILE_URL = http://localhost:8080/file/download/
+VITE_FILE_URL = http://static.kuaiyuman.cn/

+ 1 - 1
admin-web/.env.production

@@ -3,6 +3,6 @@ ENV = production
 
 # 线上环境接口地址
 VITE_API_URL =
-VITE_FILE_URL =
+VITE_FILE_URL = http://static.kuaiyuman.cn/
 
 VITE_PUBLIC_PATH = ./

+ 0 - 103
admin-web/src/components/editor/index.vue

@@ -1,103 +0,0 @@
-<template>
-	<div class="editor-container">
-		<Toolbar :editor="editorRef" :mode="mode" />
-		<Editor
-			:mode="mode"
-			:defaultConfig="state.editorConfig"
-			:style="{ height }"
-			v-model="state.editorVal"
-			@onCreated="handleCreated"
-			@onChange="handleChange"
-		/>
-	</div>
-</template>
-
-<script setup lang="ts" name="wangEditor">
-// https://www.wangeditor.com/v5/for-frame.html#vue3
-import '@wangeditor/editor/dist/css/style.css';
-import { reactive, shallowRef, watch, onBeforeUnmount } from 'vue';
-import { IDomEditor } from '@wangeditor/editor';
-import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
-
-// 定义父组件传过来的值
-const props = defineProps({
-	// 是否禁用
-	disable: {
-		type: Boolean,
-		default: () => false,
-	},
-	// 内容框默认 placeholder
-	placeholder: {
-		type: String,
-		default: () => '请输入内容...',
-	},
-	// https://www.wangeditor.com/v5/getting-started.html#mode-%E6%A8%A1%E5%BC%8F
-	// 模式,可选 <default|simple>,默认 default
-	mode: {
-		type: String,
-		default: () => 'default',
-	},
-	// 高度
-	height: {
-		type: String,
-		default: () => '180px',
-	},
-	// 双向绑定,用于获取 editor.getHtml()
-	getHtml: String,
-	// 双向绑定,用于获取 editor.getText()
-	getText: String,
-});
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['update:getHtml', 'update:getText']);
-
-// 定义变量内容
-const editorRef = shallowRef();
-const state = reactive({
-	editorConfig: {
-		placeholder: props.placeholder,
-	},
-	editorVal: props.getHtml,
-});
-
-// 编辑器回调函数
-const handleCreated = (editor: IDomEditor) => {
-	editorRef.value = editor;
-};
-// 编辑器内容改变时
-const handleChange = (editor: IDomEditor) => {
-  console.log(editor.getHtml())
-  console.log(editor.getText())
-	emit('update:getHtml', editor.getHtml());
-	emit('update:getText', editor.getText());
-};
-// 页面销毁时
-onBeforeUnmount(() => {
-	const editor = editorRef.value;
-	if (editor == null) return;
-	editor.destroy();
-});
-// 监听是否禁用改变
-// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
-watch(
-	() => props.disable,
-	(bool) => {
-		const editor = editorRef.value;
-		if (editor == null) return;
-		bool ? editor.disable() : editor.enable();
-	},
-	{
-		deep: true,
-	}
-);
-// 监听双向绑定值改变,用于回显
-watch(
-	() => props.getHtml,
-	(val) => {
-		state.editorVal = val;
-	},
-	{
-		deep: true,
-	}
-);
-</script>

+ 0 - 175
admin-web/src/components/form/ExtCaptcha.vue

@@ -1,175 +0,0 @@
-<template>
-  <canvas id="captcha" ref="sliderRef"></canvas>
-</template>
-
-<script lang="ts" setup name="ExtCaptcha">
-//验证码组件
-import {onMounted, reactive} from 'vue';
-import {$body} from "/@/utils/request";
-import {Msg} from "/@/utils/message";
-
-const sliderRef = reactive(null);
-
-const state = reactive({
-  currentCaptchaConfig: {},
-  isPrintLog: true
-})
-
-
-onMounted(()=>{
-  initConfig(590,360,590,50,null)
-})
-
-const clearPreventDefault = (event: any) => {
-  if (event.preventDefault) {
-    event.preventDefault();
-  }
-}
-
-const clearAllPreventDefault = () => {
-  if (sliderRef?.value) {
-    sliderRef.value.addEventListener("touchmove", clearPreventDefault, false)
-  }
-}
-
-const printLog = (...params: Array<any>) => {
-  if (state.isPrintLog) {
-    console.log(JSON.stringify(params));
-  }
-}
-
-const initConfig = (bgImageWidth: number, bgImageHeight: number, sliderImageWidth: number, sliderImageHeight: number, end: any) => {
-  state.currentCaptchaConfig = {
-    startTime: new Date(),
-    trackArr: [],
-    movePercent: 0,
-    bgImageWidth,
-    bgImageHeight,
-    sliderImageWidth,
-    sliderImageHeight,
-    end
-  }
-  printLog("init", state.currentCaptchaConfig);
-}
-
-
-const down = (event: any) => {
-  let targetTouches = event.originalEvent ? event.originalEvent.targetTouches : event.targetTouches;
-  let startX = event.pageX;
-  let startY = event.pageY;
-  if (startX === undefined) {
-    startX = Math.round(targetTouches[0].pageX);
-    startY = Math.round(targetTouches[0].pageY);
-  }
-  state.currentCaptchaConfig.startX = startX;
-  state.currentCaptchaConfig.startY = startY;
-
-  const pageX = state.currentCaptchaConfig.startX;
-  const pageY = state.currentCaptchaConfig.startY;
-  const startTime = state.currentCaptchaConfig.startTime;
-  const trackArr = state.currentCaptchaConfig.trackArr;
-  trackArr.push({
-    x: pageX - startX,
-    y: pageY - startY,
-    type: "down",
-    t: (new Date().getTime() - startTime.getTime())
-  });
-  printLog("start", startX, startY)
-  // pc
-  window.addEventListener("mousemove", move);
-  window.addEventListener("mouseup", up);
-  // 手机端
-  window.addEventListener("touchmove", move, false);
-  window.addEventListener("touchend", up, false);
-  doDown(state.currentCaptchaConfig);
-}
-
-
-const move = (event: any) => {
-  if (event instanceof TouchEvent) {
-    event = event.touches[0];
-  }
-  let pageX = Math.round(event.pageX);
-  let pageY = Math.round(event.pageY);
-  const startX = state.currentCaptchaConfig.startX;
-  const startY = state.currentCaptchaConfig.startY;
-  const startTime = state.currentCaptchaConfig.startTime;
-  const end = state.currentCaptchaConfig.end;
-  const bgImageWidth = state.currentCaptchaConfig.bgImageWidth;
-  const trackArr = state.currentCaptchaConfig.trackArr;
-  let moveX = pageX - startX;
-  const track = {
-    x: pageX - startX,
-    y: pageY - startY,
-    type: "move",
-    t: (new Date().getTime() - startTime.getTime())
-  };
-  trackArr.push(track);
-  if (moveX < 0) {
-    moveX = 0;
-  } else if (moveX > end) {
-    moveX = end;
-  }
-  state.currentCaptchaConfig.moveX = moveX;
-  state.currentCaptchaConfig.movePercent = moveX / bgImageWidth;
-  doMove(state.currentCaptchaConfig);
-  printLog("move", track)
-}
-
-
-const up = (event: any) => {
-  window.removeEventListener("mousemove", move);
-  window.removeEventListener("mouseup", up);
-  window.removeEventListener("touchmove", move);
-  window.removeEventListener("touchend", up);
-  if (event instanceof TouchEvent) {
-    event = event.changedTouches[0];
-  }
-  state.currentCaptchaConfig.stopTime = new Date();
-  let pageX = Math.round(event.pageX);
-  let pageY = Math.round(event.pageY);
-  const startX = state.currentCaptchaConfig.startX;
-  const startY = state.currentCaptchaConfig.startY;
-  const startTime = state.currentCaptchaConfig.startTime;
-  const trackArr = state.currentCaptchaConfig.trackArr;
-
-  const track = {
-    x: pageX - startX,
-    y: pageY - startY,
-    type: "up",
-    t: (new Date().getTime() - startTime.getTime())
-  }
-
-  trackArr.push(track);
-  printLog("up", track)
-  valid(state.currentCaptchaConfig);
-}
-
-const doDown = (config:any) => {
-  // $("#slider-move-btn").css("background-position", "-5px 31.0092%")
-}
-
-const doMove = (currentCaptchaConfig: any) => {
-  // const moveX = currentCaptchaConfig.moveX;
-  // $("#rotate-move-btn").css("transform", "translate(" + moveX + "px, 0px)")
-  // $(".rotate-img-div").css("transform", "rotate(" + (moveX / (currentCaptchaConfig.end / 360)) + "deg)")
-}
-
-const valid = (captchaConfig: any) => {
-  let data = {
-    bgImageWidth: captchaConfig.bgImageWidth,
-    bgImageHeight: captchaConfig.bgImageHeight,
-    sliderImageWidth: captchaConfig.sliderImageWidth,
-    sliderImageHeight: captchaConfig.sliderImageHeight,
-    startSlidingTime: captchaConfig.startTime,
-    endSlidingTime: captchaConfig.stopTime, // 官方demo 这里有个语法错误trackList: captchaConfig.trackArr};let sendData = {'id' : currentCaptchaId,'captchaTrack': data}$.ajax({type:"POST",url:"http://localhost:8080/check",contentType: "application/json", dataType:"json",data:JSON.stringify(sendData),success:function (res) {if (res) {alert("验证成功!!!");}refreshCaptcha();}})
-  }
-  $body('/captcha/valid', data).then(() => {
-    Msg.message('校验成功', 'success')
-  })
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 64
admin-web/src/components/form/ExtColor.vue

@@ -1,64 +0,0 @@
-<style scoped lang="scss">
-.color-box {
-  display: inline-flex;
-  align-items: center;
-  align-content: center;
-}
-
-.refresh-btn {
-  margin-left: 8px;
-  color:#409EFF ;
-}
-
-.refresh-animate{
-  transform: rotate(180deg);
-  transition: all 0.5s;
-}
-</style>
-<template>
-  <div class="color-box">
-    <el-color-picker  v-model="state.modelValue" :predefine="state.colors" @change="handleColorChange"/>
-    <SvgIcon class="refresh-btn" name="ele-Refresh" @click="handleRandomColor" :class="{'refresh-animate':state.anim}"/>
-  </div>
-</template>
-<script setup lang="ts" name="ExtDLabel">
-import {onMounted, reactive} from 'vue';
-
-const emit = defineEmits(['update:modelValue','on-change']);
-
-const props = defineProps({
-  modelValue: {
-    type: String
-  },
-})
-
-const state = reactive({
-  modelValue: '#67C23A' as string,
-  colors: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#CDD0D6', '#8230A6','#EA2517','#DA72C7','#342F3B'],
-  anim:false
-})
-
-// const modelVal = computed(() => props.value)
-
-const handleRandomColor = () => {
-  let rand = Math.floor(Math.random() * state.colors.length);
-  state.modelValue = state.colors[rand];
-  emit('update:modelValue', state.modelValue)
-  emit('on-change')
-  state.anim = true;
-  setTimeout(()=>{
-    state.anim = false;
-  },600)
-}
-
-const handleColorChange = () => {
-  emit('update:modelValue', state.modelValue)
-  emit('on-change')
-}
-
-onMounted(() => {
-  state.modelValue = props.modelValue || '#000000';
-  emit('update:modelValue', state.modelValue)
-  emit('on-change')
-});
-</script>

+ 0 - 966
admin-web/src/components/form/ExtCron.vue

@@ -1,966 +0,0 @@
-<template>
-  <div>
-    <el-input v-model="state.modelData" placeholder="请输入cron表达式">
-      <template #append>
-        <ext-button icon="ele-Setting" @click="state.tooltipVisible = !state.tooltipVisible"/>
-      </template>
-    </el-input>
-
-    <el-dialog
-        title="cron表达式工具"
-        v-model="state.tooltipVisible"
-        width="720px"
-        draggable
-        destroy-on-close
-        append-to-body
-        :modal="false"
-        :close-on-click-modal="false"
-        :close-on-press-escape="false"
-        align-center>
-      <div class="cron-container" style="width: 100%;min-height:500px; ">
-        <el-tabs type="border-card" v-model="state.activeTab">
-
-          <el-tab-pane name="generator" label="生成器" v-if="!disabled">
-
-            <el-tabs model-value="second">
-              <el-tab-pane :label="state.text.Seconds.name" name="second">
-                <div>
-                  <label for="seconds1">
-                    <input type="radio" id="seconds1" value="1" v-model="state.second.cronEvery"/>
-                    {{ state.text.Seconds.every }}
-                  </label>
-                </div>
-                <!-- 每隔多久 -->
-                <div class="mt-20">
-                  <label for="seconds2">
-                    <input type="radio" id="seconds2" value="2" v-model="state.second.cronEvery"/>
-                    {{ state.text.Seconds.interval[0] }}
-                    <el-input-number type="number" :min="1" :max="60" v-model="state.second.incrementIncrement"/>
-                    {{ state.text.Seconds.interval[1] || "" }}
-                    <el-input-number type="number" :min="0" :max="59" v-model="state.second.incrementStart"/>
-                    {{ state.text.Seconds.interval[2] || "" }}
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="seconds3">
-                    <input type="radio" id="seconds3" value="3" v-model="state.second.cronEvery"/>
-                    {{ state.text.Seconds.specific }}
-                    <el-select multiple filterable v-model="state.second.specificSpecific">
-                      <el-option :value="index" v-for="(item, index) in 60" :key="index">{{ index }}</el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="seconds4">
-                    <input type="radio" id="seconds4" value="4" v-model="state.second.cronEvery"/>
-                    {{ state.text.Seconds.cycle[0] }}
-                    <el-input-number type="number" v-model="state.second.rangeStart" :min="1" :max="60"/>
-                    {{ state.text.Seconds.cycle[1] || "" }}
-                    <el-input-number type="number" v-model="state.second.rangeEnd" :min="0" :max="59"/>
-                    {{ state.text.Seconds.cycle[2] || "" }}
-                  </label>
-                </div>
-              </el-tab-pane>
-
-              <el-tab-pane :label="state.text.Minutes.name">
-                <div>
-                  <label for="minute1">
-                    <input type="radio" id="minute1" value="1" v-model="state.minute.cronEvery"/>
-                    {{ state.text.Minutes.every }}
-                  </label>
-                </div>
-                <!-- 每隔多久 -->
-                <div class="mt-20">
-                  <label for="minute2">
-                    <input type="radio" id="minute2" value="2" v-model="state.minute.cronEvery"/>
-                    {{ state.text.Minutes.interval[0] }}
-                    <el-input-number type="number" :min="1" :max="60" v-model="state.minute.incrementIncrement"/>
-                    {{ state.text.Minutes.interval[1] || "" }}
-                    <el-input-number type="number" :min="0" :max="59" v-model="state.minute.incrementStart"/>
-                    {{ state.text.Minutes.interval[2] || "" }}
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="minute3">
-                    <input type="radio" id="minute3" value="3" v-model="state.minute.cronEvery"/>
-                    {{ state.text.Minutes.specific }}
-                    <el-select multiple filterable v-model="state.minute.specificSpecific">
-                      <el-option :value="index" v-for="(item, index) in 60" :key="index">{{ index }}</el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="minute4">
-                    <input type="radio" id="minute4" value="4" v-model="state.minute.cronEvery"/>
-                    {{ state.text.Minutes.cycle[0] }}
-                    <el-input-number type="number" v-model="state.minute.rangeStart" :min="1" :max="60"/>
-                    {{ state.text.Minutes.cycle[1] || "" }}
-                    <el-input-number type="number" v-model="state.minute.rangeEnd" :min="0" :max="59"/>
-                    {{ state.text.Minutes.cycle[2] || "" }}
-                  </label>
-                </div>
-              </el-tab-pane>
-
-
-              <el-tab-pane :label="state.text.Hours.name">
-
-                <div>
-                  <label for="hour1">
-                    <input type="radio" id="hour1" value="1" v-model="state.hour.cronEvery"/>
-                    {{ state.text.Hours.every }}
-                  </label>
-                </div>
-                <!-- 每隔多久 -->
-                <div class="mt-20">
-                  <label for="hour2">
-                    <input type="radio" id="hour2" value="2" v-model="state.hour.cronEvery"/>
-                    {{ state.text.Hours.interval[0] }}
-                    <el-input-number type="number" :min="1" :max="60" v-model="state.hour.incrementIncrement"/>
-                    {{ state.text.Hours.interval[1] || "" }}
-                    <el-input-number type="number" :min="0" :max="59" v-model="state.hour.incrementStart"/>
-                    {{ state.text.Hours.interval[2] || "" }}
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="hour3">
-                    <input type="radio" id="hour3" value="3" v-model="state.hour.cronEvery"/>
-                    {{ state.text.Hours.specific }}
-                    <el-select multiple filterable v-model="state.hour.specificSpecific">
-                      <el-option :value="index" v-for="(item, index) in 24" :key="index">{{ index }}</el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 具体秒数 -->
-                <div class="mt-20">
-                  <label for="hour4">
-                    <input type="radio" id="hour4" value="4" v-model="state.hour.cronEvery"/>
-                    {{ state.text.Hours.cycle[0] }}
-                    <el-input-number type="number" v-model="state.hour.rangeStart" :min="1" :max="60"/>
-                    {{ state.text.Hours.cycle[1] || "" }}
-                    <el-input-number type="number" v-model="state.hour.rangeEnd" :min="0" :max="59"/>
-                    {{ state.text.Hours.cycle[2] || "" }}
-                  </label>
-                </div>
-              </el-tab-pane>
-
-              <el-tab-pane :label="state.text.Day.name">
-                <!-- 1 -->
-                <div>
-                  <label for="day1">
-                    <input type="radio" id="day1" value="1" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.every }}
-                  </label>
-                </div>
-                <!-- 2 -->
-                <div class="mt-20">
-                  <label for="day2">
-                    <input type="radio" id="day2" value="2" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.intervalWeek[0] }}
-                    <el-input-number type="number" :min="1" :max="7" v-model="state.week.incrementIncrement"/>
-                    {{ state.text.Day.intervalWeek[1] }}
-                    <el-input-number type="number" :min="0" :max="59" v-model="state.week.incrementStart"/>
-                    {{ state.text.Day.intervalWeek[2] }}
-                  </label>
-                </div>
-                <!-- 3 -->
-                <div class="mt-20">
-                  <label for="day3">
-                    <input type="radio" id="day3" value="3" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.intervalDay[0] }}
-                    <el-input-number type="number" v-model="state.day.rangeStart" :min="1" :max="30"/>
-                    {{ state.text.Day.intervalDay[1] }}
-                    <el-input-number type="number" v-model="state.day.rangeEnd" :min="1" :max="30"/>
-                    {{ state.text.Day.intervalDay[2] }}
-                  </label>
-                </div>
-                <!-- 4 -->
-                <div class="mt-20">
-                  <label for="day4">
-                    <input type="radio" id="day4" value="4" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.specificWeek }}
-                    <el-select multiple filterable v-model="state.week.specificSpecific">
-                      <el-option v-for="(val, index) in 7" :key="index" :value="['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][val - 1]">
-                        {{ state.text.Week[val - 1] }}
-                      </el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 5 -->
-                <div class="mt-20">
-                  <label for="day5">
-                    <input type="radio" id="day5" value="5" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.specificDay }}
-                    <el-select multiple filterable v-model="state.day.specificSpecific">
-                      <el-option v-for="(val, index) in 31" :key="index" :value="val">
-                        {{ val }}
-                      </el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 6 -->
-                <div class="mt-20">
-                  <label for="day6">
-                    <input type="radio" id="day6" value="6" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.lastDay }}
-                  </label>
-                </div>
-                <!-- 7 -->
-                <div class="mt-20">
-                  <label for="day7">
-                    <input type="radio" id="day7" value="7" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.lastWeekday }}
-                  </label>
-                </div>
-                <!-- 8 -->
-                <div class="mt-20">
-                  <label for="day8">
-                    <input type="radio" id="day8" value="8" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.lastWeek[0] }}
-                    <el-select v-model="state.day.cronLastSpecificDomDay">
-                      <el-option v-for="(val, index) in 7" :key="index" :value="val">
-                        {{ state.text.Week[val - 1] }}
-                      </el-option>
-                    </el-select>
-                    {{ state.text.Day.lastWeek[1] || "" }}
-                  </label>
-                </div>
-                <!-- 9 -->
-                <div class="mt-20">
-                  <label for="day9">
-                    <input type="radio" id="day9" value="9" v-model="state.day.cronEvery"/>
-                    <el-input-number type="number" v-model="state.day.cronDaysBeforeEomMinus" :min="1" :max="31"/>
-                    {{ state.text.Day.beforeEndMonth[0] }}
-                  </label>
-                </div>
-                <!-- 10 -->
-                <div class="mt-20">
-                  <label for="day10">
-                    <input type="radio" id="day10" value="10" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.nearestWeekday[0] }}
-                    <el-input-number type="number" v-model="state.day.cronDaysNearestWeekday" :min="1" :max="31"/>
-                    {{ state.text.Day.nearestWeekday[1] }}
-                  </label>
-                </div>
-                <!-- 11 -->
-                <div class="mt-20">
-                  <label for="day11">
-                    <input type="radio" id="day11" value="11" v-model="state.day.cronEvery"/>
-                    {{ state.text.Day.someWeekday[0] }}
-                    <el-input-number type="number" v-model="state.week.cronNthDayNth" :min="1" :max="5"/>
-                    &nbsp;
-                    <el-select v-model="state.week.cronNthDayDay">
-                      <el-option v-for="(val, index) in 7" :key="index" :value="val">
-                        {{ state.text.Week[val - 1] }}
-                      </el-option>
-                    </el-select>
-                    {{ state.text.Day.someWeekday[1] }}
-                  </label>
-                </div>
-              </el-tab-pane>
-
-              <el-tab-pane :label="state.text.Month.name">
-                <div>
-                  <label for="month1">
-                    <input type="radio" id="month1" value="1" v-model="state.month.cronEvery"/>
-                    {{ state.text.Month.every }}
-                  </label>
-                </div>
-                <!-- 2 -->
-                <div class="mt-20">
-                  <label for="month2">
-                    <input type="radio" id="month2" value="2" v-model="state.month.cronEvery"/>
-                    {{ state.text.Month.interval[0] }}
-                    <el-input-number type="number" v-model="state.month.incrementIncrement" :min="0" :max="12"/>
-                    {{ state.text.Month.interval[1] }}
-                    <el-input-number type="number" v-model="state.month.incrementStart" :min="0" :max="12"/>
-                  </label>
-                </div>
-                <!-- 3 -->
-                <div class="mt-20">
-                  <label for="month3">
-                    <input type="radio" id="month3" value="3" v-model="state.month.cronEvery"/>
-                    {{ state.text.Month.specific }}
-                    <el-select multiple filterable v-model="state.month.specificSpecific">
-                      <el-option v-for="(val, index) in 12" :key="index" :value="val">
-                        {{ val }}
-                      </el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 4 -->
-                <div class="mt-20">
-                  <label for="month4">
-                    <input type="radio" id="month4" value="4" v-model="state.month.cronEvery"/>
-                    {{ state.text.Month.cycle[0] }}
-                    <el-input-number type="number" v-model="state.month.rangeStart" :min="1" :max="12"/>
-                    {{ state.text.Month.cycle[1] }}
-                    <el-input-number type="number" v-model="state.month.rangeEnd" :min="1" :max="12"/>
-                  </label>
-                </div>
-              </el-tab-pane>
-
-              <el-tab-pane :label="state.text.Year.name">
-                <!-- 1 -->
-                <div>
-                  <label for="year1">
-                    <input type="radio" id="year1" value="1" v-model="state.year.cronEvery"/>
-                    {{ state.text.Year.every }}
-                  </label>
-                </div>
-                <!-- 2 -->
-                <div class="mt-20">
-                  <label for="year2">
-                    <input type="radio" id="year2" value="2" v-model="state.year.cronEvery"/>
-                    {{ state.text.Year.interval[0] }}
-                    <el-input-number type="number" v-model="state.year.incrementIncrement" :min="1" :max="99"/>
-                    {{ state.text.Year.interval[1] }}
-                    <el-input-number type="number" v-model="state.year.incrementStart" :min="currYear" :max="currYear + 10"/>
-                  </label>
-                </div>
-                <!-- 3 -->
-                <div class="mt-20">
-                  <label for="year3">
-                    <input type="radio" id="year3" value="3" v-model="state.year.cronEvery"/>
-                    {{ state.text.Year.specific }}
-                    <el-select multiple filterable v-model="state.year.specificSpecific">
-                      <el-option v-for="(val, index) in 100" :key="index" :value="currYear + val">
-                        {{ currYear + val }}
-                      </el-option>
-                    </el-select>
-                  </label>
-                </div>
-                <!-- 4 -->
-                <div class="mt-20">
-                  <label for="year3">
-                    <input type="radio" id="year3" value="4" v-model="state.year.cronEvery"/>
-                    {{ state.text.Year.cycle[0] }}
-                    <el-input-number type="number" v-model="state.year.rangeStart" :min="currYear" :max="currYear + 10"/>
-                    {{ state.text.Year.cycle[1] }}
-                    <el-input-number type="number" v-model="state.year.rangeEnd" :min="currYear" :max="currYear + 10"/>
-                  </label>
-                </div>
-              </el-tab-pane>
-            </el-tabs>
-
-            <div class="v3c-footer mt20">
-              <div style="flex: 1">
-                完整表达式 &nbsp;: &nbsp;&nbsp;<span class="cron">{{ state.cron }}</span>
-                &nbsp; &nbsp; &nbsp;
-<!--                <el-button type="primary" @click.stop="handleChange">{{ state.text.Save }}</el-button>-->
-                <el-button type="success" @click.stop="handleCheck">{{ state.text.Check }}</el-button>
-              </div>
-            </div>
-          </el-tab-pane>
-
-          <el-tab-pane name="examples" label="常用示例">
-            <el-card shadow="hover" class="mt20">
-              <div class="flex justify-between flex-wrap hp code">
-                <ext-clipboard message="*/10 * * * * ?" class="hc"/>
-                <div class="mh10">*/10 * * * * ?</div>
-                <div>每隔10秒执行一次</div>
-              </div>
-
-
-              <div class="flex justify-between  flex-wrap hp code">
-                <ext-clipboard message="0 */5 * * * ?" class="hc"/>
-                <div class="mh10"> 0 */5 * * * ?</div>
-                <div>每隔5分钟执行一次</div>
-              </div>
-
-
-              <div class="flex justify-between  flex-wrap hp code">
-                <ext-clipboard message="0 2,22,32 * * * ?" class="hc"/>
-                <div class="mh10">0 2,22,32 * * * ?</div>
-                <div>在2分、22分、32分执行一次</div>
-              </div>
-
-              <div class="flex justify-between  flex-wrap hp code">
-                <ext-clipboard message="0 0 4-8 * * ?" class="hc"/>
-                <div class="mh10">0 0 4-8 * * ?</div>
-                <div>每天4-8点整点执行一次</div>
-              </div>
-
-              <div class="flex justify-between  flex-wrap hp code">
-                <ext-clipboard message="0 0 2 * * ?" class="hc"/>
-                <div class="mh10">0 0 2 * * ?</div>
-                <div>每天凌晨2点执行一次</div>
-              </div>
-
-
-              <div class="flex justify-between  flex-wrap hp code">
-                <ext-clipboard message="0 0 2 1 * ?" class="hc"/>
-                <div class="mh10">0 0 2 1 * ?</div>
-                <div>每月1号凌晨2点执行一次</div>
-              </div>
-
-
-            </el-card>
-            <p class="code">
-              格式:{Seconds} {Minutes} {Hours} {DayofMonth} {Month} {DayofWeek} {Year}[可选]
-            </p>
-            <p class="code">
-             取值说明:<br>
-              * : 表示匹配该域的任意值。比如Minutes域使用*,就表示每分钟都会触发。<br>
-              - : 表示范围。比如Minutes域使用 10-20,就表示从10分钟到20分钟每分钟都会触发一次。<br>
-              , : 表示列出枚举值。比如Minutes域使用1,3,就表示1分钟和3分钟都会触发一次。<br>
-              / : 表示间隔时间触发(开始时间/时间间隔)。例如在Minutes域使用 5/10,就表示从第5分钟开始,每隔10分钟触发一次。<br>
-              ? : 表示不指定值。简单理解就是忽略该字段的值,直接根据另一个字段的值触发执行。<br>
-              # : 表示该月第n个星期x(x#n),仅用星期域。如:星期:6#3,表示该月的第三个星期五。<br>
-              L : 表示最后,是单词"last"的缩写(最后一天或最后一个星期几);仅出现在日和星期的域中。用在日则表示该月的最后一天,用在星期则表示该月的最后一个星期。如:星期域上的值为5L,则表示该月最后一个星期的星期四。在使用'L'时,不要指定列表','或范围'-',否则易导致出现意料之外的结果。<br>
-              W: 仅用在日的域中,表示距离当月给定日期最近的工作日(周一到周五),是单词"weekday"的缩写。<br>
-            </p>
-
-
-
-          </el-tab-pane>
-          <el-tab-pane name="validator" label="校验器">
-            <h5>最近10次的cron表达式执行时间:</h5>
-            <el-input v-model="state.validatorCron">
-              <template #append>
-                <el-button @click="handleGenLatestCronTime(10)">生成执行计划</el-button>
-              </template>
-            </el-input>
-
-            <el-row v-loading="state.validatorLoading">
-              <el-col :sm="24" :md="24">{{state.cronDesc}}</el-col>
-              <el-col :sm="24" :md="24" v-for="(item,idx) in state.latestCronTimeList" :key="idx">{{ item }}</el-col>
-            </el-row>
-          </el-tab-pane>
-        </el-tabs>
-      </div>
-      <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="state.tooltipVisible = false">关闭</el-button>
-        <el-button type="primary" @click="handleChange">确定</el-button>
-      </span>
-      </template>
-    </el-dialog>
-  </div>
-
-
-</template>
-
-<script>
-import {reactive, computed, toRefs, defineComponent, ref, watch} from "vue";
-import ExtButton from "/@/components/form/ExtButton.vue";
-import {Msg} from "../../utils/message";
-import ExtClipboard from "/@/components/form/ExtClipboard.vue";
-import {$post} from "/@/utils/request.ts";
-
-import cronstrue from 'cronstrue/i18n';
-
-export default defineComponent({
-  name: "ExtCron",
-  components: {ExtClipboard, ExtButton},
-  props: {
-    i18n: {},
-    maxHeight: String,
-    change: Function,
-    value: String,
-    modelValue: String,
-    disabled:Boolean
-  },
-  setup(props, {emit}) {
-    const {i18n} = toRefs(props);
-    const state = reactive({
-      activeTab:'generator',
-      validatorLoading:false,
-      cronDesc:'',
-      validatorCron:'',
-      editorActiveTab: '秒',
-      latestCronTimeList: [],
-      modelData: '',
-      language: i18n.value,
-      tooltipVisible: false,
-      second: {
-        cronEvery: "1",
-        incrementStart: 3,
-        incrementIncrement: 5,
-        rangeStart: 0,
-        rangeEnd: 0,
-        specificSpecific: [],
-      },
-      minute: {
-        cronEvery: "1",
-        incrementStart: 3,
-        incrementIncrement: 5,
-        rangeStart: 0,
-        rangeEnd: 0,
-        specificSpecific: [],
-      },
-      hour: {
-        cronEvery: "1",
-        incrementStart: 3,
-        incrementIncrement: 5,
-        rangeStart: 0,
-        rangeEnd: 0,
-        specificSpecific: [],
-      },
-      day: {
-        cronEvery: "1",
-        incrementStart: 1,
-        incrementIncrement: 1,
-        rangeStart: 0,
-        rangeEnd: 0,
-        specificSpecific: [],
-        cronLastSpecificDomDay: 1,
-        cronDaysBeforeEomMinus: 0,
-        cronDaysNearestWeekday: 1,
-      },
-      week: {
-        cronEvery: "1",
-        incrementStart: 1,
-        incrementIncrement: 1,
-        specificSpecific: [],
-        cronNthDayDay: 1,
-        cronNthDayNth: 1,
-      },
-      month: {
-        cronEvery: "1",
-        incrementStart: 3,
-        incrementIncrement: 5,
-        rangeStart: 1,
-        rangeEnd: 1,
-        specificSpecific: [],
-      },
-      year: {
-        cronEvery: "1",
-        incrementStart: 2022,
-        incrementIncrement: 1,
-        rangeStart: 1,
-        rangeEnd: 1,
-        specificSpecific: [],
-      },
-      output: {
-        second: "",
-        minute: "",
-        hour: "",
-        day: "",
-        month: "",
-        Week: "",
-        year: "",
-      },
-      // text: computed(() => Language[state.language || "cn"]),
-      text: {
-        Seconds: {
-          name: '秒',
-          every: '每一秒钟',
-          interval: ['每隔', '秒执行 ,从', '秒开始'],
-          specific: '具体秒数(可多选)',
-          cycle: ['周期从', '到', '秒']
-        },
-        Minutes: {
-          name: '分',
-          every: '每一分钟',
-          interval: ['每隔', '分执行 ,从', '分开始'],
-          specific: '具体分钟数(可多选)',
-          cycle: ['周期从', '到', '分']
-        },
-        Hours: {
-          name: '时',
-          every: '每一小时',
-          interval: ['每隔', '小时执行 ,从', '小时开始'],
-          specific: '具体小时数(可多选)',
-          cycle: ['周期从', '到', '小时']
-        },
-        Day: {
-          name: '天',
-          every: '每一天',
-          intervalWeek: ['每隔', '周执行 ,从', '开始'],
-          intervalDay: ['每隔', '天执行 ,从', '天开始'],
-          specificWeek: '具体星期几(可多选)',
-          specificDay: '具体天数(可多选)',
-          lastDay: '在这个月的最后一天',
-          lastWeekday: '在这个月的最后一个工作日',
-          lastWeek: ['在这个月的最后一个'],
-          beforeEndMonth: ['在本月底前', '天'],
-          nearestWeekday: ['最近的工作日(周一至周五)至本月', '日'],
-          someWeekday: ['在这个月的第', '个'],
-        },
-        Week: ['天', '一', '二', '三', '四', '五', '六'].map(val => '星期' + val),
-        Month: {
-          name: '月',
-          every: '每一月',
-          interval: ['每隔', '月执行 从', '月开始'],
-          specific: '具体月数(可多选)',
-          cycle: ['从', '到', '月之间的每个月']
-        },
-        Year: {
-          name: '年',
-          every: '每一年',
-          interval: ['每隔', '年执行 从', '年开始'],
-          specific: '具体年份(可多选)',
-          cycle: ['从', '到', '年之间的每一年']
-        },
-        Save: '保存',
-        Check: '校验',
-      },
-      secondsText: computed(() => {
-        let seconds = "";
-        let cronEvery = state.second.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-            seconds = "*";
-            break;
-          case "2":
-            seconds = state.second.incrementStart + "/" + state.second.incrementIncrement;
-            break;
-          case "3":
-            state.second.specificSpecific.map((val) => {
-              seconds += val + ",";
-            });
-            seconds = seconds.slice(0, -1);
-            break;
-          case "4":
-            seconds = state.second.rangeStart + "-" + state.second.rangeEnd;
-            break;
-        }
-        return seconds;
-      }),
-      minutesText: computed(() => {
-        let minutes = "";
-        let cronEvery = state.minute.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-            minutes = "*";
-            break;
-          case "2":
-            minutes = state.minute.incrementStart + "/" + state.minute.incrementIncrement;
-            break;
-          case "3":
-            state.minute.specificSpecific.map((val) => {
-              minutes += val + ",";
-            });
-            minutes = minutes.slice(0, -1);
-            break;
-          case "4":
-            minutes = state.minute.rangeStart + "-" + state.minute.rangeEnd;
-            break;
-        }
-        return minutes;
-      }),
-      hoursText: computed(() => {
-        let hours = "";
-        let cronEvery = state.hour.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-            hours = "*";
-            break;
-          case "2":
-            hours = state.hour.incrementStart + "/" + state.hour.incrementIncrement;
-            break;
-          case "3":
-            state.hour.specificSpecific.map((val) => {
-              hours += val + ",";
-            });
-            hours = hours.slice(0, -1);
-            break;
-          case "4":
-            hours = state.hour.rangeStart + "-" + state.hour.rangeEnd;
-            break;
-        }
-        return hours;
-      }),
-      daysText: computed(() => {
-        let days = "";
-        let cronEvery = state.day.cronEvery;
-        console.log(cronEvery)
-        switch (cronEvery.toString()) {
-          case "1":
-            break;
-          case "2":
-          case "4":
-          case "11":
-            days = "?";
-            break;
-          case "3":
-            days = state.day.rangeStart + "/" + state.day.rangeEnd;
-            break;
-          case "5":
-            state.day.specificSpecific.map((val) => {
-              days += val + ",";
-            });
-            days = days.slice(0, -1);
-            break;
-          case "6":
-            days = "L";
-            break;
-          case "7":
-            days = "LW";
-            break;
-          case "8":
-            days = state.day.cronLastSpecificDomDay + "L";
-            break;
-          case "9":
-            days = "L-" + state.day.cronDaysBeforeEomMinus;
-            break;
-          case "10":
-            days = state.day.cronDaysNearestWeekday + "W";
-            break;
-        }
-        return days;
-      }),
-      weeksText: computed(() => {
-        let weeks = "";
-        let cronEvery = state.day.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-          case "3":
-          case "5":
-            weeks = "?";
-            break;
-          case "2":
-            weeks = state.week.incrementStart + "/" + state.week.incrementIncrement;
-            break;
-          case "4":
-            state.week.specificSpecific.map((val) => {
-              weeks += val + ",";
-            });
-            weeks = weeks.slice(0, -1);
-            break;
-          case "6":
-          case "7":
-          case "8":
-          case "9":
-          case "10":
-            weeks = "?";
-            break;
-          case "11":
-            weeks = state.week.cronNthDayDay + "#" + state.week.cronNthDayNth;
-            break;
-        }
-        return weeks;
-      }),
-      monthsText: computed(() => {
-        let months = "";
-        let cronEvery = state.month.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-            months = "*";
-            break;
-          case "2":
-            months = state.month.incrementStart + "/" + state.month.incrementIncrement;
-            break;
-          case "3":
-            state.month.specificSpecific.map((val) => {
-              months += val + ",";
-            });
-            months = months.slice(0, -1);
-            break;
-          case "4":
-            months = state.month.rangeStart + "-" + state.month.rangeEnd;
-            break;
-        }
-        return months;
-      }),
-      yearsText: computed(() => {
-        let years = "";
-        let cronEvery = state.year.cronEvery;
-        switch (cronEvery.toString()) {
-          case "1":
-            years = "*";
-            break;
-          case "2":
-            years = state.year.incrementStart + "/" + state.year.incrementIncrement;
-            break;
-          case "3":
-            state.year.specificSpecific.map((val) => {
-              years += val + ",";
-            });
-            years = years.slice(0, -1);
-            break;
-          case "4":
-            years = state.year.rangeStart + "-" + state.year.rangeEnd;
-            break;
-        }
-        return years;
-      }),
-      cron: computed(() => {
-        let cron =  `${state.secondsText || "*"} ${state.minutesText || "*"} ${state.hoursText || "*"} ${state.daysText || "*"} ${state.monthsText || "*"} ${state.weeksText || "?"} ${
-            state.yearsText || "*"
-        }`;
-
-        return cron;
-      }),
-    });
-
-    const handleGenLatestCronTime = () => {
-      if (!state.validatorCron) {
-        Msg.message('cron表达式为空', 'error');
-        return;
-      }
-      let cronText = state.validatorCron.trim()
-      let cs = cronstrue.toString(cronText, {locale: 'zh_CN'})
-      console.log(cs)
-
-      state.cronDesc = cs;
-      $post('checkCron',{cron:cronText}).then(res=>{
-        state.latestCronTimeList = res;
-        state.validatorLoading = false;
-      }).catch((e)=>{
-        console.error(e)
-        state.validatorLoading = false;
-      })
-    }
-
-    const handleCheck= () => {
-    state.activeTab='validator';
-    state.validatorCron = state.cron;
-    handleGenLatestCronTime()
-    };
-
-    const handleChange = () => {
-      if(props.disabled){
-        state.tooltipVisible = false;
-        return false;
-      }
-      if (typeof state.cron !== "string") return false;
-      emit("update:modelValue", state.cron);
-      emit("on-change", state.cron);
-      state.modelData = state.cron;
-      state.tooltipVisible = false;
-    };
-    const rest = (data) => {
-      for (let i in data) {
-        if (data[i] instanceof Object) {
-          this.rest(data[i]);
-        } else {
-          switch (typeof data[i]) {
-            case "object":
-              data[i] = [];
-              break;
-            case "string":
-              data[i] = "";
-              break;
-          }
-        }
-      }
-    };
-
-    const tabActive = ref(1);
-    const currYear = new Date().getFullYear() - 1;
-    const onHandleTab = (index) => {
-      tabActive.value = index;
-    };
-
-    watch(
-        () => state.cron,
-        (value) => {
-          if (typeof state.cron !== "string") return;
-          emit("update:modelValue", value);
-          emit("on-change", value);
-        }
-    );
-
-
-    watch(
-        () => props.modelValue,
-        (value) => {
-          state.modelData = value;
-        }
-    );
-
-
-    return {
-      state,
-      handleChange,
-      handleCheck,
-      handleGenLatestCronTime,
-      rest,
-      tabActive,
-      onHandleTab,
-      currYear,
-    };
-  },
-});
-</script>
-
-<style lang="css" scoped>
-.v3c {
-  width: auto;
-  border: 1px solid #f5f7fa;
-}
-
-.v3c-tab {
-  padding: 0;
-  list-style: none;
-  margin: 0;
-  background-color: #f5f7fa;
-  display: flex;
-}
-
-.v3c-tab-item {
-  flex: 1;
-  text-align: center;
-  cursor: pointer;
-  padding: 10px;
-}
-
-.v3c-tab-item.v3c-active {
-  background-color: #5b8ff9;
-  color: #ffffff;
-}
-
-.v3c-lang-btn {
-  background-color: #61ddaa;
-  color: #ffffff;
-  /* border-radius: 10px; */
-}
-
-.v3c-content {
-  padding: 20px;
-  max-height: v-bind(maxHeight);
-  overflow: hidden;
-  overflow-y: auto;
-}
-
-.p-20 {
-  padding: 20px;
-}
-
-.v3c-footer {
-  background-color: #f5f7fa;
-  padding-top: 10px;
-  padding-bottom: 10px;
-  display: flex;
-  text-align: center;
-}
-
-.mt-20 {
-  margin-top: 20px;
-}
-
-.v3c input[type="text"] {
-  width: 80px;
-}
-
-.v3c input[type="number"] {
-  width: 80px;
-  height: 28px;
-  border: 1px solid #d9d9d9;
-}
-
-.v3c select {
-  width: 80px;
-  height: 32px;
-  border: 1px solid #d9d9d9;
-}
-
-.v3c select[multiple] {
-  width: 80px;
-  height: 100px;
-  border: 1px solid #d9d9d9;
-}
-
-.cron {
-  color: #61ddaa;
-  padding: 5px;
-  padding-left: 10px;
-  padding-right: 10px;
-}
-</style>

+ 0 - 185
admin-web/src/components/form/ExtForm.vue

@@ -1,185 +0,0 @@
-<template>
-  <div class="dynamic-form-container layout-pd">
-    <el-card shadow="hover">
-      <el-form
-          :model="form"
-          :rules="rules"
-          ref="formRulesOneRef"
-          size="default" label-width="0px" class="mt35">
-        <el-row :gutter="35">
-          <el-col
-              :xs="val.xs"
-              :sm="val.sm"
-              :md="val.md"
-              :lg="val.md"
-              :xl="val.xl"
-              class="mb20"
-              v-for="(val, key) in state.cols"
-              :key="key">
-            <template v-if="val.type !== ''">
-              <el-form-item
-                  :label="val.label"
-                  :prop="val.prop"
-                  :rules="[{ required: val.required, message: `${val.label}不能为空`, trigger: val.type === 'input' ? 'blur' : 'change' }]"
-                  v-if="val.type !== ''">
-                <el-input
-                    v-if="val.type === 'text'"
-                    v-model="form[val.prop]"
-                    :placeholder="val.placeholder"
-                    clearable
-                    style="width: 100%"
-                    :disabled="val.disabled">
-                </el-input>
-                <el-date-picker
-                    v-model="form[val.prop]"
-                    type="date"
-                    :placeholder="val.placeholder"
-                    v-else-if="val.type === 'date'"
-                    style="width: 100%"
-                    :disabled="val.disabled"
-                >
-                </el-date-picker>
-                <el-select
-                    v-model="form[val.prop]"
-                    :placeholder="val.placeholder"
-                    v-else-if="val.type === 'select'"
-                    style="width: 100%"
-                    :disabled="val.disabled"
-                >
-                  <el-option v-for="item in val.options" :key="item.value" :label="item.label"
-                             :value="item.value"></el-option>
-                </el-select>
-                <el-input
-                    type="textarea"
-                    v-model="form[val.prop]"
-                    :placeholder="val.placeholder"
-                    clearable
-                    v-if="val.type === 'textarea'"
-                    style="width: 100%"
-                    :disabled="val.disabled"
-                ></el-input>
-              </el-form-item>
-            </template>
-            <template v-else>
-              <el-row :gutter="35" v-for="(v, k) in form.list" :key="k">
-                <el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="6" class="mb20">
-                  <el-form-item label="年度" :prop="`list[${k}].year`"
-                                :rules="[{ required: true, message: `年度不能为空`, trigger: 'blur' }]">
-                    <template #label>
-                      <el-button type="primary" circle size="small" @click="onAddRow" v-if="k === 0">
-                        <el-icon>
-                          <ele-Plus/>
-                        </el-icon>
-                      </el-button>
-                      <el-button type="danger" circle size="small" @click="onDelRow(k)" v-else>
-                        <el-icon>
-                          <ele-Delete/>
-                        </el-icon>
-                      </el-button>
-                      <span class="ml10">年度</span>
-                    </template>
-                    <el-input v-model="form.list[k].year" style="width: 100%" placeholder="请输入"></el-input>
-                  </el-form-item>
-                </el-col>
-                <el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="6" class="mb20">
-                  <el-form-item label="月度" :prop="`list[${k}].month`"
-                                :rules="[{ required: true, message: `月度不能为空`, trigger: 'blur' }]">
-                    <el-input v-model="form.list[k].month" style="width: 100%" placeholder="请输入"></el-input>
-                  </el-form-item>
-                </el-col>
-                <el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="6" class="mb20">
-                  <el-form-item label="日度" :prop="`list[${k}].day`"
-                                :rules="[{ required: true, message: `日度不能为空`, trigger: 'blur' }]">
-                    <el-input v-model="form.list[k].day" style="width: 100%" placeholder="请输入"></el-input>
-                  </el-form-item>
-                </el-col>
-              </el-row>
-            </template>
-          </el-col>
-        </el-row>
-      </el-form>
-      <el-row class="flex mt15">
-        <div class="flex-margin">
-          <el-button size="default" @click="onResetForm(formRulesOneRef)">
-            <el-icon>
-              <ele-RefreshRight/>
-            </el-icon>
-            重置
-          </el-button>
-          <el-button size="default" type="success" @click="onQueryChange">
-            <SvgIcon name="ele-Search"/>
-            查询
-          </el-button>
-          <slot name="extra"></slot>
-        </div>
-      </el-row>
-    </el-card>
-  </div>
-</template>
-
-<script setup lang="ts" name="ExtForm">
-import {reactive, ref, computed,onMounted} from 'vue';
-import {ElMessage} from 'element-plus';
-import type {FormInstance} from 'element-plus';
-import fieldUtil from "/@/utils/field";
-
-const emit = defineEmits(['change', 'update:value']);
-const props = defineProps({
-  columns: {
-    type: Array<IField>
-  },
-  value: {
-    type: Object,
-    require: true
-  },
-  importConfig: {
-    type: Object,
-  }
-})
-
-const form = computed(() => {
-  return {...props.value}
-})
-const rules = computed(() => {
-  return [];
-})
-
-// 定义变量内容
-const formRulesOneRef = ref<FormInstance>();
-const state = reactive({
-  cols: [],
-  form: {
-    name: '',
-    email: '',
-    autograph: '',
-    occupation: '',
-    list: [
-      {
-        year: '',
-        month: '',
-        day: '',
-      },
-    ],
-    remarks: '',
-  },
-});
-
-onMounted(()=>{
-  console.log(props.columns)
-  state.cols = fieldUtil.toFormQueryField(props.columns);
-  console.log(state.cols)
-})
-
-const onQueryChange = () => {
-  emit("update:value", form);
-  emit("change")
-}
-
-// 重置表单
-const onResetForm = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  emit("update:value", {})
-  emit("change")
-};
-</script>

+ 0 - 87
admin-web/src/components/form/ExtIcon.vue

@@ -1,87 +0,0 @@
-<template>
-  <!--  <SvgIcon style="font-size: 20px;cursor: pointer;" :name="state.currentIconName"  :title="state.currentIconName"  @click="state.dialogVisible = true"></SvgIcon>-->
-  <el-input @click="state.dialogVisible = true" v-model="state.currentIconName" style="width: 100%">
-    <template #prepend>
-      <SvgIcon style="font-size: 20px;cursor: pointer;" :name="state.currentIconName"></SvgIcon>
-    </template>
-  </el-input>
-  <el-dialog
-      append-to-body
-      v-model="state.dialogVisible"
-      title="请选择图标"
-      width="80%"
-      :before-close="handleClose"
-      @open="beforeOpen"
-  >
-    <div style="display: flex;flex-wrap: wrap">
-      <div v-for="(name,index) in state.icons" :index="index" :key="index" style="cursor: pointer;padding: 1rem"
-           :class="state.currentIconName === 'ele-'+name ? 'selected' : ''"
-           @click="handleIconSelect(name)">
-        <SvgIcon :name="'ele-'+name" :title="'ele-'+name"
-                 style="width: 2rem;height: 2rem;font-size: 20px;"></SvgIcon>
-        <!--        <component :is="name" style="width: 2rem;height: 2rem"></component>-->
-      </div>
-    </div>
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="state.dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleOk">确定</el-button>
-      </span>
-    </template>
-  </el-dialog>
-
-</template>
-
-<script setup lang="ts">
-import * as ElIcons from '@element-plus/icons-vue'
-import {reactive, onMounted} from "vue";
-
-const emit = defineEmits(['update:modelValue', 'on-change']);
-
-const props = defineProps({
-  modelValue: {
-    type: String
-  }
-})
-
-onMounted(() => {
-  if (props.modelValue) {
-    state.currentIconName = props.modelValue;
-  }
-  for (let name in ElIcons) {
-    state.icons.push(name)
-  }
-  emit(`update:modelValue`, state.currentIconName);
-})
-
-const state = reactive({
-  iconList: [],
-  icons: [],
-  dialogVisible: false,
-  currentIconName: 'ele-Aim'
-})
-
-const handleIconSelect = (name: string) => {
-  state.currentIconName = 'ele-' + name;
-}
-const handleClose = () => {
-  state.dialogVisible = false;
-}
-const beforeOpen = () => {
-
-}
-const handleOk = () => {
-  emit(`update:modelValue`, state.currentIconName);
-  emit('on-change')
-  handleClose();
-}
-
-</script>
-
-<style scoped lang="scss">
-.selected {
-  background-color: var(--el-color-primary);
-  color: white;
-}
-</style>
-

+ 0 - 119
admin-web/src/components/form/ExtImport.vue

@@ -1,119 +0,0 @@
-<style scoped>
-
-:deep(.el-upload .el-upload--text) {
-  width: 100% !important;
-}
-</style>
-<template>
-    <span style="margin: 0 10px;">
-        <el-button plain @click="state.showUpload=true" type="info" size="default" :loading="state.showUpload">
-           <SvgIcon name="ele-BottomRight"/>
-            {{ state.labelTitle }}
-          </el-button>
-
-        <el-dialog
-            draggable
-            destroy-on-close
-            ref="importDialog"
-            v-model="state.showUpload"
-            width="475"
-            :close-on-click-modal="false"
-            align-center
-            title="导入文件">
-                <el-upload
-                    type="drag"
-                    :show-upload-list="false"
-                    :before-upload="beforeUpload"
-                    :on-success="handleUploadSuccess"
-                    :max-size="20480"
-                    :on-format-error="handleUploadFormatError"
-                    accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
-                    :on-exceeded-size="handleUploadMaxSize"
-                    :action="state.uploadUrl">
-                  <el-card shadow="always" style="text-align: center;margin-left: 25px">
-                    <div class="flex flex-center flex-align-items-center w100">
-                       <SvgIcon size="36" name="ele-Upload" color="'var(--el-color-primary-light-5)'"/>
-                        <p style="color:#3399ff">
-                        点击或拖拽文件到此处上传,仅支持XLS、XLSX格式文件
-                        </p>
-                      </div>
-                  </el-card>
-
-                  <template #tip v-if="tips">
-                    <el-alert :title="tips" type="success"></el-alert>
-                  </template>
-                </el-upload>
-
-          <template #footer>
-            <el-row justify="space-around">
-              <el-col :span="12" style="text-align: left">
-                <el-link type="primary" v-if="templateUrl" style="padding-top: 10px;" :href="u.fmt.fmtServerUrl(templateUrl)">下载模板</el-link>
-              </el-col>
-              <el-col :span="12">
-                   <el-button type="primary" @click="confirm" size="default">确认</el-button>
-              </el-col>
-            </el-row>
-			</template>
-        </el-dialog>
-    </span>
-</template>
-<script lang="ts" setup name="ExtImport">
-
-import {ref, nextTick,onMounted} from 'vue';
-import u from "/@/utils/u";
-import {Msg} from "/@/utils/message";
-
-const emit = defineEmits(['update:value', 'finish']);
-
-const props = defineProps({
-  importUrl: {
-    type: String
-  },
-  label: {
-    type: String
-  },
-  tips: {
-    type: String
-  },
-  templateUrl: {
-    type: String
-  },
-})
-
-const state = ref({
-  uuid: null,
-  labelTitle: props.label || "导入",
-  showUpload: false,
-  uploadUrl:''
-})
-onMounted(()=>{
-  state.value.uploadUrl = u.fmt.fmtServerUrl(props.importUrl)
-})
-
-const beforeUpload = () => {
-  Msg.showLoading("导入中...");
-}
-const handleUploadMaxSize = () => {
-  Msg.message("文件超出20M大小限制", 'error')
-  Msg.hideLoading();
-}
-const handleUploadFormatError = () => {
-  Msg.message('请选择Excel文件导入上传', 'error');
-  Msg.hideLoading();
-}
-
-const handleUploadSuccess = (obj: any) => {
-  Msg.hideLoading();
-  if (!obj || obj.code === 200) {
-    Msg.message("导入成功", 'success');
-  }
-}
-
-const confirm = () => {
-  nextTick(() => {
-    state.value.showUpload = false;
-    emit("finish");
-  })
-}
-
-</script>

+ 0 - 57
admin-web/src/components/form/ExtProgress.vue

@@ -1,57 +0,0 @@
-<template>
-  <div id="ext_progress" @mousedown="onDragStart">
-    <el-progress
-        style="margin-top: 12px; cursor: pointer"
-        :percentage="state.progress"
-        :text-inside="true"
-        :stroke-width="16"
-        status="success"
-    ></el-progress>
-  </div>
-</template>
-<script setup lang="ts" name="ExtProgress">
-import {onMounted, reactive} from 'vue';
-
-const emit = defineEmits(['on-change', 'update:modelValue','on-choose','on-remove']);
-
-const props = defineProps({
-  modelValue: {
-    type: Number,
-    default: 0
-  },
-})
-
-onMounted(() => {
-  state.progress = props.modelValue || 0;
-})
-
-const state = reactive({
-  isDragEnd: false,
-  progress: 0
-})
-
-const onDragStart = (event: any) => {
-  state.isDragEnd = true;
-  let progressBox = document.querySelector("#ext_progress");
-  progressBox.addEventListener("mouseup", onDragEnd);
-  progressBox.addEventListener("mousemove", onDrag);
-}
-
-const onDragEnd = () => {
-  state.isDragEnd = false;
-}
-
-const onDrag = (event: any) => {
-  if (state.isDragEnd) {
-    const progressBox = document.querySelector("#ext_progress");
-    const rect = progressBox.getBoundingClientRect();
-    const width = rect.width;
-    const offsetX = event.clientX - rect.left;
-    let percentage = Math.round((offsetX / width) * 100);
-    percentage = Math.max(0, Math.min(percentage, 100));
-    state.progress = percentage;
-    emit("update:modelValue",percentage)
-  }
-}
-
-</script>

+ 0 - 19
admin-web/src/components/form/ExtRender.vue

@@ -1,19 +0,0 @@
-<script >
-import {h} from "vue";
-
-export default {
-  name: 'ExtRender',
-  props: {
-    data: Object,
-    func:Function
-  },
-  //h(节点名,属性,子组件[]或文本)
-  //通过render创建组件而不是通过template。没有:绑定参数的操作而是直接{}绑定参数
-  render() {
-    return (
-        this.func(h,this.data)
-        // this.func.apply(null,[h,this.data])
-    )
-  }
-}
-</script>>

+ 78 - 526
admin-web/src/components/form/ExtUpload.vue

@@ -1,564 +1,116 @@
-<style scoped lang="scss">
-
-
-.upload-btn {
-  width: 60px;
-  height: 60px;
-  line-height: 60px;
-  text-align: center;
-  border: 1px dashed #dddddd;
-  border-radius: 2px;
-  cursor: pointer;
-  transition:all 0.5s;
-  margin-left: 5px;
-
-  &:hover{
-    border:1px dashed var(--el-color-primary)
-
-  }
-}
-
-.tips {
-  position: absolute;
-  font-size: 8px;
-  color: #FE526A;
-  display: inline-block;
-  bottom: -15px;
-}
-
-
-::v-deep(.el-form-item--default .el-form-item__content) {
-  display: none;
-}
-
-::v-deep(.el-upload-list) {
-  display: none !important;
-}
-</style>
 <template>
-  <div style="display:flex;align-items:center;min-width: 200px;padding: 10px 0;">
-    <canvas v-if="!readonly&&mime==='image'"  id="uploadCanvasId" style="display: none;"></canvas>
-      <div style="display: flex;flex-wrap: wrap;width: 100%" v-if="state.files.length>0">
-        <draggable
-            style="width: 100%"
-            :list="state.files"
-            itemKey="name"
-            ghost-class="ghost"
-            chosen-class="chosenClass"
-            animation="300"
-            @start="onMove"
-            @end="onEnd">
-          <template #item="{element,index}">
-            <div v-if="isAvatar"  class="hp">
-              <el-avatar :src="u.fmt.fmtImg(element.uid)" :size="60"/>
-              <SvgIcon name="ele-CircleCloseFilled" color="red" class="hc" @click="handleDeleteItem(0)"/>
-            </div>
-            <Attach v-else  :attach="element" @on-delete="handleDeleteItem(index)"/>
-          </template>
-        </draggable>
-      </div>
-
-    <el-upload
-        v-if="!readonly&&state.files.length<limit"
-        ref="upload"
-        :multiple="multiple"
-        :limit="limit"
-        :show-upload-list="false"
-        :before-upload="beforeUpload"
-        :on-success="handleUploadSuccess"
-        :max-size="maxSize||2048"
-        :accept="state.accept"
-        :data="state.uploadData"
-        :on-error="handleError"
-        :on-exceed="handleUploadMaxSize"
-        :on-progress="handleUploadProgress"
-        :action="uploadUrl"
-        style="display: inline-block;">
-      <div class="upload-btn">
-        <SvgIcon size="20" :name="mime==='image'?'ele-Picture':'ele-Paperclip'"/>
-      </div>
-      <el-progress status="success" style="width: 80px;"
-                   :percentage="Number(state.percent)"
-                   :stroke-color="['#108ee9', '#87d068']" :stroke-width="5"
-                   v-if="state.percent>0&&state.percent<100"/>
-    </el-upload>
-
+  <el-upload
+      class="avatar-uploader"
+      :action="state.action"
+      :headers="state.headers"
+      :show-file-list="false"
+      :on-success="handleAvatarSuccess"
+      :on-error="handleAvatarError"
+      :before-upload="beforeAvatarUpload"
+  >
+    <img v-if="imageUrl" :src="imageUrl" class="avatar"/>
+    <el-icon v-else class="avatar-uploader-icon">
+      <Plus/>
+    </el-icon>
+  </el-upload>
+</template>
 
-    <br>
-    <!--    <div v-if="readonly&&!value" class="upload-btn">-->
-    <!--      <SvgIcon size="20" :name="mime==='image'?'ele-Picture':'ele-Paperclip'"/>-->
-    <!--    </div>-->
+<script lang="ts" setup>
+import {ref, reactive, onMounted, watch} from 'vue'
+import {ElMessage} from 'element-plus'
+import {Plus} from '@element-plus/icons-vue'
 
-    <div class="tips" v-if="tips">{{ tips }}</div>
-  </div>
-</template>
-<script setup lang="ts" name="ExtUpload">
-import draggable from "vuedraggable";
-import {reactive, onMounted, computed, watch, onBeforeMount} from 'vue';
-import {UploadRawFile} from 'element-plus';
-import {$body} from "/@/utils/request";
-import u from "/@/utils/u";
+import type {UploadProps} from 'element-plus'
 import {Session} from "/@/utils/storage";
+import u from "/@/utils/u"
 import {Msg} from "/@/utils/message";
-import Attach from "/@/components/attach/Attach.vue";
 
+const emit = defineEmits(['update:modelValue', 'on-change']);
 
 const props = defineProps({
   modelValue: {
-    type: [Object, Array, String]
-  },
-  maxSize: {
-    type: Number,
-    require: true,
-    default: 10240
-  },
-  mime: {
-    type: String,
-    require: true,
-    default: 'image'
-  },
-  tips: {
     type: String
-  },
-  multiple: {
-    type: Boolean,
-    default: false
-  },
-  readonly: {
-    type: Boolean,
-    default: false
-  },
-  limit: {
-    type: Number,
-    default:1
-  },
-  compressed: {
-    type: Boolean,
-    default: false
-  },
-  isAvatar:{
-    type:Boolean,
-    default:false
   }
-});
-
-
-const uploadUrl = computed(() => {
-  let token = Session.get('token');
-  return `${u.url.server}file/upload?X-Token=${token}`
 })
 
 const state = reactive({
-  previewUrl: '',
-  uploadData: {
-    data: undefined as any,
-    fileName: undefined as any
-  },
-  progressInterval: undefined,
-  accept: '' as string,
-  fileFormat: [] as Array<string>,
-  // uploadUrl: `${u.url.server}/file/upload?token=${app.token}`,
-  maxLength: props.limit || 1,
-  fileLength: 0,
-  files: [] as Array<any>,
-  spinShow: false,
-  images: [] as Array<any>,
-  init: false,
-  percent: '',
-  mode: 'cos',//当前上传模式,空-后台服务器/qiniu-七牛云/cos-腾讯云
-  qiniuConfig: {
-    useCdnDomain: false,
-    region: null
-  },
-  qiniuExtra: {},
-  qiniuUrl: 'http://pxal1j743.bkt.clouddn.com/',
-  cosUrl: "https://zyp-1258963180.cos.ap-guangzhou.myqcloud.com/",
-  cos: undefined,
-  cosConfig: {
-    tmpSecretId: undefined,
-    tmpSecretKey: undefined,
-    sessionToken: undefined,
-    expiredTime: undefined,
-  },
-  editable: false,
-  previewImage: null,
-  loading: null as any
+  action: '',
+  headers: {}
 })
 
-const emit = defineEmits(['update:modelValue','on-change']);
-
-watch(
-    () => state.files,
-    (val) => {
-      console.log(!val,state.files)
-      if(!state.files||state.files.length===0){
-        if (props.limit && props.limit > 1) {
-          emit('update:modelValue', []);
-          emit('on-change')
-        }else{
-          emit('update:modelValue', null);
-          emit('on-change')
-        }
-      }else{
-        console.log("watch", state.files)
-        if (props.mime !== 'image') {
-          if (props.limit && props.limit > 1) {
-            emit('update:modelValue', val);
-          } else {
-            emit('update:modelValue', val[0]);
-          }
-        } else {
-          if (props.limit && props.limit > 1) {
-            emit('update:modelValue', state.files.map(k => k.uid));
-          } else {
-            emit('update:modelValue', state.files[0].uid);
-          }
-        }
-        emit('on-change')
-      }
-    },
-    {
-      deep: true
-    }
-);
-
-
-
-onBeforeMount(() => {
-  document.body.ondrop = function (event) {
-    //组织默认事件
-    event.preventDefault();
-    //阻止时间冒泡
-    event.stopPropagation();
-  }
+watch(() => props.modelValue, (o, v) => {
+  imageUrl.value = u.fmt.fmtUrl(props.modelValue);
 })
 
+const imageUrl = ref('')
+
 onMounted(() => {
-  if (!props.limit) {
-    state.maxLength = 1;
-  } else {
-    state.maxLength = Number(props.limit);
+  let url = import.meta.env.VITE_API_URL;
+  if (!url) {
+    url = `${location.origin}/admin/`;
   }
-  prepareUpload();
-  initData();
+  state.action = `${url}file/upload`
+  state.headers = {"satoken": Session.get("token")}
 })
 
-/*const preview = (item: any) => {
-  let url = u.fmt.fmtPreview(item.url || item)
-  state.previewUrl = url;
-  console.log("preview:", item, url)
-  previewRef.value.handlePreview();
-  /!*  console.log("preview:", item)
-    let url = item.url || item;
-    let fullUrl = u.fmt.fmtUrl(url);
-    // url.indexOf("http") === 0 ? url : u.url.file + url;
-    if (!item) {
-      return;
-    }
-    let type = props.mime;
-    if (['image', 'video', 'audio'].includes(type)) {
-      ElMessage.warning(`预览功能开发中,敬请期待`)
-      // app.openModal('ExtPreview', {
-      //   url: fullUrl,
-      //   type: 'image'
-      // })
-      // this.previewImage = fullUrl;
-      // this.$refs.preview.open();
-    } else if (props.mime === 'wps' || props.mime === 'pdf') {
-      window.open("http://www.xdocin.com/xdoc?_func=to&_format=html&_cache=true&_xdoc=" + fullUrl, "_blank");
-    } else {
-      window.open(fullUrl, "_blank")
-    }*!/
-}*/
-
-const prepareUpload = () => {
-  const type = props.mime;
-  if (type == 'image') {
-    state.accept = "image/*";
-    state.fileFormat = ['jpg', 'jpeg', 'png', 'gif', 'svg'];
-  } else if (type == 'video') {
-    state.accept = "video/*";
-    state.fileFormat = ["mp4", "ogg", "avi", "webm"]
-  } else if (type == 'audio') {
-    state.accept = "audio/*";
-    state.fileFormat = ["mp3", "amr", "avi", "wav"]
-  } else if (type == 'pdf') {
-    state.accept = "application/pdf";
-    state.fileFormat = ["pdf"]
-  } else if (type == 'wps') {
-    state.fileFormat = ["doc", "docx", "xls", "xlsx", "ppt"]
-  } else {
-    state.accept = "**/*";
-  }
-  console.log(state.accept, props.mime)
+const handleAvatarError = ()=>{
+  Msg.hideLoading();
 }
 
-/*const contains = (val: any, array: Array<any>) => {
-  let contain = false;
-  if (val) {
-    if (typeof val === "object") {
-      for (let i = 0; i < array.length; i++) {
-        if (val == array[i].url) {
-          contain = true;
-          break;
-        }
-      }
-    } else {
-      for (let i = 0; i < array.length; i++) {
-        if (val == array[i]) {
-          contain = true;
-          break;
-        }
-      }
-    }
-  }
-  return contain;
-}*/
+const handleAvatarSuccess: UploadProps['onSuccess'] = (
+    response,
+    uploadFile
+) => {
+  Msg.hideLoading();
+  console.log(uploadFile, response)
+  let {url, uuid} = response.data;
+  imageUrl.value = url;
+  // imageUrl.value = URL.createObjectURL(uploadFile.raw!)
 
-const initData = () => {
-  if (state.init) {
-    return
-  }
-  if (!props.modelValue) {
-    return;
-  }
-
-
-
-    if (props.mime === 'image') {
-      if (props.multiple) {
-        state.files = props.modelValue.map(k => {
-          return {uid: k, name: k}
-        })
-      } else {
-        state.files = [{uid: props.modelValue, name: props.modelValue}]
-      }
-    } else {
-      if (props.multiple) {
-        state.files = props.modelValue;
-      } else {
-        state.files = [props.modelValue]
-      }
-    }
-
-  state.init = true;
-  console.log("init",JSON.stringify(state.files),props.modelValue)
+  emit("update:modelValue", uuid)
+  emit("on-change", uuid)
 }
 
-/**
- * 图片压缩转base64上传
- * @param image
- */
-const compress = (image: any) => {
-  const maxWidth = 1920;
-  let canvas: any = document.getElementById("uploadCanvasId");
-  let ctx = canvas?.getContext("2d");
-  // var initImgSize = image.src.length;
-  let width = image.width;
-  let height = image.height;
-  if (width > maxWidth) {
-    let ration = maxWidth / width;
-    width = maxWidth;
-    height = height * ration
-  }
-  canvas.width = width;
-  canvas.height = height;
-  //图片过大需要进行瓦片绘制,暂不做处理
-  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
-  //进行压缩
-  return canvas.toDataURL("image/jpeg", 0.5);
-}
-
-
-const beforeUpload = (file: UploadRawFile) => {
-  let originalName = file.name;
-  let size = file.size;
-  if (size > props.maxSize * 1024) {
-    Msg.message(`文件超出大小${(props.maxSize / 1024).toFixed(2)}Mb限制`, 'error')
-    return false;
+const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
+  if (rawFile.type !== 'image/jpeg') {
+    ElMessage.error('Avatar picture must be JPG format!')
+    return false
+  } else if (rawFile.size / 1024 / 1024 > 2) {
+    ElMessage.error('Avatar picture size can not exceed 2MB!')
+    return false
   }
   Msg.showLoading("上传中...")
-  //压缩上传
-  if (props.compressed && props.mime == 'image') {
-    //---压缩base64上传
-    let fileReader = new FileReader();
-    fileReader.onload = e => {
-      try {
-        let src = e.target?.result;
-        const image = new Image();
-        image.setAttribute('crossOrigin', 'anonymous');
-        Msg.showLoading("上传中...")
-        image.src = src;
-        image.onload = function () {
-          let width = image.width;
-          if (width && width > 1920) {
-            let base64Data = compress(image);
-            state.uploadData = {
-              data: base64Data,
-              fileName: file.name
-            }
-          } else {
-            state.uploadData = {
-              data: src,
-              fileName: originalName
-            }
-          }
-          let token = Session.get("token");
-          $body(`/file/uploadBase64?X-Token=${token}`, state.uploadData)
-              .then((res: any) => {
-                state.init = true;
-                state.files.unshift(res);
-                Msg.hideLoading();
-              }).catch(err => {
-            console.error(err)
-            Msg.hideLoading();
-          });
-        };
-      } catch (e) {
-        Msg.hideLoading();
-        console.error(e);
-      }
-    };
-    fileReader.readAsDataURL(file);
-    Msg.showLoading("上传中,请稍后...")
-    // state.loading = ElLoading.service({text: '上传中,请稍后'});
-    // app.showLoading("上传中,请稍后");
-    return false;
-  } else {
-    //---七牛云上传
-    if (state.mode === "qiniu") {
-      /* var qiniu = require('qiniu-js');
-       _this.$get("/file/qnToken").then(res => {
-           let fileName = app.randomDateSeq(16) + file.name.substring(file.name.lastIndexOf("."));
-           var observable = qiniu.upload(file, fileName, res, _this.qiniuExtra, _this.qiniuConfig)
-           observable.subscribe((next) => {
-               _this.percent = next.total.percent + "%";
-               if (next.total.percent >= 100) {
-                   _this.clearProgressInterval();
-               }
-           }, (error) => {
-               console.error("qnUpload ERR", error)
-           }, (compelete) => {
-               if (this.type === 'image') {
-                   this.images.push(_this.qiniuUrl + fileName);
-               } else {
-                   this.files.push({
-                       url: _this.qiniuUrl + fileName,
-                       name: originalName
-                   });
-               }
-               _this.clearProgressInterval();
-           });
-       }).catch(err => {
-           _this.clearProgressInterval();
-           console.error("qnToken ERR", err)
-       });
-       app.showLoading("上传中,请稍后");
-       //阻止upload的默认上传
-       return false;*/
-    } else if (state.mode == 'cos') {
-      /*      //----腾讯cos文件上传
-            state. loading = ElLoading.service({text: '上传中,请稍后'});
-            var COS = require('cos-js-sdk-v5');
-            let key = app.randomDateSeq(16) + file.name.substring(file.name.lastIndexOf("."));
-            if (!_this.cosConfig.expiredTime || _this.cosConfig.expiredTime < new Date().getTime()) {
-              _this.$get("/file/cosToken").then(res => {
-                _this.cosConfig = res;
-                _this.cos = new COS({
-                  SecretId: _this.cosConfig.tmpSecretId,
-                  SecretKey: _this.cosConfig.tmpSecretKey,
-                  XCosSecurityToken: _this.cosConfig.sessionToken,
-                  ExpiredTime: _this.cosConfig.expiredTime * 1000,
-                });
-                _this.cosUpload(key, file);
-              }).catch(e => {
-                console.error(e)
-                _this.clearProgressInterval();
-              });
-            } else {
-              _this.cosUpload(key, file);
-            }
-            //阻止upload的默认上传
-            return false;*/
-    } else {
-      // _this.spinShow = true;
-
-    }
-  }
-}
-
-const handleUploadMaxSize = () => {
-  Msg.message(`文件超出大小${(props.maxSize / 1024).toFixed(2)}Mb限制`, 'error')
-  // ElMessage.error(`文件超出大小${(props.maxSize / 1024).toFixed(2)}Mb限制`)
-  clearProgressInterval();
-}
-/*
-
-const handleUploadFormatError = () => {
-  ElMessage.error('请选择正确的文件格式上传');
-  clearProgressInterval();
-}
-*/
-
-const handleUploadSuccess = (res: any) => {
-  // Msg.hideLoading();
-  Msg.message('上传成功');
-  console.log(res)
-  state.init = true;
-  if (res && res.code === 200) {
-    state.files.unshift(res.data)
-    /*   if (props.mime === 'image') {
-         state.images.unshift(res.data.url);
-       } else {
-         state.files.unshift({
-           id: 0,
-           url: res.data.url,
-           name: res.data.name,
-           size: res.data.size
-         });
-       }*/
-  } else {
-    Msg.message('文件上传出错:' + res.msg, 'error')
-  }
-  clearProgressInterval();
-}
-
-const handleUploadProgress = (event: any) => {
-  console.log("progress>>>", event)
-  state.percent = Number(event.percent).toFixed(0);
-  if (event.percent >= 100) {
-    clearProgressInterval();
-  }
+  return true
 }
+</script>
 
-const handleError = (error: Error) => {
-  Msg.message("上传出错了,请稍后再试", 'error')
-  clearProgressInterval();
-  console.log(error)
+<style scoped>
+.avatar-uploader .avatar {
+  width: 60px;
+  height: 60px;
+  display: block;
 }
+</style>
 
-const clearProgressInterval = () => {
-  state.init = true;
-  // this.spinShow = false;
-  // state.loading?.close();
-  // Msg.hideLoading();
-  if (state.progressInterval) {
-    clearInterval(state.progressInterval);
-  }
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
 }
 
-const handleDeleteItem = (idx: number) => {
-  state.files.splice(idx, 1);
-  console.log(props.readonly,state.files,props.limit)
+.avatar-uploader .el-upload:hover {
+  border-color: var(--el-color-primary);
 }
 
-
-const onEnd = (event: any) => {
-  console.log("location>>>>", event)
-}
-const onMove = (event: any) => {
-  let {relatedContext, draggedContext} = event;
-  console.log(relatedContext, draggedContext)
+.el-icon.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 60px;
+  height: 60px;
+  text-align: center;
 }
-
-</script>
+</style>

+ 0 - 241
admin-web/src/components/iconSelector/index.vue

@@ -1,241 +0,0 @@
-<template>
-	<div class="icon-selector w100 h100">
-		<el-input
-			v-model="state.fontIconSearch"
-			:placeholder="state.fontIconPlaceholder"
-			:clearable="clearable"
-			:disabled="disabled"
-			:size="size"
-			ref="inputWidthRef"
-			@clear="onClearFontIcon"
-			@focus="onIconFocus"
-			@blur="onIconBlur"
-		>
-			<template #prepend>
-				<SvgIcon
-					:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
-					class="font14"
-					v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
-				/>
-				<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
-			</template>
-		</el-input>
-		<el-popover
-			placement="bottom"
-			:width="state.fontIconWidth"
-			transition="el-zoom-in-top"
-			popper-class="icon-selector-popper"
-			trigger="click"
-			:virtual-ref="inputWidthRef"
-			virtual-triggering
-		>
-			<template #default>
-				<div class="icon-selector-warp">
-					<div class="icon-selector-warp-title">{{ title }}</div>
-					<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
-						<el-tab-pane lazy label="ali" name="ali">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-						<el-tab-pane lazy label="ele" name="ele">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-						<el-tab-pane lazy label="awe" name="awe">
-							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
-						</el-tab-pane>
-					</el-tabs>
-				</div>
-			</template>
-		</el-popover>
-	</div>
-</template>
-
-<script setup lang="ts" name="iconSelector">
-import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
-import type { TabsPaneContext } from 'element-plus';
-import initIconfont from '/@/utils/getStyleSheets';
-import '/@/theme/iconSelector.scss';
-
-// 定义父组件传过来的值
-const props = defineProps({
-	// 输入框前置内容
-	prepend: {
-		type: String,
-		default: () => 'ele-Pointer',
-	},
-	// 输入框占位文本
-	placeholder: {
-		type: String,
-		default: () => '请输入内容搜索图标或者选择图标',
-	},
-	// 输入框占位文本
-	size: {
-		type: String,
-		default: () => 'default',
-	},
-	// 弹窗标题
-	title: {
-		type: String,
-		default: () => '请选择图标',
-	},
-	// 禁用
-	disabled: {
-		type: Boolean,
-		default: () => false,
-	},
-	// 是否可清空
-	clearable: {
-		type: Boolean,
-		default: () => true,
-	},
-	// 自定义空状态描述文字
-	emptyDescription: {
-		type: String,
-		default: () => '无相关图标',
-	},
-	// 双向绑定值,默认为 modelValue,
-	// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
-	// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
-	modelValue: String,
-});
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['update:modelValue', 'get', 'clear']);
-
-// 引入组件
-const IconList = defineAsyncComponent(() => import('/@/components/iconSelector/list.vue'));
-
-// 定义变量内容
-const inputWidthRef = ref();
-const state = reactive({
-	fontIconPrefix: '',
-	fontIconWidth: 0,
-	fontIconSearch: '',
-	fontIconPlaceholder: '',
-	fontIconTabActive: 'ali',
-	fontIconList: {
-		ali: [],
-		ele: [],
-		awe: [],
-	},
-});
-
-// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
-const onIconFocus = () => {
-	if (!props.modelValue) return false;
-	state.fontIconSearch = '';
-	state.fontIconPlaceholder = props.modelValue;
-};
-// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
-const onIconBlur = () => {
-	const list = fontIconTabNameList();
-	setTimeout(() => {
-		const icon = list.filter((icon: string) => icon === state.fontIconSearch);
-		if (icon.length <= 0) state.fontIconSearch = '';
-	}, 300);
-};
-// 图标搜索及图标数据显示
-const fontIconSheetsFilterList = computed(() => {
-	const list = fontIconTabNameList();
-	if (!state.fontIconSearch) return list;
-	let search = state.fontIconSearch.trim().toLowerCase();
-	return list.filter((item: string) => {
-		if (item.toLowerCase().indexOf(search) !== -1) return item;
-	});
-});
-// 根据 tab name 类型设置图标
-const fontIconTabNameList = () => {
-	let iconList: any = [];
-	if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
-	else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
-	else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
-	return iconList;
-};
-// 处理 icon 双向绑定数值回显
-const initModeValueEcho = () => {
-	if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
-	(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
-	(<string | undefined>state.fontIconPrefix) = props.modelValue;
-};
-// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
-const initFontIconName = () => {
-	let name = 'ali';
-	if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
-	else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
-	else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
-	// 初始化 tab 高亮回显
-	state.fontIconTabActive = name;
-	return name;
-};
-// 初始化数据
-const initFontIconData = async (name: string) => {
-	if (name === 'ali') {
-		// 阿里字体图标使用 `iconfont xxx`
-		if (state.fontIconList.ali.length > 0) return;
-		await initIconfont.ali().then((res: any) => {
-			state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
-		});
-	} else if (name === 'ele') {
-		// element plus 图标
-		if (state.fontIconList.ele.length > 0) return;
-		await initIconfont.ele().then((res: any) => {
-			state.fontIconList.ele = res;
-		});
-	} else if (name === 'awe') {
-		// fontawesome字体图标使用 `fa xxx`
-		if (state.fontIconList.awe.length > 0) return;
-		await initIconfont.awe().then((res: any) => {
-			state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
-		});
-	}
-	// 初始化 input 的 placeholder
-	// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
-	state.fontIconPlaceholder = props.placeholder;
-	// 初始化双向绑定回显
-	initModeValueEcho();
-};
-// 图标点击切换
-const onIconClick = (pane: TabsPaneContext) => {
-	initFontIconData(pane.paneName as string);
-	inputWidthRef.value.focus();
-};
-// 获取当前点击的 icon 图标
-const onColClick = (v: string) => {
-	state.fontIconPlaceholder = v;
-	state.fontIconPrefix = v;
-	emit('get', state.fontIconPrefix);
-	emit('update:modelValue', state.fontIconPrefix);
-	inputWidthRef.value.focus();
-};
-// 清空当前点击的 icon 图标
-const onClearFontIcon = () => {
-	state.fontIconPrefix = '';
-	emit('clear', state.fontIconPrefix);
-	emit('update:modelValue', state.fontIconPrefix);
-};
-// 获取 input 的宽度
-const getInputWidth = () => {
-	nextTick(() => {
-		state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
-	});
-};
-// 监听页面宽度改变
-const initResize = () => {
-	window.addEventListener('resize', () => {
-		getInputWidth();
-	});
-};
-// 页面加载时
-onMounted(() => {
-	initFontIconData(initFontIconName());
-	initResize();
-	getInputWidth();
-});
-// 监听双向绑定 modelValue 的变化
-watch(
-	() => props.modelValue,
-	() => {
-		initModeValueEcho();
-		initFontIconName();
-	}
-);
-</script>

+ 0 - 84
admin-web/src/components/iconSelector/list.vue

@@ -1,84 +0,0 @@
-<template>
-	<div class="icon-selector-warp-row">
-		<el-scrollbar ref="selectorScrollbarRef">
-			<el-row :gutter="10" v-if="props.list.length > 0">
-				<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
-					<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
-						<SvgIcon :name="v" />
-					</div>
-				</el-col>
-			</el-row>
-			<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
-		</el-scrollbar>
-	</div>
-</template>
-
-<script setup lang="ts" name="iconSelectorList">
-// 定义父组件传过来的值
-const props = defineProps({
-	// 图标列表数据
-	list: {
-		type: Array,
-		default: () => [],
-	},
-	// 自定义空状态描述文字
-	empty: {
-		type: String,
-		default: () => '无相关图标',
-	},
-	// 高亮当前选中图标
-	prefix: {
-		type: String,
-		default: () => '',
-	},
-});
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['get-icon']);
-
-// 当前 icon 图标点击时
-const onColClick = (v: unknown | string) => {
-	emit('get-icon', v);
-};
-</script>
-
-<style scoped lang="scss">
-.icon-selector-warp-row {
-	height: 230px;
-	overflow: hidden;
-	.el-row {
-		padding: 15px;
-	}
-	.el-scrollbar__bar.is-horizontal {
-		display: none;
-	}
-	.icon-selector-warp-item {
-		display: flex;
-		justify-content: center;
-		align-items: center;
-		border: 1px solid var(--el-border-color);
-		border-radius: 5px;
-		margin-bottom: 10px;
-		height: 30px;
-		i {
-			font-size: 20px;
-			color: var(--el-text-color-regular);
-		}
-		&:hover {
-			cursor: pointer;
-			background-color: var(--el-color-primary-light-9);
-			border: 1px solid var(--el-color-primary-light-5);
-			i {
-				color: var(--el-color-primary);
-			}
-		}
-	}
-	.icon-selector-active {
-		background-color: var(--el-color-primary-light-9);
-		border: 1px solid var(--el-color-primary-light-5);
-		i {
-			color: var(--el-color-primary);
-		}
-	}
-}
-</style>

+ 0 - 158
admin-web/src/components/preview/Preview.vue

@@ -1,158 +0,0 @@
-<template>
-  <el-dialog
-      title="文档预览"
-      v-model="state.showPreview"
-      fullscreen
-      destroy-on-close
-      append-to-body
-      :show-close="false"
-      :close-on-press-escape="false"
-      :close-on-click-modal="false">
-    <template #header="{ close, titleId, titleClass }">
-      <div class="viewer-header">
-        <h4 :id='titleId' :class="titleClass">{{ name }}  <span v-if="state.docViewer||state.xlsViewer">[兼容模式]</span></h4>
-
-        <el-button plain  type="danger" @click="close">
-          <SvgIcon name="ele-CircleCloseFilled"/>关闭
-        </el-button>
-      </div>
-    </template>
-    <div class="doc-viewer" v-if="state.docViewer||state.xlsViewer">
-      <iframe  title="文档预览" id="docViewer-viewer" ref="frame11" :src="props.url" class="iframe-viewer" :height="state.iframeHeight"/>
-<!--      <p v-html="state.content"></p>-->
-    </div>
-<!--    <div class="xls-viewer" v-if="state.xlsViewer">
-      <p v-html="state.content"></p>
-    </div>-->
-    <iframe v-if="!state.docViewer&&!state.xlsViewer" title="文档预览" id="iframe-viewer" ref="frame" :src="src" class="iframe-viewer" :height="state.iframeHeight"/>
-  </el-dialog>
-
-</template>
-
-<script setup lang="ts" name="Preview">
-import {computed, nextTick, onMounted, onBeforeUnmount, ref, reactive} from "vue";
-
-const props = defineProps<{
-  url: string,
-  file?: File,
-  name: string,
-}>()
-
-
-const state = reactive({
-  showPreview: false,
-  iframeHeight: 950,
-  docViewer: false,
-  xlsViewer: false,
-  content:''
-})
-
-// iframe引用
-const frame = ref<HTMLIFrameElement>();
-// iframe路径指向构建产物,这里是/,因为放在了public下面
-// 如果使用cdn,请使用http://viewer.flyfish.group
-const source = '/file-viewer3/index.html'
-// 查看器的源,当前示例为本源,指定为location.origin即可
-const viewerOrigin = location.origin;
-// 构建完整url
-const src = computed(() => {
-  // 文件名称,建议传递,提高体验性
-  const name = props.name || '';
-  if (props.url) {
-    // 直接拼接url
-    return `${source}?url=${encodeURIComponent(props.url)}&name=${encodeURIComponent(name)}`
-  } else if (props.file) {
-    // 直接拼接来源origin
-    return `${source}?from=${encodeURIComponent(viewerOrigin)}&name=${encodeURIComponent(name)}`
-  } else {
-    return source;
-  }
-})
-
-// 发送文件数据
-const handlePreview = () => {
-  nextTick(() => {
-    state.showPreview = true;
-    if(state.xlsViewer||state.docViewer){
-      console.log("handlePreview xxx")
-     /* $get(props.url).then((res:any)=>{
-        state.content = res;
-      })*/
-    }else{
-      let clientHeight = document.body.clientHeight;
-      console.log(clientHeight, clientHeight - 130)
-
-      state.iframeHeight = clientHeight - 130
-
-
-      const viewer = frame.value;
-      if (!viewer || !props.file) return;
-
-      viewer.onload = () => viewer.contentWindow?.postMessage(props.file, viewerOrigin);
-    }
-
-  })
-}
-
-onMounted(() => {
-  let suffix = props.name.substring(props.name.lastIndexOf(".")).toLowerCase();
-  if (suffix.endsWith(".xls")) {
-    state.xlsViewer = true;
-  }
-
-  if (suffix.endsWith(".doc")) {
-    state.docViewer = true;
-  }
-
-})
-
-onBeforeUnmount(() => {
-  state.xlsViewer = false;
-  state.docViewer = false;
-})
-
-// 暴露变量
-defineExpose({
-  handlePreview
-});
-
-
-</script>
-
-
-<style scoped lang="scss">
-.viewer-header {
-  height: 48px;
-  width: 100%;
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-}
-
-.iframe-viewer {
-  //height: calc(100vh - 2px);
-  width: 100%;
-  border: 0;
-
-  html {
-    overflow-y: hidden;
-  }
-}
-
-.xls-viewer {
-  table {
-    margin: 0 auto;
-    border: 1px solid #000000;
-    border-collapse: collapse;
-  }
-
-  th,
-  td {
-    border: 1px solid #000000;
-    text-align: center;
-  }
-
-  overflow-x: scroll;
-}
-</style>
-

+ 0 - 21
admin-web/src/components/rich/index.vue

@@ -1,21 +0,0 @@
-<template>
-  <div id="app1">
-    <ckeditor :editor="state.editor" v-model="state.editorData" :config="state.editorConfig"></ckeditor>
-  </div>
-</template>
-
-<script setup name="CkEditor">
-import { reactive, shallowRef, watch, onBeforeUnmount } from 'vue';
-import CKEditor from '@ckeditor/ckeditor5-vue';
-const ckeditor = CKEditor.component;
-import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
-
-const state = reactive({
-  editor: ClassicEditor,
-  editorData: '<p>Content of the editor.</p>',
-  editorConfig: {
-    // The configuration of the editor.
-  }
-})
-
-</script>

+ 0 - 323
admin-web/src/components/section/ExtQuerySection.vue

@@ -1,323 +0,0 @@
-<template>
-  <el-form :inline="inline"
-           v-model="query"
-           @submit.native.prevent>
-    <template v-for="item in customFields">
-      <template v-if="item.stable&&item.show">
-        <el-form-item :key="item.title">
-          <template v-if="item.type===0">
-            <ExtRender
-                class="z-w150"
-                :render-func="item.render"/>
-          </template>
-          <template v-if="item.type===1||item.type===2">
-            <Input
-                class="z-w150"
-                :placeholder="item.title"
-                v-model.trim="query[item.key]"
-                @on-change="handleQuery"
-                @keyup.enter.native="handleQuery"/>
-          </template>
-          <template v-else-if="item.type===3">
-            <ExtSelect
-                v-if="item.url"
-                :clearable="true"
-                class="z-w150"
-                :placeholder="item.title"
-                :url="item.url"
-                :query="item.query"
-                v-model="query[item.key]"
-                @on-change="handleQuery"/>
-            <ExtDSelect
-                v-else-if="item.dict"
-                class="z-w150"
-                :multiple="true"
-                :dicts="item.dicts"
-                :placeholder="item.title"
-                v-model.trim="query[item.key+'inList']"
-                @change="handleQuery"
-                :type="item.dict"/>
-            <ExtBoolean
-                v-else-if="item.boolean"
-                class="z-w150"
-                v-model="query[item.key]"
-                :placeholder="item.title"/>
-            <ExtInputNumber
-                v-else-if="item.ratio"
-                class="z-w150"
-                :ratio="item.ratio"
-                :placeholder="item.title"
-                v-model.trim="query[item.key]"
-                @on-change="handleQuery"
-                @keyup.enter.native="handleQuery"/>
-            <Input
-                v-else
-                type="number"
-                class="z-w150"
-                :placeholder="item.title"
-                v-model.trim="query[item.key]"
-                @on-change="handleQuery"
-                @keyup.enter.native="handleQuery"/>
-          </template>
-          <template v-else-if="item.type===4||item.type===5">
-            <ExtDSelect
-                class="z-w150"
-                :multiple="true"
-                :dicts="item.dicts"
-                :placeholder="item.title"
-                v-model.trim="query[item.key+'inList']"
-                @change="handleQuery"
-                :type="item.dict"/>
-          </template>
-          <template v-else-if="item.type===6||item.type===7||item.type===8">
-          </template>
-          <template v-else-if="item.type===9">
-            <ExtTreeSelect
-                v-model.trim="query[item.key+'inList']"
-                :placeholder="item.title"
-                :tree-url="item.url"
-                :clearable="true"/>
-          </template>
-          <template v-else-if="item.type===10">
-            <ExtTreeSelect
-                v-model.trim="query[item.key+'inList']"
-                class="z-w150"
-                :placeholder="item.title"
-                :tree-url="item.url"
-                tree-type="account"
-                :clearable="true"/>
-          </template>
-          <template v-else-if="item.type===11">
-            <ExtDatePicker
-                class="z-w150"
-                :placeholder="item.title"
-                v-model="query[item.key]"
-                type="date"
-                @on-change="handleQuery"/>
-          </template>
-          <template v-else-if="item.type===12">
-            <ExtDatePicker
-                class="z-w300"
-                :placeholder="item.title"
-                v-model="query"
-                type="datetimerange"
-                :fields="item.key"
-                @range-change="handleQuery"/>
-          </template>
-          <template v-else>无匹配</template>
-        </el-form-item>
-      </template>
-
-    </template>
-  </el-form>
-</template>
-
-<script setup lang="ts" name="ExtQuerySection">
-import {reactive, onMounted, computed} from 'vue';
-import {Local} from "/@/utils/storage";
-import u from "/@/utils/u"
-
-
-const props = defineProps({
-  columns: {
-    type: Array,
-    default: () => {
-      return []
-    }
-  },
-  inline: {
-    type: Boolean,
-    default: true
-  },
-  formQuery: {
-    type: Object,
-  },
-  hasOptions: {
-    type: Boolean,
-    default: false
-  },
-  name: {
-    type: String
-  }
-})
-
-const state = reactive({
-  customFields: [] as Array<QueryField>,
-  defaultCustom: ["id", "name", "title", "status", "type", "createBy", "updateBy", "createAt", "updateAt"]
-});
-
-// 定义子组件向父组件传值/事件
-const emit = defineEmits(['on-fields-change', 'on-query-change']);
-
-//computed
-const query = computed({
-  get: () => props.formQuery,
-  set: (val: any) => {
-    emit("on-query-change", val);
-  }
-});
-/*watch(
-    () => props.query,
-    () => {
-      // initModeValueEcho();
-      // initFontIconName();
-    }
-);*/
-const saveCustomQuery = () => {
-  Local.set(`${props.name}_custom_query`, state.customFields);
-}
-const loadCustomQuery = (): Array<any> => {
-  return Local.get(`${props.name}_custom_query`);
-}
-const setupCustomFields = (reset: Boolean = false) => {
-  let cacheCustomQuery = loadCustomQuery();
-  let icolumns = (<QueryFields>props.columns).filter((k: QueryField) => !!k.key && k.query);
-  // console.table(icolumns, "title", "key")
-  //<!--  public static final int FIELD_文本 = 1;-->
-  //<!--  public static final int FIELD_文本域 = 2;-->
-  //<!--  public static final int FIELD_数字 = 3;-->
-  //<!--  public static final int FIELD_单选 = 4;-->
-  //<!--  public static final int FIELD_复选 = 5;-->
-  //<!--  public static final int FIELD_富文本 = 6;-->
-  //<!--  public static final int FIELD_图片 = 7;-->
-  //<!--  public static final int FIELD_附件 = 8;-->
-  //<!--  public static final int FIELD_部门选择 = 9;-->
-  //<!--  public static final int FIELD_成员选择 = 10;-->
-  //<!--  public static final int FIELD_日期选择 = 11;-->
-  //<!--  public static final int FIELD_日期范围选择 = 12;-->
-  if (reset) {
-    let tmp: Array<QueryField> = [];
-    //1.先取默认固定字段
-    state.defaultCustom.forEach(custom => {
-      let index = icolumns.findIndex(k => k.key === custom);
-      if (index >= 0) {
-        let field = icolumns[index];
-        if (field.hasOwnProperty("key")) {
-          let {key, type, name, title, dict, url, query, boolean} = field;
-          let obj = {
-            key: key,
-            name: name,
-            title: title,
-            type: type,
-            dict: dict, //字典
-            url: url,
-            query: query,
-            boolean: boolean,
-            show: true,
-            stable: true
-          }
-          if (cacheCustomQuery) {
-            let _t = cacheCustomQuery.find(k => k.key === key);
-            if (_t) {
-              obj = Object.assign({}, obj, _t);
-            }
-          }
-          tmp.push(obj);
-        }
-      }
-    })
-    //2.再取系统字段
-    icolumns.forEach(column => {
-      if (!state.defaultCustom.includes(column.key)) {
-        let {key, type, name, title, dict, url, query, boolean} = column;
-        let obj = {
-          key: key,
-          name: name,
-          title: title,
-          type: type,
-          dict: dict, //字典
-          url: url,
-          query: query,
-          boolean: boolean,
-          show: true,
-          stable: false
-        }
-        if (cacheCustomQuery) {
-          let _t = cacheCustomQuery.find(k => k.key === key);
-          if (_t) {
-            obj = Object.assign({}, obj, _t);
-          }
-        }
-        tmp.push(obj);
-      }
-    })
-    //排序
-    if (!u.isEmptyOrNull(cacheCustomQuery)) {
-      let sortArray = cacheCustomQuery.map(k => k.key);
-      u.sortByArray(tmp, 'key', sortArray)
-    }
-    state.customFields = tmp;
-  } else {
-    let hasInit = !!cacheCustomQuery;
-    if (!hasInit) {
-      let tmp: Array<QueryField> = [];
-      icolumns.forEach(column => {
-        let {key, type, name, title, dict, url, query} = column;
-        tmp.push({
-          key: key,
-          name: name,
-          title: title,
-          type: type,
-          dict: dict,
-          url: url,
-          query: query,
-          show: true,
-          stable: state.defaultCustom.includes(column.key),
-        });
-      })
-      state.customFields = tmp;
-    } else {
-      state.customFields = icolumns;
-    }
-  }
-  emit("on-fields-change", state.customFields);
-  saveCustomQuery();
-  console.log("----->", JSON.stringify(state.customFields))
-
-}
-
-const handleQuery = () => {
-  emit("on-query-change", props.query);
-}
-const handleFieldShowChange = (show: boolean, idx: number) => {
-  state.customFields[idx].show = show;
-  emit("on-fields-change", state.customFields);
-  saveCustomQuery();
-}
-const handleMoveSort = () => {
-  saveCustomQuery();
-  return true;
-}
-
-const handleResetCustomFields = () => {
-  removeCustomQuery();
-  setupCustomFields(true);
-}
-
-const removeCustomQuery = () => {
-  Local.remove(`${props.name}_custom_query`)
-}
-
-onMounted(() => {
-  setupCustomFields(true)
-});
-</script>
-
-
-<style scoped lang="less">
-.popup-item-name {
-  cursor: all-scroll;
-  padding: 5px;
-}
-
-.actived {
-  color: #00C7D2;
-}
-
-.inline-form {
-  display: flex;
-  width: 450px;
-  flex-wrap: wrap;
-  justify-content: space-between;
-}
-</style>

+ 1 - 1
admin-web/src/layout/navBars/breadcrumb/toolbar.vue

@@ -72,7 +72,7 @@
 
     <el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
 			<span class="layout-navbars-breadcrumb-user-link">
-        <el-avatar :src="u.fmt.fmtImg(userInfos.avatar)" :size="35">
+        <el-avatar :src="u.fmt.fmtImg(userInfos.avatar)" :size="35" class="mr3">
           <SvgIcon name="ele-User"/>
         </el-avatar>
 				{{ userInfos.username }}

+ 1 - 1
admin-web/src/router/frontEnd.ts

@@ -151,7 +151,7 @@ export function hasRoles(roles: any, route: any) {
 }
 
 export function hasMenuPermission(permList: any, route: any) {
-	if (route.meta && route.meta.perm) return permList.some((perm: any) => route.meta.perm===perm);
+	if (route.meta && route.meta.perm) return permList.some((perm: any) => route.meta.perm.includes(perm));
 	else return true;
 }
 

+ 9 - 5
admin-web/src/router/route.ts

@@ -97,7 +97,6 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isKeepAlive: true,
                     isAffix: false,
                     isIframe: false,
-
                     icon: 'ele-Monitor',
                     color: 'yellow'
                     // perm:'admin'
@@ -117,6 +116,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-MapLocation',
+                    perm:"equipment.list,station.list",
                 },
                 children: [
                     {
@@ -130,7 +130,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             isKeepAlive: true,
                             isAffix: false,
                             isIframe: false,
-
+                            perm:"station.list",
                             icon: 'ele-OfficeBuilding',
                         },
                     },
@@ -145,7 +145,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             isKeepAlive: true,
                             isAffix: false,
                             isIframe: false,
-
+                            perm:"equipment.list",
                             icon: 'ele-User',
                         },
                     },
@@ -192,6 +192,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-Money',
+                    perm:"order.list",
                 }
             },
             {
@@ -206,6 +207,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-User',
+                    perm:"user.list",
                 }
             },
             {
@@ -220,6 +222,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-Wallet',
+                    perm:"recharge.list",
                 },
             },
             {
@@ -235,6 +238,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                     isAffix: false,
                     isIframe: false,
                     icon: 'ele-Tools',
+                    perm:"user.list,role.list",
                 },
                 children: [
                     {
@@ -248,7 +252,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             isKeepAlive: true,
                             isAffix: false,
                             isIframe: false,
-
+                            perm:"user.list",
                             icon: 'ele-User',
                         },
                     },
@@ -263,7 +267,7 @@ export const adminRoutes: Array<RouteRecordRaw> = [
                             isKeepAlive: true,
                             isAffix: false,
                             isIframe: false,
-
+                            perm:"role.list",
                             icon: 'ele-Compass',
                         },
                     },

+ 2 - 2
admin-web/src/utils/u.ts

@@ -516,7 +516,7 @@ const u = {
             }
             console.log(v,fileUrl)
             // return fileUrl + v;
-            return `${fileUrl}${v}?X-Token=${token}`
+            return `${fileUrl}${v}`
         },
         fmtImg(v: string) {
             let token = Session.get('token')
@@ -526,7 +526,7 @@ const u = {
             if (v.indexOf("http") === 0) {
                 return v;
             }
-            return `${fileUrl}${v}?X-Token=${token}`
+            return `${fileUrl}${v}`
         },
         fmtPreview(v: string) {
             let token = Session.get('token')

+ 39 - 21
admin-web/src/views/admin/user/dialog.vue

@@ -19,7 +19,11 @@
           size="default"
           label-width="100px"
           class="mt5 pd10">
-        <el-form-item label="用户名">
+        <el-form-item label="头像">
+          <ext-upload v-model="state.ruleForm.avatar" @on-change="handleAvatarChange"></ext-upload>
+        </el-form-item>
+
+        <el-form-item label="用户名" prop="username">
           <el-input
               v-model="state.ruleForm.username"
               placeholder="用户名"
@@ -27,7 +31,7 @@
               class="w100">
           </el-input>
         </el-form-item>
-        <el-form-item label="昵称">
+        <el-form-item label="昵称" prop="nickname">
           <el-input
               v-model="state.ruleForm.nickname"
               placeholder="昵称"
@@ -35,23 +39,24 @@
               class="w100">
           </el-input>
         </el-form-item>
-        <el-form-item label="手机号">
+        <el-form-item label="密码" prop="password" v-if="!state.ruleForm.id">
           <el-input
-              v-model="state.ruleForm.mobilePhone"
-              placeholder="手机号"
+              v-model="state.ruleForm.password"
+              placeholder="密码"
               clearable
               class="w100">
           </el-input>
         </el-form-item>
-        <el-form-item label="头像">
+        <el-form-item label="手机号" prop="mobilePhone">
           <el-input
-              v-model="state.ruleForm.avatar"
-              placeholder="头像"
+              v-model="state.ruleForm.mobilePhone"
+              placeholder="手机号"
               clearable
               class="w100">
           </el-input>
         </el-form-item>
-        <el-form-item label="状态">
+
+        <el-form-item label="状态" prop="status">
           <ext-d-select
               type="AdminUser.status"
               v-model="state.ruleForm.status"
@@ -75,7 +80,7 @@
               v-model="state.checkRoleIdList"
               @change="handleCheckedRoleChange"
           >
-            <el-checkbox v-for="role in state.roleList" :key="role.id" :label="role.id">{{role.roleName}}</el-checkbox>
+            <el-checkbox v-for="role in state.roleList" :key="role.id" :label="role.id">{{ role.roleName }}</el-checkbox>
           </el-checkbox-group>
         </el-card>
       </el-form>
@@ -97,9 +102,10 @@ import {$body, $get} from "/@/utils/request";
 import u from '/@/utils/u'
 import type {FormInstance, FormRules} from 'element-plus';
 import ExtDSelect from "/@/components/form/ExtDSelect.vue";
+import ExtUpload from "/@/components/form/ExtUpload.vue";
 
 // 引入组件
-const ExtDetailForm = defineAsyncComponent(() => import('/@/components/form/ExtDetailForm.vue'));
+// const ExtDetailForm = defineAsyncComponent(() => import('/@/components/form/ExtDetailForm.vue'));
 
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['refresh']);
@@ -116,9 +122,15 @@ const initState = () => ({
     title: '',
     submitTxt: '',
   },
-  rules: {},
-  roleList:[],
-  checkRoleIdList:[]
+  rules: {
+    username: [u.validator.required],
+    mobilePhone: [u.validator.required],
+    nickname: [u.validator.required],
+    status: [u.validator.required],
+    password: [u.validator.required],
+  },
+  roleList: [],
+  checkRoleIdList: []
 })
 
 // 定义变量内容
@@ -137,8 +149,12 @@ const open = (action: string = 'add', row: any) => {
 
 };
 
-const handleCheckedRoleChange = ()=>{
-  }
+const handleCheckedRoleChange = () => {
+}
+
+const handleAvatarChange = (val: string) => {
+  console.log("avatar:", val)
+}
 
 // 关闭弹窗
 const onClose = () => {
@@ -160,15 +176,17 @@ const onSubmit = () => {
         state.btnLoading = false;
         Msg.message('操作成功');
         console.log('submit!')
-        if(state.ruleForm.id>0){
-          $body(`admin-user/updateRole`,{userId:state.ruleForm.id,roleIdList:state.checkRoleIdList}).then(()=>{
+        if (state.ruleForm.id > 0) {
+          $body(`admin-user/updateRole`, {userId: state.ruleForm.id, roleIdList: state.checkRoleIdList}).then(() => {
             onClose();
             emit('refresh');
           })
-        }else{
+        } else {
           onClose();
           emit('refresh');
         }
+      }).catch(()=>{
+        state.btnLoading = false;
       })
     } else {
       state.btnLoading = false;
@@ -188,7 +206,7 @@ const handleFormChange = (formData: any) => {
   console.log(formData)
 }
 
-const loadRole = ()=>{
+const loadRole = () => {
   $get(`role/list`).then((res: any) => {
     state.roleList = res;
   })
@@ -199,7 +217,7 @@ const loadData = (id: number) => {
   $get(`admin-user/detail/${id}`).then((res: any) => {
     let {adminUser, roles} = res;
     state.ruleForm = adminUser;
-    state.checkRoleIdList = roles.map(k=>k.roleId)
+    state.checkRoleIdList = roles.map(k => k.roleId)
   })
 
 

+ 9 - 3
admin-web/src/views/admin/user/index.vue

@@ -76,6 +76,11 @@
           <SvgIcon name="ele-Search"/>
           查询
         </el-button>
+
+        <el-button size="default" plain  type="success" class="ml10" @click="onRowClick('add',null)">
+          <SvgIcon name="ele-FolderAdd"/>
+          新增
+        </el-button>
       </el-form>
 
       <el-table
@@ -112,6 +117,7 @@
             </template>
             <template v-else-if="'action'===field.prop">
              <el-button  size="small" plain  type="warning" @click="onRowClick('edit',row)">编辑</el-button>
+             <el-button  size="small" plain  type="danger" @click="onRowDel(row)">删除</el-button>
             </template>
             <template v-else>
               <div>{{row[field.prop]}}</div>
@@ -211,7 +217,7 @@ const loadData = (refresh: boolean = false) => {
     state.pageQuery.pageNum = 1;
   }
   state.tableData.loading = true;
-  $get(`/admin-user/listAdminUser`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
+  $get(`/admin-user/list`, {...state.formQuery, ...state.pageQuery}).then((res: any) => {
     let {list, total} = res;
     state.tableData.data = list;
     state.pageQuery.total = total;
@@ -229,8 +235,8 @@ const onRowClick = (type: string, row: any) => {
 
 // 删除用户
 const onRowDel = (row: any) => {
-  Msg.confirm(`此操作将永久删除:『${row.name}』,是否继续?`).then(() => {
-    $get(`/adminUser/delete/${row.id}`).then(() => {
+  Msg.confirm(`此操作将永久删除:『${row.username}』,是否继续?`).then(() => {
+    $get(`/admin-user/delete/${row.id}`).then(() => {
       Msg.message("删除成功", 'success')
     }).catch(() => {
       Msg.message("删除失败", 'error')

+ 6 - 0
admin/src/main/java/com/kym/admin/controller/AdminUserController.java

@@ -70,6 +70,12 @@ public class AdminUserController extends IController {
         return R.success(adminUserService.listAdminUser(params));
     }
 
+
+    @GetMapping("list")
+    R<?> listUser(@ModelAttribute CommonQueryParam params) {
+        return R.success(adminUserService.listUser(params));
+    }
+
     /**
      * 新增操作员
      *

+ 58 - 0
admin/src/main/java/com/kym/admin/controller/FileController.java

@@ -0,0 +1,58 @@
+package com.kym.admin.controller;
+
+import cn.hutool.core.io.FileUtil;
+import com.kym.common.R;
+import com.kym.common.annotation.ApiLog;
+import com.kym.common.controller.IController;
+import com.kym.common.utils.CommUtil;
+import com.kym.service.miniapp.AttachmentService;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+
+
+/**
+ * @author skyline
+ * @description 文件上传
+ * @date 2023-07-29 10:54
+ */
+@RestController
+@RequestMapping("/file")
+public class FileController extends IController {
+
+    @Resource
+    private AttachmentService attachmentService;
+
+    /**
+     * 文件上传
+     *
+     * @param file
+     * @return
+     */
+    @PostMapping("/upload")
+    public R<?> upload(MultipartFile file) {
+        var fileName = file.getOriginalFilename();
+        CommUtil.asserts(!CommUtil.isExecutable(FileUtil.getSuffix(fileName)), "上传文件类型不支持");
+        return resp(() -> {
+            try {
+                return attachmentService.upload(file);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+
+    @ApiLog("文件删除")
+    @PostMapping("/delete/{fileId}")
+    public R<?> delete(@PathVariable String fileId) {
+        CommUtil.asserts(!CommUtil.isEmptyOrNull(fileId), "删除文件不能为空");
+        return resp((t) -> attachmentService.delete(fileId));
+    }
+
+}

+ 7 - 7
admin/src/main/java/com/kym/admin/utils/MybatisPlusGeneratorForAdmin.java

@@ -35,12 +35,12 @@ public class MybatisPlusGeneratorForAdmin {
                             .controller("admin.controller")
                             // 设置mapperXml生成路径
                             .pathInfo(Map.of(
-                                    OutputFile.entity, "D://快与慢充电桩/code/charge-java/entity/src/main/java/com/kym/entity/admin",
-                                    OutputFile.service, "D://快与慢充电桩/code/charge-java/service/src/main/java/com/kym/service/admin",
-                                    OutputFile.serviceImpl, "D://快与慢充电桩/code/charge-java/service/src/main/java/com/kym/service/admin/impl",
-                                    OutputFile.mapper, "D://快与慢充电桩/code/charge-java/mapper/src/main/java/com/kym/mapper/admin",
-                                    OutputFile.controller, "D://快与慢充电桩/code/charge-java/admin/src/main/java/com/kym/admin/controller",
-                                    OutputFile.xml, "D://快与慢充电桩/code/charge-java/mapper/src/main/resources/mappers/admin"
+                                    OutputFile.entity, "/home/zuy/02.code/charge/charge-java/entity/src/main/java/com/kym/entity/admin",
+                                    OutputFile.service, "/home/zuy/02.code/charge/charge-java/service/src/main/java/com/kym/service/admin",
+                                    OutputFile.serviceImpl, "/home/zuy/02.code/charge/charge-java/service/src/main/java/com/kym/service/admin/impl",
+                                    OutputFile.mapper, "/home/zuy/02.code/charge/charge-java/mapper/src/main/java/com/kym/mapper/admin",
+                                    OutputFile.controller, "/home/zuy/02.code/charge/charge-java/admin/src/main/java/com/kym/admin/controller",
+                                    OutputFile.xml, "/home/zuy/02.code/charge/charge-java/mapper/src/main/resources/mappers/admin"
 
                             ));
                 })
@@ -50,7 +50,7 @@ public class MybatisPlusGeneratorForAdmin {
                     builder.addInclude(
 //                            "t_equipment_info",
 //                            "t_connector_info"
-                            "t_equipment_relation"
+                            "t_role"
 
                             )
                             // 设置过滤表前缀

+ 4 - 3
admin/src/main/resources/application.yml

@@ -48,10 +48,11 @@ upload:
 
 oss:
   endpoint: oss-cn-shenzhen.aliyuncs.com
-  keyId:
-  keySecret:
+  keyId: LTAI5tEPpmhZGDRb6sgqhiA2
+  keySecret: HjlRw844NVP894jAzZna45Vns6axes
   bucket: kym-static
-  prefix: static.kuaiyuman.cn
+  prefix: http://static.kuaiyuman.cn  # 前端访问拼接为 prefix+uuid,例 http://static.kuaiyuman.cn /b2ee8dbd259d4f44a63a8e36c8121f89.vue
+
 password:
  privateKey: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM1zhmu+TcTSZSQ7w0541g45o8ji4ugEC4O31GnS6tfvLTCru+9DPK2/DNdd4z1enz2PuDhktHuoEsEKPdA08fRJSMTXLL0pUEp2OQ+t7tZP6mVLvizasnP+HKAqIndXFr5nXm/okQfL/f/6L2Bben9sItoxC28Z6Y28NfAJPAg1AgMBAAECgYAKOdvQ9RHt4AMEwKzB9SXCY4AReamNntXr4nSCJ+tkgBUhvQqHqDMW+tFqztOGtHT8nXCv7eNF3GHClf3ppRj91utk8zAwZPVAVlRoNcWs60nyKRUO2uhwAV8AE+9UKDoVui7L7UaMcIkssKqQbFGIRXUjjSoPJu0yoHCdp5/3QQJBAOZTbfgmFXVgRSH4IMXJ3aZOqz+Wy3EmvNatYz8NYLBFgLJTWWXtR7URw82R7jL6F9ettfCityhAmXEZnZsEsokCQQDkWkYwFZlUYJ2ctdNEmipXw8tjpCrzQRaZnydXbjviwbSpOvOo5nrxSG5BtL9QDwiy9DL7YLBVJPykAkJm3m1NAkBn2SQTJ7CzLIXfLA4yv7LFYmEKGcZ+rRWlwaWm7zQyJhRB0xzSvSqAtJLRJEP/Dg4j+7m11te4OXA1s3QBShvpAkEAq50gpKCG5D/cE9seVK9b5SuTnmXRlZE0D+3pXi7NOOSFBq30UtosSUs6+YyCPwOdcQhPjFYlD0hFymicSL0e/QJBAOMQaABh/6BcVimWP284x/WxBQ83zzVhcl7fUyqcFvfAw1JeMmRxvm2CWYKQ9MIhQ/9ptFotRCSwMAdJTZceWys=
  publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNc4Zrvk3E0mUkO8NOeNYOOaPI4uLoBAuDt9Rp0urX7y0wq7vvQzytvwzXXeM9Xp89j7g4ZLR7qBLBCj3QNPH0SUjE1yy9KVBKdjkPre7WT+plS74s2rJz/hygKiJ3Vxa+Z15v6JEHy/3/+i9gW3p/bCLaMQtvGemNvDXwCTwINQIDAQAB

+ 1 - 2
entity/src/main/java/com/kym/entity/admin/vo/AdminUserVo.java

@@ -1,7 +1,6 @@
 package com.kym.entity.admin.vo;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.kym.entity.BaseEntity;
 import lombok.Getter;
 import lombok.Setter;
@@ -34,7 +33,7 @@ public class AdminUserVo extends BaseEntity implements Serializable {
     /**
      * 密码 返回前端忽略
      */
-    @JsonIgnore
+//    @JsonIgnore
     private String password;
 
     /**

+ 2 - 0
service/src/main/java/com/kym/service/admin/AdminUserService.java

@@ -37,4 +37,6 @@ public interface AdminUserService extends IService<AdminUser> {
     void updateRole(long userId, List<Long> roleIdList);
 
     void updateAdminUser(AdminUserVo adminUserVo);
+
+    PageBean<?> listUser(CommonQueryParam params);
 }

+ 20 - 5
service/src/main/java/com/kym/service/admin/impl/AdminUserServiceImpl.java

@@ -117,6 +117,7 @@ public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser
         var adminUser = new AdminUser();
         adminUser.setId(IDGenerator.INS().nextId());
         adminUser.setPassword(password);
+        adminUser.setCreateTime(LocalDateTime.now());
         BeanUtils.copyProperties(adminUserVo, adminUser);
         save(adminUser);
         // 角色和权限
@@ -131,7 +132,7 @@ public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser
         AdminUser adminUser = baseMapper.selectById(id);
         adminUser.setPassword(null);
         List<AdminUserRole> userRoleList = adminUserRoleService.lambdaQuery().eq(AdminUserRole::getAdminUserId, id).list();
-        return Map.of("adminUser",adminUser,"roles",userRoleList);
+        return Map.of("adminUser", adminUser, "roles", userRoleList);
     }
 
 
@@ -139,12 +140,12 @@ public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser
     @Override
     public void updateRole(long userId, List<Long> roleIdList) {
         QueryWrapper wrapper = new QueryWrapper();
-        wrapper.eq("admin_user_id",userId);
+        wrapper.eq("admin_user_id", userId);
         adminUserRoleService.remove(wrapper);
-        if(CommUtil.isEmptyOrNull(roleIdList)){
+        if (CommUtil.isEmptyOrNull(roleIdList)) {
             return;
         }
-        roleIdList.forEach(roleId->{
+        roleIdList.forEach(roleId -> {
             AdminUserRole roleEntity = new AdminUserRole();
             roleEntity.setAdminUserId(userId);
             roleEntity.setRoleId(roleId);
@@ -158,7 +159,21 @@ public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser
     @Override
     public void updateAdminUser(AdminUserVo adminUserVo) {
         AdminUser entity = new AdminUser();
-        BeanUtil.copyProperties(adminUserVo,entity);
+        BeanUtil.copyProperties(adminUserVo, entity);
         baseMapper.updateById(entity);
     }
+
+    @Override
+    public PageBean<?> listUser(CommonQueryParam params) {
+        PageHelper.startPage(params.getPageNum(), params.getPageSize());
+        List<AdminUser> list = lambdaQuery()
+                .like(CommUtil.isNotEmptyAndNull(params.getUsername()), AdminUser::getUsername, params.getUsername())
+                .like(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), AdminUser::getMobilePhone, params.getMobilePhone())
+                .like(CommUtil.isNotEmptyAndNull(params.getStatus()), AdminUser::getStatus, params.getStatus())
+                .list();
+        if (CommUtil.isNotEmptyAndNull(list)) {
+            list.forEach(user -> user.setPassword(null));
+        }
+        return new PageBean<>(list);
+    }
 }

+ 2 - 0
service/src/main/java/com/kym/service/miniapp/impl/AttachmentServiceImpl.java

@@ -3,6 +3,7 @@ package com.kym.service.miniapp.impl;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.UUID;
+import com.baomidou.dynamic.datasource.annotation.DS;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.kym.common.utils.CommUtil;
@@ -27,6 +28,7 @@ import java.util.Objects;
  * @author zuy
  * @since 2023-08-12
  */
+@DS("db-miniapp")
 @Service
 public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachment> implements AttachmentService {
 

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/impl/InvoiceServiceImpl.java

@@ -50,7 +50,7 @@ public class InvoiceServiceImpl extends ServiceImpl<InvoiceMapper, Invoice> impl
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getElecMoney).sum())    // 电费
                 .setElecMoney(orders.stream().mapToInt(ChargeOrder::getServiceMoney).sum())    // 服务费
                 .setTotalPower(orders.stream().mapToDouble(ChargeOrder::getTotalPower).sum())   // 总电量
-                .setInvoiceType(params.getInvoiceType())    // 类型
+                .setInvoiceType(String.valueOf(params.getInvoiceType()))    // 类型
                 .setInvoiceTitle(params.getInvoiceTitle())  //抬头
                 .setTaxId(params.getTaxId())    // 税号
                 .setAddress(params.getAddress())    // 公司地址