commit 429d836a71db6753bf6b639609f2a4b635bd292d Author: mac Date: Mon Jun 8 14:56:10 2026 +0800 feat: 纯瘾大 uni-app 前端 v2.1 R3修复版 - 9页面: 落地页/菜单/确认/订单/聊天 + 员工登录/看板/详情/聊天 - 9组件: Banner/分类Tab/商品卡片/出餐铃/购物车弹层/订单卡片/聊天气泡/配方弹窗/预设标签 - 3 Pinia Store: card/cart/staff - 3 Utils: request/constants/poller - 深色酒吧主题,全局无价格显示 - 响应式双列布局 - 1997号码牌跳转员工登录 - 出餐铃动画 bell-press + bell-ring-wave - 购物车点击遮罩关闭 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a37ff1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.DS_Store +*.log +unpackage/ diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..9903c17 --- /dev/null +++ b/App.vue @@ -0,0 +1,10 @@ + + diff --git a/components/BannerSwiper.vue b/components/BannerSwiper.vue new file mode 100644 index 0000000..6206f3a --- /dev/null +++ b/components/BannerSwiper.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/components/CartBar.vue b/components/CartBar.vue new file mode 100644 index 0000000..4f95b05 --- /dev/null +++ b/components/CartBar.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/components/CartPopup.vue b/components/CartPopup.vue new file mode 100644 index 0000000..d320f58 --- /dev/null +++ b/components/CartPopup.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/components/CategoryTab.vue b/components/CategoryTab.vue new file mode 100644 index 0000000..c0b93c8 --- /dev/null +++ b/components/CategoryTab.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/components/ChatBubble.vue b/components/ChatBubble.vue new file mode 100644 index 0000000..6d1b4ab --- /dev/null +++ b/components/ChatBubble.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/components/OrderCard.vue b/components/OrderCard.vue new file mode 100644 index 0000000..6fb698a --- /dev/null +++ b/components/OrderCard.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/components/PresetTags.vue b/components/PresetTags.vue new file mode 100644 index 0000000..0da91f5 --- /dev/null +++ b/components/PresetTags.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/components/ProductCard.vue b/components/ProductCard.vue new file mode 100644 index 0000000..6455950 --- /dev/null +++ b/components/ProductCard.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/components/RecipeModal.vue b/components/RecipeModal.vue new file mode 100644 index 0000000..9105c62 --- /dev/null +++ b/components/RecipeModal.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..b80458a --- /dev/null +++ b/main.js @@ -0,0 +1,9 @@ +import App from './App' +import { createSSRApp } from 'vue' +import { createPinia } from 'pinia' + +export function createApp() { + const app = createSSRApp(App) + app.use(createPinia()) + return { app } +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..277fc3a --- /dev/null +++ b/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "纯瘾大", + "appid": "__UNI__BARORDER", + "description": "纯瘾大酒吧点单小程序", + "versionName": "1.0.0", + "versionCode": "100", + "transformPx": false, + "app-plus": {}, + "quickapp": {}, + "mp-weixin": { + "appid": "", + "setting": { "urlCheck": true }, + "usingComponents": true + } +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..2e105bd --- /dev/null +++ b/pages.json @@ -0,0 +1,19 @@ +{ + "pages": [ + { "path": "pages/index/index", "style": { "navigationBarTitleText": "纯瘾大" } }, + { "path": "pages/menu/menu", "style": { "navigationBarTitleText": "酒水单" } }, + { "path": "pages/confirm/confirm", "style": { "navigationBarTitleText": "确认下单" } }, + { "path": "pages/orders/orders", "style": { "navigationBarTitleText": "我的订单" } }, + { "path": "pages/chat/chat", "style": { "navigationBarTitleText": "对话" } }, + { "path": "pages/staff/login", "style": { "navigationBarTitleText": "调酒师登录" } }, + { "path": "pages/staff/board", "style": { "navigationBarTitleText": "订单看板" } }, + { "path": "pages/staff/detail", "style": { "navigationBarTitleText": "订单详情" } }, + { "path": "pages/staff/chat", "style": { "navigationBarTitleText": "客人对话" } } + ], + "globalStyle": { + "navigationBarTextStyle": "white", + "navigationBarTitleText": "纯瘾大", + "navigationBarBackgroundColor": "#0D0D0D", + "backgroundColor": "#0D0D0D" + } +} diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue new file mode 100644 index 0000000..06b20e1 --- /dev/null +++ b/pages/chat/chat.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/pages/confirm/confirm.vue b/pages/confirm/confirm.vue new file mode 100644 index 0000000..4adee80 --- /dev/null +++ b/pages/confirm/confirm.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000..2a2391f --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/pages/menu/menu.vue b/pages/menu/menu.vue new file mode 100644 index 0000000..0b8e623 --- /dev/null +++ b/pages/menu/menu.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/pages/orders/orders.vue b/pages/orders/orders.vue new file mode 100644 index 0000000..660eadd --- /dev/null +++ b/pages/orders/orders.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/pages/staff/board.vue b/pages/staff/board.vue new file mode 100644 index 0000000..e501a52 --- /dev/null +++ b/pages/staff/board.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/pages/staff/chat.vue b/pages/staff/chat.vue new file mode 100644 index 0000000..fe59f11 --- /dev/null +++ b/pages/staff/chat.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/pages/staff/detail.vue b/pages/staff/detail.vue new file mode 100644 index 0000000..3be4ac1 --- /dev/null +++ b/pages/staff/detail.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/pages/staff/login.vue b/pages/staff/login.vue new file mode 100644 index 0000000..e3343b9 --- /dev/null +++ b/pages/staff/login.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/stores/card.js b/stores/card.js new file mode 100644 index 0000000..388dc5f --- /dev/null +++ b/stores/card.js @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useCardStore = defineStore('card', () => { + const cardNo = ref(uni.getStorageSync('cardNo') || '') + const dismissed = ref(false) + + function setCardNo(no) { + cardNo.value = no + uni.setStorageSync('cardNo', no) + } + + function clearCard() { + cardNo.value = '' + uni.removeStorageSync('cardNo') + } + + return { cardNo, dismissed, setCardNo, clearCard } +}) diff --git a/stores/cart.js b/stores/cart.js new file mode 100644 index 0000000..03cd68e --- /dev/null +++ b/stores/cart.js @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useCartStore = defineStore('cart', () => { + const items = ref([]) + + const totalCount = computed(() => items.value.reduce((s, i) => s + i.qty, 0)) + const totalAmount = computed(() => items.value.reduce((s, i) => s + i.product.price * i.qty, 0)) + + function addItem(product, qty = 1) { + const exist = items.value.find(i => i.product.id === product.id) + if (exist) { + exist.qty += qty + } else { + items.value.push({ product: { ...product }, qty }) + } + } + + function removeItem(productId) { + items.value = items.value.filter(i => i.product.id !== productId) + } + + function updateQty(productId, qty) { + const item = items.value.find(i => i.product.id === productId) + if (item) { + if (qty <= 0) removeItem(productId) + else item.qty = qty + } + } + + function clearCart() { + items.value = [] + } + + return { items, totalCount, totalAmount, addItem, removeItem, updateQty, clearCart } +}) diff --git a/stores/staff.js b/stores/staff.js new file mode 100644 index 0000000..f8bf9c2 --- /dev/null +++ b/stores/staff.js @@ -0,0 +1,31 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useStaffStore = defineStore('staff', () => { + const token = ref(uni.getStorageSync('staffToken') || '') + const nickname = ref(uni.getStorageSync('staffNickname') || '') + const staffId = ref(uni.getStorageSync('staffId') || '') + const unread = ref(0) + + const isLoggedIn = computed(() => !!token.value) + + function login(t, nick, id) { + token.value = t + nickname.value = nick + staffId.value = id + uni.setStorageSync('staffToken', t) + uni.setStorageSync('staffNickname', nick) + uni.setStorageSync('staffId', id) + } + + function logout() { + token.value = '' + nickname.value = '' + staffId.value = '' + uni.removeStorageSync('staffToken') + uni.removeStorageSync('staffNickname') + uni.removeStorageSync('staffId') + } + + return { token, nickname, staffId, unread, isLoggedIn, login, logout } +}) diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..87a3137 --- /dev/null +++ b/uni.scss @@ -0,0 +1,40 @@ +// 纯瘾大 全局样式 (严格对齐 UI/css/miniapp.css) +page { + --bg: #0D0D0D; + --bg-card: #1A1A1A; + --bg-card-hover: #222222; + --gold: #F5A623; + --gold-light: #FFD700; + --gold-dark: #C8851A; + --orange: #FF6B35; + --orange-light: #FF8C5A; + --red: #FF3B3B; + --blue: #4A90D9; + --blue-light: #6BB5FF; + --text: #E8E4DD; + --text-dim: #999590; + --text-muted: #666260; + --border: #2A2825; + --radius: 32rpx; + --radius-sm: 20rpx; + --radius-xs: 12rpx; + --nav-h: 100rpx; + --safe-b: 68rpx; + background-color: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-size: 28rpx; + min-height: 100vh; + width: 100%; + -webkit-font-smoothing: antialiased; +} + +// 动画 +@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-16rpx)}} +@keyframes pulse-glow{0%,100%{box-shadow:0 0 20rpx rgba(245,166,35,.3)}50%{box-shadow:0 0 40rpx rgba(245,166,35,.6)}} +@keyframes bell-press{0%,100%{transform:scaleY(1)}40%{transform:scaleY(.6) translateY(16rpx)}60%{transform:scaleY(.7) translateY(12rpx)}} +@keyframes bell-ring-wave{0%{transform:scale(.5);opacity:1}100%{transform:scale(2.5);opacity:0}} +@keyframes bounce-in{0%{transform:scale(0);opacity:0}60%{transform:scale(1.15);opacity:1}100%{transform:scale(1)}} +@keyframes fade-in-up{from{opacity:0;transform:translateY(32rpx)}to{opacity:1;transform:translateY(0)}} +@keyframes card-appear-top{0%{transform:translateY(-40rpx) scale(.5);opacity:0}100%{transform:translateY(0) scale(1);opacity:1}} +@keyframes float-bubble{0%{transform:translateY(0) scale(1);opacity:1}100%{transform:translateY(-180rpx) scale(1.3);opacity:0}} diff --git a/utils/constants.js b/utils/constants.js new file mode 100644 index 0000000..0165e9e --- /dev/null +++ b/utils/constants.js @@ -0,0 +1,36 @@ +// 纯瘾大 · 全局常量 + +export const API = { + BASE: 'http://127.0.0.1:8080', + CARD_GENERATE: '/api/card/generate', + CARD_CHECK: '/api/card/check', + MENU_CATEGORIES: '/api/menu/categories', + MENU_PRODUCTS: '/api/menu/products', + MENU_PRODUCT: '/api/menu/product', + ORDER_SUBMIT: '/api/order/submit', + ORDER_LIST: '/api/order/list', + ORDER_REMIND: '/api/order/remind', + MESSAGE_LIST: '/api/message/list', + MESSAGE_SEND: '/api/message/send', + STAFF_LOGIN: '/api/staff/login', + STAFF_ORDERS: '/api/staff/orders', + STAFF_ORDER: '/api/staff/order', + STAFF_CONFIRM: '/api/staff/order/confirm', + STAFF_DONE: '/api/staff/order/done', + STAFF_CANCEL: '/api/staff/order/cancel', +} + +export const ORDER_STATUS = { + 0: { label: '新订单', color: '#FF3B3B', class: 'status-new' }, + 1: { label: '进行中', color: '#4A90D9', class: 'status-confirmed' }, + 2: { label: '已完成', color: '#2ED573', class: 'status-done' }, + 3: { label: '已取消', color: '#747D8C', class: 'status-cancelled' }, +} + +export const CONFIRM_PRESETS = [ + { label: '去冰', icon: '🧊' }, + { label: '多酸', icon: '🍋' }, + { label: '少甜', icon: '🍬' }, + { label: '加烈', icon: '🥃' }, + { label: '快做', icon: '⚡' }, +] diff --git a/utils/poller.js b/utils/poller.js new file mode 100644 index 0000000..21127a4 --- /dev/null +++ b/utils/poller.js @@ -0,0 +1,18 @@ +// 纯瘾大 · 轮询引擎 +const timers = {} + +export function startPoll(name, fn, intervalMs = 5000) { + stopPoll(name) + timers[name] = setInterval(fn, intervalMs) +} + +export function stopPoll(name) { + if (timers[name]) { + clearInterval(timers[name]) + delete timers[name] + } +} + +export function stopAllPolls() { + Object.keys(timers).forEach(k => stopPoll(k)) +} diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..0ce328b --- /dev/null +++ b/utils/request.js @@ -0,0 +1,51 @@ +import { API } from './constants' + +function getBaseURL() { + return API.BASE +} + +function request(method, path, params = {}) { + return new Promise((resolve, reject) => { + const token = uni.getStorageSync('staffToken') || '' + + uni.request({ + url: getBaseURL() + path, + method: method, + data: method === 'GET' ? undefined : params, + dataType: 'json', + header: { + 'Content-Type': 'application/json', + 'Authorization': token ? 'Bearer ' + token : '', + }, + timeout: 10000, + success(res) { + const data = res.data + if (data.code === 0) { + resolve(data.data) + } else if (data.code === -1) { + if (res.statusCode === 401) { + uni.removeStorageSync('staffToken') + uni.reLaunch({ url: '/pages/staff/login' }) + } + uni.showToast({ title: data.msg || '请求失败', icon: 'none' }) + reject(new Error(data.msg)) + } else { + resolve(data) + } + }, + fail(err) { + uni.showToast({ title: '网络错误', icon: 'none' }) + reject(err) + } + }) + }) +} + +export function get(path, params = {}) { + const qs = Object.keys(params).map(k => k + '=' + encodeURIComponent(params[k])).join('&') + return request('GET', qs ? path + '?' + qs : path) +} + +export function post(path, data = {}) { + return request('POST', path, data) +}