51 Commits

Author SHA1 Message Date
gyq
75e46ec6dd 优化首页 2026-04-02 16:59:48 +08:00
gyq
c87c999d42 新增欢迎首页 2026-04-01 10:39:24 +08:00
f648a7ea5e Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into prod 2026-04-01 10:20:32 +08:00
0ab8852656 修复退款方式不显示问题 2026-03-31 17:22:26 +08:00
03883e1b26 修复optType:stock 传参时多了个空格问题 2026-03-31 14:11:01 +08:00
gyq
b9308c1f21 网络打印机新增声音开关 2026-03-31 13:48:59 +08:00
be12089ffe 修复库存开关按钮无效问题,增加商品上下架sku联动,修复商品绑定耗材编辑回显问题 2026-03-30 18:07:58 +08:00
e5cdf0a3dc 修复耗材显示不全问题 2026-03-30 16:09:01 +08:00
51812fb0cc 台桌列表页面金额显示保持两位小数 2026-03-28 18:51:43 +08:00
6e39a94f01 台桌页面增加统计显示 2026-03-28 18:50:02 +08:00
d6a33aea3b 代码合并 2026-03-28 18:19:42 +08:00
397f4b2bcf 增加用户是否可以自主结账开关,增加台桌更多信息显示 2026-03-28 18:16:57 +08:00
gyq
2f2a152b46 优化订单跳转代客下单拉取不到历史数据的问题 2026-03-28 17:38:38 +08:00
gyq
e3ddd70ce6 优化代客下单 2026-03-27 17:17:52 +08:00
gyq
3c68d25286 Merge branch 'prod' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into test 2026-03-27 17:13:26 +08:00
gyq
f5105cad87 本地服务切换到测试接口 2026-03-27 10:09:08 +08:00
gyq
372a1b35ec 更新优化换桌功能 2026-03-27 09:32:02 +08:00
c734bbe353 Merge branch 'prod' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into prod 2026-03-26 10:55:40 +08:00
d5a2f0ba24 修复支付方式不显示问题 2026-03-26 10:55:35 +08:00
gyq
e058aaacf7 修复存酒会员选择 2026-03-26 09:35:40 +08:00
2a47effa45 修复清空历史订单时消失又出现问题 2026-03-20 10:45:47 +08:00
gyq
b3d153ab86 tablecode 2026-03-19 17:25:24 +08:00
gyq
392d4aa676 优化存酒新增会员筛选 2026-03-18 10:45:24 +08:00
gyq
40b9d09671 显示应用中心 2026-03-17 09:24:48 +08:00
gyq
4fcd2cc2cf 优化 2026-03-16 10:03:26 +08:00
gyq
13890e3f8d 新增帮助中心 2026-03-13 16:30:56 +08:00
gyq
d30407b26f 优化全民股东 2026-03-06 14:38:42 +08:00
gyq
647e32567b Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into test 2026-02-05 14:55:29 +08:00
gyq
ead5aaf458 财务报表新增挂账退款金额 2026-02-05 14:55:23 +08:00
e2fa92d33d Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into test 2026-02-05 10:13:33 +08:00
f01cbf0f5b 修复代客下单选择用户搜索无用问题 2026-02-05 10:13:27 +08:00
gyq
ea2b9fcf9e 优化 2026-02-05 09:10:45 +08:00
gyq
b87b4c000e 优化财务报表 2026-02-04 16:32:55 +08:00
gyq
ddd39ec031 优化首页字段显示 2026-02-04 14:55:32 +08:00
gyq
76af0f5a83 1.新增批量导入 2.新增财务报表 2026-02-04 14:51:29 +08:00
a5b11cf4f4 修改管理员店铺列表筛选,商户号修改为手机号,正式/测试修改为商户版本, 2026-02-03 10:26:09 +08:00
gyq
782bd19e4e 修复添加分销员分店不能选择的问题 2026-01-31 15:22:17 +08:00
gyq
25a8fbbe9f Merge branch 'prod' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into test 2026-01-31 09:27:27 +08:00
gyq
058c8dcadc 新增导出 2026-01-31 09:26:25 +08:00
gyq
20de427175 Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into prod 2026-01-30 16:05:18 +08:00
gyq
3855ed4b75 新增商品导出 2026-01-30 16:01:44 +08:00
gyq
60ea128a10 优化店铺信息配置改变后取餐模式不生效的问题 2026-01-29 18:24:57 +08:00
gyq
45727d8005 优化 2026-01-29 10:35:34 +08:00
gyq
c035442628 优化进件查询 2026-01-29 10:09:30 +08:00
gyq
62c6b755af 优化添加点餐页轮播图 2026-01-28 17:47:25 +08:00
gyq
71bec03475 用户列表新增导出功能 2026-01-28 16:16:04 +08:00
gyq
ca182dc325 新增分享配置 2026-01-28 15:02:24 +08:00
gyq
ee3a54abf0 Merge branch 'test' of https://newgitea.sxczgkj.cn/czg_team/cashier-web into prod 2026-01-26 18:00:14 +08:00
84116bb951 更新ysk工具库 2026-01-26 15:51:20 +08:00
gyq
000f715bd8 更改分销字段名称 2026-01-26 15:30:19 +08:00
gyq
9991248b08 优化店铺装修默认选择首页 2026-01-23 10:59:57 +08:00
78 changed files with 4665 additions and 1109 deletions

View File

@@ -10,6 +10,8 @@ VITE_APP_BASE_API=/dev-api
# VITE_APP_API_URL=https://cashier.sxczgkj.com/ # 正式
VITE_APP_API_URL=http://192.168.1.42/ # 本地
VITE_APP_API_PHP_URL=http://192.168.1.42:8000 #php抖音美团测试环境
VITE_APP_API_PHP_IMPORT_URL=http://192.168.1.42:8789 #本地php批量导入
# VITE_APP_API_PHP_IMPORT_URL=https://diftcs.sxczgkj.com #本地线上php批量导入
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws

View File

@@ -12,6 +12,9 @@ VITE_APP_BASE_API = https://cashier.sxczgkj.com/
VITE_APP_API_PHP_URL=https://newblockwlx.sxczgkj.cn #php抖音美团正式环境
VITE_APP_API_PHP_IMPORT_URL=https://diftcs.sxczgkj.com #线上php批量导入
# WebSocket 端点(不配置则关闭),线上 ws://api.youlai.tech/ws ,本地 ws://localhost:8989/ws

View File

@@ -67,7 +67,7 @@
"vue-clipboard3": "^2.0.0",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0",
"ysk-utils": "^1.0.77"
"ysk-utils": "^1.0.85"
},
"devDependencies": {
"@commitlint/cli": "^19.7.1",
@@ -115,6 +115,7 @@
"vite-plugin-mock-dev-server": "^1.8.3",
"vite-plugin-strip-code": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-devtools": "^8.1.0",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.2.0"
},

View File

@@ -71,7 +71,16 @@ const API = {
method: "get",
params
});
}
},
// 导出
export(params: any) {
return request({
url: `${baseURL}/export`,
method: "get",
params,
responseType: 'blob'
});
},
}
export default API;
export interface getRequest {

View File

@@ -801,6 +801,68 @@ export function attendanceDetail(params) {
});
}
// 分享奖励基础:新增/修改
export function shareBasePost(data) {
return request({
url: `${Market_BaseUrl}/admin/shareBase`,
method: 'POST',
data
});
}
// 分享奖励基础 查询
export function shareBaseGet(params) {
return request({
url: `${Market_BaseUrl}/admin/shareBase`,
method: 'GET',
params
});
}
// 轮播图配置:新增/修改
export function shareCarouselPost(data) {
return request({
url: `${Market_BaseUrl}/admin/carousel`,
method: 'POST',
data
});
}
// 分享轮播图配置
export function shareCarouselGet(params) {
return request({
url: `${Market_BaseUrl}/admin/carousel`,
method: 'GET',
params
});
}
// 分享 轮播图配置 删除
export function shareCarouselDel(id) {
return request({
url: `${Market_BaseUrl}/admin/carousel/${id}`,
method: 'DELETE'
});
}
// 全民股东群聊 配置信息获取
export function disGroupGet(params) {
return request({
url: `${Market_BaseUrl}/admin/disGroup`,
method: 'get',
params
});
}
// 全民股东群聊:新增/修改
export function disGroupPost(data) {
return request({
url: `${Market_BaseUrl}/admin/disGroup`,
method: 'POST',
data
});
}

View File

@@ -72,6 +72,23 @@ const OrderApi = {
data
});
},
// 查询财务报表
financeSts(params: any) {
return request<any>({
url: `${Order_BaseUrl}/admin/finance/sts`,
method: "get",
params
});
},
// 导出财务报表
financeExport(params: any) {
return request<any>({
url: `${Order_BaseUrl}/admin/finance/export`,
method: "get",
params,
responseType: 'blob'
});
},
};
export default OrderApi;

View File

@@ -26,7 +26,6 @@ const Api = {
method: "get",
params,
responseType: 'blob'
});
},
};

View File

@@ -51,6 +51,15 @@ const AuthAPI = {
method: "delete",
});
},
// 导出常用单位
exportUnits(params: any) {
return request<any, Responseres>({
url: `${baseURL}/export`,
method: "get",
params,
responseType: 'blob'
});
}
};

View File

@@ -72,7 +72,7 @@ const AuthAPI = {
});
},
// 耗材列表
// 耗材列表分页
productcons(params: any) {
return request<any, Responseres>({
url: `/product/admin/product/cons/page`,
@@ -80,7 +80,14 @@ const AuthAPI = {
params
});
},
// 耗材列表
productconsList(params: any) {
return request<any, Responseres>({
url: `/product/admin/product/cons/list`,
method: "get",
params
});
},
// 上下架
onOff(data: any) {
return request<any, Responseres>({
@@ -181,6 +188,15 @@ const AuthAPI = {
method: "get",
params,
});
},
// 导出商品
exportProducts(params: any) {
return request<any, Responseres>({
url: `${baseURL}/export`,
method: "get",
params,
responseType: 'blob'
});
}
};

View File

@@ -48,7 +48,15 @@ const AuthAPI = {
method: "delete",
});
},
// 导出商品分类
exportCategories(params: any) {
return request<any, Responseres>({
url: `${baseURL}/export`,
method: "get",
params,
responseType: 'blob'
});
}
};

15
src/api/system/index.js Normal file
View File

