新增功能:收银台

This commit is contained in:
2024-08-05 01:33:10 +08:00
parent 05f890f343
commit 1d81fa9270
37 changed files with 3655 additions and 74 deletions

View File

@@ -23,12 +23,9 @@ export async function pageMp(params: MpParam) {
* 查询小程序信息列表
*/
export async function listMp(params?: MpParam) {
const res = await request.get<ApiResult<Mp[]>>(
MODULES_API_URL + '/cms/mp',
{
const res = await request.get<ApiResult<Mp[]>>(MODULES_API_URL + '/cms/mp', {
params
}
);
});
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}

View File

@@ -57,5 +57,6 @@ export interface Mp {
*/
export interface MpParam extends PageParam {
mpId?: number;
type?: number;
keywords?: string;
}

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Cart, CartParam } from './model';
import { Cart, CartParam, CartVo } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
@@ -23,7 +23,7 @@ export async function pageCart(params: CartParam) {
* 查询购物车列表
*/
export async function listCart(params?: CartParam) {
const res = await request.get<ApiResult<Cart[]>>(
const res = await request.get<ApiResult<CartVo>>(
MODULES_API_URL + '/shop/cart',
{
params
@@ -104,3 +104,48 @@ export async function getCart(id: number) {
}
return Promise.reject(new Error(res.data.message));
}
/**
* 挂单
* @param data
*/
export async function packCart(data: Cart) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cart/packCart',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 减少数量
* @param data
*/
export async function subCartNum(data: Cart) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cart/subCartNum',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 增加数量
* @param data
*/
export async function addCartNum(data: Cart) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cart/addCartNum',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -1,17 +1,45 @@
import type { PageParam } from '@/api';
export interface CartVo {
// 店铺列表
shops?: CartShopVo[];
// 购物车总金额
totalPrice?: number;
// 宝贝总数量
totalNums?: number;
// 已选宝贝
selectNums?: number;
// 是否全选
selectAll?: boolean;
}
export interface CartShopVo {
// 店铺ID
shopId?: number;
// 店铺名称
shopName?: string;
// 店铺LOGO
shopLogo?: string;
// 购物车商品列表
carts?: Cart[];
}
/**
* 购物车
*/
export interface Cart {
// 购物车表ID
id?: string;
id?: number;
// 类型 0商城 1预定
type?: number;
// 商品ID
goodsId?: string;
goodsId?: number;
// 商品名称
goodsName?: string;
// 唯一标识
code?: string;
// 是否多规格
specs?: number;
// 商品规格
spec?: string;
// 商品数量
@@ -36,6 +64,14 @@ export interface Cart {
createTime?: string;
// 修改时间
updateTime?: string;
// 商品图片
image?: string;
// 商品价格
price?: number;
// 加载完成
loading?: boolean;
// 收银员ID
adminId?: number;
}
/**

View File

@@ -0,0 +1,194 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Cashier, CashierParam, CashierVo } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询收银
*/
export async function pageCashier(params: CashierParam) {
const res = await request.get<ApiResult<PageResult<Cashier>>>(
MODULES_API_URL + '/shop/cashier/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询收银列表
*/
export async function listCashier(params?: CashierParam) {
const res = await request.get<ApiResult<CashierVo>>(
MODULES_API_URL + '/shop/cashier',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询收银列表
*/
export async function listByGroupId(params?: CashierParam) {
const res = await request.get<ApiResult<CashierVo[]>>(
MODULES_API_URL + '/shop/cashier/listByGroupId',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加收银
*/
export async function addCashier(data: Cashier) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改收银
*/
export async function updateCashier(data: Cashier) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除收银
*/
export async function removeCashier(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除收银
*/
export async function removeBatchCashier(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询收银
*/
export async function getCashier(id: number) {
const res = await request.get<ApiResult<Cashier>>(
MODULES_API_URL + '/shop/cashier/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 挂单
* @param data
*/
export async function packCashier(data: Cashier[]) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/packCashier',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 减少数量
* @param data
*/
export async function subCartNum(data: Cashier) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/subCartNum',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 增加数量
* @param data
*/
export async function addCartNum(data: Cashier) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/addCartNum',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 取单
*/
export async function getByGroup(groupId: number) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/getByGroup/' + groupId
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除整单
*/
export async function removeByGroup(groupId: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/cashier/removeByGroup/' + groupId
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,78 @@
import type { PageParam } from '@/api';
export interface CashierVo {
// 购物车总金额
totalPrice?: number;
// 宝贝总数量
totalNums?: number;
// 已选宝贝
selectNums?: number;
// 是否全选
selectAll?: boolean;
// 订单备注
comments?: string;
// 收银台商品列表
cashiers?: Cashier[];
// 按groupId分组
groups?: any[];
}
/**
* 收银
*/
export interface Cashier {
// 收银单ID
id?: number;
// 类型 0商城 1外卖
type?: number;
// 唯一标识
code?: string;
// 商品ID
goodsId?: string;
// 商品名称
goodsName?: string;
// 商品封面图
image?: string;
// 商品规格
spec?: string;
// 商品价格
price?: string;
// 商品数量
cartNum?: number;
// 单商品合计
totalPrice?: string;
// 0 = 未购买 1 = 已购买
isPay?: string;
// 是否为立即购买
isNew?: string;
// 是否选中
selected?: string;
// 商户ID
merchantId?: string;
// 用户ID
userId?: string;
// 收银员ID
cashierId?: string;
// 收银单分组ID
groupId?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 是否多规格
specs?: number;
// 商品规格数据
goodsSpecValue?: any;
}
/**
* 收银搜索条件
*/
export interface CashierParam extends PageParam {
id?: number;
groupId?: number;
showByGroup?: boolean;
keywords?: string;
}

119
src/api/shop/count/index.ts Normal file
View File

@@ -0,0 +1,119 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Count, CountParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
export async function data(params: CountParam) {
const res = await request.get<ApiResult<Count[]>>(
MODULES_API_URL + '/shop/count/data',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 分页查询商城销售统计表
*/
export async function pageCount(params: CountParam) {
const res = await request.get<ApiResult<PageResult<Count>>>(
MODULES_API_URL + '/shop/count/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商城销售统计表列表
*/
export async function listCount(params?: CountParam) {
const res = await request.get<ApiResult<Count[]>>(
MODULES_API_URL + '/shop/count',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商城销售统计表
*/
export async function addCount(data: Count) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/count',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商城销售统计表
*/
export async function updateCount(data: Count) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/count',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商城销售统计表
*/
export async function removeCount(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/count/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商城销售统计表
*/
export async function removeBatchCount(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/count/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商城销售统计表
*/
export async function getCount(id: number) {
const res = await request.get<ApiResult<Count>>(
MODULES_API_URL + '/shop/count/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,40 @@
import type { PageParam } from '@/api';
/**
* 商城销售统计表
*/
export interface Count {
// ID
id?: number;
// 统计日期
dateTime?: string;
// 总销售额
totalPrice?: string;
// 今日销售额
todayPrice?: string;
// 总会员数
totalUsers?: string;
// 今日新增
todayUsers?: string;
// 总订单笔数
totalOrders?: string;
// 今日订单笔数
todayOrders?: string;
// 备注
comments?: string;
// 状态, 0正常, 1冻结
status?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
}
/**
* 商城销售统计表搜索条件
*/
export interface CountParam extends PageParam {
id?: number;
dateTime?: string;
keywords?: string;
}

View File

@@ -96,6 +96,7 @@ export interface BathSet {
*/
export interface GoodsParam extends PageParam {
goodsId?: number;
goodsName?: string;
isShow?: number;
stock?: number;
keywords?: string;

View File

@@ -36,10 +36,31 @@
// 字典数据
const options = ref<SelectProps['options']>([
{ value: 0, label: '余额支付', key: 'balancePay', icon: 'PayCircleOutlined' },
{
value: 0,
label: '余额支付',
key: 'balancePay',
icon: 'PayCircleOutlined'
},
{ value: 1, label: '微信支付', key: 'wxPay', icon: 'WechatOutlined' },
{ value: 2, label: '会员卡支付',key: 'userCardPay', icon: 'IdcardOutlined' },
{ value: 3, label: '支付宝支付',key: 'aliPay', icon: 'AlipayCircleOutlined' }
{
value: 2,
label: '会员卡支付',
key: 'userCardPay',
icon: 'IdcardOutlined'
},
{
value: 3,
label: '支付宝支付',
key: 'aliPay',
icon: 'AlipayCircleOutlined'
},
{
value: 4,
label: '现金支付',
key: 'cashPayment',
icon: 'PayCircleOutlined'
}
]);
/* 更新选中数据 */

View File

@@ -82,12 +82,9 @@
// 表格配置
const columns = ref<ColumnItem[]>([
{
key: 'index',
width: 48,
align: 'center',
fixed: 'left',
hideInSetting: true,
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
title: 'ID',
dataIndex: 'dictDataId',
key: 'dictDataId'
},
{
title: '名称',

View File

@@ -0,0 +1,139 @@
<template>
<ele-modal
:width="750"
:visible="visible"
:maskClosable="false"
:title="title"
:footer="null"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
>
<ele-pro-table
ref="tableRef"
row-key="userId"
:datasource="datasource"
:columns="columns"
:pagination="false"
>
<template #toolbar>
<a-space>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="用户ID|手机号码"
style="width: 280px"
@search="reload"
@pressEnter="reload"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="done(record)">选择</a-button>
</a-space>
</template>
</template>
</ele-pro-table>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
ColumnItem,
DatasourceFunction
} from 'ele-admin-pro/es/ele-pro-table/types';
import { pageUsers } from '@/api/system/user';
import { User, UserParam } from '@/api/system/user/model';
import { EleProTable } from 'ele-admin-pro';
import useSearch from '@/utils/use-search';
defineProps<{
// 弹窗是否打开
visible: boolean;
title?: string;
// 修改回显的数据
data?: User | null;
}>();
const emit = defineEmits<{
(e: 'done', data: User): void;
(e: 'update:visible', visible: boolean): void;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 搜索内容
const searchText = ref(null);
// 表单数据
const { where } = useSearch<UserParam>({
userId: undefined,
nickname: undefined,
isStaff: true,
keywords: ''
});
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'userId'
},
{
title: '头像',
dataIndex: 'avatar'
},
{
title: '姓名',
dataIndex: 'realName'
},
{
title: '手机号码',
dataIndex: 'mobile',
key: 'mobile'
},
{
title: '所属部门',
dataIndex: 'organizationName'
},
{
title: '操作',
key: 'action',
align: 'center',
hideInSetting: true
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
// 搜索条件
if (searchText.value) {
where.keywords = searchText.value;
}
return pageUsers({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: UserParam) => {
// selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
const done = (record: User) => {
updateVisible(false);
emit('done', record);
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,64 @@
<template>
<div>
<!-- <a-input-group compact>-->
<!-- <a-input-->
<!-- disabled-->
<!-- style="width: calc(100% - 32px)"-->
<!-- v-model:value="value"-->
<!-- :placeholder="placeholder"-->
<!-- />111-->
<!-- <a-button @click="openEdit">-->
<!-- <template #icon><BulbOutlined class="ele-text-warning" />1111</template>-->
<!-- </a-button>-->
<!-- </a-input-group>-->
<a-button type="primary" :icon="h(UserAddOutlined)" @click="openEdit"
>关联会员</a-button
>
<!-- 选择弹窗 -->
<select-data
v-model:visible="showEdit"
:data="current"
:title="placeholder"
@done="onChange"
/>
</div>
</template>
<script lang="ts" setup>
import { BulbOutlined, UserAddOutlined } from '@ant-design/icons-vue';
import { ref, h } from 'vue';
import SelectData from './components/select-data.vue';
import { User } from '@/api/system/user/model';
withDefaults(
defineProps<{
value?: any;
placeholder?: string;
}>(),
{
placeholder: '请选择'
}
);
const emit = defineEmits<{
(e: 'done', User): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<User | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: User) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row?: User) => {
emit('done', row);
};
// 查询租户列表
// const appList = ref<App[] | undefined>([]);
</script>

View File

@@ -10,7 +10,7 @@ import { API_BASE_URL, TOKEN_HEADER_NAME, LAYOUT_PATH } from '@/config/setting';
import { getToken, setToken } from './token-util';
import { logout } from './page-tab-util';
import type { ApiResult } from '@/api';
import { getHostname, getTenantId } from "@/utils/domain";
import { getHostname, getTenantId } from '@/utils/domain';
const service = axios.create({
baseURL: API_BASE_URL
@@ -41,12 +41,11 @@ service.interceptors.request.use(
// 解析二级域名获取租户ID
if (getTenantId()) {
config.headers.common['TenantId'] = getTenantId();
console.log('domain');
console.log('domain', getTenantId());
return config;
}
if (TENANT_ID) {
config.headers.common['TenantId'] = TENANT_ID;
console.log('TENANT_ID');
return config;
}
}

View File

@@ -0,0 +1,216 @@
<template>
<a-card
:title="`收银员:${loginUser?.realName}`"
:bordered="false"
:body-style="{ minHeight: '50vh', width: '380px', padding: '0' }"
:head-style="{ className: 'bg-gray-500' }"
>
<template #extra>
<a-button danger @click="removeAll" size="small">整单取消</a-button>
</template>
<div class="goods-list overflow-x-hidden px-3 h-[500px]">
<a-empty v-if="cashier?.totalNums === 0" :image="simpleImage" />
<a-spin :spinning="spinning">
<template v-for="(item, index) in cashier?.cashiers" :key="index">
<div class="goods-item py-4 flex">
<div class="goods-image">
<a-image
:src="item.image"
:width="42"
:height="42"
:preview="false"
/>
</div>
<div class="goods-info w-full ml-2">
<div class="goods-bar flex justify-between">
<div class="goods-name flex-1">{{ item.goodsName }}</div>
</div>
<div class="spec text-gray-400">{{ item.spec }}</div>
<div class="goods-price flex justify-between items-center">
<div class="flex">
<div class="price text-red-500">{{ item.price }}</div>
<div class="total-num ml-5 text-gray-400"
>x {{ item.cartNum }}</div
>
</div>
<div class="add flex items-center text-gray-500">
<MinusCircleOutlined class="text-xl" @click="subNum(item)" />
<div class="block text-center px-3">{{ item.cartNum }}</div>
<PlusCircleOutlined class="text-xl" @click="addNum(item)" />
</div>
</div>
</div>
<div class="total-nums"> </div>
</div>
<a-divider dashed />
</template>
</a-spin>
</div>
<template #actions>
<div class="flex flex-col">
<div class="text-left pb-4 pl-4 text-gray-500"
>共计 {{ cashier.totalNums }} 已优惠0.00
</div>
<div class="flex justify-between px-2">
<a-button size="large" class="w-full mx-1" @click="getListByGroup"
>取单</a-button
>
<a-button size="large" class="w-full mx-1" @click="onPack"
>挂单</a-button
>
<a-button
type="primary"
class="w-full mx-1"
size="large"
@click="onPay"
>收款{{ formatNumber(cashier.totalPrice) }}
</a-button>
</div>
</div>
</template>
</a-card>
</template>
<script lang="ts" setup>
import {
DeleteOutlined,
PlusCircleOutlined,
MinusCircleOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import { ref, watch, createVNode } from 'vue';
import { Cashier, CashierVo } from '@/api/shop/cashier/model';
import { User } from '@/api/system/user/model';
import { formatNumber } from 'ele-admin-pro/es';
import {
subCartNum,
addCartNum,
removeBatchCashier,
removeCashier,
packCashier
} from '@/api/shop/cashier';
import { Empty, Modal, message } from 'ant-design-vue';
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
const props = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
loginUser?: User;
cashier?: CashierVo;
count?: number;
spinning?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'reload'): void;
(e: 'group'): void;
(e: 'pay'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 当前编辑数据
const current = ref<Cashier | null>(null);
const cashiers = ref<Cashier[]>([]);
/* 打开编辑弹窗 */
const onPay = () => {
emit('pay');
};
/* 打开编辑弹窗 */
const openEdit = (row?: Cashier) => {
current.value = row ?? null;
};
const subNum = (row: Cashier) => {
if (row.cartNum === 1) {
Modal.confirm({
title: '提示',
content: '确定要删除该商品吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeCashier(row?.id)
.then((msg) => {
hide();
message.success(msg);
emit('reload');
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
return false;
}
subCartNum(row).then(() => {
emit('reload');
});
};
const addNum = (row: Cashier) => {
addCartNum(row).then(() => {
emit('reload');
});
};
const removeAll = () => {
const ids = props.cashier?.cashiers?.map((d) => d.id);
if (ids) {
removeBatchCashier(ids)
.then(() => {})
.finally(() => {
emit('reload');
});
}
};
// 取单
const getListByGroup = () => {
emit('group');
};
// 挂单
const onPack = () => {
cashiers.value =
props.cashier?.cashiers?.map((d) => {
return {
id: d.id,
groupId: Number(d.groupId) + 1
};
}) || [];
if (cashiers.value.length === 0) {
return false;
}
packCashier(cashiers.value).then((res) => {
emit('reload');
});
};
watch(
() => props.count,
(count) => {
console.log(count);
},
{ immediate: true }
);
</script>
<script lang="ts">
import * as MenuIcons from '@/layout/menu-icons';
export default {
name: 'Cashier',
components: MenuIcons
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,325 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑菜单' : '添加菜单'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="菜单名称" name="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"
/>
</a-form-item>
<a-form-item label="路由地址" name="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<!-- <a-form-item label="选择图标" name="icon">-->
<!-- <ele-icon-picker-->
<!-- :data="iconData"-->
<!-- :allow-search="false"-->
<!-- v-model:value="form.icon"-->
<!-- placeholder="请选择菜单图标"-->
<!-- >-->
<!-- <template #icon="{ icon }">-->
<!-- <component :is="icon" />-->
<!-- </template>-->
<!-- </ele-icon-picker>-->
<!-- </a-form-item>-->
<a-form-item label="文字颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</a-form-item>
<a-form-item label="上传图标" name="avatar" extra="优先级高于图标">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="分组" name="parentId">
<SelectDict
dict-code="mpGroup"
:placeholder="`选择分组`"
v-model:value="form.groupName"
@done="chooseGroupId"
/>
</a-form-item>
<a-form-item label="所在行" name="rows">
<a-input-number
:min="0"
:max="3"
class="ele-fluid"
placeholder="请输入所在行"
v-model:value="form.rows"
/>
</a-form-item>
<a-form-item label="打开方式" name="target">
<DictSelect
dict-code="navType"
class="form-item"
placeholder="请选择链接方式"
v-model:value="form.target"
/>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
import { DictData } from '@/api/system/dict-data/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 页面ID
pageId?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
const pageId = ref(0);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
rows: 0,
isMpWeixin: true,
path: undefined,
component: undefined,
target: 'uni.navigateTo',
icon: '',
color: undefined,
avatar: undefined,
hide: undefined,
position: undefined,
active: undefined,
userId: 0,
groupName: undefined,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
parentId: [
{
required: true,
type: 'number',
message: '请选择分组',
trigger: 'blur'
}
],
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.avatar = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.avatar = '';
};
const chooseGroupId = (item: DictData) => {
form.parentId = item.dictDataId;
form.groupName = item.dictDataName;
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
pageId: pageId.value
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
pageId.value = props.pageId;
if (props.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.avatar) {
images.value.push({
uid: uuid(),
url: props.data.avatar,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>
<script lang="ts">
import * as icons from '@/layout/menu-icons';
export default {
components: icons,
data() {
return {
iconData: [
{
title: '已引入的图标',
icons: Object.keys(icons)
}
]
};
}
};
</script>

View File

@@ -0,0 +1,285 @@
<template>
<div class="goods-list ml-10 w-full">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="goodsId"
:columns="columns"
:datasource="datasource"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'goodsName'">
{{ record.goodsName }}
</template>
<template v-if="column.key === 'image'">
<a-image :src="record.image" :preview="false" :width="50" />
</template>
<template v-if="column.key === 'price'">
<span class="text-gray-800">
{{ formatNumber(record.price) }}
</span>
</template>
<template v-if="column.key === 'specs'">
<a-tag v-if="record.specs === 0">单规格</a-tag>
<a-tag v-if="record.specs === 1">多规格</a-tag>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">已上架</a-tag>
<a-tag v-if="record.status === 1">已下架</a-tag>
<a-tag v-if="record.status === 2" color="red">待审核</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="add(record)">加入结算</a-button>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<SpecForm
v-model:visible="showSpecForm"
:data="current"
@done="addSpecCashier"
/>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { EleProTable } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './search.vue';
import SpecForm from './specForm.vue';
import { pageGoods, removeGoods, removeBatchGoods } from '@/api/shop/goods';
import type { Goods, GoodsParam } from '@/api/shop/goods/model';
import { getMerchantId } from '@/utils/common';
import { useRouter } from 'vue-router';
import { formatNumber } from 'ele-admin-pro/es';
import { Cashier } from '@/api/shop/cashier/model';
import { User } from '@/api/system/user/model';
const { currentRoute } = useRouter();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
loginUser?: User;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'reload'): void;
}>();
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<Goods[]>([]);
// 当前编辑数据
const current = ref<Cashier | null>(null);
const form = ref<Cashier>({});
// 是否显示批量移动弹窗
const showMove = ref(false);
const specs = ref<string>();
const showSpecForm = ref<boolean>(false);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.merchantId = getMerchantId();
return pageGoods({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
width: 90,
dataIndex: 'goodsId'
},
{
title: '商品图片',
dataIndex: 'image',
width: 120,
align: 'center',
key: 'image'
},
{
title: '商品名称',
dataIndex: 'goodsName',
align: 'center',
key: 'goodsName'
},
{
title: '商品价格',
dataIndex: 'price',
width: 120,
align: 'center',
sorter: true,
key: 'price'
},
{
title: '商品库存',
dataIndex: 'stock',
width: 120,
align: 'center',
sorter: true,
key: 'stock'
},
{
title: '商品规格',
dataIndex: 'specs',
width: 120,
align: 'center',
key: 'specs'
},
{
title: '操作',
key: 'action',
width: 120,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: GoodsParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
emit('reload');
};
/* 打开编辑弹窗 */
const openEdit = (row?: Cashier) => {
current.value = row ?? null;
showSpecForm.value = true;
};
/* 打开编辑弹窗 */
const openSpecForm = (row?: Cashier) => {
current.value = row ?? null;
if (current.value) {
current.value.cartNum = 1;
}
showSpecForm.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
const add = (row?: Cashier) => {
if (row) {
form.value = row;
form.value.spec = specs.value;
openSpecForm(row);
// if (row.specs === 0) {
// form.value.spec = '';
// addCashier(form.value)
// .then(() => {
// emit('reload');
// })
// .catch((msg) => {
// message.error(msg.message);
// });
// }
// if (row.specs === 1) {
// form.value.spec = specs.value;
// openSpecForm(row);
// }
}
};
const addSpecCashier = () => {
emit('reload');
};
/* 删除单个 */
const remove = (row: Goods) => {
const hide = message.loading('请求中..', 0);
removeGoods(row.goodsId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchGoods(selection.value.map((d) => d.goodsId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
watch(currentRoute, () => {}, { immediate: true });
</script>
<script lang="ts">
export default {
name: 'Goods'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,156 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="1000"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:footer="null"
:title="isUpdate ? '取单' : '取单'"
:body-style="{
paddingTop: '10px',
paddingBottom: '38px',
backgroundColor: '#f3f3f3'
}"
@update:visible="updateVisible"
@ok="save"
>
<a-space direction="vertical" class="w-full">
<div v-for="(item, index) in groups" :key="index">
<a-card
:title="`订单总价: ¥${formatNumber(item.totalPrice)}`"
:body-style="{ padding: '10px' }"
>
<div class="flex flex-wrap">
<div v-for="(v, i) in item.cashiers" :key="i" class="flex m-2">
<div class="item flex flex-col bg-gray-50 p-3 w-[215px]">
<text>{{ v.goodsName }}</text>
<text class="text-gray-400">规格{{ v.spec }}</text>
<text class="text-gray-400">数量{{ v.cartNum }}</text>
<text>小计{{ v.totalPrice }}</text>
</div>
</div>
</div>
<!-- <div class="comments p-2 text-gray-400">-->
<!-- {{ item.comments }}-->
<!-- </div>-->
<template #actions>
<div class="text-left px-3">
<a-space>
<a-button type="primary" @click="getCashier(item.groupId)"
>取单</a-button
>
<a-button danger type="primary" @click="remove(item.groupId)"
>删除</a-button
>
<!-- <a-button>备注</a-button>-->
</a-space>
</div>
</template>
</a-card>
</div>
</a-space>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { message } from 'ant-design-vue';
import { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { FormInstance } from 'ant-design-vue/es/form';
import { getByGroup, listByGroupId, removeByGroup } from '@/api/shop/cashier';
import { formatNumber } from 'ele-admin-pro/es';
import { CashierVo } from '@/api/shop/cashier/model';
// 是否是修改
const isUpdate = ref(false);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 页面ID
pageId?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
const pageId = ref(0);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
//
const groups = ref<CashierVo[]>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const getCashier = (groupId: number) => {
getByGroup(groupId).then(() => {
updateVisible(false);
emit('done');
});
};
const remove = (groupId: number) => {
removeByGroup(groupId).then(() => {
reload();
});
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
pageId: pageId.value
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
const reload = () => {
listByGroupId({}).then((data) => {
groups.value = data;
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
reload();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,263 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="1000"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '支付结算' : '支付结算'"
okText="确定收款"
:ok-button-props="{ disabled: !user }"
:body-style="{
padding: '0',
paddingBottom: '0',
backgroundColor: '#f3f3f3'
}"
@update:visible="updateVisible"
@ok="save"
>
<!-- {{ form }}-->
<div class="flex justify-between py-3 px-4 columns-3 gap-3">
<div class="payment flex flex-col bg-white px-4 py-2">
<div class="title text-gray-500 leading-10">请选择支付方式</div>
<template v-for="(item, index) in payments" :key="index">
<div
class="item border-2 border-solid px-3 py-1 flex items-center mb-2 cursor-pointer"
:class="
form.type === item.type
? 'active border-red-300'
: 'border-gray-200'
"
@click="onType(item)"
>
<a-avatar :src="item.image" />
<text class="pl-1">{{ item.name }}</text>
</div>
</template>
</div>
<div class="settlement flex flex-col bg-white px-4 py-2 flex-1">
<div class="title text-gray-500 leading-10">结算信息</div>
<div class="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="备注信息"
v-model:value="form.comments"
/>
</div>
<div class="discount flex mt-3">
<div class="item w-1/2">
折扣()
<div class="input">
<a-input-number
allow-clear
min="0"
max="10"
placeholder="请输入折扣"
style="width: 200px"
v-model:value="form.discount"
@input="onDiscount"
/>
</div>
</div>
<div class="item w-1/2">
立减
<div class="input">
<a-input-number
allow-clear
placeholder="请输入立减金额"
style="width: 200px"
v-model:value="form.reducePrice"
/>
</div>
</div>
</div>
<div class="discount flex mt-8 items-center h-20 leading-10">
<div class="item w-1/2"> 优惠金额0.00</div>
<div class="item w-1/2 text-xl">
实付金额<span class="text-red-500 text-2xl">{{
formatNumber(data.totalPrice)
}}</span>
</div>
</div>
</div>
<div class="user-bar flex flex-col bg-white px-4 py-2">
<div class="title text-gray-500 leading-10">会员信息</div>
<div class="user-info text-center" v-if="!user">
<a-empty :description="`当前为游客`" class="text-gray-300" />
<SelectUserByButton @done="selectUser" />
</div>
<view class="tox-user" v-if="user">
<div class="mb-2 flex justify-between items-center">
<div class="avatar">
<a-avatar :src="user.avatar" :size="40" />
<text class="ml-1 text-gray-500">{{ user.nickname }}</text>
</div>
<CloseOutlined @click="removeUser" />
</div>
<div
class="border-dashed py-1 px-2 border-gray-300 text-gray-500 mt-2"
>名称{{ user.realName }}
</div>
<div
class="border-dashed py-1 px-2 border-gray-300 text-gray-500 mt-2"
>手机号{{ user.mobile || '未绑定' }}
</div>
<div
class="border-dashed py-1 px-2 border-gray-300 text-gray-500 mt-2"
>可用余额{{ user.balance }}
</div>
<div
class="border-dashed py-1 px-2 border-gray-300 text-gray-500 mt-2"
>可用积分{{ user.points }}
</div>
</view>
</div>
</div>
<UserForm v-model:visible="showUserForm" :type="type" @done="reload" />
</ele-modal>
</template>
<script lang="ts" setup>
import { CloseOutlined } from '@ant-design/icons-vue';
import { ref, watch, reactive } from 'vue';
import { message } from 'ant-design-vue';
import { FormInstance } from 'ant-design-vue/es/form';
import { formatNumber } from 'ele-admin-pro/es';
import { listPayment } from '@/api/system/payment';
import { Payment } from '@/api/system/payment/model';
import { User } from '@/api/system/user/model';
import { Cashier } from '@/api/shop/cashier/model';
import { addOrder, updateOrder } from '@/api/shop/order';
import { Order } from '@/api/shop/order/model';
// 是否是修改
const isUpdate = ref(false);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: Cashier | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 用户信息
const form = reactive<Order>({
// ID
// 类型 0商城 1外卖
type: 4,
// 唯一标识
// 商品价格
price: undefined,
// 单商品合计
totalPrice: undefined,
// 商户ID
merchantId: undefined,
// 用户ID
userId: undefined,
// 租户id
tenantId: undefined,
// 创建时间
createTime: undefined,
// 修改时间
updateTime: undefined
});
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const disabled = ref<boolean>(true);
const payments = ref<Payment[]>();
const showUserForm = ref<boolean>(false);
const user = ref<User | null>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const selectUser = (item: User) => {
user.value = item;
};
const onType = (item: Payment) => {
form.type = item.type;
};
const removeUser = () => {
user.value = null;
};
/* 保存编辑 */
const save = () => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateOrder : addOrder;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
};
const reload = () => {
listPayment({}).then((data) => {
payments.value = data;
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.active {
position: relative;
border: 2px solid #007cec;
&:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
border: 11px solid #007cec;
border-top-color: transparent;
border-left-color: transparent;
}
&:after {
content: '';
width: 4px;
height: 8px;
position: absolute;
right: 3px;
bottom: 4px;
border: 1px solid #fff;
border-top-color: transparent;
border-left-color: transparent;
transform: rotate(45deg);
}
}
</style>

View File

@@ -0,0 +1,50 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="where.goodsName"
@pressEnter="handleSearch"
@search="handleSearch"
/>
<a-button @click="handleSearch">搜索</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import useSearch from '@/utils/use-search';
import { GoodsParam } from '@/api/shop/goods/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GoodsParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<GoodsParam>({
goodsId: undefined,
goodsName: undefined,
keywords: undefined
});
const handleSearch = () => {
emit('search', where);
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,213 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '选择规格' : '选择规格'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="名称" name="goodsName">
{{ form.goodsName }}
</a-form-item>
<a-form-item label="价格" name="price">
<div class="text-red-500">{{ formatNumber(form.price) }}</div>
</a-form-item>
<a-form-item label="购买数量" name="cartNum">
<a-input-number
:min="1"
:max="9999"
placeholder="请输入排序号"
v-model:value="form.cartNum"
@pressEnter="save"
/>
</a-form-item>
<a-form-item v-if="form.specs === 1" label="规格" name="spec">
<template v-for="(item, index) in form?.goodsSpecValue" :key="index">
<div class="flex flex-col">
<span class="font-bold leading-8 text-gray-400">{{
item.value
}}</span>
<a-radio-group v-model:value="form.goodsSpecValue[index].spec">
<template v-for="(v, i) in item.detail" :key="i">
<a-radio-button :value="v">{{ v }}</a-radio-button>
</template>
</a-radio-group>
</div>
</template>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { Cashier } from '@/api/shop/cashier/model';
import { formatNumber } from 'ele-admin-pro/es';
import { addCashier, updateCashier } from '@/api/shop/cashier';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: Cashier | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const spec = ref<string>();
// 用户信息
const form = reactive<Cashier>({
// ID
id: undefined,
// 类型 0商城 1外卖
type: undefined,
// 唯一标识
code: undefined,
// 商品ID
goodsId: undefined,
// 商品名称
goodsName: undefined,
// 商品封面图
image: undefined,
// 商品规格
spec: undefined,
// 商品价格
price: undefined,
// 商品数量
cartNum: undefined,
// 单商品合计
totalPrice: undefined,
// 0 = 未购买 1 = 已购买
isPay: undefined,
// 是否为立即购买
isNew: undefined,
// 是否选中
selected: undefined,
// 商户ID
merchantId: undefined,
// 用户ID
userId: undefined,
// 收银员ID
cashierId: undefined,
// 收银单分组ID
groupId: undefined,
// 租户id
tenantId: undefined,
// 创建时间
createTime: undefined,
// 修改时间
updateTime: undefined,
// 是否多规格
specs: undefined,
// 商品规格数据
goodsSpecValue: []
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({});
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
console.log('>>>>>2');
if (form.specs === 1) {
const text = form.goodsSpecValue.map((d) => d.spec).join(',');
if (text === ',') {
message.error('请选择规格');
return false;
}
spec.value = text;
}
console.log('>>>>>3');
const formData = {
...form,
spec: spec.value
};
const saveOrUpdate = isUpdate.value ? updateCashier : addCashier;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
spec.value = '';
isUpdate.value = !!props.data?.id;
if (props.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,114 @@
<template>
<a-page-header :title="`收银台`" @back="() => $router.go(-1)">
<div class="flex">
<!-- 载入购物车 -->
<Cart
:spinning="spinning"
:loginUser="loginUser"
:cashier="cashier"
:count="cashier?.totalNums"
@group="openGroupForm"
@reload="reload"
@pay="onPay"
/>
<!-- 商品搜索 -->
<Goods :loginUser="loginUser" @reload="reload" />
</div>
</a-page-header>
<!-- 取单 -->
<GroupForm v-model:visible="showCashierForm" @done="reload" />
<!-- 支付结算 -->
<Pay v-model:visible="showPayForm" :data="cashier" @done="reload" />
<!-- 编辑弹窗 -->
<Edit
v-model:visible="showEdit"
:type="type"
:page-id="mpPage?.id"
@done="reload"
/>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { message } from 'ant-design-vue';
import Edit from './components/edit.vue';
import GroupForm from './components/group.vue';
import Cart from './components/cashier.vue';
import Goods from './components/goods.vue';
import Pay from './components/pay.vue';
import * as CashierApi from '@/api/shop/cashier';
import { MpPages } from '@/api/cms/mpPages/model';
import { useUserStore } from '@/store/modules/user';
import { Cashier, CashierVo } from '@/api/shop/cashier/model';
import { removeCashier } from '@/api/shop/cashier';
// 当前用户信息
const userStore = useUserStore();
const loginUser = computed(() => userStore.info ?? {});
const cashier = ref<CashierVo>({});
// 加载中状态
const spinning = ref<boolean>(true);
// 当前页面
const mpPage = ref<MpPages>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 加载状态
const type = ref<number>(2);
const showCashierForm = ref<boolean>(false);
const showPayForm = ref<boolean>(false);
/* 删除单个 */
const remove = (row: Cashier) => {
removeCashier(row.id)
.then((msg) => {
message.success(msg);
reload();
})
.catch((e) => {
message.error(e.message);
});
};
const openGroupForm = () => {
showCashierForm.value = true;
};
const onPay = () => {
showPayForm.value = true;
};
/* 加载数据 */
const reload = () => {
spinning.value = true;
CashierApi.listCashier({ groupId: 0 })
.then((data) => {
if (data) {
cashier.value = data;
}
})
.finally(() => {
console.log('.......');
spinning.value = false;
});
};
watch(
loginUser,
({ userId }) => {
reload();
},
{ immediate: true }
);
</script>
<script lang="ts">
import * as MenuIcons from '@/layout/menu-icons';
export default {
name: 'MpMenuHome',
components: MenuIcons
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,19 +1,22 @@
<!-- 搜索表单 -->
<template>
<a-space style="flex-wrap: wrap">
<!-- <a-button type="primary" class="ele-btn-icon" @click="add">-->
<!-- <template #icon>-->
<!-- <PlusOutlined />-->
<!-- </template>-->
<!-- <span>添加</span>-->
<a-button type="primary" class="ele-btn-icon" v-if="!item" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
<!-- <a-space style="flex-wrap: wrap" v-else>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-list`)">-->
<!-- <span>小程序概况</span>-->
<!-- </a-button>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-pages`)">-->
<!-- <span>页面管理</span>-->
<!-- </a-button>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-field/0`)">-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-field/${item.mpId}`)">-->
<!-- <span>参数配置</span>-->
<!-- </a-button>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-ad`)">-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-ad/${item.mpId}`)">-->
<!-- <span>广告管理</span>-->
<!-- </a-button>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-group`)">-->
@@ -22,13 +25,15 @@
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-package`)">-->
<!-- <span>分包管理</span>-->
<!-- </a-button>-->
</a-space>
<!-- </a-space>-->
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { watch } from 'vue';
import { watch, ref } from 'vue';
import { openUrl } from '@/utils/common';
import { Mp } from '@/api/cms/mp/model';
import { listMp } from '@/api/cms/mp';
const props = withDefaults(
defineProps<{
@@ -39,6 +44,8 @@
{}
);
const item = ref<Mp>();
const emit = defineEmits<{
(e: 'add'): void;
}>();
@@ -47,6 +54,14 @@
emit('add');
};
const reload = () => {
listMp({ type: 1 }).then((res) => {
item.value = res[0];
});
};
reload();
watch(
() => props.selection,
() => {}

View File

@@ -14,8 +14,8 @@
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@search="reload"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
@@ -66,10 +66,12 @@
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import { toTreeData } from 'ele-admin-pro/es';
import Search from './components/search.vue';
import MpEdit from './components/mpEdit.vue';
import { pageMp, removeMp, removeBatchMp, updateMp } from '@/api/cms/mp';
import type { Mp, MpParam } from '@/api/cms/mp/model';
import type { Menu } from '@/api/system/menu/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -82,6 +84,8 @@
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 默认小程序
const mp = ref<Mp>();
// 加载状态
const loading = ref(true);

View File

@@ -23,7 +23,7 @@
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space v-if="record.dictDataName === 'package'">
<a-space v-if="record.dictDataName !== 'MainPackage'">
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm

View File

@@ -45,8 +45,8 @@
</template>
<template v-if="column.key === 'action'">
<a-space>
<!-- <a @click="openUrl(`/mp-field/${record.id}`)">配置</a>-->
<!-- <a-divider type="vertical" />-->
<a @click="openUrl(`/mp-field/${record.id}`)">参数</a>
<a-divider type="vertical" />
<!-- <a @click="openUrl(`/mp-group/${record.id}`)">组件</a>-->
<!-- <a-divider type="vertical" />-->
<a @click="openUrl(`/mp-design/${record.id}`)">设计</a>

View File

@@ -0,0 +1,282 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑收银' : '添加收银'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="类型 0商城 1外卖" name="type">
<a-input
allow-clear
placeholder="请输入类型 0商城 1外卖"
v-model:value="form.type"
/>
</a-form-item>
<a-form-item label="唯一标识" name="code">
<a-input
allow-clear
placeholder="请输入唯一标识"
v-model:value="form.code"
/>
</a-form-item>
<a-form-item label="商品ID" name="goodsId">
<a-input
allow-clear
placeholder="请输入商品ID"
v-model:value="form.goodsId"
/>
</a-form-item>
<a-form-item label="商品规格" name="spec">
<a-input
allow-clear
placeholder="请输入商品规格"
v-model:value="form.spec"
/>
</a-form-item>
<a-form-item label="商品价格" name="price">
<a-input
allow-clear
placeholder="请输入商品价格"
v-model:value="form.price"
/>
</a-form-item>
<a-form-item label="商品数量" name="cartNum">
<a-input
allow-clear
placeholder="请输入商品数量"
v-model:value="form.cartNum"
/>
</a-form-item>
<a-form-item label="单商品合计" name="totalPrice">
<a-input
allow-clear
placeholder="请输入单商品合计"
v-model:value="form.totalPrice"
/>
</a-form-item>
<a-form-item label="0 = 未购买 1 = 已购买" name="isPay">
<a-input
allow-clear
placeholder="请输入0 = 未购买 1 = 已购买"
v-model:value="form.isPay"
/>
</a-form-item>
<a-form-item label="是否为立即购买" name="isNew">
<a-input
allow-clear
placeholder="请输入是否为立即购买"
v-model:value="form.isNew"
/>
</a-form-item>
<a-form-item label="是否选中" name="selected">
<a-input
allow-clear
placeholder="请输入是否选中"
v-model:value="form.selected"
/>
</a-form-item>
<a-form-item label="商户ID" name="merchantId">
<a-input
allow-clear
placeholder="请输入商户ID"
v-model:value="form.merchantId"
/>
</a-form-item>
<a-form-item label="用户ID" name="userId">
<a-input
allow-clear
placeholder="请输入用户ID"
v-model:value="form.userId"
/>
</a-form-item>
<a-form-item label="收银员ID" name="cashierId">
<a-input
allow-clear
placeholder="请输入收银员ID"
v-model:value="form.cashierId"
/>
</a-form-item>
<a-form-item label="收银单分组ID" name="groupId">
<a-input
allow-clear
placeholder="请输入收银单分组ID"
v-model:value="form.groupId"
/>
</a-form-item>
<a-form-item label="修改时间" name="updateTime">
<a-input
allow-clear
placeholder="请输入修改时间"
v-model:value="form.updateTime"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addCashier, updateCashier } from '@/api/shop/cashier';
import { Cashier } from '@/api/shop/cashier/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Cashier | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<Cashier>({
id: undefined,
type: undefined,
code: undefined,
goodsId: undefined,
spec: undefined,
price: undefined,
cartNum: undefined,
totalPrice: undefined,
isPay: undefined,
isNew: undefined,
selected: undefined,
merchantId: undefined,
userId: undefined,
cashierId: undefined,
groupId: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
cashierId: undefined,
cashierName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
cashierName: [
{
required: true,
type: 'string',
message: '请填写收银名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateCashier : addCashier;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,305 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="cashierId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<CashierEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import CashierEdit from './components/cashierEdit.vue';
import { pageCashier, removeCashier, removeBatchCashier } from '@/api/shop/cashier';
import type { Cashier, CashierParam } from '@/api/shop/cashier/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<Cashier[]>([]);
// 当前编辑数据
const current = ref<Cashier | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageCashier({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '收银单ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '类型 0商城 1外卖',
dataIndex: 'type',
key: 'type',
align: 'center',
},
{
title: '唯一标识',
dataIndex: 'code',
key: 'code',
align: 'center',
},
{
title: '商品ID',
dataIndex: 'goodsId',
key: 'goodsId',
align: 'center',
},
{
title: '商品规格',
dataIndex: 'spec',
key: 'spec',
align: 'center',
},
{
title: '商品价格',
dataIndex: 'price',
key: 'price',
align: 'center',
},
{
title: '商品数量',
dataIndex: 'cartNum',
key: 'cartNum',
align: 'center',
},
{
title: '单商品合计',
dataIndex: 'totalPrice',
key: 'totalPrice',
align: 'center',
},
{
title: '0 = 未购买 1 = 已购买',
dataIndex: 'isPay',
key: 'isPay',
align: 'center',
},
{
title: '是否为立即购买',
dataIndex: 'isNew',
key: 'isNew',
align: 'center',
},
{
title: '是否选中',
dataIndex: 'selected',
key: 'selected',
align: 'center',
},
{
title: '商户ID',
dataIndex: 'merchantId',
key: 'merchantId',
align: 'center',
},
{
title: '用户ID',
dataIndex: 'userId',
key: 'userId',
align: 'center',
},
{
title: '收银员ID',
dataIndex: 'cashierId',
key: 'cashierId',
align: 'center',
},
{
title: '收银单分组ID',
dataIndex: 'groupId',
key: 'groupId',
align: 'center',
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '修改时间',
dataIndex: 'updateTime',
key: 'updateTime',
align: 'center',
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: CashierParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Cashier) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: Cashier) => {
const hide = message.loading('请求中..', 0);
removeCashier(row.cashierId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchCashier(selection.value.map((d) => d.cashierId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: Cashier) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'Cashier'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,234 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑商城销售统计表' : '添加商城销售统计表'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="统计日期" name="dateTime">
<a-input
allow-clear
placeholder="请输入统计日期"
v-model:value="form.dateTime"
/>
</a-form-item>
<a-form-item label="总销售额" name="totalPrice">
<a-input
allow-clear
placeholder="请输入总销售额"
v-model:value="form.totalPrice"
/>
</a-form-item>
<a-form-item label="今日销售额" name="todayPrice">
<a-input
allow-clear
placeholder="请输入今日销售额"
v-model:value="form.todayPrice"
/>
</a-form-item>
<a-form-item label="总会员数" name="totalUsers">
<a-input
allow-clear
placeholder="请输入总会员数"
v-model:value="form.totalUsers"
/>
</a-form-item>
<a-form-item label="今日新增" name="todayUsers">
<a-input
allow-clear
placeholder="请输入今日新增"
v-model:value="form.todayUsers"
/>
</a-form-item>
<a-form-item label="总订单笔数" name="totalOrders">
<a-input
allow-clear
placeholder="请输入总订单笔数"
v-model:value="form.totalOrders"
/>
</a-form-item>
<a-form-item label="今日订单笔数" name="todayOrders">
<a-input
allow-clear
placeholder="请输入今日订单笔数"
v-model:value="form.todayOrders"
/>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="状态, 0正常, 1冻结" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addCount, updateCount } from '@/api/shop/count';
import { Count } from '@/api/shop/count/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Count | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<Count>({
id: undefined,
dateTime: undefined,
totalPrice: undefined,
todayPrice: undefined,
totalUsers: undefined,
todayUsers: undefined,
totalOrders: undefined,
todayOrders: undefined,
comments: undefined,
status: undefined,
tenantId: undefined,
createTime: undefined,
countId: undefined,
countName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
countName: [
{
required: true,
type: 'string',
message: '请填写商城销售统计表名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateCount : addCount;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,269 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="countId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<CountEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import CountEdit from './components/countEdit.vue';
import { pageCount, removeCount, removeBatchCount } from '@/api/shop/count';
import type { Count, CountParam } from '@/api/shop/count/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<Count[]>([]);
// 当前编辑数据
const current = ref<Count | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageCount({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '统计日期',
dataIndex: 'dateTime',
key: 'dateTime',
align: 'center',
},
{
title: '总销售额',
dataIndex: 'totalPrice',
key: 'totalPrice',
align: 'center',
},
{
title: '今日销售额',
dataIndex: 'todayPrice',
key: 'todayPrice',
align: 'center',
},
{
title: '总会员数',
dataIndex: 'totalUsers',
key: 'totalUsers',
align: 'center',
},
{
title: '今日新增',
dataIndex: 'todayUsers',
key: 'todayUsers',
align: 'center',
},
{
title: '总订单笔数',
dataIndex: 'totalOrders',
key: 'totalOrders',
align: 'center',
},
{
title: '今日订单笔数',
dataIndex: 'todayOrders',
key: 'todayOrders',
align: 'center',
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
align: 'center',
},
{
title: '状态, 0正常, 1冻结',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '注册时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: CountParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Count) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: Count) => {
const hide = message.loading('请求中..', 0);
removeCount(row.countId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchCount(selection.value.map((d) => d.countId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: Count) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'Count'
};
</script>
<style lang="less" scoped></style>

View File

@@ -30,9 +30,10 @@
label="订单状态"
:labelStyle="{ width: '90px', color: '#808080' }"
>
<a-tag v-if="form.orderStatus == 0">未使用</a-tag>
<a-tag v-if="form.orderStatus == 0"></a-tag>
<a-tag v-if="form.orderStatus == 1">已付款</a-tag>
<a-tag v-if="form.orderStatus == 3">已取消</a-tag>
<a-tag v-if="form.orderStatus == 2">已取消</a-tag>
<a-tag v-if="form.orderStatus == 3">取消中</a-tag>
<a-tag v-if="form.orderStatus == 4">退款申请中</a-tag>
<a-tag v-if="form.orderStatus == 5">退款被拒绝</a-tag>
<a-tag v-if="form.orderStatus == 6">退款成功</a-tag>
@@ -270,16 +271,16 @@
const columns = ref<ColumnItem[]>([
{
title: '场馆名称',
title: '商品名称',
dataIndex: 'merchantName',
key: 'merchantName'
},
{
title: '场地',
title: '购买数量',
dataIndex: 'fieldName'
},
{
title: '预定信息',
title: '商品规格',
dataIndex: 'comments',
key: 'comments'
},

View File

@@ -144,9 +144,10 @@
{{ record.orderInfoList }}
</template>
<template v-if="column.key === 'orderStatus'">
<span v-if="record.orderStatus == 0" class="ele-text-primary"
>未使用</span
>
<span
v-if="record.orderStatus == 0"
class="ele-text-primary"
></span>
<span v-if="record.orderStatus == 2" class="ele-text-placeholder"
>已取消</span
>

View File

@@ -1,6 +1,6 @@
<!-- 统计卡片 -->
<template>
<a-row :gutter="16">
<a-row :gutter="16" v-if="data">
<a-col
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
>
@@ -11,11 +11,13 @@
<ele-tag color="green">全门店</ele-tag>
</a-tooltip>
</div>
<h1 class="analysis-chart-card-num">¥ 126,560</h1>
<h1 class="analysis-chart-card-num"
>¥ {{ formatNumber(data[0].totalPrice) }}</h1
>
<a-divider />
<div class="flex justify-between">
<span>本月订单数</span>
<span>6,234</span>
<span>{{ data.todayOrders }}</span>
</div>
</a-card>
</a-col>
@@ -27,11 +29,11 @@
<div class="ele-cell-content">订单总数</div>
<ele-tag color="green">全门店</ele-tag>
</div>
<h1 class="analysis-chart-card-num">8,846</h1>
<h1 class="analysis-chart-card-num">{{ data.totalOrders }}</h1>
<a-divider />
<div class="flex justify-between">
<span>昨日订单数</span>
<span>634</span>
<span>{{ 634 }}</span>
</div>
</a-card>
</a-col>
@@ -87,9 +89,11 @@
import { GridComponent, TooltipComponent } from 'echarts/components';
import VChart from 'vue-echarts';
import { storeToRefs } from 'pinia';
import { formatNumber } from 'ele-admin-pro/es';
import { useThemeStore } from '@/store/modules/theme';
import { getPayNumList } from '@/api/dashboard/analysis';
import useEcharts from '@/utils/use-echarts';
import { Count } from '@/api/shop/count/model';
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]);
@@ -97,6 +101,13 @@
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Count | null;
}>();
//
const visitChartRef = ref<InstanceType<typeof VChart> | null>(null);
const payNumChartRef = ref<InstanceType<typeof VChart> | null>(null);

View File

@@ -1,13 +1,15 @@
<template>
<div class="ele-body ele-body-card">
<statistics-card />
<statistics-card :data="statistics" />
<sale-card />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import * as ShopCountApi from '@/api/shop/count';
import StatisticsCard from './components/statistics-card.vue';
import SaleCard from './components/sale-card.vue';
import VisitHour from './components/visit-hour.vue';
@@ -16,6 +18,18 @@
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const statistics = ref();
const reload = () => {
ShopCountApi.data({}).then((list) => {
if (list) {
statistics.value = list;
}
});
};
reload();
</script>
<script lang="ts">

View File

@@ -73,14 +73,14 @@
v-model:value="form.merchantSerialNumber"
/>
</a-form-item>
<a-form-item label="支付通知(选填)" name="notifyUrl" extra="微信支付通知原样推送到该地址(携带租户ID)">
</template>
<a-form-item label="支付通知" name="notifyUrl" extra="推送支付结果(携带租户ID的POST请求)">
<a-input
allow-clear
placeholder="请输入支付结果通知地址"
v-model:value="form.notifyUrl"
/>
</a-form-item>
</template>
<a-form-item label="图标" name="image">
<SelectFile
:placeholder="`请选择图片`"
@@ -240,6 +240,14 @@
trigger: 'blur'
}
],
notifyUrl: [
{
required: true,
type: 'string',
message: '请填写支付通知地址',
trigger: 'blur'
}
],
merchantSerialNumber: [
{
required: true,