3 Commits 5f6bf0b94d ... 5c66b1cbee

Author SHA1 Message Date
  XueNing 5c66b1cbee 升级@fs-admin/core 6 months ago
  XueNing 422509389d 修改citySelect 单选问题 6 months ago
  XueNing 17de638448 合并组件 6 months ago

+ 4 - 0
index.html

@@ -6,6 +6,10 @@
     <link rel="icon" href="/logo.png" />
     <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
     <title></title>
+
+    <!-- 如需使用百度地图,打开下方注释 -->
+    <!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=应用ak"></script>
+    <script type="text/javascript" src="//bj.bcebos.com/v1/mapopen/github/BMapGLLib/Lushu/src/Lushu.min.js"></script> -->
   </head>
   <body>
     <div id="app"></div>

+ 3 - 2
package.json

@@ -14,8 +14,9 @@
     "micro": "plop micro"
   },
   "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
     "@element-plus/icons-vue": "^2.3.1",
-    "@fskj-admin/core": "^1.2.14",
+    "@fskj-admin/core": "^1.3.4",
     "@fskj-admin/micro": "^0.1.0",
     "@icon-park/vue-next": "^1.4.2",
     "@sentry/vue": "^8.9.2",
@@ -67,4 +68,4 @@
     "vite-plugin-vue-devtools": "^7.3.1",
     "vue-tsc": "^2.0.21"
   }
-}
+}

+ 13 - 14
pnpm-lock.yaml

@@ -8,12 +8,15 @@ importers:
 
   .:
     dependencies:
+      '@amap/amap-jsapi-loader':
+        specifier: ^1.0.1
+        version: 1.0.1
       '@element-plus/icons-vue':
         specifier: ^2.3.1
         version: 2.3.1(vue@3.4.29(typescript@5.3.3))
       '@fskj-admin/core':
-        specifier: ^1.2.14
-        version: 1.2.14
+        specifier: ^1.3.4
+        version: 1.3.4
       '@fskj-admin/micro':
         specifier: ^0.1.0
         version: 0.1.0
@@ -162,6 +165,9 @@ importers:
 
 packages:
 
+  '@amap/amap-jsapi-loader@1.0.1':
+    resolution: {integrity: sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==}
+
   '@ampproject/remapping@2.3.0':
     resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
     engines: {node: '>=6.0.0'}
@@ -535,8 +541,8 @@ packages:
   '@floating-ui/utils@0.2.2':
     resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==}
 
-  '@fskj-admin/core@1.2.14':
-    resolution: {integrity: sha512-L/W/b4Ik81iOZSYdN0YExw3wCLq16d4NsggeZGHKFRzRLKBhKTD8GVNCf+yNYMJSZH6tlXRW2IXoc/fPfU67pA==}
+  '@fskj-admin/core@1.3.4':
+    resolution: {integrity: sha512-xb1rOIY5n8Qjm/ATvXqmd1LlQN4RxzQoFAv3IW/Y2pJ3xEsSAjOnJkUeYcnw9hC3fNOXMOwaf5kocBp0E1PyNA==}
 
   '@fskj-admin/micro@0.1.0':
     resolution: {integrity: sha512-4Dbg7mubKcDu+jaqll+oY6/7v08h9mh9BTncVkEHd9T4Aqg8ZzcCASaKvrCPhP6/ldgUBrM0MimmzJh0geVXnQ==}
@@ -655,55 +661,46 @@ packages:
     resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==}
     cpu: [arm]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.18.0':
     resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==}
     cpu: [arm]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.18.0':
     resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==}
     cpu: [arm64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.18.0':
     resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==}
     cpu: [arm64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.18.0':
     resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==}
     cpu: [ppc64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.18.0':
     resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==}
     cpu: [riscv64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.18.0':
     resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==}
     cpu: [s390x]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.18.0':
     resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.18.0':
     resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.18.0':
     resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==}
@@ -4223,6 +4220,8 @@ packages:
 
 snapshots:
 
+  '@amap/amap-jsapi-loader@1.0.1': {}
+
   '@ampproject/remapping@2.3.0':
     dependencies:
       '@jridgewell/gen-mapping': 0.3.5
@@ -4620,7 +4619,7 @@ snapshots:
 
   '@floating-ui/utils@0.2.2': {}
 
-  '@fskj-admin/core@1.2.14': {}
+  '@fskj-admin/core@1.3.4': {}
 
   '@fskj-admin/micro@0.1.0': {}
 

+ 4 - 6
src/components.d.ts

