Compare commits
No commits in common. "f192d89534c488a44ded0a0821b65d615b31c988" and "0b45e557b6942616f759d2c9b0a5740382c0a318" have entirely different histories.
f192d89534
...
0b45e557b6
@ -1,3 +1 @@
|
|||||||
# 小程序构建时的 API 域名(api.s-good.com 已挪作他用,勿再指向)
|
VITE_API_ORIGIN=https://api.s-good.com
|
||||||
# 提审/体验版前请改为宠小它专用域名,并在微信后台配置合法域名
|
|
||||||
VITE_API_ORIGIN=http://localhost:8080
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# 宠小它生产 API 域名(api.s-good.com 已挪作他用,勿再指向)
|
VITE_API_ORIGIN=https://api.s-good.com
|
||||||
# 部署前请改为宠小它专用域名,例如 https://api.chongxiaota.example
|
# 部署了 report.html 的 H5 站点根(无尾斜杠),用于「生成报告」后复制链接与二维码;与 API 域名可不同
|
||||||
VITE_API_ORIGIN=http://localhost:8080
|
# VITE_REPORT_PUBLIC_ORIGIN=https://www.example.com
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,8 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
node_modules
|
||||||
dist/
|
dist/
|
||||||
.uniapp-temp/
|
|
||||||
unpackage/
|
|
||||||
.DS_Store
|
|
||||||
*.local
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|||||||
1
.gitignore 2
Normal file
1
.gitignore 2
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
21
.uniapp-temp/.gitignore
vendored
Normal file
21
.uniapp-temp/.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
20
.uniapp-temp/index.html
Normal file
20
.uniapp-temp/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
|
CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
71
.uniapp-temp/package.json
Normal file
71
.uniapp-temp/package.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "uni-preset-vue",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-jd": "uni -p mp-jd",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-harmony": "uni -p mp-harmony",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:mp-xhs": "uni -p mp-xhs",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-jd": "uni build -p mp-jd",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-harmony": "uni build -p mp-harmony",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:mp-xhs": "uni build -p mp-xhs",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-harmony": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4080420251103001",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.1.9"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4080420251103001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4080420251103001",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"@vue/runtime-core": "^3.4.21",
|
||||||
|
"typescript": "^4.9.4",
|
||||||
|
"vite": "5.2.8",
|
||||||
|
"vue-tsc": "^1.0.24"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
.uniapp-temp/shims-uni.d.ts
vendored
Normal file
10
.uniapp-temp/shims-uni.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/// <reference types='@dcloudio/types' />
|
||||||
|
import 'vue'
|
||||||
|
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
|
||||||
|
interface ComponentCustomOptions extends Hooks {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
13
.uniapp-temp/src/App.vue
Normal file
13
.uniapp-temp/src/App.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
|
onLaunch(() => {
|
||||||
|
console.log("App Launch");
|
||||||
|
});
|
||||||
|
onShow(() => {
|
||||||
|
console.log("App Show");
|
||||||
|
});
|
||||||
|
onHide(() => {
|
||||||
|
console.log("App Hide");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style></style>
|
||||||
8
.uniapp-temp/src/env.d.ts
vendored
Normal file
8
.uniapp-temp/src/env.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
8
.uniapp-temp/src/main.ts
Normal file
8
.uniapp-temp/src/main.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createSSRApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
};
|
||||||
|
}
|
||||||
72
.uniapp-temp/src/manifest.json
Normal file
72
.uniapp-temp/src/manifest.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"name" : "",
|
||||||
|
"appid" : "",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.0",
|
||||||
|
"versionCode" : "100",
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : true,
|
||||||
|
"waiting" : true,
|
||||||
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp" : {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin" : {
|
||||||
|
"appid" : "",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false
|
||||||
|
},
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"uniStatistics": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"vueVersion" : "3"
|
||||||
|
}
|
||||||
16
.uniapp-temp/src/pages.json
Normal file
16
.uniapp-temp/src/pages.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "uni-app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "uni-app",
|
||||||
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
.uniapp-temp/src/pages/index/index.vue
Normal file
41
.uniapp-temp/src/pages/index/index.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<view class="content">
|
||||||
|
<image class="logo" src="/static/logo.png" />
|
||||||
|
<view class="text-area">
|
||||||
|
<text class="title">{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const title = ref('Hello')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 200rpx;
|
||||||
|
width: 200rpx;
|
||||||
|
margin-top: 200rpx;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #8f8f94;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
.uniapp-temp/src/shime-uni.d.ts
vendored
Normal file
6
.uniapp-temp/src/shime-uni.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
declare module "vue" {
|
||||||
|
type Hooks = App.AppInstance & Page.PageInstance;
|
||||||
|
interface ComponentCustomOptions extends Hooks {}
|
||||||
|
}
|
||||||
BIN
.uniapp-temp/src/static/logo.png
Normal file
BIN
.uniapp-temp/src/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
76
.uniapp-temp/src/uni.scss
Normal file
76
.uniapp-temp/src/uni.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color: #333; // 基本色
|
||||||
|
$uni-text-color-inverse: #fff; // 反色
|
||||||
|
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable: #c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color: #fff;
|
||||||
|
$uni-bg-color-grey: #f8f8f8;
|
||||||
|
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
|
||||||
|
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color: #c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm: 12px;
|
||||||
|
$uni-font-size-base: 14px;
|
||||||
|
$uni-font-size-lg: 16;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm: 20px;
|
||||||
|
$uni-img-size-base: 26px;
|
||||||
|
$uni-img-size-lg: 40px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 2px;
|
||||||
|
$uni-border-radius-base: 3px;
|
||||||
|
$uni-border-radius-lg: 6px;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 5px;
|
||||||
|
$uni-spacing-row-base: 10px;
|
||||||
|
$uni-spacing-row-lg: 15px;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 4px;
|
||||||
|
$uni-spacing-col-base: 8px;
|
||||||
|
$uni-spacing-col-lg: 12px;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2c405a; // 文章标题颜色
|
||||||
|
$uni-font-size-title: 20px;
|
||||||
|
$uni-color-subtitle: #555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle: 18px;
|
||||||
|
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph: 15px;
|
||||||
13
.uniapp-temp/tsconfig.json
Normal file
13
.uniapp-temp/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"types": ["@dcloudio/types"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
7
.uniapp-temp/vite.config.ts
Normal file
7
.uniapp-temp/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import uni from "@dcloudio/vite-plugin-uni";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [uni()],
|
||||||
|
});
|
||||||
63
App 2.vue
Normal file
63
App 2.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onLaunch() {
|
||||||
|
console.log('App Launch')
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.log('App Show')
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
console.log('App Hide')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import './uni.scss';
|
||||||
|
|
||||||
|
/* 全局样式 */
|
||||||
|
page {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333333;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用卡片 */
|
||||||
|
.card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 12px 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用按钮样式 */
|
||||||
|
.btn-primary {
|
||||||
|
background: #07c160 !important;
|
||||||
|
border-color: #07c160 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default {
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-color: #eeeeee !important;
|
||||||
|
color: #333333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用标题 */
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用分割线 */
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #eeeeee;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
41
README.md
41
README.md
@ -115,7 +115,7 @@ npm run dev:h5
|
|||||||
|
|
||||||
1. **本地构建小程序**
|
1. **本地构建小程序**
|
||||||
```bash
|
```bash
|
||||||
npm --prefix frontend run build:mp-weixin
|
npm run build:mp-weixin
|
||||||
```
|
```
|
||||||
产物目录:`dist/build/mp-weixin/`(以构建日志为准)。
|
产物目录:`dist/build/mp-weixin/`(以构建日志为准)。
|
||||||
|
|
||||||
@ -133,40 +133,5 @@ npm run dev:h5
|
|||||||
|
|
||||||
### 宠主预约草稿(P0 C3)
|
### 宠主预约草稿(P0 C3)
|
||||||
|
|
||||||
当前实现:**提交时登录 + guest 草稿恢复**。未登录用户可填写预约表单,提交时跳转登录页(带 `redirect` 回预约页并保留 `storeId`),登录成功后回到预约页并恢复 guest 草稿(草稿 key 不依赖 `userInfo.id`,避免 `userId: undefined`)。登录态用户继续按 `userId` 维度保存草稿。草稿有效期 7 天。
|
当前实现:**已登录**用户在 `CustAppointmentCreate` 填写的内容会 debounce 写入本地 `uni.storage`(7 天有效),离开页/重进可恢复;提交成功会清除草稿。
|
||||||
|
产品文档中的 **「未登录可先填单、提交时再登录并恢复草稿」** 需同时将「新建预约」页加入公开路由(如 `App.vue` 的 `isPublicRoute`)并在登录回跳后合并草稿;若未做公开路由,未登录用户仍会在进入预约页前被拦截到登录。
|
||||||
## 前端 RC 发布门禁
|
|
||||||
|
|
||||||
发布前必须依次执行并全部通过:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 安装依赖
|
|
||||||
npm --prefix frontend install
|
|
||||||
|
|
||||||
# 2. H5 构建
|
|
||||||
npm --prefix frontend run build:h5
|
|
||||||
# 期望:DONE Build complete.
|
|
||||||
|
|
||||||
# 3. 微信小程序构建
|
|
||||||
npm --prefix frontend run build:mp-weixin
|
|
||||||
# 期望:DONE Build complete. + [verify-mp-app-json] OK
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端 RC 检查项
|
|
||||||
|
|
||||||
- [ ] `VITE_API_ORIGIN` 已配置(宠小它专用后端 API 域名;**勿再用** `api.s-good.com`)
|
|
||||||
- [ ] `VITE_REPORT_PUBLIC_ORIGIN` 已配置(宠小它 H5 报告页域名,无尾斜杠;**勿再用** `www.s-good.com` 除非仍归属本项目)
|
|
||||||
- [ ] 微信小程序后台 **request 合法域名** 已加入 `VITE_API_ORIGIN`
|
|
||||||
- [ ] 微信小程序后台 **downloadFile 合法域名** 已加入 `VITE_API_ORIGIN`(成片下载)与 `VITE_REPORT_PUBLIC_ORIGIN`(H5 报告页资源,若需)
|
|
||||||
- [ ] 报告二维码链接检查:扫码后 H5 报告页可正常打开,token 匿名可访问
|
|
||||||
- [ ] 公开报告页 customer 登录态仍显示留资/预约 CTA;boss/staff 才显示内部分享/保存
|
|
||||||
- [ ] 未登录预约:填写 → 提交跳登录 → 登录后草稿恢复(门店、宠物、服务、时段、备注)
|
|
||||||
- [ ] 报告提交:无前图提示「请至少上传1张服务前照片」;无后图提示「请至少上传1张服务后照片」
|
|
||||||
- [ ] 小程序码 scene 解码:`scene=storeId%3D1` 可预填门店
|
|
||||||
|
|
||||||
## 环境变量
|
|
||||||
|
|
||||||
| 变量 | 必填 | 默认 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| `VITE_API_ORIGIN` | 生产必填 | `http://localhost:8080` | 后端 API 域名(用于 `uni.request` 与图片 URL 拼接)。**勿使用已挪作他用的 `api.s-good.com`** |
|
|
||||||
| `VITE_REPORT_PUBLIC_ORIGIN` | 生产必填 | 空 | H5 报告页域名(用于报告分享链接与二维码) |
|
|
||||||
|
|||||||
25
package-lock.json
generated
25
package-lock.json
generated
@ -3260,7 +3260,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3274,7 +3273,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3288,7 +3286,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3302,7 +3299,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3316,7 +3312,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3330,7 +3325,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3344,7 +3338,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3358,7 +3351,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3372,7 +3364,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3386,7 +3377,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3400,7 +3390,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3414,7 +3403,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3428,7 +3416,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3442,7 +3429,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3456,7 +3442,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3470,7 +3455,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3484,7 +3468,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3498,7 +3481,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3512,7 +3494,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3526,7 +3507,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3540,7 +3520,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3554,7 +3533,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3568,7 +3546,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3582,7 +3559,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@ -3596,7 +3572,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@ -1,21 +1,13 @@
|
|||||||
const API_ORIGIN = (import.meta.env.VITE_API_ORIGIN || 'http://localhost:8080').replace(/\/$/, '')
|
const API_ORIGIN = (import.meta.env.VITE_API_ORIGIN || 'http://localhost:8080').replace(/\/$/, '')
|
||||||
const BASE_URL = `${API_ORIGIN}/api`
|
const BASE_URL = `${API_ORIGIN}/api`
|
||||||
|
|
||||||
import { getSessionToken } from '../utils/session.js'
|
|
||||||
|
|
||||||
// uniapp request 封装
|
// uniapp request 封装
|
||||||
const request = (options) => new Promise((resolve, reject) => {
|
const request = (options) => new Promise((resolve, reject) => {
|
||||||
// 自动附加 Authorization: Bearer <sessionToken>(若存在)
|
|
||||||
const headers = { ...(options.header || {}) }
|
|
||||||
const token = getSessionToken()
|
|
||||||
if (token && !headers.Authorization) {
|
|
||||||
headers.Authorization = `Bearer ${token}`
|
|
||||||
}
|
|
||||||
uni.request({
|
uni.request({
|
||||||
url: BASE_URL + options.url,
|
url: BASE_URL + options.url,
|
||||||
method: options.method || 'GET',
|
method: options.method || 'GET',
|
||||||
data: options.data || {},
|
data: options.data || {},
|
||||||
header: headers,
|
header: options.header || {},
|
||||||
success: (res) => resolve(res.data),
|
success: (res) => resolve(res.data),
|
||||||
fail: (err) => reject(err)
|
fail: (err) => reject(err)
|
||||||
})
|
})
|
||||||
@ -65,42 +57,42 @@ export const registerBoss = (data) => post('/user/register-boss', data)
|
|||||||
// 注册员工
|
// 注册员工
|
||||||
export const registerStaff = (data) => post('/user/register-staff', data)
|
export const registerStaff = (data) => post('/user/register-staff', data)
|
||||||
|
|
||||||
// 宠物档案(身份由后端从 session token 派生;保留旧签名兼容,不再传 ownerUserId/storeId 给后端)
|
// 宠物档案(客户:ownerUserId;门店:storeId)
|
||||||
export const getPetList = (/* params */) => get('/pet/list', {})
|
export const getPetList = (params) => get('/pet/list', params)
|
||||||
export const createPet = (data) => post('/pet/create', data)
|
export const createPet = (data) => post('/pet/create', data)
|
||||||
export const updatePet = (data) => put('/pet/update', data)
|
export const updatePet = (data) => put('/pet/update', data)
|
||||||
export const deletePet = (id/*, operatorUserId, role */) =>
|
export const deletePet = (id, operatorUserId, role) =>
|
||||||
del(`/pet/delete?id=${id}`)
|
del(`/pet/delete?id=${id}&operatorUserId=${operatorUserId}&role=${encodeURIComponent(role)}`)
|
||||||
|
|
||||||
/** 按宠物:预约 + 报告时间线(身份由后端从 session token 派生) */
|
/** 按宠物:预约 + 报告时间线(需 operatorUserId + role 鉴权) */
|
||||||
export const getPetHistory = (petId/*, operatorUserId, role */) =>
|
export const getPetHistory = (petId, operatorUserId, role) =>
|
||||||
get('/pet/history', { petId })
|
get('/pet/history', { petId, operatorUserId, role })
|
||||||
|
|
||||||
// 预约列表(身份由后端从 session token 派生;保留旧签名兼容,不传 userId/storeId)
|
// 预约列表
|
||||||
export const getAppointmentList = (/* userId, storeId, */ options = {}) =>
|
export const getAppointmentList = (userId, storeId, options = {}) =>
|
||||||
get('/appointment/list', { status: options.status, page: options.page, pageSize: options.pageSize })
|
get('/appointment/list', { userId, storeId, ...options })
|
||||||
|
|
||||||
// 预约详情
|
// 预约详情
|
||||||
export const getAppointmentDetail = (id) => get('/appointment/detail', { id })
|
export const getAppointmentDetail = (id) => get('/appointment/detail', { id })
|
||||||
|
|
||||||
// 创建预约(userId/storeId 由后端从上下文派生;前端仍传 storeId 供 customer 选店场景,后端会按 role 决定是否采纳)
|
// 创建预约
|
||||||
export const createAppointment = (data) => post('/appointment/create', data)
|
export const createAppointment = (data) => post('/appointment/create', data)
|
||||||
|
|
||||||
/** 门店某日可预约半小时档(可约 / 已满 / 已过);公开接口 */
|
/** 门店某日可预约半小时档(可约 / 已满 / 已过) */
|
||||||
export const getAppointmentAvailableSlots = (storeId, date) =>
|
export const getAppointmentAvailableSlots = (storeId, date) =>
|
||||||
get('/appointment/available-slots', { storeId, date })
|
get('/appointment/available-slots', { storeId, date })
|
||||||
|
|
||||||
// 开始服务(staffUserId 由后端从上下文派生;保留旧签名兼容,不传 staffUserId)
|
// 开始服务
|
||||||
export const startAppointment = (appointmentId/*, staffUserId */) =>
|
export const startAppointment = (appointmentId, staffUserId) =>
|
||||||
post('/appointment/start', { appointmentId })
|
post('/appointment/start', { appointmentId, staffUserId })
|
||||||
|
|
||||||
// 取消预约(身份由后端从上下文派生)
|
// 取消预约
|
||||||
export const cancelAppointment = (id) => put(`/appointment/status?id=${id}&status=cancel`)
|
export const cancelAppointment = (id) => put(`/appointment/status?id=${id}&status=cancel`)
|
||||||
|
|
||||||
// 提交报告
|
// 提交报告
|
||||||
export const createReport = (data) => post('/report/create', data)
|
export const createReport = (data) => post('/report/create', data)
|
||||||
|
|
||||||
/** 触发服务回顾短片生成(异步)。composeMode: preset | interleave;身份由后端从上下文派生 */
|
/** 触发服务回顾短片生成(异步)。composeMode: preset | interleave,时长由后端按素材计算 */
|
||||||
export const postReportHighlightStart = (data) => post('/report/highlight/start', data)
|
export const postReportHighlightStart = (data) => post('/report/highlight/start', data)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,9 +105,8 @@ export const getReportByToken = (token) => get('/report/get', { token })
|
|||||||
export const postReportOpenTrack = (token, visitType) =>
|
export const postReportOpenTrack = (token, visitType) =>
|
||||||
post('/report/open-track', { token, visitType: visitType || 'unknown' })
|
post('/report/open-track', { token, visitType: visitType || 'unknown' })
|
||||||
|
|
||||||
/** 报告列表;身份由后端从上下文派生(boss/staff 查本店,customer 查自己) */
|
/** 报告列表;元素含与 getReportByToken 相同的短片字段(见 docs/API-报告成片字段契约.md) */
|
||||||
export const getReportList = (/* params */ options = {}) =>
|
export const getReportList = (params) => get('/report/list', params)
|
||||||
get('/report/list', { page: options.page, pageSize: options.pageSize })
|
|
||||||
|
|
||||||
// 报告页:下次服务建议日期
|
// 报告页:下次服务建议日期
|
||||||
export const getReportSuggestion = (token) => get(`/report/${encodeURIComponent(token)}/suggestion`)
|
export const getReportSuggestion = (token) => get(`/report/${encodeURIComponent(token)}/suggestion`)
|
||||||
@ -132,9 +123,9 @@ export const submitReportReminder = (token, phone, loginCode) =>
|
|||||||
export const unsubscribeReportReminder = (unsubscribeToken) =>
|
export const unsubscribeReportReminder = (unsubscribeToken) =>
|
||||||
post('/report/unsubscribe', { unsubscribeToken })
|
post('/report/unsubscribe', { unsubscribeToken })
|
||||||
|
|
||||||
// 老板后台:回访池列表(storeId 由后端从上下文派生;保留旧签名兼容)
|
// 老板后台:回访池列表
|
||||||
export const getReportLeads = (/* storeId, */ status = 'pending') =>
|
export const getReportLeads = (storeId, status = 'pending') =>
|
||||||
get('/report/leads', { status })
|
get('/report/leads', { storeId, status })
|
||||||
|
|
||||||
// 报告页:宠主寄语(生成海报前提交,供口碑墙)
|
// 报告页:宠主寄语(生成海报前提交,供口碑墙)
|
||||||
export const submitReportTestimonial = (token, content, isPublic = true) =>
|
export const submitReportTestimonial = (token, content, isPublic = true) =>
|
||||||
@ -153,16 +144,16 @@ export const createServiceType = (storeId, name) => post('/service-type/create',
|
|||||||
// 删除服务类型
|
// 删除服务类型
|
||||||
export const deleteServiceType = (id) => del(`/service-type/delete?id=${id}`)
|
export const deleteServiceType = (id) => del(`/service-type/delete?id=${id}`)
|
||||||
|
|
||||||
// 员工列表(storeId 由后端从上下文派生;保留旧签名兼容)
|
// 员工列表
|
||||||
export const getStaffList = (/* storeId */) => get('/user/staff-list', {})
|
export const getStaffList = (storeId) => get('/user/staff-list', { storeId })
|
||||||
|
|
||||||
// 添加员工(storeId 由后端从上下文派生)
|
// 添加员工
|
||||||
export const createStaff = (data) => post('/user/create-staff', data)
|
export const createStaff = (data) => post('/user/create-staff', data)
|
||||||
|
|
||||||
// 删除员工
|
// 删除员工
|
||||||
export const deleteStaff = (staffId) => del(`/user/staff?staffId=${staffId}`)
|
export const deleteStaff = (staffId) => del(`/user/staff?staffId=${staffId}`)
|
||||||
|
|
||||||
// 更新当前登录用户信息(id 由后端从上下文派生)
|
// 更新用户信息
|
||||||
export const updateUser = (data) => put('/user/update', data)
|
export const updateUser = (data) => put('/user/update', data)
|
||||||
|
|
||||||
// 门店列表(客户预约选店)
|
// 门店列表(客户预约选店)
|
||||||
@ -185,14 +176,10 @@ export const deleteScheduleBlock = (id) => del(`/schedule/block?id=${id}`)
|
|||||||
// 上传图片(uniapp 版:使用 uni.uploadFile)
|
// 上传图片(uniapp 版:使用 uni.uploadFile)
|
||||||
export const uploadImage = (filePath) =>
|
export const uploadImage = (filePath) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const header = {}
|
|
||||||
const token = getSessionToken()
|
|
||||||
if (token) header.Authorization = `Bearer ${token}`
|
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: `${BASE_URL}/upload/image`,
|
url: `${BASE_URL}/upload/image`,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
name: 'file',
|
name: 'file',
|
||||||
header,
|
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(res.data)
|
const data = JSON.parse(res.data)
|
||||||
|
|||||||
@ -129,7 +129,7 @@ import {
|
|||||||
getAppointmentAvailableSlots
|
getAppointmentAvailableSlots
|
||||||
} from '../../api/index.js'
|
} from '../../api/index.js'
|
||||||
import AppIcon from '../../components/AppIcon.vue'
|
import AppIcon from '../../components/AppIcon.vue'
|
||||||
import { getUserSession, getStoreSession, setStoreSession, getGuestApptDraft, saveGuestApptDraft, clearGuestApptDraft, apptDraftUserKey } from '../../utils/session.js'
|
import { getUserSession, getStoreSession, setStoreSession } from '../../utils/session.js'
|
||||||
import { haversineKm } from '../../utils/geo.js'
|
import { haversineKm } from '../../utils/geo.js'
|
||||||
import { normalizeToHalfHour, ymdLocal, formatHalfHourSlotRange } from '../../utils/halfHourTime.js'
|
import { normalizeToHalfHour, ymdLocal, formatHalfHourSlotRange } from '../../utils/halfHourTime.js'
|
||||||
|
|
||||||
@ -146,28 +146,14 @@ const lastServiceStorageKey = (storeId) =>
|
|||||||
const routeStoreId = ref('')
|
const routeStoreId = ref('')
|
||||||
const storeExpanded = ref(false)
|
const storeExpanded = ref(false)
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析小程序码 scene 字段中的 storeId。
|
|
||||||
* 支持形如 `storeId=1`、`sid=1`、`?storeId=1` 的 query 串;也兼容被 encodeURIComponent 编码过的字符串。
|
|
||||||
*/
|
|
||||||
function parseSceneStoreId(scene) {
|
|
||||||
if (!scene) return ''
|
|
||||||
let decoded = String(scene)
|
|
||||||
try { decoded = decodeURIComponent(decoded) } catch (_) {}
|
|
||||||
const params = new URLSearchParams(decoded.replace(/^\?/, ''))
|
|
||||||
return params.get('storeId') || params.get('sid') || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderedStores = ref([])
|
const orderedStores = ref([])
|
||||||
const selectedStoreIndex = ref(0)
|
const selectedStoreIndex = ref(0)
|
||||||
const hasUserLocation = ref(false)
|
const hasUserLocation = ref(false)
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
// 读取顺序:普通 storeId query > 小程序码 scene 解码
|
if (options?.storeId != null && String(options.storeId).trim() !== '') {
|
||||||
const directStoreId = options?.storeId != null && String(options.storeId).trim() !== ''
|
routeStoreId.value = String(options.storeId).trim()
|
||||||
? String(options.storeId).trim()
|
}
|
||||||
: ''
|
|
||||||
routeStoreId.value = directStoreId || parseSceneStoreId(options?.scene)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const storeLockedCompact = computed(() => {
|
const storeLockedCompact = computed(() => {
|
||||||
@ -578,14 +564,6 @@ const confirmNewAppt = async () => {
|
|||||||
uni.showToast({ title: '请先选择预约门店', icon: 'none' })
|
uni.showToast({ title: '请先选择预约门店', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 提交时登录策略:未登录先保存 guest 草稿,跳登录页,登录后回到本页并恢复草稿。
|
|
||||||
if (!userInfo?.id) {
|
|
||||||
saveApptDraft()
|
|
||||||
const target = `/pages/appointment/CustAppointmentCreate?storeId=${encodeURIComponent(storeId)}`
|
|
||||||
const redirect = encodeURIComponent(target)
|
|
||||||
uni.navigateTo({ url: `/pages/login/Login?redirect=${redirect}` })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
creatingAppt.value = true
|
creatingAppt.value = true
|
||||||
const ensuredPet = await ensurePetProfile(a.petName, a.petType)
|
const ensuredPet = await ensurePetProfile(a.petName, a.petType)
|
||||||
if (!ensuredPet?.id) {
|
if (!ensuredPet?.id) {
|
||||||
@ -603,9 +581,7 @@ const confirmNewAppt = async () => {
|
|||||||
uni.setStorageSync(lastServiceStorageKey(storeId), a.serviceType)
|
uni.setStorageSync(lastServiceStorageKey(storeId), a.serviceType)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
try {
|
try {
|
||||||
const key = apptDraftStorageKey()
|
uni.removeStorageSync(apptDraftStorageKey())
|
||||||
if (key) uni.removeStorageSync(key)
|
|
||||||
clearGuestApptDraft()
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
uni.showToast({ title: '预约成功', icon: 'success' })
|
uni.showToast({ title: '预约成功', icon: 'success' })
|
||||||
setTimeout(() => uni.navigateBack(), 600)
|
setTimeout(() => uni.navigateBack(), 600)
|
||||||
@ -621,11 +597,6 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadMyPets = async () => {
|
const loadMyPets = async () => {
|
||||||
// 未登录时跳过宠物列表请求;提交时登录后会重新加载本页并恢复草稿。
|
|
||||||
if (!userInfo?.id) {
|
|
||||||
myPets.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const res = await getPetList({ ownerUserId: userInfo.id })
|
const res = await getPetList({ ownerUserId: userInfo.id })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
myPets.value = res.data || []
|
myPets.value = res.data || []
|
||||||
@ -633,86 +604,56 @@ const loadMyPets = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** P0 C3:意外退出 / 返回栈恢复草稿(7 天);与「未登录可填单」全量能力见 README) */
|
||||||
* P0 C3:草稿策略冻结为「提交时登录 + guest 草稿恢复」。
|
const apptDraftStorageKey = () => `petstore_cust_appt_draft_v1_${userInfo.id || '0'}`
|
||||||
* - 未登录:写入 guest 草稿 key(不依赖 userId)。
|
|
||||||
* - 登录后:优先读 guest 草稿恢复,恢复成功后删除 guest 草稿;同时按 userId 维度保存草稿。
|
|
||||||
* 草稿有效期 7 天。
|
|
||||||
*/
|
|
||||||
const apptDraftStorageKey = () => {
|
|
||||||
const uid = userInfo?.id
|
|
||||||
return uid ? apptDraftUserKey(uid) : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildDraftPayload = () => {
|
|
||||||
const row = orderedStores.value[selectedStoreIndex.value]
|
|
||||||
return {
|
|
||||||
v: 1,
|
|
||||||
at: Date.now(),
|
|
||||||
petName: newAppt.value.petName,
|
|
||||||
petType: newAppt.value.petType,
|
|
||||||
serviceType: newAppt.value.serviceType,
|
|
||||||
appointmentTime: newAppt.value.appointmentTime,
|
|
||||||
remark: newAppt.value.remark,
|
|
||||||
selectedDateStr: selectedDateStr.value,
|
|
||||||
storeIndex: selectedStoreIndex.value,
|
|
||||||
storeId: row?.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyDraft = (d) => {
|
|
||||||
if (!d || d.v !== 1) return
|
|
||||||
if (typeof d.remark === 'string') newAppt.value.remark = d.remark
|
|
||||||
if (d.serviceType) newAppt.value.serviceType = d.serviceType
|
|
||||||
if (d.appointmentTime) newAppt.value.appointmentTime = d.appointmentTime
|
|
||||||
if (d.petName) newAppt.value.petName = d.petName
|
|
||||||
if (d.petType) newAppt.value.petType = d.petType
|
|
||||||
if (typeof d.selectedDateStr === 'string' && d.selectedDateStr) selectedDateStr.value = d.selectedDateStr
|
|
||||||
if (typeof d.storeIndex === 'number' && orderedStores.value[d.storeIndex]) {
|
|
||||||
selectedStoreIndex.value = d.storeIndex
|
|
||||||
applySelectedStoreToSession()
|
|
||||||
} else if (d.storeId && orderedStores.value.some((x) => String(x.id) === String(d.storeId))) {
|
|
||||||
selectedStoreIndex.value = orderedStores.value.findIndex((x) => String(x.id) === String(d.storeId))
|
|
||||||
applySelectedStoreToSession()
|
|
||||||
}
|
|
||||||
selectedPetId.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveApptDraft = () => {
|
const saveApptDraft = () => {
|
||||||
|
if (!userInfo?.id) return
|
||||||
try {
|
try {
|
||||||
const payload = buildDraftPayload()
|
const row = orderedStores.value[selectedStoreIndex.value]
|
||||||
// 未登录:只写 guest 草稿;登录态:按 userId 维度保存。
|
uni.setStorageSync(
|
||||||
if (!userInfo?.id) {
|
apptDraftStorageKey(),
|
||||||
saveGuestApptDraft(payload)
|
JSON.stringify({
|
||||||
return
|
v: 1,
|
||||||
}
|
at: Date.now(),
|
||||||
const key = apptDraftStorageKey()
|
petName: newAppt.value.petName,
|
||||||
if (key) uni.setStorageSync(key, JSON.stringify(payload))
|
petType: newAppt.value.petType,
|
||||||
|
serviceType: newAppt.value.serviceType,
|
||||||
|
appointmentTime: newAppt.value.appointmentTime,
|
||||||
|
remark: newAppt.value.remark,
|
||||||
|
selectedDateStr: selectedDateStr.value,
|
||||||
|
storeIndex: selectedStoreIndex.value,
|
||||||
|
storeId: row?.id
|
||||||
|
})
|
||||||
|
)
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoreApptDraft = () => {
|
const restoreApptDraft = () => {
|
||||||
|
if (!userInfo?.id) return
|
||||||
try {
|
try {
|
||||||
// 登录后优先恢复 guest 草稿(提交时登录场景),恢复后清理 guest 草稿。
|
const raw = uni.getStorageSync(apptDraftStorageKey())
|
||||||
const guest = getGuestApptDraft()
|
|
||||||
if (guest) {
|
|
||||||
if (Date.now() - (guest.at || 0) <= 7 * 86400000) {
|
|
||||||
applyDraft(guest)
|
|
||||||
}
|
|
||||||
clearGuestApptDraft()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!userInfo?.id) return
|
|
||||||
const key = apptDraftStorageKey()
|
|
||||||
if (!key) return
|
|
||||||
const raw = uni.getStorageSync(key)
|
|
||||||
if (!raw) return
|
if (!raw) return
|
||||||
const d = JSON.parse(raw)
|
const d = JSON.parse(raw)
|
||||||
|
if (!d || d.v !== 1) return
|
||||||
if (Date.now() - (d.at || 0) > 7 * 86400000) {
|
if (Date.now() - (d.at || 0) > 7 * 86400000) {
|
||||||
uni.removeStorageSync(key)
|
uni.removeStorageSync(apptDraftStorageKey())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
applyDraft(d)
|
if (typeof d.remark === 'string') newAppt.value.remark = d.remark
|
||||||
|
if (d.serviceType) newAppt.value.serviceType = d.serviceType
|
||||||
|
if (d.appointmentTime) newAppt.value.appointmentTime = d.appointmentTime
|
||||||
|
if (d.petName) newAppt.value.petName = d.petName
|
||||||
|
if (d.petType) newAppt.value.petType = d.petType
|
||||||
|
if (typeof d.selectedDateStr === 'string' && d.selectedDateStr) selectedDateStr.value = d.selectedDateStr
|
||||||
|
if (typeof d.storeIndex === 'number' && orderedStores.value[d.storeIndex]) {
|
||||||
|
selectedStoreIndex.value = d.storeIndex
|
||||||
|
applySelectedStoreToSession()
|
||||||
|
} else if (d.storeId && orderedStores.value.some((x) => String(x.id) === String(d.storeId))) {
|
||||||
|
selectedStoreIndex.value = orderedStores.value.findIndex((x) => String(x.id) === String(d.storeId))
|
||||||
|
applySelectedStoreToSession()
|
||||||
|
}
|
||||||
|
selectedPetId.value = null
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
import { ref, reactive, onUnmounted } from 'vue'
|
import { ref, reactive, onUnmounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { sendSms, login, wxPhoneLogin, registerBoss, registerStaff } from '../../api/index.js'
|
import { sendSms, login, wxPhoneLogin, registerBoss, registerStaff } from '../../api/index.js'
|
||||||
import { setStoreSession, setUserSession, setSessionToken } from '../../utils/session.js'
|
import { setStoreSession, setUserSession } from '../../utils/session.js'
|
||||||
import { openAppPage } from '../../utils/globalState.js'
|
import { openAppPage } from '../../utils/globalState.js'
|
||||||
|
|
||||||
const view = ref('login')
|
const view = ref('login')
|
||||||
@ -269,7 +269,6 @@ const handleLogin = async () => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
setUserSession(res.data.user)
|
setUserSession(res.data.user)
|
||||||
setStoreSession(res.data.store)
|
setStoreSession(res.data.store)
|
||||||
if (res.data.sessionToken) setSessionToken(res.data.sessionToken)
|
|
||||||
navigateAfterLogin()
|
navigateAfterLogin()
|
||||||
} else {
|
} else {
|
||||||
showToast(res.message || '登录失败')
|
showToast(res.message || '登录失败')
|
||||||
@ -314,7 +313,6 @@ const onWxGetPhoneNumber = async (e) => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
setUserSession(res.data.user)
|
setUserSession(res.data.user)
|
||||||
setStoreSession(res.data.store)
|
setStoreSession(res.data.store)
|
||||||
if (res.data.sessionToken) setSessionToken(res.data.sessionToken)
|
|
||||||
navigateAfterLogin()
|
navigateAfterLogin()
|
||||||
} else {
|
} else {
|
||||||
showToast(res.message || '登录失败')
|
showToast(res.message || '登录失败')
|
||||||
|
|||||||
@ -76,13 +76,13 @@
|
|||||||
<text class="row-label">手机号</text>
|
<text class="row-label">手机号</text>
|
||||||
<text class="row-value phone">{{ item.phone }}</text>
|
<text class="row-value phone">{{ item.phone }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.wechatBound" class="lead-row">
|
<view v-if="item.wechatOpenid" class="lead-row">
|
||||||
<text class="row-label">微信</text>
|
<text class="row-label">openid</text>
|
||||||
<text class="row-value phone">已绑定 {{ item.wechatOpenidMasked || '' }}</text>
|
<text class="row-value phone">{{ item.wechatOpenid }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.wechatBound && item.wechatUnionidMasked" class="lead-row">
|
<view v-if="item.wechatUnionid" class="lead-row">
|
||||||
<text class="row-label">unionid</text>
|
<text class="row-label">unionid</text>
|
||||||
<text class="row-value phone">{{ item.wechatUnionidMasked }}</text>
|
<text class="row-value phone">{{ item.wechatUnionid }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="lead-row">
|
<view class="lead-row">
|
||||||
<text class="row-label">留资时间</text>
|
<text class="row-label">留资时间</text>
|
||||||
|
|||||||
@ -227,7 +227,7 @@ import DuringMediaGrid from '../../components/report/DuringMediaGrid.vue'
|
|||||||
import TestimonialModal from '../../components/report/TestimonialModal.vue'
|
import TestimonialModal from '../../components/report/TestimonialModal.vue'
|
||||||
import ReminderCard from '../../components/report/ReminderCard.vue'
|
import ReminderCard from '../../components/report/ReminderCard.vue'
|
||||||
import ReportHighlightBlock from '../../components/report/ReportHighlightBlock.vue'
|
import ReportHighlightBlock from '../../components/report/ReportHighlightBlock.vue'
|
||||||
import { getUserSession, isStaffRole } from '../../utils/session.js'
|
import { isLoggedIn } from '../../utils/session.js'
|
||||||
import { buildReportShareTitle, buildReportShareCoverUrl } from '../../utils/reportShare.js'
|
import { buildReportShareTitle, buildReportShareCoverUrl } from '../../utils/reportShare.js'
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@ -237,8 +237,7 @@ const reportData = ref(null)
|
|||||||
const posterCanvas = ref(null)
|
const posterCanvas = ref(null)
|
||||||
const posterModalOpen = ref(false)
|
const posterModalOpen = ref(false)
|
||||||
const routeToken = ref('')
|
const routeToken = ref('')
|
||||||
// 角色判断:仅 boss / staff 为员工态;已登录 customer 仍走宠主 CTA 分支。
|
const isStaff = computed(() => isLoggedIn())
|
||||||
const isStaff = computed(() => isStaffRole(getUserSession()?.role))
|
|
||||||
|
|
||||||
const hasHighlightSource = computed(() => {
|
const hasHighlightSource = computed(() => {
|
||||||
const r = reportData.value
|
const r = reportData.value
|
||||||
@ -257,11 +256,8 @@ const getRouteToken = () => {
|
|||||||
return token || ''
|
return token || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 报告 token:只用路由 token,不依赖回包 reportToken,避免公开页暴露内部 token 字段。 */
|
/** 报告 token:接口字段优先,兼容 H5 地址栏 */
|
||||||
const reminderToken = computed(() => getRouteToken())
|
const reminderToken = computed(() => reportData.value?.reportToken || getRouteToken())
|
||||||
|
|
||||||
/** 公开报告页可返回的门店 id(用于「我也要预约」深链;不读 top-level storeId/userId)。 */
|
|
||||||
const bookingStoreId = computed(() => reportData.value?.store?.id || reportData.value?.bookingStoreId || '')
|
|
||||||
|
|
||||||
const beforePhotos = computed(() => {
|
const beforePhotos = computed(() => {
|
||||||
if (!reportData.value?.beforePhotos) return []
|
if (!reportData.value?.beforePhotos) return []
|
||||||
@ -364,15 +360,7 @@ const loadReport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => uni.reLaunch({ url: '/pages/home/Home' })
|
||||||
// 优先进入预约页并带门店参数;无门店 id 时退回首页。
|
|
||||||
const sid = bookingStoreId.value
|
|
||||||
if (sid) {
|
|
||||||
uni.navigateTo({ url: `/pages/appointment/CustAppointmentCreate?storeId=${encodeURIComponent(sid)}` })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uni.reLaunch({ url: '/pages/home/Home' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const goBackOrHome = () => {
|
const goBackOrHome = () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -678,20 +678,14 @@ const submitReport = async () => {
|
|||||||
if (!report.value.petName) return uni.showToast({ title: '请输入宠物名字', icon: 'none' })
|
if (!report.value.petName) return uni.showToast({ title: '请输入宠物名字', icon: 'none' })
|
||||||
if (!report.value.serviceType) return uni.showToast({ title: '请选择服务类型', icon: 'none' })
|
if (!report.value.serviceType) return uni.showToast({ title: '请选择服务类型', icon: 'none' })
|
||||||
if (!report.value.appointmentTime) return uni.showToast({ title: '请选择服务时间', icon: 'none' })
|
if (!report.value.appointmentTime) return uni.showToast({ title: '请选择服务时间', icon: 'none' })
|
||||||
|
|
||||||
// 报告提交必填:服务前/服务后至少各 1 张有效照片;服务过程中素材可选。
|
|
||||||
const validBefore = report.value.beforeMedia.filter((item) => !item.uploading && !item.failed)
|
|
||||||
const validAfter = report.value.afterMedia.filter((item) => !item.uploading && !item.failed)
|
|
||||||
if (validBefore.length < 1) return uni.showToast({ title: '请至少上传1张服务前照片', icon: 'none' })
|
|
||||||
if (validAfter.length < 1) return uni.showToast({ title: '请至少上传1张服务后照片', icon: 'none' })
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
|
|
||||||
// 构建图片列表(复用上方有效列表,避免重复 filter)
|
// 构建图片列表
|
||||||
const images = []
|
const images = []
|
||||||
validBefore.forEach((item, i) => {
|
report.value.beforeMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
|
||||||
images.push({ photoUrl: item.url, photoType: 'before', mediaType: item.mediaType || 'photo', sortOrder: i })
|
images.push({ photoUrl: item.url, photoType: 'before', mediaType: item.mediaType || 'photo', sortOrder: i })
|
||||||
})
|
})
|
||||||
validAfter.forEach((item, i) => {
|
report.value.afterMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
|
||||||
images.push({ photoUrl: item.url, photoType: 'after', mediaType: item.mediaType || 'photo', sortOrder: i })
|
images.push({ photoUrl: item.url, photoType: 'after', mediaType: item.mediaType || 'photo', sortOrder: i })
|
||||||
})
|
})
|
||||||
report.value.duringMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
|
report.value.duringMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
|
||||||
|
|||||||
@ -16,18 +16,6 @@ export const isLoggedIn = () => {
|
|||||||
return !!(u && u.id)
|
return !!(u && u.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前登录用户角色(未登录返回空串) */
|
|
||||||
export const getUserRole = () => {
|
|
||||||
const u = getUserSession()
|
|
||||||
return (u && u.role) || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 是否为员工态(仅 boss / staff)。customer 登录态返回 false,避免误判为内部员工。 */
|
|
||||||
export const isStaffRole = (role) => role === 'boss' || role === 'staff'
|
|
||||||
|
|
||||||
/** 当前登录用户是否为员工态(boss / staff)。 */
|
|
||||||
export const isCurrentUserStaff = () => isStaffRole(getUserRole())
|
|
||||||
|
|
||||||
export const setUserSession = (user) => {
|
export const setUserSession = (user) => {
|
||||||
uni.setStorageSync('petstore_user', JSON.stringify(user || {}))
|
uni.setStorageSync('petstore_user', JSON.stringify(user || {}))
|
||||||
}
|
}
|
||||||
@ -36,40 +24,7 @@ export const setStoreSession = (store) => {
|
|||||||
uni.setStorageSync('petstore_store', JSON.stringify(store || {}))
|
uni.setStorageSync('petstore_store', JSON.stringify(store || {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 后端签发的 HMAC session token(登录成功返回);受保护接口自动作为 Bearer Authorization 附加。 */
|
|
||||||
export const getSessionToken = () => uni.getStorageSync('petstore_session_token') || ''
|
|
||||||
|
|
||||||
export const setSessionToken = (token) => {
|
|
||||||
if (token) uni.setStorageSync('petstore_session_token', token)
|
|
||||||
else uni.removeStorageSync('petstore_session_token')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* guest 草稿(未登录预约草稿)工具。
|
|
||||||
* 草稿 key 不依赖 userId,避免未登录时出现 userId: undefined。
|
|
||||||
*/
|
|
||||||
const GUEST_APPT_DRAFT_KEY = 'petstore_appt_draft_guest'
|
|
||||||
|
|
||||||
export const getGuestApptDraft = () => parseJSON(uni.getStorageSync(GUEST_APPT_DRAFT_KEY), null)
|
|
||||||
|
|
||||||
export const saveGuestApptDraft = (draft) => {
|
|
||||||
if (draft == null) {
|
|
||||||
uni.removeStorageSync(GUEST_APPT_DRAFT_KEY)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uni.setStorageSync(GUEST_APPT_DRAFT_KEY, JSON.stringify(draft))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clearGuestApptDraft = () => {
|
|
||||||
uni.removeStorageSync(GUEST_APPT_DRAFT_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 登录态用户维度的草稿 key(登录后可继续按 userId 维度保存草稿)。 */
|
|
||||||
export const apptDraftUserKey = (userId) => `petstore_appt_draft_${userId}`
|
|
||||||
|
|
||||||
export const clearSession = () => {
|
export const clearSession = () => {
|
||||||
uni.removeStorageSync('petstore_user')
|
uni.removeStorageSync('petstore_user')
|
||||||
uni.removeStorageSync('petstore_store')
|
uni.removeStorageSync('petstore_store')
|
||||||
uni.removeStorageSync('petstore_session_token')
|
|
||||||
uni.removeStorageSync(GUEST_APPT_DRAFT_KEY)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user