浏览代码

Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

# Conflicts:
#	pnpm-lock.yaml
#	src/router/modules/remaining.ts
YunaiV 10 月之前
父节点
当前提交
c7bd9f7138

+ 10 - 10
package.json

@@ -1,6 +1,6 @@
 {
   "name": "yudao-ui-admin-vue3",
-  "version": "2.2.0-snapshot",
+  "version": "2.3.0-snapshot",
   "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "xingyu",
   "private": false,
@@ -9,11 +9,11 @@
     "dev": "vite --mode env.local",
     "dev-server": "vite --mode dev",
     "ts:check": "vue-tsc --noEmit",
-    "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
-    "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
-    "build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
-    "build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
-    "build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
+    "build:local": "node ./node_modules/vite/bin/vite.js build",
+    "build:dev": "node ./node_modules/vite/bin/vite.js build --mode dev",
+    "build:test": "node ./node_modules/vite/bin/vite.js build --mode test",
+    "build:stage": "node ./node_modules/vite/bin/vite.js build --mode stage",
+    "build:prod": "node ./node_modules/vite/bin/vite.js build --mode prod",
     "serve:dev": "vite preview --mode dev",
     "serve:prod": "vite preview --mode prod",
     "preview": "pnpm build:local && vite preview",
@@ -26,8 +26,8 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
-    "@form-create/designer": "^3.1.3",
-    "@form-create/element-ui": "^3.1.24",
+    "@form-create/designer": "^3.2.6",
+    "@form-create/element-ui": "^3.2.11",
     "@iconify/iconify": "^3.1.1",
     "@microsoft/fetch-event-source": "^2.0.1",
     "@videojs-player/vue": "^1.0.0",
@@ -47,7 +47,7 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.5.0",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.8.0",
+    "element-plus": "2.8.4",
     "fast-xml-parser": "^4.3.2",
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
@@ -67,7 +67,7 @@
     "steady-xml": "^0.1.0",
     "url": "^0.11.3",
     "video.js": "^7.21.5",
-    "vue": "3.4.21",
+    "vue": "3.5.12",
     "vue-dompurify-html": "^4.1.4",
     "vue-i18n": "9.10.2",
     "vue-router": "^4.3.0",

+ 9 - 3
src/api/mall/trade/delivery/pickUpStore/index.ts

@@ -13,10 +13,11 @@ export interface DeliveryPickUpStoreVO {
   latitude: number
   longitude: number
   status: number
+  verifyUserIds: number[] // 绑定用户编号组数
 }
 
 // 查询自提门店列表
-export const getDeliveryPickUpStorePage = async (params) => {
+export const getDeliveryPickUpStorePage = async (params: any) => {
   return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
 }
 
@@ -26,8 +27,8 @@ export const getDeliveryPickUpStore = async (id: number) => {
 }
 
 // 查询自提门店精简列表
-export const getListAllSimple = async (): Promise<DeliveryPickUpStoreVO[]> => {
-  return await request.get({ url: '/trade/delivery/pick-up-store/list-all-simple' })
+export const getSimpleDeliveryPickUpStoreList = async (): Promise<DeliveryPickUpStoreVO[]> => {
+  return await request.get({ url: '/trade/delivery/pick-up-store/simple-list' })
 }
 
 // 新增自提门店
@@ -44,3 +45,8 @@ export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) =>
 export const deleteDeliveryPickUpStore = async (id: number) => {
   return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id })
 }
+
+// 绑定自提店员
+export const bindStoreStaffId = async (data: any) => {
+  return await request.post({ url: '/trade/delivery/pick-up-store/bind', data })
+}

+ 2 - 0
src/api/pay/app/index.ts