@@ -17,16 +17,14 @@ declare module 'vue' {
     ElEmployees: typeof import('./components/form/ElEmployees.vue')['default']
     Exception: typeof import('./components/Exception.vue')['default']
     FsCheckCard: typeof import('./components/FsCheckCard/index.vue')['default']
-    FsCity: typeof import('./components/FsCity/index.vue')['default']
+    FsCity: (typeof import('./components/FsCity/index.vue'))['default']
     FsCitySelect: typeof import('./components/FsCitySelect/index.vue')['default']
-    FsDot: typeof import('./components/FsDot/index.vue')['default']
-    FsImageUpload: typeof import('./components/FsImageUpload/index.vue')['default']
-    FsMap: typeof import('./components/FsMap/index.vue')['default']
+    FsImageUpload: (typeof import('./components/FsImageUpload/index.vue'))['default']
+    FsMap: (typeof import('./components/FsMap/index.vue'))['default']
     FsMapPicker: typeof import('./components/FsMapPicker/index.vue')['default']
     FsPrinter: typeof import('./components/FsPrinter/index.vue')['default']
-    FsSplitPanel: typeof import('./components/FsSplitPanel/index.vue')['default']
     FsTableSelect: typeof import('./components/FsTableSelect/index.vue')['default']
-    FsText: typeof import('./components/FsText/index.vue')['default']
+    FsText: (typeof import('./components/FsText/index.vue'))['default']
     FsTour: typeof import('./components/FsTour/index.vue')['default']
     GlobalAside: typeof import('./components/core/GlobalAside.vue')['default']
     GlobalFooter: typeof import('./components/core/GlobalFooter.vue')['default']

+ 1 - 5
src/components/FsCitySelect/index.vue

@@ -74,11 +74,7 @@ getData()
 
 /* 修改modelValue */
 const updateModelValue = (modelValue: any) => {
-  let value = ''
-  if (Array.isArray(modelValue)) {
-    // 多选情况利用set去重
-    value = [...new Set([...modelValue])].join(',')
-  }
+  const value = Array.isArray(modelValue) ? [...new Set([...modelValue])].join(',') : modelValue
   emits('update:modelValue', value)
 }
 

+ 0 - 76
src/components/FsDot/index.vue

@@ -1,76 +0,0 @@
-<script setup lang="ts">
-import type { TextProps } from './props'
-
-const props = withDefaults(defineProps<TextProps>(), {
-  type: 'primary',
-  ripple: true,
-  size: '8px'
-})
-
-const bgColor = computed(() => {
-  return props.color || `var(--el-color-${props.type})`
-})
-</script>
-
-<!-- 状态点 -->
-<template>
-  <span :class="['dot', { 'is-ripple': ripple }]">
-    <span class="dot-status" :style="{ width: size, height: size }">
-      <span class="dot-ripple" :style="{ width: size, height: size }"></span>
-    </span>
-    <span v-if="text" class="dot-text" :style="fontStyle"> {{ text }}</span>
-  </span>
-</template>
-
-<style scoped lang="scss">
-.dot {
-  display: inline-flex;
-  align-items: center;
-
-  .dot-text {
-    flex-shrink: 0;
-    margin-left: 8px;
-  }
-
-  .dot-status {
-    flex-shrink: 0;
-    background: var(--el-color-primary);
-    border-radius: 50%;
-    position: relative;
-  }
-
-  .dot-ripple {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background: var(--el-color-primary);
-    animation: animDot 1.2s ease-in-out infinite;
-    transform-origin: center;
-    border-radius: 50%;
-    display: none;
-  }
-
-  &.is-ripple .dot-ripple {
-    display: block;
-  }
-
-  .dot-status,
-  .dot-ripple {
-    background: v-bind('bgColor');
-  }
-}
-
-@keyframes animDot {
-  from {
-    transform: scale(0.8);
-    opacity: 0.6;
-  }
-
-  to {
-    transform: scale(2.4);
-    opacity: 0;
-  }
-}
-</style>

+ 0 - 16
src/components/FsDot/props.ts

@@ -1,16 +0,0 @@
-import type { StyleValue } from 'vue'
-
-export interface TextProps {
-  //  类型
-  type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
-  // 自定义颜色
-  color?: string
-  // 是否显示水波动画
-  ripple?: boolean
-  // 文本
-  text?: string
-  // 尺寸
-  size?: string
-  // 文字样式
-  fontStyle?: StyleValue
-}

+ 0 - 310
src/components/FsImageUpload/index.vue

@@ -1,310 +0,0 @@
-<script setup lang="ts">
-import { ElMessage } from 'element-plus'
-import type { ImageUploadProps } from './props'
-import { imageUploadEmits } from './props'
-import type { UploadItem } from './types'
-import { uuid } from '@/utils/utils'
-import axios, { type AxiosProgressEvent } from 'axios'
-
-const emits = defineEmits(imageUploadEmits)
-
-const props = withDefaults(defineProps<ImageUploadProps>(), {
-  fileName: 'file',
-  autoUpload: true,
-  preview: true,
-  limit: 9,
-  fileSize: 5,
-  iconSize: 28
-})
-
-const images = ref<UploadItem[]>([])
-
-// 是否可上传
-const isUpload = computed<boolean>(() => {
-  return (
-    !props.disabled &&
-    !(typeof props.limit === 'number' && props.limit > 0 && images.value != null && images.value.length >= props.limit)
-  )
-})
-
-// 检查图片是否全部上传完毕
-const checkUpload = () => {
-  return images.value.length == 0 || (images.value.length && !images.value.some(x => x.status != 'success'))
-}
-
-// 预览图片列表
-const previewList = computed(() => {
-  if (!props.preview) {
-    return []
-  }
-  return images.value?.map(x => x.url) || []
-})
-
-/* 选择文件 */
-const onUpload = (file: File) => {
-  if (!isUpload.value || props.disabled) {
-    return false
-  }
-  if (!file.type.startsWith('image')) {
-    ElMessage.error('只能选择图片')
-    return false
-  }
-  if (file.size / 1024 / 1024 > props.fileSize) {
-    ElMessage.error(`大小不能超过 ${props.fileSize}MB`)
-    return false
-  }
-
-  const item: UploadItem = {
-    key: uuid(),
-    name: file.name,
-    status: void 0,
-    progress: 0,
-    file
-  }
-  item.url = window.URL.createObjectURL(file)
-
-  images.value.push(item)
-  // 是否自动上传
-  if (props.autoUpload) {
-    // 自动上传最后一个文件
-    uploadItem(images.value.at(-1) as UploadItem)
-  }
-  return false
-}
-
-/* 上传文件 */
-const uploadItem = (item: UploadItem) => {
-  if (!props.action && typeof props.uploadFunction != 'function') {
-    console.log('请传入action路径或者uploadFunction上传方法')
-    return false
-  }
-  // 如果配置了上传路径,TODO:awint
-  if (props.action) {
-    const formData = new FormData()
-    formData.append(props.fileName, item.file as File)
-    axios
-      .post(props.action, formData, {
-        onUploadProgress: (e: AxiosProgressEvent) => {
-          if (e.total != null) {
-            item.progress = (e.loaded / e.total) * 100
-          }
-        }
-      })
-      .then(res => {
-        item.status = 'success'
-        item.url = res.data
-      })
-      .catch(() => {
-        item.status = 'danger'
-      })
-  } else {
-    // 自定义方法上传
-    if (typeof props.uploadFunction != 'function') return false
-    ;(props.uploadFunction as Function)(item)
-  }
-}
-
-/* 手动上传文件 */
-const submit = () => {
-  if (images.value.length) {
-    images.value.forEach(item => {
-      item.status != 'success' && uploadItem(item)
-    })
-  }
-}
-
-/* 删除图片 */
-const onRemove = (index: number) => {
-  emits('remove', images.value[index])
-  images.value.splice(index, 1)
-}
-
-/* 重新上传 */
-const onRetry = (index: number) => {
-  uploadItem(images.value[index])
-}
-
-/* 修改modelValue */
-const updateModelValue = (items: any) => {
-  emits('update:modelValue', items.map((x: UploadItem) => x.url).join(',') || '')
-}
-
-watch(
-  () => props.modelValue,
-  () => {
-    // 判断modelValue存不存在,存在就是成功,不存在就没有
-    if (props.modelValue && props.modelValue != undefined) {
-      // 传进来的数据转换成内部需要数据
-      const formatData = typeof props.modelValue === 'string' ? props.modelValue.split(',').filter(x => x) : []
-      const datas: UploadItem[] = formatData.map((x: string) => {
-        return {
-          key: uuid(),
-          url: x,
-          status: 'success',
-          name: x.split('/').pop()
-        }
-      })
-      images.value = datas
-    }
-  },
-  { immediate: true }
-)
-
-watch(
-  images,
-  () => {
-    updateModelValue(images.value)
-  },
-  {
-    deep: true
-  }
-)
-
-defineExpose({
-  checkUpload,
-  submit
-})
-</script>
-
-<template>
-  <div class="upload-container">
-    <div class="upload-image" v-for="(item, index) in images" :key="item.key">
-      <el-image :src="item.url" :preview-src-list="previewList" :initial-index="index" fit="cover"></el-image>
-      <div v-if="!disabled" class="upload-remove" @click.stop="onRemove(index)">
-        <el-icon size="14">
-          <Close />
-        </el-icon>
-      </div>
-      <div v-if="item.status === 'uploading' || item.status === 'danger'" class="upload-progress">
-        <slot name="progress" :item="item">
-          <div class="upload-text">
-            {{ item.status == 'danger' ? '上传失败' : '上传中' }}
-          </div>
-          <el-progress
-            :showText="false"
-            v-bind="progressProps || {}"
-            :percentage="item.progress"
-            :status="item.status === 'danger' ? 'exception' : void 0"
-          />
-          <div v-if="!disabled">
-            <div v-if="item.status === 'danger'" class="upload-retry" @click.stop="onRetry(index)">
-              <el-icon>
-                <Refresh />
-              </el-icon>
-            </div>
-          </div>
-        </slot>
-      </div>
-    </div>
-    <div>
-      <el-upload
-        action=""
-        accept="image/*"
-        :multiple="multiple"
-        :disabled="disabled"
-        :show-file-list="false"
-        :beforeUpload="onUpload"
-        :drag="drag"
-        v-if="isUpload"
-      >
-        <div class="upload-plus">
-          <el-icon :size="iconSize">
-            <Plus />
-          </el-icon>
-        </div>
-      </el-upload>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.upload-container {
-  display: flex;
-  flex-wrap: wrap;
-  .upload-image {
-    position: relative;
-    width: 100px;
-    height: 100px;
-    border: 1px dashed #dcdfe6;
-    border-radius: var(--el-border-radius-base);
-    overflow: hidden;
-    margin: 0px 8px 8px 0;
-    cursor: pointer;
-    .upload-remove {
-      width: 20px;
-      height: 20px;
-      display: flex;
-      justify-content: center;
-      align-items: center;
-      position: absolute;
-      right: 0px;
-      top: 0px;
-      background-color: rgba($color: #000000, $alpha: 0.3);
-      color: #fff;
-      border-radius: 0px 0px 0px var(--el-border-radius-round);
-      z-index: 9;
-      &:hover {
-        background-color: var(--el-color-danger);
-      }
-      .el-icon {
-        margin-top: -5px;
-        margin-left: 2px;
-      }
-    }
-    .upload-progress {
-      position: absolute;
-      width: 100%;
-      height: 100%;
-      left: 0px;
-      top: 0px;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      padding: 10px;
-      background-color: rgba($color: #000000, $alpha: 0.4);
-      color: #fff;
-      box-sizing: border-box;
-      .upload-text {
-        text-align: center;
-        margin-bottom: 3px;
-        font-size: 12px;
-      }
-      .upload-retry {
-        text-align: center;
-        margin-top: 5px;
-      }
-    }
-    .el-image {
-      width: 100%;
-      height: 100%;
-    }
-  }
-  .upload-plus {
-    width: 100px;
-    height: 100px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    color: var(--el-color-info);
-    border: 1px dashed #dcdfe6;
-    border-radius: var(--el-border-radius-base);
-    &:not(.disabled):hover {
-      border-color: var(--el-color-primary);
-    }
-  }
-  :deep(.el-upload-dragger) {
-    width: auto;
-    height: auto;
-    padding: 0px;
-    margin: 0px;
-    border-color: transparent;
-    border-width: 0px;
-    border-radius: 0px;
-    background-color: transparent;
-    &.is-dragover {
-      outline: 1px dashed var(--el-color-primary);
-      background-color: var(--el-color-primary-light-9);
-    }
-  }
-}
-</style>

+ 0 - 47
src/components/FsImageUpload/props.ts

@@ -1,47 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import type { CSSProperties } from 'vue'
-import type { UploadItem } from './types'
-import type { ProgressProps } from 'element-plus'
-
-// 传入属性
-export interface ImageUploadProps {
-  // 已上传的列表
-  modelValue: string
-  // 上传路径
-  action?: string
-  fileName?: string
-  // 是否自动上传
-  autoUpload?: boolean
-  // 是否启用拖拽上传
-  drag?: boolean
-  // 是否禁用
-  disabled?: boolean
-  // 是否点击预览
-  preview?: boolean
-  // 上传数量
-  limit?: number
-  // 是否可以多选
-  multiple?: boolean
-  // 文件大小限制(MB)
-  fileSize?: number
-  // 宽度
-  width?: number | string
-  // 高度
-  height?: number | string
-  // 图标大小
-  iconSize?: number
-  // 进度条配置
-  progressProps?: ProgressProps
-  // 上传方法
-  uploadFunction?: (file: any) => Promise<any>
-}
-
-/* 事件 */
-export const imageUploadEmits = {
-  // 上传事件
-  upload: (_value: UploadItem) => true,
-  // 单个删除事件
-  remove: (_value: UploadItem) => true,
-  // 修改modelValue
-  'update:modelValue': (_value: UploadItem[]) => true
-}

+ 0 - 20
src/components/FsImageUpload/types/index.ts

@@ -1,20 +0,0 @@
-export interface UploadItem extends Record<keyof any, any> {
-  // 唯一标识
-  key: string | number | symbol
-  // 显示的图片地址
-  url?: string
-  // 文件名称
-  name?: string
-  // 上传状态
-  status?: UploadStatus
-  // 上传进度
-  progress?: number
-  /**  */
-  // 上传文件
-  file?: File
-}
-
-/**
- * 上传状态
- */
-export type UploadStatus = 'uploading' | 'success' | 'danger' | null

+ 275 - 2
src/components/FsMapPicker/components/baidu.vue

@@ -1,13 +1,286 @@
 <script setup lang="ts">
+import { CircleCheck, Location, Plus, Search } from '@element-plus/icons-vue'
+import { ElMessage, type AutocompleteInstance } from 'element-plus'
+
+const ICON_CLASS = 'map-main-marker'
+
+const emits = defineEmits(['done'])
+
+const props = defineProps([
+  'modelValue',
+  'height',
+  'center',
+  'mapKey',
+  'markerSrc',
+  'zoom',
+  'selectedZoom',
+  'mapStyle',
+  'poiType',
+  'poiRadius',
+  'poiKeywords',
+  'okText',
+  'required',
+  'message',
+  'suggestionCity'
+])
+
+let mapIns: any = null
+// 地图容器
+const mapRef = ref<any>()
+const autocompleteRef = ref<AutocompleteInstance>()
+
+// 是否是poi列表点击移动
+const isItemClickMove = ref<boolean>(false)
+// 图标样式
+const centerIconClass = ref<Array<string>>([ICON_CLASS])
+
+// 当前选中
+const current = ref<any>(null)
+
+// 是否加载中
+const loading = ref<boolean>(false)
+
+const poiData = ref<any[]>([])
+
+// 关键字
+const keywords = ref<string>('')
+// 最后一次搜索关键字
+const lastKeywords = ref<any>(null)
+
+// 关键字搜索数据
+const suggestionData = ref<any[]>([])
+
 onMounted(() => {
   initMap()
 })
 /* 初始化地图 */
-const initMap = () => {}
+const initMap = () => {
+  mapIns = new BMapGL.Map(mapRef.value) // 创建Map实例
+  mapIns.centerAndZoom(new BMapGL.Point(props.center[0], props.center[1]), props.zoom) // 初始化地图,设置中心点坐标和地图级别
+  mapIns.enableScrollWheelZoom(true) //开启鼠标滚轮缩放
+  if (props.mapStyle) {
+    mapIns.setMapStyleV2({ styleId: props.mapStyle })
+  }
+
+  mapIns.on('moveend', () => {
+    if (isItemClickMove.value) {
+      isItemClickMove.value = false
+      return
+    }
+    bounceIcon()
+    const { lng, lat } = mapIns.getCenter()
+    searchPOI(lng, lat)
+  })
+
+  // 第一次加载
+  searchPOI(props.center[0], props.center[1])
+}
+
+/* poi 搜索 */
+const searchPOI = (lng: number, lat: number) => {
+  loading.value = true
+  current.value = null
+  searchNearBy(lng, lat)
+    .then(res => {
+      loading.value = false
+      poiData.value = res
+    })
+    .catch(() => {
+      loading.value = false
+    })
+}
+
+/* 设置地图中心点 */
+const setMapCenter = (lng: number, lat: number, zoom: number) => {
+  if (lng && lat) {
+    if (zoom == null) {
+      mapIns.setCenter([lng, lat])
+    } else {
+      mapIns.flyTo(new BMapGL.Point(lng, lat), zoom)
+    }
+  }
+}
+
+/* poi 点击 */
+const handleItemClick = (item: any) => {
+  const { lng, lat } = item
+  setMapCenter(lng, lat, props.selectedZoom)
+  isItemClickMove.value = true
+  current.value = item
+}
+
+/* 让地图图标跳动 */
+const bounceIcon = () => {
+  nextTick(() => {
+    centerIconClass.value = [ICON_CLASS]
+    setTimeout(() => {
+      centerIconClass.value = [ICON_CLASS, 'map-main-marker-bounce']
+    }, 0)
+  })
+}
+
+/* 确认选择 */
+const handleConfirm = () => {
+  // 是否强制选择
+  if (props.required && !current.value) {
+    ElMessage.error(props.message)
+    return
+  }
+  // 如果右侧不选择,则返回当前中心点
+  const { lng, lat } = mapIns.getCenter()
+  emits('done', current.value || { lng, lat })
+}
+
+/* 关键字搜索 */
+const handleSelect = (keyword: string, callback?: any) => {
+  if (!keyword || lastKeywords.value === keyword) {
+    callback && callback(suggestionData.value)
+    return
+  }
+  lastKeywords.value = keyword
+  searchKeywords(keyword)
+    .then(result => {
+      suggestionData.value = result
+      callback && callback(suggestionData.value)
+    })
+    .catch((e: Error) => {
+      console.error(e)
+      callback && callback(suggestionData.value)
+    })
+}
+
+/* 关键字搜索选中 */
+const handleSelectSelect = (item: any) => {
+  autocompleteRef.value && autocompleteRef.value.blur()
+  const { lng, lat } = item
+  setMapCenter(lng, lat, props.selectedZoom)
+  bounceIcon()
+}
+
+/* 检索附近兴趣点 */
+const searchNearBy = (lng: number, lat: number): Promise<any[]> => {
+  return new Promise((resolve, reject) => {
+    jsonp(
+      `https://api.map.baidu.com/place/v2/search?query=${props.poiKeywords}&location=${lat},${lng}&radius=${props.poiRadius}&page_size=20&output=json&ak=${props.mapKey}`,
+      (result: any) => {
+        if (result.status === 0) {
+          resolve(result.results.map((d: any) => formatPoi(d)))
+        } else {
+          reject(new Error(result.message))
+          return
+        }
+      }
+    )
+  })
+}
+
+// 关键字检索
+const searchKeywords = (keyword: string): Promise<any[]> => {
+  return new Promise((resolve, reject) => {
+    jsonp(
+      `https://api.map.baidu.com/place/v2/suggestion?query=${keyword}&region=${props.suggestionCity}&city_limit=true&output=json&ak=${props.mapKey}`,
+      (res: any) => {
+        if (res.status === 0) {
+          const tips = (res.result as any[]).filter(d => !!d.location)
+          resolve(tips.map(d => formatPoi(d)))
+        } else {
+          reject(new Error(res.message))
+        }
+      }
+    )
+  })
+}
+
+const jsonp = (url: string, callback: Function) => {
+  // 创建一个随机的回调函数名,避免命名冲突
+  const callbackFuncName = 'jsonp_callback_' + Math.round(100000 * Math.random())
+  // 将回调函数名添加到 URL 的查询参数中
+  url += (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + callbackFuncName
+  // 创建一个全局的回调函数
+  ;(window as any)[callbackFuncName] = function (data: any) {
+    // 回调函数执行后,删除全局函数并释放资源
+    delete (window as any)[callbackFuncName]
+    document.body.removeChild(script)
+
+    // 调用传入的回调函数处理数据
+    callback(data)
+  }
+  // 创建一个 script 标签
+  const script = document.createElement('script')
+  script.src = url
+
+  // 将 script 标签插入到页面中,发起 JSONP 请求
+  document.body.appendChild(script)
+}
+
+/* 格式化返回的点位 */
+const formatPoi = (item: any) => {
+  return {
+    ...item,
+    lng: item.location.lng,
+    lat: item.location.lat
+  }
+}
+
+onBeforeUnmount(() => {
+  /* 销毁地图 */
+  mapIns && mapIns.destroy()
+  mapIns = null
+  current.value = null
+  loading.value = false
+  poiData.value = []
+})
 </script>
 
 <template>
-  <div></div>
+  <div class="map-container">
+    <div class="map-main">
+      <div ref="mapRef" :style="{ height }"></div>
+      <el-icon class="map-icon-plus" color="var(--el-color-primary)">
+        <Plus />
+      </el-icon>
+      <img :src="markerSrc" :class="centerIconClass" />
+      <el-autocomplete
+        v-model="keywords"
+        valueKey="name"
+        :clearable="true"
+        :fetch-suggestions="handleSelect"
+        placeholder="请输入关键字进行搜索"
+        @select="handleSelectSelect"
+        class="map-keywrod-input"
+        ref="autocompleteRef"
+      >
+        <template #prefix>
+          <el-icon class="el-input__icon">
+            <Search />
+          </el-icon>
+        </template>
+      </el-autocomplete>
+    </div>
+    <div class="map-poi-side">
+      <div class="map-poi-list" v-loading="loading">
+        <div class="map-poi-item" v-for="item in poiData" :key="item.id" @click="handleItemClick(item)">
+          <div class="map-poi-icon">
+            <el-icon size="18">
+              <Location />
+            </el-icon>
+          </div>
+          <div class="map-poi-desc">
+            <div class="map-poi-title">{{ item.name }}</div>
+            <div class="map-poi-address">{{ item.address }}</div>
+          </div>
+          <div class="map-poi-icon map-poi-icon-checked">
+            <el-icon size="18" color="var(--el-color-primary)" v-if="current === item">
+              <CircleCheck />
+            </el-icon>
+          </div>
+        </div>
+      </div>
+      <div class="map-poi-button">
+        <el-button type="primary" @click="handleConfirm" style="width: 100%">{{ okText }}</el-button>
+      </div>
+    </div>
+  </div>
 </template>
 
 <style scoped lang="scss"></style>

+ 3 - 2
src/components/FsMapPicker/index.vue

@@ -19,7 +19,8 @@ const props = withDefaults(defineProps<MapPickerProps>(), {
   zoom: 11,
   selectedZoom: 17,
   message: '请选择位置',
-  okText: '确定'
+  okText: '确定',
+  poiKeywords: ''
 })
 
 /* 处理选择完成事件 */
@@ -45,7 +46,7 @@ const updateModalValue = (modelValue: boolean) => {
       <!-- 高德地图 -->
       <Amap v-if="type === 'amap'" v-bind="props" :style="{ height }" @done="handleDone" />
       <!-- 百度地图 -->
-      <BaiDu v-else />
+      <BaiDu v-else v-bind="props" :style="{ height }" @done="handleDone" />
     </template>
   </el-dialog>
 </template>

+ 2 - 2
src/components/FsPrinter/index.vue

@@ -94,7 +94,7 @@ defineExpose({
           </td>
         </tr>
       </tbody>
-      <tfoot v-if="$slots.footer">
+      <!-- <tfoot v-if="$slots.footer">
         <tr>
           <td>
             <div class="custom-printer-footer" :style="footerStyle">
@@ -102,7 +102,7 @@ defineExpose({
             </div>
           </td>
         </tr>
-      </tfoot>
+      </tfoot> -->
     </table>
   </Teleport>
 </template>

+ 0 - 490
src/components/FsSplitPanel/index.vue

@@ -1,490 +0,0 @@
-<script setup lang="ts">
-import type { SplitPanelProps } from './props'
-import { splitPanelEmits } from './props'
-
-const props = withDefaults(defineProps<SplitPanelProps>(), {})
-const emits = defineEmits(splitPanelEmits)
-
-// 根节点
-const rootRef = ref<HTMLElement | null>(null)
-
-// 侧边容器节点
-const wrapRef = ref<HTMLElement | null>(null)
-
-// 侧边节点
-const sideRef = ref<HTMLElement | null>(null)
-
-// 是否折叠
-const isCollapse = ref<boolean>(false)
-
-// 拉伸后尺寸
-const resizedSize = ref<string | null>(null)
-
-// 是否正在拉伸
-const resizing = ref<boolean>(false)
-
-/* 切换折叠状态 */
-const toggleCollapse = (collapse?: boolean) => {
-  isCollapse.value = typeof collapse === 'boolean' ? collapse : !isCollapse.value
-  emits('update:collapse', isCollapse.value)
-}
-
-/* 获取最大拉伸尺寸 */
-const getMaxSize = (el: HTMLElement) => {
-  const size = props.vertical ? el.clientHeight : el.clientWidth
-  if (!props.maxSize) {
-    return size
-  }
-  if (props.maxSize < 0) {
-    // 负值形式
-    return size + props.maxSize
-  } else if (props.maxSize < 1) {
-    // 百分比形式
-    return Math.floor(size * props.maxSize)
-  }
-  return Math.min(props.maxSize, size)
-}
-
-/* 拉伸 */
-const onResize = (event: MouseEvent) => {
-  const rootEl = rootRef.value
-  const sideEl = sideRef.value
-  if (!rootEl || !sideEl) {
-    return
-  }
-  resizing.value = true
-  // 获取原始位置
-  const downX = event.clientX
-  const downY = event.clientY
-  const downW = sideEl.clientWidth
-  const downH = sideEl.clientHeight
-  const limitMin = props.minSize || 0
-  const limitMax = getMaxSize(rootEl)
-
-  // 鼠标移动事件
-  const mousemoveFn = (e: MouseEvent) => {
-    const size = props.vertical
-      ? (props.reverse ? downY - e.clientY : e.clientY - downY) + downH
-      : (props.reverse ? downX - e.clientX : e.clientX - downX) + downW
-    resizedSize.value = (size < limitMin ? limitMin : size > limitMax ? limitMax : size) + 'px'
-  }
-
-  // 鼠标抬起事件
-  const mouseupFn = () => {
-    resizing.value = false
-    document.removeEventListener('mousemove', mousemoveFn)
-    document.removeEventListener('mouseup', mouseupFn)
-  }
-
-  // 添加鼠标事件监听
-  document.addEventListener('mousemove', mousemoveFn)
-  document.addEventListener('mouseup', mouseupFn)
-}
-
-watch(
-  [() => props.collapse, () => props.allowCollapse],
-  () => {
-    if (!props.allowCollapse) {
-      isCollapse.value = false
-    } else {
-      isCollapse.value = props.collapse
-    }
-  },
-  { immediate: true }
-)
-</script>
-
-<template>
-  <div
-    ref="rootRef"
-    :class="[
-      'split-panel',
-      { 'is-reverse': reverse },
-      { 'is-vertical': vertical },
-      { 'is-collapse': isCollapse },
-      { 'is-resizing': resizing }
-    ]"
-    :style="{
-      '--split-size': resizedSize ?? size,
-      '--split-space': space
-    }"
-  >
-    <!-- 侧边容器 -->
-    <div ref="wrapRef" class="split-panel-wrap">
-      <div ref="sideRef" class="split-panel-side" :style="customStyle">
-        <slot></slot>
-      </div>
-      <!-- 间距 -->
-      <div class="split-panel-space">
-        <div v-if="resizable" class="split-resize-line" @mousedown="onResize"></div>
-      </div>
-    </div>
-    <!-- 内容 -->
-    <div class="split-panel-body" :style="bodyStyle">
-      <slot name="body" :collapse="isCollapse"></slot>
-    </div>
-    <!-- 折叠按钮 -->
-    <div v-if="allowCollapse" :style="collapseStyle" class="split-collapse-button" @click="toggleCollapse()">
-      <slot name="collapse" :collapse="isCollapse">
-        <ElIcon class="split-collapse-icon">
-          <ArrowUp v-if="vertical" />
-          <ArrowLeft v-else />
-        </ElIcon>
-      </slot>
-    </div>
-    <!-- 小屏幕遮罩层 -->
-    <div class="split-panel-mask" @click="toggleCollapse()"></div>
-  </div>
-</template>
-
-<style scoped lang="scss">
-.split-panel {
-  display: flex;
-  position: relative;
-  --split-size: 200px;
-  --split-space: 16px;
-
-  // 侧边容器
-  & > .split-panel-wrap {
-    flex-shrink: 0;
-    box-sizing: border-box;
-    width: calc(var(--split-size) + var(--split-space));
-    display: flex;
-    justify-content: flex-end;
-    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    opacity: 1;
-
-    // 侧边
-    & > .split-panel-side {
-      flex-shrink: 0;
-      width: var(--split-size);
-      border: 1px solid var(--el-border-color-light);
-      box-sizing: border-box;
-      position: relative;
-    }
-
-    // 间距
-    & > .split-panel-space {
-      flex-shrink: 0;
-      width: var(--split-space);
-      box-sizing: border-box;
-      position: relative;
-
-      // 拉伸线
-      .split-resize-line {
-        width: 12px;
-        height: 100%;
-        position: absolute;
-        left: -6px;
-        z-index: 4;
-        cursor: e-resize;
-
-        &::after {
-          content: '';
-          width: 3px;
-          height: 100%;
-          display: block;
-          margin: 0 auto;
-        }
-
-        &:hover::after {
-          background: var(--el-color-primary);
-        }
-      }
-    }
-  }
-
-  // 内容
-  & > .split-panel-body {
-    flex: 1;
-    overflow: auto;
-    box-sizing: border-box;
-    position: relative;
-  }
-
-  // 折叠按钮
-  & > .split-collapse-button {
-    width: 24px;
-    height: 24px;
-    line-height: 24px;
-    text-align: center;
-    position: absolute;
-    left: var(--split-size);
-    top: 50%;
-    margin: -12px 0 0 -12px;
-    background: var(--el-bg-color-overlay);
-    box-shadow: 0 2px 3px 0px rgba(0, 0, 0, 0.04);
-    border: 1px solid var(--el-border-color-light);
-    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    border-radius: 50%;
-    cursor: pointer;
-    z-index: 5;
-
-    .split-collapse-icon {
-      font-size: 16px;
-      vertical-align: -2px;
-      color: var(--el-color-info);
-      font-weight: bold;
-      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-      transform: scaleX(1);
-    }
-
-    &:hover .split-collapse-icon {
-      color: var(--el-color-primary);
-    }
-  }
-
-  // 折叠状态
-  &.is-collapse {
-    & > .split-panel-wrap {
-      width: 0 !important;
-      pointer-events: none;
-      opacity: 0;
-    }
-
-    & > .split-collapse-button {
-      left: 0;
-
-      .split-collapse-icon {
-        transform: scaleX(-1);
-      }
-    }
-  }
-
-  // 垂直
-  &.is-vertical {
-    flex-direction: column;
-
-    & > .split-panel-wrap {
-      flex-direction: column;
-      height: calc(var(--split-size) + var(--split-space));
-      width: auto;
-
-      & > .split-panel-side {
-        height: var(--split-size);
-        width: auto;
-      }
-
-      & > .split-panel-space {
-        height: var(--split-space);
-        width: auto;
-
-        .split-resize-line {
-          width: 100%;
-          height: 12px;
-          left: auto;
-          top: -6px;
-          cursor: n-resize;
-
-          &::after {
-            width: 100%;
-            height: 3px;
-            margin: 4px 0 0 0;
-          }
-        }
-      }
-    }
-
-    & > .split-collapse-button {
-      top: var(--split-size);
-      left: 50%;
-
-      .split-collapse-icon {
-        transform: scaleY(1);
-      }
-    }
-
-    &.is-collapse {
-      & > .split-panel-wrap {
-        width: auto !important;
-        height: 0 !important;
-      }
-
-      & > .split-collapse-button {
-        top: 0;
-
-        .split-collapse-icon {
-          transform: scaleY(-1);
-        }
-      }
-    }
-  }
-
-  // 反向
-  &.is-reverse {
-    flex-direction: row-reverse;
-
-    & > .split-panel-wrap {
-      flex-direction: row-reverse;
-
-      & > .split-panel-space .split-resize-line {
-        left: auto;
-        right: -6px;
-      }
-    }
-
-    & > .split-collapse-button {
-      left: auto;
-      right: var(--split-size);
-      margin: -12px -12px 0 0;
-
-      .split-collapse-icon {
-        transform: scaleX(-1);
-      }
-    }
-
-    &.is-collapse > .split-collapse-button {
-      right: 0;
-
-      .split-collapse-icon {
-        transform: scaleX(1);
-      }
-    }
-
-    &.is-vertical {
-      flex-direction: column-reverse;
-
-      & > .split-panel-wrap {
-        flex-direction: column-reverse;
-
-        & > .split-panel-space .split-resize-line {
-          top: auto;
-          right: auto;
-          bottom: -6px;
-        }
-      }
-
-      & > .split-collapse-button {
-        left: 50%;
-        top: auto;
-        bottom: var(--split-size);
-        margin: 0 0 -12px -12px;
-
-        .split-collapse-icon {
-          transform: scaleY(-1);
-        }
-      }
-
-      &.is-collapse > .split-collapse-button {
-        bottom: 0;
-
-        .split-collapse-icon {
-          transform: scaleY(1);
-        }
-      }
-    }
-  }
-
-  // 拉伸状态
-  &.is-resizing {
-    user-select: none;
-
-    & > .split-panel-wrap,
-    & > .split-collapse-button {
-      transition: none;
-    }
-  }
-
-  // 遮罩层
-  .split-panel-mask {
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    opacity: 0;
-    backdrop-filter: blur(6px);
-    background: rgba(158, 158, 158, 0.2);
-    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    pointer-events: none;
-    display: none;
-    z-index: 2;
-  }
-
-  // 内部表格弹性布局
-  &.is-flex-table {
-    flex: 1;
-    overflow: auto;
-
-    & > .split-panel-body,
-    & > .split-panel-wrap > .split-panel-side {
-      display: flex;
-      flex-direction: column;
-
-      & > .pro-table {
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        overflow: auto;
-
-        & > .el-table {
-          flex: 1;
-          height: 100%;
-        }
-      }
-    }
-  }
-}
-
-/* 小屏幕样式 */
-@media screen and (max-width: 768px) {
-  .split-panel.is-responsive:not(.is-vertical) {
-    &:not(.is-collapse) {
-      overflow: hidden !important;
-    }
-
-    & > .split-panel-wrap {
-      position: absolute;
-      top: 0;
-      left: 0;
-      bottom: 0;
-
-      & > .split-panel-side {
-        background: var(--el-bg-color-overlay);
-        border: none;
-        z-index: 3;
-      }
-    }
-
-    & > .split-panel-body {
-      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    }
-
-    & > .split-panel-mask {
-      display: block;
-    }
-
-    &:not(.is-collapse) {
-      & > .split-panel-mask {
-        left: var(--split-size);
-        pointer-events: all;
-        opacity: 1;
-      }
-
-      & > .split-panel-body {
-        transform: translateX(calc(var(--split-size) + var(--split-space)));
-        z-index: 1;
-      }
-    }
-
-    // 反向
-    &.is-reverse {
-      & > .split-panel-wrap {
-        right: 0;
-        left: auto;
-      }
-
-      &:not(.is-collapse) {
-        & > .split-panel-mask {
-          left: 0;
-          right: var(--split-size);
-        }
-
-        & > .split-panel-body {
-          transform: translateX(calc(0px - var(--split-size) - var(--split-space)));
-        }
-      }
-    }
-  }
-}
-</style>

+ 0 - 38
src/components/FsSplitPanel/props.ts

@@ -1,38 +0,0 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-
-import type { CSSProperties } from 'vue'
-
-export interface SplitPanelProps {
-  // 默认大小
-  size?: string
-  // 最小尺寸
-  minSize?: number
-  // 最大尺寸
-  maxSize?: number
-  // 间距
-  space?: string
-  // 自定义样式
-  customStyle?: Partial<CSSProperties> | Array<Partial<CSSProperties>>
-  // 自定义内容样式
-  bodyStyle?: Partial<CSSProperties> | Array<Partial<CSSProperties>>
-  // 是否可折叠
-  allowCollapse?: boolean
-  // 折叠按钮样式
-  collapseStyle?: Record<string, any>
-  // 是否折叠
-  collapse?: boolean
-  // 是否垂直方向
-  vertical?: boolean
-  // 是否反向布局
-  reverse?: boolean
-  // 是否可拉伸宽度
-  resizable?: boolean
-}
-
-/**
- * 事件
- */
-export const splitPanelEmits = {
-  // 更新折叠状态
-  'update:collapse': (_collapse: boolean) => true
-}

+ 33 - 0
src/config/uploadConfig.ts

@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+import { uuid } from '@/utils/utils'
+
+const uploadApi = '/common/upload'
+
+export default {
+  uploadApi,
+  basePath: import.meta.env.VITE_BASE_PATH,
+  uploadFunc: (file: any, onUploadProgress: any): Promise<any> => {
+    const formData = new FormData()
+    formData.append('file', file)
+
+    return request
+      .post(uploadApi, formData, {
+        onUploadProgress
+      })
+      .then((res: any) => {
+        return { data: res.data }
+      })
+  },
+  transformData: (data: string) => {
+    // 将逗号分隔的字符串转换为数组
+    return data
+      .split(',')
+      .filter((item: string) => item)
+      .map(x => ({
+        key: uuid(),
+        url: x,
+        status: 'success',
+        name: x.split('/').pop()
+      }))
+  }
+}

+ 2 - 0
src/global.d.ts

@@ -0,0 +1,2 @@
+declare var BMapGL: any
+declare var BMapGLLib: any

+ 4 - 7
src/main.ts

@@ -64,7 +64,9 @@ import router from './router'
 import './assets/main.css'
 import 'virtual:svg-icons-register'
 
-import { ossUpload } from '@/utils/utils'
+import uploadConfig from './config/uploadConfig'
+
+// import { ossUpload } from '@/utils/utils'
 
 // import WujieVue from 'wujie-vue3'
 
@@ -117,12 +119,7 @@ app.use(router)
 app.use(ElementPlus)
 app.use(useTable)
 app.use(FsAdminCore, {
-  upload: {
-    oss: true,
-    ossHost: '',
-    ossUpload,
-    baseApi: import.meta.env.VITE_BASE_API
-  }
+  upload: uploadConfig
 })
 // app.use(WujieVue)
 app.use(directives)

+ 0 - 1
src/views/extension/checkCard/index.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import FsCheckCard from '@/components/FsCheckCard/index.vue'
 import type { CheckCardItem } from '@/components/FsCheckCard/types'
 
 const select = ref('React')

+ 1 - 1
src/views/extension/excel/export.vue

@@ -77,7 +77,7 @@ const exportFile = () => {
   })
   exportExcel(excelParams)
     .then(() => {
-      console.log()
+      ElMessage.success('导出成功')
     })
     .catch(e => {
       ElMessage.error(e.message)

+ 3 - 7
src/views/extension/imageUpload/index.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { ElMessage } from 'element-plus'
-import FsImageUpload from '@/components/FsImageUpload/index.vue'
 import config from '@/config/defaultSetting'
 const images = ref('')
 // 手动上传数据
@@ -43,7 +42,7 @@ const submit = () => {
 
 <template>
   <el-card header="基础示例" shadow="never">
-    <fs-image-upload
+    <el-image-upload
       v-model="images"
       :limit="5"
       :uploadFunction="uploadFunction"
@@ -59,15 +58,12 @@ const submit = () => {
         <el-radio label="否" :value="false"> </el-radio>
       </el-radio-group>
     </div>
-    <div>
-      {{ images }}
-    </div>
   </el-card>
   <el-card header="支持多选" shadow="never" class="mt-3">
-    <fs-image-upload v-model="images2" multiple :limit="5" :uploadFunction="uploadFunction" @remove="onRemove" />
+    <el-image-upload v-model="images2" multiple :limit="5" :uploadFunction="uploadFunction" @remove="onRemove" />
   </el-card>
   <el-card header="基础示例" shadow="never" class="mt-3">
-    <fs-image-upload
+    <el-image-upload
       v-model="images3"
       :auto-upload="false"
       :limit="5"

+ 1 - 1
src/views/extension/map/amap.vue

@@ -24,7 +24,7 @@ const mapPickerDone = (data: any) => {
       <p>详细信息:{{ mapData.address }}</p>
     </div>
     <el-button type="primary" @click="openPicker">打开地图选择</el-button>
-    <FsMapPicker v-model="showPicker" map-key="80b0e2d75dc7bb2534e9d6e8ed79ab9e" @done="mapPickerDone" />
+    <FsMapPicker v-model="showPicker" map-key="" @done="mapPickerDone" />
   </div>
 </template>
 

+ 1 - 1
src/views/extension/map/amapTrack.vue

@@ -36,7 +36,7 @@ const lineData = [
 /* 渲染轨迹回放地图 */
 const renderTrackMap = () => {
   AMapLoader.load({
-    key: '80b0e2d75dc7bb2534e9d6e8ed79ab9e',
+    key: '',
     version: '2.0',
     plugins: ['AMap.MoveAnimation', 'AMap.Marker', 'AMap.Polyline']
   })

+ 42 - 0
src/views/extension/map/baidu.vue

@@ -0,0 +1,42 @@
+<script setup lang="ts">
+// 是否显示地图位置选择
+const showMapPicker = ref<boolean>(false)
+
+const mapData = ref({
+  lat: '',
+  lng: '',
+  name: '',
+  address: ''
+})
+
+/* 打开位置选择 */
+const openPicker = () => {
+  showMapPicker.value = true
+}
+
+/* 地图选择完毕 */
+const mapPickerDone = (data: any) => {
+  mapData.value = data
+}
+</script>
+
+<template>
+  <div>
+    <div>
+      <p>经度:{{ mapData.lng }}</p>
+      <p>纬度:{{ mapData.lat }}</p>
+      <p>地址:{{ mapData.name }}</p>
+      <p>详细信息:{{ mapData.address }}</p>
+    </div>
+    <el-button type="primary" @click="openPicker">打开地图选择</el-button>
+    <fs-map-picker
+      v-model="showMapPicker"
+      type="baidu"
+      poiKeywords="美食$银行$酒店$公园$学校$超市$医院"
+      map-key=""
+      @done="mapPickerDone"
+    ></fs-map-picker>
+  </div>
+</template>
+
+<style scoped lang="scss"></style>

+ 107 - 0
src/views/extension/map/baiduTrack.vue

@@ -0,0 +1,107 @@
+<script lang="ts" setup>
+/* 地图容器 */
+const trackMapRef = ref<HTMLElement | null>(null)
+
+/* 小车轨迹地图的实例 */
+let mapInsTrack: any
+
+let lushu: any
+
+/* 轨迹路线 */
+const lineData = [
+  [112.55863627550298, 37.86535449354035],
+  [112.56712335932487, 37.86535449354035],
+  [112.5799752291123, 37.866315720275935],
+  [112.58821982482502, 37.866507964105715],
+  [112.59234212268136, 37.866315720275935],
+  [112.59258461079057, 37.85997139015577],
+  [112.59282709889975, 37.85381878648066],
+  [112.59258461079057, 37.84901170452819],
+  [112.59258461079057, 37.83881964551115],
+  [112.59306958700896, 37.83478088959811],
+  [112.59403953944575, 37.83151125720199],
+  [112.59015972969863, 37.82227856519498],
+  [112.57294307394565, 37.82381742809322],
+  [112.5688207760893, 37.82362507200052],
+  [112.55839378739381, 37.82381742809322],
+  [112.54820928680755, 37.82343271540223],
+  [112.54263206029601, 37.82381742809322]
+]
+
+/* 渲染轨迹回放地图 */
+const renderTrackMap = () => {
+  mapInsTrack = new BMapGL.Map(trackMapRef.value) // 创建Map实例
+  mapInsTrack.centerAndZoom(new BMapGL.Point(112.55863627550298, 37.86535449354035), 12) // 初始化地图,设置中心点坐标和地图级别
+  mapInsTrack.enableScrollWheelZoom(true) //开启鼠标滚轮缩放
+
+  createLine()
+  // 创建路书
+  createLuShu()
+}
+
+/* 绘制线路 */
+const createLine = () => {
+  const polyline = new BMapGL.Polyline(
+    lineData.map((item: any) => new BMapGL.Point(item[0], item[1])),
+    {
+      strokeColor: '#ff0000',
+      strokeWeight: 4,
+      strokeOpacity: 1
+    }
+  )
+  mapInsTrack.addOverlay(polyline)
+}
+
+/* 创建路书 */
+const createLuShu = () => {
+  // 存在清除
+  if (lushu) {
+    lushu.stop()
+    lushu = null
+  }
+  // 删除选择之前的坐标
+  lushu = new BMapGLLib.LuShu(
+    mapInsTrack,
+    lineData.map((item: any) => new BMapGL.Point(item[0], item[1])),
+    {
+      autoView: true, // 是否开启自动视野调整,如果开启那么路书在运动过程中会根据视野自动调整
+      icon: new BMapGL.Icon(
+        'https://bj.bcebos.com/v1/mapopen/github/BMapGLLib/Lushu/examples/images/car.png',
+        new BMapGL.Size(42, 52),
+        { anchor: new BMapGL.Size(10, 10) }
+      ),
+      speed: 1000, // 速度
+      enableRotation: true // 是否设置marker随着道路的走向进行旋转
+    }
+  )
+}
+
+const startTrackAnim = () => {
+  lushu && lushu.start()
+}
+
+const pauseTrackAnim = () => {
+  lushu && lushu.pause()
+}
+
+/* 渲染地图 */
+onMounted(() => {
+  renderTrackMap()
+})
+
+/* 销毁地图 */
+onBeforeUnmount(() => {
+  if (mapInsTrack) {
+    mapInsTrack.destroy()
+    mapInsTrack = null
+  }
+})
+</script>
+
+<template>
+  <div ref="trackMapRef" style="height: 360px; max-width: 800px; margin-bottom: 16px"></div>
+  <div>
+    <el-button type="primary" @click="startTrackAnim"> 开始移动 </el-button>
+    <el-button type="primary" @click="pauseTrackAnim"> 暂停移动 </el-button>
+  </div>
+</template>

+ 7 - 106
src/views/extension/map/index.vue

@@ -1,108 +1,8 @@
 <script setup lang="ts">
 import Amap from './amap.vue'
 import AmapTrack from './amapTrack.vue'
-let bmap: any = null
-let lushu: any = null
-
-const data = [
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.483671256780845,
-    lon: 112.35835768559537
-  },
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.48392562378963,
-    lon: 112.35380873633531
-  },
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.484445885520906,
-    lon: 112.34942980034347
-  },
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.484613829160985,
-    lon: 112.34826673209832
-  },
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.48548191169621,
-    lon: 112.34776796383788
-  },
-  {
-    licensePlate: '',
-    speed: 47,
-    sendData: 1718434061000,
-    lat: 36.48727931765302,
-    lon: 112.34804304633398
-  }
-]
-
-const points: any = []
-
-onMounted(() => {
-  initMap()
-})
-
-const initMap = () => {
-  const center = new (window as any).BMap.Point(112.35835768559537, 36.483671256780845)
-  bmap = new (window as any).BMap.Map('map') // 创建Map实例
-  bmap.centerAndZoom(center, 14) // 初始化地图,设置中心点坐标和地图级别
-  bmap.enableScrollWheelZoom(true) // 开启鼠标滚轮缩放
-
-  // 初始化数据
-  for (let i = 0; i < data.length; i++) {
-    points.push(new (window as any).BMap.Point(data[i].lon, data[i].lat))
-  }
-
-  createTrack()
-}
-
-/* 开始动画 */
-const start = () => {
-  if (lushu) {
-    lushu.start()
-    lushu.showInfoWindow()
-  }
-}
-
-/* 暂停动画 */
-const pause = () => {
-  if (lushu) {
-    lushu.pause()
-  }
-}
-
-/* 创建轨迹动画 */
-const createTrack = () => {
-  // 实例化一个驾车导航用来生成路线
-  bmap.addOverlay(new (window as any).BMap.Polyline(points, { strokeColor: 'red' }))
-  bmap.setViewport(points)
-
-  lushu = new (window as any).BMapLib.LuShu(bmap, points, {
-    defaultContent: '默认内容',
-    autoView: true, //是否开启自动视野调整,如果开启那么路书在运动过程中会根据视野自动调整
-    icon: new (window as any).BMap.Icon('/car.png', new (window as any).BMap.Size(52, 26), {
-      anchor: new (window as any).BMap.Size(27, 13)
-    }),
-    speed: 500,
-    enableRotation: true //是否设置marker随着道路的走向进行旋转
-  })
-  console.log(lushu)
-  // const start = new (window as any).BMap.Point(points[0].lng, points[0].lat)
-  // const end = new (window as any).BMap.Point(points[points.length - 1].lng, points[points.length - 1].lat)
-}
+import Baidu from './baidu.vue'
+import BaiduTrack from './baiduTrack.vue'
 </script>
 
 <template>
@@ -112,10 +12,11 @@ const createTrack = () => {
   <el-card header="高德地图轨迹回放" shadow="never" class="mt-3">
     <AmapTrack />
   </el-card>
-  <el-card header="轨迹回放" shadow="never" class="mt-3">
-    <el-button type="primary" @click="start">开始动画</el-button>
-    <el-button type="primary" @click="pause">暂停动画</el-button>
-    <div id="map" class="w-full h-300px mt-3"></div>
+  <el-card header="百度地图" shadow="never" class="mt-3">
+    <Baidu />
+  </el-card>
+  <el-card header="百度地图轨迹回放" shadow="never" class="mt-3">
+    <BaiduTrack />
   </el-card>
 </template>
 

+ 0 - 1
src/views/extension/printer/index.vue

@@ -1,7 +1,6 @@
 <script setup lang="ts">
 import type { PrintDirection, PrintOrientation, PrintTarget } from '@/components/FsPrinter/types'
 import { ElLoading } from 'element-plus'
-import FsPrinter from '@/components/FsPrinter/index.vue'
 import PrintContract from './printContract.vue'
 import { printPdf } from '@/components/FsPrinter/util'
 

+ 27 - 27
src/views/extension/printer/printContract.vue

@@ -1,3 +1,29 @@
+<script lang="ts" setup>
+import { ref } from 'vue'
+import type { PrintTarget } from '@/components/FsPrinter/types'
+
+defineProps<{
+  /** 合同数据 */
+  data?: any
+  /** 始终显示 */
+  isStatic: boolean
+  /** 打印位置 */
+  target: PrintTarget
+}>()
+
+/** 打印组件 */
+const printerRef = ref()
+
+/** 打印 */
+const print = () => {
+  if (printerRef.value) {
+    printerRef.value?.print()
+  }
+}
+
+defineExpose({ print })
+</script>
+
 <!-- 房屋租赁合同 -->
 <template>
   <fs-printer
@@ -18,7 +44,7 @@
         <img src="/logo.png" style="height: 15px; width: 15px; vertical-align: -2px" />
         <span> 方是公寓房屋租赁合同</span>
       </div>
-      <div style="color: #888">电话:0.51-8888888</div>
+      <div style="color: #888">电话:0351-8888888</div>
     </template>
     <div v-if="!!data" style="page-break-after: always">
       <div style="font-size: 35px; text-align: center">房屋租赁合同</div>
@@ -102,32 +128,6 @@
   </fs-printer>
 </template>
 
-<script lang="ts" setup>
-import { ref } from 'vue'
-import type { PrintTarget } from '@/components/FsPrinter/types'
-
-defineProps<{
-  /** 合同数据 */
-  data?: any
-  /** 始终显示 */
-  isStatic: boolean
-  /** 打印位置 */
-  target: PrintTarget
-}>()
-
-/** 打印组件 */
-const printerRef = ref()
-
-/** 打印 */
-const print = () => {
-  if (printerRef.value) {
-    printerRef.value?.print()
-  }
-}
-
-defineExpose({ print })
-</script>
-
 <style lang="scss" scoped>
 .demo-input {
   height: 24px;

+ 15 - 17
src/views/extension/splitePanel/index.vue

@@ -1,17 +1,15 @@
-<script setup>
-import FsSplitPanel from '@/components/FsSplitPanel/index.vue'
-
+<script setup lang="ts">
 // 是否显示折叠按钮
-const allowCollapse = ref(true)
+const allowCollapse = ref<boolean>(true)
 
 // 是否支持自由拉伸
-const resizable = ref(false)
+const resizable = ref<boolean>(true)
 
 // 是否上下布局模式
-const vertical = ref(false)
+const vertical = ref<boolean>(false)
 
 // 是否反转布局方向
-const reverse = ref(false)
+const reverse = ref<boolean>(false)
 </script>
 
 <template>
@@ -42,7 +40,7 @@ const reverse = ref(false)
         </el-radio-group>
       </el-form-item>
     </el-form>
-    <fs-split-panel
+    <el-split-panel
       space="0px"
       size="160px"
       :allow-collapse="allowCollapse"
@@ -57,11 +55,11 @@ const reverse = ref(false)
       <template #body>
         <div>内容</div>
       </template>
-    </fs-split-panel>
+    </el-split-panel>
   </el-card>
   <el-card shadow="never" class="mt-5">
     <div style="margin: 0 0 8px 0">先左右再上下</div>
-    <fs-split-panel
+    <el-split-panel
       space="0px"
       :min-size="40"
       :max-size="-40"
@@ -77,7 +75,7 @@ const reverse = ref(false)
     >
       <div>边栏</div>
       <template #body>
-        <fs-split-panel
+        <el-split-panel
           space="0px"
           :min-size="40"
           :max-size="-40"
@@ -99,11 +97,11 @@ const reverse = ref(false)
           <template #body>
             <div>内容二</div>
           </template>
-        </fs-split-panel>
+        </el-split-panel>
       </template>
-    </fs-split-panel>
+    </el-split-panel>
     <div style="margin: 16px 0 8px 0">先上下再左右</div>
-    <fs-split-panel
+    <el-split-panel
       space="0px"
       size="120px"
       :min-size="40"
@@ -121,7 +119,7 @@ const reverse = ref(false)
     >
       <div>顶栏</div>
       <template #body>
-        <fs-split-panel
+        <el-split-panel
           space="0px"
           :min-size="40"
           :max-size="-40"
@@ -142,9 +140,9 @@ const reverse = ref(false)
           <template #body>
             <div>内容</div>
           </template>
-        </fs-split-panel>
+        </el-split-panel>
       </template>
-    </fs-split-panel>
+    </el-split-panel>
   </el-card>
 </template>
 

+ 4 - 8
src/views/extension/stausText/index.vue

@@ -1,13 +1,9 @@
-<script setup lang="ts">
-import FsDot from '@/components/FsDot/index.vue'
-</script>
-
 <template>
   <el-card header="文本状态" shadow="never">
-    <fs-dot type="primary" text="运行中"></fs-dot>
-    <fs-dot type="success" text="已上线" class="ml-5"></fs-dot>
-    <fs-dot type="warning" text="异常" class="ml-5"></fs-dot>
-    <fs-dot type="info" text="关闭" class="ml-5"></fs-dot>
+    <el-fs-dot type="primary" text="运行中"></el-fs-dot>
+    <el-fs-dot type="success" text="已上线" class="ml-5"></el-fs-dot>
+    <el-fs-dot type="warning" text="异常" class="ml-5"></el-fs-dot>
+    <el-fs-dot type="info" text="关闭" class="ml-5"></el-fs-dot>
   </el-card>
 </template>
 

+ 1 - 1
src/views/extension/tableSelect/base.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import FsTableSelect from '@/components/FsTableSelect/index.vue'
 import type { TableConfig } from '@/components/FsTableSelect/types'
 
 const data = ref('')
@@ -43,6 +42,7 @@ const tableConfig: TableConfig = {
       slot: 'sex'
     }
   ],
+  /* 实际开发应传入接口方法 */
   datasource: request,
   pageSize: 5
 }

+ 0 - 1
src/views/extension/tour/index.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import FsTour from '@/components/FsTour/index.vue'
 import type { TourStep } from '@/components/FsTour/types'
 import type { ElButton } from 'element-plus'
 const current = ref()