ydmyzx 5 сар өмнө
parent
commit
9c9163e68c

BIN
public/home.png


+ 65 - 0
src/components/Highlight/src/Highlight.vue

@@ -0,0 +1,65 @@
+<script lang="tsx">
+import { defineComponent, PropType, computed, h, unref } from 'vue'
+import { propTypes } from '@/utils/propTypes'
+
+export default defineComponent({
+  name: 'Highlight',
+  props: {
+    tag: propTypes.string.def('span'),
+    keys: {
+      type: Array as PropType<string[]>,
+      default: () => []
+    },
+    color: propTypes.string.def('var(--el-color-primary)')
+  },
+  emits: ['click'],
+  setup(props, { emit, slots }) {
+    const keyNodes = computed(() => {
+      return props.keys.map((key) => {
+        return h(
+          'span',
+          {
+            onClick: () => {
+              emit('click', key)
+            },
+            style: {
+              color: props.color,
+              cursor: 'pointer'
+            }
+          },
+          key
+        )
+      })
+    })
+
+    const parseText = (text: string) => {
+      props.keys.forEach((key, index) => {
+        const regexp = new RegExp(key, 'g')
+        text = text.replace(regexp, `{{${index}}}`)
+      })
+      return text.split(/{{|}}/)
+    }
+
+    const renderText = () => {
+      if (!slots?.default) return null
+      const node = slots?.default()[0].children
+
+      if (!node) {
+        return slots?.default()[0]
+      }
+
+      const textArray = parseText(node as string)
+      const regexp = /^[0-9]*$/
+      const nodes = textArray.map((t) => {
+        if (regexp.test(t)) {
+          return unref(keyNodes)[t] || t
+        }
+        return t
+      })
+      return h(props.tag, nodes)
+    }
+
+    return () => renderText()
+  }
+})
+</script>

+ 3 - 0
src/plugins/vueI18n/helper.ts

@@ -0,0 +1,3 @@
+export const setHtmlPageLang = (locale: LocaleType) => {
+  document.querySelector('html')?.setAttribute('lang', locale)
+}

+ 117 - 0
src/utils/is.ts

@@ -0,0 +1,117 @@
+// copy to vben-admin
+
+const toString = Object.prototype.toString
+
+export const is = (val: unknown, type: string) => {
+  return toString.call(val) === `[object ${type}]`
+}
+
+export const isDef = <T = unknown>(val?: T): val is T => {
+  return typeof val !== 'undefined'
+}
+
+export const isUnDef = <T = unknown>(val?: T): val is T => {
+  return !isDef(val)
+}
+
+export const isObject = (val: any): val is Record<any, any> => {
+  return val !== null && is(val, 'Object')
+}
+
+export const isEmpty = <T = unknown>(val: T): val is T => {
+  if (val === null) {
+    return true
+  }
+  if (isArray(val) || isString(val)) {
+    return val.length === 0
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0
+  }
+
+  return false
+}
+
+export const isDate = (val: unknown): val is Date => {
+  return is(val, 'Date')
+}
+
+export const isNull = (val: unknown): val is null => {
+  return val === null
+}
+
+export const isNullAndUnDef = (val: unknown): val is null | undefined => {
+  return isUnDef(val) && isNull(val)
+}
+
+export const isNullOrUnDef = (val: unknown): val is null | undefined => {
+  return isUnDef(val) || isNull(val)
+}
+
+export const isNumber = (val: unknown): val is number => {
+  return is(val, 'Number')
+}
+
+export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
+  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
+}
+
+export const isString = (val: unknown): val is string => {
+  return is(val, 'String')
+}
+
+export const isFunction = (val: unknown): val is Function => {
+  return typeof val === 'function'
+}
+
+export const isBoolean = (val: unknown): val is boolean => {
+  return is(val, 'Boolean')
+}
+
+export const isRegExp = (val: unknown): val is RegExp => {
+  return is(val, 'RegExp')
+}
+
+export const isArray = (val: any): val is Array<any> => {
+  return val && Array.isArray(val)
+}
+
+export const isWindow = (val: any): val is Window => {
+  return typeof window !== 'undefined' && is(val, 'Window')
+}
+
+export const isElement = (val: unknown): val is Element => {
+  return isObject(val) && !!val.tagName
+}
+
+export const isMap = (val: unknown): val is Map<any, any> => {
+  return is(val, 'Map')
+}
+
+export const isServer = typeof window === 'undefined'
+
+export const isClient = !isServer
+
+export const isUrl = (path: string): boolean => {
+  const reg =
+    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
+  return reg.test(path)
+}
+
+export const isDark = (): boolean => {
+  return window.matchMedia('(prefers-color-scheme: dark)').matches
+}
+
+// 是否是图片链接
+export const isImgPath = (path: string): boolean => {
+  return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
+}
+
+export const isEmptyVal = (val: any): boolean => {
+  return val === '' || val === null || val === undefined
+}

