新增排队叫号功能
This commit is contained in:
parent
0711d4b07d
commit
4b54fdaff1
15849
dist-electron/main.js
15849
dist-electron/main.js
File diff suppressed because one or more lines are too long
157
package.json
157
package.json
|
|
@ -1,79 +1,80 @@
|
|||
{
|
||||
"name": "vite-electron",
|
||||
"private": true,
|
||||
"version": "1.4.25",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "chcp 65001 && vite",
|
||||
"build": "node ./addVersion.js && vite build && electron-builder",
|
||||
"build:test": "vite build --mode test && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"build:win": "node ./addVersion.js && vite build && electron-builder --w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.6.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"electron-pos-printer": "^1.3.6",
|
||||
"electron-pos-printer-vue": "^1.0.9",
|
||||
"element-plus": "^2.4.3",
|
||||
"js-md5": "^0.8.3",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"serialport": "^12.0.0",
|
||||
"swiper": "^11.1.1",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"electron": "^28.2.3",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"path": "^0.12.7",
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^13.3.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-electron": "^0.15.4",
|
||||
"vite-plugin-electron-renderer": "^0.14.5"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.cashierdesktop.app",
|
||||
"productName": "银收客",
|
||||
"asar": true,
|
||||
"files": [
|
||||
"./dist/**/*",
|
||||
"./dist-electron/**/*"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "build",
|
||||
"output": "release"
|
||||
},
|
||||
"win": {
|
||||
"icon": "./public/logo.ico",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowElevation": true,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "./public/logo.ico",
|
||||
"uninstallerIcon": "./public/logo.ico",
|
||||
"installerHeaderIcon": "./public/logo.ico",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "vite-electron",
|
||||
"private": true,
|
||||
"version": "1.4.25",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "chcp 65001 && vite",
|
||||
"build": "node ./addVersion.js && vite build && electron-builder",
|
||||
"build:test": "vite build --mode test && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"build:win": "node ./addVersion.js && vite build && electron-builder --w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.6.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"electron-pos-printer": "^1.3.6",
|
||||
"electron-pos-printer-vue": "^1.0.9",
|
||||
"element-plus": "^2.4.3",
|
||||
"js-md5": "^0.8.3",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"reconnecting-websocket": "^4.4.0",
|
||||
"serialport": "^12.0.0",
|
||||
"speak-tts": "^2.0.8",
|
||||
"swiper": "^11.1.1",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.3.8",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"electron": "^28.2.3",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-rebuild": "^3.2.9",
|
||||
"path": "^0.12.7",
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^13.3.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-electron": "^0.15.4",
|
||||
"vite-plugin-electron-renderer": "^0.14.5"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.cashierdesktop.app",
|
||||
"productName": "银收客",
|
||||
"asar": true,
|
||||
"files": [
|
||||
"./dist/**/*",
|
||||
"./dist-electron/**/*"
|
||||
],
|
||||
"directories": {
|
||||
"buildResources": "build",
|
||||
"output": "release"
|
||||
},
|
||||
"win": {
|
||||
"icon": "./public/logo.ico",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowElevation": true,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "./public/logo.ico",
|
||||
"uninstallerIcon": "./public/logo.ico",
|
||||
"installerHeaderIcon": "./public/logo.ico",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* 排队叫号
|
||||
*/
|
||||
import request from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 记录获取
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callRecord(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/callTable/callRecord",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌型列表
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callTable(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/callTable",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加桌型
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function addCallTable(data) {
|
||||
return request({
|
||||
method: data.id ? "put" : "post",
|
||||
url: "/callTable",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除桌型
|
||||
* @param {*} data
|
||||
* @returns
|
||||
*/
|
||||
export function delCallTable(data) {
|
||||
return request({
|
||||
method: "delete",
|
||||
url: "/callTable",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置信息 获取
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callTableConfig(params) {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/callTable/config",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置信息 修改
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callTableConfigPut(data) {
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/callTable/config",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取号 排队列表获取
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callTableQueue(params) {
|
||||
return request({
|
||||
method: "GET",
|
||||
url: "/callTable/queue",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取号 手动取号
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function takeNumber(data) {
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/callTable/takeNumber",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取号 修改叫号状态
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function updateState(data) {
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/callTable/updateState",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知叫号
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function callTableCall(data) {
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/callTable/call",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
|
@ -87,6 +87,11 @@ const menus = ref([
|
|||
path: '/member',
|
||||
icon: 'User'
|
||||
},
|
||||
{
|
||||
label: '排队',
|
||||
path: '/queue',
|
||||
icon: 'Timer'
|
||||
},
|
||||
// {
|
||||
// label: '交班',
|
||||
// path: '/work',
|
||||
|
|
@ -233,7 +238,7 @@ defineExpose({
|
|||
}
|
||||
|
||||
&.more {
|
||||
margin-top: 120px;
|
||||
margin-top: 90px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<el-upload ref="uploadRef" v-model:file-list="fileList" :action="uploadUrl" :headers="headers" :list-type="listType"
|
||||
:multiple="multiple" :limit="limit" :on-exceed="handleExceed" :on-change="handleChange"
|
||||
:on-progress="handleProgress" :on-success="handleSuccess" :on-error="handleError" :before-upload="beforeUpload"
|
||||
:accept="accept" :disabled="disabled">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<template v-slot:tip>
|
||||
<div v-if="tip">{{ tip }}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import useStorage from '@/utils/useStorage'
|
||||
import { ElUpload, ElMessage } from 'element-plus';
|
||||
|
||||
const fileList = ref([])
|
||||
|
||||
// 定义接收的外部属性
|
||||
const props = defineProps({
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
default: import.meta.env.MODE == 'development' ? '/api/shopInfo/upload' : import.meta.env.VITE_API_URL + '/shopInfo/upload',
|
||||
},
|
||||
headers: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
token: useStorage.get("token"),
|
||||
loginName: useStorage.get("userInfo").loginName,
|
||||
clientType: 'pc'
|
||||
}),
|
||||
},
|
||||
listType: {
|
||||
type: String,
|
||||
default: 'picture-card',
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: 'image/jpeg,image/png,image/gif',
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
|
||||
// 内部引用上传组件实例
|
||||
const uploadRef = ref(null);
|
||||
|
||||
// 处理文件超出数量限制的情况
|
||||
const handleExceed = (files, fileList) => {
|
||||
ElMessage.warning(`超出最大允许上传图片数量:${props.limit}`);
|
||||
};
|
||||
|
||||
// 处理文件状态改变
|
||||
const handleChange = (file, fileList) => {
|
||||
console.log('图片文件状态改变', file, fileList);
|
||||
};
|
||||
|
||||
// 处理上传进度
|
||||
const handleProgress = (event, file, fileList) => {
|
||||
console.log('图片上传进度', event.percent);
|
||||
};
|
||||
|
||||
// 处理上传成功
|
||||
const handleSuccess = (response, file, fileList) => {
|
||||
ElMessage.success('图片上传成功');
|
||||
console.log('图片上传成功响应', response);
|
||||
emits('success', response.data)
|
||||
};
|
||||
|
||||
// 处理上传失败
|
||||
const handleError = (error, file, fileList) => {
|
||||
ElMessage.error('图片上传失败');
|
||||
console.error('图片上传失败原因', error);
|
||||
};
|
||||
|
||||
// 上传前校验,这里主要校验图片格式
|
||||
const beforeUpload = (file) => {
|
||||
const isImage = props.accept.split(',').some(format => file.type === format.trim());
|
||||
if (!isImage) {
|
||||
ElMessage.error('请选择正确格式的图片文件');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function init(arr) {
|
||||
fileList.value = arr
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -57,6 +57,14 @@ const routes = [
|
|||
},
|
||||
component: () => import("@/views/member/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/queue",
|
||||
name: "queue",
|
||||
meta: {
|
||||
index: 1,
|
||||
},
|
||||
component: () => import("@/views/queue/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/work",
|
||||
name: "work",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<el-dialog v-model="showAddTable" :title="addTabForm.id ? '编辑桌型' : '新增桌型'" top="10vh" @closed="addTabFormReset">
|
||||
<el-form ref="AddTabFormRef" :model="addTabForm" :rules="addTabFormRules" label-position="left"
|
||||
label-width="100">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="addTabForm.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="note">
|
||||
<el-input v-model="addTabForm.note" placeholder="请输入描述,例如1-2人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="等待时间" prop="waitTime">
|
||||
<el-input v-model="addTabForm.waitTime" placeholder="0">
|
||||
<template #append>分钟/1桌</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="号码前缀" prop="prefix">
|
||||
<el-input v-model="addTabForm.prefix" placeholder="请输入英文字母,不支持中文" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始号码" prop="start">
|
||||
<el-input v-model="addTabForm.start" placeholder="请输入开始号码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="过号保留">
|
||||
<el-input v-model="addTabForm.nearNum" :disabled="!addTabForm.isPostpone" placeholder="临近几桌提醒">
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="addTabForm.isPostpone" :true-value="1" :false-value="0" label="开启顺延" />
|
||||
</template>
|
||||
<template #append>桌</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="footer" style="display: flex;">
|
||||
<el-button style="width: 100%" @click="showAddTable = false">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" style="width: 100%" :loading="addTabFormLoading" @click="addTabConfirmHandle">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { addCallTable } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
|
||||
const showAddTable = ref(false)
|
||||
const addTabFormLoading = ref(false)
|
||||
const AddTabFormRef = ref(null)
|
||||
const resetAddTabForm = ref({})
|
||||
const addTabForm = ref({
|
||||
name: '',
|
||||
note: '',
|
||||
waitTime: '',
|
||||
prefix: '',
|
||||
start: '',
|
||||
isPostpone: 0,
|
||||
nearNum: ''
|
||||
})
|
||||
|
||||
const addTabFormRules = ref({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: ' ',
|
||||
trigger: 'blur',
|
||||
}
|
||||
],
|
||||
waitTime: [
|
||||
{
|
||||
required: true,
|
||||
message: ' ',
|
||||
trigger: 'blur',
|
||||
}
|
||||
],
|
||||
prefix: [
|
||||
{
|
||||
required: true,
|
||||
message: ' ',
|
||||
trigger: 'blur',
|
||||
}
|
||||
],
|
||||
start: [
|
||||
{
|
||||
required: true,
|
||||
message: ' ',
|
||||
trigger: 'blur',
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
// 初始化
|
||||
function addTabFormReset() {
|
||||
addTabForm.value = { ...resetAddTabForm.value }
|
||||
AddTabFormRef.value.resetFields()
|
||||
}
|
||||
|
||||
// 提交
|
||||
function addTabConfirmHandle() {
|
||||
AddTabFormRef.value.validate(async valid => {
|
||||
try {
|
||||
if (valid) {
|
||||
addTabFormLoading.value = true
|
||||
addTabForm.value.shopId = store.userInfo.shopId
|
||||
if (addTabForm.value.id) {
|
||||
addTabForm.value.callTableId = addTabForm.value.id
|
||||
}
|
||||
const res = await addCallTable(addTabForm.value)
|
||||
addTabFormLoading.value = false
|
||||
showAddTable.value = false
|
||||
ElMessage.success(addTabForm.value.id ? '编辑成功' : '添加成功')
|
||||
emits('success')
|
||||
}
|
||||
} catch (error) {
|
||||
addTabFormLoading.value = false
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function show(obj) {
|
||||
if (obj && obj.id) {
|
||||
addTabForm.value = { ...obj }
|
||||
}
|
||||
showAddTable.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
resetAddTabForm.value = { ...addTabForm.value }
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<!-- 取号 -->
|
||||
<template>
|
||||
<el-dialog title="取号" v-model="visible" @closed="onClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item label="选择桌型">
|
||||
<el-radio-group v-model="form.callTableId">
|
||||
<el-radio :value="item.id" border v-for="item in list" :key="item.id">{{ item.name
|
||||
}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请填写手机号" style="width: 60%;" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="footer" style="display: flex;padding-top: 30px;">
|
||||
<el-button style="width: 100%" @click="visible = false">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { takeNumber } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const list = ref([])
|
||||
|
||||
const loading = ref(false)
|
||||
const formRef = ref(null)
|
||||
const resetForm = ref({})
|
||||
const form = ref({
|
||||
callTableId: '',
|
||||
shopId: '',
|
||||
phone: '',
|
||||
note: '',
|
||||
name: ''
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
phone: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
let reg = /^(?:(?:\+|00)86)?1\d{10}$/
|
||||
if (!reg.test(form.value.phone)) {
|
||||
callback(new Error('手机号码不正确'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 提交
|
||||
function confirmHandle() {
|
||||
formRef.value.validate(async vaild => {
|
||||
try {
|
||||
if (vaild) {
|
||||
loading.value = true
|
||||
form.value.shopId = store.userInfo.shopId
|
||||
form.value.note = list.value.find(item => item.id == form.value.callTableId).note
|
||||
form.value.name = list.value.find(item => item.id == form.value.callTableId).name
|
||||
await takeNumber(form.value)
|
||||
loading.value = false
|
||||
ElMessage.success('取号成功')
|
||||
emits('success')
|
||||
visible.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function onClose() {
|
||||
form.value = { ...resetForm.value }
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
function show(arr) {
|
||||
visible.value = true
|
||||
list.value = [...arr]
|
||||
form.value.callTableId = list.value[0].id
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
resetForm.value = { ...form }
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<!-- 叫号记录 -->
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="叫号记录" @closed="reset" width="80vw">
|
||||
<el-table :data="tableData.list" border style="height: 84%;" height="50vh">
|
||||
<el-table-column label="桌号" prop="name"></el-table-column>
|
||||
<el-table-column label="桌型" prop="note"></el-table-column>
|
||||
<el-table-column label="手机号" prop="phone"></el-table-column>
|
||||
<el-table-column label="状态" prop="state">
|
||||
<template v-slot="scope">
|
||||
{{ statusList[scope.row.state].text }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" prop="callTime" width="200">
|
||||
<template v-slot="scope">{{ dayjs(scope.row.callTime).format('YYYY-MM-DD HH:mm:ss') }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination" style="padding-top:15px;display: flex;justify-content: flex-end;">
|
||||
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
|
||||
layout="total, prev, pager, next" :total="tableData.total" background @current-change="paginationChange"
|
||||
@size-change="paginationChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { dayjs } from 'element-plus'
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { callRecord } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const tableData = reactive({
|
||||
loading: false,
|
||||
list: [],
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0
|
||||
})
|
||||
const statusList = {
|
||||
'-1': {
|
||||
type: 'warning',
|
||||
text: '已取消'
|
||||
},
|
||||
0: {
|
||||
type: 'danger',
|
||||
text: '排队中'
|
||||
},
|
||||
1: {
|
||||
type: 'success',
|
||||
text: '叫号中'
|
||||
},
|
||||
2: {
|
||||
type: 'success',
|
||||
text: '已入座'
|
||||
},
|
||||
3: {
|
||||
type: 'success',
|
||||
text: '已过号'
|
||||
}
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
function paginationChange(e) {
|
||||
callRecordAjax()
|
||||
}
|
||||
|
||||
// 记录数据
|
||||
async function callRecordAjax() {
|
||||
try {
|
||||
tableData.loading = true
|
||||
const res = await callRecord({
|
||||
callTableId: '',
|
||||
shopId: store.userInfo.shopId,
|
||||
page: tableData.page,
|
||||
size: tableData.size
|
||||
})
|
||||
tableData.loading = false
|
||||
tableData.list = res.records
|
||||
tableData.total = res.total
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
tableData.page = 1
|
||||
}
|
||||
|
||||
function show() {
|
||||
dialogVisible.value = true
|
||||
callRecordAjax()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<!-- 播报结果 -->
|
||||
<template>
|
||||
<el-dialog title="提示" v-model="visible">
|
||||
<div class="content">
|
||||
<div style="font-size: 18px;">正在叫号,请稍等</div>
|
||||
<el-alert :title="statusList[item.status].text" :type="statusList[item.status].type" :closable="false" />
|
||||
</div>
|
||||
<div class="footer" style="display: flex;">
|
||||
<el-button style="width: 100%" @click="confirmHandle(2)">完成</el-button>
|
||||
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle(3)">过号</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { updateState } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const emits = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const item = ref({})
|
||||
const loading = ref(false)
|
||||
const statusList = {
|
||||
'-1': {
|
||||
type: 'warning',
|
||||
text: '用户未订阅'
|
||||
},
|
||||
0: {
|
||||
type: 'danger',
|
||||
text: '失败'
|
||||
},
|
||||
1: {
|
||||
type: 'success',
|
||||
text: '成功'
|
||||
}
|
||||
}
|
||||
|
||||
// 过号修改状态
|
||||
async function confirmHandle(state) {
|
||||
try {
|
||||
await updateState({
|
||||
shopId: store.userInfo.shopId,
|
||||
callQueueId: item.value.id,
|
||||
state: state
|
||||
})
|
||||
visible.value = false
|
||||
emits('success')
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function show(obj) {
|
||||
visible.value = true
|
||||
item.value = { ...obj }
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="基本设置" width="90vw" top="5vh" @closed="emits('success')">
|
||||
<div class="scroll_y">
|
||||
<el-form ref="formRef" :model="form" label-position="left" label-width="140">
|
||||
<el-form-item label="排队页面地址">{{ config.pageAddress }}</el-form-item>
|
||||
<el-form-item label="线上取号">
|
||||
<el-switch v-model="form.isOnline" :active-value="1" :inactive-value="0"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图片">
|
||||
<UploadImg ref="UploadImgRef" @success="e => form.bgCover = e" />
|
||||
</el-form-item>
|
||||
<el-form-item label="桌型">
|
||||
<el-table :data="tableData.list" border v-loading="tableData.loading">
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="描述" prop="note"></el-table-column>
|
||||
<el-table-column label="等待时间[桌]" prop="waitTime"></el-table-column>
|
||||
<el-table-column label="号码前缀" prop="prefix"></el-table-column>
|
||||
<el-table-column label="开始号码" prop="start"></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #header>
|
||||
<el-button type="primary" @click="AddTabRef.show()">添加</el-button>
|
||||
</template>
|
||||
<template v-slot="scope">
|
||||
<div style="display: flex;gap: 10px;">
|
||||
<el-text type="primary" @click="AddTabRef.show(scope.row)">编辑</el-text>
|
||||
<el-text type="danger" @click="delCallTableHandle(scope.row)">删除</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<div class="title" style="padding-bottom: 20px;">通知模板</div>
|
||||
<el-form-item label="排队成功提醒">
|
||||
<el-input disabled style="width: 50%;" v-model="config.successMsg"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排队即将排到通知">
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
<el-input disabled style="width: 50%;" v-model="config.nearMsg"></el-input>
|
||||
<el-input v-model="config.nearNum" disabled style="margin-top: 10px;">
|
||||
<template #prepend>前面等待</template>
|
||||
<template #append>桌时提醒</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="排队到号提醒">
|
||||
<el-input disabled style="width: 50%;" v-model="config.callingMsg"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button style="width: 100%" @click="dialogVisible = false">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" style="width: 100%" :loading="loading" @click="confirmHandle">
|
||||
确认
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<AddTab ref="AddTabRef" @success="getTableAjax" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import UploadImg from '@/components/uploadImg.vue'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import AddTab from './addTab.vue'
|
||||
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { callTable, delCallTable, callTableConfig, callTableConfigPut } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const AddTabRef = ref(null)
|
||||
const formRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const form = ref({
|
||||
isOnline: 1,
|
||||
bgCover: '',
|
||||
nearNum: '',
|
||||
})
|
||||
|
||||
function beforeAvatarUpload(file) {
|
||||
console.log(2, file);
|
||||
}
|
||||
|
||||
// 桌型
|
||||
const tableData = reactive({
|
||||
loading: false,
|
||||
list: []
|
||||
})
|
||||
|
||||
// 显示基本配置
|
||||
function show() {
|
||||
dialogVisible.value = true
|
||||
|
||||
getTableAjax()
|
||||
callTableConfigAjax()
|
||||
}
|
||||
|
||||
// 删除桌型
|
||||
function delCallTableHandle(row) {
|
||||
ElMessageBox.confirm('确认要删除吗?', '注意').then(async () => {
|
||||
try {
|
||||
tableData.loading = true
|
||||
const res = await delCallTable({
|
||||
shopId: store.userInfo.shopId,
|
||||
callTableId: row.id
|
||||
})
|
||||
getTableAjax()
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
// 获取桌型列表
|
||||
async function getTableAjax() {
|
||||
try {
|
||||
tableData.loading = true
|
||||
const res = await callTable({
|
||||
page: 1,
|
||||
size: 100,
|
||||
shopId: store.userInfo.shopId,
|
||||
callTableId: '',
|
||||
state: ''
|
||||
})
|
||||
tableData.loading = false
|
||||
tableData.list = res.records
|
||||
} catch (error) {
|
||||
tableData.loading = false
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取配置信息
|
||||
const config = ref({})
|
||||
const UploadImgRef = ref(null)
|
||||
async function callTableConfigAjax() {
|
||||
try {
|
||||
const res = await callTableConfig({ shopId: store.userInfo.shopId })
|
||||
config.value = res
|
||||
form.value.nearNum = res.nearNum
|
||||
if (res.bgCover) {
|
||||
UploadImgRef.value.init([{ url: res.bgCover }])
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 提交
|
||||
const emits = defineEmits(['success'])
|
||||
async function confirmHandle() {
|
||||
try {
|
||||
loading.value = true
|
||||
form.value.shopId = store.userInfo.shopId
|
||||
const res = await callTableConfigPut(form.value)
|
||||
loading.value = false
|
||||
ElMessage.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
emits('success')
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
}
|
||||
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-icon.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.avatar-uploader .avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
$btmH: 50px;
|
||||
|
||||
.scroll_y {
|
||||
height: 65vh;
|
||||
overflow-y: auto;
|
||||
padding-bottom: $btmH;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
padding: 0 100px 0;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
height: $btmH;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1));
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: $btmH*-1;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
<template>
|
||||
<div class="content">
|
||||
<div class="card" style="flex: 1;">
|
||||
<div class="tab_head">
|
||||
<el-radio-group v-model="tabActive" @change="callTableQueueAjax">
|
||||
<el-radio-button label="全部" value=""></el-radio-button>
|
||||
<el-radio-button :label="item.name" :value="item.id" v-for="item in tabHeader"
|
||||
:key="item.id"></el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="btns">
|
||||
<el-button type="danger" @click="GetNumberRef.show(tabHeader)">取号</el-button>
|
||||
<el-button type="warning" @click="RecordRef.show()">叫号记录</el-button>
|
||||
<el-button type="primary" @click="SettingRef.show()">基本设置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="queue_list" v-loading="queueList.loading">
|
||||
<div class="item" v-for="item in queueList.list" :key="item.id">
|
||||
<div class="head">
|
||||
<div class="row">
|
||||
<div class="row_item">用户</div>
|
||||
<div class="row_item">号码</div>
|
||||
<div class="row_item">桌型</div>
|
||||
<div class="row_item">等待</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row_item">{{ item.phone }}</div>
|
||||
<div class="row_item">{{ item.callNum }}</div>
|
||||
<div class="row_item">{{ item.name }}</div>
|
||||
<div class="row_item">{{ item.waitingCount }}分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btm">
|
||||
<el-button @click="cancaleHandle(item)">取消</el-button>
|
||||
<el-button type="primary" @click="callHandle(item)">播报</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="empty">
|
||||
<el-empty description="暂无数据~" v-if="!queueList.list.length" style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<el-pagination v-model:current-page="queueList.page" v-model:page-size="queueList.size"
|
||||
layout="total, prev, pager, next" :total="queueList.total" background
|
||||
@current-change="queueListChange" @size-change="queueListChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 基本设置 -->
|
||||
<Setting ref="SettingRef" @success="callTableAjax" />
|
||||
<!-- 叫号记录 -->
|
||||
<Record ref="RecordRef" />
|
||||
<!-- 取号 -->
|
||||
<GetNumber ref="GetNumberRef" @success="callTableQueueAjax" />
|
||||
<!-- 播报状态 -->
|
||||
<ResultModal ref="ResultModalRef" @success="callTableQueueAjax" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Speech from 'speak-tts'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import Setting from './components/setting.vue'
|
||||
import Record from './components/record.vue'
|
||||
import GetNumber from './components/getNumber.vue'
|
||||
import ResultModal from './components/resultModal.vue'
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { callRecord, callTable, callTableQueue, updateState, callTableCall } from '@/api/queue.js'
|
||||
import { useUser } from "@/store/user.js"
|
||||
const store = useUser()
|
||||
|
||||
const SettingRef = ref(null)
|
||||
const RecordRef = ref(null)
|
||||
const GetNumberRef = ref(null)
|
||||
const ResultModalRef = ref(null)
|
||||
|
||||
// 获取桌型列表
|
||||
const tabHeader = ref([])
|
||||
const tabActive = ref('')
|
||||
async function callTableAjax() {
|
||||
try {
|
||||
const res = await callTable({
|
||||
page: 1,
|
||||
size: 100,
|
||||
shopId: store.userInfo.shopId,
|
||||
callTableId: '',
|
||||
state: ''
|
||||
})
|
||||
tabHeader.value = res.records
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 取号 排队列表获取 start
|
||||
const queueList = reactive({
|
||||
loading: false,
|
||||
list: [],
|
||||
page: 1,
|
||||
size: 8,
|
||||
total: 0
|
||||
})
|
||||
|
||||
function queueListChange() {
|
||||
callTableQueueAjax()
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
async function callTableQueueAjax() {
|
||||
try {
|
||||
queueList.loading = true
|
||||
const res = await callTableQueue({
|
||||
page: queueList.page,
|
||||
size: queueList.size,
|
||||
shopId: store.userInfo.shopId,
|
||||
callTableId: tabActive.value,
|
||||
state: ''
|
||||
})
|
||||
queueList.loading = false
|
||||
queueList.list = res.records
|
||||
queueList.total = res.total
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
queueList.loading = false
|
||||
}
|
||||
}
|
||||
// 取号 排队列表获取 end
|
||||
|
||||
// 取消排队
|
||||
function cancaleHandle(item) {
|
||||
ElMessageBox.confirm('确定要取消排队吗?', '注意').then(async () => {
|
||||
try {
|
||||
await updateState({
|
||||
shopId: store.userInfo.shopId,
|
||||
callQueueId: item.id,
|
||||
state: '-1'
|
||||
})
|
||||
ElMessage.success('已取消')
|
||||
callTableQueueAjax()
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}).catch(() => { })
|
||||
}
|
||||
|
||||
// 播报
|
||||
async function callHandle(item) {
|
||||
try {
|
||||
startSpeech(`请${item.callNum}用餐`)
|
||||
const res = await callTableCall({
|
||||
shopId: store.userInfo.shopId,
|
||||
callQueueId: item.id,
|
||||
})
|
||||
ResultModalRef.value.show({
|
||||
...item,
|
||||
status: res.state
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 播放声音
|
||||
const speech = new Speech()
|
||||
function speechInit(params) {
|
||||
speech.init({
|
||||
volume: 1, // 音量
|
||||
lang: 'zh-CN', // 语言
|
||||
rate: 1, // 语速1正常语速,2倍语速就写2
|
||||
pitch: 1, // 音调
|
||||
splitSentences: true, // 在句子结束时暂停
|
||||
listeners: {
|
||||
// 事件
|
||||
onvoiceschanged: voices => {
|
||||
// console.log('事件声音已更改', voices);
|
||||
},
|
||||
},
|
||||
}).then(data => {
|
||||
console.log('语音已准备好,声音可用', data);
|
||||
}).catch(err => {
|
||||
console.log('初始化发生错误', err);
|
||||
})
|
||||
}
|
||||
|
||||
// 开始播报
|
||||
function startSpeech(text) {
|
||||
speech.speak({
|
||||
text: text, //这里使用文字或者i18n 都可以 看自己需求
|
||||
queue: true,
|
||||
listeners: {
|
||||
// 开始播放
|
||||
onstart: () => {
|
||||
console.log('Start utterance')
|
||||
},
|
||||
// 判断播放是否完毕
|
||||
onend: () => {
|
||||
console.log('End utterance')
|
||||
},
|
||||
// 恢复播放
|
||||
onresume: () => {
|
||||
console.log('Resume utterance')
|
||||
},
|
||||
}
|
||||
}).then(() => {
|
||||
console.log('成功!');
|
||||
}).catch(e => {
|
||||
console.error('发生错误:', e);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
callTableAjax()
|
||||
callTableQueueAjax()
|
||||
|
||||
if (speech.hasBrowserSupport()) {
|
||||
console.log('语音播报加载成功,支持播报');
|
||||
speechInit()
|
||||
} else {
|
||||
console.log('当前浏览器不支持语音播报');
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
padding: 15px;
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.tab_head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.queue_list {
|
||||
height: 80vh;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
grid-column-gap: 15px;
|
||||
grid-row-gap: 15px;
|
||||
padding: 15px 0;
|
||||
position: relative;
|
||||
|
||||
.empty {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.item {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
|
||||
.head {
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.row_item {
|
||||
flex: 1;
|
||||
|
||||
&:last-child {
|
||||
flex: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btm {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue