增加悬浮窗页面功能

This commit is contained in:
2025-12-29 18:29:33 +08:00
parent bee51d6d1a
commit a9751fa565
10 changed files with 529 additions and 28 deletions

37
src/api/account/quick.ts Normal file
View File

@@ -0,0 +1,37 @@
import request from "@/utils/request";
import { Account_BaseUrl } from "@/api/config";
const baseURL = Account_BaseUrl + "/admin/quick";
const API = {
getList(data: any) {
return request<any>({
url: `${baseURL}`,
method: "get",
params: data
});
},
add(data: any) {
return request({
url: `${baseURL}`,
method: "post",
data: data,
});
},
delete(ids: [string | number]) {
return request({
url: `${baseURL}`,
method: "delete",
data: ids,
});
},
edit(data: any) {
return request({
url: `${baseURL}`,
method: "put",
data: data,
});
},
}
export default API;

BIN
src/assets/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
src/assets/images/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,178 @@
<template>
<el-dialog :title="form.id ? '编辑' : '添加'" width="400px" v-model="visible" @closed="resetForm">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-position="left">
<el-form-item label="菜单" prop="menuId">
<el-tree-select
:check-strictly="true"
v-model="form.menuId"
:data="menus"
:render-after-expand="false"
style="width: 240px"
></el-tree-select>
</el-form-item>
<el-form-item label="排序值">
<el-input-number v-model="form.sort" :step="1" step-strictly />
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="form.status" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog_footer">
<div class="btn">
<el-button size="large" style="width: 100%" @click="visible = false"> </el-button>
</div>
<div class="btn">
<el-button
size="large"
type="primary"
:loading="loading"
style="width: 100%"
@click="submitHandle"
>
</el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { filterNumberInput } from "@/utils";
import { roleTemplateAdd } from "@/api/account/roleTemplate";
import MenuAPI from "@/api/account/menu";
import quickApi from "@/api/account/quick";
const props = defineProps({
isGetMenu: {
type: Boolean,
required: false,
},
parMenus: {
type: Array,
required: false,
},
item: {
type: Object,
required: false,
},
});
watch(
() => props.item,
(newval) => {
if (newval) {
form.value = newval;
}
},
{ deep: true }
);
const menus = ref([]);
onMounted(async () => {
if (!props.isGetMenu) {
return;
}
const res = await MenuAPI.getRoutes();
console.log("getRoutes", res);
menus.value = returnMenu(res);
console.log(menus.value);
});
watch(
() => props.parMenus.length,
(newval) => {
menus.value = returnMenu(props.parMenus);
}
);
function returnMenu(arr) {
let result = [];
for (let menu of arr) {
menu.label = menu.title;
menu.value = menu.menuId;
result.push({
...menu,
label: menu.title,
value: menu.menuId,
children: menu.children ? returnMenu(menu.children) : [],
});
}
return result;
}
const visible = defineModel({
type: Boolean,
required: false,
});
const formRef = ref(null);
const loading = ref(false);
const form = ref({
menuId: "",
sort: 0,
url: "",
status: 1,
});
const rules = ref({
menuId: [
{
required: true,
message: "请选择菜单",
triiger: "blur",
},
],
});
function resetForm() {
form.value = {
menuId: "",
sort: 0,
url: "",
status: 1,
};
formRef.value.resetFields();
}
// 提交
const emits = defineEmits(["success"]);
function submitHandle() {
formRef.value.validate(async (vaild) => {
try {
if (vaild) {
loading.value = true;
if (form.value.sort === "") {
form.value.sort = 0;
}
await quickApi.add(form.value);
ElNotification({
title: "注意",
message: "添加成功",
type: "success",
});
emits("success");
visible.value = false;
}
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false;
}, 500);
});
}
</script>
<style scoped lang="scss">
.dialog_footer {
display: flex;
gap: 14px;
.btn {
flex: 1;
}
}
</style>

View File

@@ -0,0 +1,159 @@
<template>
<div class="container">
<div class="table-box">
<div class="row mb-sm">
<el-button type="primary" @click="showAdd = true">添加</el-button>
</div>
<el-table
:data="tableData.list"
v-loading="tableData.loading"
stripe
border
row-key="id"
:tree-props="{ children: 'children' }"
default-expand-all
style="width: 100%"
>
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column label="菜单名称" prop="isEnable">
<template #default="scope">
{{ returnMenuName(scope.row) }}
</template>
</el-table-column>
<el-table-column label="启用状态" prop="isEnable">
<template #default="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="isEnableChange($event, scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="排序值" prop="sort"></el-table-column>
<el-table-column label="创建时间" prop="createTime"></el-table-column>
<el-table-column label="操作" width="250">
<template #default="scope">
<el-button link type="primary" @click="editMenu(scope.row)">编辑</el-button>
<el-popconfirm title="确认要删除吗?" @confirm="deleteHandle(scope.row)">
<template #reference>
<el-button type="danger" link>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<dialogAdd
v-model="showAdd"
:parMenus="menus"
@success="Refresh()"
:item="nowEditMenu"
></dialogAdd>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import MenuAPI from "@/api/account/menu";
import quickApi from "@/api/account/quick";
import dialogAdd from "./dialog-add.vue";
const showAdd = ref(false);
const nowEditMenu = ref({});
function editMenu(item) {
nowEditMenu.value = item;
showAdd.value = true;
}
const tableData = reactive({
loading: false,
page: 1,
size: 10,
total: 0,
list: [],
});
function Refresh() {
getList();
}
function getList() {
tableData.loading = true;
quickApi
.getList({
page: tableData.page,
size: tableData.size,
isEdit: true,
})
.then((res) => {
tableData.list = res;
tableData.total = res.length;
})
.finally(() => {
tableData.loading = false;
});
}
const menus = ref([]);
async function getMenus() {
const res = await MenuAPI.getRoutes();
menus.value = res;
}
const menusIdMap = computed(() => {
const map = getMenuMao();
return map;
});
function returnMenuName(item) {
const menu = menusIdMap.value.get(`${item.menuId}`);
return menu.title;
}
function getMenuMao() {
const map = new Map();
for (const menu of menus.value) {
map.set(menu.menuId, menu);
if (menu.children && menu.children.length > 0) {
for (const child of menu.children) {
map.set(child.menuId, child);
}
}
}
return map;
}
function isEnableChange(value, item) {
quickApi
.edit({
...item,
status: value,
})
.then((res) => {
ElMessage({
message: "修改成功",
type: "success",
});
Refresh();
});
}
function deleteHandle(item) {
quickApi.delete([item.id]).then(() => {
Refresh();
});
}
onMounted(async () => {
await getMenus();
getList();
});
</script>
<style lang="scss" scoped>
.container {
padding: 14px;
width: 100%;
.table-box {
border-radius: 8px;
background-color: #fff;
padding: 14px;
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="fast-menu">
<el-dropdown
trigger="click"
placement="top-end"
@visible-change="handleVisibleChange"
append-to-body="true"
>
<img class="img" :src="imgsrc" alt="" />
<!-- <el-icon color="#fff" size="24px">
<Plus v-if="!showMenu" />
<Close v-else />
</el-icon> -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(item, index) in list" :key="index">
Action {{ index + 1 }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const list = ref(new Array(10).fill(1));
const imgsrc = computed(() => {
return showMenu.value
? new URL("/src/assets/images/close.png", import.meta.url).href
: new URL("/src/assets/images/plus.png", import.meta.url).href;
});
const showMenu = ref(false);
function handleVisibleChange(visible: boolean) {
showMenu.value = visible;
}
</script>
<style lang="scss" scoped>
.fast-menu {
position: fixed;
$size: 40px;
right: 40px;
bottom: 40px;
width: $size;
height: $size;
.img {
width: $size;
height: $size;
}
// box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
// background: linear-gradient(135deg, #409eff, #3a84ff);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
.svg-icon {
margin-right: 0;
color: #fff;
}
}
</style>

View File

@@ -27,10 +27,11 @@
<TagsView v-if="showTagsView" />
<AppMain />
<Settings v-if="defaultSettings.showSettings" />
<!-- 返回顶部 -->
<el-backtop target=".app-main">
<!-- <el-backtop target=".app-main">
<svg-icon icon-class="backtop" size="24px" />
</el-backtop>
</el-backtop> -->
</div>
</div>
@@ -42,11 +43,15 @@
</div>
<AppMain />
<Settings v-if="defaultSettings.showSettings" />
<!-- 返回顶部 -->
<el-backtop target=".app-main">
<!-- <el-backtop target=".app-main">
<svg-icon icon-class="backtop" size="24px" />
</el-backtop>
</el-backtop> -->
</div>
<!-- 悬浮球 -->
<FastMenu></FastMenu>
</div>
</template>
@@ -105,6 +110,7 @@ function handleOutsideClick() {
function toggleSidebar() {
appStore.toggleSidebar();
}
function showFastMenu() {}
const route = useRoute();
watch(route, () => {

View File

@@ -33,8 +33,6 @@ export const usePermissionStore = defineStore("permission", () => {
.then((data) => {
if (!isTest) {
const dynamicRoutes = parseDynamicRoutes(data.filter(v => v.type == 0));
console.log('dynamicRoutes')
console.log(dynamicRoutes)
dynamicRoutes.forEach((route) => {
//过滤出可见子节点
let onlyOneChild = null

View File

@@ -0,0 +1,5 @@
<template>
<div>
<FastMenuConfig></FastMenuConfig>
</div>
</template>

View File

@@ -4,22 +4,38 @@
<template v-if="carts.list && carts.list.length >= 1">
<!-- 当前购物车 -->
<div v-for="(item, index) in carts.list" :key="index">
<carts-item :item="item" :useVipPrice="carts.useVipPrice" @changeNumber="changeNumber"
:selCart="carts.selCart" @itemClick="itemClick(item)" @editNote="editNote"></carts-item>
<carts-item
:item="item"
:useVipPrice="carts.useVipPrice"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
</div>
</template>
<!-- 赠菜 -->
<div class="cart-title" v-if="carts.giftList.length > 0"><span>以下是优惠菜品</span></div>
<div v-for="(item, index) in carts.giftList" :key="index">
<carts-item :item="item" @changeNumber="changeNumber" :useVipPrice="carts.useVipPrice" :selCart="carts.selCart"
@itemClick="itemClick(item)" @editNote="editNote"></carts-item>
<carts-item
:item="item"
@changeNumber="changeNumber"
:useVipPrice="carts.useVipPrice"
:selCart="carts.selCart"
@itemClick="itemClick(item)"
@editNote="editNote"
></carts-item>
</div>
<el-empty :image-size="60" v-if="carts.isEmpty" description="点餐列表为空" />
<!-- 打包费 -->
<template v-if="carts.packNum > 0">
<div class="cart-title"><span>打包费</span></div>
<extra-fee name="打包费" :number="carts.packNum" :price="carts.orderCostSummary.packFee"></extra-fee>
<extra-fee
name="打包费"
:number="carts.packNum"
:price="carts.orderCostSummary.packFee"
></extra-fee>
</template>
<!-- 餐位费 -->
<template v-if="perpole >= 1 && carts.dinnerType == 'dine-in'">
@@ -40,9 +56,17 @@
</div>
<div v-for="(detaiItem, index) in item" :key="index">
<carts-item :useVipPrice="carts.useVipPrice" :canChangeNumber="false" isOld :dinerType="dinerType"
:item="detaiItem" @changeNumber="changeNumber" :selCart="carts.selCart" @itemClick="itemClick(detaiItem)"
@editNote="editNote"></carts-item>
<carts-item
:useVipPrice="carts.useVipPrice"
:canChangeNumber="false"
isOld
:dinerType="dinerType"
:item="detaiItem"
@changeNumber="changeNumber"
:selCart="carts.selCart"
@itemClick="itemClick(detaiItem)"
@editNote="editNote"
></carts-item>
</div>
</template>
@@ -50,9 +74,14 @@
</div>
<div class="bottom">
<div class="u-flex u-row-right">
<el-tooltip placement="top" effect="light" popper-class="youhui-tips" :popper-options="{
'background-color': '#fff',
}">
<el-tooltip
placement="top"
effect="light"
popper-class="youhui-tips"
:popper-options="{
'background-color': '#fff',
}"
>
<template #content>
<div class="u-flex color-000 u-font-14 u-row-between">
<span class="font-bold">会员优惠</span>
@@ -74,20 +103,36 @@
</div>
<div class="u-flex u-row-between">
<el-link type="primary">打印制作单</el-link>
<!-- <el-link type="primary">打印制作单</el-link> -->
<div></div>
<div>
<span class="totalNumber">{{ customTruncateToTwoDecimals(carts.totalNumber) }}</span>
<span class="totalPrice">{{ customTruncateToTwoDecimals(carts.payMoney) }}</span>
</div>
</div>
<div class="btn-group" v-if="isXianFuKuan">
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('wx-aiplay')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('wx-aiplay')"
>
微信/支付宝
</el-button>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('cash')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('cash')"
>
现金
</el-button>
<el-button type="primary" size="large" :disabled="!disabledMorePay" @click="createOrder('more-pay')">
<el-button
type="primary"
size="large"
:disabled="!disabledMorePay"
@click="createOrder('more-pay')"
>
更多支付
</el-button>
</div>
@@ -100,20 +145,31 @@
<el-button type="primary" size="large" :disabled="disabledMorePay" @click="createOrder('to-pay')">
去结账
</el-button> -->
<el-button type="primary" size="large"
<el-button
type="primary"
size="large"
:disabled="carts.list.length == 0 || carts.oldOrder.detailMap.length == 0"
@click="createOrder('only-create')">
@click="createOrder('only-create')"
>
仅下单
</el-button>
<el-button type="primary" size="large"
<el-button
type="primary"
size="large"
:disabled="carts.list.length == 0 && isEmptyObject(carts.oldOrder.detailMap)"
@click="createOrder('to-pay')">
@click="createOrder('to-pay')"
>
去结账
</el-button>
</template>
<template v-else>
<el-button type="default" size="large" @click="hideOrder()">加菜/返回</el-button>
<el-button type="primary" size="large" :disabled="!carts.isLinkFinshed" @click="createOrder('to-pay')">
<el-button
type="primary"
size="large"
:disabled="!carts.isLinkFinshed"
@click="createOrder('to-pay')"
>
立即支付
</el-button>
</template>
@@ -131,7 +187,7 @@ import { useUserStore } from "@/store/modules/user";
function isEmptyObject(obj) {
// 步骤1排除null和非对象类型
if (obj === null || typeof obj !== 'object') {
if (obj === null || typeof obj !== "object") {
return false;
}
// 步骤2排除数组数组也是对象需单独判断
@@ -313,4 +369,4 @@ defineExpose({
margin-left: 10px;
}
}
</style>
</style>