@@ -0,0 +1,15 @@
import request from "@/utils/request";
import {
Account_BaseUrl,
Product_BaseUrl,
Market_BaseUrl,
System_BaseUrl
} from "@/api/config";
// 帮助中心
export function getHelp() {
return request({
url: `${System_BaseUrl + "/user/getHelp"}`,
method: 'get'
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
src/assets/index_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
src/assets/index_quick1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
src/assets/index_quick2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
src/assets/index_quick3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/assets/index_quick4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
src/assets/index_quick5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
src/assets/index_quick6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -3,68 +3,49 @@
<!-- 表格工具栏 -->
<div class="flex-x-between mb-[10px]">
<!-- 左侧工具栏 -->
<div>
<div style="display: flex;">
<template v-for="item in toolbar" :key="item">
<template v-if="typeof item === 'string'">
<!-- 新增 -->
<template v-if="item === 'add'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
type="primary"
icon="plus"
@click="handleToolbar(item)"
>
<el-button v-hasPerm="[`${contentConfig.pageName}:${item}`]" type="primary" icon="plus"
@click="handleToolbar(item)">
新增
</el-button>
</template>
<!-- 删除 -->
<template v-else-if="item === 'delete'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
type="danger"
icon="delete"
:disabled="removeIds.length === 0"
@click="handleToolbar(item)"
>
<el-button v-hasPerm="[`${contentConfig.pageName}:${item}`]" type="danger" icon="delete"
:disabled="removeIds.length === 0" @click="handleToolbar(item)">
删除
</el-button>
</template>
<!-- 导入 -->
<template v-else-if="item === 'import'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
type="default"
icon="upload"
@click="handleToolbar(item)"
>
<el-button v-hasPerm="[`${contentConfig.pageName}:${item}`]" type="default" icon="upload"
@click="handleToolbar(item)">
导入
</el-button>
</template>
<!-- 导出 -->
<template v-else-if="item === 'export'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
type="default"
icon="download"
@click="handleToolbar(item)"
>
<el-button v-hasPerm="[`${contentConfig.pageName}:${item}`]" type="default" icon="download"
@click="handleToolbar(item)">
导出
</el-button>
</template>
</template>
<!-- 其他 -->
<template v-else-if="typeof item === 'object'">
<el-button
v-if="item.hidden === undefined || item.hidden === false"
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
:icon="item.icon"
:type="item.type ?? 'default'"
@click="handleToolbar(item.name)"
>
<el-button v-if="item.hidden === undefined || item.hidden === false"
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]" :icon="item.icon" :type="item.type ?? 'default'"
@click="handleToolbar(item.name)">
{{ item.text }}
</el-button>
</template>
</template>
<!-- 插槽 -->
<slot name="custom" />
</div>
<!-- 右侧工具栏 -->
<div>
@@ -89,68 +70,36 @@
</template>
<!-- 导出 -->
<template v-else-if="item === 'exports'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:export`]"
icon="download"
circle
title="导出"
@click="handleToolbar(item)"
/>
<el-button v-hasPerm="[`${contentConfig.pageName}:export`]" icon="download" circle title="导出"
@click="handleToolbar(item)" />
</template>
<!-- 导入 -->
<template v-else-if="item === 'imports'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:import`]"
icon="upload"
circle
title="导入"
@click="handleToolbar(item)"
/>
<el-button v-hasPerm="[`${contentConfig.pageName}:import`]" icon="upload" circle title="导入"
@click="handleToolbar(item)" />
</template>
<!-- 搜索 -->
<template v-else-if="item === 'search'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:query`]"
icon="search"
circle
title="搜索"
@click="handleToolbar(item)"
/>
<el-button v-hasPerm="[`${contentConfig.pageName}:query`]" icon="search" circle title="搜索"
@click="handleToolbar(item)" />
</template>
</template>
<!-- 其他 -->
<template v-else-if="typeof item === 'object'">
<template v-if="item.auth">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
:icon="item.icon"
circle
:title="item.title"
@click="handleToolbar(item.name)"
/>
<el-button v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]" :icon="item.icon" circle
:title="item.title" @click="handleToolbar(item.name)" />
</template>
<template v-else>
<el-button
:icon="item.icon"
circle
:title="item.title"
@click="handleToolbar(item.name)"
/>
<el-button :icon="item.icon" circle :title="item.title" @click="handleToolbar(item.name)" />
</template>
</template>
</template>
</div>
</div>
<!-- 列表 -->
<el-table
ref="tableRef"
v-loading="loading"
v-bind="contentConfig.table"
:data="pageData"
:row-key="pk"
@selection-change="handleSelectionChange"
@filter-change="handleFilterChange"
>
<el-table ref="tableRef" v-loading="loading" v-bind="contentConfig.table" :data="pageData" :row-key="pk"
@selection-change="handleSelectionChange" @filter-change="handleFilterChange">
<template v-for="col in cols" :key="col">
<el-table-column v-if="col.show" v-bind="col">
<template #default="scope">
@@ -159,24 +108,15 @@
<template v-if="col.prop">
<template v-if="Array.isArray(scope.row[col.prop])">
<template v-for="(item, index) in scope.row[col.prop]" :key="item">
<el-image
:src="item"
:preview-src-list="scope.row[col.prop]"
:initial-index="index"
:preview-teleported="true"
:style="`width: ${col.imageWidth ?? 40}px; height: ${
col.imageHeight ?? 40
}px`"
/>
<el-image :src="item" :preview-src-list="scope.row[col.prop]" :initial-index="index"
:preview-teleported="true" :style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40
}px`" />
</template>
</template>
<template v-else>
<el-image
:src="scope.row[col.prop]"
:preview-src-list="[scope.row[col.prop]]"
<el-image :src="scope.row[col.prop]" :preview-src-list="[scope.row[col.prop]]"
:preview-teleported="true"
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
/>
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`" />
</template>
</template>
</template>
@@ -198,30 +138,20 @@
<template v-else-if="col.templet === 'switch'">
<template v-if="col.prop">
<!-- pageData.length>0: 解决el-switch组件会在表格初始化的时候触发一次change事件 -->
<el-switch
v-model="scope.row[col.prop]"
:active-value="col.activeValue ?? 1"
:inactive-value="col.inactiveValue ?? 0"
:inline-prompt="true"
:active-text="col.activeText ?? ''"
:inactive-text="col.inactiveText ?? ''"
:validate-event="false"
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
@change="
<el-switch v-model="scope.row[col.prop]" :active-value="col.activeValue ?? 1"
:inactive-value="col.inactiveValue ?? 0" :inline-prompt="true" :active-text="col.activeText ?? ''"
:inactive-text="col.inactiveText ?? ''" :validate-event="false"
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)" @change="
pageData.length > 0 && handleModify(col.prop, scope.row[col.prop], scope.row)
"
/>
" />
</template>
</template>
<!-- 生成输入框组件 -->
<template v-else-if="col.templet === 'input'">
<template v-if="col.prop">
<el-input
v-model="scope.row[col.prop]"
:type="col.inputType ?? 'text'"
<el-input v-model="scope.row[col.prop]" :type="col.inputType ?? 'text'"
:disabled="!hasAuth(`${contentConfig.pageName}:modify`)"
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)"
/>
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)" />
</template>
</template>
<!-- 格式化为价格 -->
@@ -253,7 +183,7 @@
{{
scope.row[col.prop]
? useDateFormat(scope.row[col.prop], col.dateFormat ?? "YYYY-MM-DD HH:mm:ss")
.value
.value
: ""
}}
</template>
@@ -264,21 +194,15 @@
<template v-if="typeof item === 'string'">
<!-- 编辑/删除 -->
<template v-if="item === 'edit' || item === 'delete'">
<el-button
v-hasPerm="[`${contentConfig.pageName}:${item}`]"
:type="item === 'edit' ? 'primary' : 'danger'"
:icon="item"
size="small"
link
@click="
<el-button v-hasPerm="[`${contentConfig.pageName}:${item}`]"
:type="item === 'edit' ? 'primary' : 'danger'" :icon="item" size="small" link @click="
handleOperat({
name: item,
row: scope.row,
column: scope.column,
$index: scope.$index,
})
"
>
">
{{ item === "edit" ? "编辑" : "删除" }}
</el-button>
</template>
@@ -286,63 +210,43 @@
<!-- 其他 -->
<template v-else-if="typeof item === 'object'">
<template v-if="item.hidden === undefined || item.hidden === false">
<el-button
v-if="item.isBtn"
v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
:icon="item.icon"
:type="item.type ?? 'primary'"
size="small"
link
@click="
<el-button v-if="item.isBtn" v-hasPerm="[`${contentConfig.pageName}:${item.auth}`]"
:icon="item.icon" :type="item.type ?? 'primary'" size="small" link @click="
handleOperat({
name: item.name,
row: scope.row,
column: scope.column,
$index: scope.$index,
})
"
>
">
{{ item.text }}
</el-button>
<el-dropdown style="margin-top: 4px" v-else>
<el-button
v-if="item.render === undefined || item.render(scope.row)"
v-bind="
item.auth
? { 'v-hasPerm': [`${contentConfig.pageName}:${item.auth}`] }
: {}
"
:icon="item.icon"
:type="item.type ?? 'primary'"
size="small"
link
@click="
<el-button v-if="item.render === undefined || item.render(scope.row)" v-bind="item.auth
? { 'v-hasPerm': [`${contentConfig.pageName}:${item.auth}`] }
: {}
" :icon="item.icon" :type="item.type ?? 'primary'" size="small" link @click="
handleOperat({
name: item.name,
row: scope.row,
column: scope.column,
$index: scope.$index,
})
"
>
">
{{ item.text }}
</el-button>
<template #dropdown v-if="item.options && item.options.length > 0">
<el-dropdown-menu>
<el-dropdown-item
@click="
handleOperat({
name: item.name,
row: scope.row,
column: scope.column,
$index: scope.$index,
command: opt.command ? opt.command : '',
})
"
v-for="opt in item.options"
:key="opt.value"
>
<el-dropdown-item @click="
handleOperat({
name: item.name,
row: scope.row,
column: scope.column,
$index: scope.$index,
command: opt.command ? opt.command : '',
})
" v-for="opt in item.options" :key="opt.value">
{{ opt.label }}
</el-dropdown-item>
</el-dropdown-menu>
@@ -364,33 +268,18 @@
<template v-if="showPagination">
<el-scrollbar>
<div class="mt-[12px]">
<el-pagination
v-bind="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<el-pagination v-bind="pagination" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</el-scrollbar>
</template>
<!-- 导出弹窗 -->
<el-dialog
v-model="exportsModalVisible"
:align-center="true"
title="导出数据"
width="600px"
style="padding-right: 0"
@close="handleCloseExportsModal"
>
<el-dialog v-model="exportsModalVisible" :align-center="true" title="导出数据" width="600px" style="padding-right: 0"
@close="handleCloseExportsModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form
ref="exportsFormRef"
label-width="auto"
style="padding-right: var(--el-dialog-padding-primary)"
:model="exportsFormData"
:rules="exportsFormRules"
>
<el-form ref="exportsFormRef" label-width="auto" style="padding-right: var(--el-dialog-padding-primary)"
:model="exportsFormData" :rules="exportsFormRules">
<el-form-item label="文件名" prop="filename">
<el-input v-model="exportsFormData.filename" clearable />
</el-form-item>
@@ -400,16 +289,10 @@
<el-form-item label="数据源" prop="origin">
<el-select v-model="exportsFormData.origin">
<el-option label="当前数据 (当前页的数据)" :value="ExportsOriginEnum.CURRENT" />
<el-option
label="选中数据 (所有选中的数据)"
:value="ExportsOriginEnum.SELECTED"
:disabled="selectionData.length <= 0"
/>
<el-option
label="全量数据 (所有分页的数据)"
:value="ExportsOriginEnum.REMOTE"
:disabled="contentConfig.exportsAction === undefined"
/>
<el-option label="选中数据 (所有选中的数据)" :value="ExportsOriginEnum.SELECTED"
:disabled="selectionData.length <= 0" />
<el-option label="全量数据 (所有分页的数据)" :value="ExportsOriginEnum.REMOTE"
:disabled="contentConfig.exportsAction === undefined" />
</el-select>
</el-form-item>
<el-form-item label="字段" prop="fields">
@@ -430,35 +313,17 @@
</template>
</el-dialog>
<!-- 导入弹窗 -->
<el-dialog
v-model="importModalVisible"
:align-center="true"
title="导入数据"
width="600px"
style="padding-right: 0"
@close="handleCloseImportModal"
>
<el-dialog v-model="importModalVisible" :align-center="true" title="导入数据" width="600px" style="padding-right: 0"
@close="handleCloseImportModal">
<!-- 滚动 -->
<el-scrollbar max-height="60vh">
<!-- 表单 -->
<el-form
ref="importFormRef"
label-width="auto"
style="padding-right: var(--el-dialog-padding-primary)"
:model="importFormData"
:rules="importFormRules"
>
<el-form ref="importFormRef" label-width="auto" style="padding-right: var(--el-dialog-padding-primary)"
:model="importFormData" :rules="importFormRules">
<el-form-item label="文件名" prop="files">
<el-upload
ref="uploadRef"
v-model:file-list="importFormData.files"
class="w-full"
<el-upload ref="uploadRef" v-model:file-list="importFormData.files" class="w-full"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:drag="true"
:limit="1"
:auto-upload="false"
:on-exceed="handleFileExceed"
>
:drag="true" :limit="1" :auto-upload="false" :on-exceed="handleFileExceed">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
<span>将文件拖到此处,或</span>
@@ -467,13 +332,8 @@
<template #tip>
<div class="el-upload__tip">
*.xlsx / *.xls
<el-link
v-if="contentConfig.importTemplate"
type="primary"
icon="download"
:underline="false"
@click="handleDownloadTemplate"
>
<el-link v-if="contentConfig.importTemplate" type="primary" icon="download" :underline="false"
@click="handleDownloadTemplate">
下载模板
</el-link>
</div>
@@ -485,11 +345,7 @@
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button
type="primary"
:disabled="importFormData.files.length === 0"
@click="handleImportSubmit"
>
<el-button type="primary" :disabled="importFormData.files.length === 0" @click="handleImportSubmit">
确 定
</el-button>
<el-button @click="handleCloseImportModal">取 消</el-button>
@@ -1005,10 +861,10 @@ function fetchPageData(formData: IObject = {}, isRestart = false) {
.indexAction(
showPagination
? {
[request.pageName]: pagination.currentPage,
[request.limitName]: pagination.pageSize,
...formData,
}
[request.pageName]: pagination.currentPage,
[request.limitName]: pagination.pageSize,
...formData,
}
: formData
)
.then((data) => {
@@ -1073,7 +929,7 @@ function saveXlsx(fileData: BlobPart, fileName: string) {
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
}
function test(rows: any[]) {}
function test(rows: any[]) { }
const defaultSelData = ref<IObject[]>([]);
// 设置默认选择
@@ -1115,17 +971,21 @@ defineExpose({
:deep(.el-table .el-table__cell) {
z-index: inherit;
}
.el-card {
overflow: visible;
}
:deep(.el-table) {
overflow: visible;
.el-table__header-wrapper {
position: sticky;
z-index: calc(var(--el-table-index) + 2);
top: 0;
}
}
:deep(.el-table td.el-table__cell div) {
vertical-align: middle;
}

View File

@@ -1,44 +1,42 @@
<!-- 文件上传组件 -->
<template>
<div>
<el-upload
v-model:file-list="fileList"
:style="props.style"
:before-upload="handleBeforeUpload"
:http-request="handleUpload"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-error="handleError"
:accept="props.accept"
:limit="props.limit"
multiple
>
<!-- 上传文件按钮 -->
<el-button type="primary" :disabled="fileList.length >= props.limit">
{{ props.uploadBtnText }}
</el-button>
<el-upload v-model:file-list="fileList" :style="props.style" :before-upload="handleBeforeUpload"
:http-request="handleUpload" :on-progress="handleProgress" :on-success="handleSuccess" :on-error="handleError"
:accept="props.accept" :limit="props.limit" multiple drag :tip="'支持多个文件上传,单个文件不超过 ' + props.maxFileSize + 'MB'">
<!-- 拖拽上传区域 -->
<div>
<el-icon class="el-icon--upload">
<UploadFilled />
</el-icon>
<div class="el-upload__text">
将文件拖到此处 <em>点击上传</em>
<br />
<small>支持格式{{ props.accept }}</small>
</div>
</div>
<!-- 文件列表 -->
<template #file="{ file }">
<div class="el-upload-list__item-info">
<a class="el-upload-list__item-name" @click="handleDownload(file)">
<el-icon><Document /></el-icon>
<el-icon>
<Document />
</el-icon>
<span class="el-upload-list__item-file-name">{{ file.name }}</span>
<span class="el-icon--close" @click="handleRemove(file.url!)">
<el-icon><Close /></el-icon>
<span class="el-icon--close" @click="handleRemove(file.url)">
<el-icon>
<Close />
</el-icon>
</span>
</a>
</div>
</template>
</el-upload>
<el-progress
:style="{
display: showProgress ? 'inline-flex' : 'none',
width: '100%',
}"
:percentage="progressPercent"
/>
<el-progress :style="{
display: showProgress ? 'inline-flex' : 'none',
width: '100%',
marginTop: '10px',
}" :percentage="progressPercent" />
</div>
</template>
<script lang="ts" setup>
@@ -48,9 +46,14 @@ import {
UploadProgressEvent,
UploadRequestOptions,
} from "element-plus";
import { Upload, Document, Close } from "@element-plus/icons-vue";
import CommonApi, { FileInfo, uploadResponse } from "@/api/account/common";
const emit = defineEmits<{
"upload-success": [fileInfo: string];
}>();
const props = defineProps({
/**
* 请求携带的额外参数
@@ -96,7 +99,6 @@ const props = defineProps({
type: String,
default: "上传文件",
},
/**
* 样式
*/
@@ -104,7 +106,7 @@ const props = defineProps({
type: Object,
default: () => {
return {
width: "300px",
width: "100%",
};
},
},
@@ -147,6 +149,7 @@ function handleBeforeUpload(file: UploadRawFile) {
ElMessage.warning("上传图片不能大于" + props.maxFileSize + "M");
return false;
}
showProgress.value = true;
return true;
}
@@ -190,10 +193,13 @@ const handleProgress = (event: UploadProgressEvent) => {
const handleSuccess = (fileInfo: string) => {
ElMessage.success("上传成功");
modelValue.value = [...modelValue.value, fileInfo];
emit("upload-success", fileInfo);
showProgress.value = false;
};
const handleError = (error: any) => {
ElMessage.error("上传失败");
showProgress.value = false;
};
/**

View File

@@ -0,0 +1,242 @@
<!-- 文件上传组件 -->
<template>
<div>
<el-upload v-model:file-list="fileList" :style="props.style" :before-upload="handleBeforeUpload"
:on-change="handleChange" :accept="accept" multiple drag :tip="'支持多个文件上传,单个文件不超过 ' + props.maxFileSize + 'MB'"
:auto-upload="false">
<!-- 拖拽上传区域 -->
<div>
<el-icon class="el-icon--upload">
<UploadFilled />
</el-icon>
<div class="el-upload__text">
将文件拖到此处 <em>点击上传</em>
<br />
<small>支持格式{{ accept }}</small>
</div>
</div>
<!-- 文件列表 -->
<template #file="{ file }">
<div class="el-upload-list__item-info">
<a class="el-upload-list__item-name" @click="handleDownload(file)">
<el-icon>
<Document />
</el-icon>
<span class="el-upload-list__item-file-name">{{ file.name }}</span>
<span class="el-icon--close" @click="handleRemove(file.url)">
<el-icon>
<Close />
</el-icon>
</span>
</a>
</div>
</template>
</el-upload>
</div>
</template>
<script setup>
import { UploadFilled, Document, Close } from "@element-plus/icons-vue";
const emit = defineEmits({
"file-selected": null,
});
const props = defineProps({
/**
* 文件上传数量限制
*/
limit: {
type: Number,
default: 10,
},
/**
* 单个文件上传大小限制(单位MB)
*/
maxFileSize: {
type: Number,
default: 10,
},
/**
* 上传文件类型
*/
accept: {
type: String,
default: "*",
},
/**
* 选择文件成功的回调函数
*/
successCallback: {
type: Function,
default: null,
},
/**
* 样式
*/
style: {
type: Object,
default: () => {
return {
width: "100%",
};
},
},
});
const fileList = defineModel({
type: Array,
default: () => [],
});
/**
* 文件选择变化
*/
function handleChange(file, fileList) {
if (file.status === 'ready') {
// 如果限制为1个文件移除之前的文件只保留当前新文件
if (props.limit === 1 && fileList.length > 1) {
fileList.splice(0, fileList.length - 1);
}
const rawFile = file.raw;
// 限制文件类型
if (props.accept !== "*") {
const acceptedTypes = props.accept.split(',').map(type => type.trim());
const isAccepted = acceptedTypes.some(type => {
let checkType = type;
if (!type.startsWith('.') && !type.includes('/')) {
checkType = '.' + type;
}
if (checkType.startsWith('.')) {
return rawFile.name.toLowerCase().endsWith(checkType.toLowerCase());
} else if (checkType.includes('*')) {
const [main] = checkType.split('/');
return rawFile.type.startsWith(main + '/');
} else {
return rawFile.type === checkType;
}
});
if (!isAccepted) {
ElMessage.warning("文件类型不符合要求");
// 从 fileList 中移除不符合的文件
fileList.splice(fileList.indexOf(file), 1);
return;
}
}
if (props.successCallback) {
props.successCallback(rawFile);
} else {
emit("file-selected", rawFile);
}
}
}
/**
* 上传前校验
*/
function handleBeforeUpload(file) {
// 限制文件大小
if (file.size > props.maxFileSize * 1024 * 1024) {
ElMessage.warning("上传文件不能大于" + props.maxFileSize + "M");
return false;
}
// 限制文件类型
if (props.accept !== "*") {
const acceptedTypes = props.accept.split(',').map(type => type.trim());
const isAccepted = acceptedTypes.some(type => {
if (type.startsWith('.')) {
return file.name.toLowerCase().endsWith(type.toLowerCase());
} else {
return file.type === type;
}
});
if (!isAccepted) {
ElMessage.warning("文件类型不符合要求");
return false;
}
}
return true;
}
/**
* 删除文件
*/
function handleRemove(fileUrl) {
// 从 fileList 中移除
fileList.value = fileList.value.filter(f => f.url !== fileUrl);
}
/**
* 下载文件
*/
function handleDownload(file) {
// 本地文件,不支持下载
}
</script>
<style lang="scss" scoped>
.el-upload-list__item .el-icon--close {
position: absolute;
top: 50%;
right: 5px;
color: var(--el-text-color-regular);
cursor: pointer;
opacity: 0.75;
transition: opacity var(--el-transition-duration);
transform: translateY(-50%);
&:hover {
opacity: 1;
color: var(--el-color-danger);
}
}
:deep(.el-upload-list) {
margin: 10px 0 0 0;
}
:deep(.el-upload-list__item) {
margin: 5px 0;
background: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 8px 12px;
display: flex;
align-items: center;
transition: all 0.3s ease;
&:hover {
background: #f0f0f0;
border-color: var(--el-color-primary-light-3);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.el-upload-list__item-info {
flex: 1;
a {
display: flex;
align-items: center;
text-decoration: none;
color: var(--el-text-color-primary);
&:hover {
color: var(--el-color-primary);
}
.el-icon {
margin-right: 8px;
color: var(--el-color-primary);
}
.el-upload-list__item-file-name {
flex: 1;
font-size: 14px;
font-weight: 500;
}
.el-icon--close {
margin-left: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,258 @@
<!-- 批量导入数据dialog -->
<template>
<div>
<div class="btn_row">
<el-button type="primary" icon="Upload" @click="show">批量导入</el-button>
<el-button icon="Download" @click="downloadTemplateAjax">下载银收客模板</el-button>
</div>
<el-dialog title="批量导入" width="800px" v-model="visible" :close-on-click-modal="false" :close-on-press-escape="false"
@close="dialogClose">
<div class="row">
<tabHeader v-model="tabActive" :list="tabs" />
</div>
<div class="row mt14">
<div class="import_container" v-if="tabActive == 0">
<div class="header_title">第一步选择模板</div>
<div class="row mt14 pb50">
<div class="list">
<div class="item" :class="{ active: platformActive == index }" v-for="(item, index) in platformList"
:key="item.id" @click="selectPlatform(item)">
<img class="img" :src="item.img" alt="">
</div>
</div>
</div>
<div class="header_title">第二步上传文件
<span>单次仅可上传一个文件</span>
</div>
<div class="row mt14">
<GfileUpload v-model="form.files" :accept="platformList[platformActive]?.file_type || ''" :limit="1"
@file-selected="fileSelected" />
</div>
<div class="row mt14">
<div class="footer_wrap">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" :disabled="!form.files.length" :loading="loading" @click="startImportHandle">
<template v-if="!form.files.length">请选择文件</template>
<template v-else>开始导入</template>
</el-button>
</div>
</div>
</div>
<el-table :data="tableData" border stripe v-if="tabActive == 1" height="419px">
<el-table-column prop="file_name" label="文件名称" width="300" />
<el-table-column prop="created_time" label="导入时间" width="200" />
<el-table-column prop="status_text" label="导入状态" width="150">
<template #default="scope">
<el-tag v-if="scope.row.status == 0" type="info" disable-transitions>待处理...</el-tag>
<el-tag v-else-if="scope.row.status == 1" type="warning" disable-transitions>处理中...</el-tag>
<el-tag v-else-if="scope.row.status == 2" type="success" disable-transitions>处理完成</el-tag>
<el-tag v-else-if="scope.row.status == -1" type="danger" disable-transitions>导入失败</el-tag>
</template>
</el-table-column>
<el-table-column prop="importResult" label="导入结果">
<template #default="scope">
<template v-if="scope.row.status == 2">
<div class="column">
<div>
<el-text type="success">成功{{ scope.row.success_num }} </el-text>
</div>
<div>
<el-text type="danger">失败{{ scope.row.fail_num }} </el-text>
</div>
</div>
</template>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { getplatlist, uploadFile, importlist, downloadTemp } from '@/importDataApi/index.js';
import GfileUpload from '../Upload/GfileUpload.vue';
import tabHeader from '@/views/marketing_center/components/tabHeader.vue';
const props = defineProps({
type: {
type: [String, Number],
default: 3, // 3商品 4台桌区域 5台桌 6会员 7菜品销售统计 8台桌销售统计 9订单销售统计
},
});
const platformList = ref([]);
const platformActive = ref(0);
function selectPlatform(item) {
form.value.files = [];
platformActive.value = platformList.value.findIndex(i => i.id === item.id);
}
const tabs = ref([
{ label: '导入数据', name: 'importData' },
{ label: '导入记录', name: 'importRecord' },
]);
const tabActive = ref(0)
watch(tabActive, (newVal) => {
if (newVal === 1) {
getImportRecord();
}
});
const visible = ref(false);
const loading = ref(false);
const form = ref({
files: [],
platform: ''
})
function fileSelected(file) {
console.log('fileSelected', file);
}
// 开始导入
async function startImportHandle() {
try {
form.value.platform = platformList.value[platformActive.value]?.id;
const formData = new FormData();
formData.append('file', form.value.files[0].raw);
formData.append('shop_id', localStorage.getItem('shopId'));
formData.append('type', props.type);
formData.append('platform', form.value.platform);
loading.value = true;
await uploadFile(formData);
ElMessage.success('文件上传成功,正在导入数据,请在导入记录中查看导入结果');
form.value.files = []
tabActive.value = 1
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false;
}, 500);
}
// 获取平台列表
async function getPlatformList() {
try {
const res = await getplatlist({ plat_type: props.type });
platformList.value = res;
} catch (error) {
console.log(error);
}
}
// 导入记录
const tableData = ref([]);
async function getImportRecord() {
try {
const res = await importlist({ shop_id: localStorage.getItem('shopId') });
tableData.value = res;
} catch (error) {
console.log(error);
}
}
// 下载模板
async function downloadTemplateAjax() {
try {
const res = await downloadTemp({ plat_type: props.type });
window.open(res, '_blank');
} catch (error) {
console.log(error);
}
}
const emits = defineEmits(['close'])
function dialogClose() {
emits('close')
}
onMounted(() => {
getPlatformList()
});
function show() {
visible.value = true;
}
defineExpose({
show,
});
</script>
<style scoped lang="scss">
.btn_row {
display: flex;
gap: 4px;
margin-left: 14px;
}
.header_title {
font-size: 16px;
font-weight: bold;
color: #333333;
display: flex;
align-items: center;
span {
font-size: 12px;
color: #999999;
margin-left: 10px;
}
}
.row {
&.mt14 {
margin-top: 14px;
}
&.pb50 {
padding-bottom: 50px;
}
}
.import_container {
.list {
display: flex;
gap: 24px;
.item {
width: 122px;
height: 42px;
border: 1px solid #fff;
border-radius: 4px;
overflow: hidden;
&:hover {
cursor: pointer;
border-color: var(--el-color-primary);
}
&.active {
border-color: var(--el-color-primary);
}
.img {
width: 100%;
height: 100%;
}
}
}
.footer_wrap {
display: flex;
gap: 14px;
justify-content: flex-end;
}
}
.column {
display: flex;
flex-direction: column;
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<el-select v-model="modelValue" placeholder="请选择优惠券" clearable style="width: 300px;" @change="changHandle">
<el-option v-for="coupon in couponList" :key="coupon.id" :label="coupon.title" :value="coupon.id" />
</el-select>
</template>
<script setup>
import couponApi from "@/api/market/coupon";
import { ref, onMounted } from "vue";
const modelValue = defineModel({
type: [String, Number],
default: ''
});
const name = defineModel('name', {
type: String,
default: ''
});
function changHandle(value) {
name.value = couponList.value.find(item => item.id === value)?.title || '';
}
// 优惠券列表
const couponList = ref([]);
onMounted(() => {
couponApi.getList({ size: 999 }).then((res) => {
if (res) {
couponList.value = res.records || [];
}
});
});
</script>

View File

@@ -0,0 +1,57 @@
import request from "@/utils/request-import-php";
/**
* 平台列表
* @param {*} data
* @returns
*/
export function getplatlist(data) {
return request({
url: "plat/getplatlist",
method: "post",
data
});
}
/**
* 导入
* @param {*} data
* @returns
*/
export function uploadFile(data) {
return request({
url: "upload",
method: "post",
headers: {
'Content-Type': 'multipart/form-data',
},
data
});
}
/**
* 导入记录
* @param {*} data
* @returns
*/
export function importlist(data) {
return request({
url: "plat/importlist",
method: "post",
data
});
}
/**
* 下载模板
* @param {*} data
* @returns
*/
export function downloadTemp(data) {
return request({
url: "plat/downloadTemp",
method: "post",
data
});
}

View File

@@ -0,0 +1,212 @@
<template>
<div>
<el-text size="large" style="margin: 0 14px;" @click="visible = true">帮助中心</el-text>
<el-dialog title="帮助中心" width="1000px" v-model="visible" append-to-body>
<div class="help_container">
<div class="header">关注官方公众号查看详细教程视频快速掌握使用方法</div>
<div class="wrap">
<div class="item">
<div class="title">联系方式</div>
<div class="row">
<div class="left">
<div class="icon">
<el-icon color="#fff">
<PhoneFilled />
</el-icon>
</div>
<el-text>客服电话</el-text>
</div>
<div class="right hover" @click="copyHandle(helpInfo.service_phone)">
<el-text type="primary">{{ helpInfo.service_phone }}</el-text>
<el-icon color="#666">
<CopyDocument />
</el-icon>
</div>
</div>
<div class="row">
<div class="left">
<div class="icon">
<el-icon color="#fff">
<UserFilled />
</el-icon>
</div>
<el-text>QQ告前咨询</el-text>
</div>
<div class="right hover" @click="copyHandle(helpInfo.qq_consult)">
<el-text type="primary">{{ helpInfo.qq_consult }}</el-text>
<el-icon color="#666">
<CopyDocument />
</el-icon>
</div>
</div>
<div class="row" style="margin-top: 4px;">
<div class="left">
<div class="icon" style="background-color: #fff;">
</div>
<el-text>QQ投诉通道</el-text>
</div>
<div class="right hover" @click="copyHandle(helpInfo.qq_complaint)">
<el-text type="primary">{{ helpInfo.qq_complaint }}</el-text>
<el-icon color="#666">
<CopyDocument />
</el-icon>
</div>
</div>
<div class="row">
<div class="left">
<div class="icon">
<el-icon color="#fff">
<Check />
</el-icon>
</div>
<el-text>上班时间</el-text>
</div>
<div class="right">
<el-text type="primary">{{ helpInfo.work_time }}</el-text>
</div>
</div>
</div>
<div class="item">
<div class="title">官方公众号</div>
<div class="intro">
<el-text type="info">关注后即可查看所有操作教程视频</el-text>
</div>
<div class="ewm_wrap">
<el-image :src="helpInfo.help_ac_qrcode" style="width: 145px;height:145px;"></el-image>
<el-text type="danger">关注后即可查看所有操作教程视频</el-text>
<el-text type="info">微信扫描识别二维码关注获取更多服务</el-text>
</div>
<div class="step_wrap" v-if="helpInfo.official_account">
<div class="row" v-for="(item, index) in helpInfo.official_account.split(';')" :key="index">
<div class="left">
<div class="icon">
<span class="t">{{ index + 1 }}</span>
</div>
<el-text>{{ item }}</el-text>
</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import useClipboard from "vue-clipboard3";
import { getHelp } from '@/api/system'
const { toClipboard } = useClipboard();
const visible = ref(false);
// 复制
async function copyHandle(text) {
try {
await toClipboard(text);
ElNotification({
title: "成功",
message: `复制成功`,
type: "success",
});
console.log("Copied to clipboard");
} catch (e) {
console.error(e);
}
}
// 获取帮助中心信息
const helpInfo = ref({});
async function getHelpAjax() {
try {
const res = await getHelp();
helpInfo.value = res;
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getHelpAjax()
});
</script>
<style lang="scss" scoped>
.header {
font-size: 16px;
color: #333;
}
.wrap {
display: flex;
margin-top: 37px;
padding: 0 28px;
.item {
flex: 1;
&:first-child {
border-right: 2px solid #EDEDED;
padding-right: 38px;
}
&:last-child {
padding-left: 38px;
}
.title {
font-size: 16px;
color: #333;
font-weight: bold;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
.left {
display: flex;
align-items: center;
gap: 8px;
.icon {
width: 22px;
height: 22px;
border-radius: 50%;
background-color: var(--el-color-primary);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.t {
font-size: 12px;
color: #fff;
}
}
}
.right {
display: flex;
align-items: center;
gap: 4px;
&.hover {
cursor: pointer;
}
}
}
.ewm_wrap {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 16px;
gap: 16px;
}
}
}
</style>

View File

@@ -2,6 +2,8 @@
<div class="navbar__right">
<!-- 非手机设备窄屏才显示 -->
<template v-if="!isMobile">
<!-- 帮助中心 -->
<HellpCenter />
<!-- 搜索 -->
<MenuSearch />
@@ -35,6 +37,7 @@ import { useAppStore, useSettingsStore } from "@/store";
import UserProfile from "./UserProfile.vue";
import Notification from "./Notification.vue";
import HellpCenter from "./HellpCenter.vue";
const appStore = useAppStore();
const settingStore = useSettingsStore();
@@ -48,7 +51,7 @@ const isMobile = computed(() => appStore.device === DeviceEnum.MOBILE);
align-items: center;
justify-content: center;
& > * {
&>* {
display: inline-block;
min-width: 40px;
height: $navbar-height;
@@ -67,12 +70,12 @@ const isMobile = computed(() => appStore.device === DeviceEnum.MOBILE);
margin: 10px 0;
}
.dark .navbar__right > *:hover {
.dark .navbar__right>*:hover {
background: rgb(255 255 255 / 20%);
}
.layout-top .navbar__right > *,
.layout-mix .navbar__right > * {
.layout-top .navbar__right>*,
.layout-mix .navbar__right>* {
color: #333;
}
</style>

View File

@@ -24,53 +24,17 @@ export const constantRoutes: RouteRecordRaw[] = [
},
{
path: "/",
path: '/',
component: Layout,
redirect: "/index",
meta: {
title: "数据中心",
icon: "数据统计",
alwaysShow: true,
},
redirect: '/index',
children: [
{
path: "index",
component: () => import("@/views/data/index.vue"),
name: "",
path: 'index',
component: () => import('@/views/index/index.vue'),
name: 'index',
meta: {
title: "经营数据",
affix: false,
keepAlive: true,
},
},
{
path: "sales",
name: "",
component: () => import("@/views/data/sales.vue"),
meta: {
title: "销售统计",
affix: false,
keepAlive: true,
},
},
{
path: "table",
name: "",
component: () => import("@/views/data/table.vue"),
meta: {
title: "台桌统计",
affix: false,
keepAlive: true,
},
},
{
path: "work",
name: "workStatistics",
component: () => import("@/views/data/work.vue"),
meta: {
title: "收银交班记录",
affix: false,
keepAlive: true,
title: '首页',
icon: 'homepage',
},
},
{
@@ -83,9 +47,79 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import("@/views/error/404.vue"),
meta: { hidden: true },
},
],
]
},
// {
// path: "/",
// component: Layout,
// redirect: "/index",
// meta: {
// title: "数据中心",
// icon: "数据统计",
// alwaysShow: true,
// },
// children: [
// {
// path: "index",
// component: () => import("@/views/data/index.vue"),
// name: "",
// meta: {
// title: "经营数据",
// affix: false,
// keepAlive: true,
// },
// },
// {
// path: "sales",
// name: "",
// component: () => import("@/views/data/sales.vue"),
// meta: {
// title: "销售统计",
// affix: false,
// keepAlive: true,
// },
// },
// {
// path: "table",
// name: "",
// component: () => import("@/views/data/table.vue"),
// meta: {
// title: "台桌统计",
// affix: false,
// keepAlive: true,
// },
// },
// {
// path: "finance",
// name: "",
// component: () => import("@/views/data/finance.vue"),
// meta: {
// title: "财务报表"
// },
// },
// {
// path: "work",
// name: "workStatistics",
// component: () => import("@/views/data/work.vue"),
// meta: {
// title: "收银交班记录",
// affix: false,
// keepAlive: true,
// },
// },
// {
// path: "401",
// component: () => import("@/views/error/401.vue"),
// meta: { hidden: true },
// },
// {
// path: "404",
// component: () => import("@/views/error/404.vue"),
// meta: { hidden: true },
// },
// ],
// },
// {
// path: "/shop",
// component: Layout,
// meta: {

View File

@@ -226,7 +226,25 @@ export const useCartsStore = defineStore("carts", () => {
const isLinkFinshed = ref(false);
// 当前购物车数据(现在 getAllGoodsList 能直接访问)
const list = useStorage<any[]>("carts", []);
const list = useStorage<any[]>(
"carts",
[],
localStorage,
{
serializer: {
read: (rawValue) => {
// 没有值时直接返回默认空数组
if (!rawValue) return []
try {
return JSON.parse(rawValue)
} catch {
return []
}
},
write: (value) => JSON.stringify(value),
},
}
)
// 历史订单数据(现在 getAllGoodsList 能直接访问)
const oldOrder = useStorage<any>("Instead_olold_order", {
detailMap: [],
@@ -670,11 +688,13 @@ export const useCartsStore = defineStore("carts", () => {
}
function rotTable(newVal: string | number, cart_id = []) {
sendMessage('rottable', {
new_table_code: newVal,
table_code: table_code.value,
cart_id
});
if (cart_id.length) {
sendMessage('rottable', {
new_table_code: newVal,
table_code: table_code.value,
cart_id
});
}
}
function del(data: any) {
@@ -833,8 +853,9 @@ export const useCartsStore = defineStore("carts", () => {
}
// 初始话订单信息/补全历史订单信息
async function setOldOrder(data: any) {
console.log('补全订单信息', data);
async function setOldOrder(data: any, t: number) {
console.log('补全历史订单信息来源', t);
// console.log('补全订单信息', data);
oldOrder.value = {
...data,
detailMap: returnDetailMap(data.detailMap)
@@ -849,10 +870,17 @@ export const useCartsStore = defineStore("carts", () => {
let $initParams = {} as ApifoxModel;
async function init(initParams: ApifoxModel, $oldOrder: any | undefined) {
await getGoods({});
if ($oldOrder) setOldOrder($oldOrder);
else oldOrder.value = { detailMap: [] };
// 标志位,表示是否正在清空历史订单
const isClearingOldOrder = ref(false);
async function init(initParams: ApifoxModel | undefined, $oldOrder: any | undefined) {
console.log('cart.init.initParams', initParams);
console.log('cart.init.$oldOrder', $oldOrder);
if (initParams) {
initParams.table_code = initParams.table_code || '';
@@ -860,6 +888,16 @@ export const useCartsStore = defineStore("carts", () => {
$initParams = initParams;
}
await getGoods({});
if ($oldOrder) {
await setOldOrder($oldOrder);
} else if (table_code.value) {
await getOldOrder(table_code.value);
} else {
oldOrder.value = { detailMap: [] };
}
concocatSocket($initParams);
}
@@ -977,8 +1015,15 @@ export const useCartsStore = defineStore("carts", () => {
concocatSocket({ ...$initParams, table_code: table_code.value });
}
// WebSocket 监听 product_update 消息
if (msg.operate_type === "product_update") {
init($initParams, oldOrder.value);
if (isClearingOldOrder.value) {
console.log("忽略 product_update 消息,因为正在清空历史订单");
isClearingOldOrder.value = false; // 重置标志位
return;
}
console.log("处理 product_update 消息");
init($initParams, oldOrder.value); // 重新初始化
}
if (msg.type === "bc") {
@@ -1023,6 +1068,8 @@ export const useCartsStore = defineStore("carts", () => {
newUserDiscount.value = {}
}
return {
disconnect,
dinnerType,
@@ -1084,7 +1131,7 @@ export const useCartsStore = defineStore("carts", () => {
getAllGoodsList,
vipUser,
changeTableInfo,
tableInfo,
tableInfo, isClearingOldOrder,
clearHistory
};
});

View File

@@ -111,8 +111,16 @@ export function downloadFile(obj: BlobPart, name: string, suffix: string, useUni
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
const newFilename = useUnix ? (parseTime(new Date(), undefined) + "-") : '' + name.trim()
const fileName = newFilename + "." + suffix;
// 期望行为:
// - 当 useUnix 为 true 且传入 name 时:`${timestamp}-${name}`
// - 当 useUnix 为 true 且 name 为空时:仅 `${timestamp}`(不带多余的 `-`
// - 当 useUnix 为 false使用传入的 name如为空则回退到时间戳
const safeName = (name || "").trim();
const timeStamp = parseTime(new Date(), undefined);
const fileBase = useUnix
? (safeName ? `${timeStamp}-${safeName}` : timeStamp)
: (safeName || timeStamp);
const fileName = fileBase + "." + suffix;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();

View File

@@ -0,0 +1,131 @@
import axios from "axios";
import router from "@/router";
import { getDouyinToken, getToken } from "@/utils/auth";
// 创建axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_API_PHP_IMPORT_URL, // api 的 base_url
// baseURL: "/import_api", // api 的 base_url
timeout: 1000 * 20, // 请求超时时间
});
// request拦截器
service.interceptors.request.use(
(config) => {
if (getToken()) {
config.headers["token"] = getToken();
}
if (!config.headers["Content-Type"]) {
config.headers["Content-Type"] = "application/json";
}
return config;
},
(error) => {
// 确保错误回调返回Promise避免状态异常
return Promise.reject(error);
}
);
// response 拦截器
service.interceptors.response.use(
(response) => {
const data = response.data;
console.log(data);
if (data.code == 0) {
ElNotification.error({
title: data.msg,
duration: 5000,
});
return;
}
if (data.code == 439 || data.code == 303) {
ElNotification.error({
title: "请登录",
duration: 5000,
});
return;
}
if (data.code == 4399) {
ElNotification.error({
title: data.msg,
duration: 5000,
});
return data;
}
// if (data.code == 1 && !data.data) {
// ElNotification.success({
// title: data.msg,
// duration: 5000
// })
// return true;
// }
return data.data;
},
(error) => {
console.log(error);
if (axios.isCancel(error)) {
console.log("请求已取消");
ElNotification.error({
title: "请求已取消",
duration: 5000,
});
return Promise.reject("请求已取消");
}
// 兼容blob下载出错json提示
if (
error.response.data instanceof Blob &&
error.response.data.type.toLowerCase().indexOf("json") !== -1
) {
const reader = new FileReader();
reader.readAsText(error.response.data, "utf-8");
reader.onload = function (e) {
const errorMsg = JSON.parse(reader.result).message;
ElNotification.error({
title: errorMsg,
duration: 5000,
});
};
} else {
let code = 0;
try {
code = error.response.data.status;
} catch (e) {
if (error.toString().indexOf("Error: timeout") !== -1) {
ElNotification.error({
title: "网络请求超时",
duration: 5000,
});
return Promise.reject(error);
}
}
console.log(code);
if (code) {
if (code === 401) {
// store.dispatch("LogOut").then(() => {
// // 用户登录界面提示
// Cookies.set("point", 401);
// location.reload();
// });
} else if (code === 403) {
router.push({ path: "/401" });
} else {
const errorMsg = error.response.data.message;
if (errorMsg !== undefined) {
ElNotification.error({
title: errorMsg,
duration: 5000,
});
}
}
} else {
ElNotification.error({
title: "接口请求失败",
duration: 5000,
});
}
}
return Promise.reject(error);
}
);
export default service;

View File

@@ -3,6 +3,7 @@ import { useUserStoreHook } from "@/store";
import { ElMessage, ElMessageBox } from "element-plus";
const user = useUserStoreHook();
let isTips = false
export interface ApifoxModel {
account: string;
@@ -143,6 +144,7 @@ class WebSocketManager {
this.autoConnect = false;
}
}
// 自动重连机制
private reconnect() {
if (!this.autoConnect) {
@@ -156,19 +158,24 @@ class WebSocketManager {
}, this.reconnectDelay);
} else {
clearTimeout(this.reconnectTimer);
console.error("达到最大重连次数,停止重连");
ElMessageBox.confirm('达到最大重连次数' + this.maxReconnectAttempts + '次,已停止重连,是否立即重连?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
// window.location.reload();
// console.error("达到最大重连次数,停止重连");
if (!isTips) {
ElMessageBox.confirm('达到最大重连次数' + this.maxReconnectAttempts + '次,已停止重连,是否立即重连?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
callback: (action: string) => {
console.log(action);
if (action == 'confirm') {
this.setupWebSocket();
this.reconnectAttempts = 0;
callback: (action: string) => {
console.log(action);
if (action == 'confirm') {
isTips = false
this.setupWebSocket();
this.reconnectAttempts = 0;
}
}
}
});
});
}
isTips = true
}
}

View File

@@ -23,8 +23,8 @@ import call from "@/assets/images/application/call.png";
const list = ref([
{ name: "存酒", icon: bear, path: "storingWine", desc: "用户未喝完的酒可暂存在店里" },
{ name: "点歌", icon: song, path: "song", desc: "用户可以付费点歌" },
{ name: "广告", icon: ad, path: "advertisement", desc: "添加弹窗广告" },
// { name: "点歌", icon: song, path: "song", desc: "用户可以付费点歌" },
// { name: "广告", icon: ad, path: "advertisement", desc: "添加弹窗广告" },
{ name: "叫号", icon: call, path: "lineUplist", desc: "" },
]);

View File

@@ -60,7 +60,8 @@
<myDialog ref="myDialogRefqs" title="记录" @confirm="confirmqs" width="30%">
<el-form-item label="选择用户">
<el-select v-model="datas.DialogForm.userId" filterable placeholder="请选择选择用户" style="width: 240px">
<el-option v-for="item in datas.options" :key="item.userId" :label="item.nickName" :value="item.userId" />
<el-option v-for="item in datas.options" :key="item.userId" :label="`${item.nickName}/${item.phone}`"
:value="item.userId" />
</el-select>
</el-form-item>
<el-form-item label="数量">
@@ -128,7 +129,7 @@ function myDialogRefqsevent(item) {
myDialogRefqs.value.open()
}
async function getshopUser(params) {
const res = await API.getshopUser({})
const res = await API.getshopUser({ page: 1, size: 999, ...params })
datas.options = res.records
}
async function getList(data = {}) {

View File

@@ -19,10 +19,11 @@ const AuthAPI = {
});
},
// 获取用户店铺列表
getshopUser() {
getshopUser(params: any) {
return request<any, Responseres>({
url: `/account/admin/shopUser`,
method: "get",
params,
});
},
// 新增

View File

@@ -12,7 +12,8 @@
<el-form ref="ruleFormRef" :rules="datas.rules" :model="datas.DialogForm" label-width="80px">
<el-form-item label="选择用户">
<el-select v-model="datas.DialogForm.userId" filterable placeholder="请选择选择用户" style="width: 240px">
<el-option v-for="item in datas.options" :key="item.userId" :label="item.nickName" :value="item.userId" />
<el-option v-for="item in datas.options" :key="item.userId" :label="`${item.nickName}/${item.phone}`"
:value="item.userId" />
</el-select>
</el-form-item>
<el-form-item label="选择酒品">
@@ -107,7 +108,7 @@ async function getList(data = {}) {
datas.pagingConfig.pageNumber = res.pageNumber
}
async function getshopUser(params) {
const res = await API.getshopUser({})
const res = await API.getshopUser({ page: 1, size: 999, ...params })
datas.options = res.records
}
async function getstorageGood() {

View File

@@ -18,10 +18,11 @@ const AuthAPI = {
});
},
// 获取用户店铺列表
getshopUser() {
getshopUser(params: any) {
return request<any, Responseres>({
url: `/account/admin/shopUser`,
method: "get",
params,
});
},
// 获取酒品列表

699
src/views/data/finance.vue Normal file
View File

@@ -0,0 +1,699 @@
<template>
<div class="gyq_container">
<div class="row">
<div class="between">
<el-form :model="queryForm" inline>
<el-form-item>
<el-date-picker v-model="queryForm.queryDate" type="date" placeholder="选择日期" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" :disabled-date="disabledFutureDate" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" :loading="loading" @click="handleQuery">查询</el-button>
<el-button @click="handleReset" icon="Refresh" :loading="loading">重置</el-button>
</el-form-item>
</el-form>
<el-form inline>
<el-form-item>
<el-select v-model="queryForm.platform" placeholder="选择平台" clearable style="width: 150px;">
<el-option v-for="item in platformList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleExport" icon="Download">导出</el-button>
</el-form-item>
</el-form>
</div>
</div>
<div class="row" v-loading="loading">
<div class="warp" style="width: 400px;">
<div class="card">
<div class="header_title">营业额</div>
<div class="num" style="color: var(--el-color-primary);">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.turnover || 0) }}
</div>
<div class="intro">营业额</div>
<div class="pay_wrap">
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.wechat || 0) }}
</div>
<span class="t">微信支付金额</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.alipay || 0) }}
</div>
<span class="t">支付宝支付金额</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.selfScan || 0) }}
</div>
<span class="t">二维码收款</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.barScan || 0) }}
</div>
<span class="t">扫码收款</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.cash || 0) }}
</div>
<span class="t">现金收款</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.recharge || 0) }}
</div>
<span class="t">充值</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.owed || 0) }}
</div>
<span class="t">挂账</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.turnover.balance || 0) }}
</div>
<span class="t">余额支付</span>
</div>
</div>
<!-- <div class="order_info">
<div class="order_title">
<span class="dot"></span>
<span class="t">订单</span>
</div>
<div class="order_lits">
<div class="item">
<span class="t">订单金额</span>
<span class="n">{{ tableData.order.orderAmount || 0 }}</span>
</div>
<span class="line">|</span>
<div class="item">
<span class="t">订单总数</span>
<span class="n">{{ tableData.order.orderCount || 0 }}</span>
</div>
</div>
</div> -->
</div>
<div class="card">
<div class="header_title">数据统计</div>
<div class="pay_wrap">
<div class="item">
<span class="n">{{ tableData.sts.customerCount || 0 }}</span>
<span class="t">就餐人数</span>
</div>
<div class="item">
<span class="n">{{ tableData.sts.orderCount || 0 }}</span>
<span class="t">订单数</span>
</div>
<div class="item">
<span class="n">{{ tableData.sts.tableCount || 0 }}</span>
<span class="t">桌台数</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.sts.avgPayAmount || 0) }}
</div>
<div class="t">客单价
<el-tooltip class="box-item" effect="dark" content="实付金额(包含现金支付 包含会员支付 包含挂账)/就餐人数没有具体人数时默认一桌按照1人计算"
placement="top">
<el-icon color="#666">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="item">
<span class="n">{{ tableData.sts.turnoverRate || 0 }}%</span>
<div class="t">翻台率
<el-tooltip class="box-item" effect="dark" content="(订单数-桌台数)/桌台数*100%" placement="top">
<el-icon color="#666">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.sts.profitAmount || 0) }}
</div>
<div class="t">毛利润
<el-tooltip class="box-item" effect="dark" content="(订单实付金额-商品成本)" placement="top">
<el-icon color="#666">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.sts.productCostAmount || 0) }}
</div>
<span class="t">商品成本</span>
</div>
<div class="item">
<span class="n">{{ tableData.sts.profitRate || 0 }}%</span>
<div class="t">毛利率
<el-tooltip class="box-item" effect="dark" content="(订单实付金额-商品成本)/订单实付金额*100%" placement="top">
<el-icon color="#666">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</div>
<!-- <div class="item">
<span class="n">{{ tableData.sts.netProfitAmount || 0 }}%</span>
<span class="t">净利率</span>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.sts.netProfitRate || 0) }}
</div>
<span class="t">净利润</span>
</div> -->
</div>
</div>
</div>
<div class="card" style="flex: 1;">
<div class="header_title">商家经营数据</div>
<div class="content">
<div class="card" style="flex: 1;">
<div class="num" style="color: var(--el-color-success);">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.discountAmount || 0) }}
</div>
<div class="intro">优惠金额</div>
<div class="item_list">
<div class="item">
<div class="n">
{{ tableData.discount.discountCount || 0 }}
</div>
<div class="label">
优惠笔数
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.newConsumerDiscount || 0) }}
</div>
<div class="label">
新客立减
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.freeCashAmount || 0) }}
</div>
<div class="label">
霸王餐
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.fullMinusAmount || 0) }}
</div>
<div class="label">
满减活动
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.couponAmount || 0) }}
</div>
<div class="label">
优惠券
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.memberDiscount || 0) }}
</div>
<div class="label">
会员折扣
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.pointsDiscountAmount || 0) }}
</div>
<div class="label">
积分抵扣金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.discount.orderDiscount || 0) }}
</div>
<div class="label">
订单改价
</div>
</div>
</div>
</div>
<div class="card" style="flex: 1;">
<div class="num" style="color: var(--el-color-danger);">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.refundAmount || 0) }}
</div>
<div class="intro">退款金额</div>
<div class="item_list">
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.onlineRefundAmount || 0) }}
</div>
<div class="label">
线上退款金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.cashRefundAmount || 0) }}
</div>
<div class="label">
现金退款金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.memberRefundAmount || 0) }}
</div>
<div class="label">
余额退款金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.onlineRechargeRefundAmount || 0) }}
</div>
<div class="label">
线上充值退款金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.cashRechargeRefundAmount || 0) }}
</div>
<div class="label">
现金充值退款金额
</div>
</div>
<div class="item">
<div class="n">
<span class="i"></span>
{{ multiplyAndFormat(tableData.refund.creditRefundAmount || 0) }}
</div>
<div class="label">
挂账退款金额
</div>
</div>
</div>
</div>
<div class="card" style="flex: 1;">
<div class="num" style="color: var(--el-color-warning);">
<span class="i"></span>
{{ multiplyAndFormat(tableData.order.orderAmount || 0) }}
</div>
<div class="intro">订单金额</div>
<div class="item_list">
<div class="item">
<div class="n">
{{ tableData.order.orderCount || 0 }}
</div>
<div class="label">
订单总数
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import dayjs from "dayjs";
import { ref, onMounted } from "vue";
import OrderApi from "@/api/order/order";
import { downloadFile, multiplyAndFormat } from '@/utils'
const queryForm = ref({
queryDate: dayjs().format('YYYY-MM-DD'), // 查询日期 yyyy-MM-dd
platform: 'czg',
shopId: localStorage.getItem('shopId') || '',
mainShopId: ''
});
const platformList = ref([
{ value: 'czg', label: '银收客' },
{ value: 'MTuan', label: '美团' },
{ value: 'CMeMe', label: '菜么么' },
{ value: 'KRuYun', label: '客如云' },
])
// 禁用今天以后的日期
const disabledFutureDate = (time) => {
return dayjs(time).isAfter(dayjs().startOf('day'))
}
// 查询
function handleQuery() {
getData()
}
// 重置
function handleReset() {
queryForm.value.queryDate = dayjs().format('YYYY-MM-DD')
queryForm.value.platform = 'czg'
getData()
}
// 导出
async function handleExport() {
try {
if (!queryForm.value.platform) {
ElMessage.error('请选择导出平台')
return
}
const res = await OrderApi.financeExport(queryForm.value)
downloadFile(res, '财务报表', 'xlsx')
} catch (error) {
console.log(error);
}
}
const loading = ref(false);
const tableData = ref({
turnover: {},
order: {},
discount: {},
refund: {},
sts: {}
});
// 查询财务报表
async function getData() {
try {
loading.value = true
const res = await OrderApi.financeSts(queryForm.value)
tableData.value = res
console.log('tableData.value', tableData.value);
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false
}, 500);
}
onMounted(() => {
getData()
})
</script>
<style scoped lang="scss">
.gyq_container {
padding: 14px;
.gyq_content {
padding: 14px;
background-color: #fff;
border-radius: 8px;
}
}
.between {
flex: 1;
display: flex;
justify-content: space-between;
}
.row {
display: flex;
gap: 14px;
&.mt14 {
margin-top: 14px;
}
}
.warp {
display: flex;
flex-direction: column;
gap: 14px;
}
.card {
border-radius: 12px;
background-color: #fff;
padding: 20px;
.header_title {
font-size: 16px;
color: 333;
font-weight: bold;
}
.content {
background-color: #F8F8F8;
border-radius: 8px;
margin-top: 20px;
padding: 14px;
display: flex;
gap: 14px;
.item_list {
margin-top: 14px;
.title {
font-size: 14px;
color: #666;
}
.item {
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
&:nth-child(odd) {
background-color: #F3F7FA;
}
.n {
font-size: 16px;
color: #333;
font-weight: bold;
display: flex;
align-items: center;
.i {
font-size: 10px;
position: relative;
top: 1px;
}
}
.label {
font-size: 14px;
color: #666;
}
}
}
}
.num {
font-size: 24px;
font-weight: bold;
display: flex;
align-items: center;
.i {
font-size: 12px;
position: relative;
top: 3px;
}
}
.intro {
font-size: 14px;
color: #999;
}
.pay_wrap {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-column-gap: 0px;
grid-row-gap: 14px;
background-color: #F8F8F8;
padding: 14px;
margin-top: 14px;
border-radius: 8px;
.item {
display: flex;
flex-direction: column;
.n {
font-size: 20px;
font-weight: bold;
color: #333;
display: flex;
align-items: center;
.i {
font-size: 12px;
position: relative;
top: 3px;
}
}
.t {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 4px;
}
}
}
.order_info {
border-top: 1px solid #ececec;
padding-top: 14px;
.order_title {
display: flex;
gap: 10px;
align-items: center;
.dot {
--size: 12px;
width: var(--size);
height: var(--size);
border-radius: 50%;
background-color: var(--el-color-danger);
}
.t {
font-size: 16px;
color: #333;
font-weight: bold;
}
}
.order_lits {
display: flex;
gap: 14px;
margin-top: 4px;
.line {
color: #ececec;
}
.item {
display: flex;
align-items: center;
gap: 14px;
.t {
font-size: 14px;
color: #666;
}
.n {
font-size: 16px;
color: #333;
font-weight: bold;
}
}
}
}
}
.waterfall {
/* 核心属性1定义瀑布流列数关键 */
column-count: 3;
/* 核心属性2列之间的间距替代margin避免卡片间距错乱 */
column-gap: 14px;
/* 可选:防止卡片内容被列分割(关键!避免卡片跨列断裂) */
break-inside: avoid;
.waterfall-card {
/* 必须:适配多列布局,避免卡片宽度溢出 */
width: 100%;
/* 卡片间距仅需设置底部外边距上下间距左右间距由column-gap控制 */
margin-bottom: 14px;
padding: 24px;
background-color: #fff;
border-radius: 12px;
/* 配合break-inside: avoid强化卡片不可分割 */
page-break-inside: avoid;
.header-title {
display: flex;
gap: 14px;
align-items: center;
padding-bottom: 10px;
.dot {
--size: 10px;
width: var(--size);
height: var(--size);
border-radius: 50%;
}
.t {
font-size: 24px;
color: #000;
font-weight: bold;
}
}
p {
margin-top: 10px;
font-size: 14px;
color: #666;
padding-left: 24px;
}
}
}
.tips {
margin-top: 6px;
font-size: 14px;
color: #999;
padding-left: 24px;
}
</style>

View File

@@ -52,6 +52,7 @@
<span>营业</span>
</div>
<div class="u-flex" style="flex-wrap: wrap">
<importData :type="9" style="margin-right: 14px;" @close="importDataClose" />
<el-select v-if="isHeadShop == 1 && loginType == 0" v-model="shopId" placeholder="选择分店"
style="width: 200px; margin-right: 10px;" @change="shopChange">
<el-option v-for="item in branchList" :key="item.shopId" :label="item.shopName" :value="item.shopId" />
@@ -101,7 +102,14 @@
<div class="item">
<div class="left">
<img class="icon" src="@/assets/images/data/scan.png" />
<span>主扫收款</span>
<span>二维码收款</span>
</div>
<span class="num">{{ trade.mainScanPayAmount || 0 }}</span>
</div>
<div class="item">
<div class="left">
<img class="icon" src="@/assets/images/data/scan.png" />
<span>扫码收款</span>
</div>
<span class="num">{{ trade.backScanPayAmount || 0 }}</span>
</div>
@@ -180,7 +188,7 @@
<div class="gropress l" :style="{
width: `${trade.rechargeAmount
? (trade.rechargeAmount /
(trade.memberPayAmount + trade.rechargeRefundAmount * 1)) *
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) *
100
: 0
}%`,
@@ -188,7 +196,7 @@
<div class="gropress r" :style="{
width: `${trade.rechargeRefundAmount
? (trade.rechargeRefundAmount /
(trade.memberPayAmount + trade.rechargeRefundAmount * 1)) *
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) *
100
: 0
}%`,
@@ -495,6 +503,7 @@
</template>
<script>
import importData from "@/components/importData/index.vue";
import dataSummaryApi from "@/api/order/data-summary";
import ShopApi from "@/api/account/shop";
import dayjs from "dayjs";
@@ -504,6 +513,7 @@ import { formatDateRange } from './utils/index.js'
import { multiplyAndFormat } from '@/utils/index.js'
export default {
name: "home",
components: { importData },
data() {
return {
multiplyAndFormat,
@@ -704,6 +714,15 @@ export default {
// this.initCardUserChart();
},
methods: {
importDataClose() {
// this.summaryGet();
this.dateAmount();
this.dateProduct();
// this.summaryDateGet();
this.timeChange(this.timeValue);
this.profitRateBarChart()
this.costLineChart()
},
/**
* 获取分店列表
*/

View File

@@ -44,6 +44,7 @@
<span v-if="!downloadLoading">导出Excel</span>
<span v-else>下载中...</span>
</el-button>
<importData :type="7" @close="getTableData" />
</el-form-item>
</el-form>
</div>
@@ -189,6 +190,7 @@
<script>
import _ from 'lodash'
import importData from "@/components/importData/index.vue";
import saleSummaryApi from "@/api/order/sale-summary";
import categoryApi from "@/api/product/productclassification";
import ShopApi from "@/api/account/shop";
@@ -197,6 +199,7 @@ import { downloadFile, multiplyAndFormat } from "@/utils/index";
import { formatDateRange } from './utils/index.js'
export default {
components: { importData },
data() {
return {
multiplyAndFormat,

View File

@@ -27,13 +27,14 @@
<el-option v-for="item in branchList" :key="item.shopId" :label="item.shopName" :value="item.shopId" />
</el-select>
</div>
<div>
<div style="display: flex;">
<el-button type="primary" @click="getTableData">查询</el-button>
<el-button @click="resetHandle">重置</el-button>
<el-button icon="download" v-loading="downloadLoading" @click="downloadHandle">
<span v-if="!downloadLoading">导出Excel</span>
<span v-else>下载中...</span>
</el-button>
<importData :type="8" @close="getTableData" />
</div>
</div>
</el-form>
@@ -90,16 +91,17 @@
</el-table-column>
</el-table> -->
</div>
<!-- <div class="head-container">
<el-pagination :total="tableData.total" :current-page="tableData.page + 1" :page-size="tableData.size"
<div class="head-container">
<el-pagination :total="tableData.total" :current-page="tableData.page" :page-size="tableData.size"
@current-change="paginationChange" @size-change="sizeChange"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
</div> -->
</div>
</div>
</template>
<script>
import _ from "lodash";
import importData from "@/components/importData/index.vue";
import tableSummaryApi from "@/api/order/table-summary";
import ShopApi from "@/api/account/shop";
import dayjs from "dayjs";
@@ -107,6 +109,7 @@ import { downloadFile } from "@/utils/index";
import { formatDateRange } from './utils/index.js'
export default {
components: { importData },
data() {
return {
timeValue: "today",
@@ -199,13 +202,14 @@ export default {
},
// 重置查询
resetHandle() {
this.timeValue = "";
this.timeValue = "today";
this.query = { ...this.resetQuery };
this.page = 1;
this.getTableData();
},
// 分页大小改变
sizeChange(e) {
this.tableData.page = 1
this.tableData.size = e;
this.getTableData();
},
@@ -225,8 +229,11 @@ export default {
// page: this.tableData.page,
// size: this.tableData.size,
rangeType: this.timeValue,
// rangeType: 'custom',
beginDate: this.query.createdAt[0],
endDate: this.query.createdAt[1],
// beginDate: '2026-01-26',
// endDate: '2026-01-26',
shopId: this.shopId
});
this.tableData.loading = false;

View File

@@ -163,7 +163,6 @@ const modalConfig: IModalConfig<editRequest> = {
options: options.printType,
initialValue: options.printType.map(v => v.value)
},
{
label: "打印机状态",
prop: "status",
@@ -174,6 +173,16 @@ const modalConfig: IModalConfig<editRequest> = {
inactiveValue: 0,
}
},
{
label: "媒体音开关",
prop: "volumeSwitch",
type: "switch",
initialValue: 1,
attrs: {
activeValue: 1,
inactiveValue: 0,
}
},
],
};

View File

@@ -1,5 +1,112 @@
<template>
<div>
<h1>Index</h1>
<div class="gyq_container">
<div class="item_wrap">
<div class="title">您好欢迎登录</div>
<div class="item_list">
<div class="item" v-for="(item, index) in quickStore.quickMenus.splice(0, 6)" :key="item.id"
@click="menuClick(item.menuId)">
<img class="icon" :src="icons[index + 1]" alt="">
{{ returnMenuName(item.menuId) }}
</div>
</div>
</div>
</div>
</template>
</template>
<script setup>
import { onMounted, nextTick } from "vue";
import { useQuickStore, usePermissionStore, useUserStore } from "@/store";
import icon1 from "@/assets/index_quick1.png";
import icon2 from "@/assets/index_quick2.png";
import icon3 from "@/assets/index_quick3.png";
import icon4 from "@/assets/index_quick4.png";
import icon5 from "@/assets/index_quick5.png";
import icon6 from "@/assets/index_quick6.png";
const icons = {
1: icon1,
2: icon2,
3: icon3,
4: icon4,
5: icon5,
6: icon6,
};
const userStore = useUserStore();
const quickStore = useQuickStore();
const permissionStore = usePermissionStore();
function returnMenuName(menuId) {
return permissionStore.returnMenuName(menuId);
}
function menuClick(menuId) {
permissionStore.menuJump(menuId);
}
onMounted(async () => {
await nextTick();
console.log('quickStore.quickMenus', quickStore.quickMenus);
})
</script>
<style scoped lang="scss">
.gyq_container {
width: 100%;
height: 100%;
background: url('@/assets/index_bg.png') no-repeat center center / cover;
display: flex;
align-items: center;
padding-bottom: 50px;
}
.item_wrap {
padding-left: 300px;
.title {
font-size: 40px;
margin-bottom: 50px;
font-weight: bold;
}
.item_list {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-column-gap: 90px;
grid-row-gap: 90px;
.item {
width: 152px;
height: 83px;
border-radius: 20px;
background: linear-gradient(115deg, #9EC4FF 5.9%, #6668E8 111.62%);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #fff;
padding-left: 30px;
&:hover {
cursor: pointer;
.icon {
transform: translateY(-10px);
}
}
.icon {
position: absolute;
left: -30px;
top: -20px;
width: 83px;
height: 78px;
transition: all 0.3s ease-in-out;
}
}
}
}
</style>

View File

@@ -70,16 +70,23 @@ const env = process.env.NODE_ENV
// DEV-START
const accountList = reactive([
{ username: "admin", type: 'primary', label: 'admin' },
// { username: "19191703856", type: 'warning', label: '喜气洋洋' },
// { username: "19107220837", type: 'danger', label: '快乐时光店铺' },
{ username: "18049104914", type: 'warning', label: '东风的店铺' },
{ username: "19107220837", type: 'danger', label: '快乐时光店铺' },
// { username: "18199991111", type: 'success', label: '草莓加盟主店可直接管理' },
{ username: "18821670757", type: 'success', label: '高歌的小店' },
// { username: "19112345678", type: 'danger', label: '酸橘子·云贵小馆' },
{ username: "18821670757", staffUserName: '18821670758', type: 'primary', label: '高歌的小店的员工-张三' },
{ username: "191123456", type: 'primary', label: '酸橘子' },
]);
// 快捷模拟登录
function accountHandle(item) {
state.loginForm.username = item.username;
if (item.staffUserName) {
state.loginForm.loginType = 1
state.loginForm.staffUserName = item.staffUserName
} else {
state.loginForm.loginType = 0
}
state.loginForm.code = 666666
const d = new Date();
state.loginForm.password = `czg${d.getHours().toString().padStart(2, '0')}${d.getMinutes().toString().padStart(2, '0')}`;

View File

@@ -12,7 +12,7 @@
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { ref, onMounted, nextTick, watch } from 'vue'
const props = defineProps({
// {label: '设置',value: 1}
@@ -29,10 +29,8 @@ const modelValue = defineModel('modelValue', {
required: true
})
// 改变索引
function changeHandle(index) {
modelValue.value = index
// 更新active_wrap位置
function updateActivePosition(index) {
let left = 0
itemsWidth.value.forEach((val, i) => {
if (i < index) {
@@ -40,7 +38,12 @@ function changeHandle(index) {
}
})
leftValue.value = left + gap.value * index
}
// 改变索引
function changeHandle(index) {
modelValue.value = index
updateActivePosition(index)
emits('change', index)
}
@@ -57,10 +60,14 @@ onMounted(() => {
console.log('itemRefs===', itemRefs.value);
console.log('itemsWidth===', itemsWidth.value);
changeHandle(modelValue.value)
updateActivePosition(modelValue.value)
})
})
// 监听modelValue变化更新位置
watch(modelValue, (newVal) => {
updateActivePosition(newVal)
})
</script>
<style scoped lang="scss">

View File

@@ -32,10 +32,10 @@ export const newMenus = [
intro: '协助商家拉来新客户,拓展客户群体',
childrenList: [
{
name: "分销",
name: "全民股东",
icon: "zhcz",
pathName: "distribution_page",
intro: "用户成为业务员,可促进消费"
intro: "用户成为股东,可促进消费"
},
{
name: "套餐推广",
@@ -55,6 +55,12 @@ export const newMenus = [
pathName: "group_booking",
intro: "拼团"
},
{
name: "分享配置",
icon: "fxpz",
pathName: "share_setting",
intro: "商家可配置用户分享后可获得的奖励"
},
]
},
{

View File

@@ -128,7 +128,8 @@ function submitHandle() {
// 过滤条件
function isDistribtion(str) {
let s = str.split('_')
return s[1]
let shopId = s[0]
return s[1] && shopId == localStorage.getItem('shopId') ? 1 : 0
}
const selectUser = ref([])

View File

@@ -0,0 +1,290 @@
<template>
<div class="form_content">
<div class="left">
<div class="new_preview">
<div class="header">{{ shopInfo.shopName }}</div>
<div class="content">
<div class="title">{{ form.title }}</div>
<div class="img_wrap">
<div class="img">
<el-image :src="form.groupUrl" style="width: 100%;height: 100%;border-radius: 4px;"></el-image>
</div>
</div>
<div class="intro">
{{ form.content }}
</div>
<div class="foot">
{{ defaultNote }}
</div>
</div>
</div>
</div>
<div class="form">
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="120px">
<el-form-item label="群二维码" prop="groupUrl">
<single-image-upload style="width: 120px; height: 120px" v-model="form.groupUrl"></single-image-upload>
</el-form-item>
<el-form-item label="模块标题" prop="title">
<el-input :placeholder="defaultTitle" :maxlength="15" v-model.trim="form.title"
style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="模块内容">
<el-input type="textarea" :rows="4" :maxlength="20" show-word-limit placeholder="请输入内容"
v-model.trim="form.content" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="模块提示语">
<div class="tips">{{ defaultNote }}</div>
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="form.isEnable" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="submitHandle">保存</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElNotification } from 'element-plus'
import { disGroupGet, disGroupPost } from '@/api/coupon/index'
const defaultTitle = ref('扫码进群,优惠多多')
const defaultNote = ref('如果长按不能识别,可截图或保存二维码图片至相册,通过微信扫码入群')
const defaultContent = ref('长按识别上方企微好友码,添加【福利官】')
const shopInfo = ref('')
const form = ref({
id: '',
groupUrl: '',
title: defaultTitle.value,
content: defaultContent.value,
isEnable: 1,
})
const rules = reactive({
groupUrl: [
{
required: true,
message: '请上传群二维码',
triiger: 'change'
}
],
title: [
{
required: true,
message: '请输入模块标题',
triiger: 'change'
}
],
content: [
{
required: true,
message: '请输入内容',
triiger: 'change'
}
]
})
// 开始提交
const formRef = ref(null)
const emits = defineEmits(["success"]);
const loading = ref(false);
function submitHandle() {
formRef.value.validate(async (valid) => {
try {
if (valid) {
loading.value = true;
let data = { ...form.value }
if (data.title == '') {
data.title = defaultTitle.value
}
if (data.content == '') {
data.content = defaultContent.value
}
await disGroupPost(data);
ElNotification({
title: '注意',
message: '保存成功',
type: 'success'
})
}
} catch (err) {
console.log(err);
}
loading.value = false;
});
}
// 获取配置信息
async function drainageConfigGetAjax() {
try {
const res = await disGroupGet()
form.value = res
if (form.value.title == '') {
form.value.title = defaultTitle.value
}
if (form.value.content == '') {
form.value.content = defaultContent.value
}
} catch (error) {
console.log(error);
}
}
onMounted(() => {
shopInfo.value = JSON.parse(localStorage.getItem('userInfo'))
drainageConfigGetAjax()
})
</script>
<style scoped lang="scss">
.form_content {
padding: 14px 0 0;
display: flex;
.left {
width: 374px;
height: 720px;
position: relative;
background: url('https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/3/7da616a1c56a409dbdea8bf2f41cf14b.png') no-repeat center center / 100% 100%;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
}
.preview {
width: 350px;
height: 130px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border-radius: 6px;
display: flex;
align-items: center;
padding: 14px;
.info {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5px 0;
.top {
flex: 1;
.title {
font-size: 16px;
color: #333;
font-weight: bold;
}
.content {
font-size: 12px;
color: #666;
}
}
.btm {
font-size: 14px;
color: #999;
}
}
.img_wrap {
width: 100px;
height: 100px;
.img {
width: 100%;
height: 100%;
}
}
}
.new_preview {
--bg: #3F3B37;
--color: #F6DFC4;
--borderColor: #f6dfc45b;
width: 90%;
background-color: var(--bg);
border-radius: 4px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.header {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: var(--color);
height: 50px;
border-bottom: 1px dashed var(--borderColor);
}
.content {
padding-bottom: 14px;
.title {
font-size: 14px;
color: var(--color);
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.img_wrap {
display: flex;
justify-content: center;
.img {
--size: 220px;
width: var(--size);
height: var(--size);
}
}
.intro {
height: 40px;
font-size: 14px;
color: var(--color);
display: flex;
align-items: center;
justify-content: center;
padding: 0 14px;
}
.foot {
height: 40px;
color: var(--borderColor);
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 14px;
text-align: center;
}
}
}
}
}
</style>

View File

@@ -18,7 +18,7 @@
<el-form-item label="获得佣金条件" prop="inviteCount">
<div class="column">
<el-input v-model="form.inviteCount" placeholder="请输入" :maxlength="8" style="width: 300px;"
@input="e => form.inviteCount = filterNumberInput(e, 1)">
@input="e => form.inviteCount = filterNumberInput(e, 0)">
<template #append></template>
</el-input>
<div class="tips">邀请达到指定人数才可赚取佣金</div>
@@ -135,8 +135,8 @@
<span class="required">*</span>分成比例
</div>
<div class="ipt">
<el-input v-model="item.levelOneCommission" placeholder="请输入" :maxlength="5"
style="width: 200px;" @input="e => item.levelOneCommission = filterNumberInput(e)">
<el-input v-model="item.commission" placeholder="请输入" :maxlength="5" style="width: 200px;"
@input="e => item.commission = filterNumberInput(e)">
<template #append>%</template>
</el-input>
</div>
@@ -202,15 +202,15 @@ const formRef = ref(null)
const formLoading = ref(false)
const levelConfigListObj = ref({
name: '', // 名称
inviteCount: '', // 有效人数
inviteCount: 0, // 有效人数
costAmount: '', // 消费金额
levelOneCommission: '', // 一级分销比例
commission: '', // 一级分销比例
levelTwoCommission: '', // 二级分销比例
})
const form = ref({
id: '',
openType: 'pay', // pay购买开通 auto自动开通 manual手动开通
inviteCount: '', // 邀请条件人数
inviteCount: 0, // 邀请条件人数
inviteConsume: 0, // 被邀请人消费有效 0 1
payAmount: '', // 购买开通金额
rewardCount: '', // 每人奖励次数
@@ -281,7 +281,7 @@ const rules = ref({
callback(new Error(tips))
return
}
if (item.levelOneCommission === '' || item.levelOneCommission == 0) {
if (item.commission === '' || item.commission == 0) {
tips = `请输入${index + 1}级的分成比例`
callback(new Error(tips))
return

View File

@@ -1,7 +1,7 @@
<template>
<div class="gyq_container">
<div class="gyq_content">
<HeaderCard name="分销" intro="用户成为业务员,可促进消费" icon="xffx" showSwitch v-model:isOpen="form.isEnable">
<HeaderCard name="全民股东" intro="用户成为股东,可促进消费" icon="xffx" showSwitch v-model:isOpen="form.isEnable">
</HeaderCard>
<div class="tips">
<el-alert title="请记得前往《分销明细》充值余额,余额不足时用户收益将无法正常入账" type="primary" show-icon :closable="false" />
@@ -18,6 +18,8 @@
<activation_record key="activationRecord" v-if="tabActiveIndex == 2" />
<!-- 分销明细 -->
<distribution_details key="distributionDetails" v-if="tabActiveIndex == 3" />
<!-- 股东管理群 -->
<groupSetting name="groupSetting" key="groupSetting" v-if="tabActiveIndex == 4" />
</div>
</div>
</div>
@@ -30,6 +32,7 @@ import setting from "./components/setting.vue";
import distributor from "./components/distributor.vue";
import activation_record from "./components/activation_record.vue";
import distribution_details from "./components/distribution_details.vue";
import groupSetting from "./components/groupSetting.vue";
import { ref, onMounted, watch } from 'vue'
import { distributionGet, distributionPut } from '@/api/coupon'
@@ -53,6 +56,10 @@ const tabList = ref([
label: '分销明细',
value: 3
},
{
label: '股东管理群',
value: 3
},
])
const form = ref({

View File

@@ -0,0 +1,179 @@
<template>
<el-dialog :title="form.id ? '编辑' : '添加'" width="800px" v-model="visible" @closed="resetForm">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-position="left">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入轮播图名称" :maxlength="20" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="轮播图片" prop="imageUrl">
<div class="column">
<div class="center">
<SingleImageUpload v-model="form.imageUrl" />
</div>
<div class="tips" style="margin-bottom: 8px;">建议尺寸750x300像素支持jpgpnggif格式大小不超过2MB</div>
</div>
</el-form-item>
<el-form-item label="是否可分享" prop="isShareable">
<div class="column">
<div>
<el-radio-group v-model="form.isShareable">
<el-radio label="开启" :value="1"></el-radio>
<el-radio label="关闭" :value="0"></el-radio>
</el-radio-group>
</div>
<div class="tips">开启时用户端会显示单独的分享按钮</div>
</div>
</el-form-item>
<el-form-item label="跳转页面" prop="jumpPageId">
<linkCard :includes-names="includesNames" v-model="form.jumpPageId" v-model:extendParam="form.extendParam" />
</el-form-item>
<el-form-item label="排序值" prop="sort">
<el-input v-model="form.sort" @input="e => form.sort = filterNumberInput(e, 1)" style="width: 300px;"
placeholder="排序值越大越靠前"></el-input>
</el-form-item>
<el-form-item label="是否启用">
<el-radio-group v-model="form.isEnabled">
<el-radio label="启用" :value="1"></el-radio>
<el-radio label="禁用" :value="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="loading"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import _ from 'lodash';
import { ref } from 'vue';
import { filterNumberInput } from '@/utils';
import SingleImageUpload from '@/components/Upload/SingleImageUpload.vue';
import linkCard from './linkCard.vue';
import { shareCarouselPost } from '@/api/coupon/index.js';
const visible = ref(false);
const loading = ref(false);
const formObj = ref({
id: '', // 轮播图ID编辑时传递
name: '', // 轮播图名称20字内
imageUrl: '', // 轮播图片地址
isShareable: 1, // 是否可分享 1=开启 0=关闭
jumpPageId: '', // 跳转页面 tb_mini_app_pages的 id
extendParam: '', // 扩展参数
sort: '', // 排序值,数值越大越靠前
isEnabled: 1, // 是否启用 1=启用 0=禁用
});
const form = ref(_.cloneDeep(formObj.value));
function resetForm() {
form.value = _.cloneDeep(formObj.value);
}
const formRef = ref(null);
const rules = {
name: [
{ required: true, message: '请输入轮播图名称', trigger: 'blur' }
],
imageUrl: [
{ required: true, message: '请上传轮播图片', trigger: 'change' }
],
jumpPageId: [
{
validator: (rule, value, callback) => {
if (form.value.jumpPageId && _.includes(includesNames.value.map(item => item.id), form.value.jumpPageId)) {
if (!form.value.extendParam) {
callback(new Error('请选择具体菜品'));
} else {
callback();
}
} else {
callback();
}
},
trigger: 'change'
}
],
sort: [
{ required: true, message: '请输入排序值', trigger: 'blur' }
],
}
const includesNames = ref([
{
id: '4',
name: '套餐推广商品详情页'
},
{
id: '6',
name: '商品拼团详情页'
},
{
id: '10',
name: '点餐商品详情弹窗页'
},
{
id: '12',
name: '积分商品详情页面'
}
]);
function submitForm() {
console.log(form.value);
formRef.value.validate((valid) => {
if (valid) {
shareCarouselPostAjax();
}
});
}
const emits = defineEmits(['success']);
// 轮播图配置:新增/修改
async function shareCarouselPostAjax() {
try {
loading.value = true;
await shareCarouselPost(form.value);
ElNotification({
title: '成功',
message: '保存成功',
type: 'success',
duration: 2000
});
emits('success');
visible.value = false;
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
function show(obj) {
visible.value = true;
if (obj && obj.id) {
form.value = _.cloneDeep(obj)
}
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.center {
display: flex;
}
.column {
display: flex;
flex-direction: column;
}
.tips {
font-size: 14px;
color: #999;
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<div>
<div class="row">
<el-form :model="queryForm" inline>
<el-form-item>
<el-button type="primary" icon="Plus" @click="AddBannerRef.show()">添加</el-button>
</el-form-item>
<el-form-item>
<el-select v-model="queryForm.isShareable" style="width: 150px;">
<el-option label="全部" :value="''" />
<el-option label="开启" :value="1" />
<el-option label="关闭" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="queryForm.isEnabled" style="width: 150px;">
<el-option label="全部" :value="''" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="queryForm.name" placeholder="请输入名称搜索" style="width: 200px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" :loading="tableData.loading" @click="queryHandle">搜索</el-button>
<el-button icon="Refresh" :loading="tableData.loading" @click="resetHandle">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="row">
<el-table :data="tableData.list" border stripe v-loading="tableData.loading">
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="图片" prop="imageUrl">
<template v-slot="scope">
<el-image :src="scope.row.imageUrl" style="width: 50px;height: 50px;"></el-image>
</template>
</el-table-column>
<!-- <el-table-column label="分享" prop="isShareable">
<template v-slot="scope">
<span v-if="scope.row.isShareable == 1">开启</span>
<span v-else>关闭</span>
</template>
</el-table-column> -->
<el-table-column label="启用状态" prop="isEnabled">
<template v-slot="scope">
<el-tag v-if="scope.row.isEnabled == 1" type="primary" disable-transitions>已启用</el-tag>
<el-tag v-if="scope.row.isEnabled == 0" type="info" disable-transitions>已禁用</el-tag>
</template>
</el-table-column>
<el-table-column label="排序值" prop="sort"></el-table-column>
<el-table-column label="创建时间" prop="createTime" width="200"></el-table-column>
<el-table-column label="操作" width="150">
<template v-slot="scope">
<el-button link type="primary" @click="AddBannerRef.show(scope.row)">编辑</el-button>
<el-popconfirm title="确认要删除吗?" @confirm="deleteHandle(scope.row)">
<template #reference>
<el-button type="danger" link>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<AddBanner @success="queryHandle" ref="AddBannerRef" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import AddBanner from './add_banner.vue';
import { shareCarouselGet, shareCarouselDel } from '@/api/coupon/index.js';
const AddBannerRef = ref(null);
const queryForm = ref({
name: '',
isShareable: '', // 是否可分享 1=开启 0=关闭
isEnabled: '', // 是否启用 1=启用 0=禁用
})
const tableData = ref({
loading: false,
list: []
})
// 搜索
function queryHandle() {
getTableData();
}
// 重置
function resetHandle() {
queryForm.value = {
name: '',
isShareable: '',
isEnabled: '',
};
queryHandle();
}
// 轮播图配置
async function getTableData() {
try {
tableData.value.loading = true;
const res = await shareCarouselGet(queryForm.value);
tableData.value.list = res;
} catch (error) {
console.log(error);
}
tableData.value.loading = false;
}
// 删除轮播图配置
async function deleteHandle(row) {
try {
await shareCarouselDel(row.id);
getTableData();
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getTableData();
})
</script>
<style scoped lang="scss">
.row {
&.mt14 {
margin-top: 14px;
}
}
</style>

View File

@@ -0,0 +1,213 @@
<template>
<div>
<el-select v-model="jumpPageId" style="width: 300px;" placeholder="请选择跳转页面" @change="jumpPageIdChange">
<el-option v-for="item in miniPageList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<div class="tips">选填轮播图点击后跳转的目标页面</div>
<el-select v-model="goodsId" style="width: 300px;" placeholder="请选择具体菜品,可搜索" clearable filterable remote
:remote-method="remoteMethod" :loading="loading" v-if="jumpPageId && isIncludeFlag" @change="goodsIdChange">
<el-option v-for="item in goodsList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</div>
</template>
<script setup>
import _ from 'lodash';
import { ref, onMounted, watch } from 'vue';
import { pointsGoodsPage } from '@/api/points/index.js'
import API from '@/views/application/list/advertisement/indexconfig/api';
import { packageGet, getGbWarePage } from '@/api/market/ware'
import productApi from '@/api/product';
const props = defineProps({
includesNames: {
type: Array,
default: () => ([])
}
});
const jumpPageId = defineModel({
type: [String, Number],
default: ''
});
const jumpPageName = ref('');
const isIncludeFlag = ref(false);
function checkIsInclude() {
// 增加边界判断避免includesNames为空或item.id不存在导致的错误
if (!jumpPageId.value || !Array.isArray(props.includesNames) || props.includesNames.length === 0) {
return false;
}
// 提取id数组并判断包含关系
const idList = props.includesNames.map(item => item?.id); // 可选链避免item.id不存在
return _.includes(idList, jumpPageId.value);
}
watch(jumpPageId, async (newVal, oldVal) => {
if (newVal) {
await nextTick()
watchJumpPageIdChange(newVal)
}
}, {
immediate: true, // 可选:首次加载时立即执行一次(根据需求决定)
deep: false // 无需深度监听因为值是String/Number
})
const miniPageList = ref([]);
// 获取小程序跳转页面列表
async function getList() {
try {
const res = await API.miniAppPagesPage({ page: 1, size: 999 });
miniPageList.value = res.records;
} catch (error) {
console.log(error);
}
}
function jumpPageIdChange(value) {
goodsId.value = '';
extendParam.value = ''
jumpPageName.value = miniPageList.value.find(item => item.id == value)?.name || '';
// 清空具体菜品选择
if (checkIsInclude()) {
isIncludeFlag.value = true;
getGoodsList()
} else {
isIncludeFlag.value = false;
}
}
// watch监听jumpPageId变化
async function watchJumpPageIdChange(value) {
if (miniPageList.value.length === 0) {
await getList()
}
// 清空具体菜品选择
if (checkIsInclude()) {
jumpPageName.value = miniPageList.value.find(item => item.id == value)?.name || '';
isIncludeFlag.value = true;
getGoodsList()
} else {
isIncludeFlag.value = false;
}
}
const loading = ref(false);
const goodsList = ref([]); // 具体菜品列表
const goodsId = defineModel('goodsId', {
type: [String, Number],
default: ''
});
const extendParam = defineModel('extendParam', {
type: String,
default: ''
});
// 监听extendParam的变化解析并赋值给goodsId
watch(extendParam, async (newExtendParam, oldExtendParam) => {
// console.log('extendParam变化', newExtendParam);
// 1. 空值判断如果extendParam为空重置goodsId
if (!newExtendParam) {
goodsId.value = '';
return;
}
// 2. 解析字符串匹配goodsId=xxx的格式
// 正则表达式说明匹配goodsId=后接任意字符(非空),直到字符串结束或遇到&等分隔符
const reg = /goodsId=([^&]+)/;
const matchResult = newExtendParam.match(reg);
// 3. 提取值并赋值:匹配成功则赋值,否则重置
if (matchResult && matchResult[1]) {
let extractedGoodsId = matchResult[1];
// 尝试将 id 转为数字(如果看起来像数字)以保持与后端 id 类型一致
const numeric = Number(extractedGoodsId);
const parsedId = !Number.isNaN(numeric) ? numeric : extractedGoodsId;
// 确保 options 已加载,这样 el-select 能根据 value 匹配到对应的 label
try {
await getGoodsList();
} catch (e) {
// ignore
}
// 统一使用字符串类型的 id 以保证与 options 中的 value 匹配
goodsId.value = String(parsedId);
} else {
goodsId.value = ''; // 匹配失败时清空
}
}, {
immediate: true, // 初始加载时立即执行一次解析
deep: false // 字符串是基本类型,无需深度监听
});
// 具体菜品选择变化
function goodsIdChange(value) {
// 设置扩展参数,处理清空情况以避免出现 'undefined'
if (value === null || typeof value === 'undefined' || value === '') {
extendParam.value = '';
goodsId.value = '';
return;
}
extendParam.value = `goodsId=${value}`;
}
function remoteMethod(value) {
getGoodsList(value);
}
// 获取商品咧白哦
async function getGoodsList(value = '') {
try {
// 获取套餐列表
loading.value = true;
let list = [];
if (jumpPageName.value == '套餐推广商品详情页') {
const res = await packageGet({ page: 1, size: 999, packageName: value });
list = res.records.map(item => ({
id: String(item.id),
name: item.packageName
}));
} else if (jumpPageName.value == '商品拼团详情页') {
const res = await getGbWarePage({ page: 1, size: 999, wareName: value });
list = res.records.map(item => ({
id: String(item.id),
name: item.wareName
}));
} else if (jumpPageName.value == '点餐商品详情弹窗页') {
const res = await productApi.getPage({ name: value });
list = res.records.map(item => ({
id: String(item.id),
name: item.name
}));
} else if (jumpPageName.value == '积分商品详情页面') {
const res = await pointsGoodsPage({ page: 1, size: 999 });
list = res.records.map(item => ({
id: String(item.id),
name: item.goodsName
}));
}
goodsList.value = list;
loading.value = false;
} catch (error) {
console.log(error);
loading.value = false;
}
}
onMounted(() => {
getList()
})
</script>
<style scoped lang="scss">
.tips {
font-size: 14px;
color: #999;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" label-position="left">
<el-form-item label="功能开启">
<el-switch v-model="form.isEnabled" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="可获得奖励的分享" prop="rewardSharePages">
<el-checkbox-group v-model="form.rewardSharePages">
<el-checkbox :label="item.label" :value="item.value" v-for="item in typesList" :key="item.value"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<div class="header">分享人可获得奖励</div>
<el-form-item label="选择优惠券" prop="sharerCouponId">
<SelectCoupon v-model="form.sharerCouponId" v-model:name="form.sharerCouponName" />
</el-form-item>
<el-form-item label="单次获得优惠券数量" prop="sharerCouponNum">
<el-input v-model="form.sharerCouponNum" placeholder="请输入数量"
@input="e => form.sharerCouponNum = filterNumberInput(e, 1)" style="width: 300px;" />
</el-form-item>
<el-form-item label="可获得奖励次数">
<el-radio-group v-model="form.rewardTimesType">
<el-radio label="仅1次" :value="1"></el-radio>
<el-radio label="每次分享成功" :value="2"></el-radio>
</el-radio-group>
</el-form-item>
<div class="header">被分享人可获得奖励</div>
<el-form-item label="选择优惠券">
<SelectCoupon v-model="form.sharedUserCouponId" v-model:name="form.sharedUserCouponName" />
</el-form-item>
<el-form-item label="单次获得优惠券数量">
<el-input v-model="form.sharedUserCouponNum" placeholder="请输入数量"
@input="e => form.sharedUserCouponNum = filterNumberInput(e, 1)" style="width: 300px;" />
</el-form-item>
<el-form-item label="被分享人弹窗">
<el-radio-group v-model="form.isSharedUserPopup">
<el-radio label="是" :value="1"></el-radio>
<el-radio label="否" :value="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import _ from 'lodash'
import { ref, onMounted } from 'vue'
import typesList from '../data.js'
import SelectCoupon from '@/components/selectCoupon/index.vue'
import { filterNumberInput } from '@/utils/index.js'
import { shareBasePost, shareBaseGet } from '@/api/coupon/index.js'
const formRef = ref(null)
const form = ref({
isEnabled: 0,
rewardSharePages: [], // 可获得奖励的分享页面,用逗号分隔 typesList
sharerCouponId: '', // 分享人可获得的优惠券ID
sharerCouponName: '', // 分享人可获得的优惠券名称
sharerCouponNum: '', // 分享人可获得的优惠券数量
rewardTimesType: 1, // 可获得奖励次数 1=仅1次 2=每次分享成功
sharedUserCouponId: '', // 被分享人可获得的优惠券ID
sharedUserCouponName: '', // 被分享人可获得的优惠券名称
sharedUserCouponNum: '', // 被分享人可获得的优惠券数量
isSharedUserPopup: 1, // 被分享人获得奖励时是否弹窗 1=是 0=否
})
const rules = {
rewardSharePages: [
{ required: true, message: '请选择可获得奖励的分享', trigger: 'change' }
],
sharerCouponId: [
{ required: true, message: '请选择优惠券', trigger: 'change' }
],
sharerCouponNum: [
{ required: true, message: '请输入单次获得优惠券数量', trigger: 'blur' }
]
}
function submitForm() {
console.log(form.value);
formRef.value.validate((valid) => {
if (valid) {
console.log('提交表单', form.value)
shareBasePostAjax()
} else {
console.log('表单验证失败')
return false
}
})
}
// 分享奖励基础:新增/修改
async function shareBasePostAjax() {
try {
const data = _.cloneDeep(form.value);
data.rewardSharePages = data.rewardSharePages.join(',');
await shareBasePost(data);
ElNotification({
title: '成功',
message: '保存成功',
type: 'success',
duration: 2000
});
} catch (error) {
console.log(error);
}
}
// 分享奖励基础 查询
async function shareBaseGetAjax() {
try {
const res = await shareBaseGet();
if (res) {
form.value = { ...form.value, ...res };
form.value.rewardSharePages = res.rewardSharePages ? res.rewardSharePages.split(',') : [];
}
} catch (error) {
console.log(error);
}
}
onMounted(() => {
// 初始化数据
shareBaseGetAjax();
});
</script>
<style scoped lang="scss">
.header {
color: #333;
font-size: 16px;
font-weight: 600;
margin: 0 0 20px 0;
}
</style>

View File

@@ -0,0 +1,38 @@
export default [
{
label: '店铺首页',
value: 'index'
},
// {
// label: '我的',
// value: 'dine'
// },
{
label: '点餐页',
value: 'eat'
},
{
label: '点餐页-详情',
value: 'eat-detail'
},
{
label: '套餐推广-列表',
value: 'pp-list'
},
{
label: '套餐推广-详情',
value: 'pp-detail'
},
{
label: '商品拼团-列表',
value: 'gb-list'
},
{
label: '商品拼团-详情',
value: 'gb-detail'
},
{
label: '全民股东',
value: 'dis'
}
]

View File

@@ -0,0 +1,56 @@
<template>
<div class="gyq_container">
<div class="gyq_content">
<HeaderCard name="分享配置" intro="商家可配置用户分享后可获得的奖励" icon="fxpz">
</HeaderCard>
<div class="row mt14">
<tabHeader v-model="tabActiveIndex" :list="tabList" />
</div>
<div class="row mt14">
<!-- 基础设置 -->
<setting key="setting" name="setting" v-if="tabActiveIndex == 0" />
<!-- 点餐页轮播图 -->
<bannerSetting key="bannerSetting" name="bannerSetting" v-if="tabActiveIndex == 1" />
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import HeaderCard from "../components/headerCard.vue";
import tabHeader from "../components/tabHeader.vue";
import setting from "./components/setting.vue";
import bannerSetting from "./components/banner_setting.vue";
const tabActiveIndex = ref(0)
const tabList = ref([
{
label: '基础设置',
value: 0
},
{
label: '点餐页轮播图',
value: 1
},
])
</script>
<style scoped lang="scss">
.gyq_container {
padding: 14px;
.gyq_content {
padding: 14px;
background-color: #fff;
border-radius: 8px;
}
}
.row {
&.mt14 {
margin-top: 14px;
}
}
</style>

View File

@@ -233,7 +233,7 @@ import shopExtendApi from "@/api/account/shopExtend";
export default {
data() {
return {
tableActive: "ticket_logo",
tableActive: "index_bg",
tableData: [],
selectItem: {},
imageUrl: "",

View File

@@ -81,9 +81,9 @@
<div>优惠券抵扣{{ detail.fullCouponDiscountAmount }}</div>
</div>
<div class="row">
<div>满减活动{{ detail.discountActAmount || '-' }}</div>
<div>新客立减{{ detail.newCustomerDiscountAmount || '-' }}</div>
<div>会员折扣{{ detail.vipDiscountAmount || '-' }}</div>
<div>满减活动{{ detail.discountActAmount || "-" }}</div>
<div>新客立减{{ detail.newCustomerDiscountAmount || "-" }}</div>
<div>会员折扣{{ detail.vipDiscountAmount || "-" }}</div>
</div>
<div class="row">
<div>退单金额{{ detail.refundAmount }}</div>
@@ -93,8 +93,13 @@
<div>
实收金额
<span style="color: red">{{ detail.payAmount }}</span>
<el-button v-if="detail.status != 'unpaid' && detail.refundAmount < detail.payAmount" size="small"
type="danger" class="u-m-l-10" @click="tuikuan()">
<el-button
v-if="detail.status != 'unpaid' && detail.refundAmount < detail.payAmount"
size="small"
type="danger"
class="u-m-l-10"
@click="tuikuan()"
>
<span>退款</span>
</el-button>
</div>
@@ -111,7 +116,11 @@
退款详情>
</span> -->
</div>
<div class="color-red">退款方式{{ detail.refundType }}</div>
<div class="color-red">
<span v-if="detail.refundType">
退款方式{{ detail.refundType === "cash" ? "现金退款" : "原路退回" }}
</span>
</div>
<div></div>
<div></div>
</div>
@@ -121,29 +130,44 @@
<div style="margin-bottom: 16px; font-size: 16px">商品信息</div>
<template v-for="(item, index) in detail.detailMap" :key="index">
<h4>{{ index }}次下单</h4>
<el-table :data="item" :ref="'refTable' + index" @select-all="tableSelectAll($event, index)">
<el-table
:data="item"
:ref="'refTable' + index"
@select-all="tableSelectAll($event, index)"
>
<!-- <el-table-column type="selection" width="55" /> -->
<el-table-column label="数量" type="selection">
<template v-slot="scope">
<div v-if="detail.status == 'unpaid'">
<el-checkbox v-if="scope.row.num - scope.row.returnNum > 0" v-model="scope.row.checked" />
<el-checkbox
v-if="scope.row.num - scope.row.returnNum > 0"
v-model="scope.row.checked"
/>
</div>
<div v-else>
<el-checkbox v-if="scope.row.num - scope.row.refundNum > 0" v-model="scope.row.checked" />
<el-checkbox
v-if="scope.row.num - scope.row.refundNum > 0"
v-model="scope.row.checked"
/>
</div>
</template>
</el-table-column>
<el-table-column label="商品" width="150">
<template v-slot="scope">
<div class="shop_info">
<el-image :src="scope.row.productImg" style="width: 40px; height: 40px"></el-image>
<el-image
:src="scope.row.productImg"
style="width: 40px; height: 40px"
></el-image>
<div class="info">
<span :class="[scope.row.isVip == 1 ? 'colorStyle' : '']">
{{ scope.row.productName }}
</span>
<span style="color: #999">{{ scope.row.productSkuName }}</span>
<div>
<el-tag v-if="scope.row.isTimeDiscount" type="danger" disable-transitions>限时折扣</el-tag>
<el-tag v-if="scope.row.isTimeDiscount" type="danger" disable-transitions>
限时折扣
</el-tag>
</div>
</div>
</div>
@@ -167,17 +191,32 @@
<el-table-column label="实付">
<template v-slot="scope">{{ scope.row.payAmount }}</template>
</el-table-column>
<el-table-column v-if="detail.status == 'unpaid'" label="可退菜数量" align="center" width="130px">
<el-table-column
v-if="detail.status == 'unpaid'"
label="可退菜数量"
align="center"
width="130px"
>
<template v-slot="scope">
<el-input-number v-if="scope.row.checked" :min="0" style="width: 100px" v-model="scope.row.selNumber"
:max="scope.row.num - scope.row.returnNum"></el-input-number>
<el-input-number
v-if="scope.row.checked"
:min="0"
style="width: 100px"
v-model="scope.row.selNumber"
:max="scope.row.num - scope.row.returnNum"
></el-input-number>
<span class="" v-else>{{ scope.row.num - scope.row.returnNum }}</span>
</template>
</el-table-column>
<el-table-column v-else label="可退款数量" align="center" width="130px">
<template v-slot="scope">
<el-input-number v-if="scope.row.checked" :min="0" style="width: 100px" v-model="scope.row.selNumber"
:max="scope.row.num - scope.row.refundNum - scope.row.returnNum"></el-input-number>
<el-input-number
v-if="scope.row.checked"
:min="0"
style="width: 100px"
v-model="scope.row.selNumber"
:max="scope.row.num - scope.row.refundNum - scope.row.returnNum"
></el-input-number>
<span class="" v-else>
{{ scope.row.num - scope.row.refundNum - scope.row.returnNum }}
</span>
@@ -192,13 +231,23 @@
<el-table-column label="操作" fixed="right">
<template v-slot="scope">
<template v-if="detail.status != 'unpaid'">
<el-button v-if="canTuikuan(scope.row)" link size="small" @click="tuikuan(scope.row)">
<el-button
v-if="canTuikuan(scope.row)"
link
size="small"
@click="tuikuan(scope.row)"
>
<span>退款</span>
</el-button>
<span class="color-999" v-if="scope.row.status == 'refund'">已退款</span>
</template>
<template v-if="detail.status == 'unpaid'">
<el-button v-if="canTuicai(scope.row)" link size="small" @click="tuicai(scope.row)">
<el-button
v-if="canTuicai(scope.row)"
link
size="small"
@click="tuicai(scope.row)"
>
<span>退菜</span>
</el-button>
<span class="color-999" v-else>已退菜</span>
@@ -208,25 +257,41 @@
</el-table>
</template>
<!-- 退款 -->
<div class="u-p-20 u-flex u-row-right" v-if="
detail.status !== 'refund' &&
detail.status !== 'unpaid' &&
detail.status !== 'cancelled'
">
<el-checkbox v-model="allSelected" @change="allSelectedChange" label="全选"></el-checkbox>
<div
class="u-p-20 u-flex u-row-right"
v-if="
detail.status !== 'refund' &&
detail.status !== 'unpaid' &&
detail.status !== 'cancelled'
"
>
<el-checkbox
v-model="allSelected"
@change="allSelectedChange"
label="全选"
></el-checkbox>
<el-button type="danger" class="u-m-l-20" @click.stop="tuikuan('all')">退款</el-button>
</div>
<!-- 退菜 -->
<div class="u-p-20 u-flex u-row-right" v-if="detail.status == 'unpaid'">
<el-checkbox v-model="allSelected" @change="allSelectedChange" label="全选"></el-checkbox>
<el-checkbox
v-model="allSelected"
@change="allSelectedChange"
label="全选"
></el-checkbox>
<el-button type="danger" class="u-m-l-20" @click.stop="tuicai('all')">退菜</el-button>
</div>
</div>
</div>
</el-drawer>
<!-- 退款 -->
<return-money :modal="false" ref="refReturnMoney" :max="selGoods.num" :goods="selGoods"
@confirm="refReturnMoneyConfirm"></return-money>
<return-money
:modal="false"
ref="refReturnMoney"
:max="selGoods.num"
:goods="selGoods"
@confirm="refReturnMoneyConfirm"
></return-money>
<!-- 退菜 -->
<order-return-cart ref="refReturnCart" @confirm="refReturnCartConfirm"></order-return-cart>
</div>
@@ -330,8 +395,8 @@ export default {
if (!payType) {
return "";
}
console.log(payType.replace("_pay", ""));
return returnOptionsLabel("payType", payType.replace("_pay", ""));
console.log(payType);
return returnOptionsLabel("payType", payType);
},
to2(n) {
return Number(n).toFixed(2);
@@ -345,7 +410,8 @@ export default {
},
sendTypeFilter(t) {
if (t) {
return orderEnum.sendType.find((item) => item.key == t).label;
const item = orderEnum.sendType.find((item) => item.key == t);
return item ? item.label : "-";
} else {
return t;
}

View File

@@ -7,17 +7,9 @@
@reset-click="handleResetClick" /> -->
<!-- 列表 -->
<page-content
ref="contentRef"
:content-config="contentConfig"
@add-click="handleAddClick"
@edit-click="handleEditClick"
@export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
>
<page-content ref="contentRef" :content-config="contentConfig" @add-click="handleAddClick"
@edit-click="handleEditClick" @export-click="handleExportClick" @search-click="handleSearchClick"
@toolbar-click="handleToolbarClick" @operat-click="handleOperatClick" @filter-change="handleFilterChange">
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
@@ -25,12 +17,7 @@
</template>
<template #slotNameimage="scope">
<el-image
v-if="scope.row.pic"
:src="scope.row.pic"
lazy
style="width: 40px; height: 40px"
/>
<el-image v-if="scope.row.pic" :src="scope.row.pic" lazy style="width: 40px; height: 40px" />
<div v-else></div>
</template>
<template #gender="scope">
@@ -38,42 +25,26 @@
</template>
<template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text>
<copy-button
v-if="scope.row[scope.prop]"
:text="scope.row[scope.prop]"
style="margin-left: 2px"
/>
<copy-button v-if="scope.row[scope.prop]" :text="scope.row[scope.prop]" style="margin-left: 2px" />
</template>
</page-content>
<!-- 新增 -->
<page-modal
ref="addModalRef"
:modal-config="addModalConfig"
@submit-click="handleSubmitClick"
>
<page-modal ref="addModalRef" :modal-config="addModalConfig" @submit-click="handleSubmitClick">
<template #gender="scope">
<Dict v-model="scope.formData[scope.prop]" code="gender" />
</template>
</page-modal>
<!-- 编辑 -->
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<page-modal ref="editModalRef" :modal-config="editModalConfig" @submit-click="handleSubmitClick">
<template #gender="scope">
<Dict v-model="scope.formData[scope.prop]" code="gender" v-bind="scope.attrs" />
</template>
</page-modal>
</template>
<template v-else>
<page-content
ref="contentRef"
:content-config="contentConfig2"
@operat-click="handleOperatClick"
>
<page-content ref="contentRef" :content-config="contentConfig2" @operat-click="handleOperatClick">
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
@@ -95,7 +66,7 @@ import contentConfig from "./categoryconfig/content";
import contentConfig2 from "./categoryconfig/content2";
import editModalConfig from "./categoryconfig/edit";
import searchConfig from "./categoryconfig/search";
import { isSyncStatus } from "@/utils/index";
import { isSyncStatus, downloadFile } from "@/utils/index";
const {
searchRef,
@@ -107,7 +78,7 @@ const {
// handleAddClick,
// handleEditClick,
handleSubmitClick,
handleExportClick,
// handleExportClick,
handleSearchClick,
handleFilterChange,
} = usePage();
@@ -123,6 +94,18 @@ if (isSyncStatus()) {
contentConfig.cols[contentConfig.cols.length - 1].operat[0].hidden = false
contentConfig.cols[contentConfig.cols.length - 1].operat[1].hidden = false
}
// 导出
async function handleExportClick() {
try {
const filterParams = searchRef.value?.getQueryParams();
const file = await UserAPI.exportCategories(filterParams);
downloadFile(file, "商品分类列表", "xlsx");
} catch (error) {
console.log(error);
}
}
// 新增
async function handleAddClick() {
addModalRef.value?.setModalVisible();

View File

@@ -52,6 +52,13 @@ const contentConfig: IContentConfig<UserPageQuery> = {
name: "add",
auth: "import",
},
{
icon: "Download",
text: "导出",
type: "",
name: "export",
auth: "import",
},
],
cols: [
// { type: "selection", width: 50, align: "center" },
@@ -76,7 +83,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
fixed: "right",
width: 280,
templet: "tool",
operat: [{ text: "编辑", icon: 'edit', name: "edit"}, { text: "删除", icon: 'delete', type: 'danger', name: "delete"}],
operat: [{ text: "编辑", icon: 'edit', name: "edit" }, { text: "删除", icon: 'delete', type: 'danger', name: "delete" }],
},
],
};

View File

@@ -2,19 +2,34 @@
<div class="app-container">
<!-- 列表 -->
<!-- 搜索 -->
<page-search ref="searchRef" :search-config="searchConfig" @query-click="newHandleQueryClick"
@reset-click="handleResetClick2" />
<page-search
ref="searchRef"
:search-config="searchConfig"
@query-click="newHandleQueryClick"
@reset-click="handleResetClick2"
/>
<!-- 顶部数据 -->
<Statistics :data="gongjiData"></Statistics>
<!-- 列表 -->
<page-content ref="contentRef" :content-config="contentConfig" @add-click="handleAddClick"
@edit-click="handleEditClick" @export-click="handleExportClick" @search-click="handleSearchClick"
@toolbar-click="handleToolbarClick" @operat-click="handleOperatClick" @filter-change="handleFilterChange">
<page-content
ref="contentRef"
:content-config="contentConfig"
@add-click="handleAddClick"
@edit-click="handleEditClick"
@export-click="handleExportClick"
@search-click="handleSearchClick"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
>
<!-- <template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
</el-tag>
</template> -->
<template #custom>
<importData :type="3" @close="newHandleQueryClick" />
</template>
<template #type="scope">
{{ typeFilter(scope.row[scope.prop]) }}
</template>
@@ -22,11 +37,22 @@
<DictLabel v-model="scope.row[scope.prop]" code="gender" />
</template>
<template #shangjia="scope">
<el-switch v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchChange(scope.row)"></el-switch>
<el-switch
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchChange(scope.row)"
></el-switch>
</template>
<template #isStock="scope">
<el-switch disabled v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"></el-switch>
<el-switch
v-if="!scope.row.productId"
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
:disabled="!scope.row.type"
@click="isStockChange(scope.row)"
></el-switch>
</template>
<template #kucunedit="scope">
@@ -39,16 +65,29 @@
</template>
<template #tuikuantuihui="scope">
<el-switch v-if="!scope.row.productId" v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchhaocai(scope.row)"></el-switch>
<el-switch
v-if="!scope.row.productId"
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchhaocai(scope.row)"
></el-switch>
</template>
<template #sellOut="scope">
<el-switch v-model="scope.row[scope.prop]" :active-value="1" :inactive-value="0"
@click="handleSwitchChangeTwo(scope.row)"></el-switch>
<el-switch
v-model="scope.row[scope.prop]"
:active-value="1"
:inactive-value="0"
@click="handleSwitchChangeTwo(scope.row)"
></el-switch>
</template>
<template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text>
<copy-button v-if="scope.row[scope.prop]" :text="scope.row[scope.prop]" style="margin-left: 2px" />
<copy-button
v-if="scope.row[scope.prop]"
:text="scope.row[scope.prop]"
style="margin-left: 2px"
/>
</template>
<template #consumables="scope">
<template v-if="scope.row.type != null">
@@ -71,7 +110,11 @@
</page-modal>
<!-- 编辑 -->
<page-modal ref="editModalRef" :modal-config="editModalConfig" @submit-click="handleSubmitClick">
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<template #gender="scope">
<Dict v-model="scope.formData[scope.prop]" code="gender" v-bind="scope.attrs" />
</template>
@@ -118,16 +161,31 @@
<el-table-column label="序号" type="index" width="60"></el-table-column>
<el-table-column label="耗材名称" prop="consInfoId">
<template v-slot="scope">
<el-select v-model="scope.row.consInfoId" reserve-keyword placeholder="请输入关键词"
@change="selectionChange($event, scope.row)">
<el-option v-for="item in options" :key="item.id" :label="item.conName" :value="item.id"></el-option>
<el-select
filterable
v-model="scope.row.consInfoId"
reserve-keyword
placeholder="请输入关键词"
@change="selectionChange($event, scope.row)"
>
<el-option
v-for="item in options"
:key="item.id * 1"
:label="item.conName"
:value="item.id * 1"
></el-option>
</el-select>
<!-- <div class="tips" v-if="scope.row.stockNumber">库存{{ scope.row.stockNumber }}</div> -->
</template>
</el-table-column>
<el-table-column label="单位" prop="conUnit">
<template v-slot="scope">
<el-input v-model="scope.row.conUnit" readonly disabled placeholder="请选择耗材"></el-input>
<el-input
v-model="scope.row.conUnit"
readonly
disabled
placeholder="请选择耗材"
></el-input>
<!-- <el-select v-model="scope.row.conUnit" reserve-keyword placeholder="请输入关键词">
<el-option
v-for="item in returnConUnits(scope.row.consInfoId)"
@@ -146,13 +204,20 @@
<el-table-column label="操作" width="100">
<template v-slot="scope">
<div class="table_btn_wrap">
<div class="btn sub" v-if="haocaiData.consList.length > 1"
@click="haocaiData.consList.splice(scope.$index, 1)">
<div
class="btn sub"
v-if="haocaiData.consList.length > 1"
@click="haocaiData.consList.splice(scope.$index, 1)"
>
<el-icon>
<RemoveFilled />
</el-icon>
</div>
<div class="btn add" v-if="scope.$index == haocaiData.consList.length - 1" @click="createItem(scope.row)">
<div
class="btn add"
v-if="scope.$index == haocaiData.consList.length - 1"
@click="createItem(scope.row)"
>
<el-icon>
<CirclePlusFilled />
</el-icon>
@@ -170,11 +235,12 @@
</template>
<script setup lang="ts">
import importData from "@/components/importData/index.vue";
import UserAPI from "@/api/product/index";
import { useRouter } from "vue-router";
import type { IObject, IOperatData } from "@/components/CURD/types";
import usePage from "@/components/CURD/usePage";
import { isSyncStatus } from "@/utils/index";
import { isSyncStatus, downloadFile } from "@/utils/index";
import addModalConfig from "./indexconfig/add";
import contentConfig from "./indexconfig/content";
import MultiImageUpload from "@/components/Upload/MultiImageUpload.vue";
@@ -183,7 +249,8 @@ import editModalConfig from "./indexconfig/edit";
import searchConfig from "./indexconfig/search";
import MyDialog from "@/components/mycomponents/myDialog.vue";
import Statistics from "./indexconfig/statistics.vue";
import { min } from "lodash";
const importDataRef = ref(null);
const {
searchRef,
@@ -194,7 +261,7 @@ const {
handleResetClick,
// handleEditClick,
handleSubmitClick,
handleExportClick,
// handleExportClick,
handleSearchClick,
handleFilterChange,
} = usePage();
@@ -242,13 +309,17 @@ if (isSyncStatus()) {
contentConfig.cols[contentConfig.cols.length - 1].operat[2].hidden = false;
}
onMounted(() => {
onMounted(async () => {
console.log(route.query);
if (route.query.id) {
contentRef.value?.fetchPageData({ id: route.query.id });
}
// 获取耗材列表
gethaocaiList();
const res = await UserAPI.getPage();
form.warnLine = res.warnLine || "";
// console.log('contentRef.value===', res);
});
function newHandleQueryClick(e: IObject | undefined) {
@@ -299,6 +370,22 @@ function getTongji(params: IObject | undefined) {
});
}
// 显示批量导入
function handleUploadClick() {
importDataRef.value.show();
}
// 导出商品
async function handleExportClick() {
try {
const filterParams = searchRef.value?.getQueryParams();
const file = await UserAPI.exportProducts(filterParams);
downloadFile(file, "商品列表", "xlsx");
} catch (error) {
console.log(error);
}
}
// 新增
async function handleAddClick() {
router.push({ name: "addgoods" });
@@ -317,13 +404,25 @@ function createItem(val: IObject) {
haocaiData.value.consList.push(item);
}
// 上架
function handleSwitchChange(data: any) {
async function handleSwitchChange(data: any) {
console.log(data);
let obj = {
type: data.productId ? "sku" : "product",
id: data.id,
isSale: data.isSale,
};
UserAPI.onOff(obj);
await UserAPI.onOff(obj);
ElMessage.success("修改成功!");
if (data.productId) {
//sku
contentRef.value?.fetchPageData();
} else {
//product
data.skuList.map((v) => {
v.isSale = data.isSale;
});
}
}
// 售罄
function handleSwitchChangeTwo(data: any) {
@@ -333,6 +432,7 @@ function handleSwitchChangeTwo(data: any) {
isSoldOut: data.isSoldStock,
};
UserAPI.markIsSoldOut(obj);
ElMessage.success("修改成功!");
}
function handleSwitchChangethree(data: any) {
let obj = {
@@ -343,6 +443,18 @@ function handleSwitchChangethree(data: any) {
UserAPI.markIsSoldOut(obj);
}
async function isStockChange(data: any) {
console.log(data);
let obj = {
type: "product",
id: data.id,
isSale: data.isStock,
optType: "stock",
};
await UserAPI.onOff(obj);
ElMessage.success("修改成功!");
return;
}
// 退款退回
async function handleSwitchhaocai(row: IObject) {
let res = await UserAPI.refundToStock({ isReturn: row.isRefundStock, id: row.id });
@@ -372,6 +484,9 @@ async function handleToolbarClick(name: string) {
let res = await UserAPI.sync();
ElMessage.success("操作成功,数据正在后台同步中...");
}
if (name === "upload") {
importDataRef.value.show();
}
}
async function confirm() {
let res = await UserAPI.stockWarning(form.warnLine);
@@ -423,8 +538,8 @@ async function handleOperatClick(data: IOperatData) {
}
async function gethaocaiList() {
let res = await UserAPI.productcons({ id: route.query.id });
options.value = res.records;
let res = await UserAPI.productconsList({ id: route.query.id });
options.value = res || [];
}
</script>
<style scoped lang="scss">

View File

@@ -9,6 +9,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
table: {
border: true,
highlightCurrentRow: true,
expandRowKeys: [],
rowKey: 'id',
treeProps: {
children: 'skuList' // 指定子节点存储的字段为 customChildren
@@ -75,11 +76,17 @@ const contentConfig: IContentConfig<UserPageQuery> = {
{
icon: "edit",
text: "库存预警",
type: "info",
type: "danger",
name: "custom1",
auth: "import",
},
{
icon: "Download",
text: "导出",
type: "",
name: "export",
auth: "import",
}
],
cols: [
// { type: "selection", width: 50, align: "center" },
@@ -127,7 +134,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
fixed: "right",
width: 280,
templet: "tool",
operat: [{ text: "报损", name: ''}, { text: "编辑", icon: 'edit', name: "edit"}, { text: "删除", icon: 'delete', type: 'danger', name: "delete"}],
operat: [{ text: "报损", name: '' }, { text: "编辑", icon: 'edit', name: "edit" }, { text: "删除", icon: 'delete', type: 'danger', name: "delete" }],
},
],
};

View File

@@ -61,7 +61,7 @@ import contentConfig from "./unitconfig/content";
import contentConfig2 from "./unitconfig/content2";
import editModalConfig from "./unitconfig/edit";
import searchConfig from "./unitconfig/search";
import { isSyncStatus } from "@/utils/index";
import { isSyncStatus, downloadFile } from "@/utils/index";
const {
searchRef,
@@ -73,7 +73,7 @@ const {
// handleAddClick,
// handleEditClick,
handleSubmitClick,
handleExportClick,
// handleExportClick,
handleSearchClick,
handleFilterChange,
} = usePage();
@@ -88,6 +88,17 @@ if (isSyncStatus()) {
contentConfig.cols[contentConfig.cols.length - 1].operat[1].hidden = false
}
// 导出商品单位
async function handleExportClick() {
try {
const filterParams = searchRef.value?.getQueryParams();
const file = await UserAPI.exportUnits(filterParams);
downloadFile(file, "商品单位列表", "xlsx");
} catch (error) {
console.log(error);
}
}
// 新增
async function handleAddClick() {
addModalRef.value?.setModalVisible();

View File

@@ -45,6 +45,13 @@ const contentConfig: IContentConfig<UserPageQuery> = {
name: "add",
auth: "import",
},
{
icon: "Download",
text: "导出",
type: "",
name: "export",
auth: "import",
},
],
cols: [
// { type: "selection", width: 50, align: "center" },
@@ -56,7 +63,7 @@ const contentConfig: IContentConfig<UserPageQuery> = {
fixed: "right",
width: 280,
templet: "tool",
operat: [{ text: "编辑", icon: 'edit', name: "edit"}, { text: "删除", icon: 'delete', type: 'danger', name: "delete"}],
operat: [{ text: "编辑", icon: 'edit', name: "edit" }, { text: "删除", icon: 'delete', type: 'danger', name: "delete" }],
},
],
};

View File

@@ -3,14 +3,25 @@
<div>
<el-form ref="form" :model="form" :rules="rules" label-width="160px" label-position="left">
<el-form-item label="门店名称" prop="shopName">
<el-input v-model.trim="form.shopName" placeholder="请输入门店名称" style="width: 500px"></el-input>
<el-input
v-model.trim="form.shopName"
placeholder="请输入门店名称"
style="width: 500px"
></el-input>
</el-form-item>
<el-form-item label="连锁店扩展店名">
<el-input v-model.trim="form.chainName" placeholder="请输入连锁店扩展店名" style="width: 500px"></el-input>
<el-input
v-model.trim="form.chainName"
placeholder="请输入连锁店扩展店名"
style="width: 500px"
></el-input>
</el-form-item>
<el-form-item label="门店logo">
<div class="img_box">
<single-image-upload style="width: 80px; height: 80px" v-model="form.logo"></single-image-upload>
<single-image-upload
style="width: 80px; height: 80px"
v-model="form.logo"
></single-image-upload>
</div>
</el-form-item>
<!-- <el-form-item label="门店照片">
@@ -27,14 +38,22 @@
<el-form-item label="门店收款码">
<div class="img_box">
<canvas ref="canvas" id="QRCode_header" style="width: 80px; height: 80px"></canvas>
<el-button size="small" plain v-if="form.paymentQrcode" @click="downloadCanvas(form.paymentQrcode)">
<el-button
size="small"
plain
v-if="form.paymentQrcode"
@click="downloadCanvas(form.paymentQrcode)"
>
下载
</el-button>
</div>
</el-form-item>
<el-form-item label="微信二维码">
<div class="img_box">
<single-image-upload style="width: 80px; height: 80px" v-model="form.shopQrcode"></single-image-upload>
<single-image-upload
style="width: 80px; height: 80px"
v-model="form.shopQrcode"
></single-image-upload>
</div>
</el-form-item>
<!-- <el-form-item label="店铺小程序码">
@@ -52,6 +71,21 @@
<el-radio value="after">餐饮版先下单后支付</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label="是否允许用户自行支付"
prop="isUserPay"
v-if="form.registerType === 'after'"
>
<div class="column">
<div class="center" style="display: flex; align-items: center; gap: 14px">
<el-switch v-model="form.isUserPay" :active-value="1" :inactive-value="0"></el-switch>
<div class="tips" style="font-size: 14px; color: #999">
关闭后用户将不能自行支付
</div>
</div>
</div>
</el-form-item>
<el-form-item label="就餐模式「多选」" prop="eatModel">
<el-checkbox-group v-model="form.eatModel">
<el-checkbox value="dine-in">堂食自取</el-checkbox>
@@ -65,7 +99,11 @@
</el-radio-group>
</el-form-item> -->
<el-form-item label="联系电话" prop="phone">
<el-input v-model.trim="form.phone" placeholder="请输入联系电话" style="width: 500px"></el-input>
<el-input
v-model.trim="form.phone"
placeholder="请输入联系电话"
style="width: 500px"
></el-input>
</el-form-item>
<!-- <el-form-item label="外卖起送金额">
<el-input-number v-model="form.takeaway_money" placeholder="0.00" controls-position="right"
@@ -91,30 +129,62 @@
<div style="color: #999">准确的定位便于用户导航到店铺</div>
</el-form-item>
<el-form-item label="门店详细地址">
<el-input type="textarea" v-model.trim="form.address" placeholder="请输入门店详细地址" style="width: 500px"></el-input>
<el-input
type="textarea"
v-model.trim="form.address"
placeholder="请输入门店详细地址"
style="width: 500px"
></el-input>
</el-form-item>
<el-form-item label="营业时间">
<div class="u-flex gap-2" style="width: 50%">
<el-select v-model="form.businessStartDay" placeholder="周几开始">
<el-option :value="item.label" :label="item.label" v-for="item in weeks" :key="item.value"></el-option>
<el-option
:value="item.label"
:label="item.label"
v-for="item in weeks"
:key="item.value"
></el-option>
</el-select>
<el-select v-model="form.businessEndDay" placeholder="周几结束">
<el-option :value="item.label" :label="item.label" v-for="item in weeks" :key="item.value"></el-option>
<el-option
:value="item.label"
:label="item.label"
v-for="item in weeks"
:key="item.value"
></el-option>
</el-select>
<el-time-picker placeholder="起始时间" v-model="startTime" :picker-options="{
selectableRange: '00:00:00 - 23:59:59',
format: 'HH:mm',
}" format="HH:mm" value-format="HH:mm"></el-time-picker>
<el-time-picker placeholder="结束时间" v-model="endTime" :picker-options="{
selectableRange: '00:00:00 - 23:59:59',
}" format="HH:mm" value-format="HH:mm"></el-time-picker>
<el-time-picker
placeholder="起始时间"
v-model="startTime"
:picker-options="{
selectableRange: '00:00:00 - 23:59:59',
format: 'HH:mm',
}"
format="HH:mm"
value-format="HH:mm"
></el-time-picker>
<el-time-picker
placeholder="结束时间"
v-model="endTime"
:picker-options="{
selectableRange: '00:00:00 - 23:59:59',
}"
format="HH:mm"
value-format="HH:mm"
></el-time-picker>
</div>
</el-form-item>
<el-form-item label="桌位费/位/元">
<el-input-number :disabled="!!form.isTableFee" v-model="form.tableFee" :min="0" />
<!-- <el-checkbox v-model="form.isTableFee" :label="1">免餐位费</el-checkbox> -->
<el-switch class="u-m-l-10" v-model.trim="form.isTableFee" :active-value="1" :inactive-value="0"
active-text="免餐位费"></el-switch>
<el-switch
class="u-m-l-10"
v-model.trim="form.isTableFee"
:active-value="1"
:inactive-value="0"
active-text="免餐位费"
></el-switch>
</el-form-item>
<!-- <el-form-item label="是否开启8折活动">
<el-switch v-model.trim="form.isOpenYhq" active-value="true" inactive-value="false"></el-switch>
@@ -148,42 +218,68 @@
</el-time-picker>
</el-form-item> -->
<el-form-item label="店铺简介">
<el-input type="textarea" v-model.trim="form.detail" placeholder="请输入店铺简介" style="width: 500px"></el-input>
<el-input
type="textarea"
v-model.trim="form.detail"
placeholder="请输入店铺简介"
style="width: 500px"
></el-input>
</el-form-item>
<el-form-item label="台桌预订短信">
<el-input type="textarea" v-model.trim="form.bookingSms" placeholder="请输入台桌预订短信"
style="width: 500px"></el-input>
<el-input
type="textarea"
v-model.trim="form.bookingSms"
placeholder="请输入台桌预订短信"
style="width: 500px"
></el-input>
</el-form-item>
<el-form-item label="电子围栏" prop="isOrderFence">
<div class="column">
<div class="center" style="display: flex; align-items: center; gap: 14px">
<el-switch v-model="form.isOrderFence" :active-value="1" :inactive-value="0"></el-switch>
<el-switch
v-model="form.isOrderFence"
:active-value="1"
:inactive-value="0"
></el-switch>
<div class="tips" style="font-size: 14px; color: #999">
开启后用户只能在店铺附近xx公里内点餐
</div>
</div>
<div class="center" v-if="form.isOrderFence == 1">
<el-input v-model="form.orderFenceDistance" placeholder="请输入"
@input="(e) => (form.orderFenceDistance = filterNumberInput(e))" style="width: 250px"
input-style="text-align: center;">
<el-input
v-model="form.orderFenceDistance"
placeholder="请输入"
@input="(e) => (form.orderFenceDistance = filterNumberInput(e))"
style="width: 250px"
input-style="text-align: center;"
>
<template #prepend>限制</template>
<template #append>公里</template>
</el-input>
</div>
</div>
</el-form-item>
<el-form-item label="上菜时间(分钟)" prop="isServeTimeControl">
<div class="column">
<div class="center" style="display: flex; align-items: center; gap: 14px">
<el-switch v-model="form.isServeTimeControl" :active-value="1" :inactive-value="0"></el-switch>
<el-switch
v-model="form.isServeTimeControl"
:active-value="1"
:inactive-value="0"
></el-switch>
<div class="tips" style="font-size: 14px; color: #999">
起菜到上菜的时间间隔开启后会有超时提示
</div>
</div>
<div class="center" v-if="form.isServeTimeControl == 1">
<el-input v-model="form.serveTime" placeholder="请输入"
@input="(e) => (form.serveTime = filterNumberInput(e))" style="width: 250px"
input-style="text-align: center;">
<el-input
v-model="form.serveTime"
placeholder="请输入"
@input="(e) => (form.serveTime = filterNumberInput(e))"
style="width: 250px"
input-style="text-align: center;"
>
<template #prepend>限制</template>
<template #append>分钟</template>
</el-input>
@@ -208,11 +304,24 @@
</el-form>
</div>
<ChooseAddress ref="refChooseAddress" @choose="chooseAddressConfirm"></ChooseAddress>
<el-dialog v-model="showUpload" :close-on-click-modal="false" append-to-body width="500px"
@close="showUpload = false">
<el-upload :before-remove="handleBeforeRemove" :on-success="handleSuccess" :on-error="handleError"
:file-list="fileList" :headers="headers" :action="qiNiuUploadApi" :limit="1" list-type="picture"
class="upload-demo">
<el-dialog
v-model="showUpload"
:close-on-click-modal="false"
append-to-body
width="500px"
@close="showUpload = false"
>
<el-upload
:before-remove="handleBeforeRemove"
:on-success="handleSuccess"
:on-error="handleError"
:file-list="fileList"
:headers="headers"
:action="qiNiuUploadApi"
:limit="1"
list-type="picture"
class="upload-demo"
>
<el-button size="small" type="primary">点击上传</el-button>
<template #tip>
<div style="display: block" class="el-upload__tip">请勿上传违法文件且文件不超过15M</div>
@@ -341,6 +450,14 @@ export default {
mounted() {
this.tbShopInfo();
},
watch: {
"form.registerType"(val) {
console.log(val);
if (val === "before") {
this.form.isUserPay = 1;
}
},
},
methods: {
chooseAddressConfirm(e) {
console.log(e);
@@ -459,7 +576,7 @@ export default {
handleBeforeRemove(file, fileList) {
for (let i = 0; i < this.files.length; i++) {
if (this.files[i].uid === file.uid) {
crudQiNiu.del([this.files[i].id]).then((res) => { });
crudQiNiu.del([this.files[i].id]).then((res) => {});
return true;
}
}

View File

@@ -110,11 +110,21 @@
/>
</div>
</el-form-item>
<el-form-item label="试用/正式">
<el-form-item label="商户版本">
<el-radio-group v-model="state.form.profiles">
<el-radio-button value="probation">试用</el-radio-button>
<el-radio-button value="release">正式</el-radio-button>
</el-radio-group>
<div class="tips">
<el-alert
title="请谨慎修改"
type="warning"
size="7"
effect="dark"
show-icon
:closable="false"
/>
</div>
</el-form-item>
<el-form-item label="激活码">
<el-input v-model="state.form.activateCode" placeholder="请输入激活码"></el-input>

View File

@@ -24,24 +24,34 @@
</el-form-item>
<el-form-item label="支付宝进件状态">
<div class="column">
<el-text :type="statusListFilter(form.alipayStatus).type">
{{ statusListFilter(form.alipayStatus).label }}
</el-text>
<div>
<el-text :type="statusListFilter(form.alipayStatus).type">
{{ statusListFilter(form.alipayStatus).label }}
</el-text>
</div>
<div class="center" v-if="form.alipayStatus == 'SIGN'">
<el-text>待签约</el-text>
<el-link type="primary" @click="singCodeDialogRef?.show(form.alipaySignUrl, 1)">查看签约码</el-link>
</div>
<div class="error_text" v-if="form.alipayStatus == 'REJECTED'">
失败原因{{ form.alipayErrorMsg }}
</div>
</div>
</el-form-item>
<el-form-item label="微信进件状态">
<div class="column">
<el-text :type="statusListFilter(form.wechatStatus).type">
{{ statusListFilter(form.wechatStatus).label }}
</el-text>
<div>
<el-text :type="statusListFilter(form.wechatStatus).type">
{{ statusListFilter(form.wechatStatus).label }}
</el-text>
</div>
<div class="center" v-if="form.wechatStatus == 'SIGN'">
<el-text>待签约</el-text>
<el-link type="primary" @click="singCodeDialogRef?.show(form.wechatSignUrl, 2)">查看签约码</el-link>
</div>
<div class="error_text" v-if="form.wechatStatus == 'REJECTED'">
失败原因{{ form.wechatErrorMsg }}
</div>
</div>
</el-form-item>
<el-form-item label="最后提交时间">
@@ -77,7 +87,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router';
import { shopMerchantGet, getMainMerchant, shopMerchantPut, entryManagerDetail } from '@/api/common'
import { shopMerchantGet, getMainMerchant, shopMerchantPut, entryManagerDetail, queryEntry } from '@/api/common'
import singCodeDialog from '@/views/applyments/components/singCodeDialog.vue';
const singCodeDialogRef = ref(null)
@@ -322,4 +332,9 @@ onMounted(() => {
display: flex;
flex-direction: column;
}
.error_text {
font-size: 14px;
color: #999;
}
</style>

View File

@@ -3,19 +3,50 @@
<div class="head-container">
<el-row :gutter="20">
<el-col :span="3">
<el-input v-model="state.query.name" clearable placeholder="请输入店铺名称" style="width: 100%" class="filter-item"
@keyup.enter="getTableData" />
<el-input
v-model="state.query.name"
clearable
placeholder="请输入店铺名称"
style="width: 100%"
class="filter-item"
@keyup.enter="getTableData"
/>
</el-col>
<el-col :span="3">
<el-input v-model="state.query.account" clearable placeholder="请输入商户号" style="width: 100%" class="filter-item"
@keyup.enter="getTableData" />
<el-input
v-model="state.query.phone"
clearable
placeholder="请输入手机号"
style="width: 100%"
class="filter-item"
@keyup.enter="getTableData"
/>
</el-col>
<el-col :span="3">
<el-select v-model="state.query.status" placeholder="请选择店铺状态" style="width: 100%">
<el-option v-for="item in state.status" :key="item.type" :label="item.label" :value="item.type" />
<el-option
v-for="item in state.status"
:key="item.type"
:label="item.label"
:value="item.type"
/>
</el-select>
</el-col>
<el-col :span="6">
<el-col :span="3">
<el-select
v-model="state.query.profiles"
placeholder="请选择商户版本"
style="width: 100%"
>
<el-option
v-for="item in state.profiles"
:key="item.type"
:label="item.label"
:value="item.type"
/>
</el-select>
</el-col>
<el-col :span="3">
<el-button type="primary" @click="getTableData">查询</el-button>
<el-button @click="resetHandle">重置</el-button>
</el-col>
@@ -29,8 +60,10 @@
<el-table-column label="店铺信息" width="200">
<template v-slot="scope">
<div class="shop_info">
<el-image :src="scope.row.logo"
style="width: 50px; height: 50px; border-radius: 4px; background-color: #efefef">
<el-image
:src="scope.row.logo"
style="width: 50px; height: 50px; border-radius: 4px; background-color: #efefef"
>
<template #error>
<div class="img_error">
<i class="icon el-icon-document-delete" />
@@ -57,7 +90,7 @@
</div>
</template>
</el-table-column>
<el-table-column label="登录账号" prop="phone"></el-table-column>
<el-table-column label="手机号" prop="phone"></el-table-column>
<el-table-column prop="registerType" label="经营模式">
<template v-slot="scope">
<span v-if="scope.row.registerType == 'before'">快餐版</span>
@@ -70,7 +103,8 @@
<span v-if="scope.row.shopType == 'only'">单店</span>
<span v-if="scope.row.shopType == 'chain'">连锁店</span>
<span v-if="scope.row.shopType == 'join'">加盟店</span>
<div v-if="scope.row.shopType != 'only' && scope.row.isHeadShop == 0">(主店{{ scope.row.headShopName }})
<div v-if="scope.row.shopType != 'only' && scope.row.isHeadShop == 0">
(主店{{ scope.row.headShopName }})
</div>
</div>
</template>
@@ -113,7 +147,9 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="scope.row.isHeadShop == 1" :command="0">添加分店</el-dropdown-item>
<el-dropdown-item v-if="scope.row.isHeadShop == 1" :command="0">
添加分店
</el-dropdown-item>
<el-dropdown-item :command="{ row: scope.row, command: 1 }">
支付配置
</el-dropdown-item>
@@ -129,9 +165,14 @@
</el-table>
</div>
<div class="head-container">
<el-pagination v-model:current-page="state.tableData.page" v-model:page-size="state.tableData.size"
:total="state.tableData.total" :page-sizes="[10, 20, 30, 50, 100]"
layout="total, sizes , prev, pager ,next, jumper " @current-change="paginationChange" />
<el-pagination
v-model:current-page="state.tableData.page"
v-model:page-size="state.tableData.size"
:total="state.tableData.total"
:page-sizes="[10, 20, 30, 50, 100]"
layout="total, sizes , prev, pager ,next, jumper "
@current-change="paginationChange"
/>
</div>
<addShop ref="refAddShop" @success="getTableData" />
<!-- <detailModal ref="refDetailModal" /> -->
@@ -149,7 +190,7 @@ import addShop from "./components/addShop.vue";
import detailModal from "./components/detailModal.vue";
import { useRouter } from "vue-router";
const router = useRouter()
const router = useRouter();
const refActivateCode = ref(null);
function activateCodeShow(row) {
@@ -162,8 +203,9 @@ function addShopShow(row) {
const state = reactive({
query: {
name: "",
account: "",
phone: "",
status: "",
profiles: "",
},
status: [
{
@@ -175,6 +217,16 @@ const state = reactive({
label: "关闭",
},
],
profiles: [
{
type: "probation",
label: "试用版",
},
{
type: "release",
label: "正式版",
},
],
tableData: {
list: [],
page: 1,
@@ -192,24 +244,24 @@ function dropdownClick(e, row) {
console.log(e);
console.log(row);
if (e == 0) {
refAddShop.value.show({ mainId: row.id, shopType: row.shopType, isHeadShop: 0 }, 'addBranch');
refAddShop.value.show({ mainId: row.id, shopType: row.shopType, isHeadShop: 0 }, "addBranch");
return;
}
if (e.command == 1) {
console.log('row===', row);
console.log("row===", row);
// refDetailModal.value.show(e.row);
router.push({
name: 'pay_setting',
name: "pay_setting",
query: {
shopId: row.id,
shopType: row.shopType,
isHeadShop: row.isHeadShop,
mainId: row.mainId,
shopName: row.shopName
}
})
shopName: row.shopName,
},
});
return;
}
if (e == 5) {
@@ -226,15 +278,16 @@ function dropdownClick(e, row) {
});
getTableData();
})
.catch(() => { });
.catch(() => {});
return;
}
}
// 重置查询
function resetHandle() {
state.query.name = "";
state.query.account = "";
state.query.phone = "";
state.query.status = "";
state.query.profiles = "";
getTableData();
}
// 分页回调
@@ -250,8 +303,9 @@ async function getTableData() {
page: state.tableData.page,
size: state.tableData.size,
shopName: state.query.name,
account: state.query.account,
phone: state.query.phone,
status: state.query.status,
profiles: state.query.profiles,
});
state.tableData.loading = false;
state.tableData.list = res.records;

View File

@@ -4,38 +4,22 @@
<template v-if="carts.list && carts.list.length >= 1">
<!-- 当前购物车 -->
<div v-for="(item, index) in carts.list" :key="index">
<carts-item
:item="item"
:useVipPrice="carts.useVipPrice"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
<carts-item :item="item" :useVipPrice="carts.useVipPrice" @changeNumber="changeNumber"
:selCart="carts.selCart" @itemClick="itemClick(item)" @editNote="editNote"></carts-item>
</div>
</template>
<!-- 赠菜 -->
<div class="cart-title" v-if="carts.giftList.length > 0"><span>以下是优惠菜品</span></div>
<div v-for="(item, index) in carts.giftList" :key="index">
<carts-item
:item="item"
@changeNumber="changeNumber"
:useVipPrice="carts.useVipPrice"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
<carts-item :item="item" @changeNumber="changeNumber" :useVipPrice="carts.useVipPrice" :selCart="carts.selCart"
@itemClick="itemClick(item)" @editNote="editNote"></carts-item>
</div>
<el-empty :image-size="60" v-if="carts.isEmpty" description="点餐列表为空" />
<!-- 打包费 -->
<template v-if="carts.packNum > 0">
<div class="cart-title"><span>打包费</span></div>
<extra-fee
name="打包费"
:number="carts.packNum"
:price="carts.orderCostSummary.packFee"
></extra-fee>
<extra-fee name="打包费" :number="carts.packNum" :price="carts.orderCostSummary.packFee"></extra-fee>
</template>
<!-- 餐位费 -->
<template v-if="perpole >= 1 && carts.dinnerType == 'dine-in'">
@@ -43,7 +27,7 @@
<extra-fee name="餐位费" :number="perpole" :price="canWeiFee"></extra-fee>
</template>
<!-- 历史订单 -->
<div class="u-flex u-row-between u-m-b-10" v-if="carts.oldOrder.id">
<div class="u-flex u-row-between u-m-b-10" v-if="carts.oldOrder.id && !isEmptyObject(carts.oldOrder.detailMap)">
<span class="u-font-14">历史订单</span>
<el-button link size="small" type="danger" @click="clearOldOrder()">清空</el-button>
</div>
@@ -56,17 +40,9 @@
</div>
<div v-for="(detaiItem, index) in item" :key="index">
<carts-item
:useVipPrice="carts.useVipPrice"
:canChangeNumber="false"
isOld
:dinerType="dinerType"
:item="detaiItem"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(detaiItem)"
@editNote="editNote"
></carts-item>
<carts-item :useVipPrice="carts.useVipPrice" :canChangeNumber="false" isOld :dinerType="dinerType"
:item="detaiItem" @changeNumber="changeNumber" :selCart="carts.selCart" @itemClick="itemClick(detaiItem)"
@editNote="editNote"></carts-item>
</div>
</template>
@@ -74,14 +50,9 @@
</div>
<div class="bottom">
<div class="u-flex u-row-right">
<el-tooltip
placement="top"
effect="light"
popper-class="youhui-tips"
:popper-options="{
'background-color': '#fff',
}"
>
<el-tooltip placement="top" effect="light" popper-class="youhui-tips" :popper-options="{
'background-color': '#fff',
}">
<template #content>
<div class="u-flex color-000 u-font-14 u-row-between">
<span class="font-bold">会员优惠</span>
@@ -111,28 +82,13 @@
</div>
</div>
<div class="btn-group" v-if="isXianFuKuan">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('wx-aiplay')"
>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('wx-aiplay')">
微信/支付宝
</el-button>
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('cash')"
>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('cash')">
现金
</el-button>
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('more-pay')"
>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('more-pay')">
更多支付
</el-button>
</div>
@@ -145,33 +101,20 @@
<el-button type="primary" size="large" :disabled="disabledMorePay" @click="createOrder('to-pay')">
去结账
</el-button> -->
<el-button
type="primary"
size="large"
:disabled="carts.list.length == 0 || carts.oldOrder.detailMap.length == 0"
@click="createOrder('only-create')"
>
<el-button type="primary" size="large" :disabled="carts.list.length == 0" @click="createOrder('only-create')">
仅下单
</el-button>
<el-button
type="primary"
size="large"
<el-button type="primary" size="large"
:disabled="carts.list.length == 0 && isEmptyObject(carts.oldOrder.detailMap)"
@click="createOrder('to-pay')"
>
@click="createOrder('to-pay')">
去结账
</el-button>
</template>
<template v-else>
<el-button type="default" size="large" @click="hideOrder()">加菜/返回</el-button>
<el-button
type="primary"
size="large"
:disabled="!carts.isLinkFinshed"
@click="createOrder('to-pay')"
>
<el-button type="warning" size="large" @click="hideOrder()">加菜/返回</el-button>
<!-- <el-button type="primary" size="large" :disabled="!carts.isLinkFinshed" @click="createOrder('to-pay')">
立即支付
</el-button>
</el-button> -->
</template>
</div>
</div>
@@ -188,11 +131,11 @@ import { useUserStore } from "@/store/modules/user";
function isEmptyObject(obj) {
// 步骤1排除null和非对象类型
if (obj === null || typeof obj !== "object") {
return false;
return true;
}
// 步骤2排除数组数组也是对象需单独判断
if (Array.isArray(obj)) {
return false;
return true;
}
// 步骤3判断自身可枚举属性是否为空
return Object.keys(obj).length === 0;

View File

@@ -4,7 +4,7 @@
<div class="head-container">
<el-form :model="query" inline>
<el-form-item label="">
<el-input v-model="query.name" placeholder="请输入昵称或手机号"></el-input>
<el-input v-model="query.key" placeholder="请输入昵称或手机号"></el-input>
</el-form-item>
<el-form-item>
@@ -22,7 +22,10 @@
<el-table-column label="用户" prop="headImg" width="200px">
<template v-slot="scope">
<div class="user_info">
<el-image :src="scope.row.headImg" style="width: 40px; height: 40px; flex-shrink: 0">
<el-image
:src="scope.row.headImg"
style="width: 40px; height: 40px; flex-shrink: 0"
>
<template #error>
<div class="image-slot">
<i class="el-icon-user"></i>
@@ -30,7 +33,14 @@
</template>
</el-image>
<span class="name">{{ scope.row.nickName }}</span>
<el-tag disable-transitions type="primary" style="margin-left: 10px;" v-if="scope.row.new"></el-tag>
<el-tag
disable-transitions
type="primary"
style="margin-left: 10px"
v-if="scope.row.new"
>
</el-tag>
</div>
</template>
</el-table-column>
@@ -38,7 +48,9 @@
<el-table-column label="会员" prop="isVip" width="140">
<template v-slot="scope">
<el-tag type="warning" v-if="scope.row.isVip">会员等级{{ scope.row.memberLevelName }}</el-tag>
<el-tag type="warning" v-if="scope.row.isVip">
会员等级{{ scope.row.memberLevelName }}
</el-tag>
<span v-else></span>
</template>
</el-table-column>
@@ -60,9 +72,14 @@
</el-table>
</div>
<div class="head-container">
<el-pagination :total="tableData.total" :current-page="tableData.page" :page-size="tableData.size"
@size-change="sizeChange" @current-change="paginationChange"
layout="total, sizes, prev, pager, next, jumper"></el-pagination>
<el-pagination
:total="tableData.total"
:current-page="tableData.page"
:page-size="tableData.size"
@size-change="sizeChange"
@current-change="paginationChange"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</div>
</div>
</el-dialog>
@@ -71,13 +88,13 @@
<script setup>
import shopUserApi from "@/api/account/shopUser";
import dayjs from "dayjs";
import { multiplyAndFormat } from '@/utils'
import { multiplyAndFormat } from "@/utils";
let cacheData = {};
const state = reactive({
show: false,
query: {
name: "",
key: "",
// isVip: 1,
},
tableData: {
@@ -242,4 +259,4 @@ defineExpose({
.gap-20 {
gap: 20px;
}
</style>
</style>

View File

@@ -591,7 +591,7 @@ function pointsToMoney(val) {
carts.orderCostSummary.pointDeductionAmount = finalMoney;
}
const emits = defineEmits(["chooseUser", "paysuccess"]);
const emits = defineEmits(["chooseUser", "paysuccess", 'createOrder']);
function chooseUser() {
emits("chooseUser");
}
@@ -672,6 +672,7 @@ function returnPayParams() {
newCustomerDiscountAmount:
carts.newUserDiscount !== null ? carts.newUserDiscount.amount : 0, // 新客立减金额
vipDiscountAmount: carts.orderCostSummary.vipDiscountAmount, // 超级会员折扣
remark: cashRemark.value, // 现金支付备注
},
};
}
@@ -700,29 +701,39 @@ async function getPaytype() {
payTypes.sel = payTypes.list.findIndex((v) => v.payType == "cash");
}
}
function nowPayClick(payType) {
payType = payType || payTypes.list[payTypes.sel].payType;
if (payType === "cash") {
ElMessageBox.confirm("是否确认已现金收款:" + currentpayMoney.value + "元", "快捷支付", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
payOrder("cash");
})
.catch(() => { });
return;
}
if (payType == "member-account") {
if (!carts.vipUser.id) {
return ElMessage.error("请先选择会员");
}
payOrder(payType);
return;
const cashRemark = ref("");
async function nowPayClick(payType) {
if (carts.list.length) {
await emits('createOrder', 'only-create')
}
refScanPayOpen(payType);
setTimeout(() => {
payType = payType || payTypes.list[payTypes.sel].payType;
if (payType === "cash") {
ElMessageBox.prompt("是否确认已现金收款:" + currentpayMoney.value + "元", "快捷支付", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
inputPlaceholder: '请输入现金支付备注(选填)',
})
.then(({ value }) => {
cashRemark.value = value || "";
payOrder("cash");
})
.catch(() => { });
return;
}
if (payType == "member-account") {
if (!carts.vipUser.id) {
return ElMessage.error("请先选择会员");
}
payOrder(payType);
return;
}
refScanPayOpen(payType);
}, 500);
}
let authCode = "";

View File

@@ -3,12 +3,7 @@
<el-form :rules="rules" ref="refForm" :model="form">
<el-form-item label="转桌到" prop="targetTableCode">
<el-select v-model="form.targetTableCode" placeholder="请选择转桌到" style="width: 200px">
<el-option
v-for="item in tableList"
:key="item.id"
:label="item.name"
:value="item.tableCode"
></el-option>
<el-option v-for="item in tableList" :key="item.id" :label="item.name" :value="item.tableCode"></el-option>
</el-select>
</el-form-item>
<el-form-item label="转入类型">
@@ -59,6 +54,7 @@ const basicForm = {
targetOrderId: "",
targetTableCode: "",
detailIds: [],
allMerge: 0 // 是否全部转台
};
const form = reactive({
...basicForm,
@@ -120,6 +116,17 @@ function confirm() {
type: "error",
});
}
if (rottableType.value == 1) {
form.allMerge = 1;
} else {
console.log('oldCartGoods.value===', oldCartGoods.value);
const orderCount = Object.values(oldCartGoods.value).reduce((sum, arr) => {
return sum + arr.length
}, 0)
form.allMerge = orderCount == detailIds.length ? 1 : 0
}
emits(
"confirm",
{

View File

@@ -84,10 +84,10 @@ export default {
"https://zhyx.eingdong.com/qrcode/api.php?url=https%3A%2F%2Fzhyx.eingdong.com%2Fcopyright%2F%23%2Fpay%3Fid%3D139451580",
pays: [
{
text: "扫",
text: "扫码收款",
},
{
text: "被扫",
text: "二维码收款",
},
],
number: "0",

View File

@@ -17,7 +17,9 @@
<div>
<div class="u-flex">
<div class="ft-13 color-000 no-wrap">{{ carts.vipUser.nickName }}</div>
<div class="vip" v-if="carts.vipUser.isVip">{{ carts.vipUser.memberLevelName }}</div>
<div class="vip" v-if="carts.vipUser.isVip">
{{ carts.vipUser.memberLevelName }}
</div>
</div>
<div style="margin-top: 2px" class="no-wrap color-666 ft-12">
余额{{ carts.vipUser.amount }}
@@ -41,7 +43,9 @@
</div>
</div>
<template #reference>
<el-button>{{ carts.tableInfo.name ? "桌台号:" + carts.tableInfo.name : "选择桌号" }}</el-button>
<el-button>
{{ carts.tableInfo.name ? "桌台号:" + carts.tableInfo.name : "选择桌号" }}
</el-button>
</template>
</el-popover>
<el-button type="warning" @click="refQuanHexiaoOpen">扫码验券</el-button>
@@ -126,7 +130,7 @@
</template>
<!-- 订单信息展示 -->
<Order ref="refOrder" :orderInfo="carts.oldOrder" @chooseUser="showChooseUser" @paysuccess="refresh"
:table="carts.tableInfo" :perpole="perpole" v-else :user="user"></Order>
:table="carts.tableInfo" :perpole="perpole" v-else :user="user" @create-order="createOrder"></Order>
</div>
</div>
</div>
@@ -214,7 +218,9 @@ function rottableShow() {
}
async function rottableConfirm(form, cart_id) {
carts.rotTable(form.targetTableCode, cart_id);
const res = await orderApi.mergeOrder(form);
if (form.detailIds.length) {
await orderApi.mergeOrder(form);
}
ElNotification({
title: "成功",
message: "合并成功",
@@ -246,7 +252,8 @@ async function refReturnCartConfirm(e) {
if (res) {
// 获取历史订单数据
const res1 = await orderApi.getHistoryList({
orderId: carts.oldOrder.id,
// orderId: carts.oldOrder.id,
tableCode: carts.table_code,
});
if (res1) {
carts.setOldOrder(res1);
@@ -354,7 +361,7 @@ async function createOrder(key) {
placeNum: carts.oldOrder.placeNum * 1 + 1,
waitCall: false,
vipPrice: user.value.id && user.value.isVip,
limitRate: carts.limitDiscountRes
limitRate: carts.limitDiscountRes,
});
clearTimeout(loadingTimer);
loading.close();
@@ -370,7 +377,8 @@ async function createOrder(key) {
if (res.id) {
// 获取历史订单数据
const res1 = await orderApi.getHistoryList({
orderId: res.id,
// orderId: carts.oldOrder.id,
tableCode: carts.table_code,
});
if (res1) {
carts.setOldOrder(res1);
@@ -403,7 +411,8 @@ async function clearOldOrderCallback() {
try {
// 获取历史订单数据
const res = await orderApi.getHistoryList({
orderId: carts.oldOrder.id,
// orderId: carts.oldOrder.id,
tableCode: carts.table_code,
});
const noPayStatus = {
cancelled: "订单已取消",
@@ -415,16 +424,34 @@ async function clearOldOrderCallback() {
showOrder.value = false;
return;
}
if (res) {
console.log("清空历史订单回调", res);
if (res.data !== null) {
console.log("还有历史订单数据");
carts.setOldOrder(res);
} else {
console.log("历史订单没数据了");
carts.oldOrder = {
detailMap: [],
originAmount: 0,
};
carts.setOldOrder({
detailMap: [],
originAmount: 0,
});
// carts.clearHistory();
// resetOldOrder();
}
carts.clearOldOrderCallback = false;
} catch (error) {
console.log(error);
carts.clearOldOrderCallback = false;
}
}
// 取消历史订单
async function clearOldOrder(params) {
try {
carts.isClearingOldOrder = true;
const res1 = params.placeNum
? await orderApi.rmPlaceOrder(params)
: await orderApi.cancelOrder(params);
@@ -510,7 +537,7 @@ async function tableClick(item) {
carts.setOldOrder(res);
}
table.value = item;
carts.changeTableInfo(item)
carts.changeTableInfo(item);
carts.changeTable(item.tableCode);
refTable.value.hide();
}
@@ -601,6 +628,24 @@ function dinerDisabled(item, index) {
return !shopUser.userInfo.eatModel.includes("take-out");
}
}
// 监听店铺就餐类型配置变化,自动同步 diners.sel避免切换路由后需刷新才能生效
watch(
() => shopUser.userInfo && shopUser.userInfo.eatModel,
(eatModel) => {
console.log("watch eatModel change:", eatModel);
if (!eatModel) return;
const sel = eatModel.includes("dine-in") ? 0 : 1;
if (diners.sel !== sel) {
console.log("update diners.sel:", diners.sel, "=>", sel);
diners.sel = sel;
// 同步 carts 的就餐类型
changeCartsDinerType();
}
},
{ immediate: true }
);
// 商品分类
const category = reactive({
list: [],
@@ -712,12 +757,20 @@ function clearCarts() {
});
}
function addCarts(item, isWeight = false) {
console.log('index.addCarts===', item);
console.log("index.addCarts===", item);
if (isWeight) {
carts.add({ pack_number: diners.sel ? 1 : 0, ...item, is_time_discount: item.isLimitDiscount ? 1 : 0 });
carts.add({
pack_number: diners.sel ? 1 : 0,
...item,
is_time_discount: item.isLimitDiscount ? 1 : 0,
});
return;
}
carts.add({ pack_number: diners.sel ? item.number : 0, ...item, is_time_discount: item.isLimitDiscount ? 1 : 0 });
carts.add({
pack_number: diners.sel ? item.number : 0,
...item,
is_time_discount: item.isLimitDiscount ? 1 : 0,
});
}
watch(
@@ -730,7 +783,7 @@ function init() {
getTableList();
getCategoryList();
changeCartsDinerType();
carts.init({ table_code: carts.tableInfo.tableCode }, carts.oldOrder);
carts.init({ table_code: carts.tableInfo.tableCode });
}
onBeforeRouteLeave(() => {
@@ -742,13 +795,24 @@ onMounted(async () => {
const { id, tableCode, key } = route.query;
if (id || tableCode) {
// 获取历史订单数据
const res = id
? await orderApi.getHistoryList({
// const res = id
// ? await orderApi.getHistoryList({
// orderId: id,
// })
// : await orderApi.getHistoryList({
// tableCode,
// });
let res = ''
if (id) {
res = await orderApi.getHistoryList({
orderId: id,
})
: await orderApi.getHistoryList({
tableCode,
} else {
res = await orderApi.getHistoryList({
tableCode: carts.table_code,
});
}
const noPayStatus = {
cancelled: "订单已取消",
done: "订单已关闭",
@@ -765,7 +829,7 @@ onMounted(async () => {
}
if (res.tableCode) {
table.value = { tableCode: res.tableCode };
carts.changeTableInfo(res)
carts.changeTableInfo(res);
}
if (res.userId) {
const userRes = await shopUserApi.get({ userId: res.userId });
@@ -777,10 +841,12 @@ onMounted(async () => {
const tableRes = await tableApi.get({ tableCode: res.tableCode });
if (tableRes.tableCode) {
table.value = tableRes || {};
carts.changeTableInfo(tableRes)
carts.changeTableInfo(tableRes);
}
}
if (res) {
console.log("res31231231331-=====--", res);
carts.setOldOrder(res);
showOrder.value = key == "isPayOrder" ? true : false;
perpole.value = res.seatNum || 0;
@@ -790,11 +856,39 @@ onMounted(async () => {
const tableRes = await tableApi.get({ tableCode: tableCode });
if (tableRes.tableCode) {
table.value = tableRes || {};
carts.changeTableInfo(tableRes)
carts.changeTableInfo(tableRes);
}
}
console.log(table.value);
init();
// 尝试在挂载时主动刷新店铺配置,确保从设置页面返回时能生效
await refreshShopUserInfo();
});
// 主动刷新店铺信息并同步就餐类型(用于从设置页返回时生效)
async function refreshShopUserInfo() {
try {
const res = await shopUser.getUserInfo();
if (res && res.eatModel) {
const sel = res.eatModel.includes("dine-in") ? 0 : 1;
if (diners.sel !== sel) {
diners.sel = sel;
changeCartsDinerType();
}
}
} catch (error) {
console.error("refreshShopUserInfo error", error);
}
}
// keep-alive 激活时刷新
onActivated(() => {
refreshShopUserInfo();
});
// 路由复用或更新时刷新
onBeforeRouteUpdate((to, from) => {
refreshShopUserInfo();
});
function resetOldOrder() {
@@ -811,7 +905,7 @@ function refresh() {
showOrder.value = false;
user.value = {};
table.value = {};
carts.changeTableInfo({})
carts.changeTableInfo({});
router.replace(route.path);
carts.dataReset();
carts.init();
@@ -1070,4 +1164,4 @@ $pl: 30px;
margin-left: 10px;
font-size: 10px;
}
</style>
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ const contentConfig: IContentConfig<any> = {
// // return shopUserApi.edit(data);
// },
pk: "id",
toolbar: ["add"],
toolbar: ["add", 'export'],
defaultToolbar: ["refresh", "filter", "search"],
cols: [
{ type: "selection", width: 50, align: "center" },

View File

@@ -29,8 +29,11 @@
</div>
<!-- 列表 -->
<page-content ref="contentRef" :content-config="contentConfig" @add-click="handleAddClick"
@edit-click="handleEditClick" @export-click="handleExportClick" @search-click="handleSearchClick"
@edit-click="handleEditClick" @export-click="handleExportClick" @upload-click="" @search-click="handleSearchClick"
@toolbar-click="handleToolbarClick" @operat-click="handleOperatClick" @filter-change="handleFilterChange">
<template #custom>
<importData ref="importDataRef" :type="6" @close="searchQueryClick" />
</template>
<template #status="scope">
<el-link :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
@@ -146,6 +149,7 @@
</template>
<script setup>
import importData from "@/components/importData/index.vue";
import UserCouponDialog from "./components/user-coupon-dialog.vue";
import GiveCoupon from "./components/give-coupon.vue";
import usePage from "@/components/CURD/usePage";
@@ -158,6 +162,8 @@ import { returnOptionsLabel } from "./config/config";
import shopUserApi from "@/api/account/shopUser";
import { useRoute } from 'vue-router'
import { ElNotification } from 'element-plus'
import { downloadFile } from "@/utils/index";
const editMoneyModalRef = ref(null);
const userCouponDialogRef = ref(null);
const GiveCouponRef = ref(null);
@@ -175,7 +181,7 @@ const {
// handleAddClick,
// handleEditClick,
handleSubmitClick,
handleExportClick,
// handleExportClick,
handleSearchClick,
handleFilterChange,
} = usePage();
@@ -217,6 +223,16 @@ function formDataChange(type, val) {
}
}
// 导出
async function handleExportClick() {
try {
const file = await shopUserApi.export(searchRef.value.getQueryParams());
downloadFile(file, "用户列表", "xlsx");
} catch (error) {
console.log(error);
}
}
// 新增
async function handleAddClick() {
addModalRef.value?.setModalVisible();
@@ -239,6 +255,8 @@ function handleToolbarClick(name) {
console.log(name);
if (name === "custom1") {
ElMessage.success("点击了自定义1按钮");
} else if (name === "upload") {
importDataRef.value.show()
}
}

View File

@@ -15,6 +15,9 @@ import { name, version, engines, dependencies, devDependencies } from "./package
import stripCode from 'vite-plugin-strip-code'; // 导入插件
import vueDevTools from 'vite-plugin-vue-devtools'
// 平台的名称、版本、运行所需的 node 版本、依赖、构建时间的类型提示
const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies },
@@ -54,6 +57,13 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
target: env.VITE_APP_API_URL,
rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
},
// 代理 /dev-api 的请求
'/import_api': {
changeOrigin: true,
// 代理目标地址https://api.youlai.tech
target: env.VITE_APP_API_PHP_IMPORT_URL,
rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_API_PHP_IMPORT_URL), ""),
},
},
},
plugins: [
@@ -107,7 +117,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
// 注意:标记可以自定义,只要前后一致即可
start_comment: 'DEV-START', // 开始标记(自定义)
end_comment: 'DEV-END' // 结束标记(自定义)
})
}),
// vueDevTools()
],
// 预加载项目必需的组件
optimizeDeps: {