董国庆
2026-02-26 5e9a5c75179aa9e1f9f62feb966b7136117817e4
样式修改
2个文件已添加
5个文件已修改
413 ■■■■■ 已修改文件
package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svg-icons/android-filled.svg 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appVersion/appVersionManage/add.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appVersion/appVersionManage/index.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appVersion/hotfix/index.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -39,6 +39,8 @@
  "devDependencies": {
    "@antfu/eslint-config": "^5.0.0",
    "@iconify-json/icon-park-outline": "^1.2.2",
    "@iconify-json/icon-park-solid": "^1.2.4",
    "@iconify-json/ri": "^1.2.10",
    "@iconify/vue": "^5.0.0",
    "@types/node": "^24.1.0",
    "@vitejs/plugin-vue": "^6.0.1",
src/assets/svg-icons/android-filled.svg
New file
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><path d="M270.1 741.7c0 23.4 19.1 42.5 42.6 42.5h48.7v120.4c0 30.5 24.5 55.4 54.6 55.4c30.2 0 54.6-24.8 54.6-55.4V784.1h85v120.4c0 30.5 24.5 55.4 54.6 55.4c30.2 0 54.6-24.8 54.6-55.4V784.1h48.7c23.5 0 42.6-19.1 42.6-42.5V346.4h-486v395.3zm357.1-600.1l44.9-65c2.6-3.8 2-8.9-1.5-11.4c-3.5-2.4-8.5-1.2-11.1 2.6l-46.6 67.6c-30.7-12.1-64.9-18.8-100.8-18.8c-35.9 0-70.1 6.7-100.8 18.8l-46.6-67.5c-2.6-3.8-7.6-5.1-11.1-2.6c-3.5 2.4-4.1 7.4-1.5 11.4l44.9 65c-71.4 33.2-121.4 96.1-127.8 169.6h486c-6.6-73.6-56.7-136.5-128-169.7zM409.5 244.1a26.9 26.9 0 1 1 26.9-26.9a26.97 26.97 0 0 1-26.9 26.9zm208.4 0a26.9 26.9 0 1 1 26.9-26.9a26.97 26.97 0 0 1-26.9 26.9zm223.4 100.7c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4c30.2 0 54.6-24.8 54.6-55.4V400.1c.1-30.6-24.3-55.3-54.6-55.3zm-658.6 0c-30.2 0-54.6 24.8-54.6 55.4v216.4c0 30.5 24.5 55.4 54.6 55.4c30.2 0 54.6-24.8 54.6-55.4V400.1c0-30.6-24.5-55.3-54.6-55.3z" fill="currentColor"></path></svg>
src/router/routes.static.ts
@@ -126,6 +126,19 @@
    pid: 202,
  },
  {
    name: 'addAppVersion',
    path: '/system/appVersion/appVersionManage/add',
    title: '新增APP版本号',
    requiresAuth: true,
    icon: 'icon-park-outline:tool',
    menuType: 'page',
    componentPath: '/system/appVersion/appVersionManage/add.vue',
    id: 2023,
    pid: 202,
    hide: true,
    activeMenu: '/system/appVersion/appVersionManage',
  },
  {
    name: 'systemConfig',
    path: '/system/config',
    title: '系统配置',
src/views/system/appVersion/appVersionManage/add.vue
New file
@@ -0,0 +1,182 @@
<script setup lang="tsx">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage } from 'naive-ui'
import type { UploadFileInfo, FormRules, FormInst } from 'naive-ui'
import { customUploadRequest, beforeUploadCheck } from '@/utils/upload'
import { releaseAppVersion } from '@/service/api/appVersion'
const router = useRouter()
const message = useMessage()
const formRef = ref<FormInst | null>(null)
const formModel = ref({
  version: '',
  androidFiles: [] as UploadFileInfo[],
  iosFiles: [] as UploadFileInfo[],
  updateNote: '',
  isForceUpdate: false
})
const rules: FormRules = {
  version: [
    { required: true, message: '请输入版本号', trigger: ['blur', 'input'] }
  ],
  androidFiles: [
    { required: true, type: 'array', message: '请上传安卓安装包', trigger: ['blur', 'change'] }
  ],
  iosFiles: [
    { required: true, type: 'array', message: '请上传 iOS 安装包', trigger: ['blur', 'change'] }
  ],
  updateNote: [
    { required: true, message: '请输入更新说明', trigger: ['blur', 'input'] }
  ]
}
function beforeUploadApk(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
  return beforeUploadCheck(data, { maxSize: 500, allowedTypes: [] })
}
function beforeUploadIpa(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }) {
  return beforeUploadCheck(data, { maxSize: 500, allowedTypes: [] })
}
async function customRequest(options: any) {
  await customUploadRequest(options)
}
async function handleConfirm() {
  await formRef.value?.validate(async (errors) => {
    if (!errors) {
        const params = {
            version: formModel.value.version,
            androidFile: formModel.value.androidFiles[0]?.url || '',
            iosFile: formModel.value.iosFiles[0]?.url || '',
            updateDescription: formModel.value.updateNote,
            forceUpdateFlag: formModel.value.isForceUpdate ? 1 : 0
        }
        if (!params.androidFile || !params.iosFile) {
             message.error('请等待文件上传完成')
             return
        }
        const { isSuccess } = await releaseAppVersion(params)
        if (isSuccess) {
            message.success('发布成功')
            router.back()
        }
    } else {
      message.error('请完善必填信息')
    }
  })
}
function handleCancel() {
  router.back()
}
</script>
<template>
  <div class="h-full flex flex-col bg-white rounded-md">
      <div class="flex items-center px-6 pt-6 mb-6">
        <div class="flex items-center relative pl-3">
          <div class="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-[#0068FF] rounded-sm"></div>
          <span class="text-base font-bold text-[#1D2129] ml-2">新增APP版本号</span>
        </div>
      </div>
      <div class="flex-1 overflow-y-auto px-6">
        <n-form
          ref="formRef"
          :model="formModel"
          :rules="rules"
          label-placement="left"
          inline
          require-mark-placement="left"
          :show-feedback="false"
          class="max-w-4xl flex flex-col gap-16px"
        >
              <n-form-item label="版本号" path="version">
                <n-input v-model:value="formModel.version" placeholder="请输入版本号" class="!w-[400px]" />
              </n-form-item>
              <n-form-item label="安卓文件" path="androidFiles">
                  <div class="flex flex-col gap-2">
                      <n-upload
                          v-model:file-list="formModel.androidFiles"
                          :max="1"
                          accept=".apk"
                          :on-before-upload="beforeUploadApk"
                          :custom-request="customRequest"
                          :show-file-list="false"
                      >
                          <n-button style="width: 100px">
                             <template #icon>
                               <icon-park-outline-upload />
                             </template>
                             {{ formModel.androidFiles.length > 0 ? '重新上传' : '点击上传' }}
                          </n-button>
                      </n-upload>
                      <div v-if="formModel.androidFiles.length > 0" class="flex items-center bg-[#F5F6F8] px-3 py-2 rounded w-fit gap-3">
                           <svg-icons-android-filled class="text-[#00B42A] text-lg" />
                           <span class="text-sm text-[#1D2129] max-w-[200px] truncate" :title="formModel.androidFiles[0].name">{{ formModel.androidFiles[0].name }}</span>
                           <icon-park-outline-close class="text-gray-400 cursor-pointer hover:text-gray-600" @click="formModel.androidFiles = []" />
                      </div>
                  </div>
              </n-form-item>
               <n-form-item label="iOS文件" path="iosFiles">
                  <div class="flex flex-col gap-2">
                      <n-upload
                          v-model:file-list="formModel.iosFiles"
                          :max="1"
                          accept=".ipa"
                          :on-before-upload="beforeUploadIpa"
                          :custom-request="customRequest"
                           :show-file-list="false"
                      >
                           <n-button style="width: 100px">
                             <template #icon>
                               <icon-park-outline-upload />
                             </template>
                             {{ formModel.iosFiles.length > 0 ? '重新上传' : '点击上传' }}
                          </n-button>
                      </n-upload>
                      <div v-if="formModel.iosFiles.length > 0" class="flex items-center bg-[#F5F6F8] px-3 py-2 rounded w-fit gap-3">
                           <ri-apple-fill class="text-black text-lg" />
                           <span class="text-sm text-[#1D2129] max-w-[200px] truncate" :title="formModel.iosFiles[0].name">{{ formModel.iosFiles[0].name }}</span>
                           <icon-park-outline-close class="text-gray-400 cursor-pointer hover:text-gray-600" @click="formModel.iosFiles = []" />
                      </div>
                  </div>
              </n-form-item>
              <n-form-item label="更新说明" path="updateNote" label-placement="top">
                <n-input
                  v-model:value="formModel.updateNote"
                  type="textarea"
                  placeholder="请输入"
                  :autosize="{ minRows: 6, maxRows: 10 }"
                  show-count
                  style="width: 750px;"
                  :maxlength="200"
                />
              </n-form-item>
               <n-form-item label="强制更新" path="isForceUpdate" label-placement="left">
                <n-switch v-model:value="formModel.isForceUpdate" />
              </n-form-item>
        </n-form>
      </div>
      <div class="flex justify-end gap-3 mt-auto px-6 py-4 border-t border-gray-100">
          <n-button style="padding: 8px 29px;" @click="handleCancel">返回</n-button>
          <n-button style="padding: 8px 29px;" type="primary" @click="handleConfirm">保存</n-button>
      </div>
  </div>
</template>
<style scoped>
</style>
src/views/system/appVersion/appVersionManage/index.vue
@@ -1,5 +1,6 @@
<script setup lang="tsx">
import { ref, computed, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import type { DataTableColumns, FormInst, FormRules, UploadFileInfo } from 'naive-ui'
import { NButton } from 'naive-ui'
import dayjs from 'dayjs'
@@ -7,6 +8,7 @@
import { fetchAppVersionPage, releaseAppVersion } from '@/service/api/appVersion'
import type { AppVersionItem } from '@/service/api/appVersion'
const router = useRouter()
const listData = ref<AppVersionItem[]>([])
const loading = ref(false)
const showForm = ref(false)
@@ -59,32 +61,35 @@
  {
    title: '序号',
    key: 'index',
    width: 80,
    align: 'center',
    width: 60,
    align: 'left',
    render: (_row, rowIndex) => rowIndex + 1 + (pagination.page - 1) * pagination.pageSize
  },
  {
    title: '版本号',
    key: 'version',
    minWidth: 140,
    width: 168,
    align: 'left',
    ellipsis: true
  },
  {
    title: '更新提示',
    key: 'updateDescription',
    minWidth: 220,
    align: 'left',
    ellipsis: { tooltip: true }
  },
  {
    title: '创建时间',
    key: 'createTime',
    minWidth: 160,
    width: 180,
    align: 'left',
    render: (row) => row.createTime ? dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') : '-'
  },
  {
    title: '操作',
    key: 'actions',
    width: 120,
    width: 80,
    align: 'left',
    render: (row) => {
      return (
        <NButton v-permission="20222" text type="primary" onClick={() => handleView(row)}>
@@ -123,10 +128,7 @@
}
function handleAdd() {
  formMode.value = 'add'
  viewingId.value = null
  resetForm()
  showForm.value = true
  router.push({ name: 'addAppVersion' })
}
function handleView(row: AppVersionItem) {
@@ -207,11 +209,17 @@
<template>
  <div class="h-full flex flex-col">
    <n-card style="border-radius: 6px" content-style="padding: 12px">
      <div class="mb-4 flex justify-end" v-permission="20221">
        <n-button style="height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF" @click="handleAdd">
          新增版本号
        </n-button>
    <n-card style="border-radius: 6px" content-style="padding: 24px">
      <div class="flex items-center justify-between mb-6">
        <div class="flex items-center relative pl-3">
          <div class="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-[#0068FF] rounded-sm"></div>
          <span class="text-base font-bold text-[#1D2129] ml-2">APP版本管理</span>
        </div>
        <div v-permission="20221">
          <n-button style="height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF" @click="handleAdd">
            新增版本号
          </n-button>
        </div>
      </div>
      <n-data-table
src/views/system/appVersion/hotfix/index.vue
@@ -75,50 +75,65 @@
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card content-style="padding: 12px">
      <n-form
        ref="formRef"
  <div class="h-full flex flex-col bg-white">
    <!-- Content -->
    <div class="flex-1 p-6 overflow-y-auto">
      <!-- Header -->
      <div class="flex items-center gap-2 mb-6">
        <div class="w-1 h-4 bg-blue-title rounded-sm"></div>
        <span class="text-base font-bold text-gray-800">系统维护</span>
      </div>
      <n-form
        ref="formRef"
        :model="formModel"
        :rules="rules"
        :rules="rules"
        label-placement="left"
        inline
        :show-feedback="false"
         inline
        require-mark-placement="left"
        :show-feedback="false"
      >
        <n-space vertical :size="16">
          <n-form-item label="维护状态" required>
          <n-form-item label="维护状态" path="maintenanceStatus" required>
            <n-switch v-model:value="formModel.maintenanceStatus" />
          </n-form-item>
          <n-form-item label="开始维护时间" path="startTime" required>
          <n-form-item label="开始维护时间" path="startTime">
            <n-date-picker
              v-model:value="formModel.startTime"
              type="datetime"
              placeholder="年/月/日 --:--"
              style="width: 400px;"
              placeholder="请选择日期"
              style="width: 240px;"
              clearable
            />
          </n-form-item>
          <n-form-item label="预计结束时间" path="endTime" required>
          <n-form-item label="预计结束时间" path="endTime">
            <n-date-picker
              v-model:value="formModel.endTime"
              type="datetime"
              placeholder="年/月/日 --:--"
              style="width: 400px;"
              placeholder="请选择日期"
              style="width: 240px;"
              clearable
            />
          </n-form-item>
        </n-space>
      </n-form>
    </n-card>
    </div>
    <div v-permission="20211" class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4" style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100">
      <n-button type="primary"  style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
    <!-- Footer -->
    <div
      v-permission="20211"
      class="flex justify-end gap-3 bg-white px-6 py-4 border-t border-gray-100"
      style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100"
    >
      <n-button type="primary" style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
    </div>
  </div>
</template>
<style scoped>
.bg-blue-title{
  background: #0068FF;
}
</style>
src/views/system/config/index.vue
@@ -109,8 +109,15 @@
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card content-style="padding: 12px">
  <div class="h-full flex flex-col bg-white">
    <!-- Content -->
    <div class="flex-1 p-6 overflow-y-auto">
      <!-- Header -->
      <div class="flex items-center gap-2" style="margin-bottom: 16px;">
        <div class="w-1 h-4 bg-blue-title rounded-sm"></div>
        <span class="text-base font-bold text-gray-800">系统配置</span>
      </div>
      <n-form 
        ref="formRef" 
        :model="formModel"
@@ -125,68 +132,76 @@
            <n-input 
              v-model:value="formModel.customerServicePhone" 
              placeholder="请输入"
              style="width: 300px;"
              style="width: 290px;"
            />
          </n-form-item>
          <n-form-item label="距点位" path="distanceThreshold" required>
            <n-input-number
              v-model:value="formModel.distanceThreshold"
              placeholder="请输入"
              :min="0"
              :show-button="false"
              class="mr-2"
            />
            m内可下单
            <div class="flex items-center gap-2">
              <n-input-number
                v-model:value="formModel.distanceThreshold"
                placeholder="请输入"
                :min="0"
                :show-button="false"
                style="width: 303px;"
              />
              <span>m内可下单</span>
            </div>
          </n-form-item>
          <n-form-item label="起终点之间" path="timeThreshold" required>
            <n-input-number
              v-model:value="formModel.timeThreshold"
              placeholder="请输入"
              :min="0"
              :show-button="false"
              class="mr-2"
            />
            m内不可下单
            <div class="flex items-center gap-2">
              <n-input-number
                v-model:value="formModel.timeThreshold"
                placeholder="请输入"
                :min="0"
                :show-button="false"
                style="width: 277px;"
              />
              <span>m内不可下单</span>
            </div>
          </n-form-item>
          <n-form-item label="距点位" path="attractDistance" required>
            <n-input-number
              v-model:value="formModel.attractDistance"
              placeholder="请输入"
              :min="0"
              :show-button="false"
              class="mr-2"
            />
            m内吸附
            <div class="flex items-center gap-2">
              <n-input-number
                v-model:value="formModel.attractDistance"
                placeholder="请输入"
                :min="0"
                :show-button="false"
                style="width: 303px;"
              />
              <span>m内吸附</span>
            </div>
          </n-form-item>
          <n-form-item label="到达目的地后" path="destinationDistance" required>
            <n-input-number
              v-model:value="formModel.destinationDistance"
              placeholder="请输入"
              :min="0"
              :show-button="false"
              class="mr-2"
            />
            分钟自动结束行程
            <div class="flex items-center gap-2">
              <n-input-number
                v-model:value="formModel.destinationDistance"
                placeholder="请输入"
                :min="0"
                :show-button="false"
                style="width: 264px;"
              />
              <span>分钟自动结束行程</span>
            </div>
          </n-form-item>
          <n-form-item label="小程序地图默认定位" required>
            <n-input
              v-model:value="formModel.miniProgramLongitude"
              placeholder="请输入经度"
              style="width: 200px;"
              class="mr-2"
            />
            <n-input
              v-model:value="formModel.miniProgramLatitude"
              placeholder="请输入纬度"
              style="width: 200px;"
              class="mr-2"
            />
            <span class="text-gray-500">地址位置地址位置1</span>
            <div class="flex items-center gap-2">
              <n-input
                v-model:value="formModel.miniProgramLongitude"
                placeholder="请输入经度"
                style="width: 225px;"
              />
              <n-input
                v-model:value="formModel.miniProgramLatitude"
                placeholder="请输入纬度"
                style="width: 225px;"
              />
              <span class="text-gray-500">地址信息地址信息地址信息</span>
            </div>
          </n-form-item>
          <n-form-item label="常见问题首页展示数" path="faqDisplayCount" required>
@@ -195,17 +210,26 @@
              placeholder="请输入"
              :min="0"
              :show-button="false"
              style="width: 225px;"
            />
          </n-form-item>
        </n-space>
      </n-form>
    </n-card>
    </div>
    <div v-permission="2031" class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4" style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100">
            <n-button type="primary"  style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
        </div>
    <!-- Footer -->
    <div
      v-permission="2031"
      class="flex justify-end gap-3 bg-white px-6 py-4 border-t border-gray-100"
      style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100"
    >
      <n-button type="primary" style="width: 84px; height: 32px" @click="handleSave">保存</n-button>
    </div>
  </div>
</template>
<style scoped>
.bg-blue-title {
  background: #0068FF;
}
</style>