!595 bpm 仿钉钉/飞书模式

Merge pull request !595 from 芋道源码/feature/bpm
This commit is contained in:
芋道源码
2024-11-23 02:36:14 +00:00
committed by Gitee
95 changed files with 6948 additions and 4010 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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为用户IDvalue为是否选中
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>

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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>