first commit
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"globals": {
|
||||||
|
"Component": true,
|
||||||
|
"ComponentPublicInstance": true,
|
||||||
|
"ComputedRef": true,
|
||||||
|
"DirectiveBinding": true,
|
||||||
|
"EffectScope": true,
|
||||||
|
"ExtractDefaultPropTypes": true,
|
||||||
|
"ExtractPropTypes": true,
|
||||||
|
"ExtractPublicPropTypes": true,
|
||||||
|
"InjectionKey": true,
|
||||||
|
"MaybeRef": true,
|
||||||
|
"MaybeRefOrGetter": true,
|
||||||
|
"PropType": true,
|
||||||
|
"Ref": true,
|
||||||
|
"VNode": true,
|
||||||
|
"WritableComputedRef": true,
|
||||||
|
"acceptHMRUpdate": true,
|
||||||
|
"computed": true,
|
||||||
|
"createApp": true,
|
||||||
|
"createPinia": true,
|
||||||
|
"customRef": true,
|
||||||
|
"defineAsyncComponent": true,
|
||||||
|
"defineComponent": true,
|
||||||
|
"defineStore": true,
|
||||||
|
"effectScope": true,
|
||||||
|
"getActivePinia": true,
|
||||||
|
"getCurrentInstance": true,
|
||||||
|
"getCurrentScope": true,
|
||||||
|
"h": true,
|
||||||
|
"inject": true,
|
||||||
|
"isProxy": true,
|
||||||
|
"isReactive": true,
|
||||||
|
"isReadonly": true,
|
||||||
|
"isRef": true,
|
||||||
|
"mapActions": true,
|
||||||
|
"mapGetters": true,
|
||||||
|
"mapState": true,
|
||||||
|
"mapStores": true,
|
||||||
|
"mapWritableState": true,
|
||||||
|
"markRaw": true,
|
||||||
|
"nextTick": true,
|
||||||
|
"onActivated": true,
|
||||||
|
"onAddToFavorites": true,
|
||||||
|
"onBackPress": true,
|
||||||
|
"onBeforeMount": true,
|
||||||
|
"onBeforeUnmount": true,
|
||||||
|
"onBeforeUpdate": true,
|
||||||
|
"onDeactivated": true,
|
||||||
|
"onError": true,
|
||||||
|
"onErrorCaptured": true,
|
||||||
|
"onHide": true,
|
||||||
|
"onLaunch": true,
|
||||||
|
"onLoad": true,
|
||||||
|
"onMounted": true,
|
||||||
|
"onNavigationBarButtonTap": true,
|
||||||
|
"onNavigationBarSearchInputChanged": true,
|
||||||
|
"onNavigationBarSearchInputClicked": true,
|
||||||
|
"onNavigationBarSearchInputConfirmed": true,
|
||||||
|
"onNavigationBarSearchInputFocusChanged": true,
|
||||||
|
"onPageNotFound": true,
|
||||||
|
"onPageScroll": true,
|
||||||
|
"onPullDownRefresh": true,
|
||||||
|
"onReachBottom": true,
|
||||||
|
"onReady": true,
|
||||||
|
"onRenderTracked": true,
|
||||||
|
"onRenderTriggered": true,
|
||||||
|
"onResize": true,
|
||||||
|
"onScopeDispose": true,
|
||||||
|
"onServerPrefetch": true,
|
||||||
|
"onShareAppMessage": true,
|
||||||
|
"onShareTimeline": true,
|
||||||
|
"onShow": true,
|
||||||
|
"onTabItemTap": true,
|
||||||
|
"onThemeChange": true,
|
||||||
|
"onUnhandledRejection": true,
|
||||||
|
"onUnload": true,
|
||||||
|
"onUnmounted": true,
|
||||||
|
"onUpdated": true,
|
||||||
|
"onWatcherCleanup": true,
|
||||||
|
"provide": true,
|
||||||
|
"reactive": true,
|
||||||
|
"readonly": true,
|
||||||
|
"ref": true,
|
||||||
|
"resolveComponent": true,
|
||||||
|
"setActivePinia": true,
|
||||||
|
"setMapStoreSuffix": true,
|
||||||
|
"shallowReactive": true,
|
||||||
|
"shallowReadonly": true,
|
||||||
|
"shallowRef": true,
|
||||||
|
"storeToRefs": true,
|
||||||
|
"toRaw": true,
|
||||||
|
"toRef": true,
|
||||||
|
"toRefs": true,
|
||||||
|
"toValue": true,
|
||||||
|
"triggerRef": true,
|
||||||
|
"unref": true,
|
||||||
|
"useAreaCode": true,
|
||||||
|
"useAttrs": true,
|
||||||
|
"useCssModule": true,
|
||||||
|
"useCssVars": true,
|
||||||
|
"useEventEmit": true,
|
||||||
|
"useGetMsgCode": true,
|
||||||
|
"useI18n": true,
|
||||||
|
"useId": true,
|
||||||
|
"useModel": true,
|
||||||
|
"useNetworkStatusChange": true,
|
||||||
|
"usePage": true,
|
||||||
|
"useScrollThreshold": true,
|
||||||
|
"useSlots": true,
|
||||||
|
"useTemplateRef": true,
|
||||||
|
"watch": true,
|
||||||
|
"watchEffect": true,
|
||||||
|
"watchPostEffect": true,
|
||||||
|
"watchSyncEffect": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
distMap
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
/unpackage/
|
||||||
|
docs
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||||
|
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||||
|
"version" : "0.0",
|
||||||
|
"configurations" : [
|
||||||
|
{
|
||||||
|
"app-plus" : {
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"default" : {
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"type" : "uniCloud"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"playground" : "standard",
|
||||||
|
"type" : "uni-app:app-android"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"playground" : "standard",
|
||||||
|
"type" : "uni-app:app-ios"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"src/locale",
|
||||||
|
],
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"i18n-ally.sortKeys": true,
|
||||||
|
"i18n-ally.namespace": true,
|
||||||
|
"i18n-ally.enabledParsers": [
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"i18n-ally.sourceLanguage": "zh-Hans",
|
||||||
|
"i18n-ally.displayLanguage": "zh-Hans",
|
||||||
|
"i18n-ally.translate.engines": [
|
||||||
|
"google-cn",
|
||||||
|
"google",
|
||||||
|
"deepl"
|
||||||
|
],
|
||||||
|
"i18n-ally.enabledFrameworks": [
|
||||||
|
"vue",
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<trees>
|
||||||
|
<tree path="/src/pages-user/pages/recharge" title="充值"/>
|
||||||
|
<tree path="/src/pages-store/pages/home-store" title="首页进入的商家筛选结果"/>
|
||||||
|
</trees>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
VITE_APP_TITLE=美国外卖
|
||||||
|
VITE_APP_PORT=8091
|
||||||
|
# h5是否需要配置代理
|
||||||
|
VITE_APP_PROXY=false
|
||||||
|
VITE_APP_PROXY_PREFIX=/api
|
||||||
|
# 默认语言
|
||||||
|
VITE_FALLBACK_LOCALE='zh-Hans'
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV=development
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE=false
|
||||||
|
|
||||||
|
#本地环境
|
||||||
|
#VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api
|
||||||
|
VITE_SERVER_BASEURL=http://192.168.5.23:8889
|
||||||
|
#VITE_SERVER_BASEURL=http://192.168.0.148:8888
|
||||||
|
#VITE_SERVER_BASEURL=http://liuyao.nat100.top/meiguowaimai
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV=production
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE=true
|
||||||
|
|
||||||
|
|
||||||
|
#正式环境
|
||||||
|
#VITE_SERVER_BASEURL=http://liuyao.nat100.top/meiguowaimai
|
||||||
|
#VITE_SERVER_BASEURL=https://gosplit.co.nz/apimgwm
|
||||||
|
VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||||
|
NODE_ENV = 'development'
|
||||||
|
# 是否去除console 和 debugger
|
||||||
|
VITE_DELETE_CONSOLE = false
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: ['src'], // 提取、还原、遗漏扫描入口文件夹,可以配置多个,默认是 src
|
||||||
|
outDir: 'src/locale', // i18n 输出文件夹 默认是 src/locales
|
||||||
|
outShow:2, //输出文件展示结构 1 扁平化结构 2树级结构 默认扁平化
|
||||||
|
exclude: ['src/locale'], // 不提取的文件夹, 默认是 ['src/locales']
|
||||||
|
extensions: ['.vue', '.js', '.ts'], // 提取的文件后缀名,默认是 ['.js', '.vue', '.ts']
|
||||||
|
filename: 'zh_cn', // 输出的文件名,默认为 zh_cn
|
||||||
|
extname: 'js', // 输出的文件后缀名默认为 js ,支持json和js(js格式为 module.exports = {} 或 export default {}),
|
||||||
|
langList: ['en'] // 翻译目标语言列表,默认是 ['en'] 具体语种请自行查看。注意:使用不同的翻译接口,需要更换对应的语言编码,腾讯翻译、火山翻译、谷歌翻译语言编码大致相同,百度要特别注意,与上述3种翻译有不同的语言编码
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type {GenerateServiceProps} from 'openapi-ts-request'
|
||||||
|
|
||||||
|
// 将路径转换为大驼峰命名
|
||||||
|
function pathToCamelCase(path: string): string {
|
||||||
|
return path
|
||||||
|
.split('/')
|
||||||
|
.filter(segment => segment && segment !== '')
|
||||||
|
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
schemaPath: 'http://127.0.0.1:4523/export/openapi/2?version=3.0',
|
||||||
|
serversPath: './src/service',
|
||||||
|
requestLibPath: `import request from '@/http/vue-query';\n import type { CustomRequestOptions } from '@/http/types'`,
|
||||||
|
requestOptionsType: 'CustomRequestOptions',
|
||||||
|
isGenReactQuery: false,
|
||||||
|
reactQueryMode: 'vue',
|
||||||
|
isGenJavaScript: false,
|
||||||
|
hook: {
|
||||||
|
// 自定义接口名称生成规则
|
||||||
|
customFunctionName: (data: any) => {
|
||||||
|
const {path, method} = data
|
||||||
|
// 将路径转换为大驼峰,然后加上请求方法
|
||||||
|
const camelCasePath = pathToCamelCase(path)
|
||||||
|
const methodSuffix = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase()
|
||||||
|
return `${camelCasePath}${methodSuffix}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
] as GenerateServiceProps[]
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"name": "uni-preset-vue",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"openapi": "openapi-ts",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev": "uni",
|
||||||
|
"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-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-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-4050620250312001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-i18n": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4050620250312001",
|
||||||
|
"@googlemaps/js-api-loader": "1.16.8",
|
||||||
|
"@iconify-json/fluent": "1.2.14",
|
||||||
|
"@stripe/stripe-js": "^7.3.0",
|
||||||
|
"big.js": "6.2.2",
|
||||||
|
"crypto-js": "4.2.0",
|
||||||
|
"dayjs": "1.11.10",
|
||||||
|
"fuse.js": "^7.1.0",
|
||||||
|
"gsap": "^3.13.0",
|
||||||
|
"image-tools": "1.4.0",
|
||||||
|
"js-base64": "3.7.7",
|
||||||
|
"nanoid": "^5.1.5",
|
||||||
|
"pinia": "2.0.36",
|
||||||
|
"pinia-plugin-persistedstate": "3.2.1",
|
||||||
|
"qs": "6.5.3",
|
||||||
|
"ramda": "0.30.1",
|
||||||
|
"rxjs": "^7.8.2",
|
||||||
|
"throttle-debounce": "5.0.2",
|
||||||
|
"vue": "3.4.21",
|
||||||
|
"vue-i18n": "9.1.9",
|
||||||
|
"wot-design-uni": "1.9.1",
|
||||||
|
"zod": "3.24.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "3.4.14",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4050620250312001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4050620250312001",
|
||||||
|
"@iconify-json/carbon": "^1.2.8",
|
||||||
|
"@types/big.js": "6.2.2",
|
||||||
|
"@types/crypto-js": "4.2.2",
|
||||||
|
"@types/node": "20.17.19",
|
||||||
|
"@types/qs": "6.9.18",
|
||||||
|
"@types/ramda": "0.30.2",
|
||||||
|
"@types/throttle-debounce": "5.0.2",
|
||||||
|
"@types/wechat-miniprogram": "3.4.8",
|
||||||
|
"@uni-helper/vite-plugin-uni-layouts": "0.1.10",
|
||||||
|
"@vue/runtime-core": "3.5.13",
|
||||||
|
"@vue/tsconfig": "0.1.3",
|
||||||
|
"openapi-ts-request": "^1.6.6",
|
||||||
|
"sass": "1.78.0",
|
||||||
|
"typescript": "4.9.5",
|
||||||
|
"unocss": "0.58.0",
|
||||||
|
"unocss-applet": "0.7.8",
|
||||||
|
"unocss-preset-animations": "^1.1.1",
|
||||||
|
"unplugin-auto-import": "0.17.8",
|
||||||
|
"vite": "5.4.14",
|
||||||
|
"vite-plugin-restart": "0.4.2",
|
||||||
|
"vite-plugin-vue-devtools": "7.7.2",
|
||||||
|
"vue-tsc": "1.8.27"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0",
|
||||||
|
"pnpm": ">=9.11.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"node": "20.18.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
import {setDayjsLocale, setWotDesignLocale} from "@/plugin";
|
||||||
|
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
onLaunch(initConfig);
|
||||||
|
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
console.log('%c外卖用户端--用户端', 'background: #00A76D; color: white; padding: 3px; border-radius: 2px;');
|
||||||
|
console.log("App Show");
|
||||||
|
});
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
console.log("App Hide");
|
||||||
|
});
|
||||||
|
|
||||||
|
onError((error: string) => {
|
||||||
|
console.log('App Error', error)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function initApp() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
try {
|
||||||
|
plus.screen.lockOrientation('portrait-primary')
|
||||||
|
const color = plus.android.newObject("android.graphics.Color");
|
||||||
|
const ac = plus.android.runtimeMainActivity();
|
||||||
|
const c2int = plus.android.invoke(color, "parseColor", "#FFFFFF");
|
||||||
|
console.log("c2int===" + JSON.stringify(c2int))
|
||||||
|
const win = plus.android.invoke(ac, "getWindow");
|
||||||
|
console.log("win===" + JSON.stringify(win))
|
||||||
|
plus.android.invoke(win, "setNavigationBarColor", c2int);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error', e)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化配置
|
||||||
|
function initConfig() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
setTimeout(()=> {
|
||||||
|
plus.navigator.closeSplashscreen();
|
||||||
|
}, 2000)
|
||||||
|
plus.screen.lockOrientation('portrait-primary')
|
||||||
|
// #endif
|
||||||
|
const {statusBarHeight, windowHeight, windowWidth, screenWidth, screenHeight, safeAreaInsets} =
|
||||||
|
uni.getWindowInfo()
|
||||||
|
|
||||||
|
const {deviceId} =
|
||||||
|
uni.getDeviceInfo()
|
||||||
|
|
||||||
|
|
||||||
|
const {appVersion} =
|
||||||
|
uni.getAppBaseInfo()
|
||||||
|
|
||||||
|
const {
|
||||||
|
uniPlatform,
|
||||||
|
osName,
|
||||||
|
osVersion,
|
||||||
|
|
||||||
|
} =
|
||||||
|
uni.getSystemInfoSync()
|
||||||
|
|
||||||
|
configStore.deviceId = deviceId
|
||||||
|
configStore.osName = osName
|
||||||
|
configStore.osVersion = osVersion
|
||||||
|
configStore.appVersion = appVersion
|
||||||
|
configStore.uniPlatform = uniPlatform
|
||||||
|
configStore.statusBarHeight = statusBarHeight ?? 0
|
||||||
|
configStore.windowHeight = windowHeight ?? 0
|
||||||
|
configStore.windowWidth = windowWidth ?? 0
|
||||||
|
configStore.screenWidth = screenWidth ?? 0
|
||||||
|
configStore.screenHeight = screenHeight ?? 0
|
||||||
|
|
||||||
|
configStore.safeAreaInsets = safeAreaInsets ?? {
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
}
|
||||||
|
setWotDesignLocale()
|
||||||
|
setDayjsLocale()
|
||||||
|
if (osName === 'android') {
|
||||||
|
initApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!configStore.isShowedLanguageSelectPage) {
|
||||||
|
console.log('未展示过语言选择页面,导航到语言选择页面')
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-login/pages/choose-language/index',
|
||||||
|
success() {
|
||||||
|
configStore.isShowedLanguageSelectPage = true
|
||||||
|
console.log('导航到语言选择页面成功')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/styles/index.scss";
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,459 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex items-center pb-36rpx">
|
||||||
|
<image
|
||||||
|
src="@img/chef/1330.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="w-44rpx h-44rpx shrink-0 mr-12rpx"
|
||||||
|
/>
|
||||||
|
{{ t('common.comment') }}({{ props.tableTotal }})
|
||||||
|
</view>
|
||||||
|
<template v-if="dataList && dataList.length">
|
||||||
|
<view class="c_comment" v-for="(item1, index1) in dataList" :key="item1.id">
|
||||||
|
<!-- 一级评论 -->
|
||||||
|
<CommonComp
|
||||||
|
:data="item1"
|
||||||
|
@likeClick="() => likeClick({ item1, index1 })"
|
||||||
|
@replyClick="() => replyClick({ item1, index1 })"
|
||||||
|
@deleteClick="() => deleteClick({ item1, index1 })"
|
||||||
|
/>
|
||||||
|
<view class="children_item bg-#F5F7FB rounded-16rpx" v-if="item1.childrenShow && item1.childrenShow.length> 0">
|
||||||
|
<!-- 二级评论 -->
|
||||||
|
<CommonComp
|
||||||
|
v-for="(item2, index2) in item1.childrenShow"
|
||||||
|
:key="item2.id"
|
||||||
|
:data="item2"
|
||||||
|
:pData="item1"
|
||||||
|
@likeClick="() => likeClick({ item1, index1, item2, index2 })"
|
||||||
|
@replyClick="() => replyClick({ item1, index1, item2, index2 })"
|
||||||
|
@deleteClick="() => deleteClick({ item1, index1, item2, index2 })"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-center pl-100rpx text-#666 pt-10rpx" v-if="item1.children || item1.commentCount> 0">
|
||||||
|
<!-- 展开二级评论 -->
|
||||||
|
<view
|
||||||
|
class="flex items-center"
|
||||||
|
@click="expandReply(item1)"
|
||||||
|
>
|
||||||
|
<text>{{ t('pages-user.recipe.expandReply') }}</text>
|
||||||
|
<wd-icon name="chevron-down" class="mt-4rpx" size="22px"></wd-icon>
|
||||||
|
</view>
|
||||||
|
<!-- 折叠二级评论 -->
|
||||||
|
<view
|
||||||
|
class="flex items-center"
|
||||||
|
@click="shrinkReply(item1)"
|
||||||
|
>
|
||||||
|
<text>{{ t('pages-user.recipe.collapseReply') }}</text>
|
||||||
|
<wd-icon name="chevron-up" class="mt-4rpx" size="22px"></wd-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<!-- 空盒子 -->
|
||||||
|
<view class="empty_box" v-else>
|
||||||
|
<view class="py-100rpx center">
|
||||||
|
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 评论弹窗 -->
|
||||||
|
<wd-popup ref="cPopupRef" v-model="cPopupShow" custom-style="border-radius:16rpx 16rpx 0 0;" position="bottom" @change="popChange">
|
||||||
|
<view class="w-full rounded-16rpx px-30rpx py-16rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<template v-if="Object.keys(replyTemp).length">
|
||||||
|
<text class="text_aid">{{ t('pages-user.recipe.replyTo') }}</text>
|
||||||
|
<image class="w-68rpx h-68rpx rounded-50% mx-10rpx" :src="replyTemp.item2 ? replyTemp.item2.user_avatar : replyTemp.item1.user_avatar" />
|
||||||
|
<text class="text_main">{{ replyTemp.item2 ? replyTemp.item2.user_name : replyTemp.item1.user_name }}</text>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<view class="w-full flex-center-sb gap-30rpx bg-white py-12rpx">
|
||||||
|
<view class="w-full h-74rpx center bg-#F6F6F6 rounded-16rpx px-28rpx">
|
||||||
|
<wd-input
|
||||||
|
no-border
|
||||||
|
clearable
|
||||||
|
:focus-when-clear="false"
|
||||||
|
confirm-type="send"
|
||||||
|
use-prefix-slot
|
||||||
|
custom-class="flex items-center !text-30rpx !bg-transparent flex-1"
|
||||||
|
placeholderStyle="font-size: 30rpx;color: #6D6D6D; font-weight: 500;"
|
||||||
|
@confirm="sendClick"
|
||||||
|
v-model="commentValue"
|
||||||
|
:placeholder="commentPlaceholder"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
<wd-button @click="sendClick" class="!h-74rpx !w-120rpx !rounded-16rpx !bg-#333 !text-white !text-30rpx">{{ t('common.send') }}</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
<wd-message-box/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CommonComp from "./componets/common";
|
||||||
|
import {appCommentDeleteCommentIdDelete, appCommentPublishCommentPost, appCommentReplyListPost} from "@/service";
|
||||||
|
|
||||||
|
import {useMessage} from "wot-design-uni";
|
||||||
|
import {formatTimestampWithMonthName} from "@/utils/utils";
|
||||||
|
const message = useMessage();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/** 登陆用户信息
|
||||||
|
* id: number // 登陆用户id
|
||||||
|
* user_name: number // 登陆用户名
|
||||||
|
* user_avatar: string // 登陆用户头像地址
|
||||||
|
*/
|
||||||
|
myInfo: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
/** 文章作者信息
|
||||||
|
* id: number // 文章作者id
|
||||||
|
* user_name: number // 文章作者名
|
||||||
|
* user_avatar: string // 文章作者头像地址
|
||||||
|
*/
|
||||||
|
userInfo: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
/** 评论列表
|
||||||
|
* id: number // 评论id
|
||||||
|
* parent_id: number // 父级评论id
|
||||||
|
* reply_id: number // 被回复人评论id
|
||||||
|
* reply_name: string // 被回复人名称
|
||||||
|
* user_name: string // 用户名
|
||||||
|
* user_avatar: string // 评论者头像地址
|
||||||
|
* user_content: string // 评论内容
|
||||||
|
* is_like: boolean // 是否点赞
|
||||||
|
* like_count: number // 点赞数统计
|
||||||
|
* create_time: string // 创建时间
|
||||||
|
*/
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// 评论总数
|
||||||
|
tableTotal: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
// 评论删除模式
|
||||||
|
// bind - 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
|
||||||
|
// only - 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
|
||||||
|
// all - 删除所有评论包括回复评论
|
||||||
|
deleteMode: {
|
||||||
|
type: String,
|
||||||
|
default: "all",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits([
|
||||||
|
"update:tableTotal",
|
||||||
|
"likeFun", // 点赞事件
|
||||||
|
"replyFun", // 回复事件
|
||||||
|
"deleteFun", // 删除事件
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 渲染数据(前端的格式)
|
||||||
|
let dataList = ref([]);
|
||||||
|
watch(
|
||||||
|
() => props.tableData,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal.length !== dataList.value.length) {
|
||||||
|
let temp = props.tableData;
|
||||||
|
dataList.value = treeTransForm(temp);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 数据转换
|
||||||
|
function treeTransForm(data) {
|
||||||
|
let newData = JSON.parse(JSON.stringify(data));
|
||||||
|
let result = [];
|
||||||
|
let map = {};
|
||||||
|
newData.forEach((item, i) => {
|
||||||
|
item.owner = item.user_id === props.myInfo.user_id; // 是否为当前登陆用户 可以对自己的评论进行删除 不能回复
|
||||||
|
// item.author = item.user_id === props.userInfo.user_id; // 是否为作者 显示标记
|
||||||
|
map[item.id] = item;
|
||||||
|
});
|
||||||
|
newData.forEach((item) => {
|
||||||
|
let parent = map[item.parent_id];
|
||||||
|
if (parent) {
|
||||||
|
(parent.children || (parent.children = [])).push(item); // 所有回复
|
||||||
|
if (parent.children.length === 1) {
|
||||||
|
(parent.childrenShow = []).push(item); // 显示的回复
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点赞
|
||||||
|
let setLike = (item) => {
|
||||||
|
item.is_like = !item.is_like;
|
||||||
|
item.like_count = item.is_like ? item.like_count + 1 : item.like_count - 1;
|
||||||
|
};
|
||||||
|
function likeClick({ item1, index1, item2, index2 }) {
|
||||||
|
let item = item2 || item1;
|
||||||
|
setLike(item);
|
||||||
|
emit("likeFun", { params: item }, (res) => {
|
||||||
|
// 请求后端失败, 重置点赞
|
||||||
|
setLike(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复
|
||||||
|
let cPopupRef = ref(null); // 弹窗实例
|
||||||
|
const cPopupShow = ref(false); // 弹窗显示状态
|
||||||
|
let replyTemp = reactive({}); // 临时数据
|
||||||
|
function replyClick({ item1, index1, item2, index2 }) {
|
||||||
|
replyTemp = JSON.parse(JSON.stringify({ item1, index1, item2, index2 }));
|
||||||
|
cPopupShow.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起新评论
|
||||||
|
let isNewComment = ref(false); // 是否为新评论
|
||||||
|
defineExpose({ newCommentFun });
|
||||||
|
function newCommentFun() {
|
||||||
|
isNewComment.value = true;
|
||||||
|
cPopupShow.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 评论弹窗
|
||||||
|
let focus = ref(false);
|
||||||
|
function popChange(e) {
|
||||||
|
// 关闭弹窗
|
||||||
|
if (!e.show) {
|
||||||
|
commentValue.value = ""; // 清空输入框值
|
||||||
|
replyTemp = {}; // 清空被回复人信息
|
||||||
|
isNewComment.value = false; // 恢复是否为新评论默认值
|
||||||
|
}
|
||||||
|
focus.value = e.show;
|
||||||
|
}
|
||||||
|
let commentValue = ref(""); // 输入框值
|
||||||
|
let commentPlaceholder = ref("说点什么..."); // 输入框占位符
|
||||||
|
|
||||||
|
// 发送评论
|
||||||
|
function sendClick({ item1, index1, item2, index2 } = replyTemp) {
|
||||||
|
console.log('replyTemp', replyTemp.item1)
|
||||||
|
console.log('replyTemp', replyTemp.item2)
|
||||||
|
const data = replyTemp.item2 || replyTemp.item1
|
||||||
|
appCommentPublishCommentPost({
|
||||||
|
body: {
|
||||||
|
topId: replyTemp.item2 ? replyTemp.item1.id : data.id,
|
||||||
|
parentId:data.id,
|
||||||
|
targetId: data.target_id,
|
||||||
|
targetType: 1,
|
||||||
|
content: commentValue.value,
|
||||||
|
}
|
||||||
|
}).then(res=> {
|
||||||
|
console.log(res)
|
||||||
|
emit("update");
|
||||||
|
cPopupShow.value = false;
|
||||||
|
commentValue.value = ""; // 清空输入框值
|
||||||
|
// shrinkReply(replyTemp.item1)
|
||||||
|
dataList.value.forEach((item, i) => {
|
||||||
|
if (item.id === replyTemp.item1.id) {
|
||||||
|
shrinkReply(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandReply(item1) {
|
||||||
|
// item1.childrenShow = item1.children
|
||||||
|
console.log(item1)
|
||||||
|
appCommentReplyListPost({
|
||||||
|
params: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
parentId: item1.id,
|
||||||
|
}
|
||||||
|
}).then(res=> {
|
||||||
|
console.log('回复列表', res)
|
||||||
|
item1.children = res.rows.map(item => {
|
||||||
|
let userInfo = {}
|
||||||
|
// 普通用户
|
||||||
|
if (+item.userPort === 1) {
|
||||||
|
userInfo.user_id = item.userVo.id
|
||||||
|
userInfo.user_name = `${item.userVo.firstName} ${item.userVo.surname}`
|
||||||
|
userInfo.user_avatar = item.userVo.avatar
|
||||||
|
} else {
|
||||||
|
userInfo.user_id = item.merchantVo.userId
|
||||||
|
userInfo.user_name = item.merchantVo.merchantName
|
||||||
|
userInfo.user_avatar = item.merchantVo.logo
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
topId: item.topId,
|
||||||
|
parent_id: null, // 评论父级的id
|
||||||
|
reply_id: null, // 被回复评论的id
|
||||||
|
reply_name: item.parentUserVo ? `${item.parentUserVo.firstName} ${item.parentUserVo.surname}` : null, // 被回复人名称
|
||||||
|
target_id: item1.target_id,
|
||||||
|
commentCount: item.commentCount,
|
||||||
|
user_id: userInfo.user_id, // 用户id
|
||||||
|
user_name: userInfo.user_name, // 用户名
|
||||||
|
user_avatar: userInfo.user_avatar, // 用户头像地址
|
||||||
|
user_content: item.content, // 用户评论内容
|
||||||
|
create_time: formatTimestampWithMonthName(item.createTime), // 创建时间
|
||||||
|
owner: userInfo.user_id === props.myInfo.user_id, // 是否为当前登陆用户 可以对自己的评论进行删除 不能回复
|
||||||
|
}
|
||||||
|
})
|
||||||
|
item1.childrenShow = item1.children
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function shrinkReply(item1) {
|
||||||
|
item1.childrenShow = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const delPopupRef = ref(null);
|
||||||
|
let delTemp = reactive({}); // 临时数据
|
||||||
|
function deleteClick({ item1, index1, item2, index2 }) {
|
||||||
|
console.log('删123除', item1, index1, item2, index2)
|
||||||
|
message
|
||||||
|
.confirm({
|
||||||
|
title: t("common.prompt.system-prompt"),
|
||||||
|
msg: `${t("common.prompt.system-prompt-delete")}`,
|
||||||
|
confirmButtonText: t("common.yes"),
|
||||||
|
cancelButtonText: t("common.no"),
|
||||||
|
cancelButtonProps: {
|
||||||
|
customClass:
|
||||||
|
"!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !border-#666666 !rounded-20rpx",
|
||||||
|
},
|
||||||
|
confirmButtonProps: {
|
||||||
|
customClass:
|
||||||
|
"!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !bg-primary !rounded-20rpx",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
appCommentDeleteCommentIdDelete({
|
||||||
|
params: {
|
||||||
|
id: item2.id ? item2.id : item1.id,
|
||||||
|
}
|
||||||
|
}).then(res=> {
|
||||||
|
console.log('删除成功', res)
|
||||||
|
emit("deleteFun");
|
||||||
|
shrinkReply(item1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开评论if
|
||||||
|
function expandTxtShow({ item1, index1 }) {
|
||||||
|
return item1.childrenShow?.length && item1.children.length - item1.childrenShow.length;
|
||||||
|
}
|
||||||
|
// 展开更多评论
|
||||||
|
function expandReplyFun({ item1, index1 }) {
|
||||||
|
let csLen = dataList.value[index1].childrenShow.length;
|
||||||
|
dataList.value[index1].childrenShow.push(
|
||||||
|
...dataList.value[index1].children.slice(csLen, csLen + 6) // 截取5条评论
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收起评论if
|
||||||
|
function shrinkTxtShow({ item1, index1 }) {
|
||||||
|
return item1.childrenShow?.length >= 2 && item1.children.length - item1.childrenShow.length === 0;
|
||||||
|
}
|
||||||
|
// 收起更多评论
|
||||||
|
function shrinkReplyFun({ item1, index1 }) {
|
||||||
|
let csLen = dataList.value[index1].childrenShow.length;
|
||||||
|
dataList.value[index1].childrenShow = [];
|
||||||
|
dataList.value[index1].childrenShow.push(
|
||||||
|
...dataList.value[index1].children.slice(0, 1) // 截取1条评论
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
////////////////////////
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
////////////////////////
|
||||||
|
.c_total {
|
||||||
|
//padding: 20rpx 30rpx 0 30rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.empty_box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 150rpx 10rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
.txt {
|
||||||
|
//color: $uni-text-color-disable;
|
||||||
|
}
|
||||||
|
.click {
|
||||||
|
//color: $uni-color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.c_comment {
|
||||||
|
//padding: 20rpx 30rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
|
||||||
|
.children_item {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
margin-left: 80rpx;
|
||||||
|
//background-color: $uni-bg-color-grey;
|
||||||
|
.expand_reply,
|
||||||
|
.shrink_reply {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
margin-left: 80rpx;
|
||||||
|
.txt {
|
||||||
|
font-weight: 600;
|
||||||
|
//color: $uni-color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.c_popup_box {
|
||||||
|
background-color: #fff;
|
||||||
|
.reply_text {
|
||||||
|
@extend .center;
|
||||||
|
padding: 20rpx 20rpx 0 20rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
.text_aid {
|
||||||
|
//color: $uni-text-color-grey;
|
||||||
|
margin-right: 5rpx;
|
||||||
|
}
|
||||||
|
.user_avatar {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
.text_main {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
@extend .center;
|
||||||
|
.text_area {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
.send_btn {
|
||||||
|
@extend .center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
//background-color: $uni-color-primary;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
margin-left: 5rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<view class="comment_item">
|
||||||
|
<view class="top">
|
||||||
|
<view class="top_left !text-#999 !text-26rpx">
|
||||||
|
<image v-if="+props.data.topId === 0" class="w-80rpx h-80rpx rounded-50% mr-24rpx" mode="aspectFill" :src="props.data.user_avatar" />
|
||||||
|
<text class="user_name">{{ props.data.user_name }}</text>
|
||||||
|
<text class="user_name">{{ cReplyName }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :class="[+props.data.topId === 0 ? '!pl-34rpx ml-70rpx' : '']" class="content !text-#333 !text-26rpx" @tap="replyClick(props.data)">
|
||||||
|
{{ c_content }}
|
||||||
|
</view>
|
||||||
|
<view :class="[+props.data.topId === 0 ? '!pl-100rpx' : '']" class="">
|
||||||
|
<text class="create_time !text-#999 !text-26rpx">{{ props.data.create_time }}</text>
|
||||||
|
<text v-if="props.data.owner" class="bg-#E6E6E6 rounded-4rpx px-8rpx text-22rpx text-#333 mx-10rpx" @tap="deleteClick(props.data)">{{ t('common.remove') }}</text>
|
||||||
|
<text class= "bg-#E6E6E6 rounded-4rpx px-8rpx text-22rpx text-#333 ml-10rpx">{{ t('common.reply') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref, onMounted, computed, watch, watchEffect } from "vue";
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
// 评论数据
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
// 父级评论数据
|
||||||
|
pData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 被回复人名称
|
||||||
|
const cReplyName = computed(() => {
|
||||||
|
return props.data?.reply_name ? ` ▸ ` + props.data?.reply_name : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点赞数显示
|
||||||
|
const cLikeCount = computed(() => {
|
||||||
|
return props.data.like_count === 0 ? "" : props.data.like_count > 99 ? `99+` : props.data.like_count;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 评论过长处理
|
||||||
|
let contentShowLength = 70; // 默认显示评论字符
|
||||||
|
|
||||||
|
let user_content = props.data.user_content;
|
||||||
|
let isShrink = ref(user_content.length > contentShowLength); // 是否收缩评论
|
||||||
|
let c_content = ref("");
|
||||||
|
watch(
|
||||||
|
() => isShrink.value,
|
||||||
|
(newVal) => {
|
||||||
|
c_content.value = newVal ? user_content.slice(0, contentShowLength + 1) : user_content;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 删除变更显示定制
|
||||||
|
watch(
|
||||||
|
() => props.data.user_content,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
c_content.value = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 展开文字
|
||||||
|
function expandContentFun() {
|
||||||
|
isShrink.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收起文字
|
||||||
|
function shrinkContentFun() {
|
||||||
|
isShrink.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(["likeClick", "replyClick", "deleteClick"]);
|
||||||
|
|
||||||
|
// 点赞
|
||||||
|
function likeClick(item) {
|
||||||
|
emit("likeClick", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复
|
||||||
|
function replyClick(item) {
|
||||||
|
emit("replyClick", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
function deleteClick(item) {
|
||||||
|
emit("deleteClick", item);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
////////////////////////
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ellipsis {
|
||||||
|
overflow: hidden; //超出文本隐藏
|
||||||
|
white-space: nowrap; //溢出不换行
|
||||||
|
text-overflow: ellipsis; //溢出省略号显示
|
||||||
|
}
|
||||||
|
////////////////////////
|
||||||
|
.comment_item {
|
||||||
|
font-size: 28rpx;
|
||||||
|
.top {
|
||||||
|
@extend .center;
|
||||||
|
justify-content: space-between;
|
||||||
|
.top_left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
.user_avatar {
|
||||||
|
width: 68rpx;
|
||||||
|
height: 68rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
.user_name {
|
||||||
|
@extend .ellipsis;
|
||||||
|
max-width: 180rpx;
|
||||||
|
//color: $uni-text-color-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top_right {
|
||||||
|
@extend .center;
|
||||||
|
.like_count {
|
||||||
|
//color: $uni-text-color-grey;
|
||||||
|
&.active {
|
||||||
|
//color: $uni-color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 10rpx 0;
|
||||||
|
//color: $uni-text-color;
|
||||||
|
&:active {
|
||||||
|
//background-color: $uni-bg-color-hover;
|
||||||
|
}
|
||||||
|
.shrink {
|
||||||
|
padding: 20rpx 20rpx 20rpx 0rpx;
|
||||||
|
//color: $uni-color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compileType": "miniprogram",
|
||||||
|
"setting": {
|
||||||
|
"coverView": true,
|
||||||
|
"es6": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true,
|
||||||
|
"enhance": true,
|
||||||
|
"showShadowRootInWxmlPanel": true,
|
||||||
|
"packNpmRelationList": [],
|
||||||
|
"babelSetting": {
|
||||||
|
"ignore": [],
|
||||||
|
"disablePlugins": [],
|
||||||
|
"outputPath": ""
|
||||||
|
},
|
||||||
|
"ignoreUploadUnusedFiles": true
|
||||||
|
},
|
||||||
|
"condition": {},
|
||||||
|
"editorSetting": {
|
||||||
|
"tabIndent": "tab",
|
||||||
|
"tabSize": 2
|
||||||
|
},
|
||||||
|
"libVersion": "3.6.3",
|
||||||
|
"packOptions": {
|
||||||
|
"ignore": [],
|
||||||
|
"include": []
|
||||||
|
},
|
||||||
|
"appid": ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||||
|
"projectname": "cc-comment",
|
||||||
|
"setting": {
|
||||||
|
"compileHotReLoad": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
interface DayItem {
|
||||||
|
date: string
|
||||||
|
name: string
|
||||||
|
time: number
|
||||||
|
dataFormat: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{ date?: string }>();
|
||||||
|
const emits = defineEmits<{
|
||||||
|
success: [DayItem]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const {t} = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const $dayjs = inject('dayjs') as any
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
|
// 选择的日期
|
||||||
|
const date = ref<{ fulldate: string } | null>(null)
|
||||||
|
|
||||||
|
watch(() => props.date, (value) => {
|
||||||
|
if (value) {
|
||||||
|
date.value = {fulldate: value}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
|
||||||
|
close();
|
||||||
|
const weekdays = $dayjs.weekdaysShort()
|
||||||
|
const selectedDate: string = date.value?.fulldate || ''
|
||||||
|
let day = $dayjs(selectedDate)
|
||||||
|
|
||||||
|
emits('success', {
|
||||||
|
date: day.date(),
|
||||||
|
name: weekdays[day.day()],
|
||||||
|
time: day.valueOf(),
|
||||||
|
dataFormat: selectedDate,
|
||||||
|
selected: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = debounce(Config.debounceLongTime, submit, {
|
||||||
|
atBegin: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function handleCalendarChange(event: any) {
|
||||||
|
console.log('event', event)
|
||||||
|
date.value = event
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "ChooseDate",
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
|
||||||
|
custom-class="rounded-t-30rpx"
|
||||||
|
position="bottom"
|
||||||
|
safe-area-inset-bottom
|
||||||
|
v-model="show"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="box-border flex flex-col"
|
||||||
|
>
|
||||||
|
<view class="relative p-[40rpx+30rpx] flex items-center justify-between border-bottom">
|
||||||
|
<view class="text-42rpx text-primary font-bold">
|
||||||
|
{{ t('common.select-date') }}
|
||||||
|
</view>
|
||||||
|
<image class="absolute right-30rpx w-32rpx h-32rpx" src="@img-store/2106@2x.png" @click="close"></image>
|
||||||
|
</view>
|
||||||
|
<view class="mb-30rpx">
|
||||||
|
<wu-calendar
|
||||||
|
:todayDefaultStyle="false"
|
||||||
|
:showMonth="false"
|
||||||
|
:monthShowCurrentMonth="true"
|
||||||
|
:insert="true"
|
||||||
|
color="#333"
|
||||||
|
:itemHeight="52"
|
||||||
|
:startDate="$dayjs(configStore.serverTime).format('YYYY-MM-DD')"
|
||||||
|
@change="handleCalendarChange"></wu-calendar>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<fixed-bottom-large-btn class="" :text="t('common.confirm')"
|
||||||
|
@click="handleSubmit"
|
||||||
|
/>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wu-calendar) {
|
||||||
|
|
||||||
|
|
||||||
|
.wu-calendar-item__weeks-lunar-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wu-calendar-item__weeks-box-item {
|
||||||
|
width: 43px !important;
|
||||||
|
border-radius: 16rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wu-calendar-item__weeks-box-item::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
// import {upload} from '@/utils/upload/alioss'
|
||||||
|
import { uploadToS3 } from '@/utils/upload/ymx'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
count?: number
|
||||||
|
isUpload?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
count: 1,
|
||||||
|
isUpload: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emits = defineEmits(['change', 'update:modelValue'])
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择图片
|
||||||
|
function handleChooseImage(type: 'album' | 'camera') {
|
||||||
|
close()
|
||||||
|
|
||||||
|
|
||||||
|
chooseImage(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseImage(type: 'album' | 'camera') {
|
||||||
|
// #ifdef H5
|
||||||
|
// uni.chooseImage({
|
||||||
|
// count: props.count,
|
||||||
|
// sizeType: ['original'],
|
||||||
|
// sourceType: type === 'album' ? ['album'] : ['camera'],
|
||||||
|
// success: async (res) => {
|
||||||
|
// console.log(res)
|
||||||
|
//
|
||||||
|
// let files: string | string[] = []
|
||||||
|
// const asyncList = []
|
||||||
|
//
|
||||||
|
// if (!props.isUpload) {
|
||||||
|
// return emits('change', res.tempFilePaths)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// files = res.tempFilePaths
|
||||||
|
//
|
||||||
|
// await uni.showLoading({
|
||||||
|
// title: t('common.loading') + '...',
|
||||||
|
// mask: true,
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// for (let i = 0; i < files.length; i++) {
|
||||||
|
// // asyncList.push(upload(files[i]))
|
||||||
|
// asyncList.push(uploadToS3(files[i]))
|
||||||
|
// console.log(asyncList)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Promise.all(asyncList)
|
||||||
|
// .then((results) => {
|
||||||
|
// console.log(results)
|
||||||
|
// emits('change', results)
|
||||||
|
// })
|
||||||
|
// .finally(() => {
|
||||||
|
// uni.hideLoading()
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: props.count,
|
||||||
|
mediaType: ['image'],
|
||||||
|
sourceType: type === 'album' ? ['album'] : ['camera'],
|
||||||
|
maxDuration: 30,
|
||||||
|
camera: 'back',
|
||||||
|
async success(res) {
|
||||||
|
let files: string | string[] = []
|
||||||
|
const asyncList = []
|
||||||
|
console.log('res.tempFiles', res.tempFiles)
|
||||||
|
if(res.tempFiles.length === 0) return
|
||||||
|
files = res.tempFiles.map((item) => item.tempFilePath)
|
||||||
|
if(props.count > 0) {
|
||||||
|
// 根据count截取数组长度
|
||||||
|
files = files.slice(0, props.count)
|
||||||
|
}
|
||||||
|
if (!props.isUpload) {
|
||||||
|
return emits('change', files)
|
||||||
|
}
|
||||||
|
await uni.showLoading({
|
||||||
|
title: t('common.loading') + '...',
|
||||||
|
mask: true,
|
||||||
|
})
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
// asyncList.push(upload(files[i]))
|
||||||
|
asyncList.push(uploadToS3(files[i]))
|
||||||
|
console.log(asyncList)
|
||||||
|
}
|
||||||
|
Promise.all(asyncList)
|
||||||
|
.then((results) => {
|
||||||
|
console.log(results)
|
||||||
|
emits('change', results)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
uni.hideLoading()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-popup position="bottom" safe-area-inset-bottom v-model="show" @close="close"
|
||||||
|
custom-style="border-radius: 30rpx 30rpx 0 0;">
|
||||||
|
<slot name="top"></slot>
|
||||||
|
<view class="p-[32rpx+20rpx] text-34rpx text-primary text-center font-bold" @click="handleChooseImage('album')">
|
||||||
|
<text>{{ t('common.select-from-album') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="p-[32rpx+20rpx] text-34rpx text-primary text-center font-bold" @click="handleChooseImage('camera')">
|
||||||
|
<text>{{ t('common.photograph') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="h-20rpx bg-common"></view>
|
||||||
|
<view class="p-[32rpx+20rpx] text-34rpx text-primary text-center" @click="close">{{ t('common.cancel') }}</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<view class="collection-component" @click.stop="handleCollectionClick">
|
||||||
|
<view class="icon-container">
|
||||||
|
<!-- 未收藏图标 -->
|
||||||
|
<image
|
||||||
|
v-show="!isCollected"
|
||||||
|
:src="uncollectedIcon"
|
||||||
|
:class="['collection-icon', 'collected-icon', { 'slide-in': isAnimating && !isCollected }]"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
<!-- 已收藏图标 -->
|
||||||
|
<image
|
||||||
|
v-show="isCollected"
|
||||||
|
:src="collectedIcon"
|
||||||
|
:class="['collection-icon', 'uncollected-icon', { 'slide-out': isAnimating && isCollected }]"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { throttle, debounce } from 'throttle-debounce'
|
||||||
|
|
||||||
|
// 定义组件属性
|
||||||
|
interface Props {
|
||||||
|
/** 是否已收藏 */
|
||||||
|
isCollected: boolean
|
||||||
|
/** 节流延迟时间(毫秒) */
|
||||||
|
throttleDelay?: number
|
||||||
|
/** 防抖延迟时间(毫秒) */
|
||||||
|
debounceDelay?: number
|
||||||
|
/** 是否禁用点击 */
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
isCollected: false,
|
||||||
|
throttleDelay: 1000,
|
||||||
|
debounceDelay: 1500,
|
||||||
|
disabled: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/** 收藏状态改变事件 */
|
||||||
|
'collection-change': [isCollected: boolean]
|
||||||
|
/** 点击事件 */
|
||||||
|
'click': [event: any]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 图标路径
|
||||||
|
const uncollectedIcon = '/static/images/chef/117.png'
|
||||||
|
const collectedIcon = '/static/images/chef/118.png'
|
||||||
|
|
||||||
|
// 动画状态
|
||||||
|
const isAnimating = ref(false)
|
||||||
|
|
||||||
|
// 节流处理函数
|
||||||
|
const throttledEmit = throttle(props.throttleDelay, (isCollected: boolean) => {
|
||||||
|
emit('collection-change', isCollected)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 防抖处理函数
|
||||||
|
const debouncedEmit = debounce(props.debounceDelay, (isCollected: boolean) => {
|
||||||
|
emit('collection-change', isCollected)
|
||||||
|
}, {
|
||||||
|
atBegin: true, // 立即触发
|
||||||
|
})
|
||||||
|
|
||||||
|
// 点击处理函数
|
||||||
|
const handleCollectionClick = (event: any) => {
|
||||||
|
if (props.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发点击事件
|
||||||
|
emit('click', event)
|
||||||
|
|
||||||
|
// 开始动画
|
||||||
|
isAnimating.value = true
|
||||||
|
|
||||||
|
// 立即触发状态变化
|
||||||
|
// throttledEmit(!props.isCollected)
|
||||||
|
debouncedEmit(!props.isCollected)
|
||||||
|
|
||||||
|
// 动画结束后重置状态
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating.value = false
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.collection-component {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
position: relative;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
&.uncollected-icon {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&.slide-out {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collected-icon {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&.slide-in {
|
||||||
|
animation: slide-in-from-bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-from-bottom {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
const {proxy} = getCurrentInstance() as any
|
||||||
|
const {t} = useI18n()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
const isExpandAll = ref(false)
|
||||||
|
const isShowExpandBtn = ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const selectorQuery = uni.createSelectorQuery().in(proxy)
|
||||||
|
selectorQuery.select('.expand-content').boundingClientRect((res) => {
|
||||||
|
console.log('rect', res)
|
||||||
|
console.log('res.height && res.height > uni.upx2px(10 * 42)', res.height && res.height > uni.upx2px(10 * 42))
|
||||||
|
if (res.height && res.height > uni.upx2px(20 * 42)) {
|
||||||
|
isShowExpandBtn.value = true
|
||||||
|
}
|
||||||
|
}).exec()
|
||||||
|
}, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="">
|
||||||
|
<view class="expand-content relative transition-all duration-150"
|
||||||
|
:style="[!isExpandAll? {
|
||||||
|
maxHeight: 20 * 42 + 'rpx',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}:{}]"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="center mt-28rpx" v-if="isShowExpandBtn" @click="isExpandAll=!isExpandAll">
|
||||||
|
<text class="text-24-bold" v-if="!isExpandAll">{{ t('common.see-all') }}</text>
|
||||||
|
<text class="text-24-bold" v-else>{{ t('common.hide-all') }}</text>
|
||||||
|
<image class="w-20rpx h-20rpx ml-10rpx shrink-0 transition-all" src="@img/2108@2x.png"
|
||||||
|
:class="[isExpandAll?'rotate-180':'']"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {appFiltersConfigGetFiltersGet} from "@/service";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['applyPrice'])
|
||||||
|
|
||||||
|
const priceValue = ref<number[]>([])
|
||||||
|
const priceList = ref([ '$', '$$', '$$$', '$$$$' ])
|
||||||
|
function handleClickApply() {
|
||||||
|
if (priceValue.value.length === 0) {
|
||||||
|
emit('applyPrice', [])
|
||||||
|
handleClose()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// 获取选中的价格条件数组
|
||||||
|
const selectedConditions = priceValue.value.map(index =>
|
||||||
|
priceData.value[index]?.filterCondition || ''
|
||||||
|
).filter(condition => condition !== '')
|
||||||
|
emit('applyPrice', selectedConditions)
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePrice(index: number) {
|
||||||
|
const currentIndex = priceValue.value.indexOf(index)
|
||||||
|
if (currentIndex > -1) {
|
||||||
|
// 如果已选中,则取消选中
|
||||||
|
priceValue.value.splice(currentIndex, 1)
|
||||||
|
} else {
|
||||||
|
// 如果未选中,则添加到选中列表
|
||||||
|
priceValue.value.push(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleClickReset() {
|
||||||
|
priceValue.value = []
|
||||||
|
emit('applyPrice', [])
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
function onOpen() {
|
||||||
|
show.value = true
|
||||||
|
// 获取筛选条件的值
|
||||||
|
getPriceValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceData = ref([])
|
||||||
|
function getPriceValue() {
|
||||||
|
appFiltersConfigGetFiltersGet({}).then((res: any) => {
|
||||||
|
console.log('res', res)
|
||||||
|
if(res.data && res.data.price.length > 0) {
|
||||||
|
priceData.value = res.data.price
|
||||||
|
}
|
||||||
|
console.log('res', priceData.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
onOpen
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup v-model="show" position="bottom" @close="handleClose" safe-area-inset-bottom>
|
||||||
|
<view class="bg-white px-30rpx pt-50rpx pb-60rpx">
|
||||||
|
<view class="text-center text-40rpx lh-40rpx text-#333 font-bold">{{ t('components.filtrate-tool.price') }}</view>
|
||||||
|
<view class="flex-center-sb gap-18rpx mt-56rpx">
|
||||||
|
<template v-for="(item, index) in priceList">
|
||||||
|
<view @click="changePrice(index)" :class="[priceValue.includes(index) ? 'bg-#14181B text-#fff' : 'bg-#F2F2F2 text-#333']" class="flex-1 text-center text-30rpx h-76rpx rounded-38rpx lh-76rpx font-500 ">{{ item }}</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<view class="mt-88rpx">
|
||||||
|
<wd-button custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block
|
||||||
|
@click="handleClickApply">
|
||||||
|
{{ t('common.apply') }}
|
||||||
|
</wd-button>
|
||||||
|
<view @click="handleClickReset" class="text-center mt-52rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.reset') }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['applyScore'])
|
||||||
|
const sliderValue = ref(1)
|
||||||
|
const sliderList = ref([ '3+', '3.5+', '4+', '4.5+', '5' ])
|
||||||
|
const scoreList = [
|
||||||
|
'3-3.5',
|
||||||
|
'3.5-4',
|
||||||
|
'4-4.5',
|
||||||
|
'4.5-5',
|
||||||
|
'5-10',
|
||||||
|
]
|
||||||
|
function handleClickApply() {
|
||||||
|
emit('applyScore', scoreList[sliderValue.value])
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
function handleClickReset() {
|
||||||
|
sliderValue.value = 1
|
||||||
|
emit('applyScore', null)
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
function onOpen() {
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
onOpen
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup v-model="show" position="bottom" @close="handleClose" safe-area-inset-bottom>
|
||||||
|
<view class="bg-white px-30rpx pt-50rpx pb-60rpx">
|
||||||
|
<view class="text-center text-40rpx lh-40rpx text-#333 font-bold">{{ t('components.filtrate-tool.score') }}</view>
|
||||||
|
<view class="mt-56rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('components.filtrate-tool.over') }} {{ sliderList[sliderValue] }}</view>
|
||||||
|
<view class="flex-center-sb mt-52rpx mb-30rpx">
|
||||||
|
<template v-for="item in sliderList">
|
||||||
|
<text class="text-30rpx lh-30rpx text-#333 font-500">{{ item }}</text>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<wd-slider v-model="sliderValue" :min="0" :max="4" :step="1" hide-min-max hide-label active-color="#F6F6F6" inactive-color="#333" />
|
||||||
|
|
||||||
|
<view class="mt-62rpx">
|
||||||
|
<wd-button custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block
|
||||||
|
@click="handleClickApply">
|
||||||
|
{{ t('common.apply') }}
|
||||||
|
</wd-button>
|
||||||
|
<view @click="handleClickReset" class="text-center mt-52rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('common.reset') }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const emit = defineEmits(["togglePickup", "toggleDiscount", "toggleScore", "togglePrice"]);
|
||||||
|
|
||||||
|
const isPickup = ref(false);
|
||||||
|
function togglePickup() {
|
||||||
|
isPickup.value = !isPickup.value;
|
||||||
|
emit("togglePickup", isPickup.value ? 1 : 2);
|
||||||
|
}
|
||||||
|
const isDiscount = ref(false);
|
||||||
|
function toggleDiscount() {
|
||||||
|
isDiscount.value = !isDiscount.value;
|
||||||
|
emit("toggleDiscount", isDiscount.value ? 1 : 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScore() {
|
||||||
|
emit("toggleScore");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePrice() {
|
||||||
|
emit("togglePrice");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<scroll-view :scroll-x="true">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="w-27rpx shrink-0"></view>
|
||||||
|
<view @click="togglePickup" :class="[isPickup ? 'bg-#333333 text-#fff' : 'bg-#F2F2F2 text-#333']" class="flex items-center transition-all mr-22rpx h-64rpx rounded-32rpx px-22rpx text-26rpx font-bold">
|
||||||
|
<image v-show="!isPickup" src="@img/chef/120.png" class="w-28rpx h-28rpx mr-10rpx"></image>
|
||||||
|
<image v-show="isPickup" src="@img/chef/122.png" class="w-28rpx h-28rpx mr-10rpx"></image>
|
||||||
|
{{ t('components.filtrate-tool.pickup') }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view @click="toggleDiscount" :class="[isDiscount ? 'bg-#333333 text-#fff' : 'bg-#F2F2F2 text-#333']" class="flex items-center transition-all mr-22rpx h-64rpx rounded-32rpx px-22rpx text-26rpx font-bold">
|
||||||
|
<image v-show="!isDiscount" src="@img/chef/121.png" class="w-28rpx h-28rpx mr-10rpx"></image>
|
||||||
|
<image v-show="isDiscount" src="@img/chef/123.png" class="w-28rpx h-28rpx mr-10rpx"></image>
|
||||||
|
{{ t('components.filtrate-tool.discount') }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view @click="handleScore" class="flex items-center mr-22rpx h-64rpx rounded-32rpx bg-#F2F2F2 px-22rpx text-26rpx text-#333 font-bold">
|
||||||
|
{{ t('components.filtrate-tool.score') }}
|
||||||
|
<image src="@img/chef/101.png" class="w-24rpx h-24rpx ml-10rpx"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view @click="handlePrice" class="flex items-center h-64rpx rounded-32rpx bg-#F2F2F2 px-22rpx text-26rpx text-#333 font-bold">
|
||||||
|
{{ t('components.filtrate-tool.price') }}
|
||||||
|
<image src="@img/chef/101.png" class="w-24rpx h-24rpx ml-10rpx"></image>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="w-27rpx shrink-0 op-0">1</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
text: string,
|
||||||
|
fixed?: boolean
|
||||||
|
}>(), {
|
||||||
|
fixed: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: [event: any]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
function handleClick(event: any) {
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="">
|
||||||
|
<view class="h-128rpx" :style="[configStore.iosSafeBottomPlaceholder]" v-if="props.fixed"></view>
|
||||||
|
<view class="z-1 bg-#fff shadow-[0rpx_-6rpx_24rpx_rgba(0,0,0,0.16)]" :style="[props.fixed&&{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '0',
|
||||||
|
left: '0',
|
||||||
|
right: '0',
|
||||||
|
}]">
|
||||||
|
<view class="h-128rpx p-[16rpx+30rpx] box-border">
|
||||||
|
<wd-button custom-class="!h-92rpx !text-30rpx !lh-42rpx !rounded-20rpx !bg-#14181B" block
|
||||||
|
@click="handleClick">
|
||||||
|
{{ props.text }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Config from '@/config'
|
||||||
|
import {throttle} from 'throttle-debounce'
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue?: string
|
||||||
|
disabled?: boolean
|
||||||
|
focus?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
modelValue: '',
|
||||||
|
disabled: false,
|
||||||
|
focus: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
search: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
emits('search')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = throttle(Config.throttleTime, search)
|
||||||
|
|
||||||
|
function handleInputUpdateModelValue(value: string) {
|
||||||
|
console.log(value)
|
||||||
|
emits('update:modelValue', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickLeft() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'HeaderSearch',
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="relative z-1 bg-#fff"
|
||||||
|
>
|
||||||
|
<status-bar/>
|
||||||
|
<view class="flex-center-sb px-30rpx h-88rpx">
|
||||||
|
<view class="shrink-0" @click="handleClickLeft">
|
||||||
|
<view class="i-fluent:ios-arrow-ltr-24-filled text-36rpx text-#333"></view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="px-36rpx w-626rpx h-88rpx flex items-center bg-common rounded-44rpx "
|
||||||
|
>
|
||||||
|
<image src="@img/chef/100222.png" class="w-28rpx h-28rpx mr-18rpx"></image>
|
||||||
|
<wd-input
|
||||||
|
no-border
|
||||||
|
clearable
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:disabled="disabled"
|
||||||
|
:focus="focus"
|
||||||
|
confirm-type="search"
|
||||||
|
use-prefix-slot
|
||||||
|
custom-class="flex items-center !text-30rpx !bg-transparent flex-1"
|
||||||
|
placeholderStyle="font-size: 30rpx;color: #6D6D6D; font-weight: 500;"
|
||||||
|
:modelValue="modelValue"
|
||||||
|
:placeholder="placeholder || t('common.prompt.please-enter-keyword-search')"
|
||||||
|
@update:modelValue="handleInputUpdateModelValue"
|
||||||
|
@confirm="handleSearch"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wd-input__clear) {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="container-animation">
|
||||||
|
<view class="part one"></view>
|
||||||
|
<view class="part two"></view>
|
||||||
|
<view class="part three"></view>
|
||||||
|
<view class="part four"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
$border-width: 10px;
|
||||||
|
$border-color: #333;
|
||||||
|
|
||||||
|
.container-animation {
|
||||||
|
position: relative;
|
||||||
|
animation: rotate 2s linear infinite;
|
||||||
|
}
|
||||||
|
.part {
|
||||||
|
position: absolute;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.one {
|
||||||
|
top: -30px;
|
||||||
|
left: -30px;
|
||||||
|
border-radius: 100% 0 0 0;
|
||||||
|
animation: animato1 1s linear 1s alternate infinite;
|
||||||
|
border-top: $border-width solid $border-color;
|
||||||
|
border-left: $border-width solid $border-color;
|
||||||
|
}
|
||||||
|
.two {
|
||||||
|
top: -30px;
|
||||||
|
border-radius: 0 100% 0 0;
|
||||||
|
animation: animato2 1s linear alternate infinite;
|
||||||
|
border-top: $border-width solid $border-color;
|
||||||
|
border-right: $border-width solid $border-color;
|
||||||
|
}
|
||||||
|
.three {
|
||||||
|
left: -30px;
|
||||||
|
border-radius: 0 0 0 100%;
|
||||||
|
animation: animato3 1s linear alternate infinite;
|
||||||
|
border-bottom: $border-width solid $border-color;
|
||||||
|
border-left: $border-width solid $border-color;
|
||||||
|
}
|
||||||
|
.four {
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
border-radius: 0 0 100% 0;
|
||||||
|
animation: animato4 1s linear 1s alternate infinite;
|
||||||
|
border-bottom: $border-width solid $border-color;
|
||||||
|
border-right: $border-width solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
background-color: orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes animato1 {
|
||||||
|
to {
|
||||||
|
width: 20rpx; height: 20rpx; top: -20rpx; left: -20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes animato2 {
|
||||||
|
to {
|
||||||
|
width: 20rpx; height: 20rpx; top: -20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes animato3 {
|
||||||
|
to {
|
||||||
|
width: 20rpx; height: 20rpx; left: -20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes animato4 {
|
||||||
|
to {
|
||||||
|
width: 20rpx; height: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import type {Goods} from "@/service/index-data";
|
||||||
|
import {thumbnailImg} from "@/utils/utils";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: Goods
|
||||||
|
}>()
|
||||||
|
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="w-336rpx bg-#fff rounded-20rpx"
|
||||||
|
@click="navigateTo(`/pages-shop/pages/goods-detail/index?id=${item.id}`)"
|
||||||
|
>
|
||||||
|
<view class="flex">
|
||||||
|
<image class="w-336rpx h-336rpx rounded-t-20rpx" :src="thumbnailImg(item.coverImage)"></image>
|
||||||
|
</view>
|
||||||
|
<view class="p-[18rpx+16rpx+24rpx]">
|
||||||
|
<view class="text-26rpx text-primary lh-36rpx font-bold line-clamp-2">{{ item.name }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-12rpx flex" v-if="item.goodsCategoryVo">
|
||||||
|
<view
|
||||||
|
class="h-32rpx px-10rpx center text-22rpx text-#999 lh-22rpx font-bold border-custom after:(!border-#D4D4D4 rounded-20rpx)">
|
||||||
|
{{ item.goodsCategoryVo?.name }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-18rpx flex items-end justify-between">
|
||||||
|
<text class="text-30rpx text-#666 font-bold lh-42rpx">S${{ item.price }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
title?: string;
|
||||||
|
fixed?: boolean
|
||||||
|
showLeft?: boolean
|
||||||
|
customClass?: string
|
||||||
|
}>(), {
|
||||||
|
fixed: true,
|
||||||
|
showLeft: true
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClickLeft() {
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-navbar
|
||||||
|
:title="props.title"
|
||||||
|
safeAreaInsetTop
|
||||||
|
:fixed="props.fixed"
|
||||||
|
:placeholder="props.fixed"
|
||||||
|
:bordered="false"
|
||||||
|
:custom-class="props.customClass"
|
||||||
|
@click-left="handleClickLeft">
|
||||||
|
<template #left>
|
||||||
|
<view class="shrink-0" v-if="showLeft">
|
||||||
|
<view class="i-carbon:chevron-left text-50rpx text-primary ml-[-10rpx]"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</template>
|
||||||
|
</wd-navbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wd-navbar) {
|
||||||
|
z-index: 2 !important;
|
||||||
|
.wd-navbar__title {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type SetPassword from "@/components/set-password/set-password.vue";
|
||||||
|
import type PasswordInput from "@/components/password-input/password-input.vue";
|
||||||
|
import {useUserStore} from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
success: [password: string];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const setPasswordRef = ref<InstanceType<typeof SetPassword> | null>(null);
|
||||||
|
const passwordInputRef = ref<InstanceType<typeof PasswordInput> | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
function showSetPassword() {
|
||||||
|
setPasswordRef.value?.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPasswordInput() {
|
||||||
|
if (!userStore.userInfo?.payPwd) {
|
||||||
|
showSetPassword()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
passwordInputRef.value?.init?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSuccess(password: string) {
|
||||||
|
emits("success", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "PasswordContainer",
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
showSetPassword,
|
||||||
|
showPasswordInput,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="">
|
||||||
|
<set-password ref="setPasswordRef"/>
|
||||||
|
<password-input ref="passwordInputRef" @success="handleSuccess"/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
success: [password: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const configStore= useConfigStore()
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const password = ref('')
|
||||||
|
const keyboardHeight = ref<number>(0)
|
||||||
|
const showKeyboard = ref<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
console.log('init')
|
||||||
|
password.value = ''
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false
|
||||||
|
password.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
console.log(password.value)
|
||||||
|
if (password.value.length === 6) {
|
||||||
|
emits('success', password.value)
|
||||||
|
close()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('common.please-enter-6-digit-payment-password'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = function (res: any) {
|
||||||
|
console.log(res)
|
||||||
|
keyboardHeight.value = res.height
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
uni.onKeyboardHeightChange(listener)
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
uni.offKeyboardHeightChange(listener)
|
||||||
|
// #endif
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'PasswordInput',
|
||||||
|
styleIsolation: 'shared',
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
position="bottom"
|
||||||
|
safe-area-inset-bottom
|
||||||
|
v-model="show"
|
||||||
|
custom-class="rounded-t-30rpx !p-[40rpx+20rpx+48rpx]"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="mb-48rpx relative flex justify-center text-center text-34rpx text-primary font-bold"
|
||||||
|
>
|
||||||
|
<text>{{ t('common.please-enter-your-payment-password') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="relative">
|
||||||
|
<wd-password-input custom-class="flex-1 h-50px"
|
||||||
|
:length="6"
|
||||||
|
v-model="password" :focused="showKeyboard"/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
:maxlength="6"
|
||||||
|
:cursor-spacing="20"
|
||||||
|
v-model="password"
|
||||||
|
cursor-color="transparent"
|
||||||
|
class="absolute top-0 left-0 opacity-0 w-ful h-full"
|
||||||
|
@blur="showKeyboard = false"
|
||||||
|
@focus="showKeyboard = true"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="mt-60rpx">
|
||||||
|
<wd-button
|
||||||
|
custom-class="!w-full !h-88rpx !m-0 !text-30rpx !font-bold !rounded-20rpx"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>{{ t('common.confirm') }}
|
||||||
|
</wd-button
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view class="" :style="[configStore.isIos?{
|
||||||
|
height: keyboardHeight + 'px',
|
||||||
|
transition: 'height 0.3s ease-in-out',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}:{}]"></view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wd-password-input) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-password-input__item) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { getCaptcha } from '@/service'
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
success: [{ code: string; uuid: string }]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const imgCode = ref('')
|
||||||
|
const captcha = ref({
|
||||||
|
img: '',
|
||||||
|
uuid: '',
|
||||||
|
})
|
||||||
|
const show = ref(false)
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
initData()
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false
|
||||||
|
imgCode.value = ''
|
||||||
|
captcha.value = {
|
||||||
|
img: '',
|
||||||
|
uuid: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
if (!imgCode.value) {
|
||||||
|
return uni.showToast({ title: '请输入图形验证码', icon: 'none' })
|
||||||
|
}
|
||||||
|
emits('success', { code: imgCode.value, uuid: captcha.value.uuid })
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initData() {
|
||||||
|
try {
|
||||||
|
const res = await getCaptcha()
|
||||||
|
captcha.value = {
|
||||||
|
img: 'data:image/jpeg;base64,' + res?.data?.img,
|
||||||
|
uuid: res?.data?.uuid,
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup :close-on-click-modal="false" custom-style="border-radius: 20rpx;" v-model="show">
|
||||||
|
<view class="content">
|
||||||
|
<view class="content__header">
|
||||||
|
<view class="content__title">请输入图形验证码</view>
|
||||||
|
<view class="content__close-icon" @click="close">
|
||||||
|
<image src="@/pages-login/static/images/icon_del@2x.png"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="content__body">
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border border-solid border-gray-3 rounded-50rpx"
|
||||||
|
>
|
||||||
|
<wd-input
|
||||||
|
no-border
|
||||||
|
v-model.trim="imgCode"
|
||||||
|
placeholderStyle="font-size: 28rpx;line-height: 40rpx;color: #999999;"
|
||||||
|
placeholder="请输入"
|
||||||
|
custom-class="flex-1 p-[15rpx+30rpx] !bg-transparent "
|
||||||
|
></wd-input>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<image :src="captcha.img" class="w-180rpx h-80rpx rounded-r-50rpx"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content__tip" @click="initData">{{ $t('securityCode.cantSee') }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="content__footer">
|
||||||
|
<wd-button block @click="handleSubmit">{{ $t('securityCode.confirm') }}</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 630rpx;
|
||||||
|
padding: 30rpx 40rpx 40rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
margin: 42rpx 0 40rpx;
|
||||||
|
|
||||||
|
.uni-easyinput {
|
||||||
|
height: 90rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: $uni-border-radius-lg;
|
||||||
|
border: 1rpx solid #d1d1d1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 8rpx;
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
|
||||||
|
image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 48rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tip {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 33rpx;
|
||||||
|
color: $uni-color-primary;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {useConfigStore, useUserStore} from "@/store";
|
||||||
|
import {getNailServiceDetail} from "@/service";
|
||||||
|
import type {Service} from "@/service/index-data";
|
||||||
|
import {thumbnailImg} from "@/utils/utils";
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
'success': [data: Service]
|
||||||
|
}>()
|
||||||
|
const {t} = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const info = ref<Service>({})
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
|
const images = computed(() => {
|
||||||
|
const {images} = info.value
|
||||||
|
if (R.equals(R.type(images), 'String') && R.isNotEmpty(images)) {
|
||||||
|
return R.split(',', images as string)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
function init(id: string) {
|
||||||
|
initData(id)
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handlePreview(current: string, urls: string[]) {
|
||||||
|
uni.previewImage({
|
||||||
|
current,
|
||||||
|
urls
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
if (!userStore.checkLogin()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
emits('success', info.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = debounce(Config.debounceLongTime, submit, {atBegin: true});
|
||||||
|
|
||||||
|
async function initData(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await getNailServiceDetail({
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
|
||||||
|
info.value = res.data || {}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "ServiceDetail",
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
|
||||||
|
custom-class="rounded-t-30rpx"
|
||||||
|
position="bottom"
|
||||||
|
safe-area-inset-bottom
|
||||||
|
v-model="show"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="box-border h-1190rpx p-[40rpx+30rpx+0rpx] flex flex-col"
|
||||||
|
>
|
||||||
|
<view class="relative pb-20rpx pr-32rpx flex items-center justify-between">
|
||||||
|
<view class="text-34rpx text-primary font-bold lh-48rpx">
|
||||||
|
{{ info.name }}
|
||||||
|
</view>
|
||||||
|
<image class="absolute right-0 w-32rpx h-32rpx" src="@img/2106@2x.png" @click="close"></image>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y="true" class="mt-8rpx flex-1 overflow-hidden">
|
||||||
|
<view class="text-26rpx text-#999 font-bold lh-36rpx">{{
|
||||||
|
t('common.over')
|
||||||
|
}} {{ info.serviceTime }} {{ t('common.number-minutes') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-20rpx text-26rpx text-#666 font-bold lh-36rpx">S${{ info.serviceAmount }}</view>
|
||||||
|
<view class="mt-40rpx pb-30rpx text-24-bold">
|
||||||
|
{{ info.serviceDetail }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-4rpx flex justify-between flex-wrap">
|
||||||
|
<view class="relative mt-20rpx flex shrink-0" v-for="(item, index) in images"
|
||||||
|
:key="item"
|
||||||
|
@click="handlePreview(item, images)"
|
||||||
|
>
|
||||||
|
<image class=" w-202rpx h-202rpx rounded-20rpx" :src="thumbnailImg(item)"></image>
|
||||||
|
</view>
|
||||||
|
<template v-if="images.length % 3 === 1">
|
||||||
|
<view class="w-202rpx h-202rpx"></view>
|
||||||
|
<view class="w-202rpx h-202rpx"></view>
|
||||||
|
</template>
|
||||||
|
<template v-if="images.length % 3 === 2">
|
||||||
|
<view class="w-202rpx h-202rpx"></view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<view class="h-30rpx"></view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
<fixed-bottom-large-btn :text="t('common.add-to-booking')"
|
||||||
|
@click="handleSubmit"
|
||||||
|
/>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const show = ref(false)
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirm() {
|
||||||
|
close()
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-user/pages/pay-password/set/index',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
|
||||||
|
custom-class="rounded-20rpx"
|
||||||
|
safe-area-inset-bottom
|
||||||
|
v-model="show"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="w-630rpx box-border p-[40rpx+44rpx] flex flex-col">
|
||||||
|
<view class="mt-18rpx text-34-bold text-center">
|
||||||
|
{{ t('common.prompt.not-setup-payment-password') }}
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<view class="mt-58rpx flex items-center justify-between">
|
||||||
|
<wd-button
|
||||||
|
custom-class="!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !rounded-20rpx"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('common.cancel') }}
|
||||||
|
</wd-button>
|
||||||
|
|
||||||
|
<wd-button plain
|
||||||
|
custom-class="!h-88rpx !w-258rpx !min-w-auto !text-30rpx !lh-42rpx !font-bold !border-#666666 !rounded-20rpx"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
|
{{ t('common.go-to-settings') }}
|
||||||
|
</wd-button>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
function getSystemInfo(): any {
|
||||||
|
let systemInfo: any
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
try {
|
||||||
|
// const systemSetting = uni.getSystemSetting() // 暂时不需要
|
||||||
|
const deviceInfo = uni.getDeviceInfo()
|
||||||
|
const windowInfo = uni.getWindowInfo()
|
||||||
|
const appBaseInfo = uni.getAppBaseInfo()
|
||||||
|
systemInfo = {
|
||||||
|
...deviceInfo,
|
||||||
|
...windowInfo,
|
||||||
|
...appBaseInfo
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('获取系统信息失败,降级使用uni.getSystemInfoSync:', error)
|
||||||
|
// 降级处理,使用原来的方法
|
||||||
|
systemInfo = uni.getSystemInfoSync()
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
systemInfo = uni.getSystemInfoSync()
|
||||||
|
// #endif
|
||||||
|
return systemInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
const { statusBarHeight } = getSystemInfo()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="w-750rpx shrink-0" :style="[{ height: statusBarHeight + 'px' }]"></view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type chooseLanguageVue from "@/components/choose-language/choose-language.vue";
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const chooseLanguageRef = ref<InstanceType<typeof chooseLanguageVue> | null>(null)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<view class="flex justify-end">
|
||||||
|
<view
|
||||||
|
class="flex items-center px-16rpx h-64rpx bg-primary rounded-l-50rpx shadow-[0rpx_6rpx_12rpx_rgba(19,72,173,0.24)]"
|
||||||
|
hover-class="!opacity-90"
|
||||||
|
|
||||||
|
@click.stop="chooseLanguageRef?.init()">
|
||||||
|
<image class="shrink-0 mr-6rpx w-32rpx h-32rpx" src="@img/17448@2x.png"></image>
|
||||||
|
<text class="mr-8rpx text-28rpx text-#fff font-bold">{{ t('common.switch-language') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
success: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const {t} = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
|
||||||
|
const verificationCode = ref('');
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
|
|
||||||
|
function init(code: string) {
|
||||||
|
verificationCode.value = code;
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
close();
|
||||||
|
emits('success')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = debounce(Config.debounceLongTime, submit, {
|
||||||
|
atBegin: true
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
close()
|
||||||
|
// navigateTo("/pages-shop/pages/booking-service/make-appointment/index")
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "UseCode",
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
init,
|
||||||
|
close,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
custom-class="!bg-transparent"
|
||||||
|
safe-area-inset-bottom
|
||||||
|
v-model="show"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="box-border ">
|
||||||
|
<view class="p-[40rpx+40rpx+34rpx] bg-#fff rounded-20rpx"
|
||||||
|
>
|
||||||
|
<view class="text-44rpx text-primary font-bold lh-62rpx text-center">
|
||||||
|
{{ t('pages-store.order.useCode') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-40rpx center">
|
||||||
|
<uqrcode ref="uqrcode" canvas-id="qrcode" :value="verificationCode"
|
||||||
|
:size="512"
|
||||||
|
sizeUnit="rpx"
|
||||||
|
:options="{}"></uqrcode>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="center mt-52rpx">
|
||||||
|
<image class="w-80rpx h-80rpx shrink-0" src="@img/20241@2x.png" @click="close"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
const show = ref(false);
|
||||||
|
const pickerValue = ref(0)
|
||||||
|
const emits = defineEmits(['confirm'])
|
||||||
|
|
||||||
|
function onOpen(value: number) {
|
||||||
|
if (value) {
|
||||||
|
pickerValue.value = value;
|
||||||
|
}
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
function handleSubmit() {
|
||||||
|
emits('confirm', columns.value[pickerValue.value])
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = ref([{
|
||||||
|
value: 0,
|
||||||
|
label: t('components.visit.leaveItToMePersonally'),
|
||||||
|
}, {
|
||||||
|
value: 1,
|
||||||
|
label: t('components.visit.putItAtTheDoor'),
|
||||||
|
}])
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
onOpen,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
v-model="show"
|
||||||
|
position="bottom"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view class="flex-center-sb h-102rpx bg-#F7F7F7 px-30rpx">
|
||||||
|
<view @click="handleClose" class="text-30rpx text-#999">{{ t('common.cancel') }}</view>
|
||||||
|
<view class="text-34rpx text-#333">{{ t('common.select') }}</view>
|
||||||
|
<view @click="handleSubmit" class="text-30rpx text-#FF6106">{{ t('common.confirm') }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="bg-#fff px-54rpx py-56rpx">
|
||||||
|
<wd-picker-view :columns="columns" v-model="pickerValue"/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.uni-picker-view-wrapper) {
|
||||||
|
& > uni-picker-view-column:first-of-type .uni-picker-view-group {
|
||||||
|
.uni-picker-view-indicator {
|
||||||
|
border-radius: 20rpx 0 0 20rpx !important;
|
||||||
|
&:after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.wd-picker-view-column__item) {
|
||||||
|
line-height: 94rpx !important;
|
||||||
|
}
|
||||||
|
:deep(.uni-picker-view-indicator) {
|
||||||
|
height: 94rpx !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
const Config = {
|
||||||
|
appName: "CHEFLINK",
|
||||||
|
googleMapKey: "AIzaSyDvTA1j9_hPPg7kev4fzf6RlGpf_yYhdoo",
|
||||||
|
// stripeKey:
|
||||||
|
// "pk_test_51RcyzCQVf8HI8x55xHQ11F0ydksiLEscmWuut6o0eCHV8fCYOWI9F9VrddMlKarnux65EjSIQmFb8rTwrrjrRzgG00CyQVpyRQ",
|
||||||
|
stripeKey:
|
||||||
|
"pk_live_51Rcyz0HqArf2IYTZTxK8mXrmUoxLJuC6QpNYgG76CEGSD6D3pUi48QkIwuyEAtqklEwaLC6cHGP5vntuiAFWB7cY000m2o2AU1",
|
||||||
|
defaultLanguage: "en",
|
||||||
|
timezone: "Asia/Shanghai",
|
||||||
|
debounceLongTime: 1000,
|
||||||
|
debounceShortTime: 500,
|
||||||
|
throttleTime: 1000,
|
||||||
|
throttleShortTime: 500,
|
||||||
|
iosId: "6615065357",
|
||||||
|
weixinServiceUrl: `https://chatbot.weixin.qq.com/webapp/0USvjruTJTMczuq6ND6gVwAJj4hG6g?isFloat=false&robotName=CHEFLINK`,
|
||||||
|
weixinServiceName: "CHEFLINK",
|
||||||
|
// 登录页
|
||||||
|
loginPath: "/pages-login/pages/index",
|
||||||
|
// 首页
|
||||||
|
indexPath: "/pages/home/index",
|
||||||
|
// 引导页
|
||||||
|
guidePath: "/pages-login/pages/guide-page/location",
|
||||||
|
shareLink: "https://www.howhowfresh.com/h5/",
|
||||||
|
shareImage: "https://hanguomcn.oss-ap-northeast-2.aliyuncs.com/images/20250901105756_1756695476183_56ac07ac.png",
|
||||||
|
shareDesc: "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
||||||
|
// 登录端口
|
||||||
|
userPort: 1,
|
||||||
|
// 手机区号数组
|
||||||
|
phoneCodeList: [
|
||||||
|
"+86", "+886", "+852", "+853", "+93", "+355", "+213", "+684", "+376", "+244",
|
||||||
|
"+1264", "+672", "+1268", "+54", "+374", "+297", "+61", "+43", "+994", "+973",
|
||||||
|
"+880", "+1246", "+375", "+32", "+501", "+229", "+1441", "+975", "+591", "+387",
|
||||||
|
"+267", "+55", "+1284", "+673", "+359", "+226", "+95", "+257", "+855", "+237",
|
||||||
|
"+1", "+238", "+1345", "+236", "+235", "+56", "+61", "+61", "+57", "+269", "+243",
|
||||||
|
"+242", "+682", "+506", "+225", "+385", "+53", "+357", "+420", "+45", "+253",
|
||||||
|
"+1767", "+1809", "+593", "+20", "+503", "+240", "+291", "+372", "+251", "+500",
|
||||||
|
"+298", "+679", "+358", "+33", "+594", "+689", "+241", "+995", "+49", "+233",
|
||||||
|
"+350", "+30", "+299", "+1473", "+590", "+1671", "+502", "+1481", "+224", "+245",
|
||||||
|
"+592", "+509", "+379", "+504", "+36", "+354", "+91", "+62", "+98", "+964", "+353",
|
||||||
|
"+972", "+39", "+1876", "+81", "+962", "+73", "+254", "+686", "+850", "+82", "+965",
|
||||||
|
"+996", "+856", "+371", "+961", "+266", "+231", "+218", "+423", "+370", "+352",
|
||||||
|
"+389", "+261", "+265", "+60", "+960", "+223", "+356", "+692", "+596", "+222",
|
||||||
|
"+230", "+269", "+52", "+691", "+373", "+377", "+976", "+1664", "+212", "+258",
|
||||||
|
"+264", "+674", "+977", "+31", "+599", "+687", "+64", "+505", "+227", "+234",
|
||||||
|
"+683", "+6723", "+1", "+47", "+968", "+92", "+680", "+507", "+675", "+595",
|
||||||
|
"+51", "+63", "+48", "+351", "+1809", "+974", "+262", "+40", "+7", "+250", "+290",
|
||||||
|
"+1869", "+1758", "+508", "+1784", "+685", "+378", "+239", "+966", "+221", "+381",
|
||||||
|
"+248", "+232", "+65", "+421", "+386", "+677", "+252", "+27", "+34", "+94", "+249",
|
||||||
|
"+597", "+47", "+268", "+46", "+41", "+963", "+992", "+255", "+66", "+1242", "+220",
|
||||||
|
"+228", "+690", "+676", "+1868", "+216", "+90", "+993", "+1649", "+688", "+256",
|
||||||
|
"+380", "+971", "+44", "+1", "+598", "+998", "+678", "+58", "+84", "+1340", "+681",
|
||||||
|
"+967", "+260", "+263"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Config;
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/** 协议 */
|
||||||
|
export enum Agreement {
|
||||||
|
/** 用户须知及使用协议- */
|
||||||
|
USER_AGREEMENT = "chef_user_agreement",
|
||||||
|
/** 隐私政策 */
|
||||||
|
PRIVACY_POLICY = "chef_privacy_policy",
|
||||||
|
/** 钱包说明 */
|
||||||
|
BALANCE_EXPLANATION = "balance",
|
||||||
|
/** 平台协议 */
|
||||||
|
CHEF_PLATFORM_AGREEMENT = "chef_platform_agreement",
|
||||||
|
/** 关于我们 */
|
||||||
|
ABOUT_US = "ABOUT_US",
|
||||||
|
/** 积分说明 */
|
||||||
|
POINTS_EXPLANATION = "POINTS_EXPLANATION",
|
||||||
|
/** 会员说明 */
|
||||||
|
MEMBERSHIP_DESCRIPTION = "MEMBERSHIP_DESCRIPTION",
|
||||||
|
/** 会员支付协议 */
|
||||||
|
MEMBERSHIP_PAYMENT_AGREEMENT = "MEMBERSHIP_PAYMENT_AGREEMENT",
|
||||||
|
/** 支付说明 */
|
||||||
|
DEPOSIT_EXPLANATION = "DEPOSIT_EXPLANATION",
|
||||||
|
/** GST说明 */
|
||||||
|
GST_EXPLANATION = "GST_EXPLANATION",
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 短信验证码类型 */
|
||||||
|
export enum SmsType {
|
||||||
|
/** 绑定手机号 */
|
||||||
|
USER_BIND_PHONE_NUMBER = 1,
|
||||||
|
/** 邮箱注册 */
|
||||||
|
USER_EMAIL_REGISTER = 2,
|
||||||
|
/** 忘记密码 */
|
||||||
|
USER_FORGET_PASSWORD = 3,
|
||||||
|
/** 忘记支付密码 */
|
||||||
|
USER_FORGET_PAYMENT_PASSWORD = 4,
|
||||||
|
/** 设置支付密码 */
|
||||||
|
USER_SET_PAYMENT_PASSWORD = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 登录类型 */
|
||||||
|
export enum LoginType {
|
||||||
|
/** 邮箱登录 */
|
||||||
|
EMAIL = 1,
|
||||||
|
/** 苹果登录 */
|
||||||
|
APPLE = 2,
|
||||||
|
/** 脸书登录 */
|
||||||
|
FACEBOOK = 3,
|
||||||
|
/** 谷歌登录 */
|
||||||
|
GOOGLE = 4,
|
||||||
|
/** 账号密码登录 */
|
||||||
|
ACCOUNT = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 字典 */
|
||||||
|
export enum DictType {
|
||||||
|
/** 订单相关 */
|
||||||
|
ABOUT_ORDER = "about_order",
|
||||||
|
/** 平台相关 */
|
||||||
|
ABOUT_PLATFORM = "about_platform",
|
||||||
|
/** 价格范围 */
|
||||||
|
PRICE_RANGE = "price_range",
|
||||||
|
/** 购买须知 */
|
||||||
|
PURCHASE_NOTES = "purchase_notes",
|
||||||
|
/** 活动说明 */
|
||||||
|
ACTIVITY_DESCRIPTION = "activity_description",
|
||||||
|
/** 预约提示 */
|
||||||
|
APPOINTMENT_REMINDER = "appointment_reminder",
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字典 */
|
||||||
|
export enum DictValue {
|
||||||
|
/** 待付款订单取消时间 */
|
||||||
|
PENDING_PAYMENT_ORDER_CANCEL_TIME = "pending_payment_order_cancel_time",
|
||||||
|
/** 退款订单自动审核时间 */
|
||||||
|
REFUND_ORDER_AUTO_AUDIT_TIME = "refund_order_auto_audit_time",
|
||||||
|
/** 平台联系方式 */
|
||||||
|
CONTACT_METHOD = "contact_method",
|
||||||
|
|
||||||
|
/** 多人预约折扣提示 */
|
||||||
|
GROUP_DISCOUNT_EN = "group_discount_en",
|
||||||
|
/** 多人预约折扣提示 */
|
||||||
|
GROUP_DISCOUNT_ZH = "group_discount_zh",
|
||||||
|
/**
|
||||||
|
卸甲预约、补甲预约免费时长提示 */
|
||||||
|
FREE_REMOVAL_SERVICE_AVAILABLE_EN = "free_removal_service_available_en",
|
||||||
|
/**
|
||||||
|
卸甲预约、补甲预约免费时长提示 */
|
||||||
|
FREE_REMOVAL_SERVICE_AVAILABLE_ZH = "free_removal_service_available_zh"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 自定义事件 */
|
||||||
|
export enum EventEnum {
|
||||||
|
/** 裁剪头像 */
|
||||||
|
CROPPER_AVATAR = "CROPPER_AVATAR",
|
||||||
|
/** 展示星级筛选 */
|
||||||
|
STAR_RATING_FILTER = "show-star-rating-filter",
|
||||||
|
/** 展示价格筛选 */
|
||||||
|
PRICE_FILTER = "show-price-filter",
|
||||||
|
/** 选择地址 */
|
||||||
|
CHOOSE_ADDRESS = "CHOOSE_ADDRESS",
|
||||||
|
/** 选择预约时间 */
|
||||||
|
CHOOSE_APPOINTMENT_TIME = "CHOOSE_APPOINTMENT_TIME",
|
||||||
|
/** 选择支付方式 */
|
||||||
|
CHOOSE_PAYMENT_METHOD = "CHOOSE_PAYMENT_METHOD",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户地址类型 地址类型(house,apartment,office,hotel,other)
|
||||||
|
export enum UserAddressType {
|
||||||
|
/** 家庭地址 */
|
||||||
|
HOUSE = "house",
|
||||||
|
/** 公寓地址 */
|
||||||
|
APARTMENT = "apartment",
|
||||||
|
/** 办公地址 */
|
||||||
|
OFFICE = "office",
|
||||||
|
/** 酒店地址 */
|
||||||
|
HOTEL = "hotel",
|
||||||
|
/** 其他地址 */
|
||||||
|
OTHER = "other",
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 收藏对象类型(1-菜谱 2-菜品 3-配菜) */
|
||||||
|
export enum CollectionType {
|
||||||
|
/** 菜谱 */
|
||||||
|
RECIPE = 1,
|
||||||
|
/** 菜品 */
|
||||||
|
DISH = 2,
|
||||||
|
/** 配菜 */
|
||||||
|
SIDE_DISH = 3,
|
||||||
|
/** 店铺 */
|
||||||
|
STORE = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单状态
|
||||||
|
export enum OrderStatus {
|
||||||
|
/** 已退款 */
|
||||||
|
REFUNDED = -2,
|
||||||
|
/** 已取消 */
|
||||||
|
CANCELLED = -1,
|
||||||
|
/** 待付款 */
|
||||||
|
PENDING_PAYMENT = 1,
|
||||||
|
/** 已付款 */
|
||||||
|
HAS_PENDING_PAYMENT = 2,
|
||||||
|
/** 商家已接单 */
|
||||||
|
MERCHANT_ACCEPTED = 3,
|
||||||
|
/** 配送中 */
|
||||||
|
DELIVERING = 4,
|
||||||
|
/** 已核销(已送达) */
|
||||||
|
COMPLETED = 5,
|
||||||
|
/** 商家拒绝接单 */
|
||||||
|
MERCHANT_REJECTED = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单取消状态
|
||||||
|
export enum OrderCancelStatus {
|
||||||
|
/**
|
||||||
|
* 申请退款
|
||||||
|
*/
|
||||||
|
APPLIED = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商家同意
|
||||||
|
*/
|
||||||
|
APPROVED = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商家拒绝
|
||||||
|
*/
|
||||||
|
REJECTED = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 消息类型 */
|
||||||
|
export enum MessageTypeEnum {
|
||||||
|
/** 有人评论了你 */
|
||||||
|
COMMENTED = 1,
|
||||||
|
/** 有人回复了你 */
|
||||||
|
REPLIED = 2,
|
||||||
|
/** 用户下单 */
|
||||||
|
ORDER_CREATED = 3,
|
||||||
|
/** 商家接单 */
|
||||||
|
MERCHANT_ACCEPTED = 4,
|
||||||
|
/** 商家同意退款 */
|
||||||
|
REFUND_AGREED = 5,
|
||||||
|
/** 商家拒绝退款 */
|
||||||
|
REFUND_REJECTED = 6,
|
||||||
|
/** 商家开始配送 */
|
||||||
|
DELIVERY_STARTED = 7,
|
||||||
|
/** 商家已送达 */
|
||||||
|
DELIVERY_ARRIVED = 8,
|
||||||
|
/** 订单已核销 */
|
||||||
|
ORDER_WRITTEN_OFF = 9,
|
||||||
|
/** 商家入驻审核通过 */
|
||||||
|
SETTLEMENT_APPROVED = 10,
|
||||||
|
/** 商家入驻审核未通过 */
|
||||||
|
SETTLEMENT_REJECTED = 11,
|
||||||
|
/** 评论审核通过 */
|
||||||
|
COMMENT_APPROVED = 13,
|
||||||
|
/** 评论审核未通过 */
|
||||||
|
COMMENT_REJECTED = 14,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/// <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
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
/** 网站标题,应用名称 */
|
||||||
|
readonly VITE_APP_TITLE: string
|
||||||
|
/** 服务端口号 */
|
||||||
|
readonly VITE_SERVER_PORT: string
|
||||||
|
/** 后台接口地址 */
|
||||||
|
readonly VITE_SERVER_BASEURL: string
|
||||||
|
/** H5是否需要代理 */
|
||||||
|
readonly VITE_APP_PROXY: 'true' | 'false'
|
||||||
|
/** H5是否需要代理,需要的话有个前缀 */
|
||||||
|
readonly VITE_APP_PROXY_PREFIX: string // 一般是/api
|
||||||
|
/** 是否清除console */
|
||||||
|
readonly VITE_DELETE_CONSOLE: string
|
||||||
|
/** 是否是开发环境 */
|
||||||
|
readonly DEV: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export default function useAreaCode() {
|
||||||
|
|
||||||
|
const defaultAreaCode = ref("+1")
|
||||||
|
|
||||||
|
|
||||||
|
function handlePageClick() {
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultAreaCode,
|
||||||
|
handlePageClick
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default function useEventEmit(eventName: string, callback: (result: any) => void) {
|
||||||
|
|
||||||
|
onLoad(() => uni.$on(eventName, callback))
|
||||||
|
|
||||||
|
onUnload(() => uni.$off(eventName))
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {appSmsSendPost} from "@/service";
|
||||||
|
|
||||||
|
// 获取短信验证码的自定义 Hook
|
||||||
|
export default function useGetMsgCode() {
|
||||||
|
const {t} = useI18n()
|
||||||
|
const isSend = ref(0)
|
||||||
|
|
||||||
|
async function getMsgCode({
|
||||||
|
type, phone, areaCode
|
||||||
|
}: {
|
||||||
|
type: string | number;
|
||||||
|
phone: string | number,
|
||||||
|
areaCode: string | number,
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const res = await appSmsSendPost({
|
||||||
|
body: {
|
||||||
|
areaCode,
|
||||||
|
phone,
|
||||||
|
// 1 :用户端绑定手机号 2 :用户端邮箱注册 3 :用户端忘记密码 4 :用户端忘记支付密码 5 :用户端设置支付密码
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res) {
|
||||||
|
await uni.showToast({title: t('common.verification-code-sent-successfully'), icon: 'none'})
|
||||||
|
isSend.value = 60
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (isSend.value === 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
} else {
|
||||||
|
isSend.value -= 1
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSend,
|
||||||
|
getMsgCode
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
// 监听网络状态变化的自定义 Hook
|
||||||
|
export default function useNetworkStatusChange(callback: () => void) {
|
||||||
|
|
||||||
|
const OnNetworkStatusChange = (res: any) => {
|
||||||
|
console.log('=>(useNetworkStatusChange)', res)
|
||||||
|
|
||||||
|
if (res.isConnected) {
|
||||||
|
callback && callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
uni.onNetworkStatusChange(OnNetworkStatusChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
uni.offNetworkStatusChange(OnNetworkStatusChange)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// 自定义分页 Hook
|
||||||
|
export default function usePage<T>(
|
||||||
|
queryFun: (pageNum: number, pageSize: number) => Promise<IResData<T>>,
|
||||||
|
callback?: (params?: any) => void,
|
||||||
|
) {
|
||||||
|
const paging = ref<ZPagingInstance<T> | null>(null)
|
||||||
|
const loading = ref(true)
|
||||||
|
const firstLoaded = ref(false)
|
||||||
|
const dataList = ref<T[]>([])
|
||||||
|
const totalRows = ref(0)
|
||||||
|
|
||||||
|
async function queryList(pageNum: number, pageSize: number) {
|
||||||
|
try {
|
||||||
|
const res = await queryFun(pageNum, pageSize)
|
||||||
|
console.log(res)
|
||||||
|
await paging.value?.complete(res?.rows)
|
||||||
|
totalRows.value = res?.total
|
||||||
|
firstLoaded.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
callback && callback({...res, pageNum, pageSize})
|
||||||
|
}, 100)
|
||||||
|
} catch (error) {
|
||||||
|
await paging.value?.complete(false)
|
||||||
|
setTimeout(() => {
|
||||||
|
callback && callback({pageNum, pageSize})
|
||||||
|
}, 100)
|
||||||
|
} finally {
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
paging,
|
||||||
|
firstLoaded,
|
||||||
|
loading,
|
||||||
|
dataList,
|
||||||
|
totalRows,
|
||||||
|
queryList,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { throttle } from 'throttle-debounce'
|
||||||
|
|
||||||
|
export function useScrollThreshold(threshold = 250, delay = 150) {
|
||||||
|
const isThresholdReached = ref(false)
|
||||||
|
|
||||||
|
// 创建节流处理函数 (使用箭头函数保持this指向)
|
||||||
|
const throttledHandler = throttle(delay, (scrollTop: number) => {
|
||||||
|
const newState = scrollTop > threshold
|
||||||
|
// 仅当状态变化时更新
|
||||||
|
if (isThresholdReached.value !== newState) {
|
||||||
|
isThresholdReached.value = newState
|
||||||
|
console.log("阈值状态更新:", newState, "| 滚动位置:", scrollTop)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleScroll = (e: any) => {
|
||||||
|
// 传递滚动位置给节流函数
|
||||||
|
throttledHandler(e.scrollTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
uni.$on('page-scroll', handleScroll)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
uni.$off('page-scroll', handleScroll)
|
||||||
|
throttledHandler.cancel() // 清除节流函数残留任务
|
||||||
|
})
|
||||||
|
|
||||||
|
return isThresholdReached
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { CustomRequestOptions } from '@/http/types'
|
||||||
|
import {http as httpUtils} from "@/utils/http";
|
||||||
|
|
||||||
|
export function http<T>(options: CustomRequestOptions) {
|
||||||
|
// 1. 返回 Promise 对象
|
||||||
|
return httpUtils<T>(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET 请求
|
||||||
|
* @param url 后台地址
|
||||||
|
* @param query 请求query参数
|
||||||
|
* @param header 请求头,默认为json格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
method: 'GET',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求
|
||||||
|
* @param url 后台地址
|
||||||
|
* @param data 请求body参数
|
||||||
|
* @param query 请求query参数,post请求也支持query,很多微信接口都需要
|
||||||
|
* @param header 请求头,默认为json格式
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
|
||||||
|
return http<T>({
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
data,
|
||||||
|
method: 'POST',
|
||||||
|
header,
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
http.get = httpGet
|
||||||
|
http.post = httpPost
|
||||||
|
|
||||||
|
// 支持与 alovaJS 类似的API调用
|
||||||
|
http.Get = httpGet
|
||||||
|
http.Post = httpPost
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* 在 uniapp 的 RequestOptions 和 IUniUploadFileOptions 基础上,添加自定义参数
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CustomRequestOptions extends UniApp.RequestOptions {
|
||||||
|
query?: Record<string, any>
|
||||||
|
/** 出错时是否隐藏错误提示 */
|
||||||
|
hideErrorToast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用响应格式
|
||||||
|
export interface IResponse<T = any> {
|
||||||
|
code: number | string
|
||||||
|
data: T
|
||||||
|
message: string
|
||||||
|
status: string | number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页请求参数
|
||||||
|
export interface PageParams {
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应数据
|
||||||
|
export interface PageResult<T> {
|
||||||
|
list: T[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
pageSize: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { CustomRequestOptions } from '@/http/types'
|
||||||
|
import { http } from './http'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* openapi-ts-request 工具的 request 跨客户端适配方法
|
||||||
|
*/
|
||||||
|
export default function request<T = unknown>(
|
||||||
|
url: string,
|
||||||
|
options: Omit<CustomRequestOptions, 'url'> & {
|
||||||
|
params?: Record<string, unknown>
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const requestOptions = {
|
||||||
|
url,
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.params) {
|
||||||
|
requestOptions.query = requestOptions.params
|
||||||
|
delete requestOptions.params
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.headers) {
|
||||||
|
requestOptions.header = options.headers
|
||||||
|
delete requestOptions.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
return http<T>(requestOptions)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { routeInterceptor } from './route'
|
||||||
|
export { requestInterceptor } from './request'
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import qs from 'qs'
|
||||||
|
import {useUserStore} from '@/store'
|
||||||
|
import {platform} from '@/utils/platform'
|
||||||
|
|
||||||
|
export type CustomRequestOptions = UniApp.RequestOptions & {
|
||||||
|
query?: Record<string, any>
|
||||||
|
/** 出错时是否隐藏错误提示 */
|
||||||
|
hideErrorToast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求基准地址
|
||||||
|
const baseUrl = import.meta.env.VITE_SERVER_BASEURL
|
||||||
|
const proxyPrefix = import.meta.env.VITE_APP_PROXY_PREFIX
|
||||||
|
|
||||||
|
|
||||||
|
// 拦截器配置
|
||||||
|
const httpInterceptor = {
|
||||||
|
// 拦截前触发
|
||||||
|
invoke(options: CustomRequestOptions) {
|
||||||
|
// 接口请求支持通过 query 参数配置 queryString
|
||||||
|
if (options.query) {
|
||||||
|
const queryStr = qs.stringify(options.query)
|
||||||
|
if (options.url.includes('?')) {
|
||||||
|
options.url += `&${queryStr}`
|
||||||
|
} else {
|
||||||
|
options.url += `?${queryStr}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 非 http 开头需拼接地址
|
||||||
|
if (!options.url.startsWith('http')) {
|
||||||
|
// #ifdef H5
|
||||||
|
// console.log(__VITE_APP_PROXY__)
|
||||||
|
if (JSON.parse(__VITE_APP_PROXY__)) {
|
||||||
|
// 啥都不需要做
|
||||||
|
options.url = proxyPrefix + options.url
|
||||||
|
} else {
|
||||||
|
options.url = baseUrl + options.url
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// 非H5正常拼接
|
||||||
|
// #ifndef H5
|
||||||
|
options.url = baseUrl + options.url
|
||||||
|
// #endif
|
||||||
|
// TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址
|
||||||
|
}
|
||||||
|
// 1. 请求超时
|
||||||
|
options.timeout = 10000 // 10s
|
||||||
|
|
||||||
|
// 谷歌地图的请求不额外增加请求头
|
||||||
|
if (!options.url.startsWith('https://maps.googleapis.com')) {
|
||||||
|
// 2. (可选)添加小程序端请求头标识
|
||||||
|
const localeLanguages = uni.getLocale()
|
||||||
|
console.log(localeLanguages)
|
||||||
|
options.header = {
|
||||||
|
platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源
|
||||||
|
'content-language': localeLanguages === 'zh-Hans' ? 'zh_CN' : 'en_US',
|
||||||
|
...options.header,
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(options)
|
||||||
|
// 3. 添加 token 请求头标识
|
||||||
|
const userStore = useUserStore()
|
||||||
|
if (userStore.token) {
|
||||||
|
// options.header.Authorization = `Bearer ${userStore.token}`
|
||||||
|
options.header.token = userStore.token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestInterceptor = {
|
||||||
|
install() {
|
||||||
|
// 拦截 request 请求
|
||||||
|
uni.addInterceptor('request', httpInterceptor)
|
||||||
|
// 拦截 uploadFile 文件上传
|
||||||
|
uni.addInterceptor('uploadFile', httpInterceptor)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import {useUserStore} from '@/store'
|
||||||
|
import Config from '@/config'
|
||||||
|
|
||||||
|
|
||||||
|
let loginPathPattern = ['/pages-user', '/pages/order', '/pages/invite', '/pages/scan-code']
|
||||||
|
let tabbarPaths = ['pages/index/index']
|
||||||
|
let prevPath = ''
|
||||||
|
|
||||||
|
let isDev = import.meta.env.MODE !== 'development'
|
||||||
|
|
||||||
|
// 黑登录拦截器 - (适用于大部分页面不需要登录,少部分页面需要登录)
|
||||||
|
const navigateToInterceptor = {
|
||||||
|
// 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
|
||||||
|
invoke(result: any) {
|
||||||
|
console.log('navigateToInterceptor', result)
|
||||||
|
|
||||||
|
// if (url === prevPath) {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// prevPath = url
|
||||||
|
// setTimeout(() => {
|
||||||
|
// prevPath = ''
|
||||||
|
// }, 500)
|
||||||
|
//
|
||||||
|
// const path = url.split('?')[0]
|
||||||
|
// console.log('path', path)
|
||||||
|
//
|
||||||
|
// const userStore = useUserStore()
|
||||||
|
//
|
||||||
|
// console.log(isDev)
|
||||||
|
// if (loginPathPattern.some((item) => path.includes(item))) {
|
||||||
|
// return userStore.checkLogin()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigateBackInterceptor = {
|
||||||
|
invoke(result: any) {
|
||||||
|
console.log('navigateBackInterceptor', result)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const routeInterceptor = {
|
||||||
|
install() {
|
||||||
|
uni.addInterceptor('navigateTo', navigateToInterceptor)
|
||||||
|
uni.addInterceptor('redirectTo', navigateToInterceptor)
|
||||||
|
uni.addInterceptor('navigateBack', navigateBackInterceptor)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<wd-config-provider class="h-full" :themeVars="themeVars">
|
||||||
|
<view class="" v-if="!isConnected&&configStore.showNetworkAnomaly">
|
||||||
|
<navbar :fixed="false" :title="t('navbar-network-anomaly')"/>
|
||||||
|
</view>
|
||||||
|
<slot v-else/>
|
||||||
|
</wd-config-provider>
|
||||||
|
<wd-toast/>
|
||||||
|
<wd-message-box/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type {ConfigProviderThemeVars} from 'wot-design-uni'
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const isConnected = ref(true)
|
||||||
|
|
||||||
|
const themeVars: ConfigProviderThemeVars = {
|
||||||
|
colorTheme: '#333',
|
||||||
|
buttonPrimaryBgColor: '#14181B',
|
||||||
|
}
|
||||||
|
|
||||||
|
const OnNetworkStatusChange = (res: any) => {
|
||||||
|
console.log('=>(useNetworkStatusChange)', res)
|
||||||
|
isConnected.value = !!res.isConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
uni.onNetworkStatusChange(OnNetworkStatusChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
uni.getNetworkType({
|
||||||
|
success: function (res) {
|
||||||
|
console.log('networkType', res.networkType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
if (configStore.showNetworkAnomaly) {
|
||||||
|
configStore.showNetworkAnomaly = false
|
||||||
|
}
|
||||||
|
uni.offNetworkStatusChange(OnNetworkStatusChange)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
page {
|
||||||
|
font-family: 'UberMove', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poynter-oldstyle-text {
|
||||||
|
font-family: 'UberMove', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.helvetica-now-text {
|
||||||
|
font-family: 'UberMove', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,706 @@
|
|||||||
|
{
|
||||||
|
"agreement": {
|
||||||
|
"privacy-policy": "Privacy Policy",
|
||||||
|
"user-terms-conditions": "User Terms and Conditions"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"addPicture": "Add Picture",
|
||||||
|
"app-marketplace": "Application Market",
|
||||||
|
"appleLoginFailed": "Apple sign-in failed. Please try again.",
|
||||||
|
"stock": "Stock",
|
||||||
|
"apply": "Apply",
|
||||||
|
"balance": "Balance",
|
||||||
|
"buildingType": "Building Type",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"close": "Close",
|
||||||
|
"comment": "Comment",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"continue": "Continue",
|
||||||
|
"delete": "Delete",
|
||||||
|
"distance": "Distance",
|
||||||
|
"email": "Email",
|
||||||
|
"empty-view-text": "No content yet",
|
||||||
|
"enter": "Enter",
|
||||||
|
"enterPassword": "Enter Password",
|
||||||
|
"evaluate": "Evaluate",
|
||||||
|
"expireTime": "Expire Time",
|
||||||
|
"failure": "Build failed",
|
||||||
|
"go-to-settings": "Go to Settings",
|
||||||
|
"goPay": "Go Pay",
|
||||||
|
"goSettle": "Go Settle",
|
||||||
|
"google-map": "Google Maps",
|
||||||
|
"googleLoginFailed": "Google sign-in failed. Please try again.",
|
||||||
|
"gotIt": "Got it",
|
||||||
|
"loading": "Loading",
|
||||||
|
"mile": "mile",
|
||||||
|
"minutes": "minutes",
|
||||||
|
"no": "No",
|
||||||
|
"obtain": "get",
|
||||||
|
"operation-success": "Operation successful",
|
||||||
|
"or": "OR",
|
||||||
|
"photograph": "photo",
|
||||||
|
"copyLink": "Copy Link",
|
||||||
|
"placeholder": {
|
||||||
|
"pleaseEnter": "Please enter",
|
||||||
|
"pleaseSelect": "Please select"
|
||||||
|
},
|
||||||
|
"please-enter-your-payment-password": "Please enter your payment password",
|
||||||
|
"pleaseEnterDeliveryReview": "Please enter delivery review content",
|
||||||
|
"pleaseEnterDishReview": "Please enter dish review content",
|
||||||
|
"pleaseLogin": "Please login",
|
||||||
|
"pleaseRateDelivery": "Please rate the delivery driver",
|
||||||
|
"pleaseRateDish": "Please rate the dish",
|
||||||
|
"point": "point",
|
||||||
|
"prompt": {
|
||||||
|
"authentication-failed-please-log-in": "Authentication failed. Please log in again",
|
||||||
|
"download-failed": "Download failed",
|
||||||
|
"getLocationFailed": "Failed to obtain location, please try again later",
|
||||||
|
"installed-specified-app": "Please select Map App",
|
||||||
|
"login-expired-please-log-in-again": "Your login has expired. Please log in again",
|
||||||
|
"not-setup-payment-password": "Have you not set a payment password yet?",
|
||||||
|
"phone-number-empty": "Number does not exist",
|
||||||
|
"picture-wrong-please-try-again": "Please select a file and try again",
|
||||||
|
"please-authorize-location-information": "Please authorize location information",
|
||||||
|
"please-carefully-read-and-agree": "Please carefully read and agree",
|
||||||
|
"please-enter-keyword-search": "Please enter a keyword to search",
|
||||||
|
"request-failed-please-try-again-later": "Network or server error please wait.",
|
||||||
|
"request-incorrect": "Request failed",
|
||||||
|
"save-failed": "Save failed",
|
||||||
|
"save-successfully": "Save successfully",
|
||||||
|
"system-prompt": "System prompts",
|
||||||
|
"system-prompt-delete": "Are you sure to delete it?",
|
||||||
|
"up-cross": "Loading...",
|
||||||
|
"up-failed": "Upload timed out, please try again later",
|
||||||
|
"update-failed": "Update failed",
|
||||||
|
"update-successfully": "Update successful",
|
||||||
|
"replication-successful": "Copy successful",
|
||||||
|
"claimCouponSuccessfully": "Coupon claimed successfully",
|
||||||
|
"stockInsufficient": "Stock insufficient"
|
||||||
|
},
|
||||||
|
"reEdit": "Re edit",
|
||||||
|
"recharge": "Recharge",
|
||||||
|
"remove": "Remove",
|
||||||
|
"reply": "Reply",
|
||||||
|
"reset": "Reset",
|
||||||
|
"sales": "Total sales",
|
||||||
|
"save": "Save",
|
||||||
|
"saveAndContinue": "Save and continue",
|
||||||
|
"search": "Search",
|
||||||
|
"select": "Select",
|
||||||
|
"select-from-album": "Select from mobile phone album",
|
||||||
|
"select-maps-app": "Select a map application",
|
||||||
|
"send": "Send",
|
||||||
|
"skip": "Skip",
|
||||||
|
"state": "State",
|
||||||
|
"submit": "Submit",
|
||||||
|
"unknownUser": "Unknown User",
|
||||||
|
"useCurrentLocation": "Use current location",
|
||||||
|
"useMyCurrentLocation": "use my current location",
|
||||||
|
"verification-code-sent-successfully": "Verification code sent successfully",
|
||||||
|
"yes": "Yes"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"filtrate-tool": {
|
||||||
|
"discount": "Discount",
|
||||||
|
"over": "Over",
|
||||||
|
"pickup": "Pickup",
|
||||||
|
"price": "Price",
|
||||||
|
"score": "Score"
|
||||||
|
},
|
||||||
|
"noOpen": {
|
||||||
|
"title": "The cold chain function has not been opened yet, waiting to be opened later."
|
||||||
|
},
|
||||||
|
"placeholder": "Please enter",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search CHEFLINK"
|
||||||
|
},
|
||||||
|
"searchSort": {
|
||||||
|
"comment": "Comment",
|
||||||
|
"thumbsUp": "Collection",
|
||||||
|
"time": "Time",
|
||||||
|
"title": "Sort",
|
||||||
|
"view": "Page view ranking"
|
||||||
|
},
|
||||||
|
"visit": {
|
||||||
|
"leaveItToMePersonally": "Leave it to me personally",
|
||||||
|
"putItAtTheDoor": "Put it at the door"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navbar-change-password": "Change password",
|
||||||
|
"navbar-change-payment-password": "Modify payment password",
|
||||||
|
"navbar-customer-service": "Customer Service",
|
||||||
|
"navbar-forget-password": "Forgot password",
|
||||||
|
"navbar-forget-payment-password": "Forgot your payment password",
|
||||||
|
"navbar-invited-person": "My invitation",
|
||||||
|
"navbar-nickname": "Name",
|
||||||
|
"navbar-personal-information": "personal information",
|
||||||
|
"navbar-set-payment-password": "Set a payment password",
|
||||||
|
"navbar-settings": "Settings",
|
||||||
|
"orderStatus": {
|
||||||
|
"delivered": "Delivered",
|
||||||
|
"delivering": "Delivering",
|
||||||
|
"ordered": "Ordered",
|
||||||
|
"paid": "Paid",
|
||||||
|
"received": "Received"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"address": {
|
||||||
|
"apartment": {
|
||||||
|
"titleApartment": "Apartment/Unit/Floor (required)",
|
||||||
|
"titleApartmentTips": "e.g.1208",
|
||||||
|
"titleBuilding": "Building Name",
|
||||||
|
"titleBuildingTips": "e.g.Central Building",
|
||||||
|
"titleEntry": "Entry Code",
|
||||||
|
"titleEntryTips": "e.g.10150#"
|
||||||
|
},
|
||||||
|
"appTime": "Appointment delivery",
|
||||||
|
"appointmentTime": "Appointment time",
|
||||||
|
"choose-type": {
|
||||||
|
"apartment": "Apartment",
|
||||||
|
"apartmentDescription": "Multi-unit residential building",
|
||||||
|
"description": "Please inform us of your building type so that we can improve delivery accuracy.",
|
||||||
|
"hotel": "Hotel",
|
||||||
|
"hotelDescription": "Temporary residence, motel or resort",
|
||||||
|
"house": "House",
|
||||||
|
"houseDescription": "Single or multi- family home",
|
||||||
|
"navTitle": "Building Type",
|
||||||
|
"office": "Office",
|
||||||
|
"officeDescription": "Workplaces with entry restrictions",
|
||||||
|
"other": "Other",
|
||||||
|
"otherDescription": "Hospitals, parks, outdoors, etc",
|
||||||
|
"title": "Select building type"
|
||||||
|
},
|
||||||
|
"deliveryInstructions": "Delivery Instructions",
|
||||||
|
"deliveryPointInfo": "Delivery Point Information",
|
||||||
|
"hotel": {
|
||||||
|
"titleHotel": "Hotel Name (required)",
|
||||||
|
"titleHotelTips": "e.g.orange hotel",
|
||||||
|
"titleRoom": "Room/Floor",
|
||||||
|
"titleRoomTips": "e.g.1808"
|
||||||
|
},
|
||||||
|
"house": {
|
||||||
|
"tips": "e.g.house number or name"
|
||||||
|
},
|
||||||
|
"immediateDelivery": "Immediate delivery",
|
||||||
|
"moreOptions": "More options",
|
||||||
|
"office": {
|
||||||
|
"titleCorporate": "Corporate Name (required)",
|
||||||
|
"titleCorporateTips": "e.g.XXX Technology Co., Ltd",
|
||||||
|
"titleSuite": "Suite/Floor",
|
||||||
|
"titleSuiteTips": "e.g.18th floor"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"titleApartment": "Apartment/Suite/Floor",
|
||||||
|
"titleApartmentTips": "e.g.orange hotel",
|
||||||
|
"titleCompany": "Company/Building Name",
|
||||||
|
"titleCompanyTips": "e.g.1808"
|
||||||
|
},
|
||||||
|
"otherDetails": "Other Details",
|
||||||
|
"pleaseTip": "e.g.don't knock on the door, don't ring the doorbell",
|
||||||
|
"reservation": "Reservation",
|
||||||
|
"reservationTime": {
|
||||||
|
"currentTimeExpired": "Current time has passed, automatically selected next business day",
|
||||||
|
"dateNotSelectable": "This date is not selectable",
|
||||||
|
"noAvailableTime": "No available time for current date, automatically selected next business day",
|
||||||
|
"notAvailable": "Not available",
|
||||||
|
"reservationSuccess": "Reservation successful",
|
||||||
|
"selectTimeSlot": "Please select a time slot"
|
||||||
|
},
|
||||||
|
"savedAddresses": "Saved addresses",
|
||||||
|
"title": "Address",
|
||||||
|
"titleDetail": "Address Details"
|
||||||
|
},
|
||||||
|
"browse": {
|
||||||
|
"titleCuisine": "Nearby Cuisine",
|
||||||
|
"titleRecipes": "Selected Recipes"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"all-merchants": "All merchants",
|
||||||
|
"default-location": "Select the address",
|
||||||
|
"deliveryFee": "Shipping Fee",
|
||||||
|
"featured-on": "Featured on CHEFLINK",
|
||||||
|
"featured-dishes": "Featured Dishes",
|
||||||
|
"nearby-merchants": "Nearby Merchants"
|
||||||
|
},
|
||||||
|
"mine": {
|
||||||
|
"activity-description": "Activity description",
|
||||||
|
"activity-description-tip": "Share the QR code and invite new users to register successfully. Successful invitation",
|
||||||
|
"collection": "Collection",
|
||||||
|
"complaintsAndSuggestions": "Complaints and suggestions",
|
||||||
|
"customer-service-phone": "Customer service phone",
|
||||||
|
"dial": "Dial",
|
||||||
|
"discount": "Discount",
|
||||||
|
"help": "Help",
|
||||||
|
"invitation-code": "Invitation code",
|
||||||
|
"invite-friends": "Invite friends",
|
||||||
|
"inviteFriends": "Invite Friends",
|
||||||
|
"join": "Join",
|
||||||
|
"log-out-successfully": "Log out successfully",
|
||||||
|
"login-out-tip": "Are you sure you want to log out of your current login account?",
|
||||||
|
"member-desc": "Unlock members to enjoy more discounts and promotions.",
|
||||||
|
"member-title": "Free trial of CHEFLINK",
|
||||||
|
"order": "Order",
|
||||||
|
"platformAgreement": "Platform Agreement",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"save-picture": "Save Image",
|
||||||
|
"set": "Set",
|
||||||
|
"storeSettled": "Store Settled",
|
||||||
|
"support": "Support",
|
||||||
|
"the-person-invited": "My invitation",
|
||||||
|
"wallet": "Wallet",
|
||||||
|
"work-time": "Work time",
|
||||||
|
"my-invitations": "My Invitations"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"DEL": "DEL",
|
||||||
|
"PU": "PU",
|
||||||
|
"accept": "ACPT",
|
||||||
|
"all": "ALL",
|
||||||
|
"completed": "DCOMP",
|
||||||
|
"confirmReady": "CONF/READY",
|
||||||
|
"onTheWay": "OTW",
|
||||||
|
"title": "History"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"hot-title": "Popular Categories",
|
||||||
|
"recently": "Recently",
|
||||||
|
"result": {
|
||||||
|
"food": "Food",
|
||||||
|
"recipe": "Recipe",
|
||||||
|
"result": "Result"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shop": {
|
||||||
|
"description": "This feature is not yet available, please wait for it to be released later.",
|
||||||
|
"tips": "Tips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-login": {
|
||||||
|
"and": "and",
|
||||||
|
"choose-language": {
|
||||||
|
"confirm": "Confirm language",
|
||||||
|
"success": "Language set successfully",
|
||||||
|
"tip": "You can change this setting later in the 'My' section of the app",
|
||||||
|
"title": "Hello, welcome to log in"
|
||||||
|
},
|
||||||
|
"continuing-agree": "I have read it carefully and agreed",
|
||||||
|
"forget-password": {
|
||||||
|
"description": "Set a new password for Sign In",
|
||||||
|
"newPassword": "New password"
|
||||||
|
},
|
||||||
|
"guide-page": {
|
||||||
|
"location": {
|
||||||
|
"allowLocationAccess": "Allow Location Access",
|
||||||
|
"description": "We need to know your location in order to suggest nearby services.",
|
||||||
|
"selectLocation": "Select location",
|
||||||
|
"title": "What is Your Location?"
|
||||||
|
},
|
||||||
|
"notice": {
|
||||||
|
"activateNotification": "Activate notification",
|
||||||
|
"continueLater": "Continue later",
|
||||||
|
"description": "We'll update you on order statuses, limited-time deals, local store events and more.",
|
||||||
|
"title": "Stay in the know"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"description": "Account created successfully.",
|
||||||
|
"next": "Next",
|
||||||
|
"title": "Hello"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"apple-login": "Continue with Apple",
|
||||||
|
"description": "Create an account or log in to book and manage your appointments",
|
||||||
|
"facebook-login": "Continue with Facebook",
|
||||||
|
"google-login": "Continue with Google",
|
||||||
|
"input-placeholder": "Email Address/Phone",
|
||||||
|
"prompt": {
|
||||||
|
"confirm-email-verify": "The email address was inconsistent twice",
|
||||||
|
"email-address-verify": "Please enter the correct email address",
|
||||||
|
"first-name": "Please enter a name",
|
||||||
|
"last-name": "Please enter your last name",
|
||||||
|
"password": "Please enter your password",
|
||||||
|
"phone-number": "Please enter your phone number",
|
||||||
|
"phone-number-verify": "Please enter the correct mobile phone number"
|
||||||
|
},
|
||||||
|
"title": "Log in or sign up",
|
||||||
|
"wechat-login": "Continue with Wechat"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"description": "Enter password to Sign In",
|
||||||
|
"forgotPassword": "Forgot password",
|
||||||
|
"title": "Sign In"
|
||||||
|
},
|
||||||
|
"login-successfully": "Login successfully",
|
||||||
|
"prompt": {
|
||||||
|
"first-name": "Please enter last name",
|
||||||
|
"last-name": "Please enter first name"
|
||||||
|
},
|
||||||
|
"sign-up": {
|
||||||
|
"confirm-email": "Confirm email",
|
||||||
|
"first-name": "First name",
|
||||||
|
"last-name": "Last name",
|
||||||
|
"password": "Password",
|
||||||
|
"phone-number": "Phone number",
|
||||||
|
"register-success": "Registration successful",
|
||||||
|
"title": "Sign Up"
|
||||||
|
},
|
||||||
|
"verify-code": {
|
||||||
|
"changeOne": "Change one",
|
||||||
|
"description": "For security reasons, please type characters to continue",
|
||||||
|
"title": "verification code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-store": {
|
||||||
|
"checkout": {
|
||||||
|
"addAddress": "Add address",
|
||||||
|
"addCoupon": "Add coupon",
|
||||||
|
"appointmentDelivery": "Appointment delivery",
|
||||||
|
"appointmentPickup": "Appointment pickup",
|
||||||
|
"chooseTime": "Choose the time",
|
||||||
|
"chooseTips": "Choose tips",
|
||||||
|
"contactPhone": "Contact phone",
|
||||||
|
"deliveryPreference": "Delivery preference",
|
||||||
|
"deliveryTime": "Delivery time",
|
||||||
|
"distance": "Distance",
|
||||||
|
"enableLocationForDistance": "Please enable location to get distance",
|
||||||
|
"immediateDelivery": "Immediate delivery",
|
||||||
|
"immediatePickup": "Immediate pickup",
|
||||||
|
"no": "No",
|
||||||
|
"notActivated": "Not activated",
|
||||||
|
"orderInfoSummary": "Order Information Summary",
|
||||||
|
"other": "Other",
|
||||||
|
"paymentSuccess": "Payment successful",
|
||||||
|
"pickupTime": "Pickup time",
|
||||||
|
"pleaseSelectAddress": "Please select address",
|
||||||
|
"pleaseSelectCreditCard": "Please select credit card",
|
||||||
|
"priceDetail": {
|
||||||
|
"desc": "These fees are only due to factors such as the number of shopping carts, and are used to pay for expenses related to your order, including platform services and delivery services.",
|
||||||
|
"memberDiscount": "Member discount",
|
||||||
|
"serviceFees": "Service fees and other expenses",
|
||||||
|
"taxation": "Taxation",
|
||||||
|
"title": "What content is included"
|
||||||
|
},
|
||||||
|
"tips": "Is weekly delivery available",
|
||||||
|
"tipsDesc": "Free delivery fee will be given for weekly delivery fees over 30, and delivery fees will be charged for orders below 30",
|
||||||
|
"title": "Checkout",
|
||||||
|
"yes": "Yes"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"acceptanceTime": "Order acceptance time",
|
||||||
|
"autoCancellation": "Automatic cancellation after 30 minutes",
|
||||||
|
"cancellationReason": "Cancellation reason",
|
||||||
|
"cancellationReasonDesc": "30 minutes without processing automatically agreed by the system.",
|
||||||
|
"cancellationTime": "Cancellation time",
|
||||||
|
"cancellationTitle": "Merchant processing in progress",
|
||||||
|
"cancelled": "Cancelled",
|
||||||
|
"deliveryAddress": "Delivery address",
|
||||||
|
"deliveryPhotos": "Delivery of photos",
|
||||||
|
"deliveryTime": "Delivery time",
|
||||||
|
"estimatedDeliveryTime": "Estimated delivery time",
|
||||||
|
"orderInfo": "Ordering Information",
|
||||||
|
"orderNumber": "Order number",
|
||||||
|
"orderStatus": {
|
||||||
|
"agree": "The merchant has agreed to a refund",
|
||||||
|
"agreeRefund": "Refund agreed",
|
||||||
|
"cancelled": "Cancelled",
|
||||||
|
"completed": "Completed",
|
||||||
|
"delivered": "Delivered",
|
||||||
|
"delivering": "Delivering",
|
||||||
|
"hasPendingPayment": "Waiting for orders",
|
||||||
|
"merchantRejected": "Merchant Rejected",
|
||||||
|
"merchantRejectedDesc": "Merchant has rejected the order",
|
||||||
|
"ordered": "Ordered",
|
||||||
|
"paid": "Paid",
|
||||||
|
"pending": "Waiting for the merchant to process",
|
||||||
|
"pendingPayment": "Pending payment",
|
||||||
|
"ready": "Ready",
|
||||||
|
"received": "Received",
|
||||||
|
"refund": "Refund in progress",
|
||||||
|
"rejectRefund": "Refund Rejected"
|
||||||
|
},
|
||||||
|
"orderTime": "Order time",
|
||||||
|
"rejectReason": "The reason why the merchant refused",
|
||||||
|
"subtotal": "Subtotal",
|
||||||
|
"taxesAndOtherFees": "Taxes and other fees",
|
||||||
|
"total": "Total",
|
||||||
|
"upTime": "Estimated self-pickup time",
|
||||||
|
"useCode": "Use code",
|
||||||
|
"writeOff": "Meal Code"
|
||||||
|
},
|
||||||
|
"store": {
|
||||||
|
"addToCart": "Add to cart",
|
||||||
|
"appetizers": "Appetizers",
|
||||||
|
"merchantDiscounts": "Merchant discounts",
|
||||||
|
"claimNow": "Claim now",
|
||||||
|
"claimed": "Claimed",
|
||||||
|
"claimCoupon": "Claim coupon",
|
||||||
|
"couponOff": "Off",
|
||||||
|
"validDays": "Valid Days",
|
||||||
|
"days": "days",
|
||||||
|
"get": "Get",
|
||||||
|
"congratulations": "Congratulations",
|
||||||
|
"tips-1": "Order With Coupons",
|
||||||
|
"tips-2": "for Better Discounts",
|
||||||
|
"tips-3": "Tap Get",
|
||||||
|
"areaCode": {
|
||||||
|
"singapore": "Singapore"
|
||||||
|
},
|
||||||
|
"cancelOrder": {
|
||||||
|
"dontWant": "I dont want it anymore",
|
||||||
|
"forgotCoupon": "Forgot to use coupon",
|
||||||
|
"informationError": "Information filling error",
|
||||||
|
"wrongLess": "Wrong/Less"
|
||||||
|
},
|
||||||
|
"choose": "Please choose",
|
||||||
|
"delivery": "Delivery",
|
||||||
|
"deliveryTags": {
|
||||||
|
"carefulPackaging": "Careful packaging",
|
||||||
|
"fastDelivery": "Fast delivery",
|
||||||
|
"goodAttitude": "Good attitude",
|
||||||
|
"onTime": "On time",
|
||||||
|
"polite": "Polite",
|
||||||
|
"professional": "Professional"
|
||||||
|
},
|
||||||
|
"discount": "discount",
|
||||||
|
"earTime": "Earliest delivery time",
|
||||||
|
"members": "Member",
|
||||||
|
"miles": "miles",
|
||||||
|
"orderStatus": {
|
||||||
|
"delivered": "Delivered",
|
||||||
|
"delivering": "Delivering",
|
||||||
|
"ordered": "Ordered",
|
||||||
|
"paid": "Paid",
|
||||||
|
"received": "Received",
|
||||||
|
"rejected": "Rejected"
|
||||||
|
},
|
||||||
|
"pickup": "Pickup",
|
||||||
|
"pickupAddress": "Meal pickup address",
|
||||||
|
"pickupTime": "Meal pickup time",
|
||||||
|
"recommend": "Recommended for you",
|
||||||
|
"required": "Required",
|
||||||
|
"sales": "Sales",
|
||||||
|
"securityCode": {
|
||||||
|
"cantSee": "Can't see clearly? Change one",
|
||||||
|
"confirm": "Confirm"
|
||||||
|
},
|
||||||
|
"shareFunctionality": "Share functionality",
|
||||||
|
"tips": "We will be closed in",
|
||||||
|
"tips1": "minutes. Order now or consider other options.",
|
||||||
|
"tips2": "people have reordered",
|
||||||
|
"tips3": "membership to enjoy a ",
|
||||||
|
"tips4": "Enjoy delivery service over",
|
||||||
|
"tips5": "Delivery fee",
|
||||||
|
"title": "The minimum order amount for this store is",
|
||||||
|
"toast": {
|
||||||
|
"deliveryService": "This merchant does not provide delivery service",
|
||||||
|
"selfPickup": "This merchant does not provide pickup service"
|
||||||
|
},
|
||||||
|
"today": "Today",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"use": "Use",
|
||||||
|
"weekdays": {
|
||||||
|
"friday": "FRIDAY",
|
||||||
|
"monday": "MONDAY",
|
||||||
|
"saturday": "SATURDAY",
|
||||||
|
"sunday": "SUNDAY",
|
||||||
|
"thursday": "THURSDAY",
|
||||||
|
"tuesday": "TUESDAY",
|
||||||
|
"wednesday": "WEDNESDAY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"view-reviews": {
|
||||||
|
"Score": "score",
|
||||||
|
"deliveryDriver": "Delivery Driver",
|
||||||
|
"goods": "Goods",
|
||||||
|
"viewTitle": "View reviews"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-user": {
|
||||||
|
"balance": {
|
||||||
|
"all": "All",
|
||||||
|
"detail-list": "Detail list",
|
||||||
|
"month": "Month",
|
||||||
|
"month-filter": "Month filter",
|
||||||
|
"my-balance": "My balance",
|
||||||
|
"title": "Wallet",
|
||||||
|
"type": "Type",
|
||||||
|
"year": "Year",
|
||||||
|
"year-month-select": "Year month select"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"add": "+ Add",
|
||||||
|
"desc": "After binding the card, payment can be made quickly and directly.",
|
||||||
|
"listCard": "Credit card list",
|
||||||
|
"title": "Bind the card first and make the payment later"
|
||||||
|
},
|
||||||
|
"cart": {
|
||||||
|
"addItems": "Add Items",
|
||||||
|
"addNote": "Add a note",
|
||||||
|
"items": "Items",
|
||||||
|
"requestTableware": "Request tableware, etc",
|
||||||
|
"title": "Cart",
|
||||||
|
"totalPrice": "Total price",
|
||||||
|
"viewCart": "View Shopping Cart",
|
||||||
|
"viewStore": "View Store"
|
||||||
|
},
|
||||||
|
"choosePaymethod": {
|
||||||
|
"creditCard": "Credit card payment",
|
||||||
|
"replace": "Replace",
|
||||||
|
"title": "Choose payment method",
|
||||||
|
"wallet": "Wallet payment"
|
||||||
|
},
|
||||||
|
"complaints": {
|
||||||
|
"contact-information": "Contact information",
|
||||||
|
"contact-information-tip": "Your contact information helps us communicate and solve problems, only visible to staff",
|
||||||
|
"description": "You are welcome to give us feedback on the use of our products and suggestions.",
|
||||||
|
"feedback-content": "Feedback content",
|
||||||
|
"feedback-content-placeholder": "Please fill in the feedback content",
|
||||||
|
"image": "Image",
|
||||||
|
"title": "Complaints and suggestions",
|
||||||
|
"validation": {
|
||||||
|
"contact-phone-invalid": "Please fill in the correct contact phone",
|
||||||
|
"contact-phone-required": "Please fill in the contact phone",
|
||||||
|
"content-max-length": "Feedback content cannot exceed 500 characters",
|
||||||
|
"content-min-length": "Feedback content must be at least 10 characters",
|
||||||
|
"content-required": "Please fill in the feedback content"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coupon": {
|
||||||
|
"all-merchants": "Applicable to all merchants",
|
||||||
|
"expiry-date": "Expiry date: ",
|
||||||
|
"merchant-specific": "For specific merchant use",
|
||||||
|
"no-coupons": "You currently do not have any coupons",
|
||||||
|
"redeem-now": "Redeem now",
|
||||||
|
"search-placeholder": "Enter discount code",
|
||||||
|
"title": "Coupons"
|
||||||
|
},
|
||||||
|
"member": {
|
||||||
|
"annually": "Annually",
|
||||||
|
"autoSubscribe": "Auto subscribe",
|
||||||
|
"btn-text": "Subscribe to",
|
||||||
|
"creditCard": "credit card",
|
||||||
|
"desc": "It can save an additional $23.88 per year.",
|
||||||
|
"free": "free",
|
||||||
|
"free-trial": "Free trial",
|
||||||
|
"freeTrial": "Free trial weeks",
|
||||||
|
"join": "Join",
|
||||||
|
"month": "month",
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"payTime": "Time of payment",
|
||||||
|
"paymentMethod": "Payment method",
|
||||||
|
"renewalNow": "Renewal now",
|
||||||
|
"weeks": "weeks",
|
||||||
|
"billedAnnually": "Billed at ${amount}/year",
|
||||||
|
"savingsPerYear": "It can save an additional ${amount} per year."
|
||||||
|
},
|
||||||
|
"member-center": {
|
||||||
|
"chooseLike": "Choose 3 items you like",
|
||||||
|
"likeDescription": "This information is used to provide you with a personalized service experience",
|
||||||
|
"notCurrentlyTrying": "Not currently trying CHEFLINK",
|
||||||
|
"subscribe": "Subscribe to CHEFLINK",
|
||||||
|
"temporarilySkip": "Temporarily skip",
|
||||||
|
"title": "Member Center",
|
||||||
|
"youWillMissOut": "You will miss out on discount offers for eligible meals and fresh meat orders."
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"readAll": "One-click read",
|
||||||
|
"title": "messages"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"change-password-successfully": "Change password successfully",
|
||||||
|
"forget-password-successfully": "Forget password successfully"
|
||||||
|
},
|
||||||
|
"pay-password": {
|
||||||
|
"change-payment-password-successfully": "Modify payment password successfully",
|
||||||
|
"enter-6-digit-password": "Please enter a 6-digit password",
|
||||||
|
"forget-payment-password-successfully": "Forgot your payment password successfully",
|
||||||
|
"input-placeholder": {
|
||||||
|
"enter-new-password": "Please enter a new password",
|
||||||
|
"enter-new-password-again": "Please enter the new password again",
|
||||||
|
"enter-old-password": "Please enter the original password",
|
||||||
|
"enter-phone-number": "Please enter your mobile phone number",
|
||||||
|
"enter-verification-code": "Please enter the verification code"
|
||||||
|
},
|
||||||
|
"set-payment-password-successfully": "Setting the payment password successfully",
|
||||||
|
"two-passwords-inconsistent": "The password is inconsistent when entered twice"
|
||||||
|
},
|
||||||
|
"recharge": {
|
||||||
|
"amount": "Recharge amount",
|
||||||
|
"amount-invalid": "The recharge amount cannot be less than 0",
|
||||||
|
"description": "Please enter your recharge amount",
|
||||||
|
"pay-method": "Please select a payment method",
|
||||||
|
"success": "Recharge successful",
|
||||||
|
"title": "Recharge Balance"
|
||||||
|
},
|
||||||
|
"recipe": {
|
||||||
|
"collapseReply": "Collapse reply content",
|
||||||
|
"desc": "If you like it, give it a thumbs up",
|
||||||
|
"expandReply": "Expand reply",
|
||||||
|
"replyTo": "Reply to",
|
||||||
|
"title": "Recipe Details"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"cancelAccount": "Cancel account",
|
||||||
|
"cancelAccountConfirm": "Are you sure you want to cancel your account? All your data will be deleted and cannot be recovered.",
|
||||||
|
"cancelAccountSuccess": "Account cancellation successful",
|
||||||
|
"language": "Language switching",
|
||||||
|
"logout": "Logout",
|
||||||
|
"modification": "Change password",
|
||||||
|
"payPwd": "Payment password"
|
||||||
|
},
|
||||||
|
"store-settle-in": {
|
||||||
|
"address": "Address",
|
||||||
|
"audit": {
|
||||||
|
"pass": "Successful review",
|
||||||
|
"passDesc": "Congratulations on your successful entry into the platform!",
|
||||||
|
"passDesc1": "Come and upload your product information to start your money-making journey",
|
||||||
|
"reject": "Review failed",
|
||||||
|
"rejectDesc": "Reasons for failure",
|
||||||
|
"submitted": "Under review",
|
||||||
|
"submittedDesc": "The platform is under review. Please wait patiently..."
|
||||||
|
},
|
||||||
|
"businessLicense": "Business license",
|
||||||
|
"category": "Type",
|
||||||
|
"desc": "Just fill in the information and start your money-making journey now",
|
||||||
|
"detailedAddress": "Detailed address",
|
||||||
|
"idCardFront": "Picture of ID card",
|
||||||
|
"introduction": "Introduction",
|
||||||
|
"schema": {
|
||||||
|
"address": "Please select the address",
|
||||||
|
"businessLicense": "请上传营业执照",
|
||||||
|
"category": "Please select your type",
|
||||||
|
"description": "Please enter introduction",
|
||||||
|
"detailAddress": "Please enter detailed address",
|
||||||
|
"detailedAddress": "Please enter the detailed address",
|
||||||
|
"idCardBack": "请上传身份证背面",
|
||||||
|
"idCardFront": "请上传身份证正面",
|
||||||
|
"introduction": "Please enter the introduction",
|
||||||
|
"storeName": "Please enter the store name"
|
||||||
|
},
|
||||||
|
"storeName": "Shop name",
|
||||||
|
"title": "Shop entry"
|
||||||
|
},
|
||||||
|
"user-info": {
|
||||||
|
"head-portrait": "Head portrait",
|
||||||
|
"nickname": "Nickname",
|
||||||
|
"view-larger-image": "View larger image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pickupAddress": "address",
|
||||||
|
"tabBar": {
|
||||||
|
"browse": "Browse",
|
||||||
|
"global": "Global+",
|
||||||
|
"home": "Home",
|
||||||
|
"mine": "Profile",
|
||||||
|
"order": "History"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"addCartSuccess": "Add to cart successfully",
|
||||||
|
"commentSuccess": "Comment successfully",
|
||||||
|
"deleteSuccess": "Delete successfully",
|
||||||
|
"likeTips": "You can only select up to 3",
|
||||||
|
"orderNumberCopied": "Copy successfully",
|
||||||
|
"redemptionSuccessful": "Redemption successful",
|
||||||
|
"submitSuccess": "Submit success"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {createI18n} from 'vue-i18n'// v9.x
|
||||||
|
import en from './en.json'
|
||||||
|
import zhHans from './zh-Hans.json'
|
||||||
|
import Config from "@/config";
|
||||||
|
|
||||||
|
const localeLanguage = uni.getLocale() || Config.defaultLanguage
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: localeLanguage,// 获取已设置的语言
|
||||||
|
fallbackLocale: Config.defaultLanguage,
|
||||||
|
messages:{
|
||||||
|
en,
|
||||||
|
'zh-Hans': zhHans,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export {i18n}
|
||||||
@@ -0,0 +1,706 @@
|
|||||||
|
{
|
||||||
|
"agreement": {
|
||||||
|
"privacy-policy": "隐私政策",
|
||||||
|
"user-terms-conditions": "用户须知及使用协议"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"addPicture": "添加图片",
|
||||||
|
"app-marketplace": "应用市场",
|
||||||
|
"appleLoginFailed": "苹果登录授权失败,请重试",
|
||||||
|
"stock": "库存",
|
||||||
|
"apply": "确认",
|
||||||
|
"balance": "余额",
|
||||||
|
"buildingType": "建筑类型",
|
||||||
|
"cancel": "取消",
|
||||||
|
"close": "关闭",
|
||||||
|
"comment": "评论",
|
||||||
|
"confirm": "确认",
|
||||||
|
"continue": "继续",
|
||||||
|
"delete": "删除",
|
||||||
|
"distance": "距您",
|
||||||
|
"email": "邮箱",
|
||||||
|
"empty-view-text": "暂无内容",
|
||||||
|
"enter": "输入",
|
||||||
|
"enterPassword": "输入密码",
|
||||||
|
"evaluate": "评价",
|
||||||
|
"expireTime": "到期时间",
|
||||||
|
"failure": "生成失败",
|
||||||
|
"go-to-settings": "去设置",
|
||||||
|
"goPay": "去支付",
|
||||||
|
"goSettle": "去结算",
|
||||||
|
"google-map": "谷歌地图",
|
||||||
|
"googleLoginFailed": "谷歌登录授权失败,请重试",
|
||||||
|
"gotIt": "明白了",
|
||||||
|
"loading": "加载中",
|
||||||
|
"mile": "英里",
|
||||||
|
"minutes": "分钟",
|
||||||
|
"no": "否",
|
||||||
|
"obtain": "获取",
|
||||||
|
"operation-success": "操作成功",
|
||||||
|
"or": "或",
|
||||||
|
"photograph": "拍照",
|
||||||
|
"copyLink": "复制链接",
|
||||||
|
"placeholder": {
|
||||||
|
"pleaseEnter": "请输入",
|
||||||
|
"pleaseSelect": "请选择"
|
||||||
|
},
|
||||||
|
"please-enter-your-payment-password": "请输入支付密码",
|
||||||
|
"pleaseEnterDeliveryReview": "请输入配送员评价内容",
|
||||||
|
"pleaseEnterDishReview": "请输入菜品评价内容",
|
||||||
|
"pleaseLogin": "请登录",
|
||||||
|
"pleaseRateDelivery": "请为配送员评分",
|
||||||
|
"pleaseRateDish": "请为菜品评分",
|
||||||
|
"point": "分",
|
||||||
|
"prompt": {
|
||||||
|
"authentication-failed-please-log-in": "认证失败,请重新登录",
|
||||||
|
"download-failed": "下载失败",
|
||||||
|
"getLocationFailed": "获取定位失败,请稍后重试",
|
||||||
|
"installed-specified-app": "请选择地图App",
|
||||||
|
"login-expired-please-log-in-again": "登录过期,请重新登录",
|
||||||
|
"not-setup-payment-password": "您尚未设置支付密码?",
|
||||||
|
"phone-number-empty": "号码不存在",
|
||||||
|
"picture-wrong-please-try-again": "请选择一个文件后重试",
|
||||||
|
"please-authorize-location-information": "请授权位置信息",
|
||||||
|
"please-carefully-read-and-agree": "请已仔细阅读并同意",
|
||||||
|
"please-enter-keyword-search": "请输入搜索关键词",
|
||||||
|
"request-failed-please-try-again-later": "网络或服务器错误请稍后",
|
||||||
|
"request-incorrect": "请求失败",
|
||||||
|
"save-failed": "保存失败",
|
||||||
|
"save-successfully": "保存成功",
|
||||||
|
"system-prompt": "系统提示",
|
||||||
|
"system-prompt-delete": "是否确认删除?",
|
||||||
|
"up-cross": "上传中...",
|
||||||
|
"up-failed": "上传超时,请稍后重试",
|
||||||
|
"update-failed": "更新失败",
|
||||||
|
"update-successfully": "更新成功",
|
||||||
|
"replication-successful": "复制成功",
|
||||||
|
"claimCouponSuccessfully": "领取成功",
|
||||||
|
"stockInsufficient": "库存不足"
|
||||||
|
},
|
||||||
|
"reEdit": "重新编辑",
|
||||||
|
"recharge": "充值",
|
||||||
|
"remove": "删除",
|
||||||
|
"reply": "回复",
|
||||||
|
"reset": "重置",
|
||||||
|
"sales": "总销量",
|
||||||
|
"save": "保存",
|
||||||
|
"saveAndContinue": "保存并继续",
|
||||||
|
"search": "搜索",
|
||||||
|
"select": "选择",
|
||||||
|
"select-from-album": "从相册选择",
|
||||||
|
"select-maps-app": "选择地图应用",
|
||||||
|
"send": "发送",
|
||||||
|
"skip": "跳过",
|
||||||
|
"state": "州",
|
||||||
|
"submit": "提交",
|
||||||
|
"unknownUser": "未知用户",
|
||||||
|
"useCurrentLocation": "使用当前位置",
|
||||||
|
"useMyCurrentLocation": "使用我的当前位置",
|
||||||
|
"verification-code-sent-successfully": "验证码发送成功",
|
||||||
|
"yes": "是"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"filtrate-tool": {
|
||||||
|
"discount": "折扣",
|
||||||
|
"over": "最低",
|
||||||
|
"pickup": "自取",
|
||||||
|
"price": "价格",
|
||||||
|
"score": "评分"
|
||||||
|
},
|
||||||
|
"noOpen": {
|
||||||
|
"title": "冷链功能尚未开启,等待稍后开启。"
|
||||||
|
},
|
||||||
|
"placeholder": "请输入",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "搜索 CHEFLINK"
|
||||||
|
},
|
||||||
|
"searchSort": {
|
||||||
|
"comment": "评论排序",
|
||||||
|
"thumbsUp": "收藏量排序",
|
||||||
|
"time": "时间排序",
|
||||||
|
"title": "排序",
|
||||||
|
"view": "浏览量排序"
|
||||||
|
},
|
||||||
|
"visit": {
|
||||||
|
"leaveItToMePersonally": "让我自己来决定",
|
||||||
|
"putItAtTheDoor": "放在门口"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"navbar-change-password": "修改密码",
|
||||||
|
"navbar-change-payment-password": "修改支付密码",
|
||||||
|
"navbar-customer-service": "客服中心",
|
||||||
|
"navbar-forget-password": "忘记密码",
|
||||||
|
"navbar-forget-payment-password": "忘记支付密码",
|
||||||
|
"navbar-invited-person": "我的邀请",
|
||||||
|
"navbar-nickname": "昵称",
|
||||||
|
"navbar-personal-information": "个人信息",
|
||||||
|
"navbar-set-payment-password": "设置支付密码",
|
||||||
|
"navbar-settings": "设置",
|
||||||
|
"orderStatus": {
|
||||||
|
"delivered": "已送达",
|
||||||
|
"delivering": "配送中",
|
||||||
|
"ordered": "已下单",
|
||||||
|
"paid": "已支付",
|
||||||
|
"received": "已接收"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"address": {
|
||||||
|
"apartment": {
|
||||||
|
"titleApartment": "公寓/单元/楼层(必填)",
|
||||||
|
"titleApartmentTips": "请输入",
|
||||||
|
"titleBuilding": "建筑物名称",
|
||||||
|
"titleBuildingTips": "请输入",
|
||||||
|
"titleEntry": "进入代码",
|
||||||
|
"titleEntryTips": "请输入"
|
||||||
|
},
|
||||||
|
"appTime": "预约派送时间",
|
||||||
|
"appointmentTime": "预约时间",
|
||||||
|
"choose-type": {
|
||||||
|
"apartment": "公寓",
|
||||||
|
"apartmentDescription": "多单元住宅",
|
||||||
|
"description": "请告知我们您的建筑类型,以便我们提高配送准确性。",
|
||||||
|
"hotel": "酒店",
|
||||||
|
"hotelDescription": "临时住宅、酒店或度假村",
|
||||||
|
"house": "住宅",
|
||||||
|
"houseDescription": "单户或多户住宅",
|
||||||
|
"navTitle": "建筑类型",
|
||||||
|
"office": "办公室",
|
||||||
|
"officeDescription": "有进入限制的工作场所",
|
||||||
|
"other": "其他",
|
||||||
|
"otherDescription": "医院、公园、户外等",
|
||||||
|
"title": "选择建筑类型"
|
||||||
|
},
|
||||||
|
"deliveryInstructions": "配送说明",
|
||||||
|
"deliveryPointInfo": "交货点信息",
|
||||||
|
"hotel": {
|
||||||
|
"titleHotel": "酒店名称(必填)",
|
||||||
|
"titleHotelTips": "请输入",
|
||||||
|
"titleRoom": "房间/楼层",
|
||||||
|
"titleRoomTips": "请输入"
|
||||||
|
},
|
||||||
|
"house": {
|
||||||
|
"tips": "例如门牌号或名字"
|
||||||
|
},
|
||||||
|
"immediateDelivery": "立即派送",
|
||||||
|
"moreOptions": "更多选项",
|
||||||
|
"office": {
|
||||||
|
"titleCorporate": "公司名称(必填)",
|
||||||
|
"titleCorporateTips": "请输入",
|
||||||
|
"titleSuite": "套房/楼层",
|
||||||
|
"titleSuiteTips": "请输入"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"titleApartment": "公寓/套房/楼层",
|
||||||
|
"titleApartmentTips": "请输入",
|
||||||
|
"titleCompany": "公司/建筑物名称",
|
||||||
|
"titleCompanyTips": "请输入"
|
||||||
|
},
|
||||||
|
"otherDetails": "其他信息",
|
||||||
|
"pleaseTip": "如不要敲门,不要按门铃",
|
||||||
|
"reservation": "预约",
|
||||||
|
"reservationTime": {
|
||||||
|
"currentTimeExpired": "当前时间段已过,已自动选择下一个营业日",
|
||||||
|
"dateNotSelectable": "该日期不可选择",
|
||||||
|
"noAvailableTime": "当前日期无可用时间,已自动选择下一个营业日",
|
||||||
|
"notAvailable": "不营业",
|
||||||
|
"reservationSuccess": "预约成功",
|
||||||
|
"selectTimeSlot": "请选择时间段"
|
||||||
|
},
|
||||||
|
"savedAddresses": "保存的地址",
|
||||||
|
"title": "地址管理",
|
||||||
|
"titleDetail": "地址详情"
|
||||||
|
},
|
||||||
|
"browse": {
|
||||||
|
"titleCuisine": "附近的美食",
|
||||||
|
"titleRecipes": "选择食谱"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"all-merchants": "所有商家",
|
||||||
|
"default-location": "请选择地址",
|
||||||
|
"deliveryFee": "配送费",
|
||||||
|
"featured-on": "精选商家",
|
||||||
|
"featured-dishes": "精选菜品",
|
||||||
|
"nearby-merchants": "附近商家"
|
||||||
|
},
|
||||||
|
"mine": {
|
||||||
|
"activity-description": "活动说明:",
|
||||||
|
"activity-description-tip": "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
||||||
|
"collection": "收藏",
|
||||||
|
"complaintsAndSuggestions": "投诉建议",
|
||||||
|
"customer-service-phone": "客服电话",
|
||||||
|
"dial": "拨号",
|
||||||
|
"discount": "优惠",
|
||||||
|
"help": "帮助",
|
||||||
|
"invitation-code": "邀请码",
|
||||||
|
"invite-friends": "邀请好友",
|
||||||
|
"inviteFriends": "邀请好友",
|
||||||
|
"join": "加入",
|
||||||
|
"log-out-successfully": "退出登录成功",
|
||||||
|
"login-out-tip": "是否确定退出当前登录账号",
|
||||||
|
"member-desc": "解锁会员,享受更多折扣和促销活动。",
|
||||||
|
"member-title": "CHEFLINK 免费试用",
|
||||||
|
"order": "订单",
|
||||||
|
"platformAgreement": "平台协议",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"save-picture": "保存图片",
|
||||||
|
"set": "设置",
|
||||||
|
"storeSettled": "店铺入驻",
|
||||||
|
"support": "客服",
|
||||||
|
"the-person-invited": "我的邀请",
|
||||||
|
"wallet": "钱包",
|
||||||
|
"work-time": "工作时间",
|
||||||
|
"my-invitations": "我的邀请"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"DEL": "配送",
|
||||||
|
"PU": "自取",
|
||||||
|
"accept": "接受",
|
||||||
|
"all": "全部",
|
||||||
|
"completed": "已完成",
|
||||||
|
"confirmReady": "确认/已准备",
|
||||||
|
"onTheWay": "在路上",
|
||||||
|
"title": "订单"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"hot-title": "热搜",
|
||||||
|
"recently": "历史记录",
|
||||||
|
"result": {
|
||||||
|
"food": "美食",
|
||||||
|
"recipe": "菜谱",
|
||||||
|
"result": "条结果"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shop": {
|
||||||
|
"description": "此功能暂未开放,请稍后体验",
|
||||||
|
"tips": "提示"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-login": {
|
||||||
|
"and": "和",
|
||||||
|
"choose-language": {
|
||||||
|
"confirm": "确认语言",
|
||||||
|
"success": "语言设置成功",
|
||||||
|
"tip": "您可以在应用程序的'我的'部分稍后更改此设置",
|
||||||
|
"title": "您好,欢迎登录"
|
||||||
|
},
|
||||||
|
"continuing-agree": "我已认真阅读并同意",
|
||||||
|
"forget-password": {
|
||||||
|
"description": "设置新密码",
|
||||||
|
"newPassword": "新密码"
|
||||||
|
},
|
||||||
|
"guide-page": {
|
||||||
|
"location": {
|
||||||
|
"allowLocationAccess": "允许位置访问",
|
||||||
|
"description": "我们需要知道您的位置,以便为您提供附近的服务。",
|
||||||
|
"selectLocation": "选择位置",
|
||||||
|
"title": "您的位置是什么?"
|
||||||
|
},
|
||||||
|
"notice": {
|
||||||
|
"activateNotification": "激活通知",
|
||||||
|
"continueLater": "继续稍后",
|
||||||
|
"description": "我们将更新您的订单状态、限时优惠、本地商店活动等信息。",
|
||||||
|
"title": "保持了解"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"description": "账户创建成功。",
|
||||||
|
"next": "下一步",
|
||||||
|
"title": "你好"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"apple-login": "苹果登录",
|
||||||
|
"description": "创建账户或登录以预订和管理您的预约",
|
||||||
|
"facebook-login": "Facebook登录",
|
||||||
|
"google-login": "Google登录",
|
||||||
|
"input-placeholder": "邮箱地址/手机号",
|
||||||
|
"prompt": {
|
||||||
|
"confirm-email-verify": "两次邮箱不一致",
|
||||||
|
"email-address-verify": "请输入正确的邮箱地址",
|
||||||
|
"first-name": "请输入名字",
|
||||||
|
"last-name": "请输入姓氏",
|
||||||
|
"password": "请输入密码",
|
||||||
|
"phone-number": "请输入电话号码",
|
||||||
|
"phone-number-verify": "请输入正确的手机号"
|
||||||
|
},
|
||||||
|
"title": "登录或注册",
|
||||||
|
"wechat-login": "微信登录"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"description": "输入密码以继续",
|
||||||
|
"forgotPassword": "忘记密码",
|
||||||
|
"title": "登录"
|
||||||
|
},
|
||||||
|
"login-successfully": "登录成功",
|
||||||
|
"prompt": {
|
||||||
|
"first-name": "请输入姓",
|
||||||
|
"last-name": "请输入名"
|
||||||
|
},
|
||||||
|
"sign-up": {
|
||||||
|
"confirm-email": "确认邮箱",
|
||||||
|
"first-name": "名",
|
||||||
|
"last-name": "姓",
|
||||||
|
"password": "密码",
|
||||||
|
"phone-number": "手机号",
|
||||||
|
"register-success": "注册成功",
|
||||||
|
"title": "注册"
|
||||||
|
},
|
||||||
|
"verify-code": {
|
||||||
|
"changeOne": "换一个",
|
||||||
|
"description": "出于安全原因,请键入验证码继续",
|
||||||
|
"title": "验证码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-store": {
|
||||||
|
"checkout": {
|
||||||
|
"addAddress": "新增地址",
|
||||||
|
"addCoupon": "添加优惠券",
|
||||||
|
"appointmentDelivery": "预约配送",
|
||||||
|
"appointmentPickup": "预约自取",
|
||||||
|
"chooseTime": "选择时间",
|
||||||
|
"chooseTips": "选择小费",
|
||||||
|
"contactPhone": "联系电话",
|
||||||
|
"deliveryPreference": "配送偏好",
|
||||||
|
"deliveryTime": "配送时间",
|
||||||
|
"distance": "距离",
|
||||||
|
"enableLocationForDistance": "请开启定位获取距离",
|
||||||
|
"immediateDelivery": "立即配送",
|
||||||
|
"immediatePickup": "立即自取",
|
||||||
|
"no": "否",
|
||||||
|
"notActivated": "商家暂未开通服务",
|
||||||
|
"orderInfoSummary": "订单信息摘要",
|
||||||
|
"other": "其他",
|
||||||
|
"paymentSuccess": "支付成功",
|
||||||
|
"pickupTime": "自取时间",
|
||||||
|
"pleaseSelectAddress": "请选择地址",
|
||||||
|
"pleaseSelectCreditCard": "请选择信用卡",
|
||||||
|
"priceDetail": {
|
||||||
|
"desc": "这些费用仅基于购物车数量等因素,并用于支付与您的订单相关的费用,包括平台服务和配送服务。",
|
||||||
|
"memberDiscount": "会员折扣",
|
||||||
|
"serviceFees": "服务费及其他费用",
|
||||||
|
"taxation": "税收",
|
||||||
|
"title": "包含哪些内容"
|
||||||
|
},
|
||||||
|
"tips": "是否需要每周送货",
|
||||||
|
"tipsDesc": "如您购买周配送服务,则本周30磅以上可以免配送费,30磅以下仍需要付配送费,不购买则按正常配送费计算",
|
||||||
|
"title": "结账",
|
||||||
|
"yes": "是"
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"acceptanceTime": "订单接受时间",
|
||||||
|
"autoCancellation": "30分钟后自动取消",
|
||||||
|
"cancellationReason": "取消原因",
|
||||||
|
"cancellationReasonDesc": "30分钟不处理,系统自动同意",
|
||||||
|
"cancellationTime": "取消时间",
|
||||||
|
"cancellationTitle": "商户处理中",
|
||||||
|
"cancelled": "已取消",
|
||||||
|
"deliveryAddress": "配送地址",
|
||||||
|
"deliveryPhotos": "送达照片",
|
||||||
|
"deliveryTime": "送达时间",
|
||||||
|
"estimatedDeliveryTime": "预计送达时间",
|
||||||
|
"orderInfo": "订单信息",
|
||||||
|
"orderNumber": "订单号",
|
||||||
|
"orderStatus": {
|
||||||
|
"agree": "商家已同意退款",
|
||||||
|
"agreeRefund": "已同意退款",
|
||||||
|
"cancelled": "已取消",
|
||||||
|
"completed": "已核销",
|
||||||
|
"delivered": "已完成",
|
||||||
|
"delivering": "配送中",
|
||||||
|
"hasPendingPayment": "待接单",
|
||||||
|
"merchantRejected": "已拒绝",
|
||||||
|
"merchantRejectedDesc": "商家已拒绝接单",
|
||||||
|
"ordered": "已下单",
|
||||||
|
"paid": "已付款",
|
||||||
|
"pending": "等待商家处理",
|
||||||
|
"pendingPayment": "待付款",
|
||||||
|
"ready": "已准备",
|
||||||
|
"received": "已接单",
|
||||||
|
"refund": "退款中",
|
||||||
|
"rejectRefund": "已拒绝退款"
|
||||||
|
},
|
||||||
|
"orderTime": "下单时间",
|
||||||
|
"rejectReason": "商家拒绝原因",
|
||||||
|
"subtotal": "小计",
|
||||||
|
"taxesAndOtherFees": "税金和其他费用",
|
||||||
|
"total": "总计",
|
||||||
|
"upTime": "预计自取时间",
|
||||||
|
"useCode": "核销码",
|
||||||
|
"writeOff": "核销"
|
||||||
|
},
|
||||||
|
"store": {
|
||||||
|
"addToCart": "加入购物车",
|
||||||
|
"appetizers": "开胃菜",
|
||||||
|
"merchantDiscounts": "商家折扣",
|
||||||
|
"claimNow": "立即领取",
|
||||||
|
"claimed": "已领取",
|
||||||
|
"claimCoupon": "领取优惠券",
|
||||||
|
"couponOff": "优惠",
|
||||||
|
"validDays": "有效天数",
|
||||||
|
"days": "天",
|
||||||
|
"get": "获取",
|
||||||
|
"congratulations": "恭喜!",
|
||||||
|
"tips-1": "使用优惠券下单",
|
||||||
|
"tips-2": "享受更多折扣",
|
||||||
|
"tips-3": "轻点获取",
|
||||||
|
"areaCode": {
|
||||||
|
"singapore": "新加坡"
|
||||||
|
},
|
||||||
|
"cancelOrder": {
|
||||||
|
"dontWant": "我不想要了",
|
||||||
|
"forgotCoupon": "忘记使用优惠券",
|
||||||
|
"informationError": "信息填写错误",
|
||||||
|
"wrongLess": "错误/缺少"
|
||||||
|
},
|
||||||
|
"choose": "请选择",
|
||||||
|
"delivery": "配送",
|
||||||
|
"deliveryTags": {
|
||||||
|
"carefulPackaging": "包装仔细",
|
||||||
|
"fastDelivery": "配送快速",
|
||||||
|
"goodAttitude": "态度良好",
|
||||||
|
"onTime": "准时送达",
|
||||||
|
"polite": "礼貌",
|
||||||
|
"professional": "专业"
|
||||||
|
},
|
||||||
|
"discount": "折扣",
|
||||||
|
"earTime": "最早交货时间",
|
||||||
|
"members": "会员",
|
||||||
|
"miles": "英里",
|
||||||
|
"orderStatus": {
|
||||||
|
"delivered": "已送达",
|
||||||
|
"delivering": "配送中",
|
||||||
|
"ordered": "已下单",
|
||||||
|
"paid": "已支付",
|
||||||
|
"received": "已接单",
|
||||||
|
"rejected": "商家拒绝"
|
||||||
|
},
|
||||||
|
"pickup": "取货",
|
||||||
|
"pickupAddress": "取餐地址",
|
||||||
|
"pickupTime": "取餐时间",
|
||||||
|
"recommend": "推荐给您",
|
||||||
|
"required": "必选项",
|
||||||
|
"sales": "销量",
|
||||||
|
"securityCode": {
|
||||||
|
"cantSee": "看不清?换一张",
|
||||||
|
"confirm": "确定"
|
||||||
|
},
|
||||||
|
"shareFunctionality": "分享功能",
|
||||||
|
"tips": "我们将在",
|
||||||
|
"tips1": "分钟后关闭,您可以选择现在订购或者其他时间下单。",
|
||||||
|
"tips2": "人已复购",
|
||||||
|
"tips3": "会员可享受",
|
||||||
|
"tips4": "最低起送金额",
|
||||||
|
"tips5": "配送费",
|
||||||
|
"title": "这家店的起送金额是",
|
||||||
|
"toast": {
|
||||||
|
"deliveryService": "该商家未开通配送服务",
|
||||||
|
"selfPickup": "该商家未开通取货服务"
|
||||||
|
},
|
||||||
|
"today": "今天",
|
||||||
|
"tomorrow": "明天",
|
||||||
|
"use": "使用",
|
||||||
|
"weekdays": {
|
||||||
|
"friday": "星期五",
|
||||||
|
"monday": "星期一",
|
||||||
|
"saturday": "星期六",
|
||||||
|
"sunday": "星期日",
|
||||||
|
"thursday": "星期四",
|
||||||
|
"tuesday": "星期二",
|
||||||
|
"wednesday": "星期三"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"view-reviews": {
|
||||||
|
"Score": "评分",
|
||||||
|
"deliveryDriver": "配送员",
|
||||||
|
"goods": "商品",
|
||||||
|
"viewTitle": "查看评价"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pages-user": {
|
||||||
|
"balance": {
|
||||||
|
"all": "全部",
|
||||||
|
"detail-list": "明细列表",
|
||||||
|
"month": "月",
|
||||||
|
"month-filter": "月份筛选",
|
||||||
|
"my-balance": "我的余额",
|
||||||
|
"title": "钱包",
|
||||||
|
"type": "类型",
|
||||||
|
"year": "年",
|
||||||
|
"year-month-select": "年月筛选"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"add": "添加信用卡",
|
||||||
|
"desc": "卡绑定后,可以快速直接付款",
|
||||||
|
"listCard": "信用卡列表",
|
||||||
|
"title": "先绑定卡,再付款"
|
||||||
|
},
|
||||||
|
"cart": {
|
||||||
|
"addItems": "添加商品",
|
||||||
|
"addNote": "添加备注",
|
||||||
|
"items": "项",
|
||||||
|
"requestTableware": "是否需要餐具",
|
||||||
|
"title": "购物车",
|
||||||
|
"totalPrice": "总计",
|
||||||
|
"viewCart": "查看购物车",
|
||||||
|
"viewStore": "查看店铺"
|
||||||
|
},
|
||||||
|
"choosePaymethod": {
|
||||||
|
"creditCard": "信用卡支付",
|
||||||
|
"replace": "替换",
|
||||||
|
"title": "选择支付方式",
|
||||||
|
"wallet": "余额支付"
|
||||||
|
},
|
||||||
|
"complaints": {
|
||||||
|
"contact-information": "联系电话",
|
||||||
|
"contact-information-tip": "您的联系方式有助于我们沟通解决问题,仅工作人员可见",
|
||||||
|
"description": "您好,欢迎您给我们反馈产品的使用感受和建议。",
|
||||||
|
"feedback-content": "反馈内容",
|
||||||
|
"feedback-content-placeholder": "请填写意见反馈内容",
|
||||||
|
"image": "图片",
|
||||||
|
"title": "投诉建议",
|
||||||
|
"validation": {
|
||||||
|
"contact-phone-invalid": "请填写正确的联系电话",
|
||||||
|
"contact-phone-required": "请填写联系电话",
|
||||||
|
"content-max-length": "反馈内容不能超过500个字符",
|
||||||
|
"content-min-length": "反馈内容至少需要10个字符",
|
||||||
|
"content-required": "请填写反馈内容"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coupon": {
|
||||||
|
"all-merchants": "适用于所有商户使用",
|
||||||
|
"expiry-date": "到期日:",
|
||||||
|
"merchant-specific": "指定商户使用",
|
||||||
|
"no-coupons": "您目前没有任何优惠券",
|
||||||
|
"redeem-now": "立即兑换",
|
||||||
|
"search-placeholder": "输入优惠码",
|
||||||
|
"title": "优惠券"
|
||||||
|
},
|
||||||
|
"member": {
|
||||||
|
"annually": "每年",
|
||||||
|
"autoSubscribe": "是否自动续费",
|
||||||
|
"btn-text": "订阅",
|
||||||
|
"creditCard": "添加信用卡",
|
||||||
|
"desc": "它每年可以额外节省23.88美元",
|
||||||
|
"free": "免费",
|
||||||
|
"free-trial": "免费试用",
|
||||||
|
"freeTrial": "免费试用周数",
|
||||||
|
"join": "加入",
|
||||||
|
"month": "月",
|
||||||
|
"monthly": "每月",
|
||||||
|
"payTime": "支付时间",
|
||||||
|
"paymentMethod": "支付方式",
|
||||||
|
"renewalNow": "立即加入",
|
||||||
|
"weeks": "周",
|
||||||
|
"billedAnnually": "按${amount}/年 结算",
|
||||||
|
"savingsPerYear": "它每年可以额外节省${amount}"
|
||||||
|
},
|
||||||
|
"member-center": {
|
||||||
|
"chooseLike": "选择您喜欢的3个项目",
|
||||||
|
"likeDescription": "此信息用于为您提供个性化服务体验",
|
||||||
|
"notCurrentlyTrying": "目前未尝试CHEFLINK",
|
||||||
|
"subscribe": "订阅CHEFLINK",
|
||||||
|
"temporarilySkip": "暂时跳过",
|
||||||
|
"title": "会员中心",
|
||||||
|
"youWillMissOut": "您将错过符合条件的餐品和新鲜肉类的折扣优惠。"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"readAll": "一键已读",
|
||||||
|
"title": "消息"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"change-password-successfully": "修改密码成功",
|
||||||
|
"forget-password-successfully": "忘记密码成功"
|
||||||
|
},
|
||||||
|
"pay-password": {
|
||||||
|
"change-payment-password-successfully": "修改支付密码成功",
|
||||||
|
"enter-6-digit-password": "请输入6位密码",
|
||||||
|
"forget-payment-password-successfully": "忘记支付密码成功",
|
||||||
|
"input-placeholder": {
|
||||||
|
"enter-new-password": "请输入新密码",
|
||||||
|
"enter-new-password-again": "请再次输入新密码",
|
||||||
|
"enter-old-password": "请输入原密码",
|
||||||
|
"enter-phone-number": "请输入手机号",
|
||||||
|
"enter-verification-code": "请输入验证码"
|
||||||
|
},
|
||||||
|
"set-payment-password-successfully": "设置支付密码成功",
|
||||||
|
"two-passwords-inconsistent": "两次输入的密码不一致"
|
||||||
|
},
|
||||||
|
"recharge": {
|
||||||
|
"amount": "充值金额",
|
||||||
|
"amount-invalid": "充值金额不能小于0",
|
||||||
|
"description": "请输入您的充值金额",
|
||||||
|
"pay-method": "请选择支付方式",
|
||||||
|
"success": "充值成功",
|
||||||
|
"title": "余额充值"
|
||||||
|
},
|
||||||
|
"recipe": {
|
||||||
|
"collapseReply": "收起回复内容",
|
||||||
|
"desc": "如果您喜欢它,请收藏它",
|
||||||
|
"expandReply": "展开回复",
|
||||||
|
"replyTo": "回复给",
|
||||||
|
"title": "菜谱详情"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"cancelAccount": "注销账号",
|
||||||
|
"cancelAccountConfirm": "您确定注销账号吗?账号注销后,您的所有数据将被删除且无法恢复。",
|
||||||
|
"cancelAccountSuccess": "注销成功",
|
||||||
|
"language": "语言切换",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"modification": "修改密码",
|
||||||
|
"payPwd": "支付密码"
|
||||||
|
},
|
||||||
|
"store-settle-in": {
|
||||||
|
"address": "地址",
|
||||||
|
"audit": {
|
||||||
|
"pass": "审核成功",
|
||||||
|
"passDesc": "恭喜您成功入驻平台!",
|
||||||
|
"passDesc1": "快来上传商品信息,开启赚钱之旅吧~",
|
||||||
|
"reject": "审核失败",
|
||||||
|
"rejectDesc": "失败原因",
|
||||||
|
"submitted": "审核中",
|
||||||
|
"submittedDesc": "平台审核中,耐心等待..."
|
||||||
|
},
|
||||||
|
"businessLicense": "食品操作处理证",
|
||||||
|
"category": "所属类型",
|
||||||
|
"desc": "简单填写信息,快来开启赚钱之旅",
|
||||||
|
"detailedAddress": "详细地址",
|
||||||
|
"idCardFront": "ID/护照",
|
||||||
|
"introduction": "介绍",
|
||||||
|
"schema": {
|
||||||
|
"address": "请选择地址",
|
||||||
|
"businessLicense": "请上传食品操作处理证",
|
||||||
|
"category": "请选择所属类型",
|
||||||
|
"description": "请输入介绍",
|
||||||
|
"detailAddress": "请输入详细地址",
|
||||||
|
"detailedAddress": "请输入详细地址",
|
||||||
|
"idCardBack": "请上传护照",
|
||||||
|
"idCardFront": "请上传ID",
|
||||||
|
"introduction": "请输入介绍",
|
||||||
|
"storeName": "请输入店铺名称"
|
||||||
|
},
|
||||||
|
"storeName": "店铺名称",
|
||||||
|
"title": "店铺入驻"
|
||||||
|
},
|
||||||
|
"user-info": {
|
||||||
|
"head-portrait": "头像",
|
||||||
|
"nickname": "昵称",
|
||||||
|
"view-larger-image": "查看大图"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pickupAddress": "地址",
|
||||||
|
"tabBar": {
|
||||||
|
"browse": "浏览",
|
||||||
|
"global": "全球购",
|
||||||
|
"home": "首页",
|
||||||
|
"mine": "我的",
|
||||||
|
"order": "订单"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"addCartSuccess": "添加购物车成功",
|
||||||
|
"commentSuccess": "评论成功",
|
||||||
|
"deleteSuccess": "删除成功",
|
||||||
|
"likeTips": "最多只能选择3个",
|
||||||
|
"orderNumberCopied": "复制成功",
|
||||||
|
"redemptionSuccessful": "兑换成功",
|
||||||
|
"submitSuccess": "提交成功"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {createSSRApp} from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import {Pinia, store} from '@/store'
|
||||||
|
import {installAppPlugins} from '@/plugin'
|
||||||
|
import {requestInterceptor, routeInterceptor} from "@/interceptor";
|
||||||
|
import {i18n} from "@/locale";
|
||||||
|
|
||||||
|
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
app.use(store)
|
||||||
|
app.use(requestInterceptor)
|
||||||
|
app.use(routeInterceptor)
|
||||||
|
app.use(i18n)
|
||||||
|
installAppPlugins(app)
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
Pinia,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
{
|
||||||
|
"name" : "CHEFLINK delivery",
|
||||||
|
"appid" : "__UNI__06509BE",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.11",
|
||||||
|
"versionCode" : 111,
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"checkPermissionDenied" : true,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : false,
|
||||||
|
"waiting" : false,
|
||||||
|
"autoclose" : false,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
"safearea" : {
|
||||||
|
"background" : "#fff",
|
||||||
|
"bottom" : {
|
||||||
|
"offset" : "auto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optimization" : {
|
||||||
|
"subPackages" : true
|
||||||
|
},
|
||||||
|
"runmode" : "liberate",
|
||||||
|
// 开启分包优化后,必须配置资源释放模式
|
||||||
|
"compatible" : {
|
||||||
|
"ignoreVersion" : true
|
||||||
|
},
|
||||||
|
"screenOrientation" : [ "portrait-primary", "landscape-primary" ],
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {
|
||||||
|
"Geolocation" : {},
|
||||||
|
"Camera" : {},
|
||||||
|
"OAuth" : {},
|
||||||
|
"Share" : {},
|
||||||
|
"Push" : {}
|
||||||
|
},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"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\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
|
||||||
|
],
|
||||||
|
"minSdkVersion" : 28,
|
||||||
|
"targetSdkVersion" : 35,
|
||||||
|
"schemes" : "naillover",
|
||||||
|
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
|
||||||
|
"excludePermissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {
|
||||||
|
"urltypes" : "naillover",
|
||||||
|
"idfa" : false,
|
||||||
|
"dSYMs" : false,
|
||||||
|
"privacyDescription" : {
|
||||||
|
"NSPhotoLibraryUsageDescription" : "上传用户头像等",
|
||||||
|
"NSPhotoLibraryAddUsageDescription" : "保存邀请海报图片等",
|
||||||
|
"NSCameraUsageDescription" : "上传用户头像等",
|
||||||
|
"NSLocationWhenInUseUsageDescription" : "更好的为您推荐商户",
|
||||||
|
"NSLocationAlwaysUsageDescription" : "更好的为您推荐商户",
|
||||||
|
"NSLocationAlwaysAndWhenInUseUsageDescription" : "更好的为您推荐商户"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {
|
||||||
|
"geolocation" : {
|
||||||
|
"system" : {
|
||||||
|
"__platform__" : [ "ios", "android" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maps" : {},
|
||||||
|
"payment" : {},
|
||||||
|
"ad" : {},
|
||||||
|
"oauth" : {
|
||||||
|
"apple" : {},
|
||||||
|
"google" : {
|
||||||
|
"clientid" : "455840300142-9bk9dd1u1b4bmgjc8n2c74jim89kdvph.apps.googleusercontent.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share" : {},
|
||||||
|
"push" : {
|
||||||
|
"unipush" : {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons" : {
|
||||||
|
"android" : {
|
||||||
|
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||||
|
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||||
|
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||||
|
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||||
|
},
|
||||||
|
"ios" : {
|
||||||
|
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||||
|
"ipad" : {
|
||||||
|
"app" : "unpackage/res/icons/76x76.png",
|
||||||
|
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||||
|
"notification" : "unpackage/res/icons/20x20.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||||
|
"settings" : "unpackage/res/icons/29x29.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||||
|
},
|
||||||
|
"iphone" : {
|
||||||
|
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||||
|
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||||
|
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"splashscreen" : {
|
||||||
|
"androidStyle" : "default",
|
||||||
|
"iosStyle" : "storyboard",
|
||||||
|
"ios" : {
|
||||||
|
"storyboard" : "CustomStoryboard.zip"
|
||||||
|
},
|
||||||
|
"android" : {
|
||||||
|
"hdpi" : "starter/drawable-hdpi/Untitled_design.png",
|
||||||
|
"xhdpi" : "starter/drawable-xhdpi/Untitled_design.png",
|
||||||
|
"xxhdpi" : "starter/drawable-xxhdpi/Untitled_design.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locales" : {
|
||||||
|
"en" : {
|
||||||
|
// 英文
|
||||||
|
"ios" : {
|
||||||
|
"privacyDescription" : {
|
||||||
|
"NSCameraUsageDescription" : "Allow access to your camera to upload your avatar.",
|
||||||
|
"NSPhotoLibraryUsageDescription" : "Allow access to your photo library to select or save images.",
|
||||||
|
"NSPhotoLibraryAddUsageDescription" : "Allow saving images to your photo library, such as invitation posters.",
|
||||||
|
"NSLocationWhenInUseUsageDescription" : "Allow access to your location to recommend nearby merchants.",
|
||||||
|
"NSLocationAlwaysUsageDescription" : "Allow access to your location to recommend nearby merchants.",
|
||||||
|
"NSLocationAlwaysAndWhenInUseUsageDescription" : "Allow access to your location to recommend nearby merchants."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh" : {
|
||||||
|
// 中文(简体)
|
||||||
|
"ios" : {
|
||||||
|
"privacyDescription" : {
|
||||||
|
"NSCameraUsageDescription" : "允许访问相机以上传头像。",
|
||||||
|
"NSPhotoLibraryUsageDescription" : "允许访问相册以选择或保存图片。",
|
||||||
|
"NSPhotoLibraryAddUsageDescription" : "允许保存图片到相册,例如邀请海报。",
|
||||||
|
"NSLocationWhenInUseUsageDescription" : "允许访问您的位置以推荐附近商户。",
|
||||||
|
"NSLocationAlwaysUsageDescription" : "允许访问您的位置以推荐附近商户。",
|
||||||
|
"NSLocationAlwaysAndWhenInUseUsageDescription" : "允许访问您的位置以推荐附近商户。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"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",
|
||||||
|
"locale" : "en"
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"id": "cc-comment",
|
||||||
|
"name": "cc-comment 评论列表,回复,点赞,删除组件 vue3.2+uni-ui",
|
||||||
|
"displayName": "cc-comment 评论列表,回复,点赞,删除组件 vue3.2+uni-ui",
|
||||||
|
"version": "1.5",
|
||||||
|
"description": "支持多级评论, 点赞, 删除, 回复, 发起新评论; 注释详细方便二次开发; 组件功能高完成度可直接调用后端接口进行使用;",
|
||||||
|
"keywords": [
|
||||||
|
"评论",
|
||||||
|
"评论列表",
|
||||||
|
"留言板",
|
||||||
|
"回复点赞删除",
|
||||||
|
"一级二级多级评论"
|
||||||
|
],
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {getCaptcha} from "@/pages-login/service";
|
||||||
|
|
||||||
|
const {t} = useI18n();
|
||||||
|
const emit = defineEmits(["submit"]);
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const code = ref("");
|
||||||
|
const captchaData = ref({
|
||||||
|
img: "",
|
||||||
|
uuid: "",
|
||||||
|
code: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
function onOpen() {
|
||||||
|
getCode();
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCode() {
|
||||||
|
code.value = "";
|
||||||
|
getCaptcha().then((res: any) => {
|
||||||
|
captchaData.value = res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
emit("submit", {
|
||||||
|
code: code.value,
|
||||||
|
uuid: captchaData.value.uuid,
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
onOpen,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
v-model="show"
|
||||||
|
custom-style="border-radius:20rpx;"
|
||||||
|
position="center"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<view class="relative w-630rpx box-border px-36rpx py-40rpx">
|
||||||
|
<image
|
||||||
|
class="absolute top-34rpx right-36rpx w-32rpx h-32rpx"
|
||||||
|
src="@img/chef/100404.png"
|
||||||
|
@click="handleClose"
|
||||||
|
></image>
|
||||||
|
<view class="text-36rpx text-#333333 text-center lh-33rpx mb-22rpx">{{
|
||||||
|
t("pages-login.verify-code.title")
|
||||||
|
}}
|
||||||
|
</view>
|
||||||
|
<view class="text-28rpx text-#999999 lh-28rpx text-center mb-40rpx">{{
|
||||||
|
t("pages-login.verify-code.description")
|
||||||
|
}}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="mt-24rpx overflow-hidden flex pl-30rpx items-center justify-between h-90rpx border-2rpx border-solid border-#D1D1D1 rounded-20rpx"
|
||||||
|
>
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="code"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="20"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1"
|
||||||
|
no-border
|
||||||
|
placeholderStyle="font-size: 28rpx;color: #999;line-height: 42rpx;"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
|
||||||
|
<image
|
||||||
|
:src="`data:image/gif;base64,${captchaData.img}`"
|
||||||
|
class="w-120rpx h-88rpx"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="text-24rpx text-#FF6106 lh-24rpx text-right mt-16rpx"
|
||||||
|
@click="getCode"
|
||||||
|
>{{ t("pages-login.verify-code.changeOne") }}
|
||||||
|
</view
|
||||||
|
>
|
||||||
|
<view class="mt-54rpx">
|
||||||
|
<wd-button
|
||||||
|
block
|
||||||
|
custom-class="!h-90rpx !bg-#14181B !text-36rpx text-#fff font-bold !rounded-16rpx"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
{{ t("common.continue") }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {debounce} from "throttle-debounce"
|
||||||
|
import Config from "@/config"
|
||||||
|
import {useConfigStore} from "@/store"
|
||||||
|
import {setDayjsLocale, setWotDesignLocale} from "@/plugin"
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const {locale} = useI18n()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
// 语言选项 - 参考组件的语言配置
|
||||||
|
const languageOptions = ref([
|
||||||
|
{
|
||||||
|
name: "English",
|
||||||
|
systemValue: "en",
|
||||||
|
selected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "中文",
|
||||||
|
systemValue: "zh-Hans",
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 初始化当前语言选择状态
|
||||||
|
const initLanguageSelection = () => {
|
||||||
|
const currentLocale = uni.getLocale()
|
||||||
|
languageOptions.value.forEach(option => {
|
||||||
|
option.selected = option.systemValue === currentLocale
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择语言
|
||||||
|
const selectLanguage = (selectedValue: string) => {
|
||||||
|
languageOptions.value.forEach(option => {
|
||||||
|
option.selected = option.systemValue === selectedValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认语言选择 - 参考组件的切换逻辑
|
||||||
|
const confirmLanguage = () => {
|
||||||
|
const selectedLanguage = languageOptions.value.find(option => option.selected)
|
||||||
|
if (selectedLanguage) {
|
||||||
|
const localeLanguages = uni.getLocale()
|
||||||
|
console.log(localeLanguages)
|
||||||
|
|
||||||
|
if (selectedLanguage.systemValue === uni.getLocale()) {
|
||||||
|
// 如果选择的语言和当前语言相同,直接跳转
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages-login/pages/index'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置语言 - 使用组件相同的方法
|
||||||
|
uni.setLocale(selectedLanguage.systemValue)
|
||||||
|
locale.value = selectedLanguage.systemValue
|
||||||
|
setWotDesignLocale()
|
||||||
|
setDayjsLocale()
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if (configStore.isIos) {
|
||||||
|
setTimeout(() => {
|
||||||
|
plus.runtime.restart()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages-login.choose-language.success'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟跳转到登录页面
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.switchTab({
|
||||||
|
url: '/pages/home/index'
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用防抖处理确认操作
|
||||||
|
const handleConfirm = debounce(Config.debounceShortTime, confirmLanguage, {
|
||||||
|
atBegin: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面加载时初始化语言选择
|
||||||
|
onMounted(() => {
|
||||||
|
initLanguageSelection()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="language-container">
|
||||||
|
|
||||||
|
<!-- 欢迎文字 -->
|
||||||
|
<view class="welcome-section">
|
||||||
|
<text class="welcome-text">{{ t('pages-login.choose-language.title') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 语言选择列表 -->
|
||||||
|
<view class="language-list">
|
||||||
|
<view
|
||||||
|
v-for="option in languageOptions"
|
||||||
|
:key="option.systemValue"
|
||||||
|
class="language-item"
|
||||||
|
@click="selectLanguage(option.systemValue)"
|
||||||
|
>
|
||||||
|
<view class="language-card">
|
||||||
|
<view v-show="option.selected" class="i-carbon:checkmark-filled text-primary"></view>
|
||||||
|
<view v-show="!option.selected" class="i-carbon:circle-outline text-#CCCCCC"></view>
|
||||||
|
<text :class="{ 'selected': option.selected }" class="language-name">{{ option.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提示文字 -->
|
||||||
|
<view class="tip-section">
|
||||||
|
<text class="tip-text">{{ t('pages-login.choose-language.tip') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认按钮 -->
|
||||||
|
<view class="confirm-section">
|
||||||
|
<view class="confirm-button" @click="handleConfirm">
|
||||||
|
<text class="confirm-text">{{ t('pages-login.choose-language.confirm') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.welcome-section {
|
||||||
|
padding: 286rpx 32rpx 0;
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 52rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 60rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-list {
|
||||||
|
padding: 104rpx 32rpx 0;
|
||||||
|
|
||||||
|
.language-item {
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-card {
|
||||||
|
width: 686rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 2rpx solid #E8E8E8;
|
||||||
|
border-radius: 46rpx;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
|
||||||
|
.radio-button {
|
||||||
|
.radio-outer {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 2rpx solid #666666;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-inner {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666666;
|
||||||
|
line-height: 36rpx;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-section {
|
||||||
|
padding: 324rpx 76rpx 0;
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666666;
|
||||||
|
line-height: 36rpx;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-section {
|
||||||
|
padding: 54rpx 32rpx 0;
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
width: 686rpx;
|
||||||
|
height: 92rpx;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 46rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0px 8rpx 20rpx 0px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
.confirm-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #FFFFFF;
|
||||||
|
line-height: 44rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {z} from "zod";
|
||||||
|
import {useLogicStore} from "@/pages-login/store/module/logic";
|
||||||
|
import VerificationCode from "../../components/verification-code.vue";
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {useUserStore} from "@/store";
|
||||||
|
import {appUserForgetPwdPost, appUserGetByEmailPost, appUserGetByPhonePost, appUserForgetPwdNotLoginPost} from "@/service";
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const logicStore = useLogicStore()
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const verificationCodeRef = ref();
|
||||||
|
|
||||||
|
const FormSchema = z.string()
|
||||||
|
.min(1, {message: t('pages-login.prompt.password')})
|
||||||
|
|
||||||
|
function checkForm(): boolean {
|
||||||
|
const validateFormField = FormSchema.safeParse(logicStore.forgetPasswordForm.loginPwd)
|
||||||
|
console.log('validateFormField', validateFormField)
|
||||||
|
if (!validateFormField.success) {
|
||||||
|
const formatted = validateFormField.error.format()
|
||||||
|
R.path(['_errors', 0], formatted) && uni.showToast({
|
||||||
|
title: R.path(['_errors', 0], formatted),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateFormField.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
verificationCodeRef.value.onOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeSubmit(data) {
|
||||||
|
console.log(data)
|
||||||
|
// forgetPwdNotLogin
|
||||||
|
appUserForgetPwdNotLoginPost({
|
||||||
|
body: {
|
||||||
|
email: logicStore.forgetPasswordForm.email,
|
||||||
|
phone: logicStore.forgetPasswordForm.phone,
|
||||||
|
captcha: data.code,
|
||||||
|
uuid: data.uuid,
|
||||||
|
loginPwd: logicStore.forgetPasswordForm.loginPwd,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages-login.prompt.reset-success'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
logicStore.reset()
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages-login/pages/index',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = R.when(checkForm, debounce(Config.debounceLongTime, submit, {
|
||||||
|
atBegin: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({url});
|
||||||
|
}
|
||||||
|
|
||||||
|
// onLoad(R.pipe(() => getUserInfoByEmail(R.pick(['email'], logicStore.registerForm)), R.andThen((res) => {
|
||||||
|
// console.log(res)
|
||||||
|
// logicStore.forgetPasswordForm = {
|
||||||
|
// ...R.pick(['phone', 'areaCode'], res.data),
|
||||||
|
// email: logicStore.registerForm.email,
|
||||||
|
// loginPwd: '',
|
||||||
|
// captcha: "",
|
||||||
|
// }
|
||||||
|
// })))
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
// 根据登录信息获取用户信息
|
||||||
|
if (logicStore.loginForm.type === 'email' && logicStore.loginForm.email) {
|
||||||
|
// 根据邮箱获取用户信息
|
||||||
|
appUserGetByEmailPost({
|
||||||
|
body: {
|
||||||
|
email: logicStore.loginForm.email,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
}
|
||||||
|
}).then((res: any) => {
|
||||||
|
console.log('根据邮箱获取用户信息', res)
|
||||||
|
if (res.data) {
|
||||||
|
logicStore.forgetPasswordForm.email = res.data.email
|
||||||
|
logicStore.forgetPasswordForm.phone = res.data.phone
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (logicStore.loginForm.type === 'phone' && logicStore.loginForm.phone) {
|
||||||
|
// 根据手机号获取用户信息
|
||||||
|
appUserGetByPhonePost({
|
||||||
|
body: {
|
||||||
|
phone: logicStore.loginForm.phone,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
}
|
||||||
|
}).then((res: any) => {
|
||||||
|
console.log('根据手机号获取用户信息', res)
|
||||||
|
if (res.data) {
|
||||||
|
logicStore.forgetPasswordForm.email = res.data.email
|
||||||
|
logicStore.forgetPasswordForm.phone = res.data.phone
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<navbar/>
|
||||||
|
<view class="pt-64rpx px-27rpx">
|
||||||
|
<view class="text-50rpx lh-50rpx text-#14181B font-bold">
|
||||||
|
{{ t('pages-login.login.forgotPassword') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-30rpx text-28rpx lh-28rpx text-#999 font-bold">
|
||||||
|
{{ t('pages-login.forget-password.description') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-78rpx">
|
||||||
|
<view class="">
|
||||||
|
<view class="text-32rpx lh-32rpx text-#14181B">{{ t("common.email") }}</view>
|
||||||
|
<view
|
||||||
|
class="mt-24rpx flex px-30rpx items-center h-100rpx bg-#EFEFEF rounded-20rpx">
|
||||||
|
<image class="shrink-0 mr-12rpx w-30rpx h-30rpx" src="@img-login/103.png"></image>
|
||||||
|
<text class="text-28-bold">{{ logicStore.forgetPasswordForm.email }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-36rpx">
|
||||||
|
<view class="text-32rpx lh-32rpx text-#14181B">{{ t("pages-login.sign-up.phone-number") }}</view>
|
||||||
|
<view
|
||||||
|
class="mt-24rpx flex px-30rpx items-center h-100rpx bg-#EFEFEF rounded-20rpx">
|
||||||
|
<image class="shrink-0 mr-12rpx w-30rpx h-30rpx" src="@img-login/102.png"></image>
|
||||||
|
<text class="text-28-bold">{{ logicStore.forgetPasswordForm.phone }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-36rpx">
|
||||||
|
<view class="text-32rpx lh-32rpx text-#14181B">{{ t("pages-login.forget-password.newPassword") }}</view>
|
||||||
|
<view
|
||||||
|
class="mt-24rpx flex px-30rpx items-center h-100rpx border-2rpx border-solid border-#D4D4D4 rounded-20rpx">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.forgetPasswordForm.loginPwd"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="20"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1"
|
||||||
|
no-border
|
||||||
|
placeholderStyle="font-size: 28rpx;color: #999;font-weight: bold;line-height: 42rpx;"
|
||||||
|
show-password
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-80rpx">
|
||||||
|
<wd-button block custom-class="!h-108rpx !text-36rpx font-bold !rounded-16rpx" @click="handleSubmit">
|
||||||
|
{{ t('common.continue') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<verification-code ref="verificationCodeRef" @submit="codeSubmit"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import useEventEmit from "@/hooks/useEventEmit";
|
||||||
|
import {EventEnum} from "@/constant/enums";
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
// 允许位置访问
|
||||||
|
const allowLocationAccess = () => {
|
||||||
|
userStore.getLocation();
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-login/pages/guide-page/notice'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手动选择位置
|
||||||
|
const selectLocation = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-user/pages/search-address/index'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
useEventEmit(EventEnum.CHOOSE_ADDRESS, (data) => {
|
||||||
|
console.log('搜索的地址信息', data)
|
||||||
|
if(data) {
|
||||||
|
userStore.userSetLocation = {
|
||||||
|
location: data.displayName,
|
||||||
|
longitude: data.location.lng,
|
||||||
|
latitude: data.location.lat,
|
||||||
|
}
|
||||||
|
setTimeout(()=> {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-login/pages/guide-page/notice'
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="bg-white flex flex-col">
|
||||||
|
<!-- 主要内容区域 -->
|
||||||
|
<view
|
||||||
|
class="flex-1 flex flex-col items-center justify-center px-40rpx pt-216rpx pb-120rpx"
|
||||||
|
>
|
||||||
|
<!-- 位置图标 -->
|
||||||
|
<image
|
||||||
|
src="@img-login/100218.png"
|
||||||
|
class="w-256rpx h-256rpx rounded-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="text-center mt-84rpx">
|
||||||
|
<text class="text-50-bold">{{ t('pages-login.guide-page.location.title') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 描述文本 -->
|
||||||
|
<view class="text-center mt-36rpx mb-86rpx">
|
||||||
|
<text class="text-30rpx lh-30rpx text-#999 leading-30rpx">
|
||||||
|
{{ t('pages-login.guide-page.location.description') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主要按钮 -->
|
||||||
|
<view class="mb-46rpx w-full">
|
||||||
|
<button
|
||||||
|
class="w-full h-108rpx bg-#14181B rounded-16rpx border-none"
|
||||||
|
@click="allowLocationAccess"
|
||||||
|
>
|
||||||
|
<text class="text-white text-36rpx lh-108rpx"
|
||||||
|
>{{ t('pages-login.guide-page.location.allowLocationAccess') }}</text
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 次要链接 -->
|
||||||
|
<view class="text-center">
|
||||||
|
<text class="text-36rpx text-#333" @click="selectLocation">
|
||||||
|
{{ t('pages-login.guide-page.location.selectLocation') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const allowLocationAccess = () => {
|
||||||
|
// 开启通知--获取通知权限
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
plus.push.getClientInfoAsync(
|
||||||
|
(info) => {
|
||||||
|
console.log('当前的推送信息', info)
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.log('err', err)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// #endif
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-login/pages/guide-page/welcome'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手动选择位置
|
||||||
|
const selectLocation = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-login/pages/guide-page/welcome'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="bg-white flex flex-col">
|
||||||
|
<!-- 主要内容区域 -->
|
||||||
|
<view
|
||||||
|
class="flex-1 flex flex-col items-center justify-center px-40rpx pt-216rpx pb-120rpx"
|
||||||
|
>
|
||||||
|
<!-- 位置图标 -->
|
||||||
|
<image
|
||||||
|
src="@img-login/100219.png"
|
||||||
|
class="w-256rpx h-256rpx rounded-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="text-center mt-84rpx">
|
||||||
|
<text class="text-50-bold">{{ t('pages-login.guide-page.notice.title') }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 描述文本 -->
|
||||||
|
<view class="text-center mt-36rpx mb-86rpx">
|
||||||
|
<text class="text-30rpx lh-30rpx text-#999 leading-30rpx">
|
||||||
|
{{ t('pages-login.guide-page.notice.description') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主要按钮 -->
|
||||||
|
<view class="mb-46rpx w-full">
|
||||||
|
<button
|
||||||
|
class="w-full h-108rpx bg-#14181B rounded-16rpx border-none"
|
||||||
|
@click="allowLocationAccess"
|
||||||
|
>
|
||||||
|
<text class="text-white text-36rpx lh-108rpx"
|
||||||
|
>{{ t('pages-login.guide-page.notice.activateNotification') }}</text
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 次要链接 -->
|
||||||
|
<view class="text-center">
|
||||||
|
<text class="text-36rpx text-#333" @click="selectLocation">
|
||||||
|
{{ t('pages-login.guide-page.notice.continueLater') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore, useConfigStore } from "@/store";
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const allowLocationAccess = () => {
|
||||||
|
// 设置标记为已经加载过引导页
|
||||||
|
configStore.isShowedGuidePage = true
|
||||||
|
// uni.switchTab({
|
||||||
|
// url: '/pages/home/index'
|
||||||
|
// })
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-user/pages/member-center/index'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="bg-white flex flex-col">
|
||||||
|
<!-- 主要内容区域 -->
|
||||||
|
<view
|
||||||
|
class="flex-1 flex flex-col items-center justify-center px-40rpx pt-216rpx pb-120rpx"
|
||||||
|
>
|
||||||
|
<!-- 位置图标 -->
|
||||||
|
<image
|
||||||
|
src="@img-login/100220.png"
|
||||||
|
class="w-256rpx h-256rpx rounded-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="text-center text-50-bold mt-84rpx">
|
||||||
|
<text class="mr-4rpx">{{ t('pages-login.guide-page.welcome.title') }},</text>
|
||||||
|
<text class="">{{ userStore.userInfo.firstName }} {{ userStore.userInfo.surname }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 描述文本 -->
|
||||||
|
<view class="text-center mt-36rpx mb-113rpx">
|
||||||
|
<text class="text-30rpx lh-30rpx text-#999 leading-30rpx">
|
||||||
|
{{ t('pages-login.guide-page.welcome.description') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 主要按钮 -->
|
||||||
|
<view class="w-full">
|
||||||
|
<button
|
||||||
|
class="w-full h-108rpx bg-#14181B rounded-16rpx border-none"
|
||||||
|
@click="allowLocationAccess"
|
||||||
|
>
|
||||||
|
<text class="text-white text-36rpx lh-108rpx"
|
||||||
|
>{{ t('pages-login.guide-page.welcome.next') }}</text
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {z} from "zod";
|
||||||
|
import {useLogicStore} from "@/pages-login/store/module/logic";
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {appUserCheckEmailUniquePost, appUserCheckPhoneUniquePost} from '@/service'
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
import { isEmail } from '@/utils'
|
||||||
|
|
||||||
|
const areaCode = ref<string>('+1');
|
||||||
|
const columns = ref<string[]>(Config.phoneCodeList);
|
||||||
|
|
||||||
|
interface LoginMethod {
|
||||||
|
id: number;
|
||||||
|
method: string;
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const logicStore = useLogicStore()
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
const loginMethods = computed<LoginMethod[]>(() => {
|
||||||
|
const methods = [
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
method: 'Google',
|
||||||
|
title: t('pages-login.index.google-login'),
|
||||||
|
icon: '/static/app/images/100204.png',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// id: 2,
|
||||||
|
// method: 'Facebook',
|
||||||
|
// title: t('pages-login.index.facebook-login'),
|
||||||
|
// icon: '/static/app/images/100207.png',
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
method: 'Apple',
|
||||||
|
title: t('pages-login.index.apple-login'),
|
||||||
|
icon: '/static/app/images/100208.png',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// id: 4,
|
||||||
|
// method: 'Wechat',
|
||||||
|
// title: t('pages-login.index.wechat-login'),
|
||||||
|
// icon: '/static/app/images/100209.png',
|
||||||
|
// },
|
||||||
|
]
|
||||||
|
|
||||||
|
if (R.and(configStore.isApp, configStore.isIos)) {
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods.filter(item => item.method !== 'Apple')
|
||||||
|
})
|
||||||
|
|
||||||
|
const btnLoading = ref(false)
|
||||||
|
function handleSubmit() {
|
||||||
|
const {email} = logicStore.registerForm
|
||||||
|
let type = '';
|
||||||
|
console.log('email', email)
|
||||||
|
console.log('email', isEmail(email))
|
||||||
|
if (isEmail(email)) {
|
||||||
|
btnLoading.value = true
|
||||||
|
appUserCheckEmailUniquePost({
|
||||||
|
body: {
|
||||||
|
email,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
},
|
||||||
|
options: {}
|
||||||
|
}).then(res => {
|
||||||
|
console.log('email', res)
|
||||||
|
if (res.data) {
|
||||||
|
logicStore.loginForm.email = email;
|
||||||
|
logicStore.loginForm.type = 'email';
|
||||||
|
// 邮箱已存在
|
||||||
|
uni.navigateTo({url: '/pages-login/pages/login/index'})
|
||||||
|
} else {
|
||||||
|
// 邮箱未存在
|
||||||
|
type = 'email';
|
||||||
|
// 用户没注册
|
||||||
|
logicStore.registerForm.type = type;
|
||||||
|
logicStore.registerForm.confirmEmail = email;
|
||||||
|
uni.navigateTo({url: '/pages-login/pages/sign-up/index'})
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
btnLoading.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
btnLoading.value = true
|
||||||
|
// 手机号登录
|
||||||
|
appUserCheckPhoneUniquePost({
|
||||||
|
body: {
|
||||||
|
areaCode: areaCode.value,
|
||||||
|
phone: email,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
},
|
||||||
|
options: {}
|
||||||
|
}).then(res => {
|
||||||
|
type = 'phone';
|
||||||
|
if (res.data) {
|
||||||
|
logicStore.loginForm.phone = email;
|
||||||
|
logicStore.loginForm.type = type;
|
||||||
|
logicStore.loginForm.areaCode = areaCode.value;
|
||||||
|
// 手机已存在
|
||||||
|
uni.navigateTo({url: '/pages-login/pages/login/index'})
|
||||||
|
} else {
|
||||||
|
// 用户没注册
|
||||||
|
logicStore.registerForm.type = type;
|
||||||
|
logicStore.registerForm.phone = email;
|
||||||
|
logicStore.registerForm.areaCode = areaCode.value;
|
||||||
|
logicStore.registerForm.email = '';
|
||||||
|
logicStore.registerForm.confirmEmail = '';
|
||||||
|
uni.navigateTo({url: '/pages-login/pages/sign-up/index'})
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
btnLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loginByMethod(item: LoginMethod) {
|
||||||
|
switch (item.method) {
|
||||||
|
case 'Apple': {
|
||||||
|
logicStore.appleLogin()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'Facebook': {
|
||||||
|
logicStore.facebookLogin()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'Google': {
|
||||||
|
logicStore.googleLogin()
|
||||||
|
// logicStore.testGoogleLogin()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'Wechat': {
|
||||||
|
logicStore.wechatLogin()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLoginByMethod = debounce(Config.debounceLongTime, loginByMethod, {
|
||||||
|
atBegin: true
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnload(() => {
|
||||||
|
logicStore.reset()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<status-bar/>
|
||||||
|
<view class="pt-120rpx px-30rpx">
|
||||||
|
<view class="text-50rpx lh-50rpx text-#14181B font-bold">
|
||||||
|
{{ t('pages-login.index.title') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-30rpx text-28rpx lh-28rpx text-#999 font-bold">
|
||||||
|
{{ t('pages-login.index.description') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-80rpx">
|
||||||
|
<view class="">
|
||||||
|
<view class="center h-88rpx border-2rpx border-solid border-#D4D4D4 rounded-20rpx"
|
||||||
|
:class="[index>0&&'mt-22rpx']" v-for="(item,index) in loginMethods"
|
||||||
|
:key="item.id"
|
||||||
|
@click="handleLoginByMethod(item)"
|
||||||
|
>
|
||||||
|
<image class="shrink-0 mr-16rpx w-40rpx h-40rpx" :src="item.icon"/>
|
||||||
|
<text class="text-28-bold">{{ item.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="my-38rpx flex items-center justify-between">
|
||||||
|
<view class="w-308rpx h-0rpx border-bottom"></view>
|
||||||
|
<text class="text-28-bold">{{ t('common.or') }}</text>
|
||||||
|
<view class="w-308rpx h-0rpx border-bottom"></view>
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<view class="flex px-24rpx items-center h-88rpx border-2rpx border-solid border-#D4D4D4 rounded-20rpx">
|
||||||
|
<view v-if="logicStore.registerForm.email && !isEmail(logicStore.registerForm.email)" class="text-28rpx">
|
||||||
|
<wd-picker v-model="areaCode" :columns="columns"/>
|
||||||
|
</view>
|
||||||
|
<wd-input
|
||||||
|
no-border
|
||||||
|
clearable
|
||||||
|
:focus-when-clear="false"
|
||||||
|
use-prefix-slot
|
||||||
|
:maxlength="255"
|
||||||
|
v-model.trim="logicStore.registerForm.email"
|
||||||
|
custom-class="flex-1"
|
||||||
|
placeholderStyle="font-size: 32rpx;color: #999;line-height: 32rpx;"
|
||||||
|
:placeholder="t('pages-login.index.input-placeholder')"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-42rpx">
|
||||||
|
<wd-button custom-class="!h-108rpx !text-36rpx leading-36rpx !rounded-16rpx" block @click="handleSubmit" :loading="btnLoading" loading-color="#000">
|
||||||
|
{{ t('common.continue') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-picker__cell) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
padding: 20rpx 0 !important;
|
||||||
|
|
||||||
|
.wd-picker__value {
|
||||||
|
margin-right: 8rpx !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {z} from "zod";
|
||||||
|
import {useLogicStore} from "@/pages-login/store/module/logic";
|
||||||
|
import {LoginType} from "@/constant/enums";
|
||||||
|
import {appLoginPost} from "@/service";
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {useConfigStore, useUserStore} from "@/store";
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const logicStore = useLogicStore()
|
||||||
|
|
||||||
|
const FormSchema = z.string()
|
||||||
|
.min(1, {message: t('pages-login.prompt.password')})
|
||||||
|
|
||||||
|
function checkForm(): boolean {
|
||||||
|
const validateFormField = FormSchema.safeParse(logicStore.loginForm.password)
|
||||||
|
console.log('validateFormField', validateFormField)
|
||||||
|
if (!validateFormField.success) {
|
||||||
|
const formatted = validateFormField.error.format()
|
||||||
|
|
||||||
|
R.path(['_errors', 0], formatted) && uni.showToast({
|
||||||
|
title: R.path(['_errors', 0], formatted),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateFormField.success;
|
||||||
|
}
|
||||||
|
const btnLoading = ref(false)
|
||||||
|
async function submit() {
|
||||||
|
btnLoading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
password: logicStore.loginForm.password,
|
||||||
|
type: logicStore.loginForm.type === 'email' ? LoginType.EMAIL : LoginType.ACCOUNT,
|
||||||
|
userPort: Config.userPort, // 商户端登录
|
||||||
|
areaCode: logicStore.loginForm.areaCode,
|
||||||
|
}
|
||||||
|
logicStore.loginForm.type === 'email' ? params.email = logicStore.loginForm.email : params.phone = logicStore.loginForm.phone
|
||||||
|
console.log('params42', params)
|
||||||
|
const res = await appLoginPost({
|
||||||
|
body: params,
|
||||||
|
})
|
||||||
|
btnLoading.value = false
|
||||||
|
console.log('res', res)
|
||||||
|
await uni.showToast({title: t('pages-login.login-successfully'), icon: "none"});
|
||||||
|
userStore.token = res.data.token;
|
||||||
|
nextTick(() => {
|
||||||
|
userStore.getUserInfo();
|
||||||
|
})
|
||||||
|
uni.switchTab(
|
||||||
|
{
|
||||||
|
url: Config.indexPath
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
btnLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = R.when(checkForm, debounce(Config.debounceLongTime, submit, {
|
||||||
|
atBegin: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
logicStore.resetRegisterForm()
|
||||||
|
uni.navigateTo({url});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<navbar/>
|
||||||
|
<view class="pt-60rpx px-27rpx">
|
||||||
|
<view class="text-50rpx lh-50rpx text-#14181B font-bold">
|
||||||
|
{{ t('pages-login.login.title') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-30rpx text-28rpx lh-28rpx text-#999 font-bold">
|
||||||
|
{{ t('pages-login.login.description') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-80rpx">
|
||||||
|
<view class="">
|
||||||
|
<view class="text-32rpx lh-32rpx text-#14181B">{{ t("pages-login.sign-up.password") }}</view>
|
||||||
|
<view
|
||||||
|
class="mt-24rpx flex px-30rpx items-center h-98rpx border-2rpx border-solid border-#D4D4D4 rounded-20rpx">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.loginForm.password"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="20"
|
||||||
|
:placeholder="t('common.enterPassword')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1"
|
||||||
|
no-border
|
||||||
|
placeholderStyle="font-size: 32rpx;color: #999;"
|
||||||
|
show-password
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-40rpx">
|
||||||
|
<wd-button block custom-class="!h-108rpx !text-36rpx font-bold !rounded-16rpx" @click="handleSubmit" :loading="btnLoading" loading-color="#000">
|
||||||
|
{{ t('pages-login.login.title') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view class="flex-center-sb mt-60rpx">
|
||||||
|
<text class="text-32rpx text-#333 underline" @click="navigateTo('/pages-login/pages/sign-up/index')">
|
||||||
|
{{ t('pages-login.sign-up.title') }}
|
||||||
|
</text>
|
||||||
|
<text class="text-32rpx text-#CE7138 lh-34rpx underline"
|
||||||
|
@click="navigateTo('/pages-login/pages/forget-password/index')">
|
||||||
|
{{ t('pages-login.login.forgotPassword') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import {z} from "zod";
|
||||||
|
import {useLogicStore} from "@/pages-login/store/module/logic";
|
||||||
|
import {Agreement} from "@/constant/enums";
|
||||||
|
import {debounce} from "throttle-debounce";
|
||||||
|
import Config from "@/config";
|
||||||
|
import VerificationCode from "../../components/verification-code.vue";
|
||||||
|
import {appUserRegisterPost} from "@/service";
|
||||||
|
import {useConfigStore, useUserStore} from "@/store";
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
const logicStore = useLogicStore()
|
||||||
|
|
||||||
|
const areaCode = ref<string>(logicStore.registerForm.areaCode || '+1');
|
||||||
|
const columns = ref<string[]>(Config.phoneCodeList);
|
||||||
|
|
||||||
|
const isAgreed = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
const FormSchema = z.object({
|
||||||
|
firstName: z.string().min(1, {message: t('pages-login.index.prompt.first-name')}),
|
||||||
|
surname: z.string().min(1, {message: t('pages-login.index.prompt.last-name')}),
|
||||||
|
phone: z.string().min(1, {message: t('pages-login.index.prompt.phone-number')}).regex(/^\d{6,11}$/, {message: t('pages-login.index.prompt.phone-number-verify')}),
|
||||||
|
loginPwd: z.string().min(1, {message: t('pages-login.index.prompt.password')}),
|
||||||
|
email: z.string().email({message: t('pages-login.index.prompt.email-address-verify')}),
|
||||||
|
confirmEmail: z.string().email({message: t('pages-login.index.prompt.email-address-verify')}),
|
||||||
|
}).refine((data) => data.email === data.confirmEmail, {
|
||||||
|
path: ['confirmEmail'],
|
||||||
|
message: t('pages-login.index.prompt.confirm-email-verify')
|
||||||
|
})
|
||||||
|
|
||||||
|
function checkForm(): boolean {
|
||||||
|
const validateFormField = FormSchema.safeParse(logicStore.registerForm)
|
||||||
|
if (!validateFormField.success) {
|
||||||
|
const fieldErrorMessage = validateFormField.error.flatten().fieldErrors
|
||||||
|
const errorMessage: string | undefined = R.path([0, 0], R.values(fieldErrorMessage))
|
||||||
|
errorMessage &&
|
||||||
|
uni.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} else if (!isAgreed.value) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `${t('common.prompt.please-carefully-read-and-agree')} ${t('agreement.user-terms-conditions')}${t('pages-login.and')}${t('agreement.privacy-policy')}`,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return validateFormField.success && isAgreed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const verificationCodeRef = ref()
|
||||||
|
const submit = () => {
|
||||||
|
// verificationCodeRef.value.onOpen()
|
||||||
|
codeSubmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码提交
|
||||||
|
const btnLoading = ref(false)
|
||||||
|
function codeSubmit() {
|
||||||
|
btnLoading.value = true
|
||||||
|
// console.log(data)
|
||||||
|
appUserRegisterPost({
|
||||||
|
body: {
|
||||||
|
...logicStore.registerForm,
|
||||||
|
phone: logicStore.registerForm.phone,
|
||||||
|
areaCode: areaCode.value,
|
||||||
|
// captcha: data.code,
|
||||||
|
// uuid: data.uuid,
|
||||||
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
userStore.token = res.data.token;
|
||||||
|
logicStore.reset()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages-login.sign-up.register-success'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
nextTick(() => {
|
||||||
|
userStore.getUserInfo();
|
||||||
|
uni.navigateTo({
|
||||||
|
url: Config.guidePath
|
||||||
|
})
|
||||||
|
// uni.redirectTo({
|
||||||
|
// url: '/pages-login/pages/guide-page/location'
|
||||||
|
// })
|
||||||
|
// const pages = getCurrentPages()
|
||||||
|
// if(configStore.isShowedGuidePage) {
|
||||||
|
// if(pages.length > 2) {
|
||||||
|
// uni.navigateBack({delta: 2})
|
||||||
|
// } else {
|
||||||
|
// uni.switchTab({
|
||||||
|
// url: Config.indexPath
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// uni.navigateTo({
|
||||||
|
// url: Config.guidePath
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
}).finally(() => {
|
||||||
|
btnLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = R.when(checkForm, debounce(Config.debounceLongTime, submit, {
|
||||||
|
atBegin: true
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({url});
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailIsReadonly = ref(false)
|
||||||
|
const phoneIsReadonly = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
const {email, type} = logicStore.registerForm
|
||||||
|
if (type === 'phone') {
|
||||||
|
// phoneIsReadonly.value = true
|
||||||
|
}
|
||||||
|
if (type === 'email') {
|
||||||
|
// emailIsReadonly.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<navbar/>
|
||||||
|
<view class="pt-60rpx px-27rpx pb-80rpx">
|
||||||
|
<view class="mb-60rpx text-50rpx leading-50rpx font-bold text-#14181B">
|
||||||
|
{{ t('pages-login.sign-up.title') }}
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<!-- 邮箱 -->
|
||||||
|
<view class="">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{ t("common.email") }}</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.email"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="40"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
:readonly="emailIsReadonly"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- Confirm email -->
|
||||||
|
<view class="mt-36rpx">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{
|
||||||
|
t("pages-login.sign-up.confirm-email")
|
||||||
|
}}
|
||||||
|
</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.confirmEmail"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="40"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
:readonly="emailIsReadonly"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- Password -->
|
||||||
|
<view class="mt-36rpx ">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{ t("pages-login.sign-up.password") }}</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.loginPwd"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="20"
|
||||||
|
:placeholder="t('common.enterPassword')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
showPassword
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- First name -->
|
||||||
|
<view class="mt-36rpx ">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{ t("pages-login.sign-up.first-name") }}</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.firstName"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="40"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- Last name -->
|
||||||
|
<view class="mt-36rpx ">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{ t("pages-login.sign-up.last-name") }}</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.surname"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="40"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- Phone number -->
|
||||||
|
<view class="mt-36rpx ">
|
||||||
|
<view class="text-32rpx leading-32rpx text-#14181B mb-24rpx">{{
|
||||||
|
t("pages-login.sign-up.phone-number")
|
||||||
|
}}
|
||||||
|
</view>
|
||||||
|
<view class="border-color px-30rpx flex items-center bg-#EFEFEF">
|
||||||
|
<view class="pr-14rpx text-28rpx">
|
||||||
|
<wd-picker v-model="areaCode" :columns="columns"/>
|
||||||
|
</view>
|
||||||
|
<wd-input
|
||||||
|
v-model.trim="logicStore.registerForm.phone"
|
||||||
|
:cursorSpacing="20"
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:maxlength="40"
|
||||||
|
:placeholder="t('common.enter')"
|
||||||
|
:readonly="phoneIsReadonly"
|
||||||
|
clearable
|
||||||
|
custom-class="flex-1 !bg-transparent"
|
||||||
|
no-border
|
||||||
|
placeholderClass="!text-#999 !text-32rpx"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-54rpx">
|
||||||
|
<wd-button block custom-class="!h-108rpx !text-36rpx text-#fff font-bold !rounded-16rpx"
|
||||||
|
@click="handleSubmit" :loading="btnLoading" loading-color="#000">
|
||||||
|
{{ t('pages-login.sign-up.title') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view class="mt-36rpx flex items-start text-28rpx text-primary lh-42rpx" @click="isAgreed = !isAgreed">
|
||||||
|
<view class="shrink-0 center py-5rpx px-10rpx">
|
||||||
|
<image v-show="isAgreed" class=" w-28rpx h-28rpx"
|
||||||
|
src="@img-login/101.png"></image>
|
||||||
|
<image v-show="!isAgreed" class=" w-28rpx h-28rpx"
|
||||||
|
src="@img-login/100.png"></image>
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<text class="">{{ t('pages-login.continuing-agree') }}</text>
|
||||||
|
<text class="text-#00A76D"
|
||||||
|
@click.stop="navigateTo(`/pages/agreement/index?code=${Agreement.USER_AGREEMENT}`)">
|
||||||
|
{{ t('agreement.user-terms-conditions') }}、
|
||||||
|
</text>
|
||||||
|
<text class="text-#00A76D"
|
||||||
|
@click.stop="navigateTo(`/pages/agreement/index?code=${Agreement.PRIVACY_POLICY}`)">
|
||||||
|
{{ t('agreement.privacy-policy') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
<verification-code ref="verificationCodeRef" @submit="codeSubmit"/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-color {
|
||||||
|
height: 98rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx solid #D4D4D4;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-input__clear) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-input__icon) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-picker__cell) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
|
||||||
|
.wd-picker__value {
|
||||||
|
margin-right: 8rpx !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-picker-view-column__item) {
|
||||||
|
line-height: 94rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.uni-picker-view-indicator) {
|
||||||
|
height: 94rpx !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import {http} from '@/utils/http'
|
||||||
|
|
||||||
|
// 获取图片验证码
|
||||||
|
export const getCaptcha = () => http.get<Record<string, any>>('/auth/code')
|
||||||
|
|
||||||
|
// 根据邮箱查询用户信息
|
||||||
|
export const getUserInfoByEmail = (data: Record<string, any>) => http.post<Record<string, any>>('/app/user/getByEmail', data)
|
||||||
|
|
||||||
|
// 根据手机号查询用户信息
|
||||||
|
export const getByPhone = (data: Record<string, any>) => http.post<Record<string, any>>('/app/user/getByPhone', data)
|
||||||
|
|
||||||
|
// 校验手机号是否被使用
|
||||||
|
export const checkPhoneUnique = (data: Record<string, any>) => http.post<Record<string, any>>('/app/user/checkPhoneUnique', data)
|
||||||
|
|
||||||
|
// 短信验证码
|
||||||
|
export const smsSend = (data: Record<string, any>) => http.post('/app/sms/send', data)
|
||||||
|
|
||||||
|
|
||||||
|
// 邮箱注册
|
||||||
|
export const register = (data: Record<string, any>) => http.post<{
|
||||||
|
token: string
|
||||||
|
}>('/app/user/register', data)
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
export const login = (data: Record<string, any>) =>
|
||||||
|
http.post<{
|
||||||
|
token: string
|
||||||
|
}>('/app/login', data)
|
||||||
|
|
||||||
|
|
||||||
|
// 忘记密码
|
||||||
|
export const forgetPwd = (data: Record<string, any>) => http.post<{
|
||||||
|
token: string
|
||||||
|
}>('/app/user/forgetPwd', data)
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 338 B |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,363 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {login} from "@/pages-login/service";
|
||||||
|
import {useUserStore} from "@/store";
|
||||||
|
import * as R from "ramda";
|
||||||
|
import Config from "@/config";
|
||||||
|
import {LoginType} from "@/constant/enums";
|
||||||
|
|
||||||
|
|
||||||
|
export const useLogicStore = defineStore('login-logic', () => {
|
||||||
|
const {defaultAreaCode} = useAreaCode()
|
||||||
|
const registerForm = ref({
|
||||||
|
type: "",
|
||||||
|
email: '',
|
||||||
|
confirmEmail: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
phone: '',
|
||||||
|
loginPwd: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
captcha: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginForm = ref({
|
||||||
|
type: "",
|
||||||
|
email: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
tripartiteLoginIdentify: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const forgetPasswordForm = ref({
|
||||||
|
email: '',
|
||||||
|
loginPwd: '',
|
||||||
|
captcha: "",
|
||||||
|
phone: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
registerForm.value = {
|
||||||
|
email: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
phone: '',
|
||||||
|
loginPwd: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
captcha: "",
|
||||||
|
}
|
||||||
|
loginForm.value = {
|
||||||
|
type: "",
|
||||||
|
email: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
tripartiteLoginIdentify: '',
|
||||||
|
}
|
||||||
|
forgetPasswordForm.value = {
|
||||||
|
email: '',
|
||||||
|
loginPwd: '',
|
||||||
|
captcha: "",
|
||||||
|
phone: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRegisterForm() {
|
||||||
|
registerForm.value = {
|
||||||
|
type: "",
|
||||||
|
email: '',
|
||||||
|
confirmEmail: '',
|
||||||
|
firstName: '',
|
||||||
|
surname: '',
|
||||||
|
phone: '',
|
||||||
|
loginPwd: '',
|
||||||
|
areaCode: defaultAreaCode.value,
|
||||||
|
captcha: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
function appleLogin() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'apple',
|
||||||
|
success: (res) => {
|
||||||
|
uni.getUserInfo({
|
||||||
|
provider: 'apple',
|
||||||
|
success: async (info) => {
|
||||||
|
// 获取用户信息成功, info.authResult保存用户信息
|
||||||
|
console.log('info', info)
|
||||||
|
const params = {
|
||||||
|
type: LoginType.APPLE,
|
||||||
|
email: info.userInfo?.email || '',
|
||||||
|
tripartiteLoginIdentify: info?.userInfo?.openId ?? '',
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginRes = await login(params)
|
||||||
|
console.log('loginRes', loginRes)
|
||||||
|
userStore.token = loginRes.data.token
|
||||||
|
nextTick(() => {
|
||||||
|
userStore.getUserInfo();
|
||||||
|
})
|
||||||
|
await uni.showToast({title: t('pages-login.login-successfully'), icon: "none"});
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
setTimeout(R.ifElse(() => pages.length > 1, () => uni.navigateBack({delta: 1}), () => uni.switchTab(
|
||||||
|
{
|
||||||
|
url: Config.indexPath
|
||||||
|
}
|
||||||
|
)), 1000);
|
||||||
|
resolve(true)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log('err', err)
|
||||||
|
if (R.equals(+err.code, 711)) {
|
||||||
|
registerForm.value = {
|
||||||
|
email: info.userInfo?.email || '',
|
||||||
|
areaCode: '',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
type: LoginType.APPLE,
|
||||||
|
tripartiteLoginIdentify: info?.userInfo?.openId ?? '',
|
||||||
|
}
|
||||||
|
navigateTo('/pages-login/pages/sign-up/index')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
// 登录授权失败
|
||||||
|
uni.showToast({title: t('common.appleLoginFailed'), icon: 'none'})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function facebookLogin() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'facebook',
|
||||||
|
success: (res) => {
|
||||||
|
uni.getUserInfo({
|
||||||
|
provider: 'facebook',
|
||||||
|
success: async (info) => {
|
||||||
|
// 获取用户信息成功, info.authResult保存用户信息
|
||||||
|
console.log('info', info)
|
||||||
|
const params = {
|
||||||
|
userType: 1,
|
||||||
|
type: LoginType.FACEBOOK,
|
||||||
|
openid: info?.userInfo?.openId ?? '',
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const loginRes = await login(params)
|
||||||
|
console.log('loginRes', loginRes)
|
||||||
|
userStore.token = loginRes.data.token
|
||||||
|
resolve(true)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log('err', err)
|
||||||
|
if (R.equals(+err.code, 711)) {
|
||||||
|
loginForm.value = {
|
||||||
|
email: info?.authResult?.email ?? '',
|
||||||
|
areaCode: '',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
type: LoginType.FACEBOOK,
|
||||||
|
tripartiteLoginIdentify: info.authResult.user ?? '',
|
||||||
|
}
|
||||||
|
navigateTo('/pages-login/pages/enter-phone-number/index')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
// 登录授权失败
|
||||||
|
uni.showToast({title: '登录授权失败', icon: 'none'})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function googleLogin() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'google',
|
||||||
|
success: (res) => {
|
||||||
|
uni.getUserInfo({
|
||||||
|
provider: 'google',
|
||||||
|
success: async (info) => {
|
||||||
|
// 获取用户信息成功, info.authResult保存用户信息
|
||||||
|
console.log('info', info)
|
||||||
|
const params = {
|
||||||
|
userType: 1,
|
||||||
|
type: LoginType.GOOGLE,
|
||||||
|
email: info.userInfo.email,
|
||||||
|
tripartiteLoginIdentify: info.userInfo.openid,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const loginRes = await login(params)
|
||||||
|
console.log('loginRes', loginRes)
|
||||||
|
userStore.token = loginRes.data.token
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
userStore.getUserInfo();
|
||||||
|
})
|
||||||
|
await uni.showToast({title: t('pages-login.login-successfully'), icon: "none"});
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
setTimeout(R.ifElse(() => pages.length > 1, () => uni.navigateBack({delta: 1}), () => uni.switchTab(
|
||||||
|
{
|
||||||
|
url: Config.indexPath
|
||||||
|
}
|
||||||
|
)), 1000);
|
||||||
|
|
||||||
|
resolve(true)
|
||||||
|
} catch (err: any) {
|
||||||
|
if (R.equals(+err.code, 711)) {
|
||||||
|
registerForm.value = {
|
||||||
|
email: info.userInfo.email,
|
||||||
|
areaCode: '+1',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
type: LoginType.GOOGLE,
|
||||||
|
tripartiteLoginIdentify: info.userInfo.openid,
|
||||||
|
}
|
||||||
|
navigateTo('/pages-login/pages/sign-up/index')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
// 登录授权失败
|
||||||
|
uni.showToast({title: t('common.googleLoginFailed'), icon: 'none'})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testGoogleLogin() {
|
||||||
|
let info = {
|
||||||
|
"headimgurl": "https://lh3.googleusercontent.com/a/ACg8ocLQF07FdGLYzv0wrEmPKb9v0_owckcC6I-YoX7YJY_rs8rQlaw",
|
||||||
|
"nickname": "gaofushuai",
|
||||||
|
"unionid": "101877608870663900115",
|
||||||
|
"openid": "101877608870663955115",
|
||||||
|
"email": "gfs0022097@gmail.com",
|
||||||
|
"openId": "101877608870663900215",
|
||||||
|
"nickName": "gaofushuai",
|
||||||
|
"avatarUrl": "https://lh3.googleusercontent.com/a/ACg8ocLQF07FdGLYzv0wrEmPKb9v0_owckcC6I-YoX7YJY_rs8rQlaw"
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
userType: 1,
|
||||||
|
// 1 验证码 2 密码 3 一键登录 4 微信登录 5 小程序登录 6 苹果登录
|
||||||
|
type: LoginType.GOOGLE,
|
||||||
|
email: info.email,
|
||||||
|
tripartiteLoginIdentify: info.openid,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginRes = await login(params)
|
||||||
|
|
||||||
|
console.log('testGoogleLogin', loginRes)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log('err22222222222', err)
|
||||||
|
if (R.equals(+err.code, 711)) {
|
||||||
|
registerForm.value = {
|
||||||
|
areaCode: '',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
password: '',
|
||||||
|
type: LoginType.GOOGLE,
|
||||||
|
email: info.email,
|
||||||
|
tripartiteLoginIdentify: info.openid,
|
||||||
|
}
|
||||||
|
navigateTo('/pages-login/pages/sign-up/index')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wechatLogin() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: (res) => {
|
||||||
|
const {code} = res
|
||||||
|
uni.getUserInfo({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: async (info) => {
|
||||||
|
// 获取用户信息成功, info.authResult保存用户信息
|
||||||
|
console.log('info', info)
|
||||||
|
const params = {
|
||||||
|
userType: 1,
|
||||||
|
// 1 验证码 2 密码 3 一键登录 4 微信登录 5 小程序登录 6 苹果登录
|
||||||
|
type: 6,
|
||||||
|
openid: info?.userInfo?.openId ?? '',
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const loginRes = await login(params)
|
||||||
|
console.log('loginRes', loginRes)
|
||||||
|
userStore.token = loginRes.data.token
|
||||||
|
resolve(true)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.log('err', err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.log('err', err)
|
||||||
|
// 登录授权失败
|
||||||
|
uni.showToast({title: '微信登录授权失败', icon: 'none'})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
registerForm,
|
||||||
|
loginForm,
|
||||||
|
forgetPasswordForm,
|
||||||
|
reset,
|
||||||
|
appleLogin,
|
||||||
|
facebookLogin,
|
||||||
|
googleLogin,
|
||||||
|
wechatLogin,
|
||||||
|
resetRegisterForm,
|
||||||
|
testGoogleLogin,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {getMarketingDishList} from "@/pages-store/service";
|
||||||
|
import {appMarketActivityListPost} from "@/service";
|
||||||
|
import {thumbnailImg} from "@/utils/utils";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function getList(pageNum: number, pageSize: number) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
getMarketingDishList(props.id).then(res => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const {paging, loading, firstLoaded, dataList, queryList} = usePage(getList)
|
||||||
|
|
||||||
|
onLoad(()=> {
|
||||||
|
// appMarketActivityListPost({
|
||||||
|
// options: {
|
||||||
|
// pageNum: 1,
|
||||||
|
// pageSize: 10,
|
||||||
|
// },
|
||||||
|
// }).then(res=> {
|
||||||
|
// console.log('', res)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleClickDish(item: any) {
|
||||||
|
navigateTo(`/pages-store/pages/store/dishes?id=${item.id}&storeId=${item.merchantId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateTo(url: string) {
|
||||||
|
uni.navigateTo({ url })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<z-paging
|
||||||
|
ref="paging"
|
||||||
|
v-model="dataList"
|
||||||
|
@query="queryList"
|
||||||
|
bg-color="#fff"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<navbar />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx px-32rpx">
|
||||||
|
<template v-for="(item,index) in dataList">
|
||||||
|
<view @click="handleClickDish(item)" class="w-330rpx overflow-hidden">
|
||||||
|
<image
|
||||||
|
:src="thumbnailImg(item?.dishImage?.split(',')[0])"
|
||||||
|
class="w-full h-186rpx rounded-24rpx mb-16rpx"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
||||||
|
>{{ item.dishName }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</z-paging>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import usePage from "@/hooks/usePage";
|
||||||
|
import {appMerchantRecommendListPost} from "@/service";
|
||||||
|
import {useUserStore} from "@/store";
|
||||||
|
import FiltrateTool from "@/components/filtrate-tool/index.vue";
|
||||||
|
import PriceChoose from "@/components/filtrate-tool/components/price-choose.vue";
|
||||||
|
import Score from "@/components/filtrate-tool/components/score.vue";
|
||||||
|
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
merchantCategoryIds: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const {paging, dataList, queryList} = usePage(getList)
|
||||||
|
function getList(pageNum: number, pageSize: number) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
appMerchantRecommendListPost({
|
||||||
|
params: {
|
||||||
|
pageNum: pageNum,
|
||||||
|
pageSize: pageSize,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
lat: userStore.userLocation.latitude,
|
||||||
|
lng: userStore.userLocation.longitude,
|
||||||
|
selfPickup: selfPickup.value, // 是否自提
|
||||||
|
discount: discount.value, // 是否有折扣 1是 2 否
|
||||||
|
scoreRange: scoreRange.value || null, // 评分范围 比如 3-4
|
||||||
|
priceRange: price.value || null, // 价格范围 比如 10-30
|
||||||
|
merchantCategoryIds: props.merchantCategoryIds ? [props.merchantCategoryIds] : [], // 商家分类id集合(首页中间)
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
console.log(res)
|
||||||
|
resolve({rows: res.rows})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否自提
|
||||||
|
const selfPickup = ref<number | null>(null)
|
||||||
|
function togglePickup(value: number) {
|
||||||
|
selfPickup.value = value;
|
||||||
|
paging.value.refresh()
|
||||||
|
}
|
||||||
|
// 是否有折扣
|
||||||
|
const discount = ref<number | null>(null)
|
||||||
|
function toggleDiscount(value: number) {
|
||||||
|
discount.value = value;
|
||||||
|
paging.value.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
const scoreRef = ref<any>()
|
||||||
|
const scoreRange = ref<string | null>(null)
|
||||||
|
const priceChooseRef = ref<any>()
|
||||||
|
const price = ref<string | null>(null)
|
||||||
|
function toggleScore() {
|
||||||
|
if (scoreRef.value) {
|
||||||
|
scoreRef.value.onOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function togglePrice() {
|
||||||
|
if (priceChooseRef.value) {
|
||||||
|
priceChooseRef.value.onOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyScore(value: string) {
|
||||||
|
scoreRange.value = value;
|
||||||
|
}
|
||||||
|
function applyPrice(value: string) {
|
||||||
|
price.value = value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<z-paging ref="paging" v-model="dataList" @query="queryList">
|
||||||
|
<template #top>
|
||||||
|
<view class="bg-white pb-24rpx">
|
||||||
|
<navbar :title="t('pages.home.featured-on')"/>
|
||||||
|
<!-- 筛选工具 -->
|
||||||
|
<filtrate-tool class="mt-32rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="toggleScore" @togglePrice="togglePrice" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<view class="p-30rpx">
|
||||||
|
<template v-for="(item, index) in dataList" :key="index">
|
||||||
|
<food-box :item="item" />
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</z-paging>
|
||||||
|
|
||||||
|
<score @applyScore="applyScore" ref="scoreRef" />
|
||||||
|
<price-choose @applyPrice="applyPrice" ref="priceChooseRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {appMerchantRecommendListPost} from "@/service";
|
||||||
|
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
||||||
|
import {useUserStore} from "@/store";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function getList(pageNum: number, pageSize: number) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
appMerchantRecommendListPost({
|
||||||
|
params: {
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
lat: userStore.userLocation.latitude,
|
||||||
|
lng: userStore.userLocation.longitude,
|
||||||
|
selfPickup: null, // 是否自提
|
||||||
|
discount: null, // 是否有折扣 1是 2 否
|
||||||
|
scoreRange: null, // 评分范围 比如 3-4
|
||||||
|
priceRange: null, // 价格范围 比如 10-30
|
||||||
|
merchantCategoryIds: [],
|
||||||
|
merchantLabelIds: props.id ? [props.id] : []
|
||||||
|
},
|
||||||
|
}).then(res => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const {paging, loading, firstLoaded, dataList, queryList} = usePage(getList)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<z-paging
|
||||||
|
ref="paging"
|
||||||
|
v-model="dataList"
|
||||||
|
@query="queryList"
|
||||||
|
bg-color="#fff"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<navbar />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view class="p-32rpx">
|
||||||
|
<template v-for="(item, index) in dataList" :key="index">
|
||||||
|
<food-box :item="item" />
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</z-paging>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
onMounted,
|
||||||
|
getCurrentInstance
|
||||||
|
} from 'vue';
|
||||||
|
import Search from "@/pages/home/components/tabbar-home/components/search.vue";
|
||||||
|
import useEventEmit from "@/hooks/useEventEmit";
|
||||||
|
import {EventEnum} from "@/constant/enums";
|
||||||
|
const { t } = useI18n();
|
||||||
|
import {useAddressStore} from "@/pages/address/store/address";
|
||||||
|
import {appUserAddressListPost} from "@/service";
|
||||||
|
const addressStore = useAddressStore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const addressesList = ref([])
|
||||||
|
const currentAddressId = ref('')
|
||||||
|
function getAddressList() {
|
||||||
|
appUserAddressListPost({
|
||||||
|
params: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
console.log('获取用户地址列表', res)
|
||||||
|
addressesList.value = res.rows
|
||||||
|
if(res.rows.length > 0) {
|
||||||
|
currentAddressId.value = res.rows[0].id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleClickSearch() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-user/pages/search-address/index',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEventEmit(EventEnum.CHOOSE_ADDRESS, (data) => {
|
||||||
|
console.log('搜索的地址信息', data)
|
||||||
|
if(data) {
|
||||||
|
addressStore.setAddressLocation({
|
||||||
|
displayName: data.displayName,
|
||||||
|
formattedAddress: data.formattedAddress,
|
||||||
|
longitude: data.location.lng,
|
||||||
|
latitude: data.location.lat,
|
||||||
|
})
|
||||||
|
setTimeout(()=> {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/address/choose-type'
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onShow(()=> {
|
||||||
|
getAddressList()
|
||||||
|
})
|
||||||
|
onLoad((options: any)=> {
|
||||||
|
if(options.id) {
|
||||||
|
currentAddressId.value = options.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function chooseAddress(item: any) {
|
||||||
|
if(String(item.id) === String(currentAddressId.value)) return
|
||||||
|
currentAddressId.value = item.id
|
||||||
|
eventChannel.emit('acceptDataFromOpenedPage', {
|
||||||
|
data: currentAddressId.value,
|
||||||
|
deliveryRemark: item.deliveryRemark,
|
||||||
|
deliveryType: item.deliveryType,
|
||||||
|
});
|
||||||
|
setTimeout(()=> {
|
||||||
|
uni.navigateBack()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = null
|
||||||
|
let eventChannel = null
|
||||||
|
onMounted(()=> {
|
||||||
|
instance = getCurrentInstance().proxy
|
||||||
|
eventChannel = instance.getOpenerEventChannel();
|
||||||
|
})
|
||||||
|
onUnload(()=> {
|
||||||
|
instance = null
|
||||||
|
eventChannel = null
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<navbar :title="t('pages.address.title')" />
|
||||||
|
<view class="mt-32rpx px-30rpx">
|
||||||
|
<search :is-auto-jump="false" @clickSearch="handleClickSearch" />
|
||||||
|
</view>
|
||||||
|
<view class="mt-64rpx text-40rpx lh-40rpx text-#333 font-bold pl-30rpx pb-24rpx">
|
||||||
|
{{ t('pages.address.savedAddresses') }}
|
||||||
|
</view>
|
||||||
|
<template v-for="item in addressesList">
|
||||||
|
<view @click="chooseAddress(item)" :class="[String(item.id) === String(currentAddressId) ? 'bg-#F3F3F3' : '']" class="w-full h-156rpx flex-center-sb px-30rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<image v-if="String(item.id) === String(currentAddressId)" src="@img/chef/143.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>
|
||||||
|
<image v-else src="@img/chef/145.png" class="w-44rpx h-44rpx shrink-0 mr-28rpx"></image>
|
||||||
|
<view class="flex-1 h-156rpx pt-40rpx">
|
||||||
|
<view class="text-32rpx lh-32rpx text-#333 font-500 mb-16rpx line-clamp-1">{{ item.formattedAddress }}</view>
|
||||||
|
<view class="text-28rpx lh-28rpx text-#6D6D6D">{{ item.displayName || '' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
:src="
|
||||||
|
String(item.id) === String(currentAddressId)
|
||||||
|
? '/static/images/chef/133.png'
|
||||||
|
: '/static/images/chef/134.png'
|
||||||
|
"
|
||||||
|
class="w-44rpx h-44rpx shrink-0 pl-30rpx"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-if="addressesList.length === 0">
|
||||||
|
<view class="py-100rpx center">
|
||||||
|
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
import {useConfigStore} from "@/store";
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const emit = defineEmits(['confirm']);
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
|
||||||
|
const sortOptions = [
|
||||||
|
{
|
||||||
|
label: t('pages-store.store.cancelOrder.informationError'),
|
||||||
|
value: 'time'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('pages-store.store.cancelOrder.forgotCoupon'),
|
||||||
|
value: 'browse'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('pages-store.store.cancelOrder.wrongLess'),
|
||||||
|
value: 'thumbsUp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('pages-store.store.cancelOrder.dontWant'),
|
||||||
|
value: 'comment'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const currentSort = ref(0);
|
||||||
|
function handleClick(index: number) {
|
||||||
|
// show.value = false;
|
||||||
|
currentSort.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmCancel() {
|
||||||
|
console.log('取消订单', sortOptions[currentSort.value].label)
|
||||||
|
emit('confirm', sortOptions[currentSort.value].label);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpen() {
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
onOpen,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
v-model="show"
|
||||||
|
position="bottom"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<view class="pl-30rpx pt-48rpx relative">
|
||||||
|
<image
|
||||||
|
@click="handleClose"
|
||||||
|
src="@img/chef/100404.png"
|
||||||
|
class="w-28rpx h-28rpx absolute top-30rpx right-30rpx"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
<view class="text-36rpx lh-36rpx text-#333 font-bold text-center mb-68rpx">{{ t('common.cancel') }}</view>
|
||||||
|
<template v-for="(item, index) in sortOptions">
|
||||||
|
<view @click="handleClick(index)" class="flex items-center mb-42rpx last:mb-0">
|
||||||
|
<view class="w-48rpx h-48rpx shrink-0 mr-20rpx">
|
||||||
|
<image
|
||||||
|
:src="
|
||||||
|
index === currentSort
|
||||||
|
? '/static/images/chef/152.png'
|
||||||
|
: '/static/images/chef/134.png'
|
||||||
|
"
|
||||||
|
class="w-40rpx h-40rpx"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="flex-1 text-30rpx lh-30rpx text-#333 flex items-center">{{ item.label }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
<view class="mt-72rpx px-30rpx pb-64rpx">
|
||||||
|
<wd-button @click="confirmCancel" custom-class="!h-108rpx !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
||||||
|
{{ t('common.submit') }}
|
||||||
|
</wd-button>
|
||||||
|
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useConfigStore } from "@/store";
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
import Config from '@/config'
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm']);
|
||||||
|
const areaCode = ref<string>('+1');
|
||||||
|
const columns = ref<string[]>(Config.phoneCodeList);
|
||||||
|
const localPhoneValue = ref('');
|
||||||
|
|
||||||
|
function confirmRide() {
|
||||||
|
emit('confirm', {
|
||||||
|
phone: localPhoneValue.value,
|
||||||
|
areaCode: areaCode.value,
|
||||||
|
})
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
function onOpen(data: {phone: string, areaCode: string}) {
|
||||||
|
if(data.phone){
|
||||||
|
localPhoneValue.value = data.phone;
|
||||||
|
}
|
||||||
|
if(data.areaCode){
|
||||||
|
areaCode.value = data.areaCode;
|
||||||
|
}
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
onOpen,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<wd-popup v-model="show" position="bottom" @close="handleClose">
|
||||||
|
<view class="w-full px-30rpx pt-58rpx">
|
||||||
|
<view class="">
|
||||||
|
<view class="flex items-center h-108rpx pr-28rpx rounded-12rpx b-1rpx bg-#F6F6F6">
|
||||||
|
<view class="pr-14rpx text-28rpx">
|
||||||
|
<wd-picker :columns="columns" v-model="areaCode" />
|
||||||
|
</view>
|
||||||
|
<wd-input v-model="localPhoneValue" @confirm="confirmRide" :placeholder="t('components.placeholder')" :no-border="true" placeholder-class="text-#A4A4A4 text-28rpx" custom-class="!w-full !bg-transparent"></wd-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="mt-40rpx">
|
||||||
|
<wd-button
|
||||||
|
@click="confirmRide"
|
||||||
|
custom-class="!h-108rpx !text-36rpx !rounded-16rpx !bg-#14181B"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{{ t('common.save') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.wd-picker__cell) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
.wd-picker__value {
|
||||||
|
margin-right: 8rpx !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.uni-picker-view-wrapper) {
|
||||||
|
& > uni-picker-view-column:first-of-type .uni-picker-view-group {
|
||||||
|
.uni-picker-view-indicator {
|
||||||
|
border-radius: 20rpx 0 0 20rpx !important;
|
||||||
|
&:after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.wd-picker-view-column__item) {
|
||||||
|
line-height: 94rpx !important;
|
||||||
|
}
|
||||||
|
:deep(.uni-picker-view-indicator) {
|
||||||
|
height: 94rpx !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
<template>
|
||||||
|
<view class="checkout-skeleton">
|
||||||
|
<!-- 页面标题骨架 -->
|
||||||
|
<view class="px-30rpx pt-20rpx">
|
||||||
|
<view class="page-title-skeleton skeleton-item mb-52rpx"></view>
|
||||||
|
|
||||||
|
<!-- 配送方式选择骨架 -->
|
||||||
|
<view class="delivery-method-skeleton skeleton-item mb-40rpx"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 地址信息区域骨架 -->
|
||||||
|
<view class="mt-4rpx">
|
||||||
|
<!-- 地址信息项 -->
|
||||||
|
<view v-for="i in 3" :key="i" class="address-item-skeleton flex-center-sb border-bottom py-36rpx px-30rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="address-icon-skeleton skeleton-item mr-28rpx"></view>
|
||||||
|
<view class="flex-1">
|
||||||
|
<view class="address-text-skeleton skeleton-item mb-8rpx"></view>
|
||||||
|
<view class="address-subtext-skeleton skeleton-item w-60%"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="arrow-icon-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 配送时间选择骨架 -->
|
||||||
|
<view class="flex-center-sb py-36rpx px-30rpx gap-22rpx">
|
||||||
|
<view class="delivery-time-card-skeleton skeleton-item"></view>
|
||||||
|
<view class="delivery-time-card-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<view class="divider-skeleton skeleton-item"></view>
|
||||||
|
|
||||||
|
<!-- 订单信息摘要骨架 -->
|
||||||
|
<view class="pt-36rpx pb-36rpx">
|
||||||
|
<view class="px-30rpx">
|
||||||
|
<view class="order-summary-title-skeleton skeleton-item mb-10rpx"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 折叠面板骨架 -->
|
||||||
|
<view class="collapse-skeleton px-30rpx">
|
||||||
|
<view class="collapse-header-skeleton flex-center-sb py-30rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="store-avatar-skeleton skeleton-item mr-24rpx"></view>
|
||||||
|
<view>
|
||||||
|
<view class="store-name-skeleton skeleton-item mb-16rpx"></view>
|
||||||
|
<view class="store-items-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="collapse-arrow-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品列表骨架 -->
|
||||||
|
<view v-for="i in 2" :key="i" class="order-item-skeleton flex-center-sb pl-46rpx pr-30rpx py-30rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="item-quantity-skeleton skeleton-item mr-40rpx"></view>
|
||||||
|
<view>
|
||||||
|
<view class="item-name-skeleton skeleton-item mb-12rpx"></view>
|
||||||
|
<view class="item-options-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="item-price-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部按钮区域骨架 -->
|
||||||
|
<view class="fixed bottom-0 left-0 right-0 bg-white">
|
||||||
|
<view class="membership-banner-skeleton skeleton-item"></view>
|
||||||
|
<view class="px-30rpx py-18rpx">
|
||||||
|
<view class="checkout-button-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
<view class="safe-bottom-skeleton"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 结账页面骨架屏组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 通用骨架屏样式
|
||||||
|
.skeleton-item {
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闪烁动画
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-skeleton {
|
||||||
|
background-color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用布局类
|
||||||
|
.flex-center-sb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航栏骨架
|
||||||
|
.navbar-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面标题骨架
|
||||||
|
.page-title-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 46rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配送方式选择骨架
|
||||||
|
.delivery-method-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 98rpx;
|
||||||
|
border-radius: 49rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址信息骨架
|
||||||
|
.address-icon-skeleton {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-text-skeleton {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-subtext-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon-skeleton {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配送时间卡片骨架
|
||||||
|
.delivery-time-card-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 152rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
.divider-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 10rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单摘要标题骨架
|
||||||
|
.order-summary-title-skeleton {
|
||||||
|
width: 350rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 折叠面板骨架
|
||||||
|
.store-avatar-skeleton {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-items-skeleton {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-arrow-skeleton {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单项骨架
|
||||||
|
.item-quantity-skeleton {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name-skeleton {
|
||||||
|
width: 250rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-options-skeleton {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-price-skeleton {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优惠券骨架
|
||||||
|
.coupon-icon-skeleton {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-text-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小费选择骨架
|
||||||
|
.tip-icon-skeleton {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-title-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-option-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配送周卡骨架
|
||||||
|
.weekly-title-skeleton {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-option-skeleton {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekly-description-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用明细骨架
|
||||||
|
.cost-label-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-value-skeleton {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-label-skeleton {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value-skeleton {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付方式骨架
|
||||||
|
.payment-icon-skeleton {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-text-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-type-skeleton {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部区域骨架
|
||||||
|
.membership-banner-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 76rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-button-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 92rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-bottom-skeleton {
|
||||||
|
height: 34rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 750rpx) {
|
||||||
|
.page-title-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-title-skeleton {
|
||||||
|
width: 280rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekly-title-skeleton {
|
||||||
|
width: 250rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
<template>
|
||||||
|
<view class="order-detail-skeleton">
|
||||||
|
<!-- 页面标题和时间信息骨架 -->
|
||||||
|
<view class="px-30rpx pt-20rpx pb-50rpx">
|
||||||
|
<view class="delivery-time-skeleton skeleton-item mb-26rpx"></view>
|
||||||
|
<view class="order-time-skeleton skeleton-item mb-16rpx"></view>
|
||||||
|
<view class="cancel-time-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单进度骨架 -->
|
||||||
|
<view class="mb-52rpx px-30rpx">
|
||||||
|
<view class="progress-container">
|
||||||
|
<!-- 进度条背景 -->
|
||||||
|
<view class="progress-bg-skeleton skeleton-item"></view>
|
||||||
|
|
||||||
|
<!-- 进度步骤 -->
|
||||||
|
<view class="flex justify-between items-center">
|
||||||
|
<view v-for="i in 5" :key="i" class="flex flex-col items-center">
|
||||||
|
<!-- 步骤圆点 -->
|
||||||
|
<view class="progress-dot-skeleton skeleton-item mb-16rpx"></view>
|
||||||
|
<!-- 步骤标签 -->
|
||||||
|
<view class="progress-label-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<view class="divider-skeleton skeleton-item"></view>
|
||||||
|
|
||||||
|
<!-- 商品列表骨架 -->
|
||||||
|
<view class="px-30rpx py-36rpx">
|
||||||
|
<!-- 商家信息骨架 -->
|
||||||
|
<view class="flex-center-sb h-80rpx mb-36rpx">
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="store-avatar-skeleton skeleton-item mr-24rpx"></view>
|
||||||
|
<view class="store-name-skeleton skeleton-item"></view>
|
||||||
|
<view class="store-icon-skeleton skeleton-item ml-10rpx"></view>
|
||||||
|
</view>
|
||||||
|
<view class="collection-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品项骨架 -->
|
||||||
|
<view v-for="i in 3" :key="i" class="flex items-start mb-32rpx last:mb-0">
|
||||||
|
<!-- 商品图片 -->
|
||||||
|
<view class="product-image-skeleton skeleton-item mr-20rpx"></view>
|
||||||
|
|
||||||
|
<!-- 商品信息 -->
|
||||||
|
<view class="flex-1">
|
||||||
|
<view class="product-name-skeleton skeleton-item mb-20rpx"></view>
|
||||||
|
<view class="flex-center-sb mb-24rpx">
|
||||||
|
<view class="product-specs-skeleton skeleton-item"></view>
|
||||||
|
<view class="product-quantity-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
<view class="product-price-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<view class="divider-skeleton skeleton-item"></view>
|
||||||
|
|
||||||
|
<!-- 配送地址骨架 -->
|
||||||
|
<view class="pt-36rpx">
|
||||||
|
<view class="address-title-skeleton skeleton-item pl-30rpx mb-4rpx"></view>
|
||||||
|
|
||||||
|
<!-- 地址信息项 -->
|
||||||
|
<view v-for="i in 3" :key="i" class="flex items-center py-36rpx px-30rpx border-bottom">
|
||||||
|
<view class="address-icon-skeleton skeleton-item mr-28rpx"></view>
|
||||||
|
<view class="flex-1">
|
||||||
|
<view class="address-text-skeleton skeleton-item mb-8rpx"></view>
|
||||||
|
<view class="address-subtext-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分隔线 -->
|
||||||
|
<view class="divider-skeleton skeleton-item"></view>
|
||||||
|
|
||||||
|
<!-- 订单信息骨架 -->
|
||||||
|
<view class="border-bottom px-30rpx py-36rpx">
|
||||||
|
<view class="order-info-title-skeleton skeleton-item mb-40rpx"></view>
|
||||||
|
|
||||||
|
<!-- 订单信息项 -->
|
||||||
|
<view v-for="i in 5" :key="i" class="flex-center-sb mb-40rpx last:mb-0">
|
||||||
|
<view class="order-info-label-skeleton skeleton-item"></view>
|
||||||
|
<view class="order-info-value-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 费用明细骨架 -->
|
||||||
|
<view class="border-bottom px-30rpx py-32rpx">
|
||||||
|
<!-- 费用项 -->
|
||||||
|
<view v-for="i in 3" :key="i" class="flex-center-sb mb-40rpx last:mb-0">
|
||||||
|
<view class="cost-label-skeleton skeleton-item"></view>
|
||||||
|
<view class="cost-value-skeleton skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部安全区域 -->
|
||||||
|
<view class="safe-bottom-skeleton"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 订单详情页面骨架屏组件
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 通用骨架屏样式
|
||||||
|
.skeleton-item {
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闪烁动画
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-skeleton {
|
||||||
|
background-color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用布局类
|
||||||
|
.flex-center-sb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航栏骨架
|
||||||
|
.navbar-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面标题和时间信息骨架
|
||||||
|
.delivery-time-skeleton {
|
||||||
|
width: 500rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-time-skeleton {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-time-skeleton {
|
||||||
|
width: 400rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单进度骨架
|
||||||
|
.progress-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bg-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin: 18rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-dot-skeleton {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label-skeleton {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
.divider-skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商家信息骨架
|
||||||
|
.store-avatar-skeleton {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-icon-skeleton {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-skeleton {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品信息骨架
|
||||||
|
.product-image-skeleton {
|
||||||
|
width: 136rpx;
|
||||||
|
height: 136rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name-skeleton {
|
||||||
|
width: 400rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-specs-skeleton {
|
||||||
|
width: 250rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-quantity-skeleton {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price-skeleton {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地址信息骨架
|
||||||
|
.address-title-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-icon-skeleton {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-text-skeleton {
|
||||||
|
width: 350rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-subtext-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单信息骨架
|
||||||
|
.order-info-title-skeleton {
|
||||||
|
width: 250rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-label-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-value-skeleton {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用明细骨架
|
||||||
|
.cost-label-skeleton {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost-value-skeleton {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部安全区域
|
||||||
|
.safe-bottom-skeleton {
|
||||||
|
height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 750rpx) {
|
||||||
|
.delivery-time-skeleton {
|
||||||
|
width: 400rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name-skeleton {
|
||||||
|
width: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-text-skeleton {
|
||||||
|
width: 280rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-value-skeleton {
|
||||||
|
width: 150rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 定义步骤类型
|
||||||
|
interface OrderStep {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
steps?: OrderStep[]
|
||||||
|
currentStatus?: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const steps = computed(() => props.steps ?? [
|
||||||
|
{ label: t('orderStatus.ordered'), value: 1 },
|
||||||
|
{ label: t('orderStatus.paid'), value: 2 },
|
||||||
|
{ label: t('orderStatus.received'), value: 3 },
|
||||||
|
{ label: t('orderStatus.delivering'), value: 4 },
|
||||||
|
{ label: t('orderStatus.delivered'), value: 5 },
|
||||||
|
])
|
||||||
|
|
||||||
|
const currentStatus = computed(() => props.currentStatus ?? 1)
|
||||||
|
|
||||||
|
// 计算每个步骤的激活状态
|
||||||
|
const processedSteps = computed(() => {
|
||||||
|
return steps.value.map(step => ({
|
||||||
|
...step,
|
||||||
|
active: currentStatus.value >= step.value
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算进度条的宽度百分比
|
||||||
|
const progressWidth = computed(() => {
|
||||||
|
if (steps.value.length <= 1) return 0
|
||||||
|
|
||||||
|
// 找到当前状态在步骤数组中的位置
|
||||||
|
const currentStepIndex = steps.value.findIndex(step => step.value === props.currentStatus)
|
||||||
|
const activeStepIndex = currentStepIndex >= 0 ? currentStepIndex : -1
|
||||||
|
|
||||||
|
if (activeStepIndex < 0) return 0
|
||||||
|
|
||||||
|
const totalSteps = steps.value.length
|
||||||
|
const width = Math.min(activeStepIndex / (totalSteps - 1) * 100, 100)
|
||||||
|
|
||||||
|
if(width >= 50 && width < 100) {
|
||||||
|
return width - 3
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="order-progress">
|
||||||
|
<view class="relative">
|
||||||
|
<!-- 进度条背景 -->
|
||||||
|
<view class="progress-bg w-full h-12rpx bg-#F2F2F2 rounded-10rpx absolute top-18rpx left-0"></view>
|
||||||
|
|
||||||
|
<!-- 进度条填充 -->
|
||||||
|
<view
|
||||||
|
class="progress-fill h-12rpx bg-#333333 rounded-10rpx absolute top-18rpx left-0 transition-all duration-300"
|
||||||
|
:style="{ width: `${progressWidth}%` }"
|
||||||
|
></view>
|
||||||
|
|
||||||
|
<!-- 进度步骤 -->
|
||||||
|
<view class="flex-center-sb relative z-1">
|
||||||
|
<view
|
||||||
|
v-for="(step, index) in processedSteps"
|
||||||
|
:key="step.value"
|
||||||
|
:class="[index === 0 ? '!items-start' : '', index === processedSteps.length - 1 ? '!items-end' : '']"
|
||||||
|
class="flex flex-col items-center step-item"
|
||||||
|
>
|
||||||
|
<!-- 步骤圆点 -->
|
||||||
|
<view
|
||||||
|
:class="[
|
||||||
|
'w-48rpx h-48rpx rounded-full transition-all duration-300',
|
||||||
|
index === processedSteps.length - 1 ? 'flex items-center justify-end' : 'center'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- 激活状态显示勾选图标 -->
|
||||||
|
<image
|
||||||
|
v-if="step.active"
|
||||||
|
src="@img/chef/1338.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="w-48rpx h-48rpx"
|
||||||
|
/>
|
||||||
|
<!-- 未激活状态显示空心圆 -->
|
||||||
|
<image
|
||||||
|
v-else
|
||||||
|
src="@img/chef/1339.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="w-36rpx h-36rpx"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<!-- 步骤文字 -->
|
||||||
|
<view :class="[step.active ? 'text-#333 font-500' : 'text-#9E9E9E' ]" class="text-24rpx lh-24rpx mt-12rpx">{{ step.label }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.order-progress {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-item {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
mask: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjciIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAyNyAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI2LjIyIDMuNDJMMTEuODIgMTcuODJDMTEuMSAxOC41NCA5LjkgMTguNTQgOS4xOCAxNy44Mkw5LjQyIDE3LjgyQzkuMTggMTcuODIgOC43IDE3LjU4IDguNDYgMTcuMzRMMC41NCA5LjQyQy0wLjE4IDguNyAtMC4xOCA3LjUgMC41NCA2LjU0QzEuMjYgNS44MiAyLjQ2IDUuODIgMy40MiA2LjU0TDEwLjYyIDEzLjc0TDIzLjgyIDAuNTM5OTk5QzI0LjU0IC0wLjE3OTk5OSAyNS43NCAtMC4xNzk5OTkgMjYuNDYgMC41Mzk5OTlMMjYuNyAwLjc4QzI2Ljk0IDEuNSAyNi45NCAyLjcgMjYuMjIgMy40MloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=') no-repeat center;
|
||||||
|
mask-size: contain;
|
||||||
|
-webkit-mask: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjciIGhlaWdodD0iMTgiIHZpZXdCb3g9IjAgMCAyNyAxOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI2LjIyIDMuNDJMMTEuODIgMTcuODJDMTEuMSAxOC41NCA5LjkgMTguNTQgOS4xOCAxNy44Mkw5LjQyIDE3LjgyQzkuMTggMTcuODIgOC43IDE3LjU4IDguNDYgMTcuMzRMMC41NCA5LjQyQy0wLjE4IDguNyAtMC4xOCA3LjUgMC41NCA2LjU0QzEuMjYgNS44MiAyLjQ2IDUuODIgMy40MiA2LjU0TDEwLjYyIDEzLjc0TDIzLjgyIDAuNTM5OTk5QzI0LjU0IC0wLjE3OTk5OSAyNS43NCAtMC4xNzk5OTkgMjYuNDYgMC41Mzk5OTlMMjYuNyAwLjc4QzI2Ljk0IDEuNSAyNi45NCAyLjcgMjYuMjIgMy40MloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=') no-repeat center;
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bg {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
const { t } = useI18n();
|
||||||
|
import {useConfigStore, useUserStore} from "@/store";
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 用户会员状态是否已开通
|
||||||
|
const isUserMember = computed(()=> {
|
||||||
|
if(!userStore.userInfo.userMembershipVo) return false
|
||||||
|
if(userStore.userInfo.userMembershipVo && userStore.userInfo.userMembershipVo.expireTime ){
|
||||||
|
return dayjs().isBefore(dayjs(Number(userStore.userInfo.userMembershipVo.expireTime)))
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const priceData = ref({})
|
||||||
|
const savings = ref(0)
|
||||||
|
const serviceFees = ref(0)
|
||||||
|
function onOpen(data: any, num: number) {
|
||||||
|
priceData.value = data;
|
||||||
|
savings.value = num;
|
||||||
|
// 计算服务费,使用分为单位避免浮点精度丢失
|
||||||
|
const original = Number(priceData.value?.actualAmount) || 0;
|
||||||
|
const ratio = Number(priceData.value?.merchantVo?.platformServiceFeeRatio) || 0;
|
||||||
|
const tip = Number(priceData.value?.tip) || 0;
|
||||||
|
const deliveryFee = Number(priceData.value?.deliveryFee) || 0;
|
||||||
|
|
||||||
|
console.log('原价', original)
|
||||||
|
console.log('服务费率', ratio)
|
||||||
|
console.log('小费', tip)
|
||||||
|
console.log('配送费', deliveryFee)
|
||||||
|
|
||||||
|
const toCents = (n: number) => Math.round(n * 100);
|
||||||
|
const serviceFeeCents = Math.round(toCents(original) * ratio);
|
||||||
|
const totalCents = toCents(tip) + toCents(deliveryFee);
|
||||||
|
|
||||||
|
serviceFees.value = totalCents / 100; // 保持为 number,模板显示时可格式化
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
function handleClose() {
|
||||||
|
show.value = false;
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
onOpen,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<wd-popup
|
||||||
|
v-model="show"
|
||||||
|
position="bottom"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view class="center h-102rpx bg-#F7F7F7 text-40rpx lh-40rpx font-bold text-#333">
|
||||||
|
{{ t('pages-store.checkout.priceDetail.title') }}?
|
||||||
|
</view>
|
||||||
|
<view class="border-bottom text-32rpx lh-32rpx font-500 text-#333 py-36rpx px-30rpx">
|
||||||
|
<view class="flex-center-sb mb-20rpx">
|
||||||
|
<view>{{ t('pages-store.checkout.priceDetail.serviceFees') }}</view>
|
||||||
|
<!-- <view>${{ (Number(priceData?.tip) + Number(priceData?.deliveryFee)).toFixed(2) }}</view>-->
|
||||||
|
<view>${{ serviceFees }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="text-24rpx lh-28rpx text-#9E9E9E">
|
||||||
|
{{ t('pages-store.checkout.priceDetail.desc') }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex-center-sb border-bottom h-104rpx text-32rpx lh-32rpx font-500 text-#333 px-30rpx">
|
||||||
|
<view>{{ t('pages-store.checkout.priceDetail.memberDiscount') }}</view>
|
||||||
|
<view>-${{ isUserMember ? savings : 0 }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex-center-sb border-bottom h-104rpx text-32rpx lh-32rpx font-500 text-#333 px-30rpx">
|
||||||
|
<view>{{ t('pages-store.checkout.priceDetail.taxation') }}</view>
|
||||||
|
<view>${{ Number(priceData?.tax) }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="px-30rpx pt-40rpx">
|
||||||
|
<wd-button
|
||||||
|
@click="handleClose"
|
||||||
|
custom-class="!h-108rpx !text-36rpx !rounded-16rpx !bg-#14181B"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{{ t('common.gotIt') }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||