@@ -8,6 +8,7 @@ export interface AppVO {
   remark: string
   payNotifyUrl: string
   refundNotifyUrl: string
+  transferNotifyUrl: string
   merchantId: number
   merchantName: string
   createTime: Date
@@ -19,6 +20,7 @@ export interface AppPageReqVO extends PageParam {
   remark?: string
   payNotifyUrl?: string
   refundNotifyUrl?: string
+  transferNotifyUrl?: string
   merchantName?: string
   createTime?: Date[]
 }

+ 1 - 0
src/components/RouterSearch/index.vue

@@ -20,6 +20,7 @@
   <div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
     <Icon icon="ep:search" />
     <el-select
+      @click.stop
       filterable
       :reserve-keyword="false"
       remote

+ 60 - 7
src/plugins/formCreate/index.ts

@@ -1,7 +1,37 @@
 import type { App } from 'vue'
 // 👇使用 form-create 需额外全局引入 element plus 组件
 import {
+  // ElAutocomplete,
+  // ElButton,
+  // ElCascader,
+  // ElCheckbox,
+  // ElCheckboxButton,
+  // ElCheckboxGroup,
+  // ElCol,
+  // ElColorPicker,
+  // ElDatePicker,
+  // ElDialog,
+  // ElForm,
+  // ElInput,
+  // ElInputNumber,
+  // ElPopover,
+  // ElRadio,
+  // ElRadioButton,
+  // ElRadioGroup,
+  // ElRate,
+  // ElRow,
+  // ElSelect,
+  // ElSlider,
+  // ElSwitch,
+  // ElTimePicker,
+  // ElTooltip,
+  // ElTree,
+  // ElUpload,
+  // ElIcon,
+  // ElProgress,
+  // 以上会由 @form-create/element-ui/auto-import 自动引入
   ElAlert,
+  ElTransfer,
   ElAside,
   ElContainer,
   ElDivider,
@@ -12,7 +42,18 @@ import {
   ElTableColumn,
   ElTabPane,
   ElTabs,
-  ElTransfer
+  ElDropdown,
+  ElDropdownMenu,
+  ElDropdownItem,
+  ElBadge,
+  ElTag,
+  ElText,
+  ElMenu,
+  ElMenuItem,
+  ElFooter,
+  ElMessage
+  // ElFormItem,
+  // ElOption
 } from 'element-plus'
 import FcDesigner from '@form-create/designer'
 import formCreate from '@form-create/element-ui'
@@ -41,18 +82,30 @@ const ApiSelect = useApiSelect({
 })
 
 const components = [
+  ElAlert,
+  ElTransfer,
   ElAside,
-  ElPopconfirm,
-  ElHeader,
-  ElMain,
   ElContainer,
   ElDivider,
-  ElTransfer,
-  ElAlert,
-  ElTabs,
+  ElHeader,
+  ElMain,
+  ElPopconfirm,
   ElTable,
   ElTableColumn,
   ElTabPane,
+  ElTabs,
+  ElDropdown,
+  ElDropdownMenu,
+  ElDropdownItem,
+  ElBadge,
+  ElTag,
+  ElText,
+  ElMenu,
+  ElMenuItem,
+  ElFooter,
+  ElMessage,
+  // ElFormItem,
+  // ElOption,
   UploadImg,
   UploadImgs,
   UploadFile,

+ 48 - 9
src/views/bpm/form/editor/index.vue

@@ -1,14 +1,18 @@
 <template>
-  <ContentWrap>
+  <ContentWrap :body-style="{ padding: '0px' }" class="!mb-0">
     <!-- 表单设计器 -->
-    <FcDesigner ref="designer" height="780px">
-      <template #handle>
-        <el-button round size="small" type="primary" @click="handleSave">
-          <Icon class="mr-5px" icon="ep:plus" />
-          保存
-        </el-button>
-      </template>
-    </FcDesigner>
+    <div
+      class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
+    >
+      <fc-designer class="my-designer" ref="designer" :config="designerConfig">
+        <template #handle>
+          <el-button size="small" type="success" plain @click="handleSave">
+            <Icon class="mr-5px" icon="ep:plus" />
+            保存
+          </el-button>
+        </template>
+      </fc-designer>
+    </div>
   </ContentWrap>
 
   <!-- 表单保存的弹窗 -->
@@ -55,6 +59,31 @@ const { push, currentRoute } = useRouter() // 路由
 const { query } = useRoute() // 路由信息
 const { delView } = useTagsViewStore() // 视图操作
 
+// 表单设计器配置
+const designerConfig = ref({
+  switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
+  autoActive: true, // 是否自动选中拖入的组件
+  useTemplate: false, // 是否生成vue2语法的模板组件
+  formOptions: {}, // 定义表单配置默认值
+  fieldReadonly: false, // 配置field是否可以编辑
+  hiddenDragMenu: false, // 隐藏拖拽操作按钮
+  hiddenDragBtn: false, // 隐藏拖拽按钮
+  hiddenMenu: [], // 隐藏部分菜单
+  hiddenItem: [], // 隐藏部分组件
+  hiddenItemConfig: {}, // 隐藏组件的部分配置项
+  disabledItemConfig: {}, // 禁用组件的部分配置项
+  showSaveBtn: false, // 是否显示保存按钮
+  showConfig: true, // 是否显示右侧的配置界面
+  showBaseForm: true, // 是否显示组件的基础配置表单
+  showControl: true, // 是否显示组件联动
+  showPropsForm: true, // 是否显示组件的属性配置表单
+  showEventForm: true, // 是否显示组件的事件配置表单
+  showValidateForm: true, // 是否显示组件的验证配置表单
+  showFormConfig: true, // 是否显示表单配置
+  showInputData: true, // 是否显示录入按钮
+  showDevice: true, // 是否显示多端适配选项
+  appendConfigData: [] // 定义渲染规则所需的formData
+})
 const designer = ref() // 表单设计器
 useFormCreateDesigner(designer) // 表单设计器增强
 const dialogVisible = ref(false) // 弹窗是否展示
@@ -119,3 +148,13 @@ onMounted(async () => {
   setConfAndFields(designer, data.conf, data.fields)
 })
 </script>
+
+<style>
+.my-designer {
+  ._fc-l,
+  ._fc-m,
+  ._fc-r {
+    border-top: none;
+  }
+}
+</style>

+ 456 - 0
src/views/bpm/model/index_new.vue

@@ -0,0 +1,456 @@
+<template>
+  <ContentWrap>
+    <div class="flex justify-between pl-20px items-center">
+      <h3 class="font-extrabold">表单管理</h3>
+      <!-- 搜索工作栏 -->
+      <el-form
+        class="-mb-15px flex"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item align="right" prop="key" class="ml-auto">
+          <el-input
+            v-model="queryParams.key"
+            placeholder="搜索流程"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          >
+            <template #prefix>
+              <Icon icon="ep:search" class="mx-10px" />
+            </template>
+          </el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
+            <Icon icon="ep:plus" class="mr-5px" /> 新建流程
+          </el-button>
+        </el-form-item>
+
+        <el-form-item>
+          <el-dropdown placement="bottom-end">
+            <el-button class="w-30px" plain>
+              <Icon icon="ep:setting" />
+            </el-button>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item>
+                  <Icon icon="ep:circle-plus" size="13" class="mr-5px" />
+                  新建分组
+                </el-dropdown-item>
+                <el-dropdown-item>
+                  <Icon icon="fa:sort-amount-desc" size="13" class="mr-5px" />
+                  分组排序
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <el-divider />
+
+    <!-- 分类卡片组 -->
+    <div class="px-15px">
+      <ContentWrap :body-style="{ padding: 0 }" v-for="(list, title) in categoryGroup" :key="title">
+        <!-- 默认使其全部展开 -->
+        <el-collapse :modelValue="title">
+          <el-collapse-item :name="title">
+            <template #icon="{ isActive }">
+              <div
+                class="ml-20px flex items-center"
+                :class="['transition-transform duration-300', isActive ? 'rotate-180' : 'rotate-0']"
+              >
+                <Icon icon="ep:arrow-down-bold" color="#999" />
+              </div>
+              <div class="ml-auto mr-30px">
+                <el-button link type="info" class="mr-10px" @click.stop="handleSort">
+                  <Icon icon="fa:sort-amount-desc" class="mr-5px" />
+                  排序
+                </el-button>
+                <el-button link type="info" @click.stop="handleGroup">
+                  <Icon icon="ep:setting" class="mr-5px" />
+                  分组
+                </el-button>
+              </div>
+            </template>
+            <template #title>
+              <div class="flex items-center">
+                <h3 class="ml-20px mr-8px text-18px">{{ title }}</h3>
+                <div class="color-gray-600 text-16px"> ({{ list?.length || 0 }}) </div>
+              </div>
+            </template>
+
+            <el-table
+              :header-cell-style="{ backgroundColor: isDark ? '' : '#edeff0' }"
+              v-loading="loading"
+              :data="list"
+            >
+              <el-table-column label="流程名" prop="name" min-width="150">
+                <template #default="scope">
+                  <div class="flex items-center">
+                    <el-image :src="scope.row.icon" class="h-32px w-32px mr-10px rounded" />
+                    {{ scope.row.name }}
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="可见范围" prop="startUserIds" min-width="100">
+                <template #default="scope">
+                  <el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
+                    全部可见
+                  </el-text>
+                  <el-text v-else-if="scope.row.startUsers.length == 1">
+                    {{ scope.row.startUsers[0].nickname }}
+                  </el-text>
+                  <el-text v-else>
+                    <el-tooltip
+                      class="box-item"
+                      effect="dark"
+                      placement="top"
+                      :content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
+                    >
+                      {{ scope.row.startUsers[0].nickname }}等
+                      {{ scope.row.startUsers.length }} 人可见
+                    </el-tooltip>
+                  </el-text>
+                </template>
+              </el-table-column>
+              <el-table-column label="表单信息" prop="formType" min-width="200">
+                <template #default="scope">
+                  <el-button
+                    v-if="scope.row.formType === 10"
+                    type="primary"
+                    link
+                    @click="handleFormDetail(scope.row)"
+                  >
+                    <span>{{ scope.row.formName }}</span>
+                  </el-button>
+                  <el-button
+                    v-else-if="scope.row.formType === 20"
+                    type="primary"
+                    link
+                    @click="handleFormDetail(scope.row)"
+                  >
+                    <span>{{ scope.row.formCustomCreatePath }}</span>
+                  </el-button>
+                  <label v-else>暂无表单</label>
+                </template>
+              </el-table-column>
+              <el-table-column label="最后发布" prop="deploymentTime" min-width="250">
+                <template #default="scope">
+                  <span v-if="scope.row.processDefinition">
+                    {{ formatDate(scope.row.processDefinition.deploymentTime) }}
+                  </span>
+                  <el-tag v-if="scope.row.processDefinition" class="ml-10px">
+                    v{{ scope.row.processDefinition.version }}
+                  </el-tag>
+                  <el-tag v-else type="warning">未部署</el-tag>
+                  <el-tag
+                    v-if="scope.row.processDefinition?.suspensionState === 2"
+                    type="warning"
+                    class="ml-10px"
+                  >
+                    已停用
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="200" fixed="right">
+                <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="openForm('update', scope.row.id)"
+                    v-hasPermi="['bpm:model:update']"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    修改
+                  </el-button>
+                  <el-button
+                    link
+                    class="!ml-5px"
+                    type="primary"
+                    @click="handleDesign(scope.row)"
+                    v-hasPermi="['bpm:model:update']"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    设计
+                  </el-button>
+                  <el-button
+                    link
+                    class="!ml-5px"
+                    type="primary"
+                    @click="handleDeploy(scope.row)"
+                    v-hasPermi="['bpm:model:deploy']"
+                    :disabled="!isManagerUser(scope.row)"
+                  >
+                    发布
+                  </el-button>
+                  <el-dropdown
+                    class="!align-middle ml-5px"
+                    @command="(command) => handleCommand(command, scope.row)"
+                    v-hasPermi="[
+                      'bpm:process-definition:query',
+                      'bpm:model:update',
+                      'bpm:model:delete'
+                    ]"
+                  >
+                    <el-button type="primary" link>更多</el-button>
+                    <template #dropdown>
+                      <el-dropdown-menu>
+                        <el-dropdown-item
+                          command="handleDefinitionList"
+                          v-if="checkPermi(['bpm:process-definition:query'])"
+                        >
+                          历史
+                        </el-dropdown-item>
+                        <el-dropdown-item
+                          command="handleChangeState"
+                          v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
+                          :disabled="!isManagerUser(scope.row)"
+                        >
+                          {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
+                        </el-dropdown-item>
+                        <el-dropdown-item
+                          type="danger"
+                          command="handleDelete"
+                          v-if="checkPermi(['bpm:model:delete'])"
+                          :disabled="!isManagerUser(scope.row)"
+                        >
+                          删除
+                        </el-dropdown-item>
+                      </el-dropdown-menu>
+                    </template>
+                  </el-dropdown>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-collapse-item>
+        </el-collapse>
+      </ContentWrap>
+    </div>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改流程 -->
+  <ModelForm ref="formRef" @success="getList" />
+
+  <!-- 弹窗:表单详情 -->
+  <Dialog title="表单详情" v-model="formDetailVisible" width="800">
+    <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import * as ModelApi from '@/api/bpm/model'
+import * as FormApi from '@/api/bpm/form'
+import ModelForm from './ModelForm.vue'
+import { setConfAndFields2 } from '@/utils/formCreate'
+import { CategoryApi } from '@/api/bpm/category'
+import { BpmModelType } from '@/utils/constants'
+import { checkPermi } from '@/utils/permission'
+import { useUserStoreWithOut } from '@/store/modules/user'
+import { useAppStore } from '@/store/modules/app'
+import { groupBy } from 'lodash-es'
+
+defineOptions({ name: 'BpmModel' })
+
+const appStore = useAppStore()
+const message = useMessage() // 消息弹窗
+const isDark = computed(() => appStore.getIsDark)
+const { t } = useI18n() // 国际化
+const { push } = useRouter() // 路由
+const userStore = useUserStoreWithOut() // 用户信息缓存
+const loading = ref(true) // 列表的加载中
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  key: undefined,
+  name: undefined,
+  category: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const categoryList = ref([]) // 流程分类列表
+const categoryGroup = ref<any>({}) // 按照category分组的数据
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // TODO 芋艿:这里需要一个不分页查全部的流程模型接口
+    const data = await ModelApi.getModelPage(queryParams)
+    categoryGroup.value = groupBy(data.list, 'categoryName')
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** '更多'操作按钮 */
+const handleCommand = (command: string, row: any) => {
+  switch (command) {
+    case 'handleDefinitionList':
+      handleDefinitionList(row)
+      break
+    case 'handleDelete':
+      handleDelete(row)
+      break
+    case 'handleChangeState':
+      handleChangeState(row)
+      break
+    default:
+      break
+  }
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ModelApi.deleteModel(row.id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 更新状态操作 */
+const handleChangeState = async (row: any) => {
+  const state = row.processDefinition.suspensionState
+  const newState = state === 1 ? 2 : 1
+  try {
+    // 修改状态的二次确认
+    const id = row.id
+    debugger
+    const statusState = state === 1 ? '停用' : '启用'
+    const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
+    await message.confirm(content)
+    // 发起修改状态
+    await ModelApi.updateModelState(id, newState)
+    message.success(statusState + '成功')
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 设计流程 */
+const handleDesign = (row: any) => {
+  if (row.type == BpmModelType.BPMN) {
+    push({
+      name: 'BpmModelEditor',
+      query: {
+        modelId: row.id
+      }
+    })
+  } else {
+    push({
+      name: 'SimpleWorkflowDesignEditor',
+      query: {
+        modelId: row.id
+      }
+    })
+  }
+}
+
+/** 发布流程 */
+const handleDeploy = async (row: any) => {
+  try {
+    // 删除的二次确认
+    await message.confirm('是否部署该流程!!')
+    // 发起部署
+    await ModelApi.deployModel(row.id)
+    message.success(t('部署成功'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 跳转到指定流程定义列表 */
+const handleDefinitionList = (row) => {
+  push({
+    name: 'BpmProcessDefinition',
+    query: {
+      key: row.key
+    }
+  })
+}
+
+/** 流程表单的详情按钮操作 */
+const formDetailVisible = ref(false)
+const formDetailPreview = ref({
+  rule: [],
+  option: {}
+})
+const handleFormDetail = async (row: any) => {
+  if (row.formType == 10) {
+    // 设置表单
+    const data = await FormApi.getForm(row.formId)
+    setConfAndFields2(formDetailPreview, data.conf, data.fields)
+    // 弹窗打开
+    formDetailVisible.value = true
+  } else {
+    await push({
+      path: row.formCustomCreatePath
+    })
+  }
+}
+
+/** 判断是否可以操作 */
+const isManagerUser = (row: any) => {
+  const userId = userStore.getUser.id
+  return row.managerUserIds && row.managerUserIds.includes(userId)
+}
+
+/* 排序 */
+const handleSort = () => {
+  console.log('排序')
+}
+
+/* 分组 */
+const handleGroup = () => {
+  console.log('分组')
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 查询流程分类列表
+  categoryList.value = await CategoryApi.getCategorySimpleList()
+})
+</script>
+
+<style lang="scss" scoped>
+:deep() {
+  .el-form--inline .el-form-item {
+    margin-right: 10px;
+  }
+  .el-divider--horizontal {
+    margin-top: 6px;
+  }
+  .el-collapse,
+  .el-collapse-item__header,
+  .el-collapse-item__wrap {
+    border: none;
+  }
+  .el-collapse-item__arrow {
+    margin-left: 10px;
+    font-size: 20px;
+    font-weight: 500;
+  }
+}
+</style>

+ 9 - 5
src/views/bpm/processInstance/detail/index.vue

@@ -244,10 +244,12 @@ const handleAudit = async (task, pass) => {
   if (!elForm) return
   let valid = await elForm.validate()
   if (!valid) return
-  // 校验申请表单
-  if (!fApi.value) return
-  valid = await fApi.value.validate()
-  if (!valid) return
+  // 校验申请表单(可编辑字段)
+  // TODO @jason:之前这里是 if (!fApi.value) return;针对业务表单的情况下,会导致没办法审核,可能要看下。我这里改了点,看看是不是还有别的地方兼容性
+  if (fApi.value) {
+    valid = await fApi.value.validate()
+    if (!valid) return
+  }
 
   // 2.1 提交审批
   const data = {
@@ -263,7 +265,9 @@ const handleAudit = async (task, pass) => {
       data.variables = approveForms.value[index].value
     }
     // 获取表单可编辑字段的值
-    data.variables = getWritableValueOfForm(task.fieldsPermission)
+    if (fApi.value) {
+      data.variables = getWritableValueOfForm(task.fieldsPermission)
+    }
 
     await TaskApi.approveTask(data)
     message.success('审批通过成功')

+ 4 - 0
src/views/bpm/processInstance/manager/index.vue

@@ -79,6 +79,10 @@
           class="!w-240px"
         />
       </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
     </el-form>
   </ContentWrap>
 

+ 47 - 11
src/views/infra/build/index.vue

@@ -1,16 +1,17 @@
 <template>
-  <ContentWrap>
-    <el-row>
-      <el-col>
-        <div class="float-right mb-2">
-          <el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
-          <el-button size="small" type="success" @click="showOption">生成 Options</el-button>
-          <el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
-        </div>
-      </el-col>
-    </el-row>
+  <ContentWrap :body-style="{ padding: '0px' }" class="!mb-0">
     <!-- 表单设计器 -->
-    <FcDesigner ref="designer" height="780px" />
+    <div
+      class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
+    >
+      <fc-designer class="my-designer" ref="designer" :config="designerConfig">
+        <template #handle>
+          <el-button size="small" type="primary" plain @click="showJson">生成JSON</el-button>
+          <el-button size="small" type="success" plain @click="showOption">生成Options</el-button>
+          <el-button size="small" type="danger" plain @click="showTemplate">生成组件</el-button>
+        </template>
+      </fc-designer>
+    </div>
   </ContentWrap>
 
   <!-- 弹窗:表单预览 -->
@@ -43,6 +44,31 @@ defineOptions({ name: 'InfraBuild' })
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息
 
+// 表单设计器配置
+const designerConfig = ref({
+  switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
+  autoActive: true, // 是否自动选中拖入的组件
+  useTemplate: false, // 是否生成vue2语法的模板组件
+  formOptions: {}, // 定义表单配置默认值
+  fieldReadonly: false, // 配置field是否可以编辑
+  hiddenDragMenu: false, // 隐藏拖拽操作按钮
+  hiddenDragBtn: false, // 隐藏拖拽按钮
+  hiddenMenu: [], // 隐藏部分菜单
+  hiddenItem: [], // 隐藏部分组件
+  hiddenItemConfig: {}, // 隐藏组件的部分配置项
+  disabledItemConfig: {}, // 禁用组件的部分配置项
+  showSaveBtn: false, // 是否显示保存按钮
+  showConfig: true, // 是否显示右侧的配置界面
+  showBaseForm: true, // 是否显示组件的基础配置表单
+  showControl: true, // 是否显示组件联动
+  showPropsForm: true, // 是否显示组件的属性配置表单
+  showEventForm: true, // 是否显示组件的事件配置表单
+  showValidateForm: true, // 是否显示组件的验证配置表单
+  showFormConfig: true, // 是否显示表单配置
+  showInputData: true, // 是否显示录入按钮
+  showDevice: true, // 是否显示多端适配选项
+  appendConfigData: [] // 定义渲染规则所需的formData
+})
 const designer = ref() // 表单设计器
 const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
@@ -140,3 +166,13 @@ onMounted(async () => {
   hljs.registerLanguage('json', json)
 })
 </script>
+
+<style>
+.my-designer {
+  ._fc-l,
+  ._fc-m,
+  ._fc-r {
+    border-top: none;
+  }
+}
+</style>

+ 132 - 24
src/views/mall/trade/delivery/pickUpOrder/index.vue

@@ -26,9 +26,8 @@
         <el-select
           v-model="queryParams.pickUpStoreId"
           class="!w-280px"
-          clearable
-          multiple
           placeholder="全部"
+          @change="handleQuery"
         >
           <el-option
             v-for="item in pickUpStoreList"
@@ -73,10 +72,22 @@
           <Icon class="mr-5px" icon="ep:refresh" />
           重置
         </el-button>
-        <el-button @click="handlePickup" type="success" plain v-hasPermi="['trade:order:pick-up']">
+        <el-button
+          @click="handlePickup"
+          type="success"
+          plain
+          v-hasPermi="['trade:order:pick-up']"
+          :disabled="isUse"
+        >
           <Icon class="mr-5px" icon="ep:check" />
           核销
         </el-button>
+        <el-button type="primary" @click="connectToSerialPort" :disabled="serialPort || isUse">
+          连接扫描枪
+        </el-button>
+        <el-button type="danger" @click="cutPort" :disabled="!serialPort || isUse">
+          断开扫描枪
+        </el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -216,18 +227,20 @@ import { DeliveryTypeEnum } from '@/utils/constants'
 import { TradeOrderSummaryRespVO } from '@/api/mall/trade/order'
 import { DeliveryPickUpStoreVO } from '@/api/mall/trade/delivery/pickUpStore'
 import OrderPickUpForm from '@/views/mall/trade/order/form/OrderPickUpForm.vue'
+import { ref, onMounted } from 'vue'
+import { useUserStore } from '@/store/modules/user'
+const message = useMessage() // 消息弹窗
+
+const port = ref('')
+const ports = ref([])
+const reader = ref('')
 
 defineOptions({ name: 'PickUpOrder' })
 
-// 列表的加载中
-const loading = ref(true)
-// 列表的总页数
-const total = ref(2)
-// 列表的数据
-const list = ref<TradeOrderApi.OrderVO[]>([])
-// 搜索的表单
-const queryFormRef = ref<FormInstance>()
-// 初始表单参数
+const loading = ref(true) // 列表的加载中
+const total = ref(2) // 列表的总页数
+const list = ref<TradeOrderApi.OrderVO[]>([]) // 列表的数据
+const queryFormRef = ref<FormInstance>() // 搜索的表单
 const INIT_QUERY_PARAMS = {
   // 页数
   pageNo: 1,
@@ -238,14 +251,15 @@ const INIT_QUERY_PARAMS = {
   // 配送方式
   deliveryType: DeliveryTypeEnum.PICK_UP.type,
   // 自提门店
-  pickUpStoreId: undefined
-}
-// 表单搜索
-const queryParams = ref({ ...INIT_QUERY_PARAMS })
-// 订单搜索类型 queryParam
-const queryType = reactive({ queryParam: 'no' })
-// 订单统计数据
-const summary = ref<TradeOrderSummaryRespVO>()
+  pickUpStoreId: -1
+} // 初始表单参数
+
+const queryParams = ref({ ...INIT_QUERY_PARAMS }) // 表单搜索
+const queryType = reactive({ queryParam: 'no' }) // 订单搜索类型 queryParam
+const summary = ref<TradeOrderSummaryRespVO>() // 订单统计数据
+
+const serialPort = ref(false) // 是否连接扫码枪
+const isUse = ref(true) // 是否可核销
 
 // 订单聚合搜索 select 类型配置(动态搜索)
 const dynamicSearchList = ref([
@@ -294,13 +308,21 @@ const handleQuery = async () => {
 const resetQuery = () => {
   queryFormRef.value?.resetFields()
   queryParams.value = { ...INIT_QUERY_PARAMS }
+  if (pickUpStoreList.value.length > 0) {
+    queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
+  }
   handleQuery()
 }
 
 /** 自提门店精简列表 */
 const pickUpStoreList = ref<DeliveryPickUpStoreVO[]>([])
 const getPickUpStoreList = async () => {
-  pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
+  pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
+  // 移除自己无法核销的门店
+  const userId = useUserStore().getUser.id
+  pickUpStoreList.value = pickUpStoreList.value.filter((item) =>
+    item.verifyUserIds?.includes(userId)
+  )
 }
 
 /** 显示核销表单 */
@@ -309,10 +331,96 @@ const handlePickup = () => {
   pickUpForm.value.open()
 }
 
+/** 连接扫码枪 */
+const connectToSerialPort = async () => {
+  try {
+    // 判断浏览器支持串口通信
+    if (
+      'serial' in navigator &&
+      navigator.serial != null &&
+      typeof navigator.serial === 'object' &&
+      'requestPort' in navigator.serial
+    ) {
+      // 提示用户选择一个串口
+      port.value = await navigator.serial.requestPort()
+    } else {
+      message.error('浏览器不支持扫码枪连接,请更换浏览器重试')
+      return
+    }
+
+    // 获取用户之前授予该网站访问权限的所有串口。
+    ports.value = await navigator.serial.getPorts()
+
+    // console.log(port.value, ports.value);
+    // console.log(port.value)
+    // 等待串口打开
+    await port.value.open({ baudRate: 9600, dataBits: 8, stopBits: 2 })
+
+    // console.log(typeof port.value);
+    message.success('成功连接扫码枪')
+    serialPort.value = true
+    // readData(port.value);
+    readData()
+  } catch (error) {
+    // 处理连接串口出错的情况
+    console.log('Error connecting to serial port:', error)
+  }
+}
+
+/** 监听扫码枪输入 */
+const readData = async () => {
+  reader.value = port.value.readable.getReader()
+  let data = '' //扫码数据
+  // 监听来自串口的数据
+  while (true) {
+    const { value, done } = await reader.value.read()
+    if (done) {
+      // 允许稍后关闭串口
+      reader.value.releaseLock()
+      break
+    }
+    // 获取发送的数据
+    const serialData = new TextDecoder().decode(value)
+    data = `${data}${serialData}`
+    if (serialData.includes('\r')) {
+      //读取结束
+      let codeData = data.replace('\r', '')
+      data = '' //清空下次读取不会叠加
+      console.log(`二维码数据:${codeData}`)
+      //处理拿到数据逻辑
+      pickUpForm.value.open(codeData)
+    }
+  }
+}
+
+/** 断开扫码枪 */
+const cutPort = async () => {
+  if (port.value !== '') {
+    await reader.value.cancel()
+    await port.value.close()
+    port.value = ''
+    console.log('断开扫码枪连接')
+    message.success('已成功断开扫码枪连接')
+    serialPort.value = false
+  } else {
+    message.warning('请先连接或打开扫码枪')
+  }
+}
+
 /** 初始化 **/
-onMounted(() => {
-  getList()
-  getPickUpStoreList()
+onMounted(async () => {
+  await getPickUpStoreList()
+  if (pickUpStoreList.value.length === 0) {
+    message.error('当前登录人没绑定任何自提点')
+    loading.value = false
+    isUse.value = true
+    return
+  }
+
+  // 查询
+  queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
+  isUse.value = false
+  await getList()
 })
 </script>
 <style lang="scss" scoped>

+ 143 - 0
src/views/mall/trade/delivery/pickUpStore/DeliveryPickUpStoreBindForm.vue

@@ -0,0 +1,143 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="20%">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="门店名称" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入门店名称" readonly />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="门店店员" prop="verifyUserIds">
+            <el-button type="primary" @click="storeStaffTableSelect.open()">选择店员</el-button>
+          </el-form-item>
+          <!-- 店员列表 -->
+          <ContentWrap v-if="formData.verifyUsers.length > 0">
+            <el-table :data="formData.verifyUsers">
+              <el-table-column label="编号" align="center" prop="id" />
+              <el-table-column
+                label="用户昵称"
+                align="center"
+                prop="nickname"
+                :show-overflow-tooltip="true"
+              />
+              <el-table-column label="状态" align="center" key="status">
+                <template #default="scope">
+                  <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+                </template>
+              </el-table-column>
+              <el-table-column align="center" label="操作">
+                <template #default="scope">
+                  <el-button
+                    v-hasPermi="['trade:delivery:pick-up-store:delete']"
+                    link
+                    type="danger"
+                    @click="handleDelete(scope.row.id)"
+                  >
+                    删除
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </ContentWrap>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+
+  <!-- 选择员工弹窗 -->
+  <StoreStaffTableSelect ref="storeStaffTableSelect" @change="handleSelect" />
+</template>
+<script setup lang="ts">
+import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
+import StoreStaffTableSelect from './components/StoreStaffTableSelect.vue'
+import { DICT_TYPE } from '@/utils/dict'
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: undefined,
+  name: '',
+  verifyUserIds: [],
+  verifyUsers: []
+})
+const formRules = reactive({})
+const formRef = ref() // 表单 Ref
+const storeStaffTableSelect = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = '绑定自提门店员工'
+  resetForm()
+  formLoading.value = true
+  try {
+    formData.value = await DeliveryPickUpStoreApi.getDeliveryPickUpStore(id)
+  } finally {
+    formLoading.value = false
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = {
+      id: formData.value.id,
+      verifyUserIds: formData.value.verifyUsers.map((item: any) => item.id)
+    }
+    await DeliveryPickUpStoreApi.bindStoreStaffId(data)
+    message.success('绑定成功')
+    dialogVisible.value = false
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 处理选择员工操作 */
+const handleSelect = (checkedUsers: []) => {
+  formData.value.verifyUsers = checkedUsers
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  const index = formData.value.verifyUsers.findIndex((item: any) => {
+    if (item.id == id) {
+      return true
+    }
+  })
+  formData.value.verifyUsers.splice(index, 1)
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    verifyUserIds: [],
+    verifyUsers: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 265 - 0
src/views/mall/trade/delivery/pickUpStore/components/StoreStaffTableSelect.vue

@@ -0,0 +1,265 @@
+<!-- TODO 芋艿:这块后续抽个独立的组件出来 -->
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="60%">
+    <el-row :gutter="20">
+      <!-- 左侧部门树 -->
+      <el-col :span="4" :xs="24">
+        <ContentWrap class="h-1/1">
+          <DeptTree @node-click="handleDeptNodeClick" />
+        </ContentWrap>
+      </el-col>
+      <el-col :span="20" :xs="24">
+        <!-- 搜索 -->
+        <ContentWrap>
+          <el-form
+            class="-mb-15px"
+            :model="queryParams"
+            ref="queryFormRef"
+            :inline="true"
+            label-width="68px"
+          >
+            <el-form-item label="用户名称" prop="username">
+              <el-input
+                v-model="queryParams.username"
+                placeholder="请输入用户名称"
+                clearable
+                @keyup.enter="handleQuery"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="手机号码" prop="mobile">
+              <el-input
+                v-model="queryParams.mobile"
+                placeholder="请输入手机号码"
+                clearable
+                @keyup.enter="handleQuery"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-select
+                v-model="queryParams.status"
+                placeholder="用户状态"
+                clearable
+                class="!w-240px"
+              >
+                <el-option
+                  v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="创建时间" prop="createTime">
+              <el-date-picker
+                v-model="queryParams.createTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="datetimerange"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                class="!w-240px"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+              <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+            </el-form-item>
+          </el-form>
+        </ContentWrap>
+        <ContentWrap>
+          <el-table v-loading="loading" :data="list">
+            <el-table-column width="55">
+              <template #header>
+                <el-checkbox
+                  v-model="isCheckAll"
+                  :indeterminate="isIndeterminate"
+                  @change="handleCheckAll"
+                />
+              </template>
+              <template #default="{ row }">
+                <el-checkbox
+                  v-model="checkedStatus[row.id]"
+                  @change="(checked: boolean) => handleCheckOne(checked, row, true)"
+                />
+              </template>
+            </el-table-column>
+            <el-table-column label="用户编号" align="center" key="id" prop="id" />
+            <el-table-column
+              label="用户名称"
+              align="center"
+              prop="username"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="用户昵称"
+              align="center"
+              prop="nickname"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="部门"
+              align="center"
+              key="deptName"
+              prop="deptName"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
+            <el-table-column label="状态" key="status">
+              <template #default="scope">
+                <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="创建时间"
+              align="center"
+              prop="createTime"
+              :formatter="dateFormatter"
+              width="180"
+            />
+          </el-table>
+          <Pagination
+            :total="total"
+            v-model:page="queryParams.pageNo"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </ContentWrap>
+      </el-col>
+    </el-row>
+    <template #footer>
+      <el-button type="primary" @click="handleEmitChange">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as UserApi from '@/api/system/user'
+import DeptTree from '@/views/system/user/DeptTree.vue'
+
+// 是否全选
+const isCheckAll = ref(false)
+// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
+const isIndeterminate = ref(false)
+// 选中的活动
+const checkedUsers = ref([])
+// 选中状态:key为用户ID,value为是否选中
+const checkedStatus = ref<Record<string, boolean>>({})
+
+const dialogTitle = '选择店员'
+const dialogVisible = ref(false)
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  username: undefined,
+  mobile: undefined,
+  status: undefined,
+  deptId: undefined,
+  roleId: 5,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserApi.getUserPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+/** 处理部门被点击 */
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  await getList()
+}
+
+/** 打开弹窗 */
+const open = async () => {
+  dialogVisible.value = true
+  loading.value = true
+  try {
+    await getList()
+  } finally {
+    loading.value = false
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 全选/全不选 */
+const handleCheckAll = (checked: boolean) => {
+  isCheckAll.value = checked
+  isIndeterminate.value = false
+
+  list.value.forEach((combinationActivity) => handleCheckOne(checked, combinationActivity, false))
+}
+
+/**
+ * 选中一行
+ * @param checked 是否选中
+ * @param combinationActivity 活动
+ * @param isCalcCheckAll 是否计算全选
+ */
+const handleCheckOne = (checked: boolean, combinationActivity, isCalcCheckAll: boolean) => {
+  if (checked) {
+    checkedUsers.value.push(combinationActivity as never)
+    checkedStatus.value[combinationActivity.id] = true
+  } else {
+    const index = findCheckedIndex(combinationActivity)
+    if (index > -1) {
+      checkedUsers.value.splice(index, 1)
+      checkedStatus.value[combinationActivity.id] = false
+      isCheckAll.value = false
+    }
+  }
+
+  // 计算全选框状态
+  if (isCalcCheckAll) {
+    calculateIsCheckAll()
+  }
+}
+
+// 查找活动在已选中活动列表中的索引
+const findCheckedIndex = (user) => checkedUsers.value.findIndex((item) => item.id === user.id)
+
+// 计算全选框状态
+const calculateIsCheckAll = () => {
+  isCheckAll.value = list.value.every((user) => checkedStatus.value[user.id])
+  // 计算中间状态:不是全部选中 && 任意一个选中
+  isIndeterminate.value =
+    !isCheckAll.value && list.value.some((user) => checkedStatus.value[user.id])
+}
+
+/** 多选完成 */
+const handleEmitChange = () => {
+  // 关闭弹窗
+  dialogVisible.value = false
+  emits('change', [...checkedUsers.value])
+}
+
+/** 确认选择时的触发事件 */
+const emits = defineEmits<{
+  change: [CombinationActivityApi: any]
+}>()
+</script>

+ 18 - 1
src/views/mall/trade/delivery/pickUpStore/index.vue

@@ -93,7 +93,7 @@
         prop="createTime"
         width="180"
       />
-      <el-table-column align="center" label="操作">
+      <el-table-column align="center" label="操作" min-width="110">
         <template #default="scope">
           <el-button
             v-hasPermi="['trade:delivery:pick-up-store:update']"
@@ -103,6 +103,14 @@
           >
             编辑
           </el-button>
+          <el-button
+            v-hasPermi="['trade:delivery:pick-up-store:update']"
+            link
+            type="primary"
+            @click="openFormBind(scope.row.id)"
+          >
+            绑定店员
+          </el-button>
           <el-button
             v-hasPermi="['trade:delivery:pick-up-store:delete']"
             link
@@ -115,12 +123,16 @@
       </el-table-column>
     </el-table>
   </ContentWrap>
+
   <!-- 表单弹窗:添加/修改 -->
   <DeliveryPickUpStoreForm ref="formRef" @success="getList" />
+  <!-- 表单弹窗:绑定店员 -->
+  <DeliveryPickUpStoreBindForm ref="formBindRef" />
 </template>
 <script lang="ts" name="DeliveryPickUpStore" setup>
 import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
 import DeliveryPickUpStoreForm from './PickUpStoreForm.vue'
+import DeliveryPickUpStoreBindForm from './DeliveryPickUpStoreBindForm.vue'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 
@@ -146,6 +158,11 @@ const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
+const formBindRef = ref()
+const openFormBind = (id?: number) => {
+  formBindRef.value.open(id)
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {

+ 14 - 6
src/views/mall/trade/order/form/OrderPickUpForm.vue

@@ -13,7 +13,7 @@
       </el-form-item>
     </el-form>
     <template #footer>
-      <el-button type="primary" :disabled="formLoading" @click="getOrderByPickUpVerifyCode">
+      <el-button type="primary" :disabled="formLoading" @click="getOrderByPickUpVerifyCodeClick">
         查询
       </el-button>
       <el-button @click="dialogVisible = false">取 消</el-button>
@@ -52,9 +52,14 @@ const formRef = ref() // 表单 Ref
 const orderDetails = ref<OrderVO>({})
 
 /** 打开弹窗 */
-const open = async () => {
+const open = async (pickUpVerifyCode: string) => {
   resetForm()
-  dialogVisible.value = true
+  if(pickUpVerifyCode != null){
+    formData.value.pickUpVerifyCode = pickUpVerifyCode;
+    await getOrderByPickUpVerifyCode()
+  }else{
+    dialogVisible.value = true
+  }
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -83,18 +88,21 @@ const resetForm = () => {
   formRef.value?.resetFields()
 }
 
-/** 查询核销码对应的订单 */
-const getOrderByPickUpVerifyCode = async () => {
+const getOrderByPickUpVerifyCodeClick = async () => {
   // 校验表单
   if (!formRef) return
   const valid = await formRef.value.validate()
   if (!valid) return
+  await getOrderByPickUpVerifyCode()
+}
 
+/** 查询核销码对应的订单 */
+const getOrderByPickUpVerifyCode = async () => {
   formLoading.value = true
   const data = await TradeOrderApi.getOrderByPickUpVerifyCode(formData.value.pickUpVerifyCode)
   formLoading.value = false
   if (data?.deliveryType !== DeliveryTypeEnum.PICK_UP.type) {
-    message.error('请输入正确的核销码')
+    message.error('未查询到订单')
     return
   }
   if (data?.status !== TradeOrderStatusEnum.UNDELIVERED.status) {

+ 1 - 1
src/views/mall/trade/order/index.vue

@@ -351,7 +351,7 @@ const deliveryExpressList = ref<DeliveryExpressApi.DeliveryExpressVO[]>([]) // 
 /** 初始化 **/
 onMounted(async () => {
   await getList()
-  pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
+  pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
   deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList()
 })
 </script>

+ 1 - 1
src/views/member/user/detail/UserOrderList.vue

@@ -273,7 +273,7 @@ const openDetail = (id: number) => {
 /** 初始化 **/
 onMounted(async () => {
   await getList()
-  pickUpStoreList.value = await PickUpStoreApi.getListAllSimple()
+  pickUpStoreList.value = await PickUpStoreApi.getSimpleDeliveryPickUpStoreList()
   deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList()
 })
 </script>

+ 2 - 2
src/views/member/user/index.vue

@@ -140,7 +140,7 @@
                 'member:user:update',
                 'member:user:update-level',
                 'member:user:update-point',
-                'member:user:update-balance'
+                'pay:wallet:update-balance'
               ]"
               @command="(command) => handleCommand(command, scope.row)"
             >
@@ -169,7 +169,7 @@
                     修改积分
                   </el-dropdown-item>
                   <el-dropdown-item
-                    v-if="checkPermi(['member:user:update-balance'])"
+                    v-if="checkPermi(['pay:wallet:update-balance'])"
                     command="handleUpdateBlance"
                   >
                     修改余额

+ 6 - 1
src/views/pay/app/components/AppForm.vue

@@ -30,6 +30,9 @@
       <el-form-item label="退款结果的回调地址" prop="refundNotifyUrl">
         <el-input v-model="formData.refundNotifyUrl" placeholder="请输入退款结果的回调地址" />
       </el-form-item>
+      <el-form-item label="转账结果的回调地址" prop="transferNotifyUrl">
+        <el-input v-model="formData.transferNotifyUrl" placeholder="请输入转账结果的回调地址" />
+      </el-form-item>
       <el-form-item label="备注" prop="remark">
         <el-input v-model="formData.remark" placeholder="请输入备注" />
       </el-form-item>
@@ -62,7 +65,8 @@ const formData = ref({
   status: CommonStatusEnum.ENABLE,
   remark: undefined,
   orderNotifyUrl: undefined,
-  refundNotifyUrl: undefined
+  refundNotifyUrl: undefined,
+  transferNotifyUrl: undefined
 })
 const formRules = reactive({
   name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
@@ -126,6 +130,7 @@ const resetForm = () => {
     remark: undefined,
     orderNotifyUrl: undefined,
     refundNotifyUrl: undefined,
+    transferNotifyUrl: undefined,
     appKey: undefined
   }
   formRef.value?.resetFields()