管理端细节修改

This commit is contained in:
2026-05-07 15:34:38 +08:00
parent ffad9432c5
commit 13bd39fbed
36 changed files with 2915 additions and 1482 deletions

View File

@@ -65,9 +65,10 @@
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",
"vue-draggable-plus": "^0.6.1",
"vue-i18n": "^11.1.0", "vue-i18n": "^11.1.0",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"ysk-utils": "^1.0.85" "ysk-utils": "^1.0.91"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.7.1", "@commitlint/cli": "^19.7.1",

View File

@@ -27,11 +27,34 @@ const Api = {
method: "post", method: "post",
data data
}); });
},
// 排序
sort(params: sortRequest) {
return request<any>({
url: `${baseURL}/sort`,
method: "get",
params
});
} }
}; };
export default Api; export default Api;
export interface sortRequest {
/**
* 拖动的支付类型ID
*/
id: number;
/**
* 店铺ID
*/
shopId: number;
/**
* 目标位置排序号
*/
targetSort: number;
[property: string]: any;
}
interface getRequset { interface getRequset {
id?: string id?: string

26
src/api/order/fince.ts Normal file
View File

@@ -0,0 +1,26 @@
import request from "@/utils/request";
import { Order_BaseUrl } from "@/api/config";
const baseURL = Order_BaseUrl + "/admin";
const FinanceApi = {
financeBase(params: Request) {
return request<any, any>({
url: `${baseURL}/finance/base`,
method: "get",
params
});
},
};
export interface Request {
/**
* 日期yyyy-MM-dd
*/
date: string;
shopId: number;
type: string;
[property: string]: any;
}
export default FinanceApi;

View File

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

View File

@@ -177,7 +177,7 @@ const formItems = reactive(props.searchConfig.formItems);
// 是否可展开/收缩 // 是否可展开/收缩
const isExpandable = ref(props.searchConfig.isExpandable ?? true); const isExpandable = ref(props.searchConfig.isExpandable ?? true);
// 是否已展开 // 是否已展开
const isExpand = ref(false); const isExpand = ref(props.searchConfig.isExpand ?? false);
// 表单项展示数量,若可展开,超出展示数量的表单项隐藏 // 表单项展示数量,若可展开,超出展示数量的表单项隐藏
const showNumber = computed(() => { const showNumber = computed(() => {
if (isExpandable.value === true) { if (isExpandable.value === true) {

View File

@@ -31,6 +31,7 @@ export interface ISearchConfig {
// 页面名称(参与组成权限标识,如sys:user:xxx) // 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string; pageName: string;
inline?: Boolean; inline?: Boolean;
isExpand?: boolean;
// 表单项 // 表单项
formItems: Array<{ formItems: Array<{
// 组件类型(如input,select等) // 组件类型(如input,select等)
@@ -58,6 +59,7 @@ export interface ISearchConfig {
} }
export interface IContentConfig<T = any> { export interface IContentConfig<T = any> {
rowDraggable?: boolean;
resultListKey?: string; resultListKey?: string;
// 页面名称(参与组成权限标识,如sys:user:xxx) // 页面名称(参与组成权限标识,如sys:user:xxx)
pageName: string; pageName: string;
@@ -189,6 +191,7 @@ export interface IContentConfig<T = any> {
| "icon" | "icon"
| "date" | "date"
| "tool" | "tool"
| "drag-handle"
| "custom"; | "custom";
// image模板相关参数 // image模板相关参数
imageWidth?: number; imageWidth?: number;

View File

@@ -0,0 +1,305 @@
<!-- 批量导入数据dialog -->
<template>
<div>
<div class="btn_row">
<el-button type="success" icon="RefreshRight" @click="updateData">数据更新</el-button>
<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";
import { ElMessage } from "element-plus";
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", "update"]);
function updateData() {
// 把 ElMessage 改成 ElMessageBox 就好了
ElMessageBox.confirm("确认需要更新经营数据吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
emits("update");
})
.catch(() => {
// 取消操作,空着就行
});
}
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

@@ -2,11 +2,19 @@
<template> <template>
<div> <div>
<div class="btn_row"> <div class="btn_row">
<el-button type="success" icon="RefreshRight" @click="updateData">数据更新</el-button>
<el-button type="primary" icon="Upload" @click="show">批量导入</el-button> <el-button type="primary" icon="Upload" @click="show">批量导入</el-button>
<el-button icon="Download" @click="downloadTemplateAjax">下载银收客模板</el-button> <el-button icon="Download" @click="downloadTemplateAjax">下载银收客模板</el-button>
</div> </div>
<el-dialog title="批量导入" width="800px" v-model="visible" :close-on-click-modal="false" :close-on-press-escape="false" <el-dialog
@close="dialogClose"> title="批量导入"
width="800px"
v-model="visible"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="dialogClose"
>
<div class="row"> <div class="row">
<tabHeader v-model="tabActive" :list="tabs" /> <tabHeader v-model="tabActive" :list="tabs" />
</div> </div>
@@ -15,23 +23,38 @@
<div class="header_title">第一步选择模板</div> <div class="header_title">第一步选择模板</div>
<div class="row mt14 pb50"> <div class="row mt14 pb50">
<div class="list"> <div class="list">
<div class="item" :class="{ active: platformActive == index }" v-for="(item, index) in platformList" <div
:key="item.id" @click="selectPlatform(item)"> class="item"
<img class="img" :src="item.img" alt=""> :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>
</div> </div>
<div class="header_title">第二步上传文件 <div class="header_title">
第二步上传文件
<span>单次仅可上传一个文件</span> <span>单次仅可上传一个文件</span>
</div> </div>
<div class="row mt14"> <div class="row mt14">
<GfileUpload v-model="form.files" :accept="platformList[platformActive]?.file_type || ''" :limit="1" <GfileUpload
@file-selected="fileSelected" /> v-model="form.files"
:accept="platformList[platformActive]?.file_type || ''"
:limit="1"
@file-selected="fileSelected"
/>
</div> </div>
<div class="row mt14"> <div class="row mt14">
<div class="footer_wrap"> <div class="footer_wrap">
<el-button @click="visible = false"> </el-button> <el-button @click="visible = false"> </el-button>
<el-button type="primary" :disabled="!form.files.length" :loading="loading" @click="startImportHandle"> <el-button
type="primary"
:disabled="!form.files.length"
:loading="loading"
@click="startImportHandle"
>
<template v-if="!form.files.length">请选择文件</template> <template v-if="!form.files.length">请选择文件</template>
<template v-else>开始导入</template> <template v-else>开始导入</template>
</el-button> </el-button>
@@ -43,10 +66,18 @@
<el-table-column prop="created_time" label="导入时间" width="200" /> <el-table-column prop="created_time" label="导入时间" width="200" />
<el-table-column prop="status_text" label="导入状态" width="150"> <el-table-column prop="status_text" label="导入状态" width="150">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status == 0" type="info" disable-transitions>待处理...</el-tag> <el-tag v-if="scope.row.status == 0" type="info" disable-transitions>
<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>
<el-tag v-else-if="scope.row.status == -1" type="danger" 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> </template>
</el-table-column> </el-table-column>
<el-table-column prop="importResult" label="导入结果"> <el-table-column prop="importResult" label="导入结果">
@@ -66,19 +97,45 @@
</el-table> </el-table>
</div> </div>
</el-dialog> </el-dialog>
<!-- 日期选择弹窗 -->
<el-dialog
v-model="dateDialogVisible"
title="选择更新日期"
width="500px"
@close="dateDialogVisible = false"
>
<div style="padding: 20px 0">
<el-date-picker
v-model="selectDate"
type="date"
label="选择日期"
value-format="YYYY-MM-DD"
placeholder="请选择日期"
style="width: 100%"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dateDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUpdateDate">确认更新</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from 'vue'; import { ref, onMounted, watch } from "vue";
import { getplatlist, uploadFile, importlist, downloadTemp } from '@/importDataApi/index.js'; import { getplatlist, uploadFile, importlist, downloadTemp } from "@/importDataApi/index.js";
import GfileUpload from '../Upload/GfileUpload.vue'; import GfileUpload from "../Upload/GfileUpload.vue";
import tabHeader from '@/views/marketing_center/components/tabHeader.vue'; import tabHeader from "@/views/marketing_center/components/tabHeader.vue";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({ const props = defineProps({
type: { type: {
type: [String, Number], type: [String, Number],
default: 3, // 3商品 4台桌区域 5台桌 6会员 7菜品销售统计 8台桌销售统计 9订单销售统计 default: 3,
}, },
}); });
@@ -86,15 +143,15 @@ const platformList = ref([]);
const platformActive = ref(0); const platformActive = ref(0);
function selectPlatform(item) { function selectPlatform(item) {
form.value.files = []; form.value.files = [];
platformActive.value = platformList.value.findIndex(i => i.id === item.id); platformActive.value = platformList.value.findIndex((i) => i.id === item.id);
} }
const tabs = ref([ const tabs = ref([
{ label: '导入数据', name: 'importData' }, { label: "导入数据", name: "importData" },
{ label: '导入记录', name: 'importRecord' }, { label: "导入记录", name: "importRecord" },
]); ]);
const tabActive = ref(0) const tabActive = ref(0);
watch(tabActive, (newVal) => { watch(tabActive, (newVal) => {
if (newVal === 1) { if (newVal === 1) {
@@ -106,11 +163,16 @@ const visible = ref(false);
const loading = ref(false); const loading = ref(false);
const form = ref({ const form = ref({
files: [], files: [],
platform: '' platform: "",
}) });
// 日期选择弹窗
const dateDialogVisible = ref(false);
const today = new Date().toISOString().split("T")[0];
const selectDate = ref(today); // 默认当天
function fileSelected(file) { function fileSelected(file) {
console.log('fileSelected', file); console.log("fileSelected", file);
} }
// 开始导入 // 开始导入
@@ -118,15 +180,15 @@ async function startImportHandle() {
try { try {
form.value.platform = platformList.value[platformActive.value]?.id; form.value.platform = platformList.value[platformActive.value]?.id;
const formData = new FormData(); const formData = new FormData();
formData.append('file', form.value.files[0].raw); formData.append("file", form.value.files[0].raw);
formData.append('shop_id', localStorage.getItem('shopId')); formData.append("shop_id", localStorage.getItem("shopId"));
formData.append('type', props.type); formData.append("type", props.type);
formData.append('platform', form.value.platform); formData.append("platform", form.value.platform);
loading.value = true; loading.value = true;
await uploadFile(formData); await uploadFile(formData);
ElMessage.success('文件上传成功,正在导入数据,请在导入记录中查看导入结果'); ElMessage.success("文件上传成功,正在导入数据,请在导入记录中查看导入结果");
form.value.files = [] form.value.files = [];
tabActive.value = 1 tabActive.value = 1;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@@ -149,7 +211,7 @@ async function getPlatformList() {
const tableData = ref([]); const tableData = ref([]);
async function getImportRecord() { async function getImportRecord() {
try { try {
const res = await importlist({ shop_id: localStorage.getItem('shopId') }); const res = await importlist({ shop_id: localStorage.getItem("shopId") });
tableData.value = res; tableData.value = res;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -160,20 +222,38 @@ async function getImportRecord() {
async function downloadTemplateAjax() { async function downloadTemplateAjax() {
try { try {
const res = await downloadTemp({ plat_type: props.type }); const res = await downloadTemp({ plat_type: props.type });
window.open(res, '_blank'); window.open(res, "_blank");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
const emits = defineEmits(['close']) const emits = defineEmits(["close", "update"]);
// ==================================
// 数据更新(打开日期选择框)
// ==================================
function updateData() {
dateDialogVisible.value = true;
}
// 确认选择日期并更新
function confirmUpdateDate() {
if (!selectDate.value) {
ElMessage.warning("请选择日期");
return;
}
dateDialogVisible.value = false;
emits("update", selectDate.value); // 把选中的日期传给父组件
// ElMessage.success("已选择日期:" + selectDate.value);
}
function dialogClose() { function dialogClose() {
emits('close') emits("close");
} }
onMounted(() => { onMounted(() => {
getPlatformList() getPlatformList();
}); });
function show() { function show() {

View File

@@ -16,6 +16,8 @@ import {
OrderCostSummary, OrderCostSummary,
} from "./types"; } from "./types";
import { getCompatibleFieldValue } from "./utils";
/** /**
* 返回商品单价 * 返回商品单价
* @param goods 商品 * @param goods 商品
@@ -46,7 +48,13 @@ export function returnGoodsPrice(
// 限时折扣 // 限时折扣
if (limitTimeDiscount && limitTimeDiscount.id) { if (limitTimeDiscount && limitTimeDiscount.id) {
//优先使用 //优先使用
if (goods.isTimeDiscount || goods.is_time_discount) { // 兼容 isTimeDiscount/is_time_discount这里顺便处理该字段的命名兼容
const isTimeDiscount = getCompatibleFieldValue(
goods,
"isTimeDiscount",
"is_time_discount"
);
if (isTimeDiscount) {
return new BigNumber(goods.salePrice) return new BigNumber(goods.salePrice)
.times(limitTimeDiscount.discountRate / 100) .times(limitTimeDiscount.discountRate / 100)
.decimalPlaces(2, BigNumber.ROUND_UP) .decimalPlaces(2, BigNumber.ROUND_UP)
@@ -169,7 +177,15 @@ export function returnCanDikouGoodsArr(args: CanDikouGoodsArrArgs) {
}) })
.filter((v) => { .filter((v) => {
const canUseNum = (v.num ?? 0) - (v.returnNum || 0); const canUseNum = (v.num ?? 0) - (v.returnNum || 0);
if (canUseNum <= 0 || v.is_temporary || v.is_gift) { // 兼容 is_temporary/isTemporary 和 is_gift/isGift
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
if (canUseNum <= 0 || isTemporary || isGift) {
return false; return false;
} }
@@ -188,7 +204,13 @@ function returnGoodsIsUseVipPrice(
user: ShopUserInfo, user: ShopUserInfo,
goods: BaseCartItem goods: BaseCartItem
) { ) {
if (goods.is_time_discount) { // 兼容 isTimeDiscount/is_time_discount
const isTimeDiscount = getCompatibleFieldValue(
goods,
"isTimeDiscount",
"is_time_discount"
);
if (isTimeDiscount) {
return false; return false;
} }
if (shopInfo.isMemberPrice != 1 || user.isVip != 1) { if (shopInfo.isMemberPrice != 1 || user.isVip != 1) {
@@ -214,12 +236,16 @@ function returnCanCalcGoodsList(
user: ShopUserInfo user: ShopUserInfo
) { ) {
return canCalcGoodsArr.filter((goods) => { return canCalcGoodsArr.filter((goods) => {
if ( // 兼容 isTimeDiscount/is_time_discount
!coupon.discountShare && const isTimeDiscount = getCompatibleFieldValue(
(goods.is_time_discount || goods.isTimeDiscount) goods,
) { "isTimeDiscount",
"is_time_discount"
);
if (!coupon.discountShare && isTimeDiscount) {
return false; return false;
} }
if ( if (
!coupon.vipPriceShare && !coupon.vipPriceShare &&
returnGoodsIsUseVipPrice(shopInfo, user, goods) returnGoodsIsUseVipPrice(shopInfo, user, goods)
@@ -288,12 +314,14 @@ export function returnCouponCanUse(args: couponCalcParams) {
return coupon.thresholdFoods.find((food) => food.id == v.productId); return coupon.thresholdFoods.find((food) => food.id == v.productId);
}); });
} }
canCalcGoodsArr = returnCanCalcGoodsList( canCalcGoodsArr = returnCanCalcGoodsList(
canCalcGoodsArr, canCalcGoodsArr,
coupon, coupon,
shopInfo, shopInfo,
user user
); );
fullAmount = canCalcGoodsArr.reduce((pre, cur) => { fullAmount = canCalcGoodsArr.reduce((pre, cur) => {
return ( return (
pre + pre +
@@ -328,6 +356,7 @@ export function returnCouponCanUse(args: couponCalcParams) {
reason: "当前选中的券不可与其他券同享", reason: "当前选中的券不可与其他券同享",
}; };
} }
// 满减券和折扣券计算门槛金额是否满足 // 满减券和折扣券计算门槛金额是否满足
if ([1, 3].includes(coupon.type)) { if ([1, 3].includes(coupon.type)) {
if (canCalcGoodsArr.length <= 0) { if (canCalcGoodsArr.length <= 0) {
@@ -438,7 +467,7 @@ export function calcDiscountGoodsArrPrice(
) { ) {
let hasCountNum = 0; let hasCountNum = 0;
let discountPrice = 0; let discountPrice = 0;
let hasDiscountGoodsArr = []; let hasDiscountGoodsArr:BaseCartItem[] = [];
for (let i = 0; i < discountGoodsArr.length; i++) { for (let i = 0; i < discountGoodsArr.length; i++) {
if (hasCountNum >= discountNum) { if (hasCountNum >= discountNum) {
@@ -457,10 +486,13 @@ export function calcDiscountGoodsArrPrice(
discountPrice += realPrice * num; discountPrice += realPrice * num;
hasCountNum += num; hasCountNum += num;
hasDiscountGoodsArr.push({ if(goods){
...goods, hasDiscountGoodsArr.push({
num, ...goods,
}); num,
});
}
} }
return { return {
@@ -609,7 +641,7 @@ export function returnCouponProductDiscount(
let { useFoods, discountNum, useRule } = coupon; let { useFoods, discountNum, useRule } = coupon;
discountNum = discountNum || 0; discountNum = discountNum || 0;
//抵扣商品数组 //抵扣商品数组
let discountGoodsArr = []; let discountGoodsArr:BaseCartItem[] = [];
//抵扣全部商品 //抵扣全部商品
if (useFoods.length === 0) { if (useFoods.length === 0) {
@@ -656,9 +688,17 @@ function returnCouponBuyOneGiveOneDiscount(
) { ) {
const { useFoods, useRule } = coupon; const { useFoods, useRule } = coupon;
//抵扣商品 //抵扣商品
let discountGoods = undefined; let discountGoods:BaseCartItem | undefined = undefined;
//符合买一送一条件的商品 //符合买一送一条件的商品(数量>=2 + 非临时/非赠品)
const canUseGoods = canDikouGoodsArr.filter((v) => (v.num || 0) >= 2); const canUseGoods = canDikouGoodsArr.filter((v) => {
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return (v.num || 0) >= 2 && !isTemporary && !isGift;
});
//抵扣全部商品 //抵扣全部商品
if (useFoods.length === 0) { if (useFoods.length === 0) {
if (useRule == "price_asc") { if (useRule == "price_asc") {
@@ -710,9 +750,17 @@ function returnSecoendDiscount(
) { ) {
const { useFoods, useRule } = coupon; const { useFoods, useRule } = coupon;
//抵扣商品 //抵扣商品
let discountGoods = undefined; let discountGoods:BaseCartItem | undefined = undefined;
//符合条件的商品 //符合条件的商品(数量>=2 + 非临时/非赠品)
const canUseGoods = canDikouGoodsArr.filter((v) => (v.num || 0) >= 2); const canUseGoods = canDikouGoodsArr.filter((v) => {
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return (v.num || 0) >= 2 && !isTemporary && !isGift;
});
//抵扣全部商品 //抵扣全部商品
if (useFoods.length === 0) { if (useFoods.length === 0) {
if (useRule == "price_asc") { if (useRule == "price_asc") {
@@ -767,7 +815,14 @@ export function returnCanDikouGoods(
) { ) {
const result = arr const result = arr
.filter((v) => { .filter((v) => {
return !v.is_temporary && !v.is_gift; // 兼容 is_temporary/isTemporary 和 is_gift/isGift
const isTemporary = getCompatibleFieldValue(
v,
"isTemporary",
"is_temporary"
);
const isGift = getCompatibleFieldValue(v, "isGift", "is_gift");
return !isTemporary && !isGift;
}) })
.filter((v) => { .filter((v) => {
return (v.num || 0) > 0; return (v.num || 0) > 0;
@@ -781,9 +836,6 @@ export function returnCanDikouGoods(
return result; return result;
} }
export const utils = { export const utils = {
returnGoodsPrice, returnGoodsPrice,
returnGoodsGroupMap, returnGoodsGroupMap,

File diff suppressed because it is too large Load Diff

View File

@@ -29,9 +29,7 @@ export function canUseLimitTimeDiscount(
) { ) {
shopInfo = shopInfo || {}; shopInfo = shopInfo || {};
shopUserInfo = shopUserInfo || {}; shopUserInfo = shopUserInfo || {};
if(shopInfo.isMemberPrice){
shopUserInfo.isMemberPrice=1
}
if (!limitTimeDiscountRes || !limitTimeDiscountRes.id) { if (!limitTimeDiscountRes || !limitTimeDiscountRes.id) {
return false; return false;
} }
@@ -88,6 +86,9 @@ export function returnPrice(args: returnPriceArgs) {
shopId: 0, shopId: 0,
useType: "", useType: "",
}; };
if(!goods){
return 0;
}
const canUseFoods = (limitTimeDiscountRes.foods || "").split(","); const canUseFoods = (limitTimeDiscountRes.foods || "").split(",");
const includesGoods = const includesGoods =
limitTimeDiscountRes.foodType == 1 || limitTimeDiscountRes.foodType == 1 ||
@@ -96,8 +97,7 @@ export function returnPrice(args: returnPriceArgs) {
shopUserInfo = shopUserInfo || {}; shopUserInfo = shopUserInfo || {};
if ( if (
shopUserInfo.isMemberPrice == 1 && shopUserInfo.isMemberPrice == 1 &&
shopUserInfo.isVip == 1 && shopUserInfo.isVip == 1
shopInfo.isMemberPrice == 1
) { ) {
const memberPrice = goods.memberPrice || goods.salePrice; const memberPrice = goods.memberPrice || goods.salePrice;

33
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,33 @@
/**
* 通用字段兼容工具函数:处理驼峰/下划线命名的字段取值
* @param obj 目标对象(如商品信息 BaseCartItem
* @param camelCaseKey 驼峰命名字段(如 'isTemporary'
* @param snakeCaseKey 下划线命名字段(如 'is_temporary'
* @param defaultValue 默认值(默认 false适配布尔类型字段
* @returns 字段值(优先取存在的字段,无则返回默认值)
*/
export function getCompatibleFieldValue(
obj: Record<string, any>,
camelCaseKey: string,
snakeCaseKey: string,
defaultValue: boolean = false
): boolean {
// 优先判断驼峰字段(如果存在且不是 undefined/null
if (
obj.hasOwnProperty(camelCaseKey) &&
obj[camelCaseKey] !== undefined &&
obj[camelCaseKey] !== null
) {
return Boolean(obj[camelCaseKey]);
}
// 再判断下划线字段
if (
obj.hasOwnProperty(snakeCaseKey) &&
obj[snakeCaseKey] !== undefined &&
obj[snakeCaseKey] !== null
) {
return Boolean(obj[snakeCaseKey]);
}
// 都不存在时返回默认值(布尔类型字段默认 false
return defaultValue;
}

View File

@@ -2,10 +2,11 @@ import { store } from "@/store";
import WebSocketManager, { type ApifoxModel, msgType } from "@/utils/websocket"; import WebSocketManager, { type ApifoxModel, msgType } from "@/utils/websocket";
import orderApi from "@/api/order/order"; import orderApi from "@/api/order/order";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
const shopUser = useUserStoreHook();
import { customTruncateToTwoDecimals } from '@/utils/tools' import { customTruncateToTwoDecimals } from '@/utils/tools'
import productApi from "@/api/product/index"; import productApi from "@/api/product/index";
const shopUser = useUserStoreHook();
export interface CartsState { export interface CartsState {
id: string | number; id: string | number;
[property: string]: any; [property: string]: any;

View File

@@ -11,6 +11,7 @@ import { setToken, setRefreshToken, getRefreshToken, clearToken } from "@/utils/
export const useUserStore = defineStore("user", () => { export const useUserStore = defineStore("user", () => {
// const isShopAdmin = ref(false) // 0商户 1员工 // const isShopAdmin = ref(false) // 0商户 1员工
const isShopAdmin = useStorage("isShopAdmin", false) // 0商户 1员工 const isShopAdmin = useStorage("isShopAdmin", false) // 0商户 1员工
const isAdmin = useStorage("isAdmin", false) // 是否是管理员
const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo); const userInfo = useStorage<UserInfo>("userInfo", {} as UserInfo);
const promissionList = useStorage<string[]>("promissionList", [] as string[]); const promissionList = useStorage<string[]>("promissionList", [] as string[]);
//美团抖音核销店铺信息 //美团抖音核销店铺信息
@@ -32,6 +33,7 @@ export const useUserStore = defineStore("user", () => {
AuthAPI.login(loginRequest) AuthAPI.login(loginRequest)
.then((data) => { .then((data) => {
isShopAdmin.value = data.loginType == 0 ? true : false; isShopAdmin.value = data.loginType == 0 ? true : false;
isAdmin.value = data.shopInfo.id == 1 ? true : false
Object.assign(userInfo.value, { ...data.shopInfo, shopId: data.shopInfo.id }); Object.assign(userInfo.value, { ...data.shopInfo, shopId: data.shopInfo.id });
promissionList.value = data.promissionList; promissionList.value = data.promissionList;
const token = data.tokenInfo.tokenValue; const token = data.tokenInfo.tokenValue;
@@ -120,6 +122,7 @@ export const useUserStore = defineStore("user", () => {
userInfo.value = {} as UserInfo userInfo.value = {} as UserInfo
meituan_douyin_info.value = {} meituan_douyin_info.value = {}
isShopAdmin.value = false isShopAdmin.value = false
isAdmin.value = false
clearToken(); clearToken();
usePermissionStoreHook().resetRouter(); usePermissionStoreHook().resetRouter();
useDictStoreHook().clearDictionaryCache(); useDictStoreHook().clearDictionaryCache();
@@ -137,7 +140,7 @@ export const useUserStore = defineStore("user", () => {
} }
return { return {
isShopAdmin, isShopAdmin, isAdmin,
meituan_douyin_info, meituan_douyin_info,
userInfo, userInfo,
promissionList, promissionList,

View File

@@ -19,18 +19,7 @@ const modalConfig: IModalConfig = {
console.log("提交之前处理", data); console.log("提交之前处理", data);
}, },
formItems: [ formItems: [
{
label: "状态",
prop: "status",
type: 'switch',
attrs: {
activeText: '开启',
inactiveText: '关闭',
activeValue: 1,
inactiveValue: 0
},
initialValue: 1
},
{ {
label: "挂账人", label: "挂账人",
@@ -66,6 +55,56 @@ const modalConfig: IModalConfig = {
}, },
initialValue: 0 initialValue: 0
}, },
// ===================== 新增 =====================
{
label: "还款提醒日",
prop: "expireRemindDay",
type: "input",
rules: [
{
validator: (rule, value, callback) => {
if (!value) return callback();
const num = Number(value);
if (!Number.isInteger(num) || num < 1 || num > 28) {
callback(new Error("请输入 1~28 之间的整数"));
}
callback();
}
}
],
attrs: {
type: "number",
placeholder: "每月几号提醒1-28",
style: { width: "200px" },
oninput: "value=value.replace(/\\D|\\./g,'')" // 禁止输入小数点
},
initialValue: "",
},
{
label: "还款提醒",
prop: "expireRemind",
type: "input",
rules: [
{
validator: (rule, value, callback) => {
if (!value) return callback();
const num = Number(value);
if (!Number.isInteger(num) || num < 0) {
callback(new Error("请输入大于等于 0 的整数"));
}
callback();
}
}
],
attrs: {
type: "number",
placeholder: "提醒多久以前的数据单位(月)",
style: { width: "240px" },
oninput: "value=value.replace(/\\D|\\./g,'')" // 禁止输入小数点
},
initialValue: "",
},
// ================================================
{ {
label: "还款方式", label: "还款方式",
prop: "repaymentMethod", prop: "repaymentMethod",
@@ -73,6 +112,18 @@ const modalConfig: IModalConfig = {
options: returnOptions('repaymentMethod'), options: returnOptions('repaymentMethod'),
initialValue: returnOptions('repaymentMethod')[0].value initialValue: returnOptions('repaymentMethod')[0].value
}, },
{
label: "状态",
prop: "status",
type: 'switch',
attrs: {
activeText: '开启',
inactiveText: '关闭',
activeValue: 1,
inactiveValue: 0
},
initialValue: 1
},
], ],
}; };

View File

@@ -58,16 +58,36 @@ const contentConfig: IContentConfig = {
label: "已挂账金额(元)", label: "已挂账金额(元)",
align: "center", align: "center",
prop: "owedAmount", prop: "owedAmount",
templet: "custom",
slotName: 'owedAmount',
}, },
// {
// label: "剩余挂账额度(元)",
// align: "center",
// prop: "remainingAmount",
// templet: "custom",
// slotName: 'remainingAmount',
// },
{ {
label: "剩余挂账额度(元", label: "账户余额(挂账额度+用户余额",
align: "center",
prop: "remainingAmount",
},
{
label: "账户余额",
align: "center", align: "center",
prop: "accountBalance", prop: "accountBalance",
templet: "custom",
slotName: 'accountBalance',
},
{
label: "还款提醒日",
align: "center",
prop: "expireRemindDay",
templet: "custom",
slotName: 'expireRemindDay',
},
{
label: "还款提醒",
align: "center",
prop: "expireRemind",
templet: "custom",
slotName: 'expireRemind',
}, },
{ {
label: "操作", label: "操作",

View File

@@ -19,18 +19,7 @@ const modalConfig: IModalConfig = {
console.log("提交之前处理", data); console.log("提交之前处理", data);
}, },
formItems: [ formItems: [
{
label: "状态",
prop: "status",
type: 'switch',
attrs: {
activeText: '开启',
inactiveText: '关闭',
activeValue: 1,
inactiveValue: 0
},
initialValue: 1
},
{ {
label: "挂账人", label: "挂账人",
@@ -66,6 +55,56 @@ const modalConfig: IModalConfig = {
}, },
initialValue: 0 initialValue: 0
}, },
// ===================== 新增 =====================
{
label: "还款提醒日",
prop: "expireRemindDay",
type: "input",
rules: [
{
validator: (rule, value, callback) => {
if (!value) return callback();
const num = Number(value);
if (!Number.isInteger(num) || num < 1 || num > 28) {
callback(new Error("请输入 1~28 之间的整数"));
}
callback();
}
}
],
attrs: {
type: "number",
placeholder: "每月几号提醒1-28",
style: { width: "200px" },
oninput: "value=value.replace(/\\D|\\./g,'')" // 禁止输入小数点
},
initialValue: "",
},
{
label: "还款提醒",
prop: "expireRemind",
type: "input",
rules: [
{
validator: (rule, value, callback) => {
if (!value) return callback();
const num = Number(value);
if (!Number.isInteger(num) || num < 0) {
callback(new Error("请输入大于等于 0 的整数"));
}
callback();
}
}
],
attrs: {
type: "number",
placeholder: "提醒多久以前的数据单位(月)",
style: { width: "240px" },
oninput: "value=value.replace(/\\D|\\./g,'')" // 禁止输入小数点
},
initialValue: "",
},
// ================================================
{ {
label: "还款方式", label: "还款方式",
prop: "repaymentMethod", prop: "repaymentMethod",
@@ -73,6 +112,18 @@ const modalConfig: IModalConfig = {
options: returnOptions('repaymentMethod'), options: returnOptions('repaymentMethod'),
initialValue: returnOptions('repaymentMethod')[0].value initialValue: returnOptions('repaymentMethod')[0].value
}, },
{
label: "状态",
prop: "status",
type: 'switch',
attrs: {
activeText: '开启',
inactiveText: '关闭',
activeValue: 1,
inactiveValue: 0
},
initialValue: 1
},
], ],
}; };

View File

@@ -2,19 +2,49 @@ import type { ISearchConfig } from "@/components/CURD/types";
const searchConfig: ISearchConfig = { const searchConfig: ISearchConfig = {
pageName: "sys:user", pageName: "sys:user",
isExpand: true,
formItems: [ formItems: [
{ {
type: "input", type: "input",
label: "挂账人", label: "挂账人",
prop: "keywords", prop: "debtor",
attrs: { attrs: {
placeholder: "请输入挂账人或手机号", placeholder: "请输入挂账人名称",
clearable: true, clearable: true,
style: { style: {
width: "200px", width: "200px",
}, },
}, },
}, },
{
type: "input",
label: "手机号",
prop: "mobile",
attrs: {
placeholder: "请输入手机号",
clearable: true,
style: {
width: "200px",
},
},
},
{
type: "select",
label: "状态",
prop: "status",
attrs: {
placeholder: "请选择状态",
clearable: true,
style: {
width: "200px",
},
},
initialValue: '',
options: [
{ label: "停用", value: '0' },
{ label: "启用", value: '1' },
],
},
{ {
type: "select", type: "select",
label: "还款状态", label: "还款状态",
@@ -28,9 +58,8 @@ const searchConfig: ISearchConfig = {
}, },
initialValue: '', initialValue: '',
options: [ options: [
{ label: "未还款", value: 'unpaid' }, { label: "有欠款", value: '0' },
{ label: "部分还款", value: 'partial' }, { label: "无欠款", value: '1' },
{ label: "已还清", value: 'paid' },
], ],
}, },
], ],

View File

@@ -28,6 +28,27 @@
<template #options="scope"> <template #options="scope">
{{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }} {{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }}
</template> </template>
<template #accountBalance="scope">
{{ scope.row.accountBalance }}
</template>
<template #owedAmount="scope">
{{
scope.row.creditAmount - scope.row.accountBalance > 0
? scope.row.creditAmount - scope.row.accountBalance
: 0
}}
</template>
<template #expireRemindDay="scope">
<el-text>{{ scope.row.expireRemindDay }}</el-text>
</template>
<template #expireRemind="scope">
<el-text>{{ scope.row.expireRemind }}月前</el-text>
</template>
<template #remainingAmount="scope">
{{ scope.row.creditAmount - scope.row.accountBalance }}
</template>
<template #gender="scope"> <template #gender="scope">
<DictLabel v-model="scope.row[scope.prop]" code="gender" /> <DictLabel v-model="scope.row[scope.prop]" code="gender" />
</template> </template>

View File

@@ -52,15 +52,35 @@
<span>营业</span> <span>营业</span>
</div> </div>
<div class="u-flex" style="flex-wrap: wrap"> <div class="u-flex" style="flex-wrap: wrap">
<importData :type="9" style="margin-right: 14px;" @close="importDataClose" /> <importData
<el-select v-if="isHeadShop == 1 && loginType == 0" v-model="shopId" placeholder="选择分店" :type="9"
style="width: 200px; margin-right: 10px;" @change="shopChange"> style="margin-right: 14px"
<el-option v-for="item in branchList" :key="item.shopId" :label="item.shopName" :value="item.shopId" /> @close="importDataClose"
@update="updateData"
/>
<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"
/>
</el-select> </el-select>
<div class="time_wrap u-flex" style="flex-shrink: 0"> <div class="time_wrap u-flex" style="flex-shrink: 0">
<div class="date_list"> <div class="date_list">
<div class="item" :class="{ active: dataListActive == index }" v-for="(item, index) in dateList" <div
:key="item.value" @click="timeChange(item.value, index)"> class="item"
:class="{ active: dataListActive == index }"
v-for="(item, index) in dateList"
:key="item.value"
@click="timeChange(item.value, index)"
>
<!-- 标签文本 --> <!-- 标签文本 -->
<span class="date-tab-item">{{ item.label }}</span> <span class="date-tab-item">{{ item.label }}</span>
<!-- 分隔符非最后一项才显示 --> <!-- 分隔符非最后一项才显示 -->
@@ -68,9 +88,16 @@
</div> </div>
</div> </div>
<div class="u-flex"> <div class="u-flex">
<el-date-picker v-if="timeValue == 'custom'" v-model="query.createdAt" type="daterange" <el-date-picker
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" v-if="timeValue == 'custom'"
@change="summarytrade" /> v-model="query.createdAt"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
@change="summarytrade"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -79,7 +106,9 @@
<div class="top"> <div class="top">
<div class="item earnings"> <div class="item earnings">
<div class="num_wrap"> <div class="num_wrap">
<div class="num">{{ formatDecimal(trade.payAmount + trade.rechargeAmount || 0) }}</div> <div class="num">
{{ formatDecimal(trade.payAmount + trade.rechargeAmount || 0) }}
</div>
<div class="tips"> <div class="tips">
营业额() 营业额()
<el-tooltip popper-class="popper" effect="light" placement="bottom"> <el-tooltip popper-class="popper" effect="light" placement="bottom">
@@ -156,20 +185,27 @@
<!-- <div class="t">{{ formatDecimal(tradeSale.totalpayAmount || 0) }}</div> --> <!-- <div class="t">{{ formatDecimal(tradeSale.totalpayAmount || 0) }}</div> -->
</div> </div>
<div class="line_gropress"> <div class="line_gropress">
<div class="gropress l" :style="{ <div
width: `${trade.payAmount class="gropress l"
? (trade.payAmount / (trade.payAmount * 1 + trade.refundAmount * 1)) * :style="{
100 width: `${
: 0 trade.payAmount
? (trade.payAmount / (trade.payAmount * 1 + trade.refundAmount * 1)) * 100
: 0
}%`, }%`,
}" /> }"
<div class="gropress r" :style="{ />
width: `${trade.refundAmount <div
? (trade.refundAmount / (trade.payAmount * 1 + trade.refundAmount * 1)) * class="gropress r"
100 :style="{
: 0 width: `${
trade.refundAmount
? (trade.refundAmount / (trade.payAmount * 1 + trade.refundAmount * 1)) *
100
: 0
}%`, }%`,
}" /> }"
/>
</div> </div>
<div class="line_btm"> <div class="line_btm">
<el-icon class="icon el-icon-caret-right" /> <el-icon class="icon el-icon-caret-right" />
@@ -185,22 +221,30 @@
<!-- <div class="t">{{ formatDecimal(tradeSale.totalVipAmount || 0) }}</div> --> <!-- <div class="t">{{ formatDecimal(tradeSale.totalVipAmount || 0) }}</div> -->
</div> </div>
<div class="line_gropress"> <div class="line_gropress">
<div class="gropress l" :style="{ <div
width: `${trade.rechargeAmount class="gropress l"
? (trade.rechargeAmount / :style="{
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) * width: `${
100 trade.rechargeAmount
: 0 ? (trade.rechargeAmount /
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) *
100
: 0
}%`, }%`,
}" /> }"
<div class="gropress r" :style="{ />
width: `${trade.rechargeRefundAmount <div
? (trade.rechargeRefundAmount / class="gropress r"
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) * :style="{
100 width: `${
: 0 trade.rechargeRefundAmount
? (trade.rechargeRefundAmount /
(trade.rechargeAmount * 1 + trade.rechargeRefundAmount * 1)) *
100
: 0
}%`, }%`,
}" /> }"
/>
</div> </div>
<div class="line_btm"> <div class="line_btm">
<el-icon class="icon el-icon-caret-right" /> <el-icon class="icon el-icon-caret-right" />
@@ -251,8 +295,11 @@
<div class="item item1"> <div class="item item1">
<div class="title"> <div class="title">
客单价 客单价
<el-tooltip effect="dark" :content="`订单实付金额(${trade.payAmount}/就餐人数(${trade.customerCount || 0}`" <el-tooltip
placement="top"> effect="dark"
:content="`订单实付金额(${trade.payAmount}/就餐人数(${trade.customerCount || 0}`"
placement="top"
>
<el-icon> <el-icon>
<QuestionFilled /> <QuestionFilled />
</el-icon> </el-icon>
@@ -260,15 +307,19 @@
</div> </div>
<div class="icon_wrap"> <div class="icon_wrap">
<img class="img" src="@/assets/images/data_home_item1_icon.png" /> <img class="img" src="@/assets/images/data_home_item1_icon.png" />
<div class="t" style="color: #0080FF;">{{ formatDecimal(trade.avgPayAmount || 0) }}</div> <div class="t" style="color: #0080ff">
{{ formatDecimal(trade.avgPayAmount || 0) }}
</div>
</div> </div>
</div> </div>
<div class="item item2"> <div class="item item2">
<div class="title"> <div class="title">
翻台率 翻台率
<el-tooltip effect="dark" <el-tooltip
effect="dark"
:content="`翻台率=(客单数(${trade.customerCount || 0}-桌台数(${trade.tableCount || 0}/桌台数*100%`" :content="`翻台率=(客单数(${trade.customerCount || 0}-桌台数(${trade.tableCount || 0}/桌台数*100%`"
placement="top"> placement="top"
>
<el-icon> <el-icon>
<QuestionFilled /> <QuestionFilled />
</el-icon> </el-icon>
@@ -276,7 +327,7 @@
</div> </div>
<div class="icon_wrap"> <div class="icon_wrap">
<img class="img" src="@/assets/images/data_home_item2_icon.png" /> <img class="img" src="@/assets/images/data_home_item2_icon.png" />
<div class="t" style="color: #FFB200;">{{ trade.turnoverRate || 0 }}%</div> <div class="t" style="color: #ffb200">{{ trade.turnoverRate || 0 }}%</div>
</div> </div>
</div> </div>
<div class="item item3"> <div class="item item3">
@@ -295,43 +346,57 @@
<div class="left"> <div class="left">
<span>新客立减</span> <span>新客立减</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.newCustomerDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.newCustomerDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>满减活动</span> <span>满减活动</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.fullDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.fullDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>优惠券抵扣</span> <span>优惠券抵扣</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.couponDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.couponDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>积分抵扣</span> <span>积分抵扣</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.pointDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.pointDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>霸王餐</span> <span>霸王餐</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.backDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.backDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>会员折扣</span> <span>会员折扣</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.memberDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.memberDiscountAmount || 0) }}
</span>
</div> </div>
<div class="item"> <div class="item">
<div class="left"> <div class="left">
<span>订单改价</span> <span>订单改价</span>
</div> </div>
<span class="num">{{ multiplyAndFormat(trade.orderPriceDiscountAmount || 0) }}</span> <span class="num">
{{ multiplyAndFormat(trade.orderPriceDiscountAmount || 0) }}
</span>
</div> </div>
</div> </div>
</template> </template>
@@ -342,14 +407,16 @@
</div> </div>
<div class="icon_wrap"> <div class="icon_wrap">
<img class="img" src="@/assets/images/data_home_item3_icon.png" /> <img class="img" src="@/assets/images/data_home_item3_icon.png" />
<div class="t" style="color: #FF8000;">{{ formatDecimal(trade.discountAmount || 0) }}</div> <div class="t" style="color: #ff8000">
{{ formatDecimal(trade.discountAmount || 0) }}
</div>
</div> </div>
</div> </div>
<div class="item item4"> <div class="item item4">
<div class="title">优惠笔数</div> <div class="title">优惠笔数</div>
<div class="icon_wrap"> <div class="icon_wrap">
<img class="img" src="@/assets/images/data_home_item4_icon.png" /> <img class="img" src="@/assets/images/data_home_item4_icon.png" />
<div class="t" style="color: #00CB71;">{{ trade.discountCount || 0 }}</div> <div class="t" style="color: #00cb71">{{ trade.discountCount || 0 }}</div>
</div> </div>
</div> </div>
<div class="item item5"> <div class="item item5">
@@ -357,13 +424,13 @@
<div class="row"> <div class="row">
<div class="title">毛利润</div> <div class="title">毛利润</div>
<div class="icon_wrap"> <div class="icon_wrap">
<div class="t" style="color: #0000C2;">{{ trade.profitAmount || 0 }}</div> <div class="t" style="color: #0000c2">{{ trade.profitAmount || 0 }}</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="title">毛利率</div> <div class="title">毛利率</div>
<div class="icon_wrap"> <div class="icon_wrap">
<div class="t" style="color: #0000C2;">{{ trade.profitRate || 0 }}%</div> <div class="t" style="color: #0000c2">{{ trade.profitRate || 0 }}%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -392,10 +459,18 @@
<div class="item"> <div class="item">
<div class="header"> <div class="header">
<div class="tab_wrap"> <div class="tab_wrap">
<div class="item" :class="{ active: lineChartType == 0 }" @click="lineChartTypeChange(0)"> <div
class="item"
:class="{ active: lineChartType == 0 }"
@click="lineChartTypeChange(0)"
>
销售趋势 销售趋势
</div> </div>
<div class="item" :class="{ active: lineChartType == 1 }" @click="lineChartTypeChange(1)"> <div
class="item"
:class="{ active: lineChartType == 1 }"
@click="lineChartTypeChange(1)"
>
支付占比 支付占比
</div> </div>
</div> </div>
@@ -404,9 +479,20 @@
<el-radio-button value="30">30</el-radio-button> <el-radio-button value="30">30</el-radio-button>
</el-radio-group> </el-radio-group>
</div> </div>
<div v-show="lineChartType == 0" ref="saleChart" v-loading="saleLoading" class="chart" style="height: 350px" /> <div
<div v-show="lineChartType == 1" ref="payChart" v-loading="payChartLoading" class="chart" v-show="lineChartType == 0"
style="height: 350px" /> ref="saleChart"
v-loading="saleLoading"
class="chart"
style="height: 350px"
/>
<div
v-show="lineChartType == 1"
ref="payChart"
v-loading="payChartLoading"
class="chart"
style="height: 350px"
/>
</div> </div>
<!-- 商品销售排行 --> <!-- 商品销售排行 -->
<div class="item"> <div class="item">
@@ -466,7 +552,12 @@
</el-radio-group> </el-radio-group>
</div> </div>
</div> </div>
<div ref="initInterestRate" v-loading="initInterestRateLoading" class="chart" style="height: 350px" /> <div
ref="initInterestRate"
v-loading="initInterestRateLoading"
class="chart"
style="height: 350px"
/>
</div> </div>
<!-- 成本 --> <!-- 成本 -->
<div class="item"> <div class="item">
@@ -505,12 +596,16 @@
<script> <script>
import importData from "@/components/importData/index.vue"; import importData from "@/components/importData/index.vue";
import dataSummaryApi from "@/api/order/data-summary"; import dataSummaryApi from "@/api/order/data-summary";
import finceApi from "@/api/order/fince";
import ShopApi from "@/api/account/shop"; import ShopApi from "@/api/account/shop";
import dayjs from "dayjs"; import dayjs from "dayjs";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { debounce, formatDecimal } from "@/utils/tools"; import { debounce, formatDecimal } from "@/utils/tools";
import { formatDateRange } from './utils/index.js' import { formatDateRange } from "./utils/index.js";
import { multiplyAndFormat } from '@/utils/index.js' import { multiplyAndFormat } from "@/utils/index.js";
import { ElMessage, ElMessageBox } from "element-plus";
export default { export default {
name: "home", name: "home",
components: { importData }, components: { importData },
@@ -635,12 +730,12 @@ export default {
shopInfo: JSON.parse(localStorage.getItem("userInfo")), shopInfo: JSON.parse(localStorage.getItem("userInfo")),
initInterestRateLoading: true, initInterestRateLoading: true,
initInterestRate: null, initInterestRate: null,
initInterestRateTime: '', initInterestRateTime: "",
costLoading: true, costLoading: true,
costRef: null, costRef: null,
costUpdateTime: '', costUpdateTime: "",
interestRateDay: '7', interestRateDay: "7",
costDay: '7' costDay: "7",
}; };
}, },
computed: { computed: {
@@ -660,11 +755,11 @@ export default {
}, },
}, },
mounted() { mounted() {
let shopInfo = JSON.parse(localStorage.getItem('userInfo')) let shopInfo = JSON.parse(localStorage.getItem("userInfo"));
if (shopInfo.isHeadShop) { if (shopInfo.isHeadShop) {
this.shopId = shopInfo.id this.shopId = shopInfo.id;
} else { } else {
this.shopId = localStorage.getItem('shopId') this.shopId = localStorage.getItem("shopId");
} }
// 增加首页提示是否账号30天过期 // 增加首页提示是否账号30天过期
@@ -680,8 +775,8 @@ export default {
this.dateProduct(); this.dateProduct();
// this.summaryDateGet(); // this.summaryDateGet();
this.timeChange(this.timeValue); this.timeChange(this.timeValue);
this.profitRateBarChart() this.profitRateBarChart();
this.costLineChart() this.costLineChart();
this.__resizeHandler = debounce(() => { this.__resizeHandler = debounce(() => {
if (this.saleChart) { if (this.saleChart) {
@@ -710,18 +805,33 @@ export default {
// } // }
}, 100); }, 100);
window.addEventListener("resize", this.__resizeHandler); window.addEventListener("resize", this.__resizeHandler);
this.geiShopList() this.geiShopList();
// this.initCardUserChart(); // this.initCardUserChart();
}, },
methods: { methods: {
updateData(e) {
finceApi
.financeBase({
shopId: this.shopId,
type: "all",
date: e,
})
.then((res) => {
ElMessageBox.confirm("经营数据重新计算中,请稍后刷新页面查看", "提示", {
confirmButtonText: "知道了",
showCancelButton: false,
type: "warning",
});
});
},
importDataClose() { importDataClose() {
// this.summaryGet(); // this.summaryGet();
this.dateAmount(); this.dateAmount();
this.dateProduct(); this.dateProduct();
// this.summaryDateGet(); // this.summaryDateGet();
this.timeChange(this.timeValue); this.timeChange(this.timeValue);
this.profitRateBarChart() this.profitRateBarChart();
this.costLineChart() this.costLineChart();
}, },
/** /**
* 获取分店列表 * 获取分店列表
@@ -729,29 +839,29 @@ export default {
async geiShopList() { async geiShopList() {
try { try {
if (this.shopInfo.isHeadShop) { if (this.shopInfo.isHeadShop) {
let res = await ShopApi.getBranchList() let res = await ShopApi.getBranchList();
this.branchList = res; this.branchList = res;
this.shopId = res[0].shopId this.shopId = res[0].shopId;
} else { } else {
this.shopId = this.shopInfo.id this.shopId = this.shopInfo.id;
} }
} catch (error) { } catch (error) {
console.log('获取分店列表===', error); console.log("获取分店列表===", error);
} }
}, },
shopChange() { shopChange() {
this.summarytrade(); this.summarytrade();
this.lineChartTypeChange(this.lineChartType) this.lineChartTypeChange(this.lineChartType);
this.dateProduct() this.dateProduct();
this.profitRateBarChart() this.profitRateBarChart();
this.costLineChart() this.costLineChart();
}, },
// 切换时间 // 切换时间
timeChange(e, index = 0) { timeChange(e, index = 0) {
this.dataListActive = index; this.dataListActive = index;
this.timeValue = e; this.timeValue = e;
this.query.createdAt = formatDateRange(e) this.query.createdAt = formatDateRange(e);
if (e != "custom") { if (e != "custom") {
this.summarytrade(); this.summarytrade();
} }
@@ -767,7 +877,7 @@ export default {
beginDate: this.query.createdAt[0], beginDate: this.query.createdAt[0],
endDate: this.query.createdAt[1], endDate: this.query.createdAt[1],
rangeType: this.timeValue, rangeType: this.timeValue,
shopId: this.shopId shopId: this.shopId,
}); });
this.trade = res; this.trade = res;
this.tradeSale = res.sale; this.tradeSale = res.sale;
@@ -990,7 +1100,7 @@ export default {
backgroundColor: "#fff", backgroundColor: "#fff",
borderColor: "#eee", borderColor: "#eee",
borderWidth: 1, borderWidth: 1,
boxShadow: "0 2px 8px rgba(0,0,0,0.08)" boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
}, },
xAxis: [ xAxis: [
{ {
@@ -1006,11 +1116,11 @@ export default {
}, },
], ],
grid: { grid: {
top: '5%', top: "5%",
right: '5%', right: "5%",
bottom: '8%', bottom: "8%",
left: '8%', left: "8%",
containLabel: true // 确保标签不溢出,不影响点的显示 containLabel: true, // 确保标签不溢出,不影响点的显示
}, },
color: "#165DFF", // 折线和默认点的颜色 color: "#165DFF", // 折线和默认点的颜色
yAxis: [ yAxis: [
@@ -1035,8 +1145,8 @@ export default {
borderColor: "#000", // 黑色边框 borderColor: "#000", // 黑色边框
borderWidth: 2, // 边框宽度 borderWidth: 2, // 边框宽度
// 悬浮时填充色不变(仍为 #165DFF // 悬浮时填充色不变(仍为 #165DFF
color: "#165DFF" color: "#165DFF",
} },
}, },
// 确保所有数据点都显示(避免自动隐藏) // 确保所有数据点都显示(避免自动隐藏)
showAllSymbol: true, showAllSymbol: true,
@@ -1044,8 +1154,8 @@ export default {
itemStyle: { itemStyle: {
color: "#165DFF", // 点的填充色(与折线一致) color: "#165DFF", // 点的填充色(与折线一致)
borderWidth: 1, // 基础状态可加细边框(可选,不加则无) borderWidth: 1, // 基础状态可加细边框(可选,不加则无)
borderColor: "#fff" // 基础状态边框颜色(与背景区分,可选) borderColor: "#fff", // 基础状态边框颜色(与背景区分,可选)
} },
}, },
], ],
}); });
@@ -1131,8 +1241,8 @@ export default {
this.initInterestRate = echarts.init(this.$refs.initInterestRate); this.initInterestRate = echarts.init(this.$refs.initInterestRate);
// 预处理数据:将毛利率和净利率按索引对应(关键,用于手动匹配) // 预处理数据:将毛利率和净利率按索引对应(关键,用于手动匹配)
const profitRateData = data.map(item => item.profitRate || 0); const profitRateData = data.map((item) => item.profitRate || 0);
const netProfitRateData = data.map(item => item.netProfitRate || 0); const netProfitRateData = data.map((item) => item.netProfitRate || 0);
this.initInterestRate.setOption({ this.initInterestRate.setOption({
tooltip: { tooltip: {
@@ -1159,7 +1269,7 @@ export default {
backgroundColor: "#fff", backgroundColor: "#fff",
borderColor: "#eee", borderColor: "#eee",
borderWidth: 1, borderWidth: 1,
boxShadow: "0 2px 8px rgba(0,0,0,0.08)" boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
}, },
xAxis: [ xAxis: [
{ {
@@ -1184,10 +1294,10 @@ export default {
}, },
], ],
grid: { grid: {
top: '5%', top: "5%",
right: '5%', right: "5%",
bottom: '8%', bottom: "8%",
left: '8%', left: "8%",
}, },
series: [ series: [
{ {
@@ -1196,7 +1306,7 @@ export default {
barGap: "0%", // 保持柱子紧贴 barGap: "0%", // 保持柱子紧贴
barWidth: time.length <= 7 ? "50%" : "30%", barWidth: time.length <= 7 ? "50%" : "30%",
data: profitRateData, // 预处理后的毛利率数据 data: profitRateData, // 预处理后的毛利率数据
} },
], ],
}); });
}, },
@@ -1359,9 +1469,12 @@ export default {
async profitRateBarChart() { async profitRateBarChart() {
try { try {
this.initInterestRateLoading = true; this.initInterestRateLoading = true;
const res = await dataSummaryApi.profitRateBarChart({ day: this.interestRateDay, shopId: this.shopId }); const res = await dataSummaryApi.profitRateBarChart({
day: this.interestRateDay,
shopId: this.shopId,
});
this.initInterestRateTime = dayjs().format('HH:mm') this.initInterestRateTime = dayjs().format("HH:mm");
const data = res.map((item) => { const data = res.map((item) => {
return { return {
@@ -1385,7 +1498,7 @@ export default {
this.costLoading = true; this.costLoading = true;
const res = await dataSummaryApi.costLineChart({ day: this.costDay, shopId: this.shopId }); const res = await dataSummaryApi.costLineChart({ day: this.costDay, shopId: this.shopId });
this.costUpdateTime = dayjs().format('HH:mm') this.costUpdateTime = dayjs().format("HH:mm");
const data = res.map((item) => item.productCostAmount || 0); const data = res.map((item) => item.productCostAmount || 0);
const time = res.map((item) => item.tradeDay); const time = res.map((item) => item.tradeDay);

View File

@@ -1,8 +1,8 @@
export const options: optionObject = { export const options: optionObject = {
connectionType: [ connectionType: [
{ label: "USB", value: 'USB' }, { label: "USB", value: 'USB' },
{ label: "网络", value: '网络' }, { label: "云打印", value: '云打印' },
{ label: "蓝牙", value: '蓝牙' }, { label: "局域网", value: '局域网' },
], ],
subType: [ subType: [
{ label: "标签", value: 'label' }, { label: "标签", value: 'label' },

View File

@@ -0,0 +1,98 @@
<template>
<div class="goods_info">
<div v-for="(item, index) in showList" :key="item.id || index" class="row">
<el-image :src="item.productImg" class="cover" lazy />
<div class="info">
<div class="name">
<span :class="[item.isVip == 1 ? 'colorStyle' : '']">
{{ item.productName }}
</span>
<span v-if="item.refundNum" class="refund">(退 - {{ item.refundNum }})</span>
</div>
<div class="sku">{{ item.skuName }}</div>
</div>
</div>
<div v-if="needShowMore" class="show-more" @click="showAll = !showAll">
{{ showAll ? "收起" : "查看全部" }}
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
// 接收商品列表
const props = defineProps<{
goods: any[];
}>();
// 组件内部独立状态,自己控制,不影响外部
const showAll = ref(false);
// 计算需要展示的列表
const showList = computed(() => {
const list = props.goods || [];
return showAll.value ? list : list.slice(0, 2);
});
// 是否需要显示查看更多
const needShowMore = computed(() => {
return (props.goods || []).length > 2;
});
</script>
<style scoped lang="scss">
.goods_info {
.row {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-top: 10px;
}
.cover {
width: 40px;
height: 40px;
object-fit: cover;
}
.info {
flex: 1;
display: flex;
flex-direction: column;
margin-top: 2px;
text-align: center;
.name {
font-size: 12px;
}
.sku {
color: #999;
font-size: 12px;
}
}
}
}
.show-more {
color: #1989fa;
font-size: 12px;
margin-top: 6px;
cursor: pointer;
text-align: center;
}
.colorStyle {
color: #ffc315;
}
.refund {
color: #ff9731;
font-weight: bold;
}
</style>

View File

@@ -2,10 +2,10 @@ import type { statusType } from "@/api/order/order";
export const statusOptions: statusOptions[] = [ export const statusOptions: statusOptions[] = [
{ label: "全部", value: "" }, { label: "全部", value: "" },
{ label: "待支付", value: "unpaid" }, { label: "待支付", value: "unpaid" },
{ label: "制作中", value: "in-production" }, // { label: "制作中", value: "in-production" },
{ label: "待取餐", value: "wait-out" }, // { label: "待取餐", value: "wait-out" },
{ label: "订单完成", value: "done" }, { label: "订单完成", value: "done" },
{ label: "申请退单", value: "refunding" }, // { label: "申请退单", value: "refunding" },
{ label: "退单", value: "refund" }, { label: "退单", value: "refund" },
{ label: "部分退单", value: "part_refund" }, { label: "部分退单", value: "part_refund" },
{ label: "取消订单", value: "cancelled" }, { label: "取消订单", value: "cancelled" },
@@ -107,6 +107,14 @@ export function returnOptionsLabel(optionsType: optionsType, value: string | num
const option = options.find((item) => item.value === value); const option = options.find((item) => item.value === value);
return option ? option.label : ""; return option ? option.label : "";
} }
export function returnPayTypeOptionsLabel(optionsType: optionsType, value: string | number) {
const options = returnOptions(optionsType).filter(v => v.value);
if (!options) {
return "";
}
const option = options.find((item) => item.value === value);
return option ? option.label : "";
}
export interface options { export interface options {

View File

@@ -86,6 +86,14 @@ const contentConfig: IContentConfig = {
prop: "payAmount", prop: "payAmount",
width: 120, width: 120,
}, },
{
label: "支付方式",
align: "center",
prop: "payType",
width: 120,
templet: "custom",
slotName: "payType",
},
{ {
label: "订单金额 (扣除各类折扣)", label: "订单金额 (扣除各类折扣)",
align: "center", align: "center",

View File

@@ -2,19 +2,37 @@
<div class="app-container"> <div class="app-container">
<!-- 列表 --> <!-- 列表 -->
<!-- 搜索 --> <!-- 搜索 -->
<page-search ref="searchRef" :search-config="searchConfig" :isOpenAutoSearch="true" @query-click="handleQueryClick" <page-search
@reset-click="handleResetClick" /> ref="searchRef"
:search-config="searchConfig"
:isOpenAutoSearch="true"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
<!-- 列表 --> <!-- 列表 -->
<page-content ref="contentRef" :content-config="contentConfig" @add-click="handleAddClick" <page-content
@edit-click="handleEditClick" @export-click="handleExportClick" @search-click="handleSearchClick" ref="contentRef"
@toolbar-click="handleToolbarClick" @operat-click="handleOperatClick" @filter-change="handleFilterChange"> :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 #originAmount="scope"> <template #originAmount="scope">
{{ returnOriginAmount(scope.row) }} {{ returnOriginAmount(scope.row) }}
</template> </template>
<template #orderNo="scope"> <template #orderNo="scope">
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"> <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
<el-tooltip class="box-item" effect="dark" :content="scope.row.orderNo" placement="top-start"> <el-tooltip
class="box-item"
effect="dark"
:content="scope.row.orderNo"
placement="top-start"
>
{{ scope.row.orderNo }} {{ scope.row.orderNo }}
</el-tooltip> </el-tooltip>
</div> </div>
@@ -26,7 +44,7 @@
</el-tag> </el-tag>
</template> </template>
<template #goods="scope"> <template #goods="scope">
<div class="goods_info"> <!-- <div class="goods_info">
<div v-for="item in scope.row.goods" :key="item.id" class="row"> <div v-for="item in scope.row.goods" :key="item.id" class="row">
<el-image :src="item.productImg" class="cover" lazy /> <el-image :src="item.productImg" class="cover" lazy />
<div class="info"> <div class="info">
@@ -39,7 +57,8 @@
<div class="sku">{{ item.skuName }}</div> <div class="sku">{{ item.skuName }}</div>
</div> </div>
</div> </div>
</div> </div> -->
<GoodsListCollapse :goods="scope.row.goods" />
</template> </template>
<template #table="scope"> <template #table="scope">
<div> <div>
@@ -47,14 +66,14 @@
名称 名称
<el-tag type="primary">{{ scope.row.tableName || "无" }}</el-tag> <el-tag type="primary">{{ scope.row.tableName || "无" }}</el-tag>
</p> </p>
<p v-if="scope.row.tableCode">编号{{ scope.row.tableCode }}</p> <!-- <p v-if="scope.row.tableCode">编号{{ scope.row.tableCode }}</p> -->
</div> </div>
</template> </template>
<!-- 打印状态 --> <!-- 打印状态 -->
<template #printStatus="scope"> <template #printStatus="scope">
<span v-if="scope.row.printStatus.length > 0" style="color: var(--el-color-danger)"> <span v-if="scope.row.printStatus.length > 0" style="color: var(--el-color-danger)">
打印失败{{scope.row.printStatus.map(item => item.name).join('、')}} 打印失败{{ scope.row.printStatus.map((item) => item.name).join("、") }}
</span> </span>
</template> </template>
@@ -68,14 +87,25 @@
</template> </template>
<template #mobile="scope"> <template #mobile="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text> <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 #payType="scope">
{{ returnPayTypeOptionsLabel(scope.prop, scope.row[scope.prop]) }}
</template> </template>
<template #operate="scope"> <template #operate="scope">
<div> <div>
<el-button link @click="printOrderHandle(scope.row)">打印</el-button> <el-button link @click="printOrderHandle(scope.row)">打印</el-button>
<el-button link @click="showdetail(scope.row)">详情</el-button> <el-button link @click="showdetail(scope.row)">详情</el-button>
<el-button v-if="scope.row.status == 'done'" link>开票</el-button> <!-- <el-button v-if="scope.row.status == 'done'" link>开票</el-button> -->
<el-button v-if="scope.row.status == 'unpaid'" type="primary" @click="toPayOrder(scope.row)"> <el-button
v-if="scope.row.status == 'unpaid'"
type="primary"
@click="toPayOrder(scope.row)"
>
结账 结账
</el-button> </el-button>
</div> </div>
@@ -91,7 +121,11 @@
</page-modal> </page-modal>
<!-- 编辑 --> <!-- 编辑 -->
<page-modal ref="editModalRef" :modal-config="editModalConfig" @submit-click="handleSubmitClick"> <page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
>
<template #url="scope"> <template #url="scope">
<FileUpload v-model="scope.formData[scope.prop]" :limit="1" v-bind="scope.attrs" /> <FileUpload v-model="scope.formData[scope.prop]" :limit="1" v-bind="scope.attrs" />
<!-- <Dict v-model="scope.formData[scope.prop]" code="gender" v-bind="scope.attrs" /> --> <!-- <Dict v-model="scope.formData[scope.prop]" code="gender" v-bind="scope.attrs" /> -->
@@ -111,8 +145,8 @@ import addModalConfig from "./config/add";
import contentConfig from "./config/content"; import contentConfig from "./config/content";
import editModalConfig from "./config/edit"; import editModalConfig from "./config/edit";
import searchConfig from "./config/search"; import searchConfig from "./config/search";
import { returnOptionsLabel } from "./config/config"; import { returnOptionsLabel, returnPayTypeOptionsLabel } from "./config/config";
import GoodsListCollapse from "./components/GoodsListCollapse.vue";
const { const {
searchRef, searchRef,
contentRef, contentRef,
@@ -161,7 +195,7 @@ async function printOrderHandle(order: getListResponse) {
try { try {
await orderApi.printOrder({ await orderApi.printOrder({
id: order.id, id: order.id,
type: 0 type: 0,
}); });
ElMessage.success("打印成功"); ElMessage.success("打印成功");
} catch (error) { } catch (error) {
@@ -370,7 +404,7 @@ function showdetail(row: OrderInfoVo) {
left: 36%; left: 36%;
padding: 18px; padding: 18px;
>div:first-child { > div:first-child {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -381,7 +415,7 @@ function showdetail(row: OrderInfoVo) {
transform: translateX(-80px); transform: translateX(-80px);
} }
>div:last-child { > div:last-child {
text-align: center; text-align: center;
} }
} }

View File

@@ -7,7 +7,7 @@ import type { IModalConfig } from "@/components/CURD/types";
const modalConfig: IModalConfig<payType> = { const modalConfig: IModalConfig<payType> = {
pageName: "sys:user", pageName: "sys:user",
dialog: { dialog: {
title: "添加支付方式", title: "添加收银方式",
width: 800, width: 800,
draggable: true, draggable: true,
}, },
@@ -23,12 +23,12 @@ const modalConfig: IModalConfig<payType> = {
formItems: [ formItems: [
{ {
label: "支付类型", label: "收银类型",
prop: "payType", prop: "payType",
rules: [{ required: true, message: "请选择支付类型", trigger: "blur" }], rules: [{ required: true, message: "请选择收银类型", trigger: "blur" }],
type: "select", type: "select",
attrs: { attrs: {
placeholder: "请选择支付类型", placeholder: "请选择收银类型",
}, },
options: returnOptions("payType"), options: returnOptions("payType"),
col: { col: {
@@ -38,11 +38,11 @@ const modalConfig: IModalConfig<payType> = {
}, },
{ {
type: "input", type: "input",
label: "支付名称", label: "收银名称",
prop: "payName", prop: "payName",
rules: [{ required: true, message: "请输入支付名称", trigger: "blur" }], rules: [{ required: true, message: "请输入收银名称", trigger: "blur" }],
attrs: { attrs: {
placeholder: "请输入支付名称", placeholder: "请输入收银名称",
}, },
col: { col: {
xs: 24, xs: 24,

View File

@@ -1,64 +1,72 @@
import Api from "@/api/account/payType"; import Api from "@/api/account/payType";
import type { IContentConfig } from "@/components/CURD/types"; import type { IContentConfig } from "@/components/CURD/types";
import { ElMessage } from "element-plus";
const contentConfig: IContentConfig = { const contentConfig: IContentConfig = {
pageName: "sys:user", pageName: "sys:user",
// ✅ 开启拖动排序
rowDraggable: true,
table: { table: {
border: true, border: true,
highlightCurrentRow: true, highlightCurrentRow: true,
// ✅ 拖动完成后调用接口
onRowDrop: async ({ row, oldIndex, newIndex }) => {
try {
await Api.sort({
id: row.id,
oldIndex,
newIndex,
});
ElMessage.success("排序成功");
} catch (e) {
ElMessage.error("排序失败");
}
},
}, },
pagination: { pagination: false,
background: true,
layout: "prev,pager,next,jumper,total,sizes",
pageSize: 20,
pageSizes: [10, 20, 30, 50],
},
indexAction: function (params) { indexAction: function (params) {
return Api.getList(); return Api.getList();
}, },
modifyAction: async function (data) { modifyAction: async function (data) {
const res = await Api.edit(data); const res = await Api.edit(data);
ElMessage.success(res ? '修改成功' : '修改失败'); ElMessage.success(res ? "修改成功" : "修改失败");
return res return res;
}, },
pk: "id", pk: "id",
toolbar: ["add"], toolbar: [],
defaultToolbar: ["refresh", "filter", "search"], defaultToolbar: ["refresh", "filter", "search"],
cols: [ cols: [
// { type: "selection", width: 50, align: "center" }, // ✅ 拖动图标列
{
label: "排序",
align: "center",
width: 60,
templet: "custom", // 拖动把手
prop: "sort",
},
{ {
label: "图标", label: "图标",
align: "center", align: "center",
prop: "icon", prop: "icon",
templet: 'image' templet: "image",
}, },
{ {
label: "支付方式", label: "收银方式",
align: "center", align: "center",
prop: "payName", prop: "payName",
}, },
{ {
label: "支付类型", label: "收银类型",
align: "center", align: "center",
prop: "payType", prop: "payType",
}, },
{
label: "开钱箱权限",
align: "center",
prop: "isOpenCashDrawer",
templet: 'switch',
},
{ {
label: "是否显示", label: "是否显示",
align: "center", align: "center",
prop: "isDisplay", prop: "isDisplay",
templet: 'switch', templet: "switch",
},
{
label: "条件排序",
align: "center",
prop: "sorts",
}, },
{ {
label: "操作", label: "操作",
@@ -66,7 +74,7 @@ const contentConfig: IContentConfig = {
fixed: "right", fixed: "right",
width: 280, width: 280,
templet: "tool", templet: "tool",
operat: ["edit"], operat: [],
}, },
], ],
}; };

View File

@@ -7,7 +7,7 @@ import type { IModalConfig } from "@/components/CURD/types";
const modalConfig: IModalConfig<payType> = { const modalConfig: IModalConfig<payType> = {
pageName: "sys:user", pageName: "sys:user",
dialog: { dialog: {
title: "编辑支付方式", title: "编辑收银方式",
width: 800, width: 800,
draggable: true, draggable: true,
}, },
@@ -23,12 +23,12 @@ const modalConfig: IModalConfig<payType> = {
formItems: [ formItems: [
{ {
label: "支付类型", label: "收银类型",
prop: "payType", prop: "payType",
rules: [{ required: true, message: "请选择支付类型", trigger: "blur" }], rules: [{ required: true, message: "请选择收银类型", trigger: "blur" }],
type: "select", type: "select",
attrs: { attrs: {
placeholder: "请选择支付类型", placeholder: "请选择收银类型",
}, },
options: returnOptions("payType"), options: returnOptions("payType"),
col: { col: {
@@ -38,11 +38,11 @@ const modalConfig: IModalConfig<payType> = {
}, },
{ {
type: "input", type: "input",
label: "支付名称", label: "收银名称",
prop: "payName", prop: "payName",
rules: [{ required: true, message: "请输入支付名称", trigger: "blur" }], rules: [{ required: true, message: "请输入收银名称", trigger: "blur" }],
attrs: { attrs: {
placeholder: "请输入支付名称", placeholder: "请输入收银名称",
}, },
col: { col: {
xs: 24, xs: 24,

View File

@@ -0,0 +1,123 @@
<template>
<div class="app-container">
<!-- 列表 -->
<!-- 搜索 -->
<!-- <page-search
ref="searchRef"
:search-config="searchConfig"
@query-click="handleQueryClick"
@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"
>
<template #status="scope">
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
</el-tag>
</template>
<template #options="scope">
{{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }}
</template>
<template #switch="scope">
<el-switch
v-model="scope.row[scope.prop]"
disabled
:inactive-value="0"
:active-value="1"
></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"
/>
</template>
</page-content>
<!-- 新增 -->
<page-modal
ref="addModalRef"
:modal-config="addModalConfig"
@submit-click="handleSubmitClick"
></page-modal>
<!-- 编辑 -->
<page-modal
ref="editModalRef"
:modal-config="editModalConfig"
@submit-click="handleSubmitClick"
></page-modal>
</div>
</template>
<script setup lang="ts">
import VersionApi from "@/api/system/version";
import type { IObject, IOperatData } from "@/components/CURD/types";
import usePage from "@/components/CURD/usePage";
import addModalConfig from "./config/add";
import contentConfig from "./config/content";
import editModalConfig from "./config/edit";
import searchConfig from "./config/search";
import { returnOptionsLabel } from "./config/config";
import { useUserStoreHook } from "@/store/modules/user";
const shopUser = useUserStoreHook();
console.log(shopUser.isAdmin);
if (shopUser.isAdmin) {
contentConfig.toolbar = ["add"];
contentConfig.cols[contentConfig.cols.length - 1].operat = ["edit"];
}
const {
searchRef,
contentRef,
addModalRef,
editModalRef,
handleQueryClick,
handleResetClick,
// handleAddClick,
// handleEditClick,
handleSubmitClick,
handleExportClick,
handleSearchClick,
handleFilterChange,
} = usePage();
// 新增
async function handleAddClick() {
addModalRef.value?.setModalVisible();
// addModalConfig.formItems[2]!.attrs!.data =
}
// 编辑
async function handleEditClick(row: IObject) {
editModalRef.value?.handleDisabled(false);
editModalRef.value?.setModalVisible();
// 根据id获取数据进行填充
console.log(row);
editModalRef.value?.setFormData({ ...row });
}
1;
// 其他工具栏
function handleToolbarClick(name: string) {
console.log(name);
if (name === "custom1") {
ElMessage.success("点击了自定义1按钮");
}
}
// 其他操作列
async function handleOperatClick(data: IOperatData) {
console.log(data);
}
</script>

View File

@@ -9,42 +9,56 @@
@reset-click="handleResetClick" @reset-click="handleResetClick"
/> --> /> -->
<!-- 列表 --> <!-- 列表 -->
<page-content <VueDraggable
ref="contentRef" v-model="list"
:content-config="contentConfig" target="tbody"
@add-click="handleAddClick" handle=".sort-span"
@edit-click="handleEditClick" :animation="150"
@export-click="handleExportClick" ghost-class="draggable-ghost"
@search-click="handleSearchClick" @end="onDragEnd"
@toolbar-click="handleToolbarClick"
@operat-click="handleOperatClick"
@filter-change="handleFilterChange"
> >
<template #status="scope"> <page-content
<el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'"> ref="contentRef"
{{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }} :content-config="contentConfig"
</el-tag> @add-click="handleAddClick"
</template> @edit-click="handleEditClick"
<template #options="scope"> @export-click="handleExportClick"
{{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }} @search-click="handleSearchClick"
</template> @toolbar-click="handleToolbarClick"
<template #switch="scope"> @operat-click="handleOperatClick"
<el-switch @filter-change="handleFilterChange"
v-model="scope.row[scope.prop]" >
disabled <template #status="scope">
:inactive-value="0" <el-tag :type="scope.row[scope.prop] == 1 ? 'success' : 'info'">
:active-value="1" {{ scope.row[scope.prop] == 1 ? "启用" : "禁用" }}
></el-switch> </el-tag>
</template> </template>
<template #mobile="scope"> <template #options="scope">
<el-text>{{ scope.row[scope.prop] }}</el-text> {{ returnOptionsLabel(scope.prop, scope.row[scope.prop]) }}
<copy-button </template>
v-if="scope.row[scope.prop]" <template #sort="scope">
:text="scope.row[scope.prop]" <span class="sort-span">
style="margin-left: 2px" <el-icon><Rank /></el-icon>
/> </span>
</template> </template>
</page-content> <template #switch="scope">
<el-switch
v-model="scope.row[scope.prop]"
disabled
:inactive-value="0"
:active-value="1"
></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"
/>
</template>
</page-content>
</VueDraggable>
<!-- 新增 --> <!-- 新增 -->
<page-modal <page-modal
@@ -63,7 +77,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import VersionApi from "@/api/system/version"; import PayTypeApi, { sortRequest } from "@/api/account/payType";
import type { IObject, IOperatData } from "@/components/CURD/types"; import type { IObject, IOperatData } from "@/components/CURD/types";
import usePage from "@/components/CURD/usePage"; import usePage from "@/components/CURD/usePage";
import addModalConfig from "./config/add"; import addModalConfig from "./config/add";
@@ -71,6 +85,15 @@ import contentConfig from "./config/content";
import editModalConfig from "./config/edit"; import editModalConfig from "./config/edit";
import searchConfig from "./config/search"; import searchConfig from "./config/search";
import { returnOptionsLabel } from "./config/config"; import { returnOptionsLabel } from "./config/config";
import { VueDraggable } from "vue-draggable-plus";
import { useUserStoreHook } from "@/store/modules/user";
const shopUser = useUserStoreHook();
console.log(shopUser.isAdmin);
if (shopUser.isAdmin) {
contentConfig.toolbar = ["add"];
contentConfig.cols[contentConfig.cols.length - 1].operat = ["edit"];
}
const { const {
searchRef, searchRef,
@@ -86,6 +109,50 @@ const {
handleSearchClick, handleSearchClick,
handleFilterChange, handleFilterChange,
} = usePage(); } = usePage();
// ✅ 拖拽只用这个 list
const list = ref([]);
// ✅ 监听表格数据,同步到拖拽列表
watch(
() => contentRef.value?.pageData,
(val) => {
if (val) {
list.value = val;
}
},
{ deep: true, immediate: true }
);
// ==========================================
// ✅ 拖拽结束 → 调用排序接口
// ==========================================
async function onDragEnd(e) {
console.log("拖拽结束===", e);
const { oldIndex, newIndex } = e;
if (oldIndex === newIndex) return;
console.log(oldIndex, newIndex);
// 当前拖动的行
const row = contentRef.value?.pageData[oldIndex];
const newRow = contentRef.value?.pageData[newIndex];
console.log(row, newRow);
// 目标排序号newIndex + 1
const targetSort = newRow.sorts;
try {
await PayTypeApi.sort({
shopId: row.shopId, // 店铺ID
id: row.id, // 支付类型ID
targetSort: targetSort, // 目标位置排序
});
ElMessage.success("排序成功");
// 刷新列表
contentRef.value?.fetchPageData();
} catch (err) {
ElMessage.error("排序失败");
}
}
// 新增 // 新增
async function handleAddClick() { async function handleAddClick() {
@@ -100,7 +167,6 @@ async function handleEditClick(row: IObject) {
console.log(row); console.log(row);
editModalRef.value?.setFormData({ ...row }); editModalRef.value?.setFormData({ ...row });
} }
1;
// 其他工具栏 // 其他工具栏
function handleToolbarClick(name: string) { function handleToolbarClick(name: string) {
console.log(name); console.log(name);

View File

@@ -30,8 +30,8 @@
<el-table-column label="挂账人" prop="debtor" /> <el-table-column label="挂账人" prop="debtor" />
<el-table-column label="手机号" prop="mobile" /> <el-table-column label="手机号" prop="mobile" />
<el-table-column label="已挂账金额(元)" prop="owedAmount" /> <el-table-column label="账户余额(元)" prop="accountBalance" />
<el-table-column label="可用挂账额度(元)" prop="remainingAmount" /> <el-table-column label="可用挂账额度(元)" prop="creditAmount" />
<el-table-column label="操作"> <el-table-column label="操作">
<template v-slot="scope"> <template v-slot="scope">
<el-button type="text" @click="cellClick(scope.row)">选择</el-button> <el-button type="text" @click="cellClick(scope.row)">选择</el-button>
@@ -53,7 +53,6 @@
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import creditApi from "@/api/order/credit"; import creditApi from "@/api/order/credit";
export default { export default {

View File

@@ -44,12 +44,23 @@
<span class="u-font-14">部分抵扣</span> <span class="u-font-14">部分抵扣</span>
</el-radio> --> </el-radio> -->
</el-radio-group> </el-radio-group>
<el-input-number class="u-m-l-10" v-if="score.sel != -1" v-model="usePointsNumber" step-strictly <el-input-number
:step="pointsRes.equivalentPoints" placeholder="请输入积分抵扣数量" :min="pointsRes.minDeductionPoints" class="u-m-l-10"
:max="pointsRes.maxUsablePoints" :disabled="!pointsRes.usable" v-if="score.sel != -1"
@change="pointsToMoney"></el-input-number> v-model="usePointsNumber"
step-strictly
:step="pointsRes.equivalentPoints"
placeholder="请输入积分抵扣数量"
:min="pointsRes.minDeductionPoints"
:max="pointsRes.maxUsablePoints"
:disabled="!pointsRes.usable"
@change="pointsToMoney"
></el-input-number>
</div> </div>
<p class="u-font-14 color-666 u-m-t-10" v-if="pointsRes.unusableReason && !pointsRes.usable"> <p
class="u-font-14 color-666 u-m-t-10"
v-if="pointsRes.unusableReason && !pointsRes.usable"
>
<span class="color-red">*</span> <span class="color-red">*</span>
<span>{{ pointsRes.unusableReason }}</span> <span>{{ pointsRes.unusableReason }}</span>
</p> </p>
@@ -70,10 +81,13 @@
</div> </div>
<div class="u-flex u-col-center u-m-t-20 no-wrap"> <div class="u-flex u-col-center u-m-t-20 no-wrap">
<span class="u-font-14 font-bold u-m-r-20">优惠券</span> <span class="u-font-14 font-bold u-m-r-20">优惠券</span>
<div v-if=" <div
carts.orderCostSummary.fullReduction !== undefined && v-if="
carts.orderCostSummary.fullReduction.actualAmount > 0 carts.orderCostSummary.fullReduction !== undefined &&
" style="font-size: 14px; color: #555"> carts.orderCostSummary.fullReduction.actualAmount > 0
"
style="font-size: 14px; color: #555"
>
参与满减活动不可用优惠券! 参与满减活动不可用优惠券!
</div> </div>
<div class="u-flex my-select" @click="openCoupon" v-else> <div class="u-flex my-select" @click="openCoupon" v-else>
@@ -124,9 +138,14 @@
<p class="u-font-16 font-bold u-m-r-20 font-bold u-flex">选择支付方式</p> <p class="u-font-16 font-bold u-m-r-20 font-bold u-flex">选择支付方式</p>
<div class="u-m-t-20"> <div class="u-m-t-20">
<div> <div>
<el-button v-for="(item, index) in payTypes.list" :key="index" size="large" <el-button
:type="index == payTypes.sel ? 'primary' : ''" :disabled="canUsePayType(item)" v-for="(item, index) in payTypes.list"
@click="changePayType(index)"> :key="index"
size="large"
:type="index == payTypes.sel ? 'primary' : ''"
:disabled="canUsePayType(item)"
@click="changePayType(index)"
>
{{ item.payName }} {{ item.payName }}
</el-button> </el-button>
</div> </div>
@@ -213,20 +232,43 @@
</div> </div>
</div> </div>
<!-- 扫码 --> <!-- 扫码 -->
<scanPay ref="refScanPay" :order="orderInfo" @confirm="refScanPayConfirm" @paysuccess="paysuccess"></scanPay> <scanPay
ref="refScanPay"
:order="orderInfo"
@confirm="refScanPayConfirm"
@paysuccess="paysuccess"
></scanPay>
<!-- 打折 --> <!-- 打折 -->
<discount ref="refDiscount" @confirm="discountConfirm"></discount> <discount ref="refDiscount" @confirm="discountConfirm"></discount>
<!-- 优惠券 --> <!-- 优惠券 -->
<popup-coupon ref="refCoupon" :user="carts.vipUser" @confirm="refCouponConfirm"></popup-coupon> <popup-coupon ref="refCoupon" :user="carts.vipUser" @confirm="refCouponConfirm"></popup-coupon>
<!-- 挂账 --> <!-- 挂账 -->
<chooseGuaZahng ref="refGuaZhang" :payMoney="currentpayMoney" @confirm="refGuaZhangConfirm"></chooseGuaZahng> <chooseGuaZahng
ref="refGuaZhang"
:payMoney="currentpayMoney"
@confirm="refGuaZhangConfirm"
></chooseGuaZahng>
<!-- 扫码等待用户支付转台查询 --> <!-- 扫码等待用户支付转台查询 -->
<el-dialog v-model="showCheckPayStauts" width="400" title="订单支付状态查询中..." :close-on-click-modal="false" <el-dialog
:show-close="false"> v-model="showCheckPayStauts"
width="400"
title="订单支付状态查询中..."
:close-on-click-modal="false"
:show-close="false"
>
<div class="pay_status_content"> <div class="pay_status_content">
<div class="loading" v-loading="checkPayStautsLoading" element-loading-text="用户支付中..."></div> <div
class="loading"
v-loading="checkPayStautsLoading"
element-loading-text="用户支付中..."
></div>
<div class="btn"> <div class="btn">
<el-button style="width: 100%" :disabled="!closeState" type="primary" @click="resetScanCode"> <el-button
style="width: 100%"
:disabled="!closeState"
type="primary"
@click="resetScanCode"
>
<span v-if="!closeState">{{ closeStateTime }}秒后可重新扫码</span> <span v-if="!closeState">{{ closeStateTime }}秒后可重新扫码</span>
<span v-else>重新扫码</span> <span v-else>重新扫码</span>
</el-button> </el-button>
@@ -262,7 +304,7 @@ import discount from "./discount.vue";
import { ElLoading } from "element-plus"; import { ElLoading } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { BigNumber } from "bignumber.js"; import { BigNumber } from "bignumber.js";
import { onUnmounted } from 'vue' import { onUnmounted } from "vue";
// 配置BigNumber精度 // 配置BigNumber精度
BigNumber.set({ BigNumber.set({
@@ -272,7 +314,7 @@ BigNumber.set({
//挂账 //挂账
const refGuaZhang = ref(); const refGuaZhang = ref();
function refGuaZhangConfirm(guazhangRen) { function refGuaZhangConfirm(guazhangRen) {
payOrder("arrears", true, guazhangRen); payOrder("virtual", true, guazhangRen);
} }
function refGuaZhangShow() { function refGuaZhangShow() {
refGuaZhang.value.open(); refGuaZhang.value.open();
@@ -377,7 +419,7 @@ function discountShow(e) {
const props = defineProps({ const props = defineProps({
table: { table: {
type: Object, type: Object,
default: () => { }, default: () => {},
}, },
user: { user: {
type: Object, type: Object,
@@ -391,7 +433,7 @@ const props = defineProps({
}, },
orderInfo: { orderInfo: {
type: Object, type: Object,
default: () => { }, default: () => {},
}, },
}); });
@@ -506,7 +548,16 @@ async function pointsInit() {
computedMax = Math.floor(computedMax / eq) * eq; computedMax = Math.floor(computedMax / eq) * eq;
} }
res.maxUsablePoints = computedMax; res.maxUsablePoints = computedMax;
console.debug("pointsInit debug:", { finalPay: finalPay, basePay: basePay, eq, rawMaxRatio, maxRatio, maxByMoney, userPoints, computedMax }); console.debug("pointsInit debug:", {
finalPay: finalPay,
basePay: basePay,
eq,
rawMaxRatio,
maxRatio,
maxByMoney,
userPoints,
computedMax,
});
// 最小抵扣积分为配置值或等于换算比 // 最小抵扣积分为配置值或等于换算比
res.minDeductionPoints = pointsConfig?.minDeductionPoints || eq; res.minDeductionPoints = pointsConfig?.minDeductionPoints || eq;
if (res.maxUsablePoints < res.minDeductionPoints) { if (res.maxUsablePoints < res.minDeductionPoints) {
@@ -565,7 +616,10 @@ function pointsToMoney(val) {
} }
// 计算抵扣金额(元),向下保留两位 // 计算抵扣金额(元),向下保留两位
const money = new BigNumber(pts).div(cfg.equivalentPoints).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber(); const money = new BigNumber(pts)
.div(cfg.equivalentPoints)
.decimalPlaces(2, BigNumber.ROUND_DOWN)
.toNumber();
// 再次校验不超过允许的最大抵扣金额(基于比例或门槛) // 再次校验不超过允许的最大抵扣金额(基于比例或门槛)
let finalPay = Number(carts.orderCostSummary.finalPayAmount) || 0; let finalPay = Number(carts.orderCostSummary.finalPayAmount) || 0;
@@ -577,7 +631,9 @@ function pointsToMoney(val) {
carts.orderCostSummary.pointDeductionAmount = 0; carts.orderCostSummary.pointDeductionAmount = 0;
return; return;
} }
const maxAllowedMoney = new BigNumber(maxByRatio).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber(); const maxAllowedMoney = new BigNumber(maxByRatio)
.decimalPlaces(2, BigNumber.ROUND_DOWN)
.toNumber();
console.debug("pointsToMoney debug:", { finalPay, cfg, pts, money, maxByRatio, maxAllowedMoney }); console.debug("pointsToMoney debug:", { finalPay, cfg, pts, money, maxByRatio, maxAllowedMoney });
if (money > maxAllowedMoney) { if (money > maxAllowedMoney) {
// 调整积分到允许的最大金额对应的积分 // 调整积分到允许的最大金额对应的积分
@@ -586,12 +642,15 @@ function pointsToMoney(val) {
} }
usePointsNumber.value = pts; usePointsNumber.value = pts;
const finalMoney = new BigNumber(pts).div(cfg.equivalentPoints).decimalPlaces(2, BigNumber.ROUND_DOWN).toNumber(); const finalMoney = new BigNumber(pts)
.div(cfg.equivalentPoints)
.decimalPlaces(2, BigNumber.ROUND_DOWN)
.toNumber();
carts.orderCostSummary.pointUsed = pts; carts.orderCostSummary.pointUsed = pts;
carts.orderCostSummary.pointDeductionAmount = finalMoney; carts.orderCostSummary.pointDeductionAmount = finalMoney;
} }
const emits = defineEmits(["chooseUser", "paysuccess", 'createOrder']); const emits = defineEmits(["chooseUser", "paysuccess", "createOrder"]);
function chooseUser() { function chooseUser() {
emits("chooseUser"); emits("chooseUser");
} }
@@ -669,8 +728,7 @@ function returnPayParams() {
allPack: carts.dinnerType == "take-out" ? 1 : 0, allPack: carts.dinnerType == "take-out" ? 1 : 0,
limitRate: carts.limitDiscountRes, limitRate: carts.limitDiscountRes,
newCustomerDiscountId: carts.newUserDiscount !== null ? carts.newUserDiscount.id : "", // 新客立减Id newCustomerDiscountId: carts.newUserDiscount !== null ? carts.newUserDiscount.id : "", // 新客立减Id
newCustomerDiscountAmount: newCustomerDiscountAmount: carts.newUserDiscount !== null ? carts.newUserDiscount.amount : 0, // 新客立减金额
carts.newUserDiscount !== null ? carts.newUserDiscount.amount : 0, // 新客立减金额
vipDiscountAmount: carts.orderCostSummary.vipDiscountAmount, // 超级会员折扣 vipDiscountAmount: carts.orderCostSummary.vipDiscountAmount, // 超级会员折扣
remark: cashRemark.value, // 现金支付备注 remark: cashRemark.value, // 现金支付备注
}, },
@@ -688,7 +746,8 @@ function refScanPayOpen(payType) {
if (payType == "scanCode") { if (payType == "scanCode") {
return refScanPay.value.open(returnPayParams(), "scanCode"); return refScanPay.value.open(returnPayParams(), "scanCode");
} }
if (payType == "arrears") { console.log("payType", payType);
if (payType == "virtual") {
refGuaZhangShow(); refGuaZhangShow();
return; return;
} }
@@ -705,9 +764,8 @@ async function getPaytype() {
const cashRemark = ref(""); const cashRemark = ref("");
async function nowPayClick(payType) { async function nowPayClick(payType) {
if (carts.list.length) { if (carts.list.length) {
await emits('createOrder', 'only-create') await emits("createOrder", "only-create");
} }
setTimeout(() => { setTimeout(() => {
payType = payType || payTypes.list[payTypes.sel].payType; payType = payType || payTypes.list[payTypes.sel].payType;
if (payType === "cash") { if (payType === "cash") {
@@ -715,13 +773,13 @@ async function nowPayClick(payType) {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning", type: "warning",
inputPlaceholder: '请输入现金支付备注(选填)', inputPlaceholder: "请输入现金支付备注(选填)",
}) })
.then(({ value }) => { .then(({ value }) => {
cashRemark.value = value || ""; cashRemark.value = value || "";
payOrder("cash"); payOrder("cash");
}) })
.catch(() => { }); .catch(() => {});
return; return;
} }
if (payType == "member-account") { if (payType == "member-account") {
@@ -777,16 +835,16 @@ async function payOrder(payType, isScan, guazhangren) {
shopUserId: carts.vipUser.id, shopUserId: carts.vipUser.id,
}); });
} }
if (payType == "arrears") { if (payType == "virtual") {
res = await payApi.creditPay({ ...returnPayParams(), creditBuyerId: guazhangren.id }); res = await payApi.creditPay({ ...returnPayParams(), creditBuyerId: guazhangren.id });
} }
carts.clear(); carts.clear();
} catch (error) { } catch (error) {
console.log('payOrder===', error); console.log("payOrder===", error);
// 启动状态查询 // 启动状态查询
if (error.code == 211) { if (error.code == 211) {
showCheckPayStauts.value = true showCheckPayStauts.value = true;
autoCheckOrder() autoCheckOrder();
} }
clearTimeout(payTimer); clearTimeout(payTimer);
loading.close(); loading.close();
@@ -799,68 +857,68 @@ async function payOrder(payType, isScan, guazhangren) {
} }
function clearAutoCheckOrder() { function clearAutoCheckOrder() {
clearInterval(timer.value) clearInterval(timer.value);
timer.value = null timer.value = null;
} }
// 关闭查询 // 关闭查询
function closeScanCode() { function closeScanCode() {
showCheckPayStauts.value = false; showCheckPayStauts.value = false;
reset() reset();
} }
// 重新扫码 // 重新扫码
function resetScanCode() { function resetScanCode() {
reset() reset();
showCheckPayStauts.value = false; showCheckPayStauts.value = false;
clearInterval(timer.value) clearInterval(timer.value);
timer.value = null timer.value = null;
clearInterval(closeStateTimer.value) clearInterval(closeStateTimer.value);
closeStateTimer.value = null closeStateTimer.value = null;
setTimeout(() => { setTimeout(() => {
refScanPay.value.open(returnPayParams(), "scanCode") refScanPay.value.open(returnPayParams(), "scanCode");
}, 500) }, 500);
} }
const closeState = ref(false) const closeState = ref(false);
const closeStateTime = ref(5); const closeStateTime = ref(5);
const closeStateTimer = ref(null) const closeStateTimer = ref(null);
function closeStateTimerFuc() { function closeStateTimerFuc() {
closeStateTimer.value = setInterval(() => { closeStateTimer.value = setInterval(() => {
closeStateTime.value-- closeStateTime.value--;
if (closeStateTime.value <= 0) { if (closeStateTime.value <= 0) {
clearInterval(closeStateTimer.value) clearInterval(closeStateTimer.value);
closeStateTimer.value = null closeStateTimer.value = null;
closeState.value = true closeState.value = true;
} }
}, 1000) }, 1000);
} }
// 自动查询订单状态 // 自动查询订单状态
const timer = ref(null) const timer = ref(null);
function autoCheckOrder() { function autoCheckOrder() {
closeStateTimerFuc() closeStateTimerFuc();
timer.value = setInterval(() => { timer.value = setInterval(() => {
// 开始锁单 // 开始锁单
// goodsStore.isOrderLock({ // goodsStore.isOrderLock({
// table_code: table_code.value // table_code: table_code.value
// }, 'pay_lock') // }, 'pay_lock')
checkPayStauts(false) checkPayStauts(false);
}, 2000) }, 2000);
} }
function reset() { function reset() {
checkPayStautsLoading.value = true; checkPayStautsLoading.value = true;
closeState.value = false closeState.value = false;
closeStateTime.value = 5 closeStateTime.value = 5;
} }
// 查询订单支付状态 // 查询订单支付状态
const showCheckPayStauts = ref(false) const showCheckPayStauts = ref(false);
const checkPayStautsLoading = ref(true) const checkPayStautsLoading = ref(true);
async function checkPayStauts(tips = true) { async function checkPayStauts(tips = true) {
try { try {
// 扫码下单 // 扫码下单
@@ -875,7 +933,7 @@ async function checkPayStauts(tips = true) {
checkPayStautsLoading.value = false; checkPayStautsLoading.value = false;
// scanCode.value = ""; // scanCode.value = "";
showCheckPayStauts.value = false; showCheckPayStauts.value = false;
clearAutoCheckOrder() clearAutoCheckOrder();
paysuccess(); paysuccess();
return; return;
} }
@@ -885,8 +943,8 @@ async function checkPayStauts(tips = true) {
} }
return; return;
} else { } else {
clearAutoCheckOrder() clearAutoCheckOrder();
ElMessage.warning(res.msg || ''); ElMessage.warning(res.msg || "");
return; return;
} }
} catch (error) { } catch (error) {
@@ -955,8 +1013,8 @@ watch(
); );
onUnmounted(() => { onUnmounted(() => {
clearAutoCheckOrder() clearAutoCheckOrder();
}) });
onMounted(() => { onMounted(() => {
carts.payParamsInit(); carts.payParamsInit();

View File

@@ -9,7 +9,7 @@
</div> </div>
<div> <div>
<div class="">可用额度</div> <div class="">可用额度</div>
<div class="u-m-t-6">{{ guazhangRen.remainingAmount }}</div> <div class="u-m-t-6">{{ guazhangRen.creditAmount + guazhangRen.accountBalance }}</div>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@@ -44,7 +44,7 @@
</div> </div>
</template> </template>
<script> <script>
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import keyBoard from "./keyboard.vue"; import keyBoard from "./keyboard.vue";
import chooseGuazhang from "./choose-guazhang.vue"; import chooseGuazhang from "./choose-guazhang.vue";
@@ -145,7 +145,7 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-button) { :deep(.el-button) {
padding: 12px 20px; padding: 12px 20px;
} }

View File

@@ -29,8 +29,9 @@
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { BigNumber } from "bignumber.js";
export default { export default {
data() { data() {
return { return {
@@ -60,6 +61,7 @@ export default {
open(opt) { open(opt) {
this.show = true; this.show = true;
this.option = opt; this.option = opt;
console.log(new BigNumber("0.00"));
}, },
close() { close() {
this.show = false; this.show = false;
@@ -73,7 +75,7 @@ export default {
if (valid) { if (valid) {
console.log(valid); console.log(valid);
if (this.form.discount_sale_amount * 1 <= 0 || this.form.discount_sale_amount * 1 <= 0) { if (this.form.discount_sale_amount * 1 <= 0 || this.form.discount_sale_amount * 1 <= 0) {
return ElMessage.error("价格和数量必须大于0"); // return ElMessage.error("价格和数量必须大于0");
} }
this.submit(); this.submit();
} else { } else {
@@ -87,7 +89,7 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-dialog__body) { :deep(.el-dialog__body) {
margin-bottom: 14px; margin-bottom: 14px;
margin-top: 14px; margin-top: 14px;

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="box" v-loading="!carts.isLinkFinshed" element-loading-text="购物车连接初始化中,请稍等……"> <div class="box">
<div class="content"> <div class="content">
<div class="top"> <div class="top">
<div class="left u-flex u-col-center"> <div class="left u-flex u-col-center">
@@ -9,8 +9,12 @@
<el-button type="primary" v-if="!carts.vipUser.id">选择用户</el-button> <el-button type="primary" v-if="!carts.vipUser.id">选择用户</el-button>
<div v-else class="flex cur-pointer"> <div v-else class="flex cur-pointer">
<img v-if="carts.vipUser.headImg && carts.vipUser.headImg != 'null'" class="headimg" <img
:src="carts.vipUser.headImg" alt="" /> v-if="carts.vipUser.headImg && carts.vipUser.headImg != 'null'"
class="headimg"
:src="carts.vipUser.headImg"
alt=""
/>
<div v-else class="headimg flex flex-x-y-center"> <div v-else class="headimg flex flex-x-y-center">
<i class="el-icon-user"></i> <i class="el-icon-user"></i>
</div> </div>
@@ -28,11 +32,19 @@
</div> </div>
</div> </div>
<el-popover placement="right" width="333" trigger="click" ref="refTable"> <el-popover placement="right" width="333" trigger="click" ref="refTable">
<el-input placeholder="请输入内容" prefix-icon="search" v-model="tableSearchText" <el-input
@input="tablesearchInput"></el-input> placeholder="请输入内容"
prefix-icon="search"
v-model="tableSearchText"
@input="tablesearchInput"
></el-input>
<div style="max-height: 398px; overflow-y: scroll" class="u-m-t-12"> <div style="max-height: 398px; overflow-y: scroll" class="u-m-t-12">
<div class="u-flex u-row-between u-p-t-8 table-item u-p-b-8 u-p-r-30" v-for="(item, index) in tableList" <div
:key="index" @click="tableClick(item, index)"> class="u-flex u-row-between u-p-t-8 table-item u-p-b-8 u-p-r-30"
v-for="(item, index) in tableList"
:key="index"
@click="tableClick(item, index)"
>
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<span :style="{ color: returnTableColor(item.status) }"> <span :style="{ color: returnTableColor(item.status) }">
{{ returnTableLabel(item.status) }} {{ returnTableLabel(item.status) }}
@@ -52,7 +64,12 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<el-input placeholder="请输入商品名称" v-model="goods.query.name" clearable @change="getGoods"> <el-input
placeholder="请输入商品名称"
v-model="goods.query.name"
clearable
@change="getGoods"
>
<template #suffix> <template #suffix>
<el-icon class="el-input__icon"> <el-icon class="el-input__icon">
<search /> <search />
@@ -66,16 +83,24 @@
<div class="diners"> <div class="diners">
<!-- 就餐类型 --> <!-- 就餐类型 -->
<el-button-group v-model="diners.sel" style="width: 100%; display: flex"> <el-button-group v-model="diners.sel" style="width: 100%; display: flex">
<el-button :class="{ active: index == diners.sel }" v-for="(item, index) in diners.list" <el-button
:disabled="dinerDisabled(item, index)" @click="changeDinersSel(index)" :key="index"> :class="{ active: index == diners.sel }"
v-for="(item, index) in diners.list"
:disabled="dinerDisabled(item, index)"
@click="changeDinersSel(index)"
:key="index"
>
{{ item.label }} {{ item.label }}
</el-button> </el-button>
</el-button-group> </el-button-group>
</div> </div>
<div class="u-flex u-font-14 clear u-m-t-10 perpoles"> <div class="u-flex u-font-14 clear u-m-t-10 perpoles">
<div @click="showDinerNumber" class="u-flex u-p-r-14 u-m-r-14" <div
style="border-right: 1px solid #ebebeb; line-height: 1"> @click="showDinerNumber"
class="u-flex u-p-r-14 u-m-r-14"
style="border-right: 1px solid #ebebeb; line-height: 1"
>
<span>就餐人数{{ perpole || "-" }} </span> <span>就餐人数{{ perpole || "-" }} </span>
<el-icon> <el-icon>
<ArrowRight /> <ArrowRight />
@@ -86,19 +111,39 @@
</el-button> </el-button>
</div> </div>
<!-- 购物车 --> <!-- 购物车 -->
<cartsList @editNote="showNote(true)" @createOrder="createOrder" @hideOrder="hideOrder" <cartsList
@clearOldOrder="clearOldOrder" :showOrder="showOrder" :goodsList="carts.goods" :dinerType="diners.sel" @editNote="showNote(true)"
:perpole="perpole" :remark="remark" :table="carts.tableInfo" ref="refCart"></cartsList> @createOrder="createOrder"
@hideOrder="hideOrder"
@clearOldOrder="clearOldOrder"
:showOrder="showOrder"
:goodsList="carts.goods"
:dinerType="diners.sel"
:perpole="perpole"
:remark="remark"
:table="carts.tableInfo"
ref="refCart"
></cartsList>
</div> </div>
<div class="center"> <div class="center">
<!-- 购物车控制操作按钮 --> <!-- 购物车控制操作按钮 -->
<Controls @noteClick="showNote" @packClick="showPack" @changePriceClick="showChangePrice" <Controls
@return="refReturnCartShow" @rottable="rottableShow" @changeCartNumberShow="refChangeNumberShow" /> @noteClick="showNote"
@packClick="showPack"
@changePriceClick="showChangePrice"
@return="refReturnCartShow"
@rottable="rottableShow"
@changeCartNumberShow="refChangeNumberShow"
/>
</div> </div>
<div class="right"> <div class="right">
<template v-if="!showOrder"> <template v-if="!showOrder">
<div class="flex categoty u-col-center"> <div class="flex categoty u-col-center">
<div class="show_more_btn" :class="{ showAll: category.showAll }" @click="toggleShowAll"> <div
class="show_more_btn"
:class="{ showAll: category.showAll }"
@click="toggleShowAll"
>
<div class="flex"> <div class="flex">
<div class="flex showmore"> <div class="flex showmore">
<el-icon color="#fff"> <el-icon color="#fff">
@@ -109,8 +154,16 @@
</div> </div>
</div> </div>
<div class="flex categorys" :class="{ 'flex-wrap': category.showAll }"> <div class="flex categorys" :class="{ 'flex-wrap': category.showAll }">
<div v-for="(item, index) in category.list" :key="index" @click="changeCategoryId(item)"> <div
<el-tag size="large" :type="goods.query.categoryId === item.id ? 'primary' : 'info'" effect="dark"> v-for="(item, index) in category.list"
:key="index"
@click="changeCategoryId(item)"
>
<el-tag
size="large"
:type="goods.query.categoryId === item.id ? 'primary' : 'info'"
effect="dark"
>
{{ item.name }} {{ item.name }}
</el-tag> </el-tag>
</div> </div>
@@ -124,13 +177,26 @@
</el-icon> </el-icon>
<div class="u-m-t-10">临时菜</div> <div class="u-m-t-10">临时菜</div>
</div> </div>
<GoodsItem :item="item" @itemClick="goodsClick(item)" v-for="item in carts.goods" :key="item.id"> <GoodsItem
</GoodsItem> :item="item"
@itemClick="goodsClick(item)"
v-for="item in carts.goods"
:key="item.id"
></GoodsItem>
</div> </div>
</template> </template>
<!-- 订单信息展示 --> <!-- 订单信息展示 -->
<Order ref="refOrder" :orderInfo="carts.oldOrder" @chooseUser="showChooseUser" @paysuccess="refresh" <Order
:table="carts.tableInfo" :perpole="perpole" v-else :user="user" @create-order="createOrder"></Order> ref="refOrder"
:orderInfo="carts.oldOrder"
@chooseUser="showChooseUser"
@paysuccess="refresh"
:table="carts.tableInfo"
:perpole="perpole"
v-else
:user="user"
@create-order="createOrder"
></Order>
</div> </div>
</div> </div>
</div> </div>
@@ -145,7 +211,11 @@
<!-- 临时菜 --> <!-- 临时菜 -->
<addLingShiCai ref="refAddLingShiCai" @confirm="addLingShiCaiConfirm"></addLingShiCai> <addLingShiCai ref="refAddLingShiCai" @confirm="addLingShiCaiConfirm"></addLingShiCai>
<!-- 改价 --> <!-- 改价 -->
<changePrice ref="refChangePrice" :useVipPrice="carts.useVipPrice" @confirm="changePriceConfirm"></changePrice> <changePrice
ref="refChangePrice"
:useVipPrice="carts.useVipPrice"
@confirm="changePriceConfirm"
></changePrice>
<!-- 称重商品 --> <!-- 称重商品 -->
<change-weight ref="refChangeWeight" @confirm="changeWeightConfirm"></change-weight> <change-weight ref="refChangeWeight" @confirm="changeWeightConfirm"></change-weight>
<!-- 可选套餐 --> <!-- 可选套餐 -->
@@ -518,7 +588,7 @@ async function getTableDetail(params) {
const res = await tableApi.get(params); const res = await tableApi.get(params);
return res; return res;
} }
function tablesearchInput() { } function tablesearchInput() {}
//返回桌台状态颜色 //返回桌台状态颜色
function returnTableColor(key) { function returnTableColor(key) {
const item = $status[key]; const item = $status[key];
@@ -665,7 +735,7 @@ function getCategoryList() {
size: 200, size: 200,
}) })
.then((res) => { .then((res) => {
localStorage.setItem('categorys', JSON.stringify(res)) localStorage.setItem("categorys", JSON.stringify(res));
res.unshift({ name: "全部", id: "" }); res.unshift({ name: "全部", id: "" });
category.list = res; category.list = res;
}); });
@@ -803,14 +873,14 @@ onMounted(async () => {
// : await orderApi.getHistoryList({ // : await orderApi.getHistoryList({
// tableCode, // tableCode,
// }); // });
let res = '' let res = "";
if (id) { if (id) {
res = await orderApi.getHistoryList({ res = await orderApi.getHistoryList({
orderId: id, orderId: id,
}) });
} else { } else {
res = await orderApi.getHistoryList({ res = await orderApi.getHistoryList({
tableCode: carts.table_code, tableCode: carts.table_code || tableCode,
}); });
} }

View File

@@ -181,16 +181,16 @@
</el-button> </el-button>
</template> </template>
<template v-else-if="item.status != 'idle' && item.status != 'subscribe'"> <template v-else-if="item.status != 'idle' && item.status != 'subscribe'">
<template v-if="item.status == 'using'"> <template v-if="item.status == 'unsettled'">
<el-button <el-button
:disabled="!item.tableId || item.status === 'closed'" :disabled="!item.orderId || item.status === 'closed'"
@click="diancanShow(item, 'isAddGoods')" @click="diancanShow(item, 'isAddGoods')"
> >
加菜 加菜
</el-button> </el-button>
<el-button <el-button
type="danger" type="danger"
:disabled="!item.tableId || item.status === 'closed'" :disabled="!item.orderId || item.status === 'closed'"
@click="diancanShow(item, 'isPayOrder')" @click="diancanShow(item, 'isPayOrder')"
> >
结账 结账
@@ -539,7 +539,7 @@ onMounted(() => {});
max-width: 210px; max-width: 210px;
min-width: 190px; min-width: 190px;
&.using { &.unsettled {
background-color: rgb(250, 85, 85); background-color: rgb(250, 85, 85);
} }