Update latest code
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
node_modules/
|
||||
node_modules
|
||||
|
||||
1
.gitignore 2
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
||||
2
dist/build/mp-weixin/app.js
vendored
@ -1 +1 @@
|
||||
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./common/vendor.js"),n=require("./utils/session.js");Math;function r(){setTimeout(()=>{try{if(n.isLoggedIn())return;const r=getCurrentPages(),t=r[r.length-1];if((e=>!(!e||!e.includes("pages/login/Login")&&!e.includes("pages/report-view/reportView")))(t&&t.route?t.route:""))return;e.index.reLaunch({url:"/pages/login/Login"})}catch(r){e.index.reLaunch({url:"/pages/login/Login"})}},50)}const t={onLaunch(){r()},onShow(){r()}};function o(){return{app:e.createSSRApp(t)}}o().app.mount("#app"),exports.createApp=o;
|
||||
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./common/vendor.js"),n=require("./utils/session.js");Math;function t(){setTimeout(()=>{try{if(n.isLoggedIn())return;const t=getCurrentPages();if(0===t.length)return;const r=t[t.length-1];if((e=>!(!e||!e.includes("pages/login/Login")&&!e.includes("pages/report-view/reportView")))(r&&r.route?r.route:""))return;e.index.reLaunch({url:"/pages/login/Login"})}catch(t){e.index.reLaunch({url:"/pages/login/Login"})}},50)}const r={onLaunch(){t()},onShow(){t()}};function o(){return{app:e.createSSRApp(r)}}o().app.mount("#app"),exports.createApp=o;
|
||||
|
||||
2
dist/build/mp-weixin/pages/home/Home.js
vendored
2
dist/build/mp-weixin/pages/home/Home.wxml
vendored
2
dist/build/mp-weixin/pages/home/Home.wxss
vendored
2
dist/build/mp-weixin/pages/mine/Mine.js
vendored
@ -1 +1 @@
|
||||
"use strict";const e=require("../../common/vendor.js"),n=require("../../utils/session.js"),o=require("../../composables/useNavigator.js");Math||(t+r)();const r=()=>"../../components/TabBar.js",t=()=>"../../components/AppIcon.js",s={__name:"Mine",emits:["change-page"],setup(r,{emit:t}){const s=n.getUserSession(),{goPage:a,navigateTo:i}=o.useNavigator(),u=(()=>{var n,o,r,t;let s=((null==(o=(n=e.index).getSystemInfoSync)?void 0:o.call(n).statusBarHeight)||20)+12;const a=null==(t=(r=e.index).getMenuButtonBoundingClientRect)?void 0:t.call(r);return a&&a.top&&a.height&&(s=a.top+a.height+8),`padding-top:${s}px;`})(),c=e.computed(()=>s.name?s.name.slice(0,1).toUpperCase():"?"),p=e.computed(()=>{var e;if(s.avatar)return{};const n=["#ff7c43","#07c160","#8b6914","#e06040","#5090d0"];return{background:n[((null==(e=s.name)?void 0:e.charCodeAt(0))||0)%n.length]}}),f=()=>{e.index.showModal({title:"提示",content:"确定退出登录?",success:e=>{e.confirm&&(n.clearSession(),i("login"))}})};return(n,o)=>e.e({a:e.unref(s).avatar},e.unref(s).avatar?{b:e.unref(s).avatar}:{c:e.t(c.value)},{d:e.s(p.value),e:e.t(e.unref(s).name),f:e.t(e.unref(s).phone),g:e.t("boss"===e.unref(s).role?"店长":"员工"),h:e.s(e.unref(u)),i:e.o(n=>e.unref(i)("profile")),j:"boss"===e.unref(s).role},"boss"===e.unref(s).role?{k:e.p({name:"staff",size:15}),l:e.o(n=>e.unref(i)("staff")),m:e.p({name:"service",size:15}),n:e.o(n=>e.unref(i)("serviceType")),o:e.p({name:"store",size:15}),p:e.o(n=>e.unref(i)("store"))}:{},{q:e.p({name:"profile",size:15}),r:e.o(n=>e.unref(i)("profile")),s:e.p({name:"report",size:15}),t:e.o(n=>e.unref(i)("myReports")),v:e.p({name:"orders",size:15}),w:e.o(n=>e.unref(i)("myOrders")),x:e.p({name:"logout",size:15,color:"#dc2626"}),y:e.o(f),z:e.o(e.unref(a)),A:e.p({"current-page":"mine"})})}},a=e._export_sfc(s,[["__scopeId","data-v-27768e55"]]);wx.createPage(a);
|
||||
"use strict";const e=require("../../common/vendor.js"),r=require("../../utils/session.js"),n=require("../../composables/useNavigator.js");Math||(t+o)();const o=()=>"../../components/TabBar.js",t=()=>"../../components/AppIcon.js",s={__name:"Mine",emits:["change-page"],setup(o,{emit:t}){const s=r.getUserSession(),{goPage:a,navigateTo:u}=n.useNavigator(),i=(()=>{var r,n,o,t;let s=((null==(n=(r=e.index).getSystemInfoSync)?void 0:n.call(r).statusBarHeight)||20)+12;const a=null==(t=(o=e.index).getMenuButtonBoundingClientRect)?void 0:t.call(o);return a&&a.top&&a.height&&(s=a.top+a.height+8),`padding-top:${s}px;`})(),c=e.computed(()=>s.name?s.name.slice(0,1).toUpperCase():"?"),p=e.computed(()=>{var e;if(s.avatar)return{};const r=["#ff7c43","#07c160","#8b6914","#e06040","#5090d0"];return{background:r[((null==(e=s.name)?void 0:e.charCodeAt(0))||0)%r.length]}}),f=()=>{e.index.showModal({title:"提示",content:"确定退出登录?",success:e=>{e.confirm&&(r.clearSession(),u("login"))}})};return(r,n)=>e.e({a:e.unref(s).avatar},e.unref(s).avatar?{b:e.unref(s).avatar}:{c:e.t(c.value)},{d:e.s(p.value),e:e.t(e.unref(s).name),f:e.t(e.unref(s).phone),g:e.t("boss"===e.unref(s).role?"店长":"customer"===e.unref(s).role?"客户":"员工"),h:e.s(e.unref(i)),i:e.o(r=>e.unref(u)("profile")),j:"boss"===e.unref(s).role},"boss"===e.unref(s).role?{k:e.p({name:"staff",size:15}),l:e.o(r=>e.unref(u)("staff")),m:e.p({name:"service",size:15}),n:e.o(r=>e.unref(u)("serviceType")),o:e.p({name:"store",size:15}),p:e.o(r=>e.unref(u)("store"))}:{},{q:e.p({name:"profile",size:15}),r:e.o(r=>e.unref(u)("profile")),s:"customer"!==e.unref(s).role},"customer"!==e.unref(s).role?{t:e.p({name:"report",size:15}),v:e.o(r=>e.unref(u)("myReports"))}:{},{w:e.p({name:"orders",size:15}),x:e.o(r=>e.unref(u)("myOrders")),y:e.p({name:"logout",size:15,color:"#dc2626"}),z:e.o(f),A:e.o(e.unref(a)),B:e.p({"current-page":"mine"})})}},a=e._export_sfc(s,[["__scopeId","data-v-5428c025"]]);wx.createPage(a);
|
||||
|
||||
2
dist/build/mp-weixin/pages/mine/Mine.wxml
vendored
@ -1 +1 @@
|
||||
<view class="mine-page data-v-27768e55"><view class="user-card data-v-27768e55" style="{{h}}" bindtap="{{i}}"><view class="user-info data-v-27768e55"><view class="user-avatar data-v-27768e55" style="{{d}}"><image wx:if="{{a}}" src="{{b}}" class="avatar-img data-v-27768e55"/><label wx:else class="avatar-initials data-v-27768e55">{{c}}</label></view><view class="user-info-content data-v-27768e55"><view class="user-name data-v-27768e55">{{e}}</view><view class="user-phone data-v-27768e55">{{f}}</view><view class="role-pill data-v-27768e55">{{g}}</view></view></view></view><view wx:if="{{j}}" class="menu-section data-v-27768e55"><view class="menu-card data-v-27768e55"><view class="menu-title data-v-27768e55">店铺管理</view><view class="menu-item data-v-27768e55" bindtap="{{l}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{k}}" class="data-v-27768e55" u-i="27768e55-0" bind:__l="__l" u-p="{{k}}"/></label><label class="menu-text data-v-27768e55">员工管理</label></view><label class="menu-arrow data-v-27768e55">›</label></view><view class="menu-item data-v-27768e55" bindtap="{{n}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{m}}" class="data-v-27768e55" u-i="27768e55-1" bind:__l="__l" u-p="{{m}}"/></label><label class="menu-text data-v-27768e55">服务类型</label></view><label class="menu-arrow data-v-27768e55">›</label></view><view class="menu-item data-v-27768e55" bindtap="{{p}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{o}}" class="data-v-27768e55" u-i="27768e55-2" bind:__l="__l" u-p="{{o}}"/></label><label class="menu-text data-v-27768e55">店铺设置</label></view><label class="menu-arrow data-v-27768e55">›</label></view></view></view><view class="menu-section data-v-27768e55"><view class="menu-card data-v-27768e55"><view class="menu-title data-v-27768e55">个人中心</view><view class="menu-item data-v-27768e55" bindtap="{{r}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{q}}" class="data-v-27768e55" u-i="27768e55-3" bind:__l="__l" u-p="{{q}}"/></label><label class="menu-text data-v-27768e55">个人信息</label></view><label class="menu-arrow data-v-27768e55">›</label></view><view class="menu-item data-v-27768e55" bindtap="{{t}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{s}}" class="data-v-27768e55" u-i="27768e55-4" bind:__l="__l" u-p="{{s}}"/></label><label class="menu-text data-v-27768e55">我的报告</label></view><label class="menu-arrow data-v-27768e55">›</label></view><view class="menu-item data-v-27768e55" bindtap="{{w}}"><view class="menu-left data-v-27768e55"><label class="menu-icon data-v-27768e55"><app-icon wx:if="{{v}}" class="data-v-27768e55" u-i="27768e55-5" bind:__l="__l" u-p="{{v}}"/></label><label class="menu-text data-v-27768e55">我的订单</label></view><label class="menu-arrow data-v-27768e55">›</label></view></view></view><view class="menu-section data-v-27768e55"><button class="logout-btn data-v-27768e55" bindtap="{{y}}"><app-icon wx:if="{{x}}" class="data-v-27768e55" u-i="27768e55-6" bind:__l="__l" u-p="{{x}}"/>退出登录</button></view></view><tab-bar wx:if="{{A}}" class="data-v-27768e55" bindchange="{{z}}" u-i="27768e55-7" bind:__l="__l" u-p="{{A}}"/>
|
||||
<view class="mine-page data-v-5428c025"><view class="user-card data-v-5428c025" style="{{h}}" bindtap="{{i}}"><view class="user-info data-v-5428c025"><view class="user-avatar data-v-5428c025" style="{{d}}"><image wx:if="{{a}}" src="{{b}}" class="avatar-img data-v-5428c025"/><label wx:else class="avatar-initials data-v-5428c025">{{c}}</label></view><view class="user-info-content data-v-5428c025"><view class="user-name data-v-5428c025">{{e}}</view><view class="user-phone data-v-5428c025">{{f}}</view><view class="role-pill data-v-5428c025">{{g}}</view></view></view></view><view wx:if="{{j}}" class="menu-section data-v-5428c025"><view class="menu-card data-v-5428c025"><view class="menu-title data-v-5428c025">店铺管理</view><view class="menu-item data-v-5428c025" bindtap="{{l}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{k}}" class="data-v-5428c025" u-i="5428c025-0" bind:__l="__l" u-p="{{k}}"/></label><label class="menu-text data-v-5428c025">员工管理</label></view><label class="menu-arrow data-v-5428c025">›</label></view><view class="menu-item data-v-5428c025" bindtap="{{n}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{m}}" class="data-v-5428c025" u-i="5428c025-1" bind:__l="__l" u-p="{{m}}"/></label><label class="menu-text data-v-5428c025">服务类型</label></view><label class="menu-arrow data-v-5428c025">›</label></view><view class="menu-item data-v-5428c025" bindtap="{{p}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{o}}" class="data-v-5428c025" u-i="5428c025-2" bind:__l="__l" u-p="{{o}}"/></label><label class="menu-text data-v-5428c025">店铺设置</label></view><label class="menu-arrow data-v-5428c025">›</label></view></view></view><view class="menu-section data-v-5428c025"><view class="menu-card data-v-5428c025"><view class="menu-title data-v-5428c025">个人中心</view><view class="menu-item data-v-5428c025" bindtap="{{r}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{q}}" class="data-v-5428c025" u-i="5428c025-3" bind:__l="__l" u-p="{{q}}"/></label><label class="menu-text data-v-5428c025">个人信息</label></view><label class="menu-arrow data-v-5428c025">›</label></view><view wx:if="{{s}}" class="menu-item data-v-5428c025" bindtap="{{v}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{t}}" class="data-v-5428c025" u-i="5428c025-4" bind:__l="__l" u-p="{{t}}"/></label><label class="menu-text data-v-5428c025">我的报告</label></view><label class="menu-arrow data-v-5428c025">›</label></view><view class="menu-item data-v-5428c025" bindtap="{{x}}"><view class="menu-left data-v-5428c025"><label class="menu-icon data-v-5428c025"><app-icon wx:if="{{w}}" class="data-v-5428c025" u-i="5428c025-5" bind:__l="__l" u-p="{{w}}"/></label><label class="menu-text data-v-5428c025">我的订单</label></view><label class="menu-arrow data-v-5428c025">›</label></view></view></view><view class="menu-section data-v-5428c025"><button class="logout-btn data-v-5428c025" bindtap="{{z}}"><app-icon wx:if="{{y}}" class="data-v-5428c025" u-i="5428c025-6" bind:__l="__l" u-p="{{y}}"/>退出登录</button></view></view><tab-bar wx:if="{{B}}" class="data-v-5428c025" bindchange="{{A}}" u-i="5428c025-7" bind:__l="__l" u-p="{{B}}"/>
|
||||
2
dist/build/mp-weixin/pages/mine/Mine.wxss
vendored
@ -1 +1 @@
|
||||
.mine-page.data-v-27768e55{padding-bottom:140rpx;background:#f5f7fb;min-height:100vh}.user-card.data-v-27768e55{background:linear-gradient(135deg,#22c55e,#16a34a);border-radius:0 0 24px 24px;padding:24px 16px 22px;color:#fff;cursor:pointer;box-shadow:0 10px 24px rgba(34,197,94,.25)}.user-info.data-v-27768e55{display:flex;align-items:center;gap:12px}.user-info-content.data-v-27768e55{flex:1}.user-avatar.data-v-27768e55{width:58px;height:58px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;overflow:hidden;flex-shrink:0;border:2px solid rgba(255,255,255,.3)}.avatar-img.data-v-27768e55{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.avatar-initials.data-v-27768e55{color:#fff;font-size:22px;font-weight:700}.user-name.data-v-27768e55{font-size:21px;font-weight:700;color:#fff;line-height:1.1}.user-phone.data-v-27768e55{font-size:14px;opacity:.92;margin-top:4px}.role-pill.data-v-27768e55{margin-top:8px;display:inline-flex;align-items:center;height:24px;padding:0 10px;border-radius:999px;font-size:12px;font-weight:700;background:rgba(255,255,255,.92);color:#166534}.menu-section.data-v-27768e55{padding:0 14px;margin-top:12px}.menu-card.data-v-27768e55{background:#fff;border:1px solid #e6ecf4;border-radius:16px;box-shadow:0 8px 20px rgba(15,23,42,.05);overflow:hidden}.menu-title.data-v-27768e55{font-size:12px;color:#94a3b8;font-weight:700;padding:12px 14px 8px}.menu-item.data-v-27768e55{min-height:54px;padding:0 14px;display:flex;align-items:center;justify-content:space-between}.menu-item+.menu-item.data-v-27768e55{border-top:1px solid #eef2f7}.menu-left.data-v-27768e55{display:flex;align-items:center;gap:10px}.menu-icon.data-v-27768e55{width:28px;height:28px;border-radius:8px;background:#f1f5f9;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.menu-text.data-v-27768e55{font-size:16px;color:#1f2937;font-weight:600}.menu-arrow.data-v-27768e55{color:#c4cfdd;font-size:18px}.logout-btn.data-v-27768e55{width:100%;height:44px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:12px;border:1px solid #fecaca;background:#fff1f2;color:#dc2626;font-size:15px;font-weight:700}
|
||||
.mine-page.data-v-5428c025{padding-bottom:140rpx;background:#f5f7fb;min-height:100vh}.user-card.data-v-5428c025{background:linear-gradient(135deg,#22c55e,#16a34a);border-radius:0 0 24px 24px;padding:24px 16px 22px;color:#fff;cursor:pointer;box-shadow:0 10px 24px rgba(34,197,94,.25)}.user-info.data-v-5428c025{display:flex;align-items:center;gap:12px}.user-info-content.data-v-5428c025{flex:1}.user-avatar.data-v-5428c025{width:58px;height:58px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;overflow:hidden;flex-shrink:0;border:2px solid rgba(255,255,255,.3)}.avatar-img.data-v-5428c025{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.avatar-initials.data-v-5428c025{color:#fff;font-size:22px;font-weight:700}.user-name.data-v-5428c025{font-size:21px;font-weight:700;color:#fff;line-height:1.1}.user-phone.data-v-5428c025{font-size:14px;opacity:.92;margin-top:4px}.role-pill.data-v-5428c025{margin-top:8px;display:inline-flex;align-items:center;height:24px;padding:0 10px;border-radius:999px;font-size:12px;font-weight:700;background:rgba(255,255,255,.92);color:#166534}.menu-section.data-v-5428c025{padding:0 14px;margin-top:12px}.menu-card.data-v-5428c025{background:#fff;border:1px solid #e6ecf4;border-radius:16px;box-shadow:0 8px 20px rgba(15,23,42,.05);overflow:hidden}.menu-title.data-v-5428c025{font-size:12px;color:#94a3b8;font-weight:700;padding:12px 14px 8px}.menu-item.data-v-5428c025{min-height:54px;padding:0 14px;display:flex;align-items:center;justify-content:space-between}.menu-item+.menu-item.data-v-5428c025{border-top:1px solid #eef2f7}.menu-left.data-v-5428c025{display:flex;align-items:center;gap:10px}.menu-icon.data-v-5428c025{width:28px;height:28px;border-radius:8px;background:#f1f5f9;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.menu-text.data-v-5428c025{font-size:16px;color:#1f2937;font-weight:600}.menu-arrow.data-v-5428c025{color:#c4cfdd;font-size:18px}.logout-btn.data-v-5428c025{width:100%;height:44px;display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:12px;border:1px solid #fecaca;background:#fff1f2;color:#dc2626;font-size:15px;font-weight:700}
|
||||
|
||||
1
dist/dev/mp-weixin/app.js
vendored
@ -26,6 +26,7 @@ function scheduleAuthGuard() {
|
||||
try {
|
||||
if (utils_session.isLoggedIn()) return;
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length === 0) return;
|
||||
const cur = pages[pages.length - 1];
|
||||
const route = cur && cur.route ? cur.route : "";
|
||||
if (isPublicRoute(route)) return;
|
||||
|
||||
89
dist/dev/mp-weixin/pages/home/Home.js
vendored
@ -182,9 +182,32 @@ const _sfc_main = {
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.o(($event) => showNewAppt.value = true),
|
||||
a: common_vendor.unref(userInfo).role === "customer"
|
||||
}, common_vendor.unref(userInfo).role === "customer" ? {
|
||||
b: common_vendor.s(common_vendor.unref(navSafeStyle)),
|
||||
c: common_vendor.f(statusTabs, (tab, k0, i0) => {
|
||||
c: common_vendor.t(common_vendor.unref(storeInfo).name || "宠伴生活馆"),
|
||||
d: newAppt.value.petName,
|
||||
e: common_vendor.o(($event) => newAppt.value.petName = $event.detail.value),
|
||||
f: common_vendor.t(newAppt.value.petType || "请选择"),
|
||||
g: petTypes,
|
||||
h: common_vendor.o((e) => newAppt.value.petType = petTypes[e.detail.value].value),
|
||||
i: common_vendor.t(newAppt.value.serviceType || "请选择"),
|
||||
j: serviceTypes.value,
|
||||
k: common_vendor.o((e) => newAppt.value.serviceType = serviceTypes.value[e.detail.value].value),
|
||||
l: common_vendor.t(appointmentDate.value || "请选择日期"),
|
||||
m: appointmentDate.value,
|
||||
n: common_vendor.o(onAppointmentDateChange),
|
||||
o: common_vendor.t(appointmentTime.value || "请选择时间"),
|
||||
p: appointmentTime.value,
|
||||
q: common_vendor.o(onAppointmentTimeOnlyChange),
|
||||
r: newAppt.value.remark,
|
||||
s: common_vendor.o(($event) => newAppt.value.remark = $event.detail.value),
|
||||
t: creatingAppt.value,
|
||||
v: common_vendor.o(confirmNewAppt)
|
||||
} : common_vendor.e({
|
||||
w: common_vendor.o(($event) => showNewAppt.value = true),
|
||||
x: common_vendor.s(common_vendor.unref(navSafeStyle)),
|
||||
y: common_vendor.f(statusTabs, (tab, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(tab.title),
|
||||
b: tab.name === "new" && newApptCount.value > 0
|
||||
@ -198,9 +221,9 @@ const _sfc_main = {
|
||||
f: common_vendor.o(($event) => currentStatus.value = tab.name, tab.name)
|
||||
});
|
||||
}),
|
||||
d: filteredOrders.value.length > 0
|
||||
z: filteredOrders.value.length > 0
|
||||
}, filteredOrders.value.length > 0 ? {
|
||||
e: common_vendor.f(filteredOrders.value, (item, k0, i0) => {
|
||||
A: common_vendor.f(filteredOrders.value, (item, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.n(`dot-${item.status}`),
|
||||
b: "bd9bb22b-0-" + i0,
|
||||
@ -225,45 +248,45 @@ const _sfc_main = {
|
||||
q: item.id
|
||||
});
|
||||
}),
|
||||
f: common_vendor.p({
|
||||
B: common_vendor.p({
|
||||
name: "profile",
|
||||
size: 14
|
||||
}),
|
||||
g: common_vendor.p({
|
||||
C: common_vendor.p({
|
||||
name: "orders",
|
||||
size: 13
|
||||
})
|
||||
} : {}, {
|
||||
h: filteredOrders.value.length === 0
|
||||
D: filteredOrders.value.length === 0
|
||||
}, filteredOrders.value.length === 0 ? {} : {}, {
|
||||
i: showNewAppt.value
|
||||
E: showNewAppt.value
|
||||
}, showNewAppt.value ? {
|
||||
j: common_vendor.o(($event) => showNewAppt.value = false),
|
||||
k: newAppt.value.petName,
|
||||
l: common_vendor.o(($event) => newAppt.value.petName = $event.detail.value),
|
||||
m: common_vendor.t(newAppt.value.petType || "请选择"),
|
||||
n: petTypes,
|
||||
o: common_vendor.o((e) => newAppt.value.petType = petTypes[e.detail.value].value),
|
||||
p: common_vendor.t(newAppt.value.serviceType || "请选择"),
|
||||
q: serviceTypes.value,
|
||||
r: common_vendor.o((e) => newAppt.value.serviceType = serviceTypes.value[e.detail.value].value),
|
||||
s: common_vendor.t(appointmentDate.value || "请选择日期"),
|
||||
t: appointmentDate.value,
|
||||
v: common_vendor.o(onAppointmentDateChange),
|
||||
w: common_vendor.t(appointmentTime.value || "请选择时间"),
|
||||
x: appointmentTime.value,
|
||||
y: common_vendor.o(onAppointmentTimeOnlyChange),
|
||||
z: newAppt.value.remark,
|
||||
A: common_vendor.o(($event) => newAppt.value.remark = $event.detail.value),
|
||||
B: common_vendor.o(($event) => showNewAppt.value = false),
|
||||
C: creatingAppt.value,
|
||||
D: common_vendor.o(confirmNewAppt),
|
||||
E: common_vendor.o(() => {
|
||||
F: common_vendor.o(($event) => showNewAppt.value = false),
|
||||
G: newAppt.value.petName,
|
||||
H: common_vendor.o(($event) => newAppt.value.petName = $event.detail.value),
|
||||
I: common_vendor.t(newAppt.value.petType || "请选择"),
|
||||
J: petTypes,
|
||||
K: common_vendor.o((e) => newAppt.value.petType = petTypes[e.detail.value].value),
|
||||
L: common_vendor.t(newAppt.value.serviceType || "请选择"),
|
||||
M: serviceTypes.value,
|
||||
N: common_vendor.o((e) => newAppt.value.serviceType = serviceTypes.value[e.detail.value].value),
|
||||
O: common_vendor.t(appointmentDate.value || "请选择日期"),
|
||||
P: appointmentDate.value,
|
||||
Q: common_vendor.o(onAppointmentDateChange),
|
||||
R: common_vendor.t(appointmentTime.value || "请选择时间"),
|
||||
S: appointmentTime.value,
|
||||
T: common_vendor.o(onAppointmentTimeOnlyChange),
|
||||
U: newAppt.value.remark,
|
||||
V: common_vendor.o(($event) => newAppt.value.remark = $event.detail.value),
|
||||
W: common_vendor.o(($event) => showNewAppt.value = false),
|
||||
X: creatingAppt.value,
|
||||
Y: common_vendor.o(confirmNewAppt),
|
||||
Z: common_vendor.o(() => {
|
||||
}),
|
||||
F: common_vendor.o(($event) => showNewAppt.value = false)
|
||||
} : {}, {
|
||||
G: common_vendor.o(common_vendor.unref(goPage)),
|
||||
H: common_vendor.p({
|
||||
aa: common_vendor.o(($event) => showNewAppt.value = false)
|
||||
} : {}), {
|
||||
ab: common_vendor.o(common_vendor.unref(goPage)),
|
||||
ac: common_vendor.p({
|
||||
["current-page"]: "home"
|
||||
})
|
||||
});
|
||||
|
||||
2
dist/dev/mp-weixin/pages/home/Home.wxml
vendored
20
dist/dev/mp-weixin/pages/home/Home.wxss
vendored
@ -25,6 +25,26 @@
|
||||
}
|
||||
.list-content.data-v-bd9bb22b { padding: 0;
|
||||
}
|
||||
.c-header.data-v-bd9bb22b {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
padding: 20px 20px 40px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
border-radius: 0 0 24px 24px;
|
||||
}
|
||||
.c-title.data-v-bd9bb22b { font-size: 24px; font-weight: 700; margin-bottom: 8px;
|
||||
}
|
||||
.c-sub.data-v-bd9bb22b { font-size: 14px; opacity: 0.8;
|
||||
}
|
||||
.c-booking-card.data-v-bd9bb22b {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
margin: -20px 16px 0;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.c-booking-title.data-v-bd9bb22b { font-size: 18px; font-weight: 600; color: #333; margin-bottom: 20px; text-align: center;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.btn-primary.data-v-bd9bb22b {
|
||||
|
||||
21
dist/dev/mp-weixin/pages/mine/Mine.js
vendored
@ -57,7 +57,7 @@ const _sfc_main = {
|
||||
d: common_vendor.s(avatarStyle.value),
|
||||
e: common_vendor.t(common_vendor.unref(userInfo).name),
|
||||
f: common_vendor.t(common_vendor.unref(userInfo).phone),
|
||||
g: common_vendor.t(common_vendor.unref(userInfo).role === "boss" ? "店长" : "员工"),
|
||||
g: common_vendor.t(common_vendor.unref(userInfo).role === "boss" ? "店长" : common_vendor.unref(userInfo).role === "customer" ? "客户" : "员工"),
|
||||
h: common_vendor.s(common_vendor.unref(userCardSafeStyle)),
|
||||
i: common_vendor.o(($event) => common_vendor.unref(navigateTo)("profile")),
|
||||
j: common_vendor.unref(userInfo).role === "boss"
|
||||
@ -83,24 +83,27 @@ const _sfc_main = {
|
||||
size: 15
|
||||
}),
|
||||
r: common_vendor.o(($event) => common_vendor.unref(navigateTo)("profile")),
|
||||
s: common_vendor.p({
|
||||
s: common_vendor.unref(userInfo).role !== "customer"
|
||||
}, common_vendor.unref(userInfo).role !== "customer" ? {
|
||||
t: common_vendor.p({
|
||||
name: "report",
|
||||
size: 15
|
||||
}),
|
||||
t: common_vendor.o(($event) => common_vendor.unref(navigateTo)("myReports")),
|
||||
v: common_vendor.p({
|
||||
v: common_vendor.o(($event) => common_vendor.unref(navigateTo)("myReports"))
|
||||
} : {}, {
|
||||
w: common_vendor.p({
|
||||
name: "orders",
|
||||
size: 15
|
||||
}),
|
||||
w: common_vendor.o(($event) => common_vendor.unref(navigateTo)("myOrders")),
|
||||
x: common_vendor.p({
|
||||
x: common_vendor.o(($event) => common_vendor.unref(navigateTo)("myOrders")),
|
||||
y: common_vendor.p({
|
||||
name: "logout",
|
||||
size: 15,
|
||||
color: "#dc2626"
|
||||
}),
|
||||
y: common_vendor.o(logout),
|
||||
z: common_vendor.o(common_vendor.unref(goPage)),
|
||||
A: common_vendor.p({
|
||||
z: common_vendor.o(logout),
|
||||
A: common_vendor.o(common_vendor.unref(goPage)),
|
||||
B: common_vendor.p({
|
||||
["current-page"]: "mine"
|
||||
})
|
||||
});
|
||||
|
||||
2
dist/dev/mp-weixin/pages/mine/Mine.wxml
vendored
@ -1 +1 @@
|
||||
<view class="mine-page data-v-02264ef7"><view class="user-card data-v-02264ef7" style="{{h}}" bindtap="{{i}}"><view class="user-info data-v-02264ef7"><view class="user-avatar data-v-02264ef7" style="{{d}}"><image wx:if="{{a}}" src="{{b}}" class="avatar-img data-v-02264ef7"/><label wx:else class="avatar-initials data-v-02264ef7">{{c}}</label></view><view class="user-info-content data-v-02264ef7"><view class="user-name data-v-02264ef7">{{e}}</view><view class="user-phone data-v-02264ef7">{{f}}</view><view class="role-pill data-v-02264ef7">{{g}}</view></view></view></view><view wx:if="{{j}}" class="menu-section data-v-02264ef7"><view class="menu-card data-v-02264ef7"><view class="menu-title data-v-02264ef7">店铺管理</view><view class="menu-item data-v-02264ef7" bindtap="{{l}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{k}}" class="data-v-02264ef7" u-i="02264ef7-0" bind:__l="__l" u-p="{{k}}"/></label><label class="menu-text data-v-02264ef7">员工管理</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{n}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{m}}" class="data-v-02264ef7" u-i="02264ef7-1" bind:__l="__l" u-p="{{m}}"/></label><label class="menu-text data-v-02264ef7">服务类型</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{p}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{o}}" class="data-v-02264ef7" u-i="02264ef7-2" bind:__l="__l" u-p="{{o}}"/></label><label class="menu-text data-v-02264ef7">店铺设置</label></view><label class="menu-arrow data-v-02264ef7">›</label></view></view></view><view class="menu-section data-v-02264ef7"><view class="menu-card data-v-02264ef7"><view class="menu-title data-v-02264ef7">个人中心</view><view class="menu-item data-v-02264ef7" bindtap="{{r}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{q}}" class="data-v-02264ef7" u-i="02264ef7-3" bind:__l="__l" u-p="{{q}}"/></label><label class="menu-text data-v-02264ef7">个人信息</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{t}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{s}}" class="data-v-02264ef7" u-i="02264ef7-4" bind:__l="__l" u-p="{{s}}"/></label><label class="menu-text data-v-02264ef7">我的报告</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{w}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{v}}" class="data-v-02264ef7" u-i="02264ef7-5" bind:__l="__l" u-p="{{v}}"/></label><label class="menu-text data-v-02264ef7">我的订单</label></view><label class="menu-arrow data-v-02264ef7">›</label></view></view></view><view class="menu-section data-v-02264ef7"><button class="logout-btn data-v-02264ef7" bindtap="{{y}}"><app-icon wx:if="{{x}}" class="data-v-02264ef7" u-i="02264ef7-6" bind:__l="__l" u-p="{{x}}"/>退出登录</button></view></view><tab-bar wx:if="{{A}}" class="data-v-02264ef7" bindchange="{{z}}" u-i="02264ef7-7" bind:__l="__l" u-p="{{A}}"/>
|
||||
<view class="mine-page data-v-02264ef7"><view class="user-card data-v-02264ef7" style="{{h}}" bindtap="{{i}}"><view class="user-info data-v-02264ef7"><view class="user-avatar data-v-02264ef7" style="{{d}}"><image wx:if="{{a}}" src="{{b}}" class="avatar-img data-v-02264ef7"/><label wx:else class="avatar-initials data-v-02264ef7">{{c}}</label></view><view class="user-info-content data-v-02264ef7"><view class="user-name data-v-02264ef7">{{e}}</view><view class="user-phone data-v-02264ef7">{{f}}</view><view class="role-pill data-v-02264ef7">{{g}}</view></view></view></view><view wx:if="{{j}}" class="menu-section data-v-02264ef7"><view class="menu-card data-v-02264ef7"><view class="menu-title data-v-02264ef7">店铺管理</view><view class="menu-item data-v-02264ef7" bindtap="{{l}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{k}}" class="data-v-02264ef7" u-i="02264ef7-0" bind:__l="__l" u-p="{{k}}"/></label><label class="menu-text data-v-02264ef7">员工管理</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{n}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{m}}" class="data-v-02264ef7" u-i="02264ef7-1" bind:__l="__l" u-p="{{m}}"/></label><label class="menu-text data-v-02264ef7">服务类型</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{p}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{o}}" class="data-v-02264ef7" u-i="02264ef7-2" bind:__l="__l" u-p="{{o}}"/></label><label class="menu-text data-v-02264ef7">店铺设置</label></view><label class="menu-arrow data-v-02264ef7">›</label></view></view></view><view class="menu-section data-v-02264ef7"><view class="menu-card data-v-02264ef7"><view class="menu-title data-v-02264ef7">个人中心</view><view class="menu-item data-v-02264ef7" bindtap="{{r}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{q}}" class="data-v-02264ef7" u-i="02264ef7-3" bind:__l="__l" u-p="{{q}}"/></label><label class="menu-text data-v-02264ef7">个人信息</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view wx:if="{{s}}" class="menu-item data-v-02264ef7" bindtap="{{v}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{t}}" class="data-v-02264ef7" u-i="02264ef7-4" bind:__l="__l" u-p="{{t}}"/></label><label class="menu-text data-v-02264ef7">我的报告</label></view><label class="menu-arrow data-v-02264ef7">›</label></view><view class="menu-item data-v-02264ef7" bindtap="{{x}}"><view class="menu-left data-v-02264ef7"><label class="menu-icon data-v-02264ef7"><app-icon wx:if="{{w}}" class="data-v-02264ef7" u-i="02264ef7-5" bind:__l="__l" u-p="{{w}}"/></label><label class="menu-text data-v-02264ef7">我的订单</label></view><label class="menu-arrow data-v-02264ef7">›</label></view></view></view><view class="menu-section data-v-02264ef7"><button class="logout-btn data-v-02264ef7" bindtap="{{z}}"><app-icon wx:if="{{y}}" class="data-v-02264ef7" u-i="02264ef7-6" bind:__l="__l" u-p="{{y}}"/>退出登录</button></view></view><tab-bar wx:if="{{B}}" class="data-v-02264ef7" bindchange="{{A}}" u-i="02264ef7-7" bind:__l="__l" u-p="{{B}}"/>
|
||||
9
main.js
@ -1,9 +0,0 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
@ -1,539 +0,0 @@
|
||||
<template>
|
||||
<view class="home-page">
|
||||
<!-- C端视角:微官网与预约 -->
|
||||
<view v-if="userInfo.role === 'customer'" class="c-home">
|
||||
<view class="c-header">
|
||||
<view class="c-title">{{ storeInfo.name || '宠伴生活馆' }}</view>
|
||||
<view class="c-sub">宠物服务,让爱更专业</view>
|
||||
</view>
|
||||
|
||||
<view class="c-booking-card">
|
||||
<view class="c-booking-title">预约服务</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">宠物名字</text>
|
||||
<input class="form-input" v-model="newAppt.petName" placeholder="请输入宠物名字" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">宠物类型</text>
|
||||
<picker :value="petTypeIndex" :range="petTypes" @change="onPetTypeChange">
|
||||
<view class="picker-val">{{ newAppt.petType || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">服务类型</text>
|
||||
<picker :value="serviceTypeIndex" :range="serviceTypeOptions" :range-key="'name'" @change="onServiceTypeChange">
|
||||
<view class="picker-val">{{ newAppt.serviceType || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">预约时间</text>
|
||||
<picker mode="datetime" :value="newAppt.appointmentTime" @change="onDateChange">
|
||||
<view class="picker-val">{{ newAppt.appointmentTime || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">备注</text>
|
||||
<input class="form-input" v-model="newAppt.remark" placeholder="可选" />
|
||||
</view>
|
||||
<button class="btn-primary-full" :loading="creatingAppt" @click="onNewApptConfirm">提交预约</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- B端视角:订单管理 -->
|
||||
<view v-else class="b-home">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<view class="nav-title">宠伴生活馆</view>
|
||||
<view class="nav-right">
|
||||
<view class="new-appt-btn" @click="showNewAppt = true">+ 新建预约</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['tab-item', { active: currentStatus === tab.key }]"
|
||||
@click="currentStatus = tab.key"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="list-content">
|
||||
<view v-if="filteredOrders.length > 0" class="timeline">
|
||||
<view v-for="(item, index) in filteredOrders" :key="item.id" class="timeline-item">
|
||||
<!-- 时间轴线+圆点 -->
|
||||
<view class="timeline-dot" :class="'dot-' + item.status">
|
||||
<view class="dot-inner"></view>
|
||||
</view>
|
||||
<view v-if="index < filteredOrders.length - 1" class="timeline-line"></view>
|
||||
|
||||
<!-- 卡片 -->
|
||||
<view class="order-card" :class="'card-' + item.status">
|
||||
<view class="card-header">
|
||||
<view class="pet-info">
|
||||
<text class="pet-emoji">{{ petEmoji(item.petType) }}</text>
|
||||
<text class="pet-name">{{ item.petName }}</text>
|
||||
<text class="service-tag" :class="'tag-' + item.status">{{ item.serviceType }}</text>
|
||||
</view>
|
||||
<view class="status-tag" :class="'status-' + item.status">{{ item.statusText }}</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="card-time">📅 {{ item.time }}</text>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<view v-if="item.status === 'new'" class="action-btns">
|
||||
<view class="btn-sm btn-primary-sm" @click="startService(item)">开始服务</view>
|
||||
<view class="btn-sm btn-default-sm" @click="cancelService(item)">取消</view>
|
||||
</view>
|
||||
<view v-else-if="item.status === 'doing'">
|
||||
<view class="btn-sm btn-outline-sm" @click="goReport(item)">填写报告</view>
|
||||
</view>
|
||||
<text v-else class="done-label">{{ item.status === 'cancel' ? '已取消' : '已完成' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-wrap">
|
||||
<text class="empty-icon">📭</text>
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新建预约弹窗 -->
|
||||
<view v-if="showNewAppt" class="dialog-mask" @click="showNewAppt = false">
|
||||
<view class="dialog-content" @click.stop>
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">新建预约</text>
|
||||
<text class="dialog-close" @click="showNewAppt = false">✕</text>
|
||||
</view>
|
||||
<view class="dialog-body">
|
||||
<view class="form-item">
|
||||
<text class="form-label">宠物名字</text>
|
||||
<input class="form-input" v-model="newAppt.petName" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">宠物类型</text>
|
||||
<picker :value="petTypeIndex" :range="petTypes" @change="onPetTypeChange">
|
||||
<view class="picker-val">{{ newAppt.petType || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">服务类型</text>
|
||||
<picker :value="serviceTypeIndex" :range="serviceTypeOptions" :range-key="'name'" @change="onServiceTypeChange">
|
||||
<view class="picker-val">{{ newAppt.serviceType || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">预约时间</text>
|
||||
<picker mode="datetime" :value="newAppt.appointmentTime" @change="onDateChange">
|
||||
<view class="picker-val">{{ newAppt.appointmentTime || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">备注</text>
|
||||
<input class="form-input" v-model="newAppt.remark" placeholder="可选" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="dialog-footer">
|
||||
<view class="btn-cancel" @click="showNewAppt = false">取消</view>
|
||||
<view class="btn-confirm" :class="{ loading: creatingAppt }" @click="onNewApptConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import {
|
||||
getAppointmentList, createAppointment, startAppointment, cancelAppointment, getServiceTypeList
|
||||
} from '../../utils/api.js'
|
||||
|
||||
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
const currentUserId = userInfo.id
|
||||
|
||||
const currentStatus = ref('new')
|
||||
const orders = ref([])
|
||||
const serviceTypes = ref([])
|
||||
const showNewAppt = ref(false)
|
||||
const creatingAppt = ref(false)
|
||||
const newAppt = ref({ petName: '', petType: '', serviceType: '', appointmentTime: '', remark: '' })
|
||||
const petTypeIndex = ref(0)
|
||||
const serviceTypeIndex = ref(0)
|
||||
|
||||
const petTypes = ['猫', '狗', '其他']
|
||||
|
||||
const tabs = [
|
||||
{ key: 'new', label: '待确认' },
|
||||
{ key: 'doing', label: '进行中' },
|
||||
{ key: 'done', label: '已完成' }
|
||||
]
|
||||
|
||||
const serviceTypeOptions = computed(() => serviceTypes.value.map(s => ({ name: `${serviceEmoji(s.name)} ${s.name}`, id: s.id })))
|
||||
|
||||
const filteredOrders = computed(() => orders.value.filter(o => {
|
||||
if (currentStatus.value === 'new') return o.status === 'new'
|
||||
if (currentStatus.value === 'doing') return o.status === 'doing'
|
||||
if (currentStatus.value === 'done') return o.status === 'done' || o.status === 'cancel'
|
||||
return true
|
||||
}))
|
||||
|
||||
const petEmoji = (petType) => {
|
||||
const map = { '猫': '🐱', '狗': '🐶' }
|
||||
return map[petType] || '🐾'
|
||||
}
|
||||
|
||||
const serviceEmoji = (name) => {
|
||||
if (!name) return ''
|
||||
if (name.includes('洗澡') && name.includes('美容')) return '🛁✂️'
|
||||
if (name.includes('洗澡')) return '🛁'
|
||||
if (name.includes('美容')) return '✂️'
|
||||
if (name.includes('剪指甲')) return '💅'
|
||||
if (name.includes('驱虫')) return '🐛'
|
||||
return '✨'
|
||||
}
|
||||
|
||||
const statusTagClass = (status) => {
|
||||
const map = { new: 'warning', doing: 'primary', done: 'success', cancel: 'default' }
|
||||
return map[status] || 'default'
|
||||
}
|
||||
|
||||
const fetchAppointments = async () => {
|
||||
if (!currentUserId) return
|
||||
const res = await getAppointmentList(currentUserId)
|
||||
if (res.code === 200) {
|
||||
orders.value = res.data.map(appt => ({
|
||||
id: appt.id,
|
||||
title: appt.serviceType || '洗澡美容预约',
|
||||
desc: `${appt.petType || ''} - ${appt.petName || ''}`,
|
||||
time: appt.appointmentTime ? new Date(appt.appointmentTime).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '',
|
||||
status: appt.status || 'new',
|
||||
statusText: { new: '待确认', doing: '服务中', done: '已完成', cancel: '已取消' }[appt.status] || '待确认',
|
||||
petName: appt.petName,
|
||||
petType: appt.petType,
|
||||
serviceType: appt.serviceType,
|
||||
appointmentTime: appt.appointmentTime
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const loadServiceTypes = async () => {
|
||||
const sid = storeInfo.id || 1
|
||||
const res = await getServiceTypeList(sid)
|
||||
if (res.code === 200) serviceTypes.value = res.data
|
||||
}
|
||||
|
||||
const onPetTypeChange = (e) => {
|
||||
petTypeIndex.value = e.detail.value
|
||||
newAppt.value.petType = petTypes[e.detail.value]
|
||||
}
|
||||
|
||||
const onServiceTypeChange = (e) => {
|
||||
serviceTypeIndex.value = e.detail.value
|
||||
newAppt.value.serviceType = serviceTypes.value[e.detail.value]?.name || ''
|
||||
}
|
||||
|
||||
const onDateChange = (e) => {
|
||||
newAppt.value.appointmentTime = e.detail.value
|
||||
}
|
||||
|
||||
const startService = async (item) => {
|
||||
const res = await startAppointment(item.id, userInfo.id)
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '已开始服务', icon: 'success' })
|
||||
fetchAppointments()
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const cancelService = async (item) => {
|
||||
uni.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定取消该预约吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const r = await cancelAppointment(item.id)
|
||||
if (r.code === 200) {
|
||||
uni.showToast({ title: '已取消', icon: 'success' })
|
||||
fetchAppointments()
|
||||
} else {
|
||||
uni.showToast({ title: r.message || '操作失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goReport = (item) => {
|
||||
uni.setStorageSync('petstore_report_prefill', JSON.stringify({
|
||||
appointmentId: item.id,
|
||||
petName: item.petName,
|
||||
serviceType: item.serviceType,
|
||||
appointmentTime: item.appointmentTime
|
||||
}))
|
||||
uni.switchTab({ url: '/pages/report/report' })
|
||||
}
|
||||
|
||||
const onNewApptConfirm = async () => {
|
||||
const a = newAppt.value
|
||||
if (!a.petName) { uni.showToast({ title: '请输入宠物名字', icon: 'none' }); return }
|
||||
if (!a.petType) { uni.showToast({ title: '请选择宠物类型', icon: 'none' }); return }
|
||||
if (!a.serviceType) { uni.showToast({ title: '请选择服务类型', icon: 'none' }); return }
|
||||
if (!a.appointmentTime) { uni.showToast({ title: '请选择预约时间', icon: 'none' }); return }
|
||||
creatingAppt.value = true
|
||||
const res = await createAppointment({ ...a, storeId: storeInfo.id || 1, userId: userInfo.id })
|
||||
creatingAppt.value = false
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '预约创建成功', icon: 'success' })
|
||||
newAppt.value = { petName: '', petType: '', serviceType: '', appointmentTime: '', remark: '' }
|
||||
showNewAppt.value = false
|
||||
fetchAppointments()
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '创建失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchAppointments()
|
||||
loadServiceTypes()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
|
||||
|
||||
/* C端样式 */
|
||||
.c-home { padding-bottom: 20px; }
|
||||
.c-header {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
padding: 60px 20px 40px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
border-radius: 0 0 24px 24px;
|
||||
}
|
||||
.c-title { font-size: 24px; font-weight: 700; margin-bottom: 8px; }
|
||||
.c-sub { font-size: 14px; opacity: 0.8; }
|
||||
.c-booking-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
margin: -20px 16px 0;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.c-booking-title { font-size: 18px; font-weight: 600; color: #333; margin-bottom: 20px; text-align: center; }
|
||||
.btn-primary-full {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
/* B端样式 */
|
||||
.nav-bar {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.nav-title { color: #fff; font-size: 17px; font-weight: 600; }
|
||||
.new-appt-btn {
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.tab-item.active { color: #07c160; border-bottom-color: #07c160; font-weight: 600; }
|
||||
|
||||
.list-content { padding: 12px 16px 0; }
|
||||
|
||||
.timeline { padding: 4px 0; }
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
}
|
||||
.timeline-dot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
flex-shrink: 0;
|
||||
padding-top: 14px;
|
||||
}
|
||||
.dot-inner {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
background: #fff;
|
||||
z-index: 1;
|
||||
}
|
||||
.dot-new .dot-inner { border-color: #ff9f00; }
|
||||
.dot-doing .dot-inner { border-color: #07c160; }
|
||||
.dot-done .dot-inner { border-color: #c0c0c0; }
|
||||
.dot-cancel .dot-inner { border-color: #d0d0d0; }
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 28px;
|
||||
bottom: -4px;
|
||||
width: 2px;
|
||||
background: #e8e0d8;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
margin-left: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||
border-left: 4px solid;
|
||||
}
|
||||
.card-new { border-left-color: #ff9f00; }
|
||||
.card-doing { border-left-color: #07c160; }
|
||||
.card-done { border-left-color: #c0c0c0; }
|
||||
.card-cancel { border-left-color: #d8d8d8; opacity: 0.7; }
|
||||
|
||||
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.pet-info { display: flex; align-items: center; gap: 8px; }
|
||||
.pet-emoji { font-size: 20px; }
|
||||
.pet-name { font-weight: 600; color: #333; font-size: 15px; }
|
||||
.service-tag {
|
||||
font-size: 12px;
|
||||
background: #f0f9f4;
|
||||
color: #52c41a;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tag-new { background: #fff3e8; color: #ff9f00; }
|
||||
.tag-doing { background: #e8f7ef; color: #07c160; }
|
||||
.tag-done { background: #f0f0f0; color: #888; }
|
||||
|
||||
.status-tag {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.status-new { background: #fff3e8; color: #ff9f00; }
|
||||
.status-doing { background: #e8f7ef; color: #07c160; }
|
||||
.status-done { background: #f0f0f0; color: #888; }
|
||||
.status-cancel { background: #f5f5f5; color: #999; }
|
||||
|
||||
.card-body { margin-bottom: 10px; }
|
||||
.card-time { font-size: 13px; color: #999; }
|
||||
|
||||
.card-footer { display: flex; justify-content: flex-end; align-items: center; }
|
||||
.action-btns { display: flex; gap: 8px; }
|
||||
.done-label { font-size: 13px; color: #999; }
|
||||
|
||||
.btn-sm {
|
||||
font-size: 12px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-primary-sm { background: #07c160; color: #fff; border: 1px solid #07c160; }
|
||||
.btn-default-sm { background: #fff; color: #666; border: 1px solid #ddd; }
|
||||
.btn-outline-sm { background: #fff; color: #07c160; border: 1px solid #07c160; }
|
||||
|
||||
.empty-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 0; }
|
||||
.empty-icon { font-size: 48px; }
|
||||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||||
|
||||
/* 弹窗 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
.dialog-content {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
background: #fff;
|
||||
border-radius: 16px 16px 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.dialog-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.dialog-close { font-size: 18px; color: #999; }
|
||||
.dialog-body { padding: 16px 20px; max-height: 55vh; overflow-y: auto; }
|
||||
.dialog-footer { display: flex; border-top: 1px solid #eee; }
|
||||
.btn-cancel, .btn-confirm {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 14px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.btn-cancel { color: #666; border-right: 1px solid #eee; }
|
||||
.btn-confirm { color: #07c160; font-weight: 600; }
|
||||
|
||||
.form-item { margin-bottom: 16px; }
|
||||
.form-label { font-size: 13px; color: #999; display: block; margin-bottom: 6px; }
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.picker-val {
|
||||
height: 40px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
color: #333;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@ -1,348 +0,0 @@
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<!-- Logo区 -->
|
||||
<view class="login-logo">
|
||||
<view class="login-logo-text">宠伴生活馆</view>
|
||||
<view class="login-logo-sub">宠物服务,让爱更专业</view>
|
||||
</view>
|
||||
|
||||
<!-- 老板注册成功 -->
|
||||
<view v-if="bossRegistered" class="form-card">
|
||||
<view class="success-icon">🎉</view>
|
||||
<view class="success-title">入驻成功</view>
|
||||
<view class="success-sub">欢迎加入宠伴生活馆</view>
|
||||
<view class="success-info">
|
||||
<view class="info-row"><span class="label">店铺名称</span><span class="value">{{ regResult.store.name }}</span></view>
|
||||
<view class="info-row"><span class="label">您的账号</span><span class="value">{{ regResult.user.phone }}</span></view>
|
||||
<view class="info-row"><span class="label">初始密码</span><span class="value">{{ regResult.user.password }}</span></view>
|
||||
<view class="info-row"><span class="label">员工邀请码</span><span class="value">{{ regResult.store.inviteCode }}</span></view>
|
||||
</view>
|
||||
<button class="btn-primary-full" @click="copyCode">📋 复制员工邀请码</button>
|
||||
<button class="btn-default-full" @click="goLogin" style="margin-top:12px;">前往登录 →</button>
|
||||
</view>
|
||||
|
||||
<!-- 员工注册成功 -->
|
||||
<view v-else-if="staffRegistered" class="form-card">
|
||||
<view class="success-icon">✅</view>
|
||||
<view class="success-title">注册成功</view>
|
||||
<view class="success-sub">您已成功加入 {{ regResult.store?.name }} 团队</view>
|
||||
<view class="success-info">
|
||||
<view class="info-row"><span class="label">所属店铺</span><span class="value">{{ regResult.store?.name }}</span></view>
|
||||
<view class="info-row"><span class="label">您的账号</span><span class="value">{{ regResult.user?.phone }}</span></view>
|
||||
</view>
|
||||
<button class="btn-primary-full" @click="goLogin">前往登录 →</button>
|
||||
</view>
|
||||
|
||||
<!-- 登录/注册表单 -->
|
||||
<view v-else class="form-card">
|
||||
<!-- Tab切换 -->
|
||||
<view class="tab-header">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['tab-item', { active: activeTab === tab.key }]"
|
||||
@click="activeTab = tab.key"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户登录 -->
|
||||
<view v-if="activeTab === 'staff'" class="tab-content">
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="loginForm.phone" type="tel" placeholder="请输入手机号" maxlength="11" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="code-input-wrap">
|
||||
<input class="form-input" v-model="loginForm.code" type="digit" placeholder="短信验证码" maxlength="6" />
|
||||
<view class="code-btn" :class="{ disabled: smsCountdown > 0 }" @click="handleSendSms">
|
||||
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<button class="btn-primary-full" :loading="loginLoading" @click="handleLogin">登录</button>
|
||||
<view class="login-divider">其他登录方式</view>
|
||||
<button class="btn-default-full" @click="handleWechatLogin">
|
||||
<text class="wechat-icon">📱</text> 微信授权登录
|
||||
</button>
|
||||
<view class="links">
|
||||
<text class="link" @click="activeTab = 'staff-reg'">员工注册</text>
|
||||
<text class="link" @click="activeTab = 'boss-reg'">商家入驻</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 老板登录 -->
|
||||
<view v-if="activeTab === 'boss'" class="tab-content">
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="loginForm.phone" type="tel" placeholder="请输入手机号" maxlength="11" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<view class="code-input-wrap">
|
||||
<input class="form-input" v-model="loginForm.code" type="digit" placeholder="短信验证码" maxlength="6" />
|
||||
<view class="code-btn" :class="{ disabled: smsCountdown > 0 }" @click="handleSendSms">
|
||||
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<button class="btn-primary-full" :loading="loginLoading" @click="handleLogin">登录</button>
|
||||
<view class="links" style="margin-top:20px;">
|
||||
<text class="link" @click="activeTab = 'boss-reg'">商家入驻</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册老板 -->
|
||||
<view v-if="activeTab === 'boss-reg'" class="tab-content">
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="bossForm.storeName" placeholder="店铺名称" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="bossForm.bossName" placeholder="您的姓名" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="bossForm.phone" type="tel" placeholder="手机号" maxlength="11" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="bossForm.password" password placeholder="登录密码(至少6位)" />
|
||||
</view>
|
||||
<button class="btn-primary-full" :loading="regLoading" @click="handleRegisterBoss">提交申请</button>
|
||||
<view class="links" style="margin-top:20px;">
|
||||
<text class="link" @click="activeTab = 'staff'">返回登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册员工 -->
|
||||
<view v-if="activeTab === 'staff-reg'" class="tab-content">
|
||||
<view class="invite-hint">请输入店长提供的邀请码加入团队</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="staffForm.inviteCode" placeholder="请输入8位邀请码" maxlength="8" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="staffForm.name" placeholder="您的姓名" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="staffForm.phone" type="tel" placeholder="手机号" maxlength="11" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<input class="form-input" v-model="staffForm.password" password placeholder="登录密码(至少6位)" />
|
||||
</view>
|
||||
<button class="btn-primary-full" :loading="regLoading" @click="handleRegisterStaff">注册</button>
|
||||
<view class="links" style="margin-top:20px;">
|
||||
<text class="link" @click="activeTab = 'staff'">返回登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { sendSms, login, registerBoss, registerStaff } from '../../utils/api.js'
|
||||
|
||||
const activeTab = ref('staff')
|
||||
const loginForm = reactive({ phone: '13800138001', code: '123456' })
|
||||
const bossForm = reactive({ storeName: '', bossName: '', phone: '', password: '' })
|
||||
const staffForm = reactive({ inviteCode: '', name: '', phone: '', password: '' })
|
||||
const loginLoading = ref(false)
|
||||
const regLoading = ref(false)
|
||||
const smsCountdown = ref(0)
|
||||
const bossRegistered = ref(false)
|
||||
const staffRegistered = ref(false)
|
||||
const regResult = ref({ store: {}, user: {} })
|
||||
|
||||
const tabs = [
|
||||
{ key: 'staff', label: '用户登录' },
|
||||
{ key: 'boss', label: '老板登录' },
|
||||
{ key: 'boss-reg', label: '注册老板' },
|
||||
{ key: 'staff-reg', label: '注册员工' }
|
||||
]
|
||||
|
||||
let smsTimer = null
|
||||
|
||||
const showToast = (msg) => uni.showToast({ title: msg, icon: 'none' })
|
||||
|
||||
const handleSendSms = async () => {
|
||||
if (!loginForm.phone || loginForm.phone.length !== 11) return showToast('请输入正确的手机号')
|
||||
const res = await sendSms(loginForm.phone)
|
||||
if (res.code === 200) {
|
||||
showToast('验证码已发送')
|
||||
smsCountdown.value = 60
|
||||
smsTimer = setInterval(() => {
|
||||
smsCountdown.value--
|
||||
if (smsCountdown.value <= 0) clearInterval(smsTimer)
|
||||
}, 1000)
|
||||
} else {
|
||||
showToast(res.message || '发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!loginForm.phone || loginForm.phone.length !== 11) return showToast('请输入正确的手机号')
|
||||
if (!loginForm.code || loginForm.code.length !== 6) return showToast('请输入6位验证码')
|
||||
loginLoading.value = true
|
||||
const res = await login(loginForm.phone, loginForm.code)
|
||||
loginLoading.value = false
|
||||
if (res.code === 200) {
|
||||
uni.setStorageSync('petstore_user', JSON.stringify(res.data.user))
|
||||
uni.setStorageSync('petstore_store', JSON.stringify(res.data.store))
|
||||
uni.switchTab({ url: '/pages/home/home' })
|
||||
} else {
|
||||
showToast(res.message || '登录失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleWechatLogin = () => {
|
||||
showToast('跳转到微信授权...')
|
||||
setTimeout(() => {
|
||||
const demoUser = { id: 99, name: '微信用户', phone: '', role: 'staff' }
|
||||
uni.setStorageSync('petstore_user', JSON.stringify(demoUser))
|
||||
uni.setStorageSync('petstore_store', JSON.stringify({ id: 2, name: '宠伴生活馆测试店' }))
|
||||
uni.switchTab({ url: '/pages/home/home' })
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
const handleRegisterBoss = async () => {
|
||||
const f = bossForm
|
||||
if (!f.storeName) return showToast('请输入店铺名称')
|
||||
if (!f.bossName) return showToast('请输入您的姓名')
|
||||
if (!f.phone || f.phone.length !== 11) return showToast('请输入正确的手机号')
|
||||
if (!f.password || f.password.length < 6) return showToast('密码至少6位')
|
||||
regLoading.value = true
|
||||
const res = await registerBoss(f)
|
||||
regLoading.value = false
|
||||
if (res.code === 200) {
|
||||
regResult.value = res.data
|
||||
bossRegistered.value = true
|
||||
} else {
|
||||
showToast(res.message || '注册失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegisterStaff = async () => {
|
||||
const f = staffForm
|
||||
if (!f.inviteCode || f.inviteCode.length !== 8) return showToast('请输入8位邀请码')
|
||||
if (!f.name) return showToast('请输入您的姓名')
|
||||
if (!f.phone || f.phone.length !== 11) return showToast('请输入正确的手机号')
|
||||
if (!f.password || f.password.length < 6) return showToast('密码至少6位')
|
||||
regLoading.value = true
|
||||
const res = await registerStaff(f)
|
||||
regLoading.value = false
|
||||
if (res.code === 200) {
|
||||
regResult.value = res.data
|
||||
staffRegistered.value = true
|
||||
} else {
|
||||
showToast(res.message || '注册失败')
|
||||
}
|
||||
}
|
||||
|
||||
const copyCode = () => {
|
||||
uni.setClipboardData({ data: regResult.value.store.inviteCode, success: () => showToast('邀请码已复制') })
|
||||
}
|
||||
|
||||
const goLogin = () => {
|
||||
activeTab.value = 'staff'
|
||||
bossRegistered.value = false
|
||||
staffRegistered.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 80px 32px 32px;
|
||||
}
|
||||
.login-logo { text-align: center; margin-bottom: 48px; }
|
||||
.login-logo-text { font-size: 28px; font-weight: 700; color: #fff; letter-spacing: 2px; }
|
||||
.login-logo-sub { font-size: 13px; color: rgba(255,255,255,0.7); margin-top: 4px; }
|
||||
.form-card { background: #fff; border-radius: 16px; padding: 24px 20px; }
|
||||
.tab-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.tab-item {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
padding: 10px 8px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.tab-item.active { color: #07c160; border-bottom-color: #07c160; font-weight: 600; }
|
||||
.tab-content { padding-top: 16px; }
|
||||
.form-group { margin-bottom: 12px; }
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
background: #fafafa;
|
||||
}
|
||||
.form-input:focus { border-color: #07c160; outline: none; }
|
||||
.code-input-wrap { display: flex; align-items: center; gap: 8px; }
|
||||
.code-input-wrap .form-input { flex: 1; }
|
||||
.code-btn {
|
||||
font-size: 13px;
|
||||
color: #07c160;
|
||||
white-space: nowrap;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #07c160;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.code-btn.disabled { color: #999; border-color: #ddd; }
|
||||
.login-divider {
|
||||
text-align: center;
|
||||
margin: 16px 0 12px;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
position: relative;
|
||||
}
|
||||
.links { display: flex; justify-content: space-between; font-size: 13px; margin-top: 12px; }
|
||||
.link { color: #07c160; cursor: pointer; }
|
||||
.invite-hint {
|
||||
background: #f0f9f4;
|
||||
border: 1px solid #b7eb8f;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
color: #52c41a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.wechat-icon { margin-right: 6px; }
|
||||
.success-icon { font-size: 60px; text-align: center; margin-bottom: 16px; }
|
||||
.success-title { font-size: 20px; font-weight: 600; text-align: center; color: #333; margin-bottom: 8px; }
|
||||
.success-sub { font-size: 14px; color: #999; text-align: center; margin-bottom: 24px; }
|
||||
.success-info { background: #f9f9f9; border-radius: 12px; padding: 16px; margin-bottom: 20px; }
|
||||
.info-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; }
|
||||
.info-row .label { color: #999; }
|
||||
.info-row .value { color: #333; font-weight: 500; }
|
||||
.btn-primary-full {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-default-full {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@ -1,170 +0,0 @@
|
||||
<template>
|
||||
<view class="mine-page">
|
||||
<!-- 用户卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info">
|
||||
<view class="user-avatar" :style="avatarStyle">
|
||||
<image v-if="userInfo.avatar" :src="userInfo.avatar" class="avatar-img" />
|
||||
<text v-else class="avatar-initials">{{ initials }}</text>
|
||||
</view>
|
||||
<view class="user-detail">
|
||||
<text class="user-name">{{ userInfo.name }}</text>
|
||||
<text class="user-phone">{{ userInfo.phone }}</text>
|
||||
<view class="user-role">
|
||||
<view v-if="userInfo.role === 'boss'" class="role-tag boss">🏠 店长</view>
|
||||
<view v-else-if="userInfo.role === 'customer'" class="role-tag customer">🌟 客户</view>
|
||||
<view v-else class="role-tag staff">👤 员工</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 老板菜单 -->
|
||||
<view v-if="userInfo.role === 'boss'" class="menu-section">
|
||||
<view class="menu-card">
|
||||
<view class="menu-item" @click="goPage('/pages/mine/staff/staff')">
|
||||
<text class="menu-icon">👥</text>
|
||||
<text class="menu-text">员工管理</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-divider"></view>
|
||||
<view class="menu-item" @click="goPage('/pages/mine/service-type/service-type')">
|
||||
<text class="menu-icon">🛁</text>
|
||||
<text class="menu-text">服务类型</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-divider"></view>
|
||||
<view class="menu-item" @click="goPage('/pages/mine/store/store')">
|
||||
<text class="menu-icon">⚙️</text>
|
||||
<text class="menu-text">店铺设置</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- C端菜单 -->
|
||||
<view v-if="userInfo.role === 'customer'" class="menu-section">
|
||||
<view class="menu-card">
|
||||
<view class="menu-item" @click="goPage('/pages/mine/my-orders/my-orders')">
|
||||
<text class="menu-icon">📅</text>
|
||||
<text class="menu-text">我的预约</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- B端通用菜单 -->
|
||||
<view v-if="userInfo.role !== 'customer'" class="menu-section">
|
||||
<view class="menu-card">
|
||||
<view class="menu-item" @click="goPage('/pages/mine/my-reports/my-reports')">
|
||||
<text class="menu-icon">📋</text>
|
||||
<text class="menu-text">我的报告</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-divider"></view>
|
||||
<view class="menu-item" @click="goPage('/pages/mine/my-orders/my-orders')">
|
||||
<text class="menu-icon">📦</text>
|
||||
<text class="menu-text">我的订单</text>
|
||||
<text class="menu-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录 -->
|
||||
<view class="menu-section">
|
||||
<view class="menu-card">
|
||||
<view class="menu-item logout" @click="logout">
|
||||
<text class="menu-text">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
||||
|
||||
const initials = computed(() => {
|
||||
if (!userInfo.name) return '?'
|
||||
return userInfo.name.slice(0, 1).toUpperCase()
|
||||
})
|
||||
|
||||
const avatarStyle = computed(() => {
|
||||
if (userInfo.avatar) return {}
|
||||
const colors = ['#ff7c43', '#07c160', '#8b6914', '#e06040', '#5090d0']
|
||||
const idx = (userInfo.name?.charCodeAt(0) || 0) % colors.length
|
||||
return { background: colors[idx] }
|
||||
})
|
||||
|
||||
const goPage = (url) => {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
uni.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync('petstore_user')
|
||||
uni.removeStorageSync('petstore_store')
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mine-page { padding-bottom: 20px; background: #f5f5f5; min-height: 100vh; }
|
||||
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
border-radius: 0 0 24px 24px;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
.user-info { display: flex; align-items: center; gap: 16px; }
|
||||
.user-avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
.avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.avatar-initials { color: #fff; font-size: 24px; font-weight: 600; }
|
||||
.user-detail { display: flex; flex-direction: column; gap: 3px; }
|
||||
.user-name { font-size: 18px; font-weight: 600; color: #fff; }
|
||||
.user-phone { font-size: 13px; opacity: 0.8; }
|
||||
.user-role { margin-top: 4px; }
|
||||
.role-tag {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.role-tag.boss { background: rgba(255,159,0,0.3); color: #fff; }
|
||||
.role-tag.customer { background: rgba(255,255,255,0.4); color: #fff; }
|
||||
.role-tag.staff { background: rgba(255,255,255,0.3); color: #fff; }
|
||||
|
||||
.menu-section { padding: 16px 16px 0; }
|
||||
.menu-card { background: #fff; border-radius: 12px; overflow: hidden; }
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.menu-icon { font-size: 18px; margin-right: 12px; }
|
||||
.menu-text { flex: 1; font-size: 15px; color: #333; }
|
||||
.menu-arrow { font-size: 18px; color: #ccc; }
|
||||
.menu-divider { height: 1px; background: #f0f0f0; margin-left: 46px; }
|
||||
.menu-item.logout { justify-content: center; }
|
||||
.menu-item.logout .menu-text { color: #ff4d4f; text-align: center; flex: none; }
|
||||
</style>
|
||||
@ -1,176 +0,0 @@
|
||||
<template>
|
||||
<view class="orders-page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<text class="nav-back" @click="goBack">‹</text>
|
||||
<text class="nav-title">我的订单</text>
|
||||
<view style="width:40px;"></view>
|
||||
</view>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<view class="tab-bar">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['tab-item', { active: currentStatus === tab.key }]"
|
||||
@click="currentStatus = tab.key"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="order-list">
|
||||
<view v-for="item in filteredOrders" :key="item.id" class="order-item">
|
||||
<view class="order-title">{{ item.title }}</view>
|
||||
<view class="order-desc">{{ item.desc }}</view>
|
||||
<view class="order-footer">
|
||||
<text class="order-time">{{ item.time }}</text>
|
||||
<view class="status-tag" :class="'status-' + item.status">{{ item.statusText }}</view>
|
||||
</view>
|
||||
<view v-if="item.status === 'new'" class="action-btns">
|
||||
<view class="btn-sm btn-primary-sm" @click="startService(item)">开始服务</view>
|
||||
<view class="btn-sm btn-default-sm" @click="cancelService(item)">取消</view>
|
||||
</view>
|
||||
<view v-else-if="item.status === 'doing'">
|
||||
<view class="btn-sm btn-outline-sm" @click="goReport(item)">填写报告</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="filteredOrders.length === 0" class="empty-wrap">
|
||||
<text class="empty-icon">📦</text>
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getAppointmentList, startAppointment, cancelAppointment } from '../../../utils/api.js'
|
||||
|
||||
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
||||
const currentUserId = userInfo.id
|
||||
|
||||
const currentStatus = ref('new')
|
||||
const orders = ref([])
|
||||
|
||||
const tabs = [
|
||||
{ key: 'new', label: '待确认' },
|
||||
{ key: 'doing', label: '进行中' },
|
||||
{ key: 'done', label: '已完成' }
|
||||
]
|
||||
|
||||
const filteredOrders = computed(() => orders.value.filter(o => {
|
||||
if (currentStatus.value === 'new') return o.status === 'new'
|
||||
if (currentStatus.value === 'doing') return o.status === 'doing'
|
||||
if (currentStatus.value === 'done') return o.status === 'done' || o.status === 'cancel'
|
||||
return true
|
||||
}))
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const fetchOrders = async () => {
|
||||
if (!currentUserId) return
|
||||
const res = await getAppointmentList(currentUserId)
|
||||
if (res.code === 200) {
|
||||
orders.value = res.data.map(appt => ({
|
||||
id: appt.id,
|
||||
title: appt.serviceType || '洗澡美容预约',
|
||||
desc: `${appt.petType || ''} - ${appt.petName || ''}`,
|
||||
time: appt.appointmentTime ? new Date(appt.appointmentTime).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '',
|
||||
status: appt.status || 'new',
|
||||
statusText: { new: '待确认', doing: '服务中', done: '已完成', cancel: '已取消' }[appt.status] || '待确认',
|
||||
petName: appt.petName,
|
||||
petType: appt.petType,
|
||||
serviceType: appt.serviceType,
|
||||
appointmentTime: appt.appointmentTime
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const startService = async (item) => {
|
||||
const res = await startAppointment(item.id, userInfo.id)
|
||||
if (res.code === 200) { uni.showToast({ title: '已开始服务', icon: 'success' }); fetchOrders() }
|
||||
else { uni.showToast({ title: res.message || '操作失败', icon: 'none' }) }
|
||||
}
|
||||
|
||||
const cancelService = async (item) => {
|
||||
uni.showModal({
|
||||
title: '确认取消',
|
||||
content: '确定取消该预约?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const r = await cancelAppointment(item.id)
|
||||
if (r.code === 200) { uni.showToast({ title: '已取消', icon: 'success' }); fetchOrders() }
|
||||
else { uni.showToast({ title: r.message || '操作失败', icon: 'none' }) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goReport = (item) => {
|
||||
uni.setStorageSync('petstore_report_prefill', JSON.stringify({
|
||||
appointmentId: item.id, petName: item.petName, serviceType: item.serviceType, appointmentTime: item.appointmentTime
|
||||
}))
|
||||
uni.switchTab({ url: '/pages/report/report' })
|
||||
}
|
||||
|
||||
onMounted(() => fetchOrders())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.orders-page { padding-bottom: 20px; background: #f5f5f5; min-height: 100vh; }
|
||||
.nav-bar {
|
||||
background: #fff;
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.tab-item.active { color: #07c160; border-bottom-color: #07c160; font-weight: 600; }
|
||||
|
||||
.order-list { padding: 12px 16px 0; }
|
||||
.order-item {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
}
|
||||
.order-title { font-size: 15px; font-weight: 500; color: #333; margin-bottom: 6px; }
|
||||
.order-desc { font-size: 13px; color: #999; margin-bottom: 10px; }
|
||||
.order-footer { display: flex; justify-content: space-between; align-items: center; }
|
||||
.order-time { font-size: 12px; color: #999; }
|
||||
.action-btns { display: flex; gap: 8px; margin-top: 10px; }
|
||||
|
||||
.btn-sm { font-size: 12px; padding: 5px 14px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; }
|
||||
.btn-primary-sm { background: #07c160; color: #fff; border: 1px solid #07c160; }
|
||||
.btn-default-sm { background: #fff; color: #666; border: 1px solid #ddd; }
|
||||
.btn-outline-sm { background: #fff; color: #07c160; border: 1px solid #07c160; }
|
||||
|
||||
.status-tag { font-size: 12px; padding: 2px 8px; border-radius: 10px; }
|
||||
.status-new { background: #fff3e8; color: #ff9f00; }
|
||||
.status-doing { background: #e8f7ef; color: #07c160; }
|
||||
.status-done { background: #f0f0f0; color: #888; }
|
||||
.status-cancel { background: #f5f5f5; color: #999; }
|
||||
|
||||
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
|
||||
.empty-icon { font-size: 48px; }
|
||||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||||
</style>
|
||||
@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<view class="my-reports-page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<text class="nav-back" @click="goBack">‹</text>
|
||||
<text class="nav-title">我的报告</text>
|
||||
<view style="width:40px;"></view>
|
||||
</view>
|
||||
|
||||
<!-- 网格相册 -->
|
||||
<view v-if="reportList.length > 0" class="gallery-grid">
|
||||
<view
|
||||
v-for="r in reportList"
|
||||
:key="r.id"
|
||||
class="gallery-item"
|
||||
@click="viewReport(r)"
|
||||
>
|
||||
<view class="gallery-cover">
|
||||
<image
|
||||
v-if="r.beforePhoto"
|
||||
:src="imgUrl(r.beforePhoto)"
|
||||
class="cover-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="cover-placeholder">
|
||||
<text style="font-size:32px;">📷</text>
|
||||
</view>
|
||||
<!-- 叠加信息 -->
|
||||
<view class="gallery-overlay">
|
||||
<text class="overlay-name">🐾 {{ r.petName }}</text>
|
||||
<text class="overlay-service">{{ serviceEmoji(r.serviceType) }} {{ r.serviceType }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!loading && reportList.length === 0" class="empty-wrap">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无报告</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getReportList, imgUrl } from '../../../utils/api.js'
|
||||
|
||||
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
const loading = ref(false)
|
||||
const reportList = ref([])
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const serviceEmoji = (name) => {
|
||||
if (!name) return ''
|
||||
if (name.includes('洗澡') && name.includes('美容')) return '🛁✂️'
|
||||
if (name.includes('洗澡')) return '🛁'
|
||||
if (name.includes('美容')) return '✂️'
|
||||
if (name.includes('剪指甲')) return '💅'
|
||||
if (name.includes('驱虫')) return '🐛'
|
||||
return '✨'
|
||||
}
|
||||
|
||||
const loadReports = async () => {
|
||||
loading.value = true
|
||||
const params = userInfo.role === 'boss'
|
||||
? { storeId: storeInfo.id }
|
||||
: { userId: userInfo.id }
|
||||
const res = await getReportList(params)
|
||||
loading.value = false
|
||||
if (res.code === 200) reportList.value = res.data
|
||||
}
|
||||
|
||||
const viewReport = (r) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
const url = `/pages/report-view/reportView?token=${r.reportToken}`
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
const url = `/pages/report-view/reportView?token=${r.reportToken}`
|
||||
// #endif
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
onMounted(() => loadReports())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.my-reports-page { padding: 0 12px 20px; background: #f5f5f5; min-height: 100vh; }
|
||||
.nav-bar {
|
||||
background: #fff;
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
|
||||
.gallery-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.gallery-item {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
.gallery-cover {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cover-img { width: 100%; height: 100%; }
|
||||
.cover-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f0ede8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.gallery-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px 10px;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.65));
|
||||
color: #fff;
|
||||
}
|
||||
.overlay-name { font-size: 13px; font-weight: 600; display: block; }
|
||||
.overlay-service { font-size: 11px; opacity: 0.9; margin-top: 2px; display: block; }
|
||||
|
||||
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 80px 0; }
|
||||
.empty-icon { font-size: 60px; }
|
||||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||||
</style>
|
||||
@ -1,193 +0,0 @@
|
||||
<template>
|
||||
<view class="st-page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<text class="nav-back" @click="goBack">‹</text>
|
||||
<text class="nav-title">服务类型</text>
|
||||
<view style="width:40px;"></view>
|
||||
</view>
|
||||
|
||||
<view class="add-btn-wrap">
|
||||
<view class="btn-add" @click="showNew = true">+ 新增服务类型</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="list-wrap">
|
||||
<view v-for="s in serviceTypes" :key="s.id" class="list-item">
|
||||
<text class="item-name">{{ s.name }}</text>
|
||||
<view v-if="s.storeId" class="item-actions">
|
||||
<text class="delete-btn" @click="deleteST(s.id)">删除</text>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="system-tag">系统默认</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="serviceTypes.length === 0" class="empty-wrap">
|
||||
<text class="empty-icon">🛁</text>
|
||||
<text class="empty-text">暂无服务类型</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增弹窗 -->
|
||||
<view v-if="showNew" class="dialog-mask" @click="showNew = false">
|
||||
<view class="dialog-content" @click.stop>
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">新增服务类型</text>
|
||||
<text class="dialog-close" @click="showNew = false">✕</text>
|
||||
</view>
|
||||
<view class="dialog-body">
|
||||
<view class="form-item">
|
||||
<text class="form-label">服务类型名称</text>
|
||||
<input class="form-input" v-model="newName" placeholder="请输入" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="dialog-footer">
|
||||
<view class="btn-cancel" @click="showNew = false">取消</view>
|
||||
<view class="btn-confirm" @click="onAddConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getServiceTypeList, createServiceType, deleteServiceType } from '../../../utils/api.js'
|
||||
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
|
||||
const serviceTypes = ref([])
|
||||
const showNew = ref(false)
|
||||
const newName = ref('')
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const load = async () => {
|
||||
const res = await getServiceTypeList(storeInfo.id)
|
||||
if (res.code === 200) serviceTypes.value = res.data
|
||||
}
|
||||
|
||||
const onAddConfirm = async () => {
|
||||
if (!newName.value) { uni.showToast({ title: '请输入服务类型名称', icon: 'none' }); return }
|
||||
const res = await createServiceType(storeInfo.id, newName.value)
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '添加成功', icon: 'success' })
|
||||
newName.value = ''
|
||||
showNew.value = false
|
||||
load()
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '添加失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const deleteST = async (id) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定删除该服务类型?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const r = await deleteServiceType(id)
|
||||
if (r.code === 200) {
|
||||
uni.showToast({ title: '已删除', icon: 'success' })
|
||||
load()
|
||||
} else {
|
||||
uni.showToast({ title: r.message || '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => load())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.st-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
|
||||
.nav-bar {
|
||||
background: #fff;
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.add-btn-wrap { padding: 12px 16px; }
|
||||
.btn-add {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
.list-wrap { padding: 0 16px; }
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.item-name { font-size: 15px; color: #333; font-weight: 500; }
|
||||
.delete-btn { font-size: 13px; color: #ff4d4f; }
|
||||
.system-tag {
|
||||
font-size: 12px;
|
||||
background: #e8f7ef;
|
||||
color: #07c160;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
|
||||
.empty-icon { font-size: 48px; }
|
||||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||||
|
||||
/* 弹窗 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.dialog-content {
|
||||
width: 80%;
|
||||
max-width: 340px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.dialog-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.dialog-close { font-size: 18px; color: #999; }
|
||||
.dialog-body { padding: 16px 20px; }
|
||||
.dialog-footer { display: flex; border-top: 1px solid #eee; }
|
||||
.btn-cancel, .btn-confirm { flex: 1; text-align: center; padding: 14px 0; font-size: 15px; }
|
||||
.btn-cancel { color: #666; border-right: 1px solid #eee; }
|
||||
.btn-confirm { color: #07c160; font-weight: 600; }
|
||||
.form-item { }
|
||||
.form-label { font-size: 13px; color: #999; display: block; margin-bottom: 6px; }
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@ -1,247 +0,0 @@
|
||||
<template>
|
||||
<view class="staff-page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<text class="nav-back" @click="goBack">‹</text>
|
||||
<text class="nav-title">员工管理</text>
|
||||
<view style="width:40px;"></view>
|
||||
</view>
|
||||
|
||||
<view class="invite-code-row">
|
||||
<text class="invite-label">员工邀请码</text>
|
||||
<text class="invite-code">{{ storeInfo.inviteCode }}</text>
|
||||
<view class="copy-btn" @click="copyCode">复制</view>
|
||||
</view>
|
||||
|
||||
<view class="add-btn-wrap">
|
||||
<view class="btn-add" @click="showAddStaff = true">+ 新增员工</view>
|
||||
</view>
|
||||
|
||||
<!-- 员工列表 -->
|
||||
<view class="staff-list">
|
||||
<view v-for="s in staffList" :key="s.id" class="staff-item">
|
||||
<view class="staff-avatar" :style="avatarStyle(s)">
|
||||
<image v-if="s.avatar" :src="s.avatar" class="avatar-img" />
|
||||
<text v-else class="avatar-initials">{{ s.name ? s.name[0] : '?' }}</text>
|
||||
</view>
|
||||
<view class="staff-info">
|
||||
<text class="staff-name">{{ s.name }}</text>
|
||||
<text class="staff-phone">{{ s.phone }}</text>
|
||||
<text class="staff-role">{{ s.role === 'boss' ? '🏠 店长' : '👤 员工' }}</text>
|
||||
</view>
|
||||
<text v-if="s.role !== 'boss'" class="delete-btn" @click="deleteStaff(s.id)">删除</text>
|
||||
</view>
|
||||
<view v-if="staffList.length === 0" class="empty-wrap">
|
||||
<text class="empty-icon">👥</text>
|
||||
<text class="empty-text">暂无员工</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增员工弹窗 -->
|
||||
<view v-if="showAddStaff" class="dialog-mask" @click="showAddStaff = false">
|
||||
<view class="dialog-content" @click.stop>
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">新增员工</text>
|
||||
<text class="dialog-close" @click="showAddStaff = false">✕</text>
|
||||
</view>
|
||||
<view class="dialog-body">
|
||||
<view class="form-item">
|
||||
<text class="form-label">员工姓名</text>
|
||||
<input class="form-input" v-model="newStaff.name" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">手机号</text>
|
||||
<input class="form-input" v-model="newStaff.phone" type="tel" placeholder="请输入" maxlength="11" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="dialog-footer">
|
||||
<view class="btn-cancel" @click="showAddStaff = false">取消</view>
|
||||
<view class="btn-confirm" @click="onAddStaffConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getStaffList, createStaff, deleteStaff as delStaff } from '../../../utils/api.js'
|
||||
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
|
||||
const staffList = ref([])
|
||||
const showAddStaff = ref(false)
|
||||
const newStaff = ref({ name: '', phone: '' })
|
||||
|
||||
const COLORS = ['#ff7c43', '#07c160', '#8b6914', '#e06040', '#5090d0', '#9b59b6']
|
||||
const avatarStyle = (s) => {
|
||||
if (s.avatar) return {}
|
||||
const idx = (s.name?.charCodeAt(0) || 0) % COLORS.length
|
||||
return { background: COLORS[idx] }
|
||||
}
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const loadStaff = async () => {
|
||||
const res = await getStaffList(storeInfo.id)
|
||||
if (res.code === 200) staffList.value = res.data
|
||||
}
|
||||
|
||||
const copyCode = () => {
|
||||
uni.setClipboardData({ data: storeInfo.inviteCode, success: () => uni.showToast({ title: '邀请码已复制', icon: 'success' }) })
|
||||
}
|
||||
|
||||
const onAddStaffConfirm = async () => {
|
||||
if (!newStaff.value.name) { uni.showToast({ title: '请输入员工姓名', icon: 'none' }); return }
|
||||
if (!newStaff.value.phone || newStaff.value.phone.length !== 11) { uni.showToast({ title: '请输入正确的手机号', icon: 'none' }); return }
|
||||
const res = await createStaff({ storeId: storeInfo.id, name: newStaff.value.name, phone: newStaff.value.phone })
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: `添加成功,密码:${res.data.password}`, icon: 'none', duration: 3000 })
|
||||
showAddStaff.value = false
|
||||
newStaff.value = { name: '', phone: '' }
|
||||
loadStaff()
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '添加失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const deleteStaff = async (staffId) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定删除该员工?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const r = await delStaff(staffId)
|
||||
if (r.code === 200) {
|
||||
uni.showToast({ title: '已删除', icon: 'success' })
|
||||
loadStaff()
|
||||
} else {
|
||||
uni.showToast({ title: r.message || '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => loadStaff())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.staff-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
|
||||
|
||||
.nav-bar {
|
||||
background: #fff;
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
|
||||
.invite-code-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
margin: 12px 16px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.invite-label { font-size: 14px; color: #666; }
|
||||
.invite-code { flex: 1; font-size: 15px; color: #07c160; font-weight: 600; font-family: monospace; }
|
||||
.copy-btn { font-size: 13px; color: #07c160; padding: 4px 12px; border: 1px solid #07c160; border-radius: 4px; }
|
||||
|
||||
.add-btn-wrap { padding: 0 16px 12px; }
|
||||
.btn-add {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.staff-list { padding: 0 16px; }
|
||||
.staff-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 10px;
|
||||
gap: 12px;
|
||||
}
|
||||
.staff-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.avatar-initials { font-weight: 600; }
|
||||
.staff-info { flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
||||
.staff-name { font-size: 15px; font-weight: 500; color: #333; }
|
||||
.staff-phone { font-size: 12px; color: #999; }
|
||||
.staff-role { font-size: 11px; color: #999; }
|
||||
.delete-btn { font-size: 13px; color: #ff4d4f; }
|
||||
|
||||
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
|
||||
.empty-icon { font-size: 48px; }
|
||||
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
|
||||
|
||||
/* 弹窗 */
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.dialog-content {
|
||||
width: 80%;
|
||||
max-width: 340px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.dialog-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
.dialog-close { font-size: 18px; color: #999; }
|
||||
.dialog-body { padding: 16px 20px; }
|
||||
.dialog-footer { display: flex; border-top: 1px solid #eee; }
|
||||
.btn-cancel, .btn-confirm { flex: 1; text-align: center; padding: 14px 0; font-size: 15px; }
|
||||
.btn-cancel { color: #666; border-right: 1px solid #eee; }
|
||||
.btn-confirm { color: #07c160; font-weight: 600; }
|
||||
|
||||
.form-item { margin-bottom: 16px; }
|
||||
.form-item:last-child { margin-bottom: 0; }
|
||||
.form-label { font-size: 13px; color: #999; display: block; margin-bottom: 6px; }
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@ -1,124 +0,0 @@
|
||||
<template>
|
||||
<view class="store-page">
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav-bar">
|
||||
<text class="nav-back" @click="goBack">‹</text>
|
||||
<text class="nav-title">店铺设置</text>
|
||||
<view style="width:40px;"></view>
|
||||
</view>
|
||||
|
||||
<view class="form-wrap">
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<text class="row-label">店铺名称</text>
|
||||
<input class="row-input" v-model="form.name" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="row-label">联系电话</text>
|
||||
<input class="row-input" v-model="form.phone" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="row-label">地址</text>
|
||||
<input class="row-input" v-model="form.address" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="form-row" style="flex-direction:column; align-items:flex-start;">
|
||||
<text class="row-label" style="margin-bottom:8px;">简介</text>
|
||||
<textarea class="row-textarea" v-model="form.intro" placeholder="请输入店铺简介" rows="3" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="save-btn-wrap">
|
||||
<view class="btn-save" :class="{ loading: saving }" @click="saveStore">保存设置</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { updateStore } from '../../../utils/api.js'
|
||||
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
const saving = ref(false)
|
||||
const form = ref({ name: '', phone: '', address: '', intro: '' })
|
||||
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const saveStore = async () => {
|
||||
if (!form.value.name) { uni.showToast({ title: '请输入店铺名称', icon: 'none' }); return }
|
||||
saving.value = true
|
||||
const res = await updateStore({ id: storeInfo.id, ...form.value })
|
||||
saving.value = false
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
const updated = { ...storeInfo, ...form.value }
|
||||
uni.setStorageSync('petstore_store', JSON.stringify(updated))
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
form.value = {
|
||||
name: storeInfo.name || '',
|
||||
phone: storeInfo.phone || '',
|
||||
address: storeInfo.address || '',
|
||||
intro: storeInfo.intro || ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
|
||||
.nav-bar {
|
||||
background: #fff;
|
||||
padding: 40px 16px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
|
||||
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
|
||||
|
||||
.form-wrap { padding: 12px 16px 0; }
|
||||
.form-card { background: #fff; border-radius: 12px; padding: 4px 16px; }
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.form-row:last-child { border-bottom: none; }
|
||||
.row-label { font-size: 14px; color: #666; width: 72px; flex-shrink: 0; }
|
||||
.row-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
.row-textarea {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
border: none;
|
||||
resize: none;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.save-btn-wrap { padding: 16px; }
|
||||
.btn-save {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 23px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-save.loading { opacity: 0.7; }
|
||||
</style>
|
||||
@ -1,416 +0,0 @@
|
||||
<template>
|
||||
<view class="report-view">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loading" class="loading-wrap">
|
||||
<view class="loading-inner">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载报告中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 未找到 -->
|
||||
<view v-else-if="notFound" class="not-found">
|
||||
<text class="not-found-icon">🔍</text>
|
||||
<text class="not-found-text">报告不存在或链接已失效</text>
|
||||
</view>
|
||||
|
||||
<!-- 报告内容 -->
|
||||
<view v-else-if="reportData" class="report-content">
|
||||
<!-- 品牌头部 -->
|
||||
<view class="brand-header">
|
||||
<view class="brand-logo">{{ reportData.store?.name || '宠伴生活馆' }}</view>
|
||||
<view class="brand-sub">宠物服务,让爱更专业</view>
|
||||
<view class="brand-contact" v-if="reportData.store?.phone || reportData.store?.address">
|
||||
<text v-if="reportData.store?.phone">📞 {{ reportData.store.phone }}</text>
|
||||
<text v-if="reportData.store?.address">📍 {{ reportData.store.address }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 报告标题 -->
|
||||
<view class="report-title-wrap">
|
||||
<view class="report-title">服务报告</view>
|
||||
<view class="report-time">{{ formatTime(reportData.appointmentTime) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务信息 -->
|
||||
<view class="service-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">宠物名字</text>
|
||||
<text class="info-val">{{ reportData.petName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">服务项目</text>
|
||||
<text class="info-val">{{ reportData.serviceType || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">服务时间</text>
|
||||
<text class="info-val">{{ formatTime(reportData.appointmentTime) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">服务技师</text>
|
||||
<text class="info-val">{{ reportData.staffName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 照片对比 -->
|
||||
<view class="photo-section">
|
||||
<view class="section-label">服务前后对比</view>
|
||||
<view class="photo-grid">
|
||||
<view class="photo-cell">
|
||||
<text class="photo-label">服务前</text>
|
||||
<image
|
||||
v-if="reportData.beforePhoto"
|
||||
:src="imgUrl(reportData.beforePhoto)"
|
||||
class="photo-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="photo-empty">暂无照片</view>
|
||||
</view>
|
||||
<view class="photo-cell">
|
||||
<text class="photo-label">服务后</text>
|
||||
<image
|
||||
v-if="reportData.afterPhoto"
|
||||
:src="imgUrl(reportData.afterPhoto)"
|
||||
class="photo-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="photo-empty">暂无照片</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view v-if="reportData.remark" class="remark-section">
|
||||
<view class="section-label">备注</view>
|
||||
<view class="remark-content">{{ reportData.remark }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成海报按钮 -->
|
||||
<view class="action-section">
|
||||
<view class="btn-share" @click="generatePoster">📸 生成图片分享朋友圈</view>
|
||||
<view class="btn-book" @click="goHome">我要预约</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Canvas海报(隐藏) -->
|
||||
<canvas canvas-id="posterCanvas" id="posterCanvas" class="poster-canvas" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getReportByToken, imgUrl } from '../../utils/api.js'
|
||||
|
||||
const loading = ref(true)
|
||||
const notFound = ref(false)
|
||||
const reportData = ref(null)
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (!time) return ''
|
||||
const d = new Date(time)
|
||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`
|
||||
}
|
||||
|
||||
const loadReport = async () => {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const token = currentPage?.options?.token || ''
|
||||
|
||||
if (!token) {
|
||||
notFound.value = true
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const res = await getReportByToken(token)
|
||||
loading.value = false
|
||||
if (res.code === 200) {
|
||||
reportData.value = res.data
|
||||
} else {
|
||||
notFound.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const loadImage = (src) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = () => resolve(null)
|
||||
img.src = src
|
||||
})
|
||||
}
|
||||
|
||||
const goHome = () => {
|
||||
uni.switchTab({ url: '/pages/home/home' })
|
||||
}
|
||||
|
||||
const generatePoster = async () => {
|
||||
if (!reportData.value) return
|
||||
uni.showLoading({ title: '生成中...' })
|
||||
const r = reportData.value
|
||||
const canvas = uni.createCanvasContext('posterCanvas')
|
||||
|
||||
canvas.width = 750
|
||||
canvas.height = 1100
|
||||
|
||||
// 背景
|
||||
canvas.setFillStyle('#ffffff')
|
||||
canvas.fillRect(0, 0, 750, 1100)
|
||||
|
||||
// 顶部绿色
|
||||
canvas.setFillStyle('#07c160')
|
||||
canvas.fillRect(0, 0, 750, 300)
|
||||
|
||||
const storeName = r.store?.name || '宠伴生活馆'
|
||||
|
||||
// Logo
|
||||
canvas.setFillStyle('#ffffff')
|
||||
canvas.setFontSize(36)
|
||||
canvas.setTextAlign('center')
|
||||
canvas.fillText(storeName, 375, 70)
|
||||
canvas.setFontSize(20)
|
||||
canvas.setGlobalAlpha(0.7)
|
||||
canvas.fillText('宠物服务,让爱更专业', 375, 105)
|
||||
canvas.setGlobalAlpha(1)
|
||||
|
||||
// 联系信息
|
||||
const storePhone = r.store?.phone || ''
|
||||
const storeAddr = r.store?.address || ''
|
||||
if (storePhone || storeAddr) {
|
||||
canvas.setFontSize(18)
|
||||
canvas.setGlobalAlpha(0.85)
|
||||
const contactLine = [storePhone, storeAddr].filter(Boolean).join(' | ')
|
||||
canvas.fillText(contactLine, 375, 138)
|
||||
canvas.setGlobalAlpha(1)
|
||||
}
|
||||
|
||||
// 报告标题
|
||||
canvas.setFillStyle('#333333')
|
||||
canvas.setFontSize(36)
|
||||
canvas.fillText('服务报告', 375, 220)
|
||||
|
||||
// 服务信息卡片
|
||||
canvas.setFillStyle('#f8f6f3')
|
||||
roundRect(canvas, 40, 260, 670, 220, 20)
|
||||
canvas.fill()
|
||||
|
||||
const infoItems = [
|
||||
['宠物名字', r.petName || '-'],
|
||||
['服务项目', r.serviceType || '-'],
|
||||
['服务时间', formatTime(r.appointmentTime) || '-'],
|
||||
['服务技师', r.staffName || '-']
|
||||
]
|
||||
let y = 310
|
||||
canvas.setTextAlign('left')
|
||||
infoItems.forEach(([label, val]) => {
|
||||
canvas.setFillStyle('#999999')
|
||||
canvas.setFontSize(22)
|
||||
canvas.fillText(label, 80, y)
|
||||
canvas.setFillStyle('#333333')
|
||||
canvas.setFontSize(24)
|
||||
canvas.fillText(val, 220, y)
|
||||
y += 48
|
||||
})
|
||||
|
||||
// 照片区域
|
||||
canvas.setFillStyle('#f8f6f3')
|
||||
roundRect(canvas, 40, 500, 670, 360, 20)
|
||||
canvas.fill()
|
||||
|
||||
canvas.setFillStyle('#333333')
|
||||
canvas.setFontSize(24)
|
||||
canvas.setTextAlign('center')
|
||||
canvas.fillText('服务前后对比', 375, 545)
|
||||
|
||||
const imgY = 575
|
||||
const imgH = 260
|
||||
const imgW = 300
|
||||
|
||||
// 服务前占位
|
||||
canvas.setFillStyle('#e0e0e0')
|
||||
roundRect(canvas, 60, imgY, imgW, imgH, 16)
|
||||
canvas.fill()
|
||||
canvas.setFillStyle('#999999')
|
||||
canvas.setFontSize(20)
|
||||
canvas.fillText('服务前', 210, imgY + imgH/2)
|
||||
|
||||
// 服务后占位
|
||||
canvas.setFillStyle('#e0e0e0')
|
||||
roundRect(canvas, 390, imgY, imgW, imgH, 16)
|
||||
canvas.fill()
|
||||
canvas.setFillStyle('#999999')
|
||||
canvas.fillText('服务后', 540, imgY + imgH/2)
|
||||
|
||||
// 备注
|
||||
if (r.remark) {
|
||||
canvas.setFillStyle('#f8f6f3')
|
||||
roundRect(canvas, 40, 880, 670, 100, 20)
|
||||
canvas.fill()
|
||||
canvas.setFillStyle('#666666')
|
||||
canvas.setFontSize(22)
|
||||
canvas.setTextAlign('left')
|
||||
const remark = r.remark
|
||||
if (remark.length > 30) {
|
||||
canvas.fillText(remark.substring(0, 30), 70, 920)
|
||||
canvas.fillText(remark.substring(30), 70, 955)
|
||||
} else {
|
||||
canvas.fillText(remark, 70, 930)
|
||||
}
|
||||
}
|
||||
|
||||
// 底部Logo
|
||||
canvas.setFillStyle('#07c160')
|
||||
canvas.setFontSize(22)
|
||||
canvas.setTextAlign('center')
|
||||
canvas.fillText(`— ${storeName} —`, 375, 1050)
|
||||
|
||||
canvas.draw()
|
||||
|
||||
// 异步绘制真实照片
|
||||
const beforeImgSrc = r.beforePhoto ? imgUrl(r.beforePhoto) : null
|
||||
const afterImgSrc = r.afterPhoto ? imgUrl(r.afterPhoto) : null
|
||||
const [beforeImg, afterImg] = await Promise.all([
|
||||
beforeImgSrc ? loadImage(beforeImgSrc) : Promise.resolve(null),
|
||||
afterImgSrc ? loadImage(afterImgSrc) : Promise.resolve(null)
|
||||
])
|
||||
|
||||
if (beforeImg) {
|
||||
canvas.drawImage(beforeImg, 60, imgY, imgW, imgH)
|
||||
canvas.draw()
|
||||
}
|
||||
if (afterImg) {
|
||||
canvas.drawImage(afterImg, 390, imgY, imgW, imgH)
|
||||
canvas.draw()
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '海报已生成,请截图分享', icon: 'none', duration: 3000 })
|
||||
}
|
||||
|
||||
function roundRect(ctx, x, y, w, h, r) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + r, y)
|
||||
ctx.lineTo(x + w - r, y)
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r)
|
||||
ctx.lineTo(x + w, y + h - r)
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
|
||||
ctx.lineTo(x + r, y + h)
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r)
|
||||
ctx.lineTo(x, y + r)
|
||||
ctx.arcTo(x, y, x + r, y, r)
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
onMounted(() => loadReport())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.report-view { background: #f8f6f3; min-height: 100vh; }
|
||||
.loading-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.loading-inner { display: flex; flex-direction: column; align-items: center; gap: 16px; }
|
||||
.loading-spinner {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 3px solid #07c160;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.loading-text { font-size: 14px; color: #999; }
|
||||
.not-found { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; gap: 12px; }
|
||||
.not-found-icon { font-size: 60px; }
|
||||
.not-found-text { font-size: 15px; color: #999; }
|
||||
|
||||
.report-content { max-width: 430px; margin: 0 auto; background: #fff; min-height: 100vh; }
|
||||
.brand-header {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
padding: 24px 24px 20px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
.brand-logo { font-size: 20px; font-weight: 700; letter-spacing: 2px; margin-bottom: 4px; }
|
||||
.brand-sub { font-size: 12px; opacity: 0.7; margin-bottom: 12px; }
|
||||
.brand-contact { font-size: 12px; opacity: 0.85; display: flex; justify-content: center; gap: 16px; flex-wrap: wrap; }
|
||||
.report-title-wrap { text-align: center; padding: 24px 24px 16px; }
|
||||
.report-title { font-size: 22px; font-weight: 700; color: #333; }
|
||||
.report-time { font-size: 13px; color: #999; margin-top: 6px; }
|
||||
|
||||
.service-info {
|
||||
margin: 0 16px 16px;
|
||||
background: #f8f6f3;
|
||||
border-radius: 12px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 14px; color: #999; }
|
||||
.info-val { font-size: 14px; color: #333; font-weight: 500; }
|
||||
|
||||
.photo-section { margin: 0 24px 24px; }
|
||||
.section-label { font-size: 15px; font-weight: 600; color: #333; margin-bottom: 12px; }
|
||||
.photo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.photo-cell { display: flex; flex-direction: column; gap: 8px; }
|
||||
.photo-label { font-size: 13px; color: #999; text-align: center; }
|
||||
.photo-img { width: 100%; height: 160px; border-radius: 12px; object-fit: cover; }
|
||||
.photo-empty {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ccc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.remark-section { margin: 0 24px 24px; }
|
||||
.remark-content { background: #f9f9f9; border-radius: 12px; padding: 16px; font-size: 14px; color: #666; line-height: 1.6; min-height: 60px; }
|
||||
.action-section { margin: 0 24px 24px; }
|
||||
.btn-share {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border-radius: 23px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.btn-book {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
background: #fff;
|
||||
color: #07c160;
|
||||
border: 1px solid #07c160;
|
||||
border-radius: 23px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.poster-canvas {
|
||||
position: fixed;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
width: 750px;
|
||||
height: 1100px;
|
||||
}
|
||||
</style>
|
||||
@ -1,333 +0,0 @@
|
||||
<template>
|
||||
<view class="report-page">
|
||||
<!-- 提交结果 -->
|
||||
<view v-if="reportResult" class="result-wrap">
|
||||
<view class="notice-bar">✓ 提交成功,可复制链接或扫描二维码分享给宠主</view>
|
||||
<view class="result-content">
|
||||
<view class="result-link-row">
|
||||
<text class="result-link-label">报告链接</text>
|
||||
<text class="result-link-val" selectable>{{ reportResult.reportUrl }}</text>
|
||||
</view>
|
||||
<view class="qr-wrap">
|
||||
<image :src="qrUrl" mode="aspectFit" class="qr-img" />
|
||||
</view>
|
||||
<button class="btn-primary-full" @click="downloadQr">📥 保存二维码到相册</button>
|
||||
<button class="btn-default-full" @click="goHome" style="margin-top:12px;">返回首页</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 填写表单 -->
|
||||
<view v-else class="report-form">
|
||||
<!-- 基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="form-title">基本信息</view>
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<text class="row-label">宠物名字</text>
|
||||
<input class="row-input" v-model="report.petName" placeholder="请输入宠物名字" />
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="row-label">服务类型</text>
|
||||
<picker :value="serviceTypeIdx" :range="serviceTypeOptions" :range-key="'name'" @change="onServiceTypeChange">
|
||||
<view class="picker-val">{{ report.serviceType || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-row">
|
||||
<text class="row-label">服务时间</text>
|
||||
<picker mode="datetime" :value="report.appointmentTime" @change="onDateChange">
|
||||
<view class="picker-val">{{ report.appointmentTime || '请选择' }} ▾</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务照片 -->
|
||||
<view class="form-section">
|
||||
<view class="form-title">服务照片</view>
|
||||
<view class="form-card">
|
||||
<view class="photo-row">
|
||||
<text class="row-label">服务前</text>
|
||||
<view class="photo-upload" @click="chooseImage('before')">
|
||||
<image v-if="report.before" :src="report.before" class="photo-preview" mode="aspectFill" />
|
||||
<view v-else class="photo-placeholder">
|
||||
<text class="plus-icon">+</text>
|
||||
<text class="upload-tip">上传照片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="photo-row">
|
||||
<text class="row-label">服务后</text>
|
||||
<view class="photo-upload" @click="chooseImage('after')">
|
||||
<image v-if="report.after" :src="report.after" class="photo-preview" mode="aspectFill" />
|
||||
<view v-else class="photo-placeholder">
|
||||
<text class="plus-icon">+</text>
|
||||
<text class="upload-tip">上传照片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-section">
|
||||
<view class="form-title">备注</view>
|
||||
<view class="form-card">
|
||||
<textarea class="remark-input" v-model="report.remark" placeholder="请输入备注信息..." rows="3" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit-wrap">
|
||||
<button class="btn-submit" :class="{ loading: submitting }" @click="submitReport">提交报告</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { createReport, getServiceTypeList, uploadImage } from '../../utils/api.js'
|
||||
import { imgUrl } from '../../utils/api.js'
|
||||
|
||||
const userInfo = JSON.parse(uni.getStorageSync('petstore_user') || '{}')
|
||||
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
|
||||
|
||||
const report = ref({ petName: '', serviceType: '', appointmentTime: '', before: '', after: '', remark: '' })
|
||||
const reportResult = ref(null)
|
||||
const serviceTypes = ref([])
|
||||
const serviceTypeIdx = ref(0)
|
||||
const submitting = ref(false)
|
||||
const currentAppointmentId = ref(null)
|
||||
|
||||
const serviceTypeOptions = computed(() =>
|
||||
serviceTypes.value.map(s => ({
|
||||
name: `${serviceEmoji(s.name)} ${s.name}`,
|
||||
value: s.name
|
||||
}))
|
||||
)
|
||||
|
||||
const serviceEmoji = (name) => {
|
||||
if (!name) return ''
|
||||
if (name.includes('洗澡') && name.includes('美容')) return '🛁✂️'
|
||||
if (name.includes('洗澡')) return '🛁'
|
||||
if (name.includes('美容')) return '✂️'
|
||||
if (name.includes('剪指甲')) return '💅'
|
||||
if (name.includes('驱虫')) return '🐛'
|
||||
return '✨'
|
||||
}
|
||||
|
||||
const qrUrl = computed(() => reportResult.value
|
||||
? `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(reportResult.value.reportUrl)}`
|
||||
: '')
|
||||
|
||||
const loadServiceTypes = async () => {
|
||||
if (!storeInfo.id) return
|
||||
const res = await getServiceTypeList(storeInfo.id)
|
||||
if (res.code === 200) serviceTypes.value = res.data
|
||||
}
|
||||
|
||||
const onServiceTypeChange = (e) => {
|
||||
serviceTypeIdx.value = e.detail.value
|
||||
report.value.serviceType = serviceTypes.value[e.detail.value]?.name || ''
|
||||
}
|
||||
|
||||
const onDateChange = (e) => {
|
||||
report.value.appointmentTime = e.detail.value
|
||||
}
|
||||
|
||||
const chooseImage = async (field) => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: async (res) => {
|
||||
const filePath = res.tempFilePaths[0]
|
||||
uni.showLoading({ title: '上传中...' })
|
||||
const r = await uploadImage(filePath)
|
||||
uni.hideLoading()
|
||||
if (r.code === 200) {
|
||||
report.value[field] = imgUrl(r.data.url)
|
||||
uni.showToast({ title: '上传成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: r.message || '上传失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const submitReport = async () => {
|
||||
if (!report.value.petName) { uni.showToast({ title: '请输入宠物名字', icon: 'none' }); return }
|
||||
if (!report.value.serviceType) { uni.showToast({ title: '请选择服务类型', icon: 'none' }); return }
|
||||
submitting.value = true
|
||||
const payload = {
|
||||
appointmentId: currentAppointmentId.value || null,
|
||||
userId: userInfo.id,
|
||||
petName: report.value.petName,
|
||||
serviceType: report.value.serviceType,
|
||||
appointmentTime: report.value.appointmentTime || null,
|
||||
beforePhoto: report.value.before,
|
||||
afterPhoto: report.value.after,
|
||||
remark: report.value.remark
|
||||
}
|
||||
const res = await createReport(payload)
|
||||
submitting.value = false
|
||||
if (res.code === 200) {
|
||||
const token = res.data.reportToken
|
||||
// #ifdef MP-WEIXIN
|
||||
const reportUrl = `/pages/report-view/reportView?token=${token}`
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
const reportUrl = `${window.location.origin}/#/pages/report-view/reportView?token=${token}`
|
||||
// #endif
|
||||
reportResult.value = { token, reportUrl }
|
||||
} else {
|
||||
uni.showToast({ title: res.message || '提交失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const copyLink = () => {
|
||||
uni.setClipboardData({ data: reportResult.value.reportUrl, success: () => uni.showToast({ title: '链接已复制', icon: 'success' }) })
|
||||
}
|
||||
|
||||
const downloadQr = () => {
|
||||
uni.showToast({ title: '请长按二维码图片保存', icon: 'none' })
|
||||
}
|
||||
|
||||
const goHome = () => {
|
||||
reportResult.value = null
|
||||
report.value = { petName: '', serviceType: '', appointmentTime: '', before: '', after: '', remark: '' }
|
||||
uni.switchTab({ url: '/pages/home/home' })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadServiceTypes()
|
||||
const prefill = JSON.parse(uni.getStorageSync('petstore_report_prefill') || 'null')
|
||||
if (prefill) {
|
||||
currentAppointmentId.value = prefill.appointmentId
|
||||
report.value.petName = prefill.petName || ''
|
||||
report.value.serviceType = prefill.serviceType || ''
|
||||
report.value.appointmentTime = prefill.appointmentTime ? prefill.appointmentTime.slice(0, 16) : ''
|
||||
uni.removeStorageSync('petstore_report_prefill')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.report-page { padding-bottom: 20px; background: #f5f5f5; min-height: 100vh; }
|
||||
|
||||
.notice-bar {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
font-size: 13px;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
.result-content { padding: 16px; }
|
||||
.result-link-row { background: #f9f9f9; border-radius: 8px; padding: 12px; margin-bottom: 16px; }
|
||||
.result-link-label { font-size: 12px; color: #999; display: block; margin-bottom: 4px; }
|
||||
.result-link-val { font-size: 12px; color: #07c160; word-break: break-all; }
|
||||
|
||||
.qr-wrap { text-align: center; padding: 20px; background: #f9f9f9; border-radius: 12px; margin: 16px 0; }
|
||||
.qr-img { width: 180px; height: 180px; }
|
||||
|
||||
.form-section { padding: 12px 16px 0; }
|
||||
.form-title { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 8px; padding-left: 4px; }
|
||||
.form-card { background: #fff; border-radius: 12px; padding: 4px 16px; }
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.form-row:last-child { border-bottom: none; }
|
||||
.row-label { font-size: 14px; color: #666; width: 80px; flex-shrink: 0; }
|
||||
.row-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
.picker-val {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
.photo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.photo-row:last-child { border-bottom: none; }
|
||||
.photo-upload {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.photo-preview {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.photo-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 1px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
.plus-icon { font-size: 24px; line-height: 1; }
|
||||
.upload-tip { font-size: 11px; margin-top: 2px; }
|
||||
.remark-input {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
border: none;
|
||||
resize: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.submit-wrap { padding: 16px; }
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 23px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-submit.loading { opacity: 0.6; }
|
||||
|
||||
.btn-primary-full {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-default-full {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@ -14,6 +14,7 @@ function scheduleAuthGuard() {
|
||||
try {
|
||||
if (isLoggedIn()) return
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length === 0) return // 还没加载完页面,默认第一个页面是 login,不需要拦截
|
||||
const cur = pages[pages.length - 1]
|
||||
const route = cur && cur.route ? cur.route : ''
|
||||
if (isPublicRoute(route)) return
|
||||
|
||||
@ -18,5 +18,5 @@
|
||||
"navigationBarTitleText": "宠伴生活馆",
|
||||
"navigationBarBackgroundColor": "#07c160",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,49 @@
|
||||
<template>
|
||||
<div class="page-shell home-page">
|
||||
<!-- C端视角:微官网与预约 -->
|
||||
<view v-if="userInfo.role === 'customer'" class="c-home">
|
||||
<view class="home-nav nav-gradient" :style="navSafeStyle">
|
||||
<text class="nav-title">宠伴生活馆</text>
|
||||
</view>
|
||||
<view class="c-header">
|
||||
<view class="c-title">{{ storeInfo.name || '宠伴生活馆' }}</view>
|
||||
<view class="c-sub">宠物服务,让爱更专业</view>
|
||||
</view>
|
||||
|
||||
<view class="c-booking-card">
|
||||
<view class="c-booking-title">预约服务</view>
|
||||
<view class="field-label">宠物名字</view>
|
||||
<input v-model="newAppt.petName" class="van-field" placeholder="请输入宠物名字" />
|
||||
|
||||
<view class="field-label">宠物类型</view>
|
||||
<picker mode="selector" :range="petTypes" range-key="label" @change="e => newAppt.petType = petTypes[e.detail.value].value">
|
||||
<view class="van-field picker-field">{{ newAppt.petType || '请选择' }}</view>
|
||||
</picker>
|
||||
|
||||
<view class="field-label">服务类型</view>
|
||||
<picker mode="selector" :range="serviceTypes" range-key="label" @change="e => newAppt.serviceType = serviceTypes[e.detail.value].value">
|
||||
<view class="van-field picker-field">{{ newAppt.serviceType || '请选择' }}</view>
|
||||
</picker>
|
||||
|
||||
<view class="field-label">预约日期</view>
|
||||
<picker mode="date" :value="appointmentDate" @change="onAppointmentDateChange">
|
||||
<view class="van-field picker-field">{{ appointmentDate || '请选择日期' }}</view>
|
||||
</picker>
|
||||
|
||||
<view class="field-label">预约时间</view>
|
||||
<picker mode="time" :value="appointmentTime" @change="onAppointmentTimeOnlyChange">
|
||||
<view class="van-field picker-field">{{ appointmentTime || '请选择时间' }}</view>
|
||||
</picker>
|
||||
|
||||
<view class="field-label">备注(可选)</view>
|
||||
<input v-model="newAppt.remark" class="van-field" placeholder="可选" />
|
||||
|
||||
<button class="btn-block" style="margin-top: 24px;" :loading="creatingAppt" @click="confirmNewAppt">提交预约</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- B端视角:订单管理 -->
|
||||
<view v-else>
|
||||
<!-- 顶部导航 -->
|
||||
<view class="home-nav nav-gradient" :style="navSafeStyle">
|
||||
<text class="nav-title">宠伴生活馆</text>
|
||||
@ -107,6 +151,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</div>
|
||||
|
||||
<TabBar current-page="home" @change="goPage" />
|
||||
@ -316,6 +361,24 @@ onMounted(() => {
|
||||
.hero-sub { margin-top: 4px; font-size: 12px; color: #4b5563; line-height: 1.45; }
|
||||
.list-content { padding: 0; }
|
||||
|
||||
.c-header {
|
||||
background: linear-gradient(135deg, #07c160 0%, #10b76f 100%);
|
||||
padding: 20px 20px 40px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
border-radius: 0 0 24px 24px;
|
||||
}
|
||||
.c-title { font-size: 24px; font-weight: 700; margin-bottom: 8px; }
|
||||
.c-sub { font-size: 14px; opacity: 0.8; }
|
||||
.c-booking-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
margin: -20px 16px 0;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.c-booking-title { font-size: 18px; font-weight: 600; color: #333; margin-bottom: 20px; text-align: center; }
|
||||
|
||||
/* 按钮 */
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<div class="user-info-content">
|
||||
<div class="user-name">{{ userInfo.name }}</div>
|
||||
<div class="user-phone">{{ userInfo.phone }}</div>
|
||||
<div class="role-pill">{{ userInfo.role === 'boss' ? '店长' : '员工' }}</div>
|
||||
<div class="role-pill">{{ userInfo.role === 'boss' ? '店长' : userInfo.role === 'customer' ? '客户' : '员工' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -42,7 +42,7 @@
|
||||
<div class="menu-left"><span class="menu-icon"><AppIcon name="profile" :size="15" /></span><span class="menu-text">个人信息</span></div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="navigateTo('myReports')">
|
||||
<div v-if="userInfo.role !== 'customer'" class="menu-item" @click="navigateTo('myReports')">
|
||||
<div class="menu-left"><span class="menu-icon"><AppIcon name="report" :size="15" /></span><span class="menu-text">我的报告</span></div>
|
||||
<span class="menu-arrow">›</span>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 175 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#07c160"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">🏠</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 172 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#999"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">🏠</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 208 B |
|
Before Width: | Height: | Size: 175 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#07c160"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">👤</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 172 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#999"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">👤</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 208 B |
|
Before Width: | Height: | Size: 175 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#07c160"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">📋</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 172 B |
@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="81" height="81" viewBox="0 0 81 81">
|
||||
<rect width="81" height="81" fill="#999"/>
|
||||
<text x="40.5" y="45" text-anchor="middle" font-size="36">📋</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 208 B |
21
uni.scss
@ -1,21 +0,0 @@
|
||||
/* uni.scss - 全局样式变量 */
|
||||
$uni-color-primary: #07c160;
|
||||
$uni-color-success: #07c160;
|
||||
$uni-color-warning: #ff9f00;
|
||||
$uni-color-error: #ff4d4f;
|
||||
$uni-color-white: #ffffff;
|
||||
$uni-color-black: #000000;
|
||||
$uni-bg-color: #f5f5f5;
|
||||
$uni-text-color: #333333;
|
||||
$uni-text-color-grey: #999999;
|
||||
$uni-border-color: #eeeeee;
|
||||
|
||||
/* 主题色变量(兼容部分 vant 变量) */
|
||||
:root {
|
||||
--van-primary-color: #07c160;
|
||||
--van-orange: #ff9f00;
|
||||
--van-text: #333333;
|
||||
--van-text-secondary: #999999;
|
||||
--van-cream: #f8f6f3;
|
||||
--van-brown: #8b6914;
|
||||
}
|
||||
124
utils/api.js
@ -1,124 +0,0 @@
|
||||
// utils/api.js - uniapp API 封装
|
||||
const BASE_URL = 'http://localhost:8080/api'
|
||||
|
||||
// 统一请求封装
|
||||
const request = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: BASE_URL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: options.header || {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve(res.data)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const get = (url, params = {}) => {
|
||||
return request({ url, method: 'GET', data: params })
|
||||
}
|
||||
|
||||
const post = (url, data = {}) => {
|
||||
return request({ url, method: 'POST', data })
|
||||
}
|
||||
|
||||
const put = (url, data = {}) => {
|
||||
return request({ url, method: 'PUT', data })
|
||||
}
|
||||
|
||||
const del = (url) => {
|
||||
return request({ url, method: 'DELETE' })
|
||||
}
|
||||
|
||||
// 图片完整URL
|
||||
export const imgUrl = (path) => {
|
||||
if (!path) return ''
|
||||
if (path.startsWith('http')) return path
|
||||
return `http://localhost:8080${path}`
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
export const sendSms = (phone) => post('/sms/send', { phone })
|
||||
|
||||
// 登录
|
||||
export const login = (phone, code) => post('/user/login', { phone, code })
|
||||
|
||||
// 注册老板
|
||||
export const registerBoss = (data) => post('/user/register-boss', data)
|
||||
|
||||
// 注册员工
|
||||
export const registerStaff = (data) => post('/user/register-staff', data)
|
||||
|
||||
// 预约列表
|
||||
export const getAppointmentList = (userId) => get('/appointment/list', { userId })
|
||||
|
||||
// 创建预约
|
||||
export const createAppointment = (data) => post('/appointment/create', data)
|
||||
|
||||
// 开始服务
|
||||
export const startAppointment = (appointmentId, staffUserId) =>
|
||||
post('/appointment/start', { appointmentId, staffUserId })
|
||||
|
||||
// 取消预约
|
||||
export const cancelAppointment = (id) => put(`/appointment/status?id=${id}&status=cancel`)
|
||||
|
||||
// 提交报告
|
||||
export const createReport = (data) => post('/report/create', data)
|
||||
|
||||
// 获取报告(通过token)
|
||||
export const getReportByToken = (token) => get('/report/get', { token })
|
||||
|
||||
// 报告列表
|
||||
export const getReportList = (params) => get('/report/list', params)
|
||||
|
||||
// 服务类型列表
|
||||
export const getServiceTypeList = (storeId) => get('/service-type/list', { storeId })
|
||||
|
||||
// 创建服务类型
|
||||
export const createServiceType = (storeId, name) => post('/service-type/create', { storeId, name })
|
||||
|
||||
// 删除服务类型
|
||||
export const deleteServiceType = (id) => del(`/service-type/delete?id=${id}`)
|
||||
|
||||
// 员工列表
|
||||
export const getStaffList = (storeId) => get('/user/staff-list', { storeId })
|
||||
|
||||
// 添加员工
|
||||
export const createStaff = (data) => post('/user/create-staff', data)
|
||||
|
||||
// 删除员工
|
||||
export const deleteStaff = (staffId) => del(`/user/staff?staffId=${staffId}`)
|
||||
|
||||
// 更新店铺
|
||||
export const updateStore = (data) => put('/store/update', data)
|
||||
|
||||
// 上传图片(uniapp版)
|
||||
export const uploadImage = (filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: BASE_URL + '/upload/image',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
resolve(data)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||