+ 73 - 0
src/views/infra/job/JobDetail.vue

@@ -0,0 +1,73 @@
+<template>
+  <Dialog v-model="dialogVisible" title="任务详细" width="700px">
+    <el-descriptions :column="1" border>
+      <el-descriptions-item label="任务编号" min-width="60">
+        {{ detailData.id }}
+      </el-descriptions-item>
+      <el-descriptions-item label="任务名称">
+        {{ detailData.name }}
+      </el-descriptions-item>
+      <el-descriptions-item label="任务名称">
+        <dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="detailData.status" />
+      </el-descriptions-item>
+      <el-descriptions-item label="处理器的名字">
+        {{ detailData.handlerName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="处理器的参数">
+        {{ detailData.handlerParam }}
+      </el-descriptions-item>
+      <el-descriptions-item label="Cron 表达式">
+        {{ detailData.cronExpression }}
+      </el-descriptions-item>
+      <el-descriptions-item label="重试次数">
+        {{ detailData.retryCount }}
+      </el-descriptions-item>
+      <el-descriptions-item label="重试间隔">
+        {{ detailData.retryInterval + ' 毫秒' }}
+      </el-descriptions-item>
+      <el-descriptions-item label="监控超时时间">
+        {{ detailData.monitorTimeout > 0 ? detailData.monitorTimeout + ' 毫秒' : '未开启' }}
+      </el-descriptions-item>
+      <el-descriptions-item label="后续执行时间">
+        <el-timeline>
+          <el-timeline-item
+            v-for="(nextTime, index) in nextTimes"
+            :key="index"
+            :timestamp="formatDate(nextTime)"
+          >
+            第 {{ index + 1 }} 次
+          </el-timeline-item>
+        </el-timeline>
+      </el-descriptions-item>
+    </el-descriptions>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE } from '@/utils/dict'
+import { formatDate } from '@/utils/formatTime'
+import * as JobApi from '@/api/infra/job'
+
+defineOptions({ name: 'InfraJobDetail' })
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const detailLoading = ref(false) // 表单的加载中
+const detailData = ref({} as JobApi.JobVO) // 详情数据
+const nextTimes = ref([]) // 下一轮执行时间的数组
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  dialogVisible.value = true
+  // 查看,设置数据
+  if (id) {
+    detailLoading.value = true
+    try {
+      detailData.value = await JobApi.getJob(id)
+      // 获取下一次执行时间
+      nextTimes.value = await JobApi.getJobNextTimes(id)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 137 - 0
src/views/infra/job/JobForm.vue

@@ -0,0 +1,137 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="任务名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入任务名称" />
+      </el-form-item>
+      <el-form-item label="处理器的名字" prop="handlerName">
+        <el-input
+          :readonly="formData.id !== undefined"
+          v-model="formData.handlerName"
+          placeholder="请输入处理器的名字"
+        />
+      </el-form-item>
+      <el-form-item label="处理器的参数" prop="handlerParam">
+        <el-input v-model="formData.handlerParam" placeholder="请输入处理器的参数" />
+      </el-form-item>
+      <el-form-item label="CRON 表达式" prop="cronExpression">
+        <crontab v-model="formData.cronExpression" />
+      </el-form-item>
+      <el-form-item label="重试次数" prop="retryCount">
+        <el-input
+          v-model="formData.retryCount"
+          placeholder="请输入重试次数。设置为 0 时,不进行重试"
+        />
+      </el-form-item>
+      <el-form-item label="重试间隔" prop="retryInterval">
+        <el-input
+          v-model="formData.retryInterval"
+          placeholder="请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔"
+        />
+      </el-form-item>
+      <el-form-item label="监控超时时间" prop="monitorTimeout">
+        <el-input v-model="formData.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as JobApi from '@/api/infra/job'
+
+defineOptions({ name: 'JobForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  handlerName: '',
+  handlerParam: '',
+  cronExpression: '',
+  retryCount: undefined,
+  retryInterval: undefined,
+  monitorTimeout: undefined
+})
+const formRules = reactive({
+  name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+  handlerName: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }],
+  cronExpression: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }],
+  retryCount: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }],
+  retryInterval: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await JobApi.getJob(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交按钮 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as JobApi.JobVO
+    if (formType.value === 'create') {
+      await JobApi.createJob(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await JobApi.updateJob(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    handlerName: '',
+    handlerParam: '',
+    cronExpression: '',
+    retryCount: undefined,
+    retryInterval: undefined,
+    monitorTimeout: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

BIN
src/views/mall/promotion/kefu/components/asserts/hongxin.png


BIN
src/views/mall/promotion/kefu/components/asserts/huaixiao.png


BIN
src/views/mall/promotion/kefu/components/asserts/jingkong.png


BIN
src/views/mall/promotion/kefu/components/asserts/jingshu.png


BIN
src/views/mall/promotion/kefu/components/asserts/jingya.png


BIN
src/views/mp/menu/assets/iphone_backImg.png