1
This commit is contained in:
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
unpackage/
|
||||
dist/
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.project
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
24
README.md
Normal file
24
README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# pet-home
|
||||
|
||||
## node
|
||||
|
||||
node v14+
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
|
||||
81
babel.config.js
Normal file
81
babel.config.js
Normal file
@ -0,0 +1,81 @@
|
||||
const webpack = require('webpack')
|
||||
const plugins = []
|
||||
|
||||
if (process.env.UNI_OPT_TREESHAKINGNG) {
|
||||
plugins.push(require('@dcloudio/vue-cli-plugin-uni-optimize/packages/babel-plugin-uni-api/index.js'))
|
||||
}
|
||||
|
||||
if (
|
||||
(
|
||||
process.env.UNI_PLATFORM === 'app-plus' &&
|
||||
process.env.UNI_USING_V8
|
||||
) ||
|
||||
(
|
||||
process.env.UNI_PLATFORM === 'h5' &&
|
||||
process.env.UNI_H5_BROWSER === 'builtin'
|
||||
)
|
||||
) {
|
||||
const path = require('path')
|
||||
|
||||
const isWin = /^win/.test(process.platform)
|
||||
|
||||
const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path)
|
||||
|
||||
const input = normalizePath(process.env.UNI_INPUT_DIR)
|
||||
try {
|
||||
plugins.push([
|
||||
require('@dcloudio/vue-cli-plugin-hbuilderx/packages/babel-plugin-console'),
|
||||
{
|
||||
file (file) {
|
||||
file = normalizePath(file)
|
||||
if (file.indexOf(input) === 0) {
|
||||
return path.relative(input, file)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
])
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
process.UNI_LIBRARIES = process.UNI_LIBRARIES || ['@dcloudio/uni-ui']
|
||||
process.UNI_LIBRARIES.forEach(libraryName => {
|
||||
plugins.push([
|
||||
'import',
|
||||
{
|
||||
'libraryName': libraryName,
|
||||
'customName': (name) => {
|
||||
return `${libraryName}/lib/${name}/${name}`
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
if (process.env.UNI_PLATFORM !== 'h5') {
|
||||
plugins.push('@babel/plugin-transform-runtime')
|
||||
}
|
||||
|
||||
const config = {
|
||||
presets: [
|
||||
[
|
||||
'@vue/app',
|
||||
{
|
||||
modules: webpack.version[0] > 4 ? 'auto' : 'commonjs',
|
||||
useBuiltIns: process.env.UNI_PLATFORM === 'h5' ? 'usage' : 'entry'
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins
|
||||
}
|
||||
|
||||
const UNI_H5_TEST = '**/@dcloudio/uni-h5/dist/index.umd.min.js'
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
config.overrides = [{
|
||||
test: UNI_H5_TEST,
|
||||
compact: true,
|
||||
}]
|
||||
} else {
|
||||
config.ignore = [UNI_H5_TEST]
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
9
jsconfig.json
Normal file
9
jsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"@dcloudio/types",
|
||||
"miniprogram-api-typings",
|
||||
"mini-types"
|
||||
]
|
||||
}
|
||||
}
|
||||
42359
package-lock.json
generated
Normal file
42359
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
117
package.json
Normal file
117
package.json
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"name": "pet-home",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "npm run dev:h5",
|
||||
"build": "npm run build:h5",
|
||||
"build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
|
||||
"build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
|
||||
"build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
|
||||
"build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
|
||||
"build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build",
|
||||
"build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build",
|
||||
"build:mp-jd": "cross-env NODE_ENV=production UNI_PLATFORM=mp-jd vue-cli-service uni-build",
|
||||
"build:mp-kuaishou": "cross-env NODE_ENV=production UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build",
|
||||
"build:mp-lark": "cross-env NODE_ENV=production UNI_PLATFORM=mp-lark vue-cli-service uni-build",
|
||||
"build:mp-qq": "cross-env NODE_ENV=production UNI_PLATFORM=mp-qq vue-cli-service uni-build",
|
||||
"build:mp-toutiao": "cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build",
|
||||
"build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
|
||||
"build:mp-xhs": "cross-env NODE_ENV=production UNI_PLATFORM=mp-xhs vue-cli-service uni-build",
|
||||
"build:quickapp-native": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build",
|
||||
"build:quickapp-webview": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build",
|
||||
"build:quickapp-webview-huawei": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build",
|
||||
"build:quickapp-webview-union": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build",
|
||||
"dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch",
|
||||
"dev:custom": "cross-env NODE_ENV=development uniapp-cli custom",
|
||||
"dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve",
|
||||
"dev:mp-360": "cross-env NODE_ENV=development UNI_PLATFORM=mp-360 vue-cli-service uni-build --watch",
|
||||
"dev:mp-alipay": "cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch",
|
||||
"dev:mp-baidu": "cross-env NODE_ENV=development UNI_PLATFORM=mp-baidu vue-cli-service uni-build --watch",
|
||||
"dev:mp-jd": "cross-env NODE_ENV=development UNI_PLATFORM=mp-jd vue-cli-service uni-build --watch",
|
||||
"dev:mp-kuaishou": "cross-env NODE_ENV=development UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build --watch",
|
||||
"dev:mp-lark": "cross-env NODE_ENV=development UNI_PLATFORM=mp-lark vue-cli-service uni-build --watch",
|
||||
"dev:mp-qq": "cross-env NODE_ENV=development UNI_PLATFORM=mp-qq vue-cli-service uni-build --watch",
|
||||
"dev:mp-toutiao": "cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch",
|
||||
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch",
|
||||
"dev:mp-xhs": "cross-env NODE_ENV=development UNI_PLATFORM=mp-xhs vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-native": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview-huawei": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview-union": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build --watch",
|
||||
"info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
|
||||
"serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js",
|
||||
"test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i",
|
||||
"test:h5": "cross-env UNI_PLATFORM=h5 jest -i",
|
||||
"test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest -i",
|
||||
"test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest -i",
|
||||
"test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-app-plus": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-h5": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-i18n": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-360": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-alipay": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-baidu": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-jd": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-kuaishou": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-lark": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-qq": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-toutiao": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-vue": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-weixin": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-mp-xhs": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-quickapp-native": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-quickapp-webview": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-stacktracey": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-stat": "^2.0.2-3080720230703001",
|
||||
"@tencentcloud/chat": "^3.2.0",
|
||||
"@vue/shared": "^3.0.0",
|
||||
"core-js": "^3.6.5",
|
||||
"crypto-js": "^4.2.0",
|
||||
"flyio": "^0.6.2",
|
||||
"moment": "^2.29.3",
|
||||
"node-sass": "4.14.1",
|
||||
"sass": "1.26.2",
|
||||
"sass-loader": "8.0.2",
|
||||
"sass-resources-loader": "^2.2.4",
|
||||
"tim-upload-plugin": "^1.3.0",
|
||||
"vue": "^2.6.11",
|
||||
"vuex": "^3.2.0",
|
||||
"vuex-persistedstate": "^4.1.0",
|
||||
"wxml-to-canvas": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.3.2",
|
||||
"@dcloudio/uni-automator": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-cli-i18n": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-cli-shared": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-helper-json": "*",
|
||||
"@dcloudio/uni-migration": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/uni-template-compiler": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/webpack-uni-mp-loader": "^2.0.2-3080720230703001",
|
||||
"@dcloudio/webpack-uni-pages-loader": "^2.0.2-3080720230703001",
|
||||
"@vue/cli-plugin-babel": "~4.5.19",
|
||||
"@vue/cli-service": "~4.5.19",
|
||||
"babel-plugin-import": "^1.11.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"jest": "^25.4.0",
|
||||
"mini-types": "*",
|
||||
"miniprogram-api-typings": "*",
|
||||
"postcss-comment": "^2.0.0",
|
||||
"uqrcodejs": "^3.6.0",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"Android >= 4.4",
|
||||
"ios >= 9"
|
||||
],
|
||||
"uni-app": {
|
||||
"scripts": {}
|
||||
}
|
||||
}
|
||||
27
postcss.config.js
Normal file
27
postcss.config.js
Normal file
@ -0,0 +1,27 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const config = {
|
||||
parser: require('postcss-comment'),
|
||||
plugins: [
|
||||
require('postcss-import')({
|
||||
resolve (id, basedir, importOptions) {
|
||||
if (id.startsWith('~@/')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3))
|
||||
} else if (id.startsWith('@/')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2))
|
||||
} else if (id.startsWith('/') && !id.startsWith('//')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1))
|
||||
}
|
||||
return id
|
||||
}
|
||||
}),
|
||||
require('autoprefixer')({
|
||||
remove: process.env.UNI_PLATFORM !== 'h5'
|
||||
}),
|
||||
require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
|
||||
]
|
||||
}
|
||||
if (webpack.version[0] > 4) {
|
||||
delete config.parser
|
||||
}
|
||||
module.exports = config
|
||||
25
project.config.json
Normal file
25
project.config.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"uglifyFileName": false,
|
||||
"enhance": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"useCompilerPlugins": false,
|
||||
"minifyWXML": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"appid": "wx00e2dcdc7c02b23a",
|
||||
"editorSetting": {}
|
||||
}
|
||||
14
project.private.config.json
Normal file
14
project.private.config.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"libVersion": "3.8.10",
|
||||
"projectname": "pet_home_wxapp",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
||||
10
shime-uni.d.ts
vendored
Normal file
10
shime-uni.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
declare module "vue/types/options" {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
interface ComponentOptions<V extends Vue> extends Hooks {
|
||||
/**
|
||||
* 组件类型
|
||||
*/
|
||||
mpType?: string;
|
||||
}
|
||||
}
|
||||
4
shime-vue.d.ts
vendored
Normal file
4
shime-vue.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
71
src/App.vue
Normal file
71
src/App.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script>
|
||||
import Store from "./store";
|
||||
import { RouterInterceptor } from "./utils/common";
|
||||
|
||||
export default {
|
||||
globalData: {
|
||||
navBarHeight: uni.getSystemInfoSync().statusBarHeight + 44,
|
||||
inviteCode: "",
|
||||
},
|
||||
onLaunch(options) {
|
||||
const inviteCode = options?.query?.yaoqing_code || ''
|
||||
const referrerID = options?.query?.referrerID || ''
|
||||
console.log('小程序启动参数1111', options)
|
||||
|
||||
setTimeout(() => {
|
||||
if (inviteCode) {
|
||||
getApp().globalData.inviteCode = inviteCode
|
||||
console.log('小程序启动参数---邀请码222', getApp().globalData.inviteCode)
|
||||
}
|
||||
// 扫码进入时如果带有 referrerID,则写入 vuex,登录时使用
|
||||
if (referrerID) {
|
||||
Store.dispatch('user/setReferrerID', Number(referrerID) || 0)
|
||||
}
|
||||
}, 200)
|
||||
// 路由拦截
|
||||
RouterInterceptor();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||||
@import "./styles/font.css";
|
||||
/* 每个页面公共css */
|
||||
@import "./styles/common.scss";
|
||||
|
||||
:root {
|
||||
--app-font-scale: 1;
|
||||
}
|
||||
|
||||
page {
|
||||
height: 100vh;
|
||||
// padding-bottom: constant(safe-area-inset-bottom);
|
||||
// padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
// background: $app_color_background_color;
|
||||
font-family: "Arial", sans-serif;
|
||||
}
|
||||
|
||||
view,
|
||||
text {
|
||||
@extend .app-fs-main;
|
||||
@extend .app-fc-main;
|
||||
font-family: PingFangSC, "Arial", sans-serif;
|
||||
font-display: swap; /* 或者使用 block, fallback */
|
||||
}
|
||||
|
||||
/* 隐藏所有 scroll-view 滚动条 */
|
||||
scroll-view::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
/* 隐藏页面级滚动条(在 app.wxss 中设置) */
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
15
src/androidPrivacy.json
Normal file
15
src/androidPrivacy.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"version" : "1",
|
||||
"prompt" : "template",
|
||||
"title" : "服务协议和隐私政策",
|
||||
"message" : " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
|
||||
"buttonAccept" : "同意并接受",
|
||||
"buttonRefuse" : "暂不同意",
|
||||
"hrefLoader" : "system|default",
|
||||
"second" : {
|
||||
"title" : "确认提示",
|
||||
"message" : " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
|
||||
"buttonAccept" : "同意并继续",
|
||||
"buttonRefuse" : "退出应用"
|
||||
}
|
||||
}
|
||||
70
src/api/address.js
Normal file
70
src/api/address.js
Normal file
@ -0,0 +1,70 @@
|
||||
import request from "../utils/request";
|
||||
import { ADDRESS_LIST, ADDRESS_CREATE, ADDRESS_UPDATE, ADDRESS_EDIT, ADDRESS_INFO, ADDRESS_DEL, ADDRESS_DELETE } from "./url";
|
||||
|
||||
export const getAddressList = (data) => {
|
||||
const { user_id } = data;
|
||||
return request({
|
||||
url: ADDRESS_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
user_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 创建地址
|
||||
export const createAddress = (data) => {
|
||||
return request({
|
||||
url: ADDRESS_CREATE,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 更新地址
|
||||
export const updateAddress = (data) => {
|
||||
return request({
|
||||
url: ADDRESS_UPDATE,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const editAddress = (data) => {
|
||||
return request({
|
||||
url: ADDRESS_EDIT,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const getAddressInfo = (id) => {
|
||||
const data = {
|
||||
id: +id
|
||||
}
|
||||
return request({
|
||||
url: ADDRESS_INFO,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const delAddress = (id) => {
|
||||
const data = {
|
||||
address_id: id
|
||||
}
|
||||
return request({
|
||||
url: ADDRESS_DEL,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 删除地址(新接口)
|
||||
export const deleteAddress = (data) => {
|
||||
return request({
|
||||
url: ADDRESS_DELETE,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
7
src/api/app.js
Normal file
7
src/api/app.js
Normal file
@ -0,0 +1,7 @@
|
||||
// 文章协议
|
||||
export const getArticleInfo = (code) => {
|
||||
return request({
|
||||
url: `/mp/article/${code}`,
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
27883
src/api/areas.js
Normal file
27883
src/api/areas.js
Normal file
File diff suppressed because it is too large
Load Diff
12
src/api/article.js
Normal file
12
src/api/article.js
Normal file
@ -0,0 +1,12 @@
|
||||
import request from "../utils/request";
|
||||
import { ARTICLE_DETAIL } from "./url";
|
||||
|
||||
export const getArticleDetail = (id) => {
|
||||
return request({
|
||||
url: ARTICLE_DETAIL,
|
||||
method: "post",
|
||||
data: {
|
||||
article_id: id,
|
||||
},
|
||||
});
|
||||
};
|
||||
199
src/api/common.js
Normal file
199
src/api/common.js
Normal file
@ -0,0 +1,199 @@
|
||||
import request from "@/utils/request"
|
||||
import * as URL from "@/api/url";
|
||||
|
||||
/**
|
||||
* 获取轮播图
|
||||
* @param type
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
export function getImageList(type = 1) {
|
||||
return request({
|
||||
url: URL.HOST_BANNER_LIST,
|
||||
method: 'POST',
|
||||
data: { type }
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUserInfo() {
|
||||
return request({
|
||||
url: '/mp/auth/info',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取地址
|
||||
export function getRegionList(id) {
|
||||
const data = {
|
||||
parent_id: id
|
||||
}
|
||||
return request({
|
||||
url: '/app/home/city_list',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
export function uploadImg(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('image', file)
|
||||
return request({
|
||||
url: '/mp/file/uploadPicture',
|
||||
method: 'POST',
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
|
||||
// 上传视频
|
||||
export function uploadVideo() {
|
||||
const formData = new FormData()
|
||||
formData.append('image', file)
|
||||
return request({
|
||||
url: '/mp/file/uploadVideo',
|
||||
method: 'POST',
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宠物体重区间列表
|
||||
*/
|
||||
export function getWeightList() {
|
||||
return request({
|
||||
url: URL.WEIGHT_LIST,
|
||||
method: 'POST',
|
||||
data: {
|
||||
p: 1,
|
||||
num: 9999,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//获取宠物列表
|
||||
export function getPetList(id) {
|
||||
let data = {
|
||||
user_id: id
|
||||
}
|
||||
return request({
|
||||
url: URL.PET_LIST,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换宠物档案
|
||||
* @param {Object} data - 请求参数 { pet_id: 宠物ID }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function switchPetArchive(data) {
|
||||
return request({
|
||||
url: URL.PET_ARCHIVES_SWITCH,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宠物套餐列表
|
||||
* @param {Object} data - 请求参数
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getHomeServices(data) {
|
||||
return request({
|
||||
url: URL.HOME_SERVICES,
|
||||
method: 'POST',
|
||||
data: data || {}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取领养列表
|
||||
* @param {Object} data - 请求参数 { page: 1, page_size: 10 }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getAdoptList(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_LIST,
|
||||
method: 'POST',
|
||||
data: data || { page: 1, page_size: 10 }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宠物详情
|
||||
* @param {Object} data - 请求参数 { adoption_id: 宠物id }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getPetDetail(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_AGGREGATE,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 宠物收藏/取消收藏
|
||||
* @param {Object} data - 请求参数 { adoption_id: 宠物id, action: 1收藏/0取消收藏 }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function togglePetFavorite(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_FAVORITE,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交领养人信息
|
||||
* @param {Object} data - 请求参数
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function submitApplicantProfile(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_APPLICANT_PROFILE,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的申请记录列表
|
||||
* @param {Object} data - 请求参数 { page: 1, page_size: 10 }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getMyApplications(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_MYAPPLICATIONS,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销申请
|
||||
* @param {Object} data - 请求参数 { application_id: 1 }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function cancelApplication(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_APPLICANT_CANCEL,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销申请(新接口)
|
||||
* @param {Object} data - 请求参数 { adoption_id: 1 }
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function cancelPetApply(data) {
|
||||
return request({
|
||||
url: URL.ADOPTIONS_PET_APPLY_CANCEL,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
86
src/api/community.js
Normal file
86
src/api/community.js
Normal file
@ -0,0 +1,86 @@
|
||||
import request from "../utils/request";
|
||||
import {
|
||||
COMMUNITY_DELETE,
|
||||
COMMUNITY_DETAIL,
|
||||
COMMUNITY_FORWARD,
|
||||
COMMUNITY_LIST,
|
||||
COMMUNITY_MYLIST,
|
||||
COMMUNITY_ZAN,
|
||||
} from "./url";
|
||||
|
||||
// 宠圈列表
|
||||
export const getCommunityList = ({ p, num }) => {
|
||||
return request({
|
||||
url: COMMUNITY_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 我的宠圈列表
|
||||
export const getMyCommunityList = ({ p, num }) => {
|
||||
return request({
|
||||
url: COMMUNITY_MYLIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 圈子详情
|
||||
export const getCommunityDetail = ({
|
||||
chongquan_id,
|
||||
is_see = 0,
|
||||
is_share = 0,
|
||||
}) => {
|
||||
return request({
|
||||
url: COMMUNITY_DETAIL,
|
||||
method: "post",
|
||||
data: {
|
||||
chongquan_id,
|
||||
is_see,
|
||||
is_share,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 删除我的圈子
|
||||
export const deleteCommunity = (id) => {
|
||||
return request({
|
||||
url: COMMUNITY_DELETE,
|
||||
method: "post",
|
||||
data: {
|
||||
chongquan_id: id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 点赞圈子 type类型 1.点赞 2.取消
|
||||
export const zanCommunity = ({ chongquan_id, type }) => {
|
||||
return request({
|
||||
url: COMMUNITY_ZAN,
|
||||
method: "post",
|
||||
data: {
|
||||
chongquan_id,
|
||||
type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 一键转发到宠圈
|
||||
export const shareCommunity = ({ order_id, old_pic, new_pic }) => {
|
||||
return request({
|
||||
url: COMMUNITY_FORWARD,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id,
|
||||
old_pic,
|
||||
new_pic,
|
||||
},
|
||||
});
|
||||
};
|
||||
9
src/api/config.js
Normal file
9
src/api/config.js
Normal file
@ -0,0 +1,9 @@
|
||||
import request from "../utils/request";
|
||||
import { CONFIG_INFO } from "./url";
|
||||
|
||||
export const getConfig = () => {
|
||||
return request({
|
||||
url: CONFIG_INFO,
|
||||
method: "post",
|
||||
});
|
||||
};
|
||||
151
src/api/coupon.js
Normal file
151
src/api/coupon.js
Normal file
@ -0,0 +1,151 @@
|
||||
import request from "../utils/request";
|
||||
import {
|
||||
CREATE_SERVICE_ORDER,
|
||||
CREATE_SERVICE_ORDER_PAY,
|
||||
GET_COUPON_DATA,
|
||||
GET_COUPON_LIST,
|
||||
GET_COUPON_LIST_OWN,
|
||||
GET_SERVICE_COUPON_DATA,
|
||||
GET_SERVICE_COUPON_DETAIL,
|
||||
GET_SERVICE_COUPON_LIST,
|
||||
GET_SERVICE_COUPON_LIST_BUY,
|
||||
RECEIVE_COUPON,
|
||||
} from "./url";
|
||||
|
||||
// 获取优惠券列表
|
||||
export const getCouponData = ({ is_lingqu, p, num, type = '', is_xinren = '' }) => {
|
||||
const data = {
|
||||
is_lingqu,
|
||||
p,
|
||||
num,
|
||||
}
|
||||
if (type) {
|
||||
data.type = type
|
||||
}
|
||||
if (is_xinren) {
|
||||
data.is_xinren = is_xinren
|
||||
}
|
||||
return request({
|
||||
url: GET_COUPON_LIST,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 我的优惠券列表
|
||||
export const getOwnCouponData = ({ use_status, p, num, is_xinren, type }) => {
|
||||
return request({
|
||||
url: GET_COUPON_LIST_OWN,
|
||||
method: "post",
|
||||
data: {
|
||||
use_status,
|
||||
p,
|
||||
num,
|
||||
is_xinren,
|
||||
type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 领取优惠券
|
||||
export const receiveCoupon = (coupon_id) => {
|
||||
return request({
|
||||
url: RECEIVE_COUPON,
|
||||
method: "post",
|
||||
data: {
|
||||
coupon_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 服务券列表
|
||||
export const getServiceCouponList = ({ p, num, type, weight_id }) => {
|
||||
return request({
|
||||
url: GET_SERVICE_COUPON_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
type,
|
||||
weight_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 我的服务券列表
|
||||
export const getMyServiceCouponList = ({ p, num, status }) => {
|
||||
return request({
|
||||
url: GET_SERVICE_COUPON_LIST_BUY,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
status
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 服务券详情
|
||||
export const getServiceCouponDetail = (fuwuquan_id) => {
|
||||
return request({
|
||||
url: GET_SERVICE_COUPON_DETAIL,
|
||||
method: "post",
|
||||
data: {
|
||||
fuwuquan_id
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 服务券下单
|
||||
export const serviceCouponCreateOrder = (fuwuquan_id) => {
|
||||
return request({
|
||||
url: CREATE_SERVICE_ORDER,
|
||||
method: "post",
|
||||
data: {
|
||||
fuwuquan_id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 服务券支付
|
||||
export const serviceCouponOrderPay = (order_id) => {
|
||||
return request({
|
||||
url: CREATE_SERVICE_ORDER_PAY,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id,
|
||||
pay_type: 1, // 支付类型 1.微信
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据订单价格查询可用优惠券
|
||||
* @param orderPrice 订单价格
|
||||
* @param orderType 订单类型 1.商品 2.宠物
|
||||
*/
|
||||
export const getCouponListByOrderPrice = (userId, basePrice) => {
|
||||
return request({
|
||||
url: GET_COUPON_DATA,
|
||||
method: "post",
|
||||
data: {
|
||||
user_id: userId,
|
||||
base_price: basePrice
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据体重id查询可用服务券
|
||||
* @param weightId
|
||||
*/
|
||||
export const getServiceCouponListByWeightId = (weightId) => {
|
||||
return request({
|
||||
url: GET_SERVICE_COUPON_DATA,
|
||||
method: "post",
|
||||
data: {
|
||||
weight_id: weightId
|
||||
}
|
||||
});
|
||||
}
|
||||
14
src/api/franchise.js
Normal file
14
src/api/franchise.js
Normal file
@ -0,0 +1,14 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 加盟申请
|
||||
* @param {Object} data - 申请表单数据
|
||||
*/
|
||||
export function franchiseApply(data) {
|
||||
return request({
|
||||
url: '/franchise/leads',
|
||||
method: 'POST',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
293
src/api/login.js
Normal file
293
src/api/login.js
Normal file
@ -0,0 +1,293 @@
|
||||
import request from "../utils/request";
|
||||
import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USER_TRANSACTION,USER_ADDITIONAL,MEMBER_TYPES,
|
||||
USER_RECHARGE,USER_REDEEM,USER_HolderList,USER_MEMBERSHIP,USER_BINDPETS,USER_PETBINDING,USER_DISCOUNTFEE,USER_COUPONLIST,CANCEL_PET_ORDER,
|
||||
GET_VIP_PRICE,POINTS_RECHARGE_LIST,POINTS_DONATE,POINTS_RECORDS,POINTS_RANK,OSS_STS,DONATION_SUMMARY,
|
||||
USER_DISPATCHFEE,CANCEL_MALL_ORDER
|
||||
} from "./url";
|
||||
// 微信登陆
|
||||
export const getCodeByWxLogin = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: "weixin",
|
||||
success: function (loginRes) {
|
||||
resolve(loginRes.code);
|
||||
},
|
||||
fail: function (err) {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
import Store from "../store";
|
||||
|
||||
// 登录鉴权
|
||||
export const login = (phone) => {
|
||||
return getCodeByWxLogin().then((code) => {
|
||||
// 从 vuex 中获取 referrerID(如果通过二维码扫描进入)
|
||||
const referrerID = Store.state.user?.referrerID || 0;
|
||||
|
||||
return request({
|
||||
url: LOGIN,
|
||||
method: "POST",
|
||||
data: {
|
||||
code: code,
|
||||
// yaoqing_code: inviteCode || null,
|
||||
phone: phone || null,
|
||||
source: "wechat",
|
||||
referrerID: Number(referrerID) || 0,
|
||||
referrerType: "wechat"
|
||||
},
|
||||
}).then((res) => {
|
||||
// 登录接口使用完 referrerID 后,清除一次,避免重复使用
|
||||
if (referrerID) {
|
||||
Store.dispatch('user/setReferrerID', 0);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
});
|
||||
};
|
||||
// 获取手机号
|
||||
export const getPhone = (code) => {
|
||||
return request({
|
||||
url: GET_PHONE,
|
||||
method: "POST",
|
||||
data: {
|
||||
code: code
|
||||
},
|
||||
});
|
||||
};
|
||||
// 用户分享
|
||||
export const userShare = (id) => {
|
||||
return request({
|
||||
url: USER_SHARE,
|
||||
method: "POST",
|
||||
data: {
|
||||
member_id:id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 用户钱包
|
||||
export const userWllet = (id) => {
|
||||
return request({
|
||||
url: USER_WALLET,
|
||||
method: "POST",
|
||||
data: {
|
||||
user_id: +id
|
||||
},
|
||||
});
|
||||
};
|
||||
// 兑换码
|
||||
export const rechargeRedeem = (data) => {
|
||||
return request({
|
||||
url: USER_REDEEM,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
// 钱包充值列表
|
||||
export const rechargeWallet = (data) => {
|
||||
return request({
|
||||
url: RECHARGE_WALLET,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
// 钱包充值
|
||||
export const walletWxpay = (data) => {
|
||||
return request({
|
||||
url: USER_WXPAY,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 钱包金额
|
||||
export const walletRechagre = (data) => {
|
||||
return request({
|
||||
url: USER_RECHARGE,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 积分充值列表
|
||||
export const pointsRechargeList = (data) => {
|
||||
return request({
|
||||
url: POINTS_RECHARGE_LIST,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 捐赠积分
|
||||
export const pointsDonate = (data) => {
|
||||
return request({
|
||||
url: POINTS_DONATE,
|
||||
method: "POST",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取积分明细
|
||||
export const getPointsRecords = (data) => {
|
||||
return request({
|
||||
url: POINTS_RECORDS,
|
||||
method: "POST",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取排行榜
|
||||
export const getPointsRank = () => {
|
||||
return request({
|
||||
url: POINTS_RANK,
|
||||
method: "POST",
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取捐赠汇总
|
||||
export const getDonationSummary = () => {
|
||||
return request({
|
||||
url: DONATION_SUMMARY,
|
||||
method: "POST",
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取OSS配置
|
||||
export const getOssConfig = () => {
|
||||
return request({
|
||||
url: OSS_STS,
|
||||
method: "GET"
|
||||
});
|
||||
};
|
||||
|
||||
// 钱包消费
|
||||
export const walletTransaction = (data) => {
|
||||
return request({
|
||||
url: USER_TRANSACTION,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 卡包列表
|
||||
export const userHoldrlist = (data) => {
|
||||
return request({
|
||||
url: USER_HolderList,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 会员钱包支付接口
|
||||
export const membershipWallet = (data) => {
|
||||
return request({
|
||||
url: USER_MEMBERSHIP,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 取消宠物订单
|
||||
export const cancelPetOrderRefund = (data) => {
|
||||
return request({
|
||||
url: CANCEL_PET_ORDER,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 取消商城订单
|
||||
export const cancelPetOrderMall = (data) => {
|
||||
return request({
|
||||
url: CANCEL_MALL_ORDER,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 绑定宠物列表
|
||||
export const bindPets = (data) => {
|
||||
return request({
|
||||
url: USER_BINDPETS,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 绑定宠物
|
||||
export const petBinding = (data) => {
|
||||
return request({
|
||||
url: USER_PETBINDING,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 获取夜间费调度费
|
||||
export const gitMidnight = (data) => {
|
||||
return request({
|
||||
url: USER_DISPATCHFEE,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 获取基础价格
|
||||
export const gitDiscountfee = (data) => {
|
||||
return request({
|
||||
url: USER_DISCOUNTFEE,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
// 获取会员卡信息
|
||||
export const getVipPrice = (data) => {
|
||||
return request({
|
||||
url: GET_VIP_PRICE,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 优惠劵新接口
|
||||
export const userCoupolistwo = (data) => {
|
||||
return request({
|
||||
url: USER_COUPONLIST,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 会员详情
|
||||
export const memberType = (data) => {
|
||||
return request({
|
||||
url: MEMBER_TYPES,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 会员支付
|
||||
export const memberWXPAY = (data) => {
|
||||
return request({
|
||||
url: USER_WXPAY,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 添加附加项
|
||||
export const additionalItems = (data) => {
|
||||
return request({
|
||||
url: USER_ADDITIONAL,
|
||||
method: "POST",
|
||||
data: data
|
||||
});
|
||||
}
|
||||
24
src/api/notice.js
Normal file
24
src/api/notice.js
Normal file
@ -0,0 +1,24 @@
|
||||
import request from "../utils/request";
|
||||
import { NEWS_DETAIL, NEWS_LIST } from "./url";
|
||||
|
||||
// 消息通知
|
||||
export const getNoticeList = (data) => {
|
||||
const { p, num } = data;
|
||||
return request({
|
||||
url: NEWS_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 消息详情
|
||||
export const getNoticeDetails = (id) => {
|
||||
return request({
|
||||
url: NEWS_DETAIL,
|
||||
method: "post",
|
||||
data: { notice_id: id },
|
||||
});
|
||||
};
|
||||
206
src/api/order.js
Normal file
206
src/api/order.js
Normal file
@ -0,0 +1,206 @@
|
||||
import request from "@/utils/request"
|
||||
import * as URL from "@/api/url";
|
||||
|
||||
//获取订单列表
|
||||
export const getOrderList = (data) => {
|
||||
return request({
|
||||
url: URL.ORDER_LIST,
|
||||
method: "post",
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const getOrderDetail = (id) => {
|
||||
return request({
|
||||
url: URL.ORDER_DETAIL,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id: id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取订单详情(通用接口)
|
||||
export const getOrderWideDetail = (data) => {
|
||||
return request({
|
||||
url: URL.ORDERS_WIDE_DETAIL,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
// 卡片列表
|
||||
export const getCardList = (id) => {
|
||||
return request({
|
||||
url: URL.CARD_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
member_id: id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const getYuYueTimeList = (time) => {
|
||||
return request({
|
||||
url: URL.RESERVATION_TIME_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
date: time,
|
||||
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const createOrder = (data) => {
|
||||
return request({
|
||||
url: URL.CREATE_ORDER,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const cancelOrder = (id) => {
|
||||
return request({
|
||||
url: URL.CANCEL_ORDER,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id: id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 取消宠物订单
|
||||
export const cancelPetOrder = (data) => {
|
||||
return request({
|
||||
url: URL.CANCEL_PET_ORDER,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const payOrder = (data) => {
|
||||
return request({
|
||||
url: URL.PAY_ORDER,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const addServicePay = (id, weightId) => {
|
||||
return request({
|
||||
url: URL.ADD_SERVICE_FEE,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id: id,
|
||||
new_weight_id: weightId
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 城市是否开通服务
|
||||
export const getCityIsOpen = (cityId) => {
|
||||
return request({
|
||||
url: URL.ADDRESS_IS_SERVICE,
|
||||
method: "post",
|
||||
data: {
|
||||
area_id: cityId,
|
||||
},
|
||||
showErrToast: false,
|
||||
});
|
||||
};
|
||||
|
||||
// 创建训练订单(上门训练/寄养训练)
|
||||
export const createTrainingOrder = (data) => {
|
||||
return request({
|
||||
url: URL.HOMETRAINING_ORDERS,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 创建上门喂养订单
|
||||
export const createFeedOrder = (data) => {
|
||||
return request({
|
||||
url: URL.HOME_FEED_ORDERS,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 创建上门遛宠订单
|
||||
export const createWalkOrder = (data) => {
|
||||
return request({
|
||||
url: URL.HOME_WALK_ORDERS,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 取消上门训练订单
|
||||
export const cancelTrainingOrder = (data) => {
|
||||
return request({
|
||||
url: URL.HOMETRAINING_ORDERS_CANCEL,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
// 取消上门服务订单
|
||||
export const cancelHomeOrder = (order_id) => {
|
||||
return request({
|
||||
url: URL.HOME_ORDERS_CANCEL,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 支付完成后添加附加项
|
||||
export const appendAdditionalServices = (data) => {
|
||||
return request({
|
||||
url: URL.ADDITIONAL_SERVICES_APPEND,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取宠物服务记录列表
|
||||
export const getPetServiceRecords = (data) => {
|
||||
return request({
|
||||
url: URL.PET_SERVICE_RECORDS,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 获取宠物预检记录列表
|
||||
export const getPetPrecheckRecords = (data) => {
|
||||
return request({
|
||||
url: URL.PET_PRECHECK_QUERY,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 校验服务码是否存在
|
||||
export const checkWaExists = (data) => {
|
||||
return request({
|
||||
url: URL.CHECK_WA_EXISTS,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 校验节假日费用(参数 date)
|
||||
export const checkHolidayFee = (data) => {
|
||||
return request({
|
||||
url: URL.CHECK_HOLIDAY_FEE,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
81
src/api/record.js
Normal file
81
src/api/record.js
Normal file
@ -0,0 +1,81 @@
|
||||
import request from "../utils/request";
|
||||
import { RECORD_WEIGHT, RECORD_TYPE, RECORD_YUYUE, RECORD_LIST, RECORD_EDIT, RECORD_INFO, RECORD_DEL, PET_DELETE, PET_UPDATE } from "./url";
|
||||
|
||||
export const getRecordWeightList = (data) => {
|
||||
return request({
|
||||
url: RECORD_WEIGHT,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRecordTypeList = (data) => {
|
||||
return request({
|
||||
url: RECORD_TYPE,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const getRecordList = (data) => {
|
||||
const { p, num } = data;
|
||||
return request({
|
||||
url: RECORD_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const editRecord = (data) => {
|
||||
return request({
|
||||
url: RECORD_EDIT,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const getRecordInfo = (id, userId) => {
|
||||
const data = {
|
||||
pet_id : +id,
|
||||
user_id: userId
|
||||
}
|
||||
return request({
|
||||
url: RECORD_INFO,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const delRecord = (id) => {
|
||||
const data = {
|
||||
chongwu_id: id
|
||||
}
|
||||
return request({
|
||||
url: RECORD_DEL,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const deletePet = (petId, userId) => {
|
||||
const data = {
|
||||
pet_id: petId,
|
||||
user_id: userId
|
||||
}
|
||||
return request({
|
||||
url: PET_DELETE,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
export const updatePet = (data) => {
|
||||
return request({
|
||||
url: PET_UPDATE,
|
||||
method: "post",
|
||||
data: data
|
||||
});
|
||||
};
|
||||
366
src/api/shop.js
Normal file
366
src/api/shop.js
Normal file
@ -0,0 +1,366 @@
|
||||
// 商品相关接口
|
||||
|
||||
import request from "../utils/request";
|
||||
import {
|
||||
ADD_CART,
|
||||
CART_LIST,
|
||||
CREATE_CART_ORDER,
|
||||
CREATE_ORDER_NEW,
|
||||
DELETE_CART,
|
||||
GET_GOODS_CATEGORY,
|
||||
GET_GOODS_DETAIL,
|
||||
GET_GOODS_LIST,
|
||||
PAY_ORDER_NEW,
|
||||
SHOP_COLLECT,
|
||||
SHOP_COLLECT_LIST,
|
||||
SHOP_ORDER_AFTER_SALE,
|
||||
SHOP_ORDER_CANCEL,
|
||||
SHOP_ORDER_CONFIRM,
|
||||
SHOP_ORDER_DETAILS,
|
||||
SHOP_ORDER_LIST,
|
||||
SHOP_ORDER_REMARK,
|
||||
SHOP_ORDER_REMARK_DETAILS,
|
||||
SHOP_ORDER_REMARK_LIST,
|
||||
COMMENTS_CREATE,
|
||||
COMMENTS_SERVICE_IMGS,
|
||||
SHOP_ORDER_REMIND_SLIVER,
|
||||
SHOP_ORDER_SLIVERINFO,
|
||||
UPDATE_CART_NUM,
|
||||
UPDATE_CART_SELECT,
|
||||
} from "./url";
|
||||
|
||||
// 获取商品分类
|
||||
export const getGoodsClassify = () => {
|
||||
return request({
|
||||
url: GET_GOODS_CATEGORY,
|
||||
method: "post"
|
||||
});
|
||||
};
|
||||
|
||||
// 商品列表
|
||||
export const getGoodsListData = (data) => {
|
||||
return request({
|
||||
url: GET_GOODS_LIST,
|
||||
method: "post",
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
// 商品详情
|
||||
export const getGoodsDetail = ({product_id }) => {
|
||||
return request({
|
||||
url: GET_GOODS_DETAIL,
|
||||
method: "post",
|
||||
data: {
|
||||
product_id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 加入购物车
|
||||
export const addCart = (data) => {
|
||||
const { item_id, item_type, price_id, price_desc,item_name,item_price,number,item_pic,is_select,property } = data;
|
||||
return request({
|
||||
url: ADD_CART,
|
||||
method: "post",
|
||||
data: {
|
||||
item_id,
|
||||
item_type,
|
||||
price_id,
|
||||
price_desc,
|
||||
item_name,
|
||||
item_price,
|
||||
number,
|
||||
item_pic,
|
||||
is_select,
|
||||
property
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 购物车列表
|
||||
export const getCartList = ({ p, num }) => {
|
||||
return request({
|
||||
url: CART_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 购物车修改数量
|
||||
export const updateCartNum = ({ cart_id, number }) => {
|
||||
return request({
|
||||
url: UPDATE_CART_NUM,
|
||||
method: "post",
|
||||
data: {
|
||||
cart_id,
|
||||
number,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 选择购物车商品
|
||||
export const updateCartSelect = ({ is_select,cart_id }) => {
|
||||
return request({
|
||||
url: UPDATE_CART_SELECT,
|
||||
method: "post",
|
||||
data: {
|
||||
is_select,
|
||||
cart_id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 删除购物车
|
||||
export const deleteCart = ({ cart_id }) => {
|
||||
return request({
|
||||
url: DELETE_CART,
|
||||
method: "post",
|
||||
data: {
|
||||
cart_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 创建订单
|
||||
export const createOrder = ({
|
||||
goods_id,
|
||||
price_id,
|
||||
dikou_id,
|
||||
number,
|
||||
shuxing_name,
|
||||
liuyan,
|
||||
address_id,
|
||||
yuxiadan,
|
||||
order_type,
|
||||
chongwu_order_id
|
||||
}) => {
|
||||
return request({
|
||||
url: CREATE_ORDER_NEW,
|
||||
method: "post",
|
||||
data: {
|
||||
goods_id,
|
||||
price_id,
|
||||
dikou_id,
|
||||
number,
|
||||
shuxing_name,
|
||||
liuyan,
|
||||
address_id,
|
||||
yuxiadan,
|
||||
order_type,
|
||||
chongwu_order_id
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 创建购物车订单
|
||||
export const createCartOrder = ({
|
||||
type = "" ,
|
||||
original_price = "",
|
||||
actual_price = "",
|
||||
reduction_amount = 0,
|
||||
pay_amount = "",
|
||||
pay_type = "",
|
||||
address_id = "",
|
||||
address = "",
|
||||
name = "",
|
||||
phone = "",
|
||||
note = "",
|
||||
items = []
|
||||
}) => {
|
||||
return request({
|
||||
url: CREATE_CART_ORDER,
|
||||
method: "post",
|
||||
data: {
|
||||
type,
|
||||
original_price,
|
||||
actual_price,
|
||||
reduction_amount,
|
||||
pay_amount,
|
||||
pay_type,
|
||||
address_id,
|
||||
address,
|
||||
name,
|
||||
phone,
|
||||
note,
|
||||
items
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 订单支付
|
||||
export const payOrder = ({ type, total_fee,order_id,order_no }) => {
|
||||
return request({
|
||||
url: PAY_ORDER_NEW,
|
||||
method: "post",
|
||||
data: {
|
||||
type,
|
||||
total_fee,
|
||||
order_id,
|
||||
order_no,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 商城订单列表
|
||||
export const getShopOrderList = ({ p, num, status = 0, tui_status = 0 }) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
status,
|
||||
tui_status,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 商城订单详情
|
||||
export const getShopOrderDetails = (order_id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_DETAILS,
|
||||
method: "post",
|
||||
data: { order_id },
|
||||
});
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
export const cancelOrder = (order_id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_CANCEL,
|
||||
method: "post",
|
||||
data: { order_id },
|
||||
});
|
||||
};
|
||||
|
||||
// 提醒发货
|
||||
export const remindOrder = (order_id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_REMIND_SLIVER,
|
||||
method: "post",
|
||||
data: { order_id },
|
||||
});
|
||||
};
|
||||
|
||||
// 确认收货
|
||||
export const confirmOrder = (order_id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_CONFIRM,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id,
|
||||
status:6
|
||||
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 查看物流
|
||||
export const getSliverInfo = (order_id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_SLIVERINFO,
|
||||
method: "post",
|
||||
data: { order_id },
|
||||
});
|
||||
}
|
||||
|
||||
// 发起售后
|
||||
export const createAfterSale = ({
|
||||
order_id,
|
||||
tui_yuanyin,
|
||||
tui_desc,
|
||||
tui_pic,
|
||||
}) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_AFTER_SALE,
|
||||
method: "post",
|
||||
data: { order_id, tui_yuanyin, tui_desc, tui_pic },
|
||||
});
|
||||
};
|
||||
|
||||
// 收藏
|
||||
export const collectShop = ({ goods_id, type }) => {
|
||||
return request({
|
||||
url: SHOP_COLLECT,
|
||||
method: "post",
|
||||
data: {
|
||||
goods_id,
|
||||
type: type,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 收藏列表
|
||||
export const collectList = ({ p, num }) => {
|
||||
return request({
|
||||
url: SHOP_COLLECT_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
p,
|
||||
num,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 评价
|
||||
export const createRemark = ({
|
||||
order_id,
|
||||
type,
|
||||
imgs,
|
||||
content,
|
||||
description,
|
||||
attitude,
|
||||
}) => {
|
||||
return request({
|
||||
url: COMMENTS_CREATE,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id,
|
||||
type,
|
||||
imgs,
|
||||
content,
|
||||
description,
|
||||
attitude,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取服务图片(洗护对比图)
|
||||
export const getServiceImgs = (order_id) => {
|
||||
return request({
|
||||
url: COMMENTS_SERVICE_IMGS,
|
||||
method: "post",
|
||||
data: {
|
||||
order_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 评价列表
|
||||
export const getRemarkList = ({ type, p, num, guanlian_id }) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_REMARK_LIST,
|
||||
method: "post",
|
||||
data: {
|
||||
type,
|
||||
p,
|
||||
num,
|
||||
guanlian_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 评价详情
|
||||
export const getRemarkDetails = (id) => {
|
||||
return request({
|
||||
url: SHOP_ORDER_REMARK_DETAILS,
|
||||
method: "post",
|
||||
data: {
|
||||
pinglun_id: id
|
||||
},
|
||||
});
|
||||
};
|
||||
306
src/api/url.js
Normal file
306
src/api/url.js
Normal file
@ -0,0 +1,306 @@
|
||||
import appConfig from "@/constants/app.config";
|
||||
|
||||
/* 登录注册 */
|
||||
export const LOGIN = "/auth/login";
|
||||
/* 获取手机号 */
|
||||
export const GET_PHONE = "/auth/wx_phone";
|
||||
|
||||
// 用户信息
|
||||
export const USER_INFO = "/user/info";
|
||||
// 修改用户信息
|
||||
export const UPDATE_USER_INFO = "/user/update";
|
||||
// 退出登录
|
||||
export const USER_LOGIN_OUT = "/app/member/login_out";
|
||||
// 申请管家
|
||||
export const APPLY_KEHU = "/app/shenqing/shenqing_info";
|
||||
// 申请管家详情
|
||||
export const APPLY_KEHU_DETAILS = '/app/shenqing/shenqing_show'
|
||||
|
||||
//分享接口
|
||||
export const USER_SHARE = '/shared/info'
|
||||
|
||||
//钱包接口
|
||||
export const RECHARGE_WALLET = '/wallet/details'
|
||||
|
||||
//钱包充值列表接口
|
||||
export const USER_WALLET = '/wallet/info'
|
||||
|
||||
//钱包充值接口
|
||||
export const USER_WXPAY = '/wxpay/jsapi'
|
||||
//钱包充值金额
|
||||
export const USER_RECHARGE = '/recharge/bonus-list'
|
||||
//积分充值列表
|
||||
export const POINTS_RECHARGE_LIST = '/points/recharge/list'
|
||||
//捐赠积分接口
|
||||
export const POINTS_DONATE = '/public_service/points/donate'
|
||||
//积分明细接口
|
||||
export const POINTS_RECORDS = '/user/points/records'
|
||||
//排行榜接口
|
||||
export const POINTS_RANK = '/public_service/points/rank'
|
||||
//捐赠汇总接口
|
||||
export const DONATION_SUMMARY = '/public_service/donation/summary'
|
||||
//证书列表接口
|
||||
export const USER_CERTIFICATES = '/user/certificates'
|
||||
//OSS配置接口
|
||||
export const OSS_STS = '/oss/sts'
|
||||
//钱包消费接口
|
||||
export const USER_TRANSACTION = '/wallet/transaction'
|
||||
|
||||
// 取消预约(宠物订单)
|
||||
export const PET_ORDER_CANCEL = '/home/orders/cancel'
|
||||
// 支付完成后添加附加项接口
|
||||
export const ADDITIONAL_SERVICES_APPEND = '/order/additional_services/append'
|
||||
// 取消预约订单接口
|
||||
export const CANCEL_PET_ORDER = '/order/pet/cancel'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 取消商城接口
|
||||
export const CANCEL_MALL_ORDER = '/product/order/cancel'
|
||||
|
||||
// 卡包列表接口
|
||||
export const USER_HolderList = '/membership/instances'
|
||||
|
||||
// 会员钱包支付接口
|
||||
export const USER_MEMBERSHIP = '/wallet/transaction'
|
||||
|
||||
// 绑定宠物列表接口
|
||||
export const USER_BINDPETS = '/membership/bindable_pets'
|
||||
|
||||
// 绑定宠物
|
||||
export const USER_PETBINDING = '/membership/bind_pets'
|
||||
|
||||
// 获取折扣和免服务费
|
||||
export const USER_DISCOUNTFEE = '/order/pet/base_price'
|
||||
|
||||
// 获取夜间费和调度费
|
||||
export const USER_DISPATCHFEE = '/membership/pet_benefits'
|
||||
|
||||
// 获取会员价格
|
||||
export const GET_VIP_PRICE = '/membership/pet_benefits'
|
||||
|
||||
// 兑换码
|
||||
export const USER_REDEEM = '/coupon/redeem'
|
||||
|
||||
// 新优惠劵接口
|
||||
export const USER_COUPONLIST = '/coupon/distribution'
|
||||
|
||||
//预约添加附加项接口
|
||||
export const USER_ADDITIONAL = '/order/additional_services/by_pet'
|
||||
|
||||
//会员详情
|
||||
export const MEMBER_TYPES = '/membership/cards'
|
||||
|
||||
// 消息列表
|
||||
export const NEWS_LIST = "/app/notice/notice_list";
|
||||
|
||||
// 消息详情
|
||||
export const NEWS_DETAIL = "/app/notice/notice_show";
|
||||
|
||||
// 地址列表
|
||||
export const ADDRESS_LIST = "/user/addresses";
|
||||
// 创建地址
|
||||
export const ADDRESS_CREATE = "/user/addresses/create";
|
||||
// 更新地址
|
||||
export const ADDRESS_UPDATE = "/user/addresses/update";
|
||||
// 地址列表新增或修改
|
||||
export const ADDRESS_EDIT = "/app/address/address_info";
|
||||
// 地址详情
|
||||
export const ADDRESS_INFO = "/user/addresses/detail ";
|
||||
// 删除地址
|
||||
// export const ADDRESS_DEL = "/app/address/address_del";
|
||||
// 删除地址(新接口)
|
||||
export const ADDRESS_DELETE = "/user/addresses/delete";
|
||||
// 地址是否开通服务
|
||||
export const ADDRESS_IS_SERVICE = "/app/home/is_city";
|
||||
|
||||
//获取轮播图
|
||||
export const HOST_BANNER_LIST = "/app/ad/ad_list";
|
||||
|
||||
//获取订单列表
|
||||
export const ORDER_LIST = "/orders/wide/list";
|
||||
//获取订单详情(通用接口)
|
||||
export const ORDERS_WIDE_DETAIL = "/orders/wide/detail";
|
||||
|
||||
//获取体重列表
|
||||
export const WEIGHT_LIST = "/pet/weights";
|
||||
|
||||
//获取宠物列表
|
||||
export const PET_LIST = "/order/pet/by_user";
|
||||
//获取宠物服务记录列表
|
||||
export const PET_SERVICE_RECORDS = "/order/pet/by_pet";
|
||||
|
||||
// 预检记录查询
|
||||
export const PET_PRECHECK_QUERY = "/pet/precheck/query";
|
||||
|
||||
//获取订单详情
|
||||
export const ORDER_DETAIL = "/app/chongwu_order/order_show";
|
||||
|
||||
//卡片列表
|
||||
export const CARD_LIST = "/app/card/card_list";
|
||||
|
||||
// 文章详情
|
||||
export const ARTICLE_DETAIL = "/ad_page/detail";
|
||||
|
||||
// 获取配置(客服)
|
||||
export const CONFIG_INFO = "/app/home/config";
|
||||
|
||||
// 宠物体重列表
|
||||
export const RECORD_WEIGHT = "/pet/weights";
|
||||
// 宠物品种列表
|
||||
export const RECORD_TYPE = "/pet/breeds";
|
||||
// 宠物预约时间
|
||||
export const RECORD_YUYUE = "/app/chongwu/yuyue_time";
|
||||
// 宠物列表
|
||||
export const RECORD_LIST = "/app/chongwu/chongwu_list";
|
||||
// 添加修改宠物
|
||||
export const RECORD_EDIT = "/pet/add";
|
||||
// 宠物更新
|
||||
export const PET_UPDATE = "/pet/update";
|
||||
// 宠物详情
|
||||
export const RECORD_INFO = "/pet/info";
|
||||
// 宠物删除
|
||||
export const RECORD_DEL = "/app/chongwu/chongwu_del";
|
||||
// 宠物删除(新接口)
|
||||
export const PET_DELETE = "/pet/delete";
|
||||
// 切换宠物档案
|
||||
export const PET_ARCHIVES_SWITCH = "/pet/archives/switch";
|
||||
|
||||
//获取预约时间列表
|
||||
export const RESERVATION_TIME_LIST = "/order/periods";
|
||||
|
||||
//创建订单
|
||||
export const CREATE_ORDER = "/order/pet/create";
|
||||
// 校验服务码是否存在
|
||||
export const CHECK_WA_EXISTS = "/order/check_wa_exists";
|
||||
// 创建训练订单(上门训练/寄养训练)
|
||||
export const HOMETRAINING_ORDERS = "/hometraining/orders";
|
||||
// 创建上门喂养订单
|
||||
export const HOME_FEED_ORDERS = "/home/orders/create/feed";
|
||||
// 创建上门遛宠订单
|
||||
export const HOME_WALK_ORDERS = "/home/orders/create/walk";
|
||||
// 取消上门服务订单
|
||||
export const HOME_ORDERS_CANCEL = "/home/orders/cancel";
|
||||
// 校验节假日费用
|
||||
export const CHECK_HOLIDAY_FEE = "/check_holiday_fee";
|
||||
//支付订单
|
||||
export const PAY_ORDER = "/wxpay/jsapi";
|
||||
|
||||
//取消订单
|
||||
export const CANCEL_ORDER = "/app/chongwu_order/order_quxiao";
|
||||
|
||||
//调整服务费
|
||||
export const ADD_SERVICE_FEE = "/app/chongwu_order/order_fuwufei";
|
||||
|
||||
// 商品分类
|
||||
export const GET_GOODS_CATEGORY = "/product/type/list";
|
||||
// 商品列表
|
||||
export const GET_GOODS_LIST = "/product/list";
|
||||
// 商品详情
|
||||
export const GET_GOODS_DETAIL = "/product/detail";
|
||||
|
||||
// 获取优惠券列表
|
||||
export const GET_COUPON_LIST = "/app/coupon/coupon_list";
|
||||
// 已领取的优惠券列表
|
||||
export const GET_COUPON_LIST_OWN = "/app/coupon/fafang_list";
|
||||
// 领取优惠券
|
||||
export const RECEIVE_COUPON = "/app/coupon/coupon_lingqu";
|
||||
|
||||
// 服务券列表
|
||||
export const GET_SERVICE_COUPON_LIST = "/app/fuwuquan/fuwuquan_list";
|
||||
// 我的服务券列表
|
||||
export const GET_SERVICE_COUPON_LIST_BUY = "/app/fuwuquan/order_list";
|
||||
// 服务券详情
|
||||
export const GET_SERVICE_COUPON_DETAIL = "/app/fuwuquan/fuwuquan_show";
|
||||
// 服务券下单
|
||||
export const CREATE_SERVICE_ORDER = "/app/fuwuquan/order_creat";
|
||||
// 服务券支付
|
||||
export const CREATE_SERVICE_ORDER_PAY = "/app/fuwuquan/order_pay";
|
||||
|
||||
// 加入购物车
|
||||
export const ADD_CART = "/product/cart/add";
|
||||
// 获取购物车
|
||||
export const CART_LIST = "/product/cart/list";
|
||||
// 修改购物车数量
|
||||
export const UPDATE_CART_NUM = "/cart/update";
|
||||
// 选择购物车商品
|
||||
export const UPDATE_CART_SELECT = "/cart/select/all";
|
||||
// 删除购物车
|
||||
export const DELETE_CART = "/product/cart/delete";
|
||||
|
||||
// 创建订单
|
||||
export const CREATE_ORDER_NEW = "/product/order/create";
|
||||
// 创建购物车订单
|
||||
export const CREATE_CART_ORDER = "/product/order/create";
|
||||
// 订单支付
|
||||
export const PAY_ORDER_NEW = "/wxpay/jsapi";
|
||||
// 订单列表
|
||||
export const SHOP_ORDER_LIST = "/product/order/list";
|
||||
// 订单详情
|
||||
export const SHOP_ORDER_DETAILS = "/product/order/show";
|
||||
// 取消订单
|
||||
export const SHOP_ORDER_CANCEL = '/app/order/order_quxiao'
|
||||
// 提醒发货
|
||||
export const SHOP_ORDER_REMIND_SLIVER = '/app/order/order_tixing'
|
||||
// 确认收货
|
||||
export const SHOP_ORDER_CONFIRM = '/product/order/update_status'
|
||||
// 发起售后
|
||||
export const SHOP_ORDER_AFTER_SALE = '/app/order/order_tui'
|
||||
// 查看物流
|
||||
export const SHOP_ORDER_SLIVERINFO = '/app/order/order_wuliu'
|
||||
|
||||
// 收藏商品
|
||||
export const SHOP_COLLECT = "/app/goods/goods_shoucang";
|
||||
// 收藏商品列表
|
||||
export const SHOP_COLLECT_LIST = "/app/goods/shoucang_list";
|
||||
|
||||
// 创建评价
|
||||
export const SHOP_ORDER_REMARK = '/app/pinglun/creat_pinglun'
|
||||
export const COMMENTS_CREATE = '/comments/create'
|
||||
export const COMMENTS_SERVICE_IMGS = '/comments/service_imgs'
|
||||
// 评价列表
|
||||
export const SHOP_ORDER_REMARK_LIST = '/app/pinglun/pinglun_list'
|
||||
// 评价详情
|
||||
export const SHOP_ORDER_REMARK_DETAILS = '/app/pinglun/pinglun_show'
|
||||
|
||||
// 圈子列表
|
||||
export const COMMUNITY_LIST = '/app/chongquan/chongquan_list'
|
||||
// 我的圈子列表
|
||||
export const COMMUNITY_MYLIST = '/app/chongquan/my_list'
|
||||
// 删除圈子
|
||||
export const COMMUNITY_DELETE = '/app/chongquan/chongquan_del'
|
||||
// 点赞圈子
|
||||
export const COMMUNITY_ZAN = '/app/chongquan/zan_info'
|
||||
// 圈子详情
|
||||
export const COMMUNITY_DETAIL = '/app/chongquan/chongquan_show'
|
||||
// 一键转发到宠圈
|
||||
export const COMMUNITY_FORWARD = '/app/chongquan/chongquan_add'
|
||||
|
||||
//根据订单价格查询可用优惠券
|
||||
export const GET_COUPON_DATA = '/order/coupons/usable'
|
||||
|
||||
//根据体重id查询可用服务券
|
||||
export const GET_SERVICE_COUPON_DATA = '/app/fuwuquan/order_shiyong'
|
||||
// 获取宠物套餐列表
|
||||
export const HOME_SERVICES = '/home/services'
|
||||
// 领养列表接口
|
||||
export const ADOPTIONS_PET_LIST = '/adoptions/pet/list'
|
||||
// 宠物详情接口
|
||||
export const ADOPTIONS_PET_AGGREGATE = '/adoptions/pet/aggregate'
|
||||
// 宠物收藏接口
|
||||
export const ADOPTIONS_PET_FAVORITE = '/adoptions/pet/favorite'
|
||||
// 提交领养人信息接口
|
||||
export const ADOPTIONS_PET_APPLICANT_PROFILE = '/adoptions/pet/applicant/profile'
|
||||
// 我的申请记录接口
|
||||
export const ADOPTIONS_PET_MYAPPLICATIONS = '/adoptions/pet/myapplications'
|
||||
// 撤销申请
|
||||
export const ADOPTIONS_PET_APPLICANT_CANCEL = '/adoptions/pet/applicant/cancel'
|
||||
// 撤销申请(新接口)
|
||||
export const PET_APPLY_CANCEL = '/pet/apply/cancel'
|
||||
// 撤销申请(上门训练订单)
|
||||
export const HOMETRAINING_ORDERS_CANCEL = '/hometraining/orders/cancel'
|
||||
// 撤销申请(领养申请)
|
||||
export const ADOPTIONS_PET_APPLY_CANCEL = '/adoptions/pet/apply/cancel'
|
||||
|
||||
60
src/api/user.js
Normal file
60
src/api/user.js
Normal file
@ -0,0 +1,60 @@
|
||||
import request from "../utils/request";
|
||||
import { USER_INFO, UPDATE_USER_INFO, USER_LOGIN_OUT, APPLY_KEHU, APPLY_KEHU_DETAILS, USER_SHARE, USER_CERTIFICATES } from "./url";
|
||||
|
||||
// 获取用户信息
|
||||
export const getUserInfo = () => {
|
||||
return request({
|
||||
url: USER_INFO,
|
||||
method: "POST",
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
|
||||
// 修改用户信息
|
||||
export const updateUserInfo = (data) => {
|
||||
return request({
|
||||
url: UPDATE_USER_INFO,
|
||||
method: "POST",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
export const loginOut = (data) => {
|
||||
return request({
|
||||
url: USER_LOGIN_OUT,
|
||||
method: "POST",
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
// 申请管家
|
||||
export const applyKeeper = ({ name, phone }) => {
|
||||
return request({
|
||||
url: APPLY_KEHU,
|
||||
method: "POST",
|
||||
data: {
|
||||
name, phone
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 申请管家详情
|
||||
export const applyKeeperDetail = () => {
|
||||
return request({
|
||||
url: APPLY_KEHU_DETAILS,
|
||||
method: "POST",
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
|
||||
// 获取证书列表
|
||||
export const getCertificates = (data) => {
|
||||
return request({
|
||||
url: USER_CERTIFICATES,
|
||||
method: "POST",
|
||||
data: data || {},
|
||||
});
|
||||
};
|
||||
88
src/components/CommonCell.vue
Normal file
88
src/components/CommonCell.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view
|
||||
class="flex-row-start common-cell"
|
||||
:class="[{ 'common-cell-line': showSplitLine }]"
|
||||
:style="fontScaleStyle"
|
||||
@click="clickAction"
|
||||
>
|
||||
<image
|
||||
v-if="showLeftIcon"
|
||||
class="cell-title-icon"
|
||||
:src="leftIcon"
|
||||
/>
|
||||
<text
|
||||
class="app-fc-main fs-32 cell-content"
|
||||
:style="textStyle"
|
||||
>{{title}}</text>
|
||||
<image
|
||||
v-if="showRightIcon"
|
||||
class="cell-right-icon"
|
||||
mode="scaleToFill"
|
||||
:src="rightIcon"
|
||||
></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CommonCell',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '标题',
|
||||
},
|
||||
showLeftIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showRightIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
leftIcon: {
|
||||
type: String,
|
||||
},
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: require('@/static/images/arrow_right.png'),
|
||||
},
|
||||
showSplitLine: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
textStyle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickAction() {
|
||||
this.$emit('clickAction')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-cell {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 40rpx 36rpx;
|
||||
background: #fff;
|
||||
&.common-cell-line {
|
||||
border-bottom: 1rpx solid#f2f2f2;
|
||||
}
|
||||
.cell-content {
|
||||
flex: 1;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
.cell-title-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
.cell-right-icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
237
src/components/CompareModal.vue
Normal file
237
src/components/CompareModal.vue
Normal file
@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<select-modal
|
||||
class="good-info-modal"
|
||||
title="洗护对比图"
|
||||
@close="$emit('close', false)"
|
||||
>
|
||||
<view class="compare-content">
|
||||
<view class="compare-wrapper">
|
||||
<text class="fs-36 app-fc-normal">洗护前</text>
|
||||
<view class="flex-row-start imgs-list">
|
||||
<view
|
||||
class="imgs-item"
|
||||
:class="{ 'no-margin': index % 3 === 2 }"
|
||||
v-for="(item, index) in beforeImgs"
|
||||
:key="index"
|
||||
@click="selectItem(item, index)"
|
||||
>
|
||||
<image class="item-img" :src="item.fullUrl" mode="aspectFit" />
|
||||
<image
|
||||
v-if="selectList.some(v => v.url === item.url)"
|
||||
class="select-icon"
|
||||
src="@/static/images/cart_checked.png"
|
||||
/>
|
||||
<view v-else class="select-icon un-select"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="fs-36 app-fc-normal">洗护后</text>
|
||||
<view class="flex-row-start imgs-list">
|
||||
<view
|
||||
class="imgs-item"
|
||||
:class="{ 'no-margin': index % 3 === 2 }"
|
||||
v-for="(item, index) in afterImgs"
|
||||
:key="index"
|
||||
@click="selectItem(item, index)"
|
||||
>
|
||||
<image class="item-img" :src="item.fullUrl" mode="aspectFit" />
|
||||
<image
|
||||
v-if="selectList.some(v => v.url === item.url)"
|
||||
class="select-icon"
|
||||
src="@/static/images/cart_checked.png"
|
||||
/>
|
||||
<view v-else class="select-icon un-select"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="flex-row-between compare-footer"
|
||||
v-if="isCanRemark || isCanShare"
|
||||
>
|
||||
<!-- <view
|
||||
v-if="isCanRemark"
|
||||
class="flex-center fs-30 app-fc-white confirm-btn left"
|
||||
@click="submit"
|
||||
>
|
||||
添加到评论
|
||||
</view> -->
|
||||
|
||||
<view
|
||||
v-if="isCanShare"
|
||||
class="flex-center fs-30 app-fc-white confirm-btn"
|
||||
@click="shareToCommunity"
|
||||
>
|
||||
一键转发到宠圈
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isCanShare: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isCanRemark: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
beforeImgs: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
afterImgs: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectImgs: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectList: [], // 选中的图片
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SelectModal,
|
||||
},
|
||||
watch: {
|
||||
selectImgs: {
|
||||
handler(val) {
|
||||
this.selectList = val;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (!this.selectList?.length) {
|
||||
uni.showToast({
|
||||
title: "请选择图片",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.$emit("add", this.selectList);
|
||||
},
|
||||
shareToCommunity() {
|
||||
const front = this.selectList
|
||||
.filter((v) => this.beforeImgs.some((item) => v.url === item.url))
|
||||
.map((v) => v.url)
|
||||
const end = this.selectList
|
||||
.filter((v) => this.afterImgs.some((item) => v.url === item.url))
|
||||
.map((v) => v.url)
|
||||
if (!front.length) {
|
||||
uni.showToast({
|
||||
title: "请至少选择一张洗护前图片",
|
||||
icon: "none",
|
||||
});
|
||||
return
|
||||
}
|
||||
if (!end.length) {
|
||||
uni.showToast({
|
||||
title: "请至少选择一张洗护后图片",
|
||||
icon: "none",
|
||||
});
|
||||
return
|
||||
}
|
||||
this.$emit("share", {
|
||||
front: front,
|
||||
end: end,
|
||||
});
|
||||
},
|
||||
selectItem(item) {
|
||||
const index = this.selectList.findIndex((v) => v.url === item.url);
|
||||
if (index < 0) {
|
||||
this.selectList.push(item);
|
||||
} else {
|
||||
this.selectList.splice(index, 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep {
|
||||
.selected-modal .model-container {
|
||||
background-color: #fff0f5;
|
||||
}
|
||||
}
|
||||
|
||||
.compare-content {
|
||||
padding: 48rpx 32rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
.compare-wrapper {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.imgs-list {
|
||||
flex-wrap: wrap;
|
||||
margin-top: 28rpx;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.imgs-item {
|
||||
width: 204rpx;
|
||||
height: 204rpx;
|
||||
border-radius: 20rpx;
|
||||
position: relative;
|
||||
margin-right: 14rpx;
|
||||
margin-bottom: 14rpx;
|
||||
background: #fff;
|
||||
|
||||
&.no-margin {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.item-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
|
||||
.un-select {
|
||||
border: 3rpx solid #beb5ba;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compare-footer {
|
||||
background: #fff;
|
||||
padding: 20rpx 0 0;
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 90rpx;
|
||||
border-radius: 90rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 45rpx 45rpx 45rpx 45rpx;
|
||||
color: #fff;
|
||||
|
||||
&.left {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
src/components/ContactModal.vue
Normal file
118
src/components/ContactModal.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="flex-center contact-modal">
|
||||
<view class="flex-center contact-modal-content">
|
||||
<view class="flex-center contact-modal-inner">
|
||||
<!-- <image
|
||||
class="modal-title-icon"
|
||||
src="@/static/images/contact_title_icon.png"
|
||||
/> -->
|
||||
<image
|
||||
class="qrcode-img"
|
||||
:src="details.kefu_pic"
|
||||
:show-menu-by-longpress="true"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<text class="PingFangSC-Heavy fs-44 app-fc-main modal-tip">
|
||||
扫码添加客服
|
||||
</text>
|
||||
<text class="fs-28 app-fc-normal PingFangSC-Medium">
|
||||
客服在线时间{{ details.kefu_time }}
|
||||
</text>
|
||||
</view>
|
||||
<image
|
||||
class="close-icon"
|
||||
src="@/static/images/modal_close.png"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getConfig } from "../api/config";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
details: {},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
data(val) {
|
||||
val && (this.details = val);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.data || !Object.keys(this.data).length) {
|
||||
this.getConfig();
|
||||
} else {
|
||||
this.details = this.data;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getConfig() {
|
||||
getConfig().then((res) => {
|
||||
this.details.kefu_pic = res.info.kefu_pic;
|
||||
this.details.kefu_time = res.info.kefu_time;
|
||||
this.$forceUpdate();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contact-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
|
||||
.contact-modal-content {
|
||||
margin-top: -50rpx;
|
||||
|
||||
.contact-modal-inner {
|
||||
width: 646rpx;
|
||||
padding: 134rpx 0 80rpx;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
border-radius: 36rpx;
|
||||
|
||||
.modal-title-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
}
|
||||
|
||||
.qrcode-img {
|
||||
width: 260rpx;
|
||||
height: 260rpx;
|
||||
padding: 14rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.modal-tip {
|
||||
margin: 76rpx 0 56rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-top: 50rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
184
src/components/CustomePicker.vue
Normal file
184
src/components/CustomePicker.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<view class="custom-picker">
|
||||
<view class="mask" @click="handleClose"></view>
|
||||
<view class="picker-content">
|
||||
<!-- 自定义头部 -->
|
||||
<view class="picker-header">
|
||||
<view class="header-hidden"></view>
|
||||
<text class="title app-text-ellipse ali-puhui-bold">{{ title }}</text>
|
||||
<image
|
||||
src="@/static/images/close_icon.png"
|
||||
class="close-icon"
|
||||
@click="handleClose"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 选择器主体 -->
|
||||
<picker-view
|
||||
:value="currentValue"
|
||||
class="picker-view"
|
||||
:style="{ height: rowHeight * visibleRows + 'px' }"
|
||||
@change="handleChange"
|
||||
:indicator-style="`height: ${rowHeight}px`"
|
||||
>
|
||||
<picker-view-column>
|
||||
<view
|
||||
class="picker-item"
|
||||
v-for="(item, index) in options"
|
||||
:key="index"
|
||||
:style="{ height: rowHeight + 'px', lineHeight: rowHeight + 'px' }"
|
||||
>
|
||||
{{ item[labelKey] }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
|
||||
<!-- 自定义底部插槽 -->
|
||||
<view
|
||||
class="flex-center PingFangSC-Semibold fs-30 confirm-btn"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "CustomPicker",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
value: {
|
||||
type: [Number, Array],
|
||||
default: 0,
|
||||
},
|
||||
visibleRows: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
rowHeight: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: "label",
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: "value",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: [""],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
this.currentValue = Array.isArray(val) ? val : [val];
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(e) {
|
||||
this.currentValue = e.detail.value;
|
||||
this.$emit("change", e.detail.value[0]);
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit("cancel");
|
||||
},
|
||||
handleConfirm() {
|
||||
const index = this.currentValue[0] || 0;
|
||||
const _index = index < 0 ? 0 : index;
|
||||
const value = this.options[_index][this.valueKey];
|
||||
this.$emit("confirm", value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-picker {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.picker-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
|
||||
.picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 52rpx 30rpx;
|
||||
|
||||
.header-hidden {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
// font-weight: 500;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 630rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 90rpx;
|
||||
background: #FF19A0;
|
||||
border-radius: 45rpx 45rpx 45rpx 45rpx;
|
||||
color: #fff;
|
||||
margin: 20rpx auto 60rpx;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
|
||||
.picker-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
219
src/components/DateTimePicker.vue
Normal file
219
src/components/DateTimePicker.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view class="datetime-picker">
|
||||
<view
|
||||
class="mask"
|
||||
:class="{ show: open }"
|
||||
@touchmove.stop.prevent
|
||||
catchtouchmove="true"
|
||||
@click="changeState(false)"
|
||||
></view>
|
||||
<view class="wrap" :class="{ show: open }">
|
||||
<view
|
||||
class="picker-header title"
|
||||
@touchmove.stop.prevent
|
||||
catchtouchmove="true"
|
||||
>
|
||||
<view class="PingFangSC-Bold fs-36 btn-picker">{{ title }}</view>
|
||||
<image
|
||||
class="close-icon"
|
||||
src="@/static/images/close.png"
|
||||
@click="changeState(false)"
|
||||
/>
|
||||
</view>
|
||||
<view class="picker-body">
|
||||
<DateTimePicker
|
||||
:mode="modeValue"
|
||||
:minDate="minDate"
|
||||
:maxDate="maxDate"
|
||||
:defaultDate="defaultDate"
|
||||
@onChange="onDateChange"
|
||||
/>
|
||||
</view>
|
||||
<!-- <view class="picker-header" @touchmove.stop.prevent catchtouchmove="true">
|
||||
<view class="submit" @click="submit">确定</view>
|
||||
</view> -->
|
||||
<view class="picker-bottom">
|
||||
<view
|
||||
class="fs-30 PingFangSC-Semibold app-fc-white flex-center picker-confirm"
|
||||
@click="submit"
|
||||
>
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DateTimePicker from "@/components/dengrq-datetime-picker/dateTimePicker/index.vue";
|
||||
|
||||
export default {
|
||||
name: "DateTimePicker",
|
||||
components: {
|
||||
DateTimePicker,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 日期模式,1:年月日(默认),2:年月,3:年份,4:年月日时分秒,5:时分秒,6:时分
|
||||
mode: {
|
||||
type: Number,
|
||||
default: 4,
|
||||
},
|
||||
// 可选的最小日期,默认十年前
|
||||
minDate: {
|
||||
type: String,
|
||||
default: "1940-01-01",
|
||||
},
|
||||
// 可选的最大日期,默认十年后
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: new Date().format("yyyy-MM-dd"),
|
||||
},
|
||||
// 默认选中日期(注意要跟日期模式对应)
|
||||
defaultDate: {
|
||||
type: String,
|
||||
default: "1970-01-01",
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
date: new Date().format("yyyy-MM-dd"),
|
||||
modeValue: 4,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mode: {
|
||||
handler(val) {
|
||||
this.modeValue = val;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
visible: {
|
||||
handler(value) {
|
||||
this.open = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
defaultDate: {
|
||||
handler(value) {
|
||||
this.date = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
changeState(value) {
|
||||
this.open = value;
|
||||
this.$emit("onModalStateChange", value);
|
||||
},
|
||||
submit() {
|
||||
this.$emit("onSubmit", this.date || new Date().format("yyyy-MM-dd"));
|
||||
},
|
||||
onDateChange(value) {
|
||||
this.date = value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$transition: all 0.3s ease;
|
||||
$primary: #488ee9;
|
||||
|
||||
.datetime-picker {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
picker-view {
|
||||
height: 100%;
|
||||
}
|
||||
.mask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: $transition;
|
||||
&.show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.wrap {
|
||||
z-index: 1001;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: $transition;
|
||||
transform: translateY(100%);
|
||||
&.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.picker-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 50rpx;
|
||||
box-sizing: border-box;
|
||||
// background-color: darken(#fff, 2%);
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
&.title {
|
||||
border-top-right-radius: 30rpx;
|
||||
border-top-left-radius: 30rpx;
|
||||
}
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 50rpx;
|
||||
right: 50rpx;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
}
|
||||
.picker-body {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
.submit {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: constant(safe-area-inset-bottom);
|
||||
margin-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
.btn-picker {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #3e4055;
|
||||
}
|
||||
|
||||
.picker-bottom {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding: 20rpx 0 40rpx;
|
||||
.picker-confirm {
|
||||
width: 630rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 90rpx;
|
||||
background: $app_color_main;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
340
src/components/FormCell.vue
Normal file
340
src/components/FormCell.vue
Normal file
@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<view class="form-cell-wrapper">
|
||||
<view class="form-cell" :class="[{ 'no-border': noBorder }]" @click="clickAction">
|
||||
|
||||
<text class="required" v-if="isRequired">*</text>
|
||||
<text class="title">
|
||||
{{ title }}
|
||||
</text>
|
||||
<view class="flex-center form-content">
|
||||
<input v-if="type === 'input'" :type="inputType" :disabled="disabled"
|
||||
class="fs-24 app-fc-main form-inner app-text-ellipse" :class="{ disabled: disabled }"
|
||||
placeholder-class="fs-24 form-placeholder" v-model="curValue" :placeholder="placeholderText"
|
||||
@input="onChange" @confirm="onChangeDone" />
|
||||
<text v-else-if="['jumpToMap'].includes(type)" class="fs-24 app-fc-main form-inner"
|
||||
:class="{ 'form-placeholder': !value, 'fs-26': !value }" @click="jumpToLoationAction">
|
||||
{{ value || placeholderText || "请选择" }}
|
||||
</text>
|
||||
<text v-else-if="
|
||||
['checkDate', 'checkPicker', 'checkAddress'].includes(type)
|
||||
" class="fs-24 form-inner" :class="{ 'form-placeholder': !value, 'fs-26': !value }"
|
||||
@click="showModal(type)">
|
||||
{{
|
||||
((type === "checkPicker"
|
||||
? labelKey
|
||||
? pickerData[pickerIndex][labelKey]
|
||||
: pickerData[pickerIndex]
|
||||
: value) || "") + showText ||
|
||||
placeholderText ||
|
||||
"请选择"
|
||||
}}
|
||||
</text>
|
||||
<slot v-else-if="type === 'custom'" name="right"></slot>
|
||||
</view>
|
||||
<image v-show="showRightArrow" class="form-arrow" :src="`${imgPrefix}right-arrow.png`"
|
||||
@click="$emit('arrowClick')" />
|
||||
</view>
|
||||
<DateTimePicker :mode="mode" :visible="showDatePicker" :title="title.replace('*', '')" :minDate="minDate"
|
||||
:maxDate="maxDate" :defaultDate="defaultDate" @onSubmit="onDataChange"
|
||||
@onModalStateChange="onModalStateChange" />
|
||||
<CustomePicker v-if="showPicker" :title="title.replace('*', '')" :options="pickerData" :value="pickerIndex"
|
||||
:labelKey="labelKey" :valueKey="valueKey" @confirm="onDataChange" @cancel="showPicker = false" />
|
||||
<template v-if="showAreaPicker">
|
||||
<RegionPicker :title="title.replace('*', '')" :list="regionList" :defaultValue="defaultRegionList"
|
||||
@confirm="onDataChange" @cancel="showAreaPicker = false" />
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DateTimePicker from "@/components/DateTimePicker.vue";
|
||||
import CustomePicker from "@/components/CustomePicker.vue";
|
||||
import RegionPicker from "@/components/RegionPicker.vue";
|
||||
import {
|
||||
getRegionList
|
||||
} from "@/api/common.js";
|
||||
import {
|
||||
jumpToLoation
|
||||
} from "@/utils/common";
|
||||
import {
|
||||
pcaData
|
||||
} from "@/api/areas.js";
|
||||
|
||||
import {
|
||||
imgPrefix
|
||||
} from "@/utils/common";
|
||||
|
||||
|
||||
export default {
|
||||
name: "form-cell",
|
||||
props: {
|
||||
// input/checkDate/checkPicker/checkAddress
|
||||
type: {
|
||||
default: "input",
|
||||
type: String,
|
||||
},
|
||||
// type===checkPicker ? 索引 : value值
|
||||
value: {
|
||||
default: "",
|
||||
type: String | Number,
|
||||
},
|
||||
inputType: {
|
||||
default: "text",
|
||||
type: String,
|
||||
},
|
||||
valueKey: {
|
||||
default: "value",
|
||||
type: String,
|
||||
},
|
||||
showText: {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
labelKey: {
|
||||
default: "",
|
||||
type: String,
|
||||
},
|
||||
disabled: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
// type===checkPicker时有效
|
||||
pickerData: {
|
||||
default: () => [],
|
||||
type: Array,
|
||||
},
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: "请填写",
|
||||
},
|
||||
// 日期模式,1:年月日(默认),2:年月,3:年份,4:年月日时分秒,5:时分秒,6:时分
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
mode: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
// 可选的最小日期,默认十年前
|
||||
minDate: {
|
||||
type: String,
|
||||
default: "1940-01-01",
|
||||
},
|
||||
// 可选的最大日期,默认十年后
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: new Date().format("yyyy-MM-dd"),
|
||||
},
|
||||
// 默认选中日期(注意要跟日期模式对应)
|
||||
defaultDate: {
|
||||
type: String,
|
||||
default: "1970-01-01",
|
||||
},
|
||||
// 默认区域
|
||||
defaultRegion: {
|
||||
type: Array,
|
||||
default: () => [0, 0, 0],
|
||||
},
|
||||
extraData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showRightArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
noBorder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isRequired: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DateTimePicker,
|
||||
CustomePicker,
|
||||
RegionPicker
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
curValue: "",
|
||||
showDatePicker: false,
|
||||
showPicker: false,
|
||||
showAreaPicker: false,
|
||||
regionList: [],
|
||||
defaultRegionList: [0, 0, 0],
|
||||
imgPrefix
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pickerIndex() {
|
||||
let index = 0;
|
||||
if (this.type === "checkPicker") {
|
||||
const _index = this.pickerData.findIndex(
|
||||
(v) => v[this.valueKey] === this.curValue
|
||||
);
|
||||
index = _index;
|
||||
}
|
||||
return index;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(value) {
|
||||
this.curValue = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
defaultRegion: {
|
||||
handler(val) {
|
||||
const [province, city, area] = val;
|
||||
this.defaultRegionList = [
|
||||
province > -1 ? province : 0,
|
||||
city > -1 ? city : 0,
|
||||
area > -1 ? area : 0,
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
jumpToLoationAction() {
|
||||
if (this.disabled) return;
|
||||
jumpToLoation(this.extraData.latitude, this.extraData.longitude);
|
||||
},
|
||||
formatRegionData(list) {
|
||||
return list.map((p) => {
|
||||
const _p = Object.assign({}, {
|
||||
label: p.area_name,
|
||||
value: p.area_id
|
||||
});
|
||||
if (p.children) {
|
||||
_p.children = p.children.map((city) => {
|
||||
const _city = Object.assign({}, {
|
||||
label: city.area_name,
|
||||
value: city.area_id,
|
||||
});
|
||||
if (city.children) {
|
||||
_city.children = city.children.map((area) => {
|
||||
return Object.assign({}, {
|
||||
label: area.area_name,
|
||||
value: area.area_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
return _city;
|
||||
});
|
||||
}
|
||||
return _p;
|
||||
});
|
||||
},
|
||||
onChange(e) {
|
||||
this.$emit("onChange", e.detail.value);
|
||||
},
|
||||
onChangeDone(e) {
|
||||
this.$emit("onChangeDone", e.detail.value);
|
||||
},
|
||||
onModalStateChange(value) {
|
||||
this.showDatePicker = value;
|
||||
},
|
||||
async showModal(type) {
|
||||
if (this.disabled) return;
|
||||
switch (type) {
|
||||
case "checkDate":
|
||||
this.showDatePicker = true;
|
||||
break;
|
||||
case "checkPicker":
|
||||
this.showPicker = true;
|
||||
break;
|
||||
case "checkAddress":
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
// const res = await getRegionList(-1);
|
||||
const res = pcaData;
|
||||
this.regionList = this.formatRegionData(res || []);
|
||||
this.showAreaPicker = true;
|
||||
uni.hideLoading();
|
||||
break;
|
||||
}
|
||||
},
|
||||
onDataChange(data) {
|
||||
let _data = data;
|
||||
this.showDatePicker = false;
|
||||
this.showPicker = false;
|
||||
this.showAreaPicker = false;
|
||||
this.$emit("onChange", _data);
|
||||
},
|
||||
clickAction() {
|
||||
this.$emit("clickAction");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-cell-wrapper {
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.form-cell {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1rpx solid #ececec;
|
||||
|
||||
.title {
|
||||
color: #3D3D3D;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
&.no-border {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
margin: 0 10rpx;
|
||||
|
||||
.form-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
text-align: right;
|
||||
|
||||
&.disabled {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: #8f9090;
|
||||
}
|
||||
}
|
||||
|
||||
.form-arrow {
|
||||
width: 11rpx;
|
||||
height: 18rpx;
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.u-datetime-picker {
|
||||
.u-picker-header {
|
||||
border-top-left-radius: 40rpx;
|
||||
border-top-right-radius: 40rpx;
|
||||
height: unset;
|
||||
padding: 50rpx 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #FF19A0;
|
||||
}
|
||||
</style>
|
||||
235
src/components/ListPageTemp.vue
Normal file
235
src/components/ListPageTemp.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
|
||||
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||
<slot name="top" />
|
||||
<view class="list-content" v-if="list.length > 0">
|
||||
<view v-for="(item, index) in list" :key="item[idKey]" :class="{ left: index % 2 === 0 }"
|
||||
class="flex-column-start news-item" @click="clickCell(item)">
|
||||
<slot style="width: 100%" name="item" :data="{
|
||||
...item,
|
||||
...listExtraFields,
|
||||
deleteSelect: !!item.deleteSelect,
|
||||
}" />
|
||||
</view>
|
||||
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
|
||||
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
|
||||
</view>
|
||||
<view v-else class="empty-container">
|
||||
<image class="home-ration" mode="widthFix" :src="emptyImage || defaultEmptyImage" />
|
||||
<text v-if="emptyText" class="empty-text">{{ emptyText }}</text>
|
||||
</view>
|
||||
<slot name="bottom" />
|
||||
|
||||
</scroll-view>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ListPageTemp",
|
||||
props: {
|
||||
getDataPromise: {
|
||||
type: Function,
|
||||
},
|
||||
// 格式化请求结果的方法
|
||||
resultFormatFunc: {
|
||||
type: Function,
|
||||
},
|
||||
requestData: {
|
||||
defult: () => { },
|
||||
type: Object,
|
||||
},
|
||||
// 列表元素额外字段
|
||||
listExtraFields: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
reloadFlag: {
|
||||
default: 0,
|
||||
type: Number,
|
||||
},
|
||||
// 是否分页
|
||||
isPagiantion: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 分页数量, 不分页时有效
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 999,
|
||||
},
|
||||
defaultList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disableScroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: "id",
|
||||
},
|
||||
// 占位图片地址
|
||||
emptyImage: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 空状态文本(传入时才显示)
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: "",
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
total: 0,
|
||||
list: [],
|
||||
refreshTriggered: false,
|
||||
p: 1,
|
||||
num: 10,
|
||||
defaultEmptyImage: 'https://activity.wagoo.live/empty.png', // 默认占位图片
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
reloadFlag: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.p = 1;
|
||||
this.num = 10;
|
||||
this.list = [];
|
||||
this.total = 0;
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
requestData: {
|
||||
handler(data) {
|
||||
if (data) {
|
||||
this.p = 1;
|
||||
this.num = 10;
|
||||
this.list = [];
|
||||
this.total = 0;
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
defaultList: {
|
||||
handler(list) {
|
||||
this.list = list;
|
||||
this.$forceUpdate();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onRefresh() {
|
||||
if (this.refreshTriggered) return;
|
||||
this.refreshTriggered = true;
|
||||
this.p = 1;
|
||||
this.num = 10;
|
||||
// this.list = [];
|
||||
this.total = 0;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
onLoadMore() {
|
||||
if (!this.isPagiantion) {
|
||||
return;
|
||||
}
|
||||
if (!this.isLoading && this.total > this.list.length) {
|
||||
this.p++;
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
clickCell(item) {
|
||||
this.$emit("clickCell", item);
|
||||
},
|
||||
getList() {
|
||||
if (this.isLoading) return;
|
||||
this.isLoading = true;
|
||||
|
||||
// TODO: 删除测试数据
|
||||
if (!this.getDataPromise) {
|
||||
this.list = [0, 1, 2, 3, 4, 5, 6].map((v, k) => {
|
||||
return {
|
||||
title: "消息名称0000sssss撒大苏打大苏打" + v,
|
||||
id: k,
|
||||
len: 10,
|
||||
};
|
||||
});
|
||||
this.total = 9;
|
||||
this.isLoading = false;
|
||||
this.refreshTriggered = false;
|
||||
uni.stopPullDownRefresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Object.assign(
|
||||
{},
|
||||
{ p: this.p, num: !this.isPagiantion ? this.pageSize : this.num },
|
||||
this.requestData
|
||||
);
|
||||
this.getDataPromise(data)
|
||||
.then((res) => {
|
||||
const list =
|
||||
this.p === 1
|
||||
? res?.data || []
|
||||
: [...this.list, ...(res?.data || [])];
|
||||
this.list = this.resultFormatFunc
|
||||
? this.resultFormatFunc(list)
|
||||
: list;
|
||||
this.total = res?.count || 0;
|
||||
this.$emit("getList", this.list, res);
|
||||
// console.log(this.list,'???')
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
this.refreshTriggered = false;
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-template-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.list-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 400rpx auto 0;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.home-ration {
|
||||
width: 160px;
|
||||
height: 174px;
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/components/NavBar.vue
Normal file
52
src/components/NavBar.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<view class="nav-container">
|
||||
<view :style="{ height: ststuaBarHeight + 'px' }"></view>
|
||||
<view
|
||||
class="flex-row-center nav-title ali-puhui-bold"
|
||||
:style="{ height: menuButtonHeight + 'px' }"
|
||||
>
|
||||
{{ title }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
navBarHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
ststuaBarHeight() {
|
||||
const systemInfo = wx.getSystemInfoSync();
|
||||
return systemInfo.statusBarHeight;
|
||||
},
|
||||
menuButtonHeight() {
|
||||
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
|
||||
return (
|
||||
(menuButtonInfo.top - this.ststuaBarHeight) * 2 + menuButtonInfo.height
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-container {
|
||||
background: #fff;
|
||||
|
||||
.nav-title {
|
||||
font-size: 14px;
|
||||
// font-weight: bold;
|
||||
color: #000;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
src/components/NoData.vue
Normal file
40
src/components/NoData.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<view class="flex-center no-data">
|
||||
<image class="no-data-icon" :src="imgUrl" mode="widthFix" />
|
||||
<text class="no-data-tip app-fc-normal app-fs-base">{{ tip }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NoData',
|
||||
props: {
|
||||
imgUrl: {
|
||||
type: String,
|
||||
// default: 'https://oss.chuanglingmeng.com/cl/common/no_data.png'
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
default: '暂无记录'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.no-data {
|
||||
width: 100%;
|
||||
padding: 60rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-center;
|
||||
align-items: center;
|
||||
.no-data-icon {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
.no-data-tip {
|
||||
margin: 10rpx 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
src/components/PopUpModal.vue
Normal file
85
src/components/PopUpModal.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<view class="flex-center pop-up-modal">
|
||||
<view class="pop-content">
|
||||
<view
|
||||
class="app-text-center fs-36 app-fc-main PingFangSC-Medium pop-inner"
|
||||
>
|
||||
{{ content }}
|
||||
</view>
|
||||
<view class="flex-row-between pop-bottom">
|
||||
<view
|
||||
class="fs-32 PingFangSC-Regular flex-center pop-btn cancel"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
取消
|
||||
</view>
|
||||
<view
|
||||
class="fs-32 PingFangSC-Regular flex-center app-fc-white pop-btn confirm"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pop-up-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
.pop-content {
|
||||
background: #fff;
|
||||
width: 620rpx;
|
||||
border-radius: 40rpx 40rpx 40rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 60rpx 76rpx;
|
||||
|
||||
.pop-inner {
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.pop-bottom {
|
||||
margin-top: 60rpx;
|
||||
|
||||
.pop-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
background: #f2f2f2;
|
||||
border-radius: 8rpx 8rpx 8rpx 8rpx;
|
||||
|
||||
&.cancel {
|
||||
color: #3e4055;
|
||||
background: #f2f2f2;
|
||||
margin-right: 36rpx;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
background: $app_color_main;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
239
src/components/RegionPicker.vue
Normal file
239
src/components/RegionPicker.vue
Normal file
@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="custom-picker">
|
||||
<view class="mask" @click="handleClose"></view>
|
||||
<view class="picker-content">
|
||||
<!-- 头部 -->
|
||||
<view class="picker-header">
|
||||
<view class="header-hidden"></view>
|
||||
<text class="title app-text-ellipse ali-puhui-bold">{{ title }}</text>
|
||||
<image
|
||||
src="@/static/images/close_icon.png"
|
||||
class="close-icon"
|
||||
@click="handleClose"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 选择器主体 -->
|
||||
<picker-view
|
||||
:value="currentValue"
|
||||
class="picker-view"
|
||||
:style="{ height: rowHeight * visibleRows + 'px' }"
|
||||
@change="handleChange"
|
||||
:indicator-style="`height: ${rowHeight}px`"
|
||||
>
|
||||
<!-- 省份列 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
class="picker-item"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:style="{ height: rowHeight + 'px', lineHeight: rowHeight + 'px' }"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 城市列 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
class="picker-item"
|
||||
v-for="(item, index) in cityList"
|
||||
:key="index"
|
||||
:style="{ height: rowHeight + 'px', lineHeight: rowHeight + 'px' }"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
|
||||
<!-- 区县列 -->
|
||||
<picker-view-column>
|
||||
<view
|
||||
class="picker-item"
|
||||
v-for="(item, index) in areaList"
|
||||
:key="index"
|
||||
:style="{ height: rowHeight + 'px', lineHeight: rowHeight + 'px' }"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
|
||||
<!-- 底部确认按钮 -->
|
||||
<view
|
||||
class="flex-center PingFangSC-Semibold fs-30 confirm-btn"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "RegionPicker",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "选择地区",
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
defaultValue: {
|
||||
type: Array,
|
||||
default: () => [0, 0, 0],
|
||||
},
|
||||
visibleRows: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
rowHeight: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: [0, 0, 0],
|
||||
cityList: [],
|
||||
areaList: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
defaultValue: {
|
||||
handler(val) {
|
||||
this.currentValue = val;
|
||||
this.updateCityAndArea();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
list: {
|
||||
handler() {
|
||||
this.updateCityAndArea();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateCityAndArea() {
|
||||
if (this.list.length > 0) {
|
||||
const [provinceIndex, cityIndex] = this.currentValue;
|
||||
|
||||
// 更新城市列表
|
||||
this.cityList = this.list[provinceIndex]?.children || [];
|
||||
|
||||
// 更新区县列表
|
||||
this.areaList = this.cityList[cityIndex]?.children || [];
|
||||
}
|
||||
},
|
||||
|
||||
handleChange(e) {
|
||||
const newValue = e.detail.value;
|
||||
|
||||
// 判断省份是否改变
|
||||
if (newValue[0] !== this.currentValue[0]) {
|
||||
newValue[1] = 0;
|
||||
newValue[2] = 0;
|
||||
}
|
||||
// 判断城市是否改变
|
||||
else if (newValue[1] !== this.currentValue[1]) {
|
||||
newValue[2] = 0;
|
||||
}
|
||||
|
||||
this.currentValue = newValue;
|
||||
this.updateCityAndArea();
|
||||
},
|
||||
|
||||
handleClose() {
|
||||
this.$emit("cancel");
|
||||
},
|
||||
|
||||
handleConfirm() {
|
||||
const [provinceIndex, cityIndex, areaIndex] = this.currentValue;
|
||||
|
||||
this.$emit("confirm", [
|
||||
this.list[provinceIndex],
|
||||
this.cityList[cityIndex],
|
||||
this.areaList[areaIndex],
|
||||
]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-picker {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.picker-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
|
||||
.picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 52rpx 30rpx;
|
||||
|
||||
.header-hidden {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
// font-weight: 500;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 630rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 90rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 45rpx 45rpx 45rpx 45rpx;
|
||||
color: #fff;
|
||||
margin: 20rpx auto 60rpx;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
|
||||
.picker-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
359
src/components/ServiceRecord.vue
Normal file
359
src/components/ServiceRecord.vue
Normal file
@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<view class="service-record" :class="{ 'service-record--empty': !isLoading && recordList.length === 0 }">
|
||||
<scroll-view class="record-scroll" scroll-y :show-scrollbar="false" :enhanced="true" @scrolltolower="loadMore">
|
||||
<view class="record-list">
|
||||
<view class="record-item" v-for="(item, index) in recordList" :key="index" @click="goToDetail(item)">
|
||||
<view class="record-content">
|
||||
<view class="pet-info">
|
||||
<text class="pet-name">{{ petInfo.name || '--' }}</text>
|
||||
<text class="pet-gender">{{ formatGender(petInfo.gender) }}</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view class="order-info">
|
||||
<view class="info-row">
|
||||
<text class="label">订单编号</text>
|
||||
<text class="value">{{ item.order_no || '--' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">车牌号</text>
|
||||
<text class="value">{{ item.license_plate || '--' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">预约时间</text>
|
||||
<text class="value">{{ formatTimeRange(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-badge">
|
||||
<text class="status-text">预检</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="isLoading">
|
||||
<uni-load-more status="loading" :show-text="false" />
|
||||
</view>
|
||||
<view class="load-more" v-if="isNoMore && recordList.length > 0">
|
||||
<text class="no-more-text">没有更多了</text>
|
||||
</view>
|
||||
<view class="empty-state" v-if="!isLoading && recordList.length === 0">
|
||||
<image class="empty-img" :src="imgPrefix + 'certificatePlaceholder.png'" mode="widthFix" />
|
||||
<text class="empty-text">暂无记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPetPrecheckRecords } from "@/api/order";
|
||||
import { imgPrefix } from "@/utils/common";
|
||||
|
||||
export default {
|
||||
name: 'ServiceRecord',
|
||||
props: {
|
||||
// 宠物ID
|
||||
petId: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgPrefix,
|
||||
recordList: [],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
isLoading: false,
|
||||
isNoMore: false,
|
||||
petInfo: {}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
watch: {
|
||||
petId: {
|
||||
handler() {
|
||||
this.resetData();
|
||||
this.loadData();
|
||||
},
|
||||
immediate: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重置数据
|
||||
resetData() {
|
||||
this.page = 1;
|
||||
this.recordList = [];
|
||||
this.isNoMore = false;
|
||||
},
|
||||
// 加载数据
|
||||
loadData() {
|
||||
if (this.isLoading || this.isNoMore) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.petId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
getPetPrecheckRecords({
|
||||
pet_id: [this.petId],
|
||||
order_id: 0
|
||||
})
|
||||
.then((res) => {
|
||||
this.isLoading = false;
|
||||
if (res.code === 0 && res.data) {
|
||||
const list = res.data.list || []
|
||||
if (this.page === 1) {
|
||||
this.recordList = list;
|
||||
} else {
|
||||
this.recordList = [...this.recordList, ...list];
|
||||
}
|
||||
this.isNoMore = list.length < this.pageSize;
|
||||
this.petInfo = res.data.pet;
|
||||
} else {
|
||||
this.isNoMore = true;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.isLoading = false;
|
||||
console.error('获取服务记录失败:', err);
|
||||
uni.showToast({
|
||||
title: err?.msg || err?.message || '获取服务记录失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (this.isNoMore || this.isLoading) {
|
||||
return;
|
||||
}
|
||||
this.page++;
|
||||
this.loadData();
|
||||
},
|
||||
formatGender(val) {
|
||||
if (!val) return '--';
|
||||
const v = String(val).toLowerCase();
|
||||
if (v === 'male' || v === '男') return '男生';
|
||||
if (v === 'female' || v === '女') return '女生';
|
||||
return val;
|
||||
},
|
||||
formatTimeRange(item) {
|
||||
const time = item.reservation_time || item.created_at;
|
||||
if (!time) return '--';
|
||||
if (typeof time === 'string' && /^\d{4}-\d{2}-\d{2}[\s\S]+/.test(time)) {
|
||||
return time;
|
||||
}
|
||||
return this.formatTime(time);
|
||||
},
|
||||
formatTime(time) {
|
||||
if (!time) return '--';
|
||||
if (typeof time === 'number') {
|
||||
const date = new Date(time * 1000);
|
||||
return this.formatDateTime(date);
|
||||
}
|
||||
const date = new Date(time);
|
||||
return this.formatDateTime(date);
|
||||
},
|
||||
formatDateTime(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
},
|
||||
// 跳转到预检详情页面
|
||||
goToDetail(item) {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
// 获取订单ID,可能是 order_id 或 id
|
||||
const orderId = item.order_id || item.id;
|
||||
if (!orderId) {
|
||||
uni.showToast({
|
||||
title: '订单信息异常',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 跳转到预检详情页面(筛查报告)
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/screening/details?orderId=${orderId}&petId=${this.petId}`
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.service-record {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.service-record--empty {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.record-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
width: calc(100% - 40rpx);
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
padding: 0rpx 20rpx;
|
||||
padding-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 2rpx solid #FF19A0;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
.record-content {
|
||||
flex: 1;
|
||||
padding: 32rpx 88rpx 32rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
||||
.pet-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
flex-shrink: 0;
|
||||
width: 100rpx;
|
||||
|
||||
.pet-name {
|
||||
font-size: 24rpx;
|
||||
color: #272427;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.pet-gender {
|
||||
font-size: 24rpx;
|
||||
color: #9B939A;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 2rpx;
|
||||
height: 88rpx;
|
||||
background: #ececec;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
width: 100%;
|
||||
|
||||
.label {
|
||||
font-size: 24rpx;
|
||||
color: #9B939A;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #272427;
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
width: 60rpx;
|
||||
background: #FF19A0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
border-radius: 0 22rpx 22rpx 0;
|
||||
|
||||
.status-text {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0;
|
||||
|
||||
.no-more-text {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
|
||||
.empty-img {
|
||||
width: 426rpx;
|
||||
height: 320rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/components/SplitView.vue
Normal file
29
src/components/SplitView.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<view class="split-view" :style="{ height: height, background:bgColor }"></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
bgColor: {
|
||||
default: '#F0F0F0',
|
||||
type: String,
|
||||
},
|
||||
height: {
|
||||
default: '10rpx',
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.split-view {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
112
src/components/SuccessModal.vue
Normal file
112
src/components/SuccessModal.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<view class="pay-success-modal" @click.stop="">
|
||||
<view class="body-view" @click.stop="">
|
||||
<image
|
||||
v-if="showImg"
|
||||
:src="imgSrc"
|
||||
class="pay-success-img"
|
||||
:style="[imgStyle]"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="app-fc-main fs-40 app-font-bold-700">{{ title }}</text>
|
||||
<text class="app-fc-normal fs-28 tip-text">{{ message }}</text>
|
||||
<view class="handle-btn" @click.stop="okAction">
|
||||
<text class="app-fc-white fs-32">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
showImg: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
imgSrc: {
|
||||
type: String,
|
||||
default: require("@/static/images/success_icon.png"),
|
||||
},
|
||||
imgStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: "确定",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit("close");
|
||||
},
|
||||
okAction() {
|
||||
this.$emit("ok");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pay-success-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.body-view {
|
||||
width: calc(100% - 132rpx);
|
||||
padding: 60rpx 10rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 10vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.pay-success-img {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 42rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.handle-btn {
|
||||
margin-top: 52rpx;
|
||||
width: 216rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
176
src/components/TabBar.vue
Normal file
176
src/components/TabBar.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<view class="tab-bar" id="tab-bar">
|
||||
<view class="tab-bar-inner">
|
||||
<view v-for="item in tabList" :key="item.pageId" class="tab-item" :class="{
|
||||
'tab-item--active': activePageId === item.pageId,
|
||||
'tab-item--special': item.isSpecial
|
||||
}" @click="handleTabClick(item.pageId)">
|
||||
<image class="tab-icon" :src="activePageId === item.pageId ? item.selectedIconPath : item.iconPath"
|
||||
mode="aspectFit" />
|
||||
<text v-if="!item.isSpecial" class="tab-text"
|
||||
:class="activePageId === item.pageId ? 'tab-text--active' : 'tab-text--inactive'">
|
||||
{{ item.text }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* TabBar 配置
|
||||
* 集中管理所有 Tab 项的配置信息
|
||||
*/
|
||||
import {
|
||||
imgPrefix
|
||||
} from "@/utils/common";
|
||||
const TAB_CONFIG = [{
|
||||
pageId: "homePage",
|
||||
iconPath: `${imgPrefix}tabBar-home.png`,
|
||||
selectedIconPath: `${imgPrefix}tabBar-selectedHome.png`,
|
||||
text: "首页",
|
||||
isSpecial: false,
|
||||
},
|
||||
{
|
||||
pageId: "filesPage",
|
||||
iconPath: `${imgPrefix}tabBar-record.png`,
|
||||
selectedIconPath: `${imgPrefix}tabBar-selectedRecord.png`,
|
||||
text: "档案",
|
||||
isSpecial: false,
|
||||
},
|
||||
{
|
||||
pageId: "reservationPage",
|
||||
iconPath: `${imgPrefix}tabbar-reservation.gif`,
|
||||
selectedIconPath: `${imgPrefix}tabbar-bath.png`,
|
||||
text: "立即预约",
|
||||
isSpecial: true, // 特殊样式标记(中间突出的按钮)
|
||||
},
|
||||
{
|
||||
pageId: "shopPage",
|
||||
iconPath: `${imgPrefix}tabBar-mall.png`,
|
||||
selectedIconPath: `${imgPrefix}tabBar-selectdMall.png`,
|
||||
text: "商城",
|
||||
isSpecial: false,
|
||||
},
|
||||
{
|
||||
pageId: "minePage",
|
||||
iconPath: `${imgPrefix}tabBar-mine.png`,
|
||||
selectedIconPath: `${imgPrefix}tabBar-selectedMine.png`,
|
||||
text: "我的",
|
||||
isSpecial: false,
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
name: "TabBar",
|
||||
props: {
|
||||
activePageId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tabList() {
|
||||
return TAB_CONFIG;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 处理 Tab 点击事件
|
||||
* @param {String} pageId - 页面ID
|
||||
*/
|
||||
handleTabClick(pageId) {
|
||||
if (this.activePageId === pageId) {
|
||||
return; // 避免重复点击
|
||||
}
|
||||
this.$emit("changeTab", [pageId]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tab-bar {
|
||||
width: 100vw;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
left: var(--window-left);
|
||||
right: var(--window-right);
|
||||
bottom: 0;
|
||||
z-index: 998;
|
||||
background: #fff;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
border-radius: 32rpx 32rpx 0px 0px;
|
||||
box-shadow: 0px 0px 22px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.tab-bar-inner {
|
||||
background: #fff;
|
||||
border-radius: 16rpx 16rpx 0px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 0rpx 50rpx;
|
||||
padding-top: 24rpx;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
transition: all 0.3s ease;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
.tab-icon {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
margin-bottom: 9rpx;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 20rpx;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&--active {
|
||||
color: $app_fc_mark;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
color: #AAA2A9;
|
||||
}
|
||||
}
|
||||
|
||||
// 激活状态
|
||||
&--active {
|
||||
.tab-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊样式(中间突出的按钮)
|
||||
&--special {
|
||||
.tab-icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -76rpx;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
color: #FFFFFF;
|
||||
padding: 20rpx 22rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击反馈
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
192
src/components/TabsList.vue
Normal file
192
src/components/TabsList.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<view class="flex-row-start tabs-list" :style="[customStyle]">
|
||||
<view class="tabs-scroll-view">
|
||||
<scroll-view
|
||||
:scroll-x="isScroll"
|
||||
:scroll-with-animation="isScroll"
|
||||
:scroll-left="tabScrollWidth"
|
||||
@scroll="onTabScroll"
|
||||
>
|
||||
<view class="list-wrapper" :class="flexClass">
|
||||
<view
|
||||
class="tabs-item"
|
||||
:class="{ first: index === 0, last: index === list.length - 1 }"
|
||||
v-for="(item, index) in list"
|
||||
:key="item[idKey]"
|
||||
@click="changeTab(item, index)"
|
||||
>
|
||||
<view
|
||||
class="flex-center fs-28 item-inner"
|
||||
:class="{ 'fs-32 app-fc-main active ali-puhui-bold': index === curIndex }"
|
||||
>
|
||||
{{ item[nameKey] }}
|
||||
<view class="item-border" v-if="showBorder"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-if="showFilterIcon" class="tabs-right" @click="showFilterView">
|
||||
<!-- <image class="right-icon" src="https://oss.chuanglingmeng.com/cl/common/more.png" /> -->
|
||||
</view>
|
||||
<slot v-else name="right" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TabsList",
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isScroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
nameKey: {
|
||||
type: String,
|
||||
default: "name",
|
||||
},
|
||||
idKey: {
|
||||
type: String,
|
||||
default: "id",
|
||||
},
|
||||
flexClass: {
|
||||
type: String,
|
||||
default: "flex-row-start",
|
||||
},
|
||||
showFilterIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
curIndex: 0,
|
||||
tabScrollWidth: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
currentIndex: {
|
||||
handler(value) {
|
||||
this.curIndex = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
curIndex: {
|
||||
async handler(value) {
|
||||
if (!this.isScroll) return;
|
||||
// 计算scroll-view应该移动的距离
|
||||
const listData = await this.getElementStyle(".list-wrapper");
|
||||
const tabItemData = await this.getElementStyle(".tabs-item");
|
||||
const listWidth = listData?.[0]?.width || 0;
|
||||
const listLeft = listData?.[0]?.left || 0;
|
||||
const tabItemWidth = tabItemData?.[value]?.width || 0;
|
||||
const tabItemLeft = tabItemData?.[value]?.left || 0;
|
||||
this.tabScrollWidth =
|
||||
tabItemLeft + tabItemWidth / 2 - listWidth / 2 - listLeft;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onTabScroll() {},
|
||||
changeTab(item, index) {
|
||||
this.curIndex = index;
|
||||
this.$emit("change", item, index);
|
||||
},
|
||||
getElementStyle(el) {
|
||||
return new Promise((resolve) => {
|
||||
this.$nextTick(() => {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.selectAll(el)
|
||||
.boundingClientRect()
|
||||
.exec((data) => {
|
||||
resolve(data[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
showFilterView() {
|
||||
this.$emit("showFilterView");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabs-list {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
|
||||
border-top: 2rpx solid #f4f4f4;
|
||||
.tabs-scroll-view {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
.list-wrapper {
|
||||
padding: 0;
|
||||
.tabs-item {
|
||||
padding: 0 20rpx;
|
||||
height: 100rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
.item-inner {
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
color: #666666;
|
||||
|
||||
.item-border {
|
||||
position: absolute;
|
||||
bottom: 12rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 24rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 100px;
|
||||
background: transparent;
|
||||
}
|
||||
&.active {
|
||||
// font-weight: bold;
|
||||
color: $app_fc_main;
|
||||
.item-border {
|
||||
background: $app_color_main;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabs-right {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
background: #fff;
|
||||
box-shadow: -5rpx 1rpx 6rpx 0 rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.right-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
532
src/components/coupon/CouponItem.vue
Normal file
532
src/components/coupon/CouponItem.vue
Normal file
@ -0,0 +1,532 @@
|
||||
<template>
|
||||
<view class="coupon-item" :class="{ disabled: disabled, disabled: past}" @click.stop="$emit('jumpToDetails', data)">
|
||||
<view class="flex-row-start" :class="[data.expire_coupon == 1 ? 'item-top2' : 'item-top']">
|
||||
<view v-if="data.tab" class="tab-label">{{ data.tab }}</view>
|
||||
<image v-if="data.expire_coupon == 1" class="expireCoupons"
|
||||
src="https://activity.wagoo.live/expire-coupons.png" mode="aspectFit" />
|
||||
<image v-if="data.new_flag == 1" class="initiation" src="https://activity.wagoo.live/initiation-rite.png"
|
||||
mode="aspectFit" />
|
||||
<view class="coupon-content-wrapper">
|
||||
<view class="coupon-info-left">
|
||||
<text class="coupon-title">{{ data.coupon_title }}</text>
|
||||
<text class="coupon-time">
|
||||
有效期至{{ data.end_time || data.start_time }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="coupon-info-right">
|
||||
<view class="coupon-price-wrapper">
|
||||
<text class="coupon-price-number">{{ couponPrice }}</text>
|
||||
<text class="coupon-price-unit">元</text>
|
||||
</view>
|
||||
<text class="coupon-condition">
|
||||
{{ `${upPrice}` === "0" ? "无门槛立减" : `满${couponPrice}减${upPrice}` }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showOptBtn" class="flex-center app-fc-white item-btn" @click="useCoupon">
|
||||
{{ optBtnText }}
|
||||
</view>
|
||||
<view v-if="showSelectIcon && !showOptBtn" class="flex-center">
|
||||
<image v-if="isSelected" class="select-icon-img" src="@/static/images/cart_checked.png"
|
||||
mode="aspectFit" />
|
||||
<image v-else class="select-icon-img" src="@/static/images/unchecked.png" mode="aspectFit" />
|
||||
</view>
|
||||
<image v-if="disabled" class="used-stamp" :src="usedStampImg" mode="aspectFit" />
|
||||
<image v-if="past" class="used-stamp" :src="imgPrefix + 'pastDue.png'" mode="aspectFit" />
|
||||
<view v-else>
|
||||
<slot name="status" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-bottom">
|
||||
<view class="flex-row-between" @click.stop="showGuide = !showGuide">
|
||||
<view class="flex-row-start" @click.stop="showGuide = !showGuide">
|
||||
<text class="rules-label">使用规则</text>
|
||||
<image class="arrow-down" :class="{ 'arrow-up': showGuide }" src="@/static/images/arrow_up.png"
|
||||
mode="widthFix" />
|
||||
</view>
|
||||
<view v-if="showUseLink && !disabled && !past" class="use-btn" @click.stop="$emit('useCoupon', data)">
|
||||
<text class="use-btn-text">去使用</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showGuide" class="guide-details">
|
||||
{{ data.coupon_desc }}
|
||||
</view>
|
||||
<view class="circle"></view>
|
||||
<view class="circle right"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment";
|
||||
import { imgPrefix } from "@/utils/common";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
// 是否展示操作按钮:使用/领取
|
||||
showOptBtn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 操作按钮文本
|
||||
optBtnText: {
|
||||
type: String,
|
||||
default: "使用",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showCountDown: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showSelectIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 是否展示底部“去使用”按钮
|
||||
showUseLink: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
past: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showGuide: false,
|
||||
usedStampImg: require('@/pages/client/coupon/static/coupon_used.png'),
|
||||
imgPrefix,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
couponPrice() {
|
||||
return +this.data.coupon_amount || 0;
|
||||
},
|
||||
upPrice() {
|
||||
return +this.data.coupon_min_spend || 0;
|
||||
},
|
||||
startTime() {
|
||||
return moment(this.data.add_time * 1000).format("YYYY-MM-DD");
|
||||
},
|
||||
endTime() {
|
||||
return moment(this.data.guoqi_time ? this.data.guoqi_time * 1000 : this.data.end_time * 1000).format(
|
||||
"YYYY-MM-DD");
|
||||
},
|
||||
time() {
|
||||
const seconds = this.data.daojishi || 0;
|
||||
const day = Math.floor(seconds / 3600 / 24) || 0;
|
||||
const hour = Math.floor((seconds - day * 24 * 3600) / 3600) || 0;
|
||||
const minutes = Math.floor((seconds - hour * 3600) / 60) || 0;
|
||||
const second = seconds - hour * 3600 - minutes * 60 || 0;
|
||||
return {
|
||||
day,
|
||||
hour,
|
||||
minutes,
|
||||
second,
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.data, '???22')
|
||||
},
|
||||
methods: {
|
||||
|
||||
useCoupon() {
|
||||
this.$emit("useCoupon", this.data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-item {
|
||||
width: 686rpx;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: flex-start;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
border-radius: 16rpx;
|
||||
|
||||
&.disabled,
|
||||
&.expired {
|
||||
.tab-label {
|
||||
background: #AFA5AE !important;
|
||||
border-radius: 8px 0px 8px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.expired {
|
||||
.item-top2 .tab-label {
|
||||
background: #AFA5AE !important;
|
||||
border-radius: 8px 0px 8px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
||||
.coupon-title {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
color: #bbb !important;
|
||||
}
|
||||
|
||||
.coupon-price-number,
|
||||
.coupon-price-unit,
|
||||
.coupon-condition {
|
||||
color: #bdbdbd !important;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
border-top: 2rpx dashed #e0e0e0;
|
||||
}
|
||||
|
||||
.rules-label,
|
||||
.guide-details {
|
||||
color: #bbb !important;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.item-top {
|
||||
flex: 1;
|
||||
padding: 40rpx 32rpx 28rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.tab-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 10rpx 24rpx;
|
||||
background: #FF19A0;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
z-index: 2;
|
||||
border-radius: 8px 0px 8px 0px;
|
||||
}
|
||||
|
||||
.initiation {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 69px;
|
||||
height: 15px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.coupon-content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-left: 0;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.coupon-info-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.coupon-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #272427;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 12rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: 24rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
.coupon-price-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 1;
|
||||
|
||||
.coupon-price-number {
|
||||
font-size: 64rpx;
|
||||
font-weight: 500;
|
||||
color: #FF19A0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.coupon-price-unit {
|
||||
font-size: 24rpx;
|
||||
color: #FF19A0;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-condition {
|
||||
font-size: 24rpx;
|
||||
color: #FF19A0;
|
||||
margin-top: 8rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.item-btn {
|
||||
width: 116rpx;
|
||||
height: 60rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 60rpx;
|
||||
margin-left: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.select-icon-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.used-stamp {
|
||||
position: absolute;
|
||||
left: 60%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.item-top2 {
|
||||
flex: 1;
|
||||
padding: 40rpx 32rpx 28rpx;
|
||||
opacity: 0.6;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.tab-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 10rpx 24rpx;
|
||||
background: #FF19A0;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
z-index: 2;
|
||||
border-top-left-radius: 40rpx;
|
||||
border-bottom-left-radius: 40rpx;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.initiation {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 69px;
|
||||
height: 15px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.expireCoupons {
|
||||
position: absolute;
|
||||
right: 47rpx;
|
||||
top: 46rpx;
|
||||
width: 112rpx;
|
||||
height: 70rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.coupon-content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-left: 0;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.coupon-info-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.coupon-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #272427;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 12rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: 24rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
.coupon-price-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
line-height: 1;
|
||||
|
||||
.coupon-price-number {
|
||||
font-size: 64rpx;
|
||||
font-weight: 500;
|
||||
color: #FF19A0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.coupon-price-unit {
|
||||
font-size: 24rpx;
|
||||
color: #FF19A0;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-condition {
|
||||
font-size: 24rpx;
|
||||
color: #FF19A0;
|
||||
margin-top: 8rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.item-btn {
|
||||
width: 116rpx;
|
||||
height: 60rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 60rpx;
|
||||
margin-left: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.select-icon-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.used-stamp {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
border-top: 2rpx solid #f7f3f7;
|
||||
margin: 0 32rpx;
|
||||
padding: 20rpx 0;
|
||||
position: relative;
|
||||
|
||||
.rules-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
transform: rotate(180deg);
|
||||
margin-left: 12rpx;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.reset-time {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.guide-details {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin: 20rpx 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: -32rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: #ffecf3;
|
||||
clip-path: polygon(0 0, 0 100%, 100% 50%);
|
||||
|
||||
&.right {
|
||||
right: -32rpx;
|
||||
left: unset;
|
||||
transform: translate(50%, -50%);
|
||||
clip-path: polygon(100% 0, 100% 100%, 0 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.use-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #FF19A0;
|
||||
border-radius: 100rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.use-btn-text {
|
||||
font-size: 20rpx;
|
||||
color: #FFFFFF;
|
||||
text-shadow: 0 0 8rpx rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
src/components/coupon/RechargeCouponModal.vue
Normal file
165
src/components/coupon/RechargeCouponModal.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<view>
|
||||
<select-modal class="coupon-list-modal" title="优惠券" @close="$emit('close', false)">
|
||||
<!-- <view>{{couponList.title}}</view> -->
|
||||
<image class="coupon-icon" src="https://activity.wagoo.live/coupon-icon.png" />
|
||||
<view class="coupon-list-content">
|
||||
|
||||
<text class="fs-28 app-fc-normal coupon-list-title">
|
||||
待使用({{ couponList.length }})
|
||||
</text>
|
||||
<view
|
||||
v-for="item in couponList"
|
||||
:key="item.distribution_id || item.id || item"
|
||||
class="coupon-item-wrapper"
|
||||
>
|
||||
<coupon-item
|
||||
:data="item"
|
||||
@useCoupon="useCoupon"
|
||||
@jumpToDetails="useCoupon"
|
||||
:showOptBtn="showOptBtn"
|
||||
:is-selected="selectedItem.distribution_id === item.distribution_id"
|
||||
:show-select-icon="true"
|
||||
:show-use-link="false"
|
||||
/>
|
||||
</view>
|
||||
<!-- <template v-else>
|
||||
<coupon-item
|
||||
v-for="item in availableList"
|
||||
:key="item"
|
||||
:data="item"
|
||||
@useCoupon="useCoupon"
|
||||
@jumpToDetails="useCoupon"
|
||||
:showOptBtn="showOptBtn"
|
||||
:is-selected="selectedItem.fafang_id === item.fafang_id"
|
||||
:show-select-icon="true"
|
||||
/>
|
||||
<coupon-item
|
||||
v-for="item in disableList"
|
||||
:key="item"
|
||||
:data="item"
|
||||
:showOptBtn="showOptBtn"
|
||||
:disabled="true"
|
||||
:show-select-icon="false"
|
||||
/>
|
||||
</template> -->
|
||||
|
||||
</view>
|
||||
</select-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "../select-modal.vue";
|
||||
import CouponItem from "./CouponItem.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
couponList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isShowAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
showOptBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SelectModal,
|
||||
CouponItem,
|
||||
},
|
||||
options: {
|
||||
styleIsolation: "shared",
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showGuide: false,
|
||||
couponGuide: "",
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
//可用列表
|
||||
availableList() {
|
||||
return this.couponList.filter((data) => Number(this.price) >= Number(data.up_money))
|
||||
},
|
||||
//不可以列表
|
||||
disableList() {
|
||||
return this.couponList.filter((data) => Number(this.price) < Number(data.up_money))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.couponList, '???11')
|
||||
},
|
||||
methods: {
|
||||
useCoupon(data) {
|
||||
this.$emit("useCoupon", data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-list-modal {
|
||||
.coupon-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
width: 218rpx;
|
||||
height: 210rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.coupon-list-content {
|
||||
padding: 44rpx 32rpx 24rpx;
|
||||
max-height: 956rpx;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.coupon-list-title {
|
||||
display: block;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.coupon-item-wrapper + .coupon-item-wrapper {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.selected-modal .model-container {
|
||||
background: #fff0f5;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-guide-modal {
|
||||
::v-deep {
|
||||
.use-guide-modal {
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.guide-content {
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
179
src/components/coupon/ServiceCouponCell.vue
Normal file
179
src/components/coupon/ServiceCouponCell.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<view class="service-coupon" @click.stop="$emit('jumpToDetails', serviceCoupon)">
|
||||
<view class="service-img">
|
||||
<image :src="serviceCoupon.fuwuquan_pic" mode="aspectFill"/>
|
||||
</view>
|
||||
<view class="service-info">
|
||||
<view class="service-name">
|
||||
<text class="fs-28 app-fc-main ali-puhui-bold">
|
||||
{{ serviceCoupon.name }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="service-tags">
|
||||
<text v-for="(item, index) in serviceTags" :key="index" class="tag fs-20" :class="{'active-tag': index===0}">
|
||||
{{item}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="service-weight fs-24">适用于{{ serviceCoupon.weight_name }}</view>
|
||||
<view class="service-price-info">
|
||||
<view class="price-container">
|
||||
<text class="current-price-unit fs-24 ali-puhui-bold">¥</text>
|
||||
<text class="current-price fs-44 ali-puhui-bold">{{ serviceCoupon.price }}</text>
|
||||
<text class="original-price fs-24">¥{{ serviceCoupon.old_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="service-sales-info">
|
||||
<view class="sales-info fs-20 ali-puhui-regular">
|
||||
已售 {{ serviceCoupon.xiaoliang }} | 剩余 {{ serviceCoupon.kucun }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="buy-btn" @click.stop="buyService">购买</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
serviceCoupon: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
serviceTags() {
|
||||
if (this.serviceCoupon?.label) {
|
||||
return this.serviceCoupon.label.split(',').filter(v => !!v)
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buyService() {
|
||||
this.$emit('buyService', this.serviceCoupon)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.service-coupon {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.service-img {
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
margin-right: 24rpx;
|
||||
border-radius: 40rpx;
|
||||
overflow: hidden;
|
||||
background-color: #FFF0F5;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.service-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
.service-name {
|
||||
// font-weight: bold;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.service-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.tag {
|
||||
border: 1rpx solid #FE019B;
|
||||
background-color: #FFF;
|
||||
color: #FE019B;
|
||||
padding: 2rpx 6rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.active-tag {
|
||||
background-color: #FE019B;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.service-weight {
|
||||
color: #726E71;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.service-price-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.price-container {
|
||||
.current-price-unit {
|
||||
color: #FE019B;
|
||||
// font-weight: bold;
|
||||
}
|
||||
|
||||
.current-price {
|
||||
color: #FE019B;
|
||||
// font-weight: bold;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
color: #726E71;
|
||||
text-decoration: line-through;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.service-sales-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.sales-info {
|
||||
flex: 1;
|
||||
color: #9B939A;
|
||||
// font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buy-btn {
|
||||
position: absolute;
|
||||
bottom: 18rpx;
|
||||
right: 24rpx;
|
||||
width: 136rpx;
|
||||
height: 60rpx;
|
||||
background-color: #FE019B;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
border-radius: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
238
src/components/coupon/ServiceCouponItem.vue
Normal file
238
src/components/coupon/ServiceCouponItem.vue
Normal file
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<view
|
||||
class="coupon-item"
|
||||
:class="{ disabled: disabled }"
|
||||
@click.stop="$emit('jumpToDetails', data)"
|
||||
>
|
||||
<view class="flex-row-start item-top">
|
||||
<view class="flex-center coupon-left">
|
||||
<text class="fs-24 app-fc-danger app-font-bold coupon-price">
|
||||
¥<text class="fs-52 app-fc-danger app-font-bold coupon-price">{{
|
||||
couponPrice
|
||||
}}</text>
|
||||
</text>
|
||||
<text class="fs-24 app-fc-normal coupon-tip">
|
||||
适用于{{ data.weight_name || "" }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex-center flex-1 coupon-info">
|
||||
<text class="fs-32 app-fc-main">{{ data.name }}</text>
|
||||
<text class="fs-24 app-fc-normal coupon-time">
|
||||
{{ startTime }}至{{ endTime }}
|
||||
</text>
|
||||
<view v-if="showOrderBtn" class="flex-row-end order-btn-wrapper">
|
||||
<view
|
||||
class="fs-28 app-fc-white flex-center order-btn"
|
||||
@click="jumpToReservation"
|
||||
>
|
||||
立即预约
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showOptBtn"
|
||||
class="flex-center app-fc-white item-btn"
|
||||
@click="useCoupon"
|
||||
>
|
||||
{{ optBtnText }}
|
||||
</view>
|
||||
<view v-if="showSelectIcon && !showOptBtn" class="flex-center">
|
||||
<image
|
||||
v-if="isSelected"
|
||||
class="select-icon-img"
|
||||
src="@/static/images/cart_checked.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<image
|
||||
v-else
|
||||
class="select-icon-img"
|
||||
src="@/static/images/unchecked.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<slot name="status" />
|
||||
</view>
|
||||
<view class="item-bottom" v-if="data.desc">
|
||||
<view class="flex-row-start" @click="showGuide = !showGuide">
|
||||
<text class="fs-24 app-fc-normal">使用规则</text>
|
||||
<image
|
||||
class="arrow-down"
|
||||
:class="{ 'arrow-up': showGuide }"
|
||||
src="@/static/images/arrow_up.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="showGuide" class="fs-26 app-fc-normal guide-details">
|
||||
{{ data.desc }}
|
||||
</view>
|
||||
<view class="circle"></view>
|
||||
<view class="circle right"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment";
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
// 是否展示操作按钮:使用/领取
|
||||
showOptBtn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 是否展示预约按钮
|
||||
showOrderBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showSelectIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 操作按钮文本
|
||||
optBtnText: {
|
||||
type: String,
|
||||
default: "使用",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showGuide: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
couponPrice() {
|
||||
return +this.data.price || 0;
|
||||
},
|
||||
startTime() {
|
||||
return moment(this.data.add_time * 1000).format("YYYY-MM-DD");
|
||||
},
|
||||
endTime() {
|
||||
return moment(this.data.youxiao_time * 1000).format("YYYY-MM-DD");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
useCoupon() {
|
||||
this.$emit("useCoupon", this.data);
|
||||
},
|
||||
jumpToReservation() {
|
||||
uni.navigateTo({
|
||||
url: "/pageHome/reservation/index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-item {
|
||||
width: 686rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 40rpx 40rpx 40rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: flex-start;
|
||||
align-items: stretch;
|
||||
margin-top: 24rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.item-top {
|
||||
flex: 1;
|
||||
padding: 40rpx;
|
||||
.item-btn {
|
||||
width: 116rpx;
|
||||
height: 60rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 60rpx;
|
||||
}
|
||||
.coupon-tip {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
.coupon-info {
|
||||
align-items: flex-start;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
|
||||
.coupon-time {
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.order-btn-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.order-btn {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 10rpx;
|
||||
background: $app_color_main;
|
||||
margin-top: 18rpx;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-left {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.select-icon-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
border-top: 2rpx solid #f7f3f7;
|
||||
margin: 0 32rpx;
|
||||
padding: 20rpx 0;
|
||||
position: relative;
|
||||
|
||||
.arrow-down {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
transform: rotate(180deg);
|
||||
margin-left: 12rpx;
|
||||
|
||||
&.arrow-up {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.guide-details {
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: -32rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
background: #fff0f5;
|
||||
border-radius: 30rpx;
|
||||
|
||||
&.right {
|
||||
right: -32rpx;
|
||||
left: unset;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
141
src/components/coupon/ServiceCouponModal.vue
Normal file
141
src/components/coupon/ServiceCouponModal.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<view>
|
||||
<select-modal
|
||||
class="coupon-list-modal"
|
||||
title="服务券"
|
||||
@close="$emit('close', false)"
|
||||
>
|
||||
<image class="coupon-icon" src="https://activity.wagoo.live/coupon-icon.png"/>
|
||||
<view class="coupon-list-content">
|
||||
<text class="fs-28 app-fc-normal coupon-list-title">
|
||||
待使用({{ availableList.length || 0 }})
|
||||
</text>
|
||||
|
||||
<ServiceCouponItem
|
||||
v-for="item in availableList"
|
||||
:key="item"
|
||||
:data="item"
|
||||
@jumpToDetails="useService"
|
||||
@useCoupon="useService"
|
||||
:showOptBtn="false"
|
||||
:disabled="false"
|
||||
:is-selected="selectedItem.order_id === item.order_id"
|
||||
:show-select-icon="true"
|
||||
/>
|
||||
<ServiceCouponItem
|
||||
v-for="item in disableList"
|
||||
:key="item"
|
||||
:data="item"
|
||||
:showOptBtn="false"
|
||||
:disabled="true"
|
||||
:show-select-icon="false"
|
||||
/>
|
||||
</view>
|
||||
</select-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "../select-modal.vue";
|
||||
import ServiceCouponItem from "@/components/coupon/ServiceCouponItem.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
serviceList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
weightId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ServiceCouponItem,
|
||||
SelectModal,
|
||||
},
|
||||
options: {
|
||||
styleIsolation: "shared",
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showGuide: false,
|
||||
couponGuide: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
//可用列表
|
||||
availableList() {
|
||||
return this.serviceList.filter((data) => this.weightId === data.weight_id && Number(this.price) >= Number(data.price))
|
||||
},
|
||||
//不可以列表
|
||||
disableList() {
|
||||
return this.serviceList.filter((data) => this.weightId !== data.weight_id || Number(this.price) < Number(data.price))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
useService(data) {
|
||||
this.$emit("useService", data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-list-modal {
|
||||
.coupon-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 40rpx;
|
||||
width: 218rpx;
|
||||
height: 210rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.coupon-list-content {
|
||||
padding: 44rpx 32rpx 24rpx;
|
||||
max-height: 956rpx;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.coupon-list-title {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.selected-modal .model-container {
|
||||
background: #fff0f5;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-guide-modal {
|
||||
::v-deep {
|
||||
.use-guide-modal {
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.guide-content {
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
src/components/coupon/UseGuideModal.vue
Normal file
95
src/components/coupon/UseGuideModal.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<view class="use-guide-modal" @click.stop="">
|
||||
<view class="body-view" @click.stop="">
|
||||
<text class="app-fc-main fs-36 app-font-bold-700">{{ title }}</text>
|
||||
<view class="guide-content" v-html="content"></view>
|
||||
<view class="flex-center handle-btn" @click.stop="okAction">
|
||||
<text class="app-fc-white fs-32">{{ confirmText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: "确定",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
okAction() {
|
||||
this.$emit("ok");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.use-guide-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.body-view {
|
||||
width: calc(100% - 132rpx);
|
||||
padding: 60rpx 10rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 10vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.pay-success-img {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 42rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.handle-btn {
|
||||
margin-top: 52rpx;
|
||||
width: 490rpx;
|
||||
height: 92rpx;
|
||||
background: $app_color_main;
|
||||
border-radius: 92rpx;
|
||||
}
|
||||
|
||||
.guide-content {
|
||||
width: 100%;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
max-height: 500rpx;
|
||||
min-height: 200rpx;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,27 @@
|
||||
.picker-view {
|
||||
height: 356rpx;
|
||||
padding: 0 110rpx;
|
||||
}
|
||||
|
||||
.picker-view-column {
|
||||
font-size: 14px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
color: #3E4055;
|
||||
font-family: PingFangSC-Bold;
|
||||
}
|
||||
|
||||
.picker-view-column-innner {
|
||||
/* height: calc(356rpx / 3);
|
||||
line-height: calc(356rpx / 3); */
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
/* 覆盖默认样式,样式可以按需自己改 */
|
||||
.uni-picker-view-indicator {
|
||||
background-color: rgba(106, 123, 255, 0.1);
|
||||
}
|
||||
.uni-picker-view-indicator::before,
|
||||
.uni-picker-view-indicator::after {
|
||||
content: none;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
// 所有列选项数据
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 每一列默认选中值数组,不传默认选中第一项
|
||||
selectVals: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 每一列选中项的索引,当默认选中值变化的时候这个值也要变化
|
||||
indexArr: {
|
||||
// 多维数组,深度监听
|
||||
cache: false,
|
||||
get() {
|
||||
if (this.selectVals.length > 0) {
|
||||
return this.columns.map((col, cIdx) => {
|
||||
return col.findIndex((i) => i == this.selectVals[cIdx]);
|
||||
});
|
||||
} else {
|
||||
return [].fill(0, 0, this.columns.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(e) {
|
||||
const { value } = e.detail;
|
||||
|
||||
let ret = this.columns.map((item, index) => {
|
||||
let idx = value[index];
|
||||
if (idx < 0) {
|
||||
idx = 0;
|
||||
}
|
||||
if (idx > item.length - 1) {
|
||||
idx = item.length - 1;
|
||||
}
|
||||
return item[idx];
|
||||
});
|
||||
|
||||
this.$emit('onChange', {
|
||||
value: ret
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<picker-view class="picker-view" :value="indexArr" @change="onChange" :indicator-style="`height: 60px`">
|
||||
<picker-view-column class="picker-view-column" v-for="(col, colIdx) in columns" :key="colIdx">
|
||||
<view class="picker-view-column-innner" v-for="(item, idx) in col" :key="idx">{{ item }}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</template>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
|
||||
<style lang="css" scoped src="./index.css"></style>
|
||||
57
src/components/dengrq-datetime-picker/dateSelector/index.css
Normal file
57
src/components/dengrq-datetime-picker/dateSelector/index.css
Normal file
@ -0,0 +1,57 @@
|
||||
.date-selector {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.select-date-wrapper {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select-date {
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
border-radius: 2px;
|
||||
border: 1rpx solidrgba(6, 7, 46, 0.05);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.select-date.active {
|
||||
border-color: #6a7bff;
|
||||
}
|
||||
|
||||
.select-date-placeholder {
|
||||
color: rgba(6, 7, 46, 0.3);
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
margin: 48rpx 0;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
width: 180px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: rgba(33, 58, 255, 0.85);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
width: 144px;
|
||||
height: 40px;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1rpx solid#979797;
|
||||
font-size: 14px;
|
||||
color: #06072e;
|
||||
}
|
||||
209
src/components/dengrq-datetime-picker/dateSelector/index.js
Normal file
209
src/components/dengrq-datetime-picker/dateSelector/index.js
Normal file
@ -0,0 +1,209 @@
|
||||
import DateTimePicker from '../dateTimePicker/index.vue';
|
||||
import DateUtil from '../dateTimePicker/dateUtil';
|
||||
import { DATE_TYPES } from '../dateTimePicker/constant';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DateTimePicker
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showStartDatePicker: false,
|
||||
showEndDatePicker: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
activeDate: 'startDate' // 正在处理哪一个日期值,startDate/endDate
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 日期筛选模式,1:年月日(默认),2:年月,3:年,4:年月日时分秒,5:时分秒,6:时分
|
||||
mode: {
|
||||
type: Number,
|
||||
default: DATE_TYPES.YMD
|
||||
},
|
||||
// 默认开始日期
|
||||
defaultStartDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 默认结束日期
|
||||
defaultEndDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 可选的最小日期
|
||||
minDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 可选的最大日期
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mode() {
|
||||
// 筛选模式更换时清空一下数据
|
||||
this.resetData();
|
||||
},
|
||||
startDate() {
|
||||
this.$emit('onChange', {
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate
|
||||
});
|
||||
},
|
||||
endDate() {
|
||||
this.$emit('onChange', {
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate
|
||||
});
|
||||
},
|
||||
defaultStartDate: {
|
||||
handler(defaultStartDate) {
|
||||
if (!defaultStartDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode == DATE_TYPES.HMS || this.mode == DATE_TYPES.HM) {
|
||||
console.error('时分秒/时分模式不支持设置默认开始时间');
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateUtil.isBefore(defaultStartDate, this.minDate)) {
|
||||
console.warn(
|
||||
`默认开始日期不可小于最小可选日期,已把默认开始日期设为最小可选日期。默认开始日期:${defaultStartDate},最小可选日期:${this.minDate}`
|
||||
);
|
||||
this.startDate = this.getModeFormatDateString(this.minDate);
|
||||
} else {
|
||||
this.startDate = this.getModeFormatDateString(defaultStartDate);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
defaultEndDate: {
|
||||
handler(defaultEndDate) {
|
||||
if (!defaultEndDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode == DATE_TYPES.HMS || this.mode == DATE_TYPES.HM) {
|
||||
console.error('时分秒/时分模式不支持设置默认结束时间');
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateUtil.isAfter(defaultEndDate, this.maxDate)) {
|
||||
console.warn(
|
||||
`默认结束日期不可大于最大可选日期,已把默认结束日期设为最大可选日期。默认结束日期:${defaultEndDate},最大可选日期:${this.maxDate}`
|
||||
);
|
||||
this.endDate = this.getModeFormatDateString(this.maxDate);
|
||||
} else {
|
||||
this.endDate = this.getModeFormatDateString(defaultEndDate);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
minDate(val) {
|
||||
if ((val && this.mode == DATE_TYPES.HMS) || this.mode == DATE_TYPES.HM) {
|
||||
console.error('时分秒/时分模式不支持设置最小可选时间');
|
||||
return;
|
||||
}
|
||||
},
|
||||
maxDate(val) {
|
||||
if ((val && this.mode == DATE_TYPES.HMS) || this.mode == DATE_TYPES.HM) {
|
||||
console.error('时分秒/时分模式不支持设置最大可选时间');
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onTapStartDate() {
|
||||
this.showEndDatePicker = false;
|
||||
if (!this.startDate) {
|
||||
this.startDate = this.getModeFormatDateString(new Date());
|
||||
}
|
||||
this.activeDate = 'startDate';
|
||||
this.showStartDatePicker = true;
|
||||
},
|
||||
onTapEndDate() {
|
||||
this.showStartDatePicker = false;
|
||||
if (!this.endDate) {
|
||||
this.endDate = this.startDate;
|
||||
}
|
||||
this.activeDate = 'endDate';
|
||||
this.showEndDatePicker = true;
|
||||
},
|
||||
onChangeStartDate(date) {
|
||||
this.startDate = date;
|
||||
},
|
||||
onChangeEndDate(date) {
|
||||
this.endDate = date;
|
||||
},
|
||||
validateInput() {
|
||||
if (!this.startDate) {
|
||||
uni.showToast({
|
||||
title: '请选择开始时间',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
} else if (!this.endDate) {
|
||||
uni.showToast({
|
||||
title: '请选择结束时间',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
} else if (DateUtil.isAfter(this.startDate, this.endDate)) {
|
||||
uni.showToast({
|
||||
title: '结束时间不能小于开始时间',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onCancel() {
|
||||
this.resetData();
|
||||
},
|
||||
onConfirm() {
|
||||
if (this.validateInput()) {
|
||||
this.$emit('onSubmit', {
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate
|
||||
});
|
||||
this.showStartDatePicker = false;
|
||||
this.showEndDatePicker = false;
|
||||
}
|
||||
},
|
||||
resetData() {
|
||||
this.startDate = '';
|
||||
this.endDate = '';
|
||||
this.activeDate = 'startDate';
|
||||
this.showStartDatePicker = false;
|
||||
this.showEndDatePicker = false;
|
||||
},
|
||||
// 返回对应日期模式的时间字符串
|
||||
getModeFormatDateString(date) {
|
||||
let fmt = 'YYYY-MM-DD';
|
||||
switch (this.mode) {
|
||||
case DATE_TYPES.YM:
|
||||
fmt = 'YYYY-MM';
|
||||
break;
|
||||
case DATE_TYPES.Y:
|
||||
fmt = 'YYYY';
|
||||
break;
|
||||
case DATE_TYPES['YMD-HMS']:
|
||||
fmt = 'YYYY-MM-DD HH:mm:ss';
|
||||
break;
|
||||
case DATE_TYPES.HMS:
|
||||
fmt = 'HH:mm:ss';
|
||||
break;
|
||||
case DATE_TYPES.HM:
|
||||
fmt = 'HH:mm';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return DateUtil.formatDate(date, fmt);
|
||||
}
|
||||
}
|
||||
};
|
||||
41
src/components/dengrq-datetime-picker/dateSelector/index.vue
Normal file
41
src/components/dengrq-datetime-picker/dateSelector/index.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view class="date-selector">
|
||||
<view class="select-date-wrapper">
|
||||
<view class="select-date" :class="{ active: activeDate == 'startDate' }" @tap="onTapStartDate">
|
||||
<view class="select-date-value" v-if="startDate">{{ startDate }}</view>
|
||||
<view class="select-date-placeholder" v-else>请选择时间</view>
|
||||
</view>
|
||||
<view style="margin: 0 16px">至</view>
|
||||
<view class="select-date" :class="{ active: activeDate == 'endDate' }" @tap="onTapEndDate">
|
||||
<view class="select-date-value" v-if="endDate">{{ endDate }}</view>
|
||||
<view class="select-date-placeholder" v-else>请选择时间</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<DateTimePicker
|
||||
v-if="showStartDatePicker"
|
||||
@onChange="onChangeStartDate"
|
||||
:defaultDate="startDate"
|
||||
:minDate="minDate || ''"
|
||||
:maxDate="endDate || maxDate || ''"
|
||||
:mode="mode"
|
||||
/>
|
||||
<DateTimePicker
|
||||
v-if="showEndDatePicker"
|
||||
@onChange="onChangeEndDate"
|
||||
:defaultDate="endDate"
|
||||
:minDate="startDate || minDate || ''"
|
||||
:maxDate="maxDate || ''"
|
||||
:mode="mode"
|
||||
/>
|
||||
|
||||
<view class="btn-group" v-if="showStartDatePicker || showEndDatePicker">
|
||||
<view class="btn-cancel" @tap="onCancel">取消</view>
|
||||
<view class="btn-confirm" @tap="onConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
|
||||
<style lang="css" scoped src="./index.css"></style>
|
||||
@ -0,0 +1,15 @@
|
||||
// 日期模式
|
||||
export const DATE_TYPES = {
|
||||
// 年月日
|
||||
YMD: 1,
|
||||
// 年月
|
||||
YM: 2,
|
||||
// 年份
|
||||
Y: 3,
|
||||
// 年月日时分秒
|
||||
'YMD-HM': 4,
|
||||
// 时分秒
|
||||
HMS: 5,
|
||||
// 时分
|
||||
HM: 6
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 日期时间格式化
|
||||
* @param {Date} date 要格式化的日期对象
|
||||
* @param {String} fmt 格式化字符串,eg:YYYY-MM-DD HH:mm:ss
|
||||
* @returns 格式化后的日期字符串
|
||||
*/
|
||||
function formatDate(date, fmt) {
|
||||
if (typeof date == 'string') {
|
||||
date = new Date(handleDateStr(date));
|
||||
}
|
||||
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'd+': date.getDate(), // 日
|
||||
'D+': date.getDate(), // 日
|
||||
'H+': date.getHours(), // 小时
|
||||
'h+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
};
|
||||
|
||||
if (/([y|Y]+)/.test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').slice(4 - RegExp.$1.length));
|
||||
}
|
||||
|
||||
for (const k in o) {
|
||||
if (new RegExp('(' + k + ')').test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).slice(('' + o[k]).length));
|
||||
}
|
||||
}
|
||||
|
||||
return fmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理时间字符串,兼容ios下new Date()返回NaN问题
|
||||
* @param {*} dateStr 日期字符串
|
||||
* @returns
|
||||
*/
|
||||
function handleDateStr(dateStr) {
|
||||
return dateStr.replace(/\-/g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断日期1是否在日期2之前,即日期1小于日期2
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
* @returns
|
||||
*/
|
||||
function isBefore(date1, date2) {
|
||||
if (typeof date1 == 'string') {
|
||||
date1 = new Date(handleDateStr(date1));
|
||||
}
|
||||
if (typeof date2 == 'string') {
|
||||
date2 = new Date(handleDateStr(date2));
|
||||
}
|
||||
return date1.getTime() < date2.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断日期1是否在日期2之后,即日期1大于日期2
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
* @returns
|
||||
*/
|
||||
function isAfter(date1, date2) {
|
||||
if (typeof date1 == 'string') {
|
||||
date1 = new Date(handleDateStr(date1));
|
||||
}
|
||||
if (typeof date2 == 'string') {
|
||||
date2 = new Date(handleDateStr(date2));
|
||||
}
|
||||
return date1.getTime() > date2.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查传入的字符串是否能转换为有效的Date对象
|
||||
* @param {String} date
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function isValid(date) {
|
||||
return new Date(date) !== 'Invalid Date' && !isNaN(new Date(date));
|
||||
}
|
||||
|
||||
export default {
|
||||
formatDate,
|
||||
handleDateStr,
|
||||
isBefore,
|
||||
isAfter,
|
||||
isValid
|
||||
};
|
||||
377
src/components/dengrq-datetime-picker/dateTimePicker/index.js
Normal file
377
src/components/dengrq-datetime-picker/dateTimePicker/index.js
Normal file
@ -0,0 +1,377 @@
|
||||
import CustomPickerView from '../customPickerView/index.vue';
|
||||
import DateUtil from '../dateTimePicker/dateUtil';
|
||||
import { DATE_TYPES } from './constant';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomPickerView
|
||||
},
|
||||
props: {
|
||||
// 日期模式,1:年月日(默认),2:年月,3:年份,4:年月日时分,5:时分秒,6:时分
|
||||
mode: {
|
||||
type: Number,
|
||||
default: DATE_TYPES.YMD
|
||||
},
|
||||
// 可选的最小日期,默认十年前
|
||||
minDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 可选的最大日期,默认十年后
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 默认选中日期(注意要跟日期模式对应)
|
||||
defaultDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectYear: new Date().getFullYear(),
|
||||
selectMonth: new Date().getMonth() + 1, // 选中的月份,1~12
|
||||
selectDay: new Date().getDate(),
|
||||
selectHour: new Date().getHours(),
|
||||
selectMinute: new Date().getMinutes(),
|
||||
selectSecond: new Date().getSeconds()
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
defaultDate: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val) {
|
||||
if (this.mode == DATE_TYPES.YM && val.replace(/\-/g, '/').split('/').length == 2) {
|
||||
// 日期模式为年月时有可能传进来的defaultDate是2022-02这样的格式,在ios下new Date会报错,加上日期部分做兼容
|
||||
val += '-01';
|
||||
} else if (this.mode == DATE_TYPES.HMS || this.mode == DATE_TYPES.HM) {
|
||||
// 只有时分秒或者只有时分是不能调用new Date生成Date对象的,先加上一个假设的年月日(就取当年一月一日)来兼容
|
||||
const now = new Date();
|
||||
val = `${now.getFullYear()}-01-01 ${val}`;
|
||||
}
|
||||
|
||||
let date = new Date(DateUtil.handleDateStr(val));
|
||||
this.selectYear = date.getFullYear();
|
||||
this.selectMonth = date.getMonth() + 1;
|
||||
this.selectDay = date.getDate();
|
||||
this.selectHour = date.getHours();
|
||||
this.selectMinute = date.getMinutes();
|
||||
this.selectSecond = date.getSeconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
minDateObj() {
|
||||
let minDate = this.minDate;
|
||||
if (minDate) {
|
||||
if (this.mode == DATE_TYPES.YM && minDate.replace(/\-/g, '/').split('/').length == 2) {
|
||||
// 日期模式为年月时有可能传进来的minDate是2022-02这样的格式,在ios下new Date会报错,加上日期部分做兼容
|
||||
minDate += '-01';
|
||||
} else if (this.mode == DATE_TYPES.HMS || this.mode == DATE_TYPES.HM) {
|
||||
// 只有时分秒或者只有时分是不能调用new Date生成Date对象的,先加上一个假设的年月日(就取当年一月一日)来兼容
|
||||
const now = new Date();
|
||||
minDate = `${now.getFullYear()}-01-01 ${minDate}`;
|
||||
}
|
||||
return new Date(DateUtil.handleDateStr(minDate));
|
||||
} else {
|
||||
// 没有传最小日期,默认十年前
|
||||
let year = new Date().getFullYear() - 10;
|
||||
minDate = new Date(year, 0, 1);
|
||||
return minDate;
|
||||
}
|
||||
},
|
||||
maxDateObj() {
|
||||
let maxDate = this.maxDate;
|
||||
if (maxDate) {
|
||||
if (this.mode == DATE_TYPES.YM && maxDate.replace(/\-/g, '/').split('/').length == 2) {
|
||||
// 日期模式为年月时有可能传进来的maxDate是2022-02这样的格式,在ios下new Date会报错,加上日期部分做兼容
|
||||
maxDate += '-01';
|
||||
} else if (this.mode == DATE_TYPES.HMS || this.mode == DATE_TYPES.HM) {
|
||||
// 只有时分秒或者只有时分是不能调用new Date生成Date对象的,先加上一个假设的年月日(就取当年一月一日)来兼容
|
||||
const now = new Date();
|
||||
maxDate = `${now.getFullYear()}-01-01 ${maxDate}`;
|
||||
}
|
||||
return new Date(DateUtil.handleDateStr(maxDate));
|
||||
} else {
|
||||
// 没有传最大日期,默认十年后
|
||||
let year = new Date().getFullYear() + 10;
|
||||
maxDate = new Date(year, 11, 31);
|
||||
return maxDate;
|
||||
}
|
||||
},
|
||||
years() {
|
||||
let years = [];
|
||||
let minYear = this.minDateObj.getFullYear();
|
||||
let maxYear = this.maxDateObj.getFullYear();
|
||||
for (let i = minYear; i <= maxYear; i++) {
|
||||
years.push(i);
|
||||
}
|
||||
|
||||
return years;
|
||||
},
|
||||
months() {
|
||||
let months = [];
|
||||
let minMonth = 1;
|
||||
let maxMonth = 12;
|
||||
|
||||
// 如果选中的年份刚好是最小可选日期的年份,那月份就要从最小日期的月份开始
|
||||
if (this.selectYear == this.minDateObj.getFullYear()) {
|
||||
minMonth = this.minDateObj.getMonth() + 1;
|
||||
}
|
||||
// 如果选中的年份刚好是最大可选日期的年份,那月份就要在最大日期的月份结束
|
||||
if (this.selectYear == this.maxDateObj.getFullYear()) {
|
||||
maxMonth = this.maxDateObj.getMonth() + 1;
|
||||
}
|
||||
|
||||
for (let i = minMonth; i <= maxMonth; i++) {
|
||||
months.push(i);
|
||||
}
|
||||
|
||||
return months;
|
||||
},
|
||||
days() {
|
||||
// 一年中12个月每个月的天数
|
||||
let monthDaysConfig = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
// 闰年2月有29天
|
||||
if (this.selectMonth == 2 && this.selectYear % 4 == 0) {
|
||||
monthDaysConfig[1] = 29;
|
||||
}
|
||||
|
||||
let minDay = 1;
|
||||
let maxDay = monthDaysConfig[this.selectMonth - 1];
|
||||
|
||||
if (this.selectYear == this.minDateObj.getFullYear() && this.selectMonth == this.minDateObj.getMonth() + 1) {
|
||||
minDay = this.minDateObj.getDate();
|
||||
}
|
||||
if (this.selectYear == this.maxDateObj.getFullYear() && this.selectMonth == this.maxDateObj.getMonth() + 1) {
|
||||
maxDay = this.maxDateObj.getDate();
|
||||
}
|
||||
|
||||
let days = [];
|
||||
for (let i = minDay; i <= maxDay; i++) {
|
||||
days.push(i);
|
||||
}
|
||||
|
||||
return days;
|
||||
},
|
||||
hours() {
|
||||
let hours = [];
|
||||
let minHour = 0;
|
||||
let maxHour = 23;
|
||||
|
||||
if (
|
||||
this.selectYear == this.minDateObj.getFullYear() &&
|
||||
this.selectMonth == this.minDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.minDateObj.getDate()
|
||||
) {
|
||||
minHour = this.minDateObj.getHours();
|
||||
}
|
||||
if (
|
||||
this.selectYear == this.maxDateObj.getFullYear() &&
|
||||
this.selectMonth == this.maxDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.maxDateObj.getDate()
|
||||
) {
|
||||
maxHour = this.maxDateObj.getHours();
|
||||
}
|
||||
|
||||
for (let i = minHour; i <= maxHour; i++) {
|
||||
hours.push(i);
|
||||
}
|
||||
|
||||
return hours;
|
||||
},
|
||||
minutes() {
|
||||
let mins = [];
|
||||
let minMin = 0;
|
||||
let maxMin = 59;
|
||||
|
||||
if (
|
||||
this.selectYear == this.minDateObj.getFullYear() &&
|
||||
this.selectMonth == this.minDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.minDateObj.getDate() &&
|
||||
this.selectHour == this.minDateObj.getHours()
|
||||
) {
|
||||
minMin = this.minDateObj.getMinutes();
|
||||
}
|
||||
if (
|
||||
this.selectYear == this.maxDateObj.getFullYear() &&
|
||||
this.selectMonth == this.maxDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.maxDateObj.getDate() &&
|
||||
this.selectHour == this.maxDateObj.getHours()
|
||||
) {
|
||||
maxMin = this.maxDateObj.getMinutes();
|
||||
}
|
||||
|
||||
for (let i = minMin; i <= maxMin; i++) {
|
||||
mins.push(i);
|
||||
}
|
||||
|
||||
return mins;
|
||||
},
|
||||
seconds() {
|
||||
let seconds = [];
|
||||
let minSecond = 0;
|
||||
let maxSecond = 59;
|
||||
|
||||
if (
|
||||
this.selectYear == this.minDateObj.getFullYear() &&
|
||||
this.selectMonth == this.minDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.minDateObj.getDate() &&
|
||||
this.selectHour == this.minDateObj.getHours() &&
|
||||
this.selectMinute == this.minDateObj.getMinutes()
|
||||
) {
|
||||
minSecond = this.minDateObj.getSeconds();
|
||||
}
|
||||
if (
|
||||
this.selectYear == this.maxDateObj.getFullYear() &&
|
||||
this.selectMonth == this.maxDateObj.getMonth() + 1 &&
|
||||
this.selectDay == this.maxDateObj.getDate() &&
|
||||
this.selectHour == this.maxDateObj.getHours() &&
|
||||
this.selectMinute == this.maxDateObj.getMinutes()
|
||||
) {
|
||||
maxSecond = this.maxDateObj.getSeconds();
|
||||
}
|
||||
|
||||
for (let i = minSecond; i <= maxSecond; i++) {
|
||||
seconds.push(i);
|
||||
}
|
||||
|
||||
return seconds;
|
||||
},
|
||||
// 传给pickerView组件的数组,根据mode来生成不同的数据
|
||||
dateConfig() {
|
||||
let years = this.years.map((y) => y + '年');
|
||||
let months = this.months.map((m) => m + '月');
|
||||
let days = this.days.map((d) => d + '日');
|
||||
let hours = this.hours.map((h) => h + '时');
|
||||
let minutes = this.minutes.map((m) => m + '分');
|
||||
let seconds = this.seconds.map((s) => s + '秒');
|
||||
|
||||
let ret = [];
|
||||
switch (this.mode) {
|
||||
case DATE_TYPES.YM:
|
||||
ret = [years, months];
|
||||
break;
|
||||
case DATE_TYPES.Y:
|
||||
ret = [years];
|
||||
break;
|
||||
case DATE_TYPES['YMD-HM']:
|
||||
ret = [years, months, days, hours, minutes];
|
||||
break;
|
||||
case DATE_TYPES.HMS:
|
||||
ret = [hours, minutes, seconds];
|
||||
break;
|
||||
case DATE_TYPES.HM:
|
||||
ret = [hours, minutes];
|
||||
break;
|
||||
default:
|
||||
ret = [years, months, days];
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
selectVals() {
|
||||
let ret = [];
|
||||
switch (this.mode) {
|
||||
case DATE_TYPES.YM:
|
||||
ret = [this.selectYear + '年', this.selectMonth + '月'];
|
||||
break;
|
||||
case DATE_TYPES.Y:
|
||||
ret = [this.selectYear + '年'];
|
||||
break;
|
||||
case DATE_TYPES['YMD-HM']:
|
||||
ret = [
|
||||
this.selectYear + '年',
|
||||
this.selectMonth + '月',
|
||||
this.selectDay + '日',
|
||||
this.selectHour + '时',
|
||||
// this.selectMinute + '分',
|
||||
];
|
||||
break;
|
||||
case DATE_TYPES.HMS:
|
||||
ret = [this.selectHour + '时', this.selectMinute + '分', this.selectSecond + '秒'];
|
||||
break;
|
||||
case DATE_TYPES.HM:
|
||||
ret = [this.selectHour + '时', this.selectMinute + '分'];
|
||||
break;
|
||||
default:
|
||||
ret = [this.selectYear + '年', this.selectMonth + '月', this.selectDay + '日'];
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChangePickerValue(e) {
|
||||
const { value } = e;
|
||||
|
||||
if (this.mode == DATE_TYPES.YM && value[0] && value[1]) {
|
||||
// 年月模式
|
||||
this.selectYear = Number(value[0].replace('年', ''));
|
||||
this.selectMonth = Number(value[1].replace('月', ''));
|
||||
} else if (this.mode == DATE_TYPES.Y && value[0]) {
|
||||
// 只有年份模式
|
||||
this.selectYear = Number(value[0].replace('年', ''));
|
||||
} else if (this.mode == DATE_TYPES['YMD-HM'] && value[0] && value[1] && value[2] != '' && value[3] && value[4]) {
|
||||
// 年月日时分秒模式
|
||||
this.selectYear = Number(value[0].replace('年', ''));
|
||||
this.selectMonth = Number(value[1].replace('月', ''));
|
||||
this.selectDay = Number(value[2].replace('日', ''));
|
||||
this.selectHour = Number(value[3].replace('时', ''));
|
||||
this.selectMinute = Number(value[4].replace('分', ''));
|
||||
// this.selectSecond = Number(value[5].replace('秒', ''));
|
||||
} else if (this.mode == DATE_TYPES.HMS && value[0] && value[1] && value[2]) {
|
||||
// 时分秒模式
|
||||
this.selectHour = Number(value[0].replace('时', ''));
|
||||
this.selectMinute = Number(value[1].replace('分', ''));
|
||||
this.selectSecond = Number(value[2].replace('秒', ''));
|
||||
} else if (this.mode == DATE_TYPES.HM && value[0] && value[1]) {
|
||||
// 时分模式
|
||||
this.selectHour = Number(value[0].replace('时', ''));
|
||||
this.selectMinute = Number(value[1].replace('分', ''));
|
||||
} else if (value[0] && value[1] && value[2]) {
|
||||
// 默认,年月日模式
|
||||
this.selectYear = Number(value[0].replace('年', ''));
|
||||
this.selectMonth = Number(value[1].replace('月', ''));
|
||||
this.selectDay = Number(value[2].replace('日', ''));
|
||||
} else {
|
||||
// 其他情况可能是pickerView返回的数据有问题,不处理
|
||||
console.log('onChangePickerValue其他情况');
|
||||
return;
|
||||
}
|
||||
|
||||
let formatTmpl = 'YYYY-MM-DD';
|
||||
switch (this.mode) {
|
||||
case DATE_TYPES.YM:
|
||||
formatTmpl = 'YYYY-MM';
|
||||
break;
|
||||
case DATE_TYPES.Y:
|
||||
formatTmpl = 'YYYY';
|
||||
break;
|
||||
case DATE_TYPES['YMD-HM']:
|
||||
formatTmpl = 'YYYY-MM-DD HH:mm';
|
||||
break;
|
||||
case DATE_TYPES.HMS:
|
||||
formatTmpl = 'HH:mm:ss';
|
||||
break;
|
||||
case DATE_TYPES.HM:
|
||||
formatTmpl = 'HH:mm';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.$emit(
|
||||
'onChange',
|
||||
DateUtil.formatDate(
|
||||
new Date(`${this.selectYear}/${this.selectMonth}/${this.selectDay} ${this.selectHour}:${this.selectMinute}:${this.selectSecond}`),
|
||||
formatTmpl
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<view class="datetime-picker">
|
||||
<CustomPickerView :columns="dateConfig" :selectVals="selectVals" @onChange="onChangePickerValue" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script src="./index.js"></script>
|
||||
|
||||
<style scoped lang="css"></style>
|
||||
228
src/components/goods/AddGoodsModal.vue
Normal file
228
src/components/goods/AddGoodsModal.vue
Normal file
@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<select-modal title="选择规格" @close="$emit('change', false)">
|
||||
<view>{{data.prices.product_name}}</view>
|
||||
<view class="add-goods-content">
|
||||
<view class="flex-row-start good-info">
|
||||
<image class="good-img" :src="data.product_pic" />
|
||||
<view class="flex-column-between good-info-inner">
|
||||
<view class="fs-32 app-fc-main app-text-ellipse">{{data.product_name || ''}}</view>
|
||||
<view>
|
||||
<text class="fs-24 app-fc-mark">¥</text>
|
||||
<text class="fs-44 app-font-bold app-fc-mark">{{data.prices[0].actual_price || data.prices[0].product_actual_price }}</text>
|
||||
<text class="fs-24 origin-price">¥{{data.prices[0].original_price || data.prices[0].product_original_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="fs-28 size-title">选择{{ data.shuxing_name || "" }}</view> -->
|
||||
<view class="flex-row-start size-list">
|
||||
<view
|
||||
class="size-item"
|
||||
:class="{ 'app-fc-mark active ali-puhui-bold': item.name === selectQuality }"
|
||||
v-for="(item, i) in data.shuxing_list"
|
||||
:key="i"
|
||||
@click="selectQuality = item.name"
|
||||
>
|
||||
{{ item.name || "" }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="fs-28 size-title">商品规格</view>
|
||||
<view class="flex-row-start size-list">
|
||||
<view
|
||||
class="size-item"
|
||||
:class="{ 'active ali-puhui-bold': item.price_id === selectPriceId }"
|
||||
v-for="item in data.price_list"
|
||||
:key="item.price_id"
|
||||
@click="selectPriceId = item.price_id"
|
||||
>
|
||||
{{ item.price_name || "" }}
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="fs-28 size-title">选择数量</view>
|
||||
<view class="flex-row-start">
|
||||
<image
|
||||
class="card-num-icon"
|
||||
src="@/static/images/cart_decrease.png"
|
||||
@click="decrease"
|
||||
/>
|
||||
<text class="fs-28 app-fc-main cart-num-text">{{ curGoodsNum }}</text>
|
||||
<image
|
||||
class="card-num-icon"
|
||||
src="@/static/images/cart_add.png"
|
||||
@click="add"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex-center app-fc-white fs-30 opt-btn" @click="optAction">
|
||||
{{ optText }}
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import selectModal from "../select-modal.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
qualityName: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
priceId: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
goodsNum: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
optText: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
components: {
|
||||
selectModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectQuality: "",
|
||||
selectPriceId: "",
|
||||
curGoodsNum: 1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 商品规格库存
|
||||
priceStore() {
|
||||
const priceInfo = this.data?.prices?.find(
|
||||
(v) => v.id === this.selectPriceId
|
||||
);
|
||||
return priceInfo?.stock || 0;
|
||||
},
|
||||
priceInfo() {
|
||||
const priceInfo = this.data?.prices?.find(
|
||||
(v) => v.id === this.selectPriceId
|
||||
);
|
||||
return {
|
||||
price: ((priceInfo?.actual_price || 0) * this.curGoodsNum).toFixed(2)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
qualityName(val) {
|
||||
this.selectQuality = val;
|
||||
},
|
||||
priceId(val) {
|
||||
this.selectPriceId = val;
|
||||
},
|
||||
goodsNum(val) {
|
||||
this.curGoodsNum = val;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.selectQuality = this.data?.shuxing_list?.[0]?.name || "";
|
||||
this.selectPriceId = this.data?.prices?.[0]?.id || "";
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
if (this.priceStore > this.curGoodsNum) {
|
||||
this.curGoodsNum += 1;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "库存不足",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
decrease() {
|
||||
if (this.curGoodsNum > 1) {
|
||||
this.curGoodsNum -= 1;
|
||||
}
|
||||
},
|
||||
optAction() {
|
||||
this.$emit("optAction", {
|
||||
...this.data,
|
||||
price_id: this.selectPriceId,
|
||||
number: this.curGoodsNum
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-goods-content {
|
||||
padding: 30rpx 52rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.good-info {
|
||||
margin-bottom: 40rpx;
|
||||
align-items: stretch;
|
||||
|
||||
.good-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
.good-info-inner {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.origin-price {
|
||||
color: #726E71;
|
||||
margin-left: 12rpx;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.size-title {
|
||||
margin: 20rpx 0 36rpx;
|
||||
}
|
||||
|
||||
.size-list {
|
||||
flex-wrap: wrap;
|
||||
.size-item {
|
||||
padding: 18rpx 38rpx;
|
||||
background: transparent;
|
||||
border: 2rpx solid #f5f5f5;
|
||||
color: #726e71;
|
||||
background: #f5f5f5;
|
||||
margin-right: 28rpx;
|
||||
margin-bottom: 18rpx;
|
||||
border-radius: 24rpx;
|
||||
|
||||
&.active {
|
||||
background: #fee9f3;
|
||||
border: 2rpx solid $app_color_main;
|
||||
color: $app_color_main;
|
||||
// font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-num-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
.cart-num-text {
|
||||
min-width: 78rpx;
|
||||
margin: 0 8rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.opt-btn {
|
||||
width: 630rpx;
|
||||
height: 92rpx;
|
||||
background: #fe019b;
|
||||
border-radius: 200rpx;
|
||||
margin: 66rpx auto 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
119
src/components/goods/RemarkItem.vue
Normal file
119
src/components/goods/RemarkItem.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="remark-item">
|
||||
<view class="flex-row-start remark-item-info">
|
||||
<image class="info-avator" :src="data.head_pic" />
|
||||
<view class="info-center">
|
||||
<view class="app-text-ellipse fs-26 app-fc-main">{{
|
||||
data.nick_name || ""
|
||||
}}</view>
|
||||
<view class="flex-row-start star-list">
|
||||
<image
|
||||
v-for="item in [1, 2, 3, 4, 5]"
|
||||
:key="item"
|
||||
class="star-item"
|
||||
:src="
|
||||
item <= data.star
|
||||
? require('@/static/images/star.png')
|
||||
: require('@/static/images/star_dark.png')
|
||||
"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<text class="fs-24 app-fc-normal"> {{ formatTime(data.add_time) }} </text>
|
||||
</view>
|
||||
<text class="fs-26 app-fc-normal">
|
||||
{{ data.content || "" }}
|
||||
</text>
|
||||
<view class="flex-row-start remark-imgs">
|
||||
<image
|
||||
v-for="(item, i) in imgList"
|
||||
:key="item"
|
||||
class="remark-img"
|
||||
:class="{ 'remark-img-right': i % 3 === 2 }"
|
||||
:src="item"
|
||||
@click="preview(i)"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from "moment";
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
imgList() {
|
||||
return this.data?.pic_list || [];
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
formatTime(time) {
|
||||
return moment(time * 1000).format("YYYY/MM/DD");
|
||||
},
|
||||
preview(index) {
|
||||
uni.previewImage({
|
||||
urls: this.imgList,
|
||||
current: index,
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.remark-item {
|
||||
padding: 40rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-sizing: border-box;
|
||||
margin: 0 0 20rpx 32rpx;
|
||||
width: calc(100vw - 32rpx * 2);
|
||||
|
||||
.remark-item-info {
|
||||
margin-bottom: 20rpx;
|
||||
align-items: stretch;
|
||||
.info-avator {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 60rpx;
|
||||
}
|
||||
.info-center {
|
||||
flex: 1;
|
||||
margin: 0 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.star-item {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
margin-right: 6rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.remark-imgs {
|
||||
margin-top: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
.remark-img {
|
||||
width: calc((100vw - 32rpx * 2 - 24rpx * 2 - 14rpx * 2) / 3);
|
||||
height: calc((100vw - 32rpx * 2 - 24rpx * 2 - 14rpx * 2) / 3);
|
||||
border-radius: 20rpx;
|
||||
margin-right: 14rpx;
|
||||
margin-bottom: 14rpx;
|
||||
|
||||
&.remark-img-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
836
src/components/htz-image-upload/htz-image-upload.vue
Normal file
836
src/components/htz-image-upload/htz-image-upload.vue
Normal file
@ -0,0 +1,836 @@
|
||||
<template>
|
||||
<view class="htz-image-upload-list">
|
||||
<view class="htz-image-upload-Item" v-for="(item,index) in uploadLists" :key="index">
|
||||
<view class="htz-image-upload-Item-video" v-if="isVideo(item)">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video :disabled="false" :controls="false" :src="getFileUrl(item)">
|
||||
<cover-view class="htz-image-upload-Item-video-fixed" @click="previewVideo(getFileUrl(item))">
|
||||
</cover-view>
|
||||
|
||||
<!-- <cover-view class="htz-image-upload-Item-del-cover" v-if="remove && previewVideoSrc==''" @click="imgDel(index)">×</cover-view> -->
|
||||
|
||||
</video>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="htz-image-upload-Item-video-fixed" @click="previewVideo(getFileUrl(item))"></view>
|
||||
<image v-if="dataType==1 && item.cover" class="htz-image-upload-Item-video-app-poster" mode="widthFix" :src="item.cover"></image>
|
||||
<image v-else class="htz-image-upload-Item-video-app-poster" mode="widthFix" :src="appVideoPoster"></image>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<image v-else :src="getFileUrl(item)" @click="imgPreview(getFileUrl(item))"></image>
|
||||
|
||||
<view class="htz-image-upload-Item-del" v-if="remove" @click="imgDel(index)">×</view>
|
||||
</view>
|
||||
<view class="htz-image-upload-Item htz-image-upload-Item-add" v-if="uploadLists.length<max && add" @click="chooseFile">
|
||||
+
|
||||
</view>
|
||||
<view class="preview-full" v-if="previewVideoSrc!=''">
|
||||
<video :autoplay="true" :src="previewVideoSrc" :show-fullscreen-btn="false">
|
||||
<cover-view class="preview-full-close ali-puhui-bold" @click="previewVideoClose"> ×
|
||||
</cover-view>
|
||||
</video>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.ceshi {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: #FFFFFF;
|
||||
color: #2C405A;
|
||||
opacity: 0.5;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'htz-image-upload',
|
||||
props: {
|
||||
max: { //展示图片最大值
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
chooseNum: { //选择图片数
|
||||
type: Number,
|
||||
default: 9,
|
||||
},
|
||||
name: { //发到后台的文件参数名
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
dataType: { //v-model的数据结构类型
|
||||
type: Number,
|
||||
default: 0, // 0: ['http://xxxx.jpg','http://xxxx.jpg'] 1:[{type:0,url:'http://xxxx.jpg'}] type 0 图片 1 视频 url 文件地址 此类型是为了给没有文件后缀的状况使用的
|
||||
},
|
||||
remove: { //是否展示删除按钮
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
add: { //是否展示添加按钮
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: { //是否禁用
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sourceType: { //选择照片来源 【ps:H5就别费劲了,设置了也没用。不是我说的,官方文档就这样!!!】
|
||||
type: Array,
|
||||
default: () => ['album', 'camera'],
|
||||
},
|
||||
action: { //上传地址 如需使用uniCloud服务,设置为uniCloud即可
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
headers: { //上传的请求头部
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
formData: { //HTTP 请求中其他额外的 form data
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
compress: { //是否需要压缩
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
quality: { //压缩质量,范围0~100
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
// #ifndef VUE3
|
||||
value: { //受控图片列表
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: { //受控图片列表
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// #endif
|
||||
uploadSuccess: {
|
||||
default: (res) => {
|
||||
return {
|
||||
success: false,
|
||||
url: ''
|
||||
}
|
||||
},
|
||||
},
|
||||
mediaType: { //文件类型 image/video/all
|
||||
type: String,
|
||||
default: 'image',
|
||||
},
|
||||
maxDuration: { //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。 (只针对拍摄视频有用)
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
camera: { //'front'、'back',默认'back'(只针对拍摄视频有用)
|
||||
type: String,
|
||||
default: 'back',
|
||||
},
|
||||
appVideoPoster: { //app端视频展示封面 只对app有效
|
||||
type: String,
|
||||
default: '/static/htz-image-upload/play.png',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
uploadLists: [],
|
||||
mediaTypeData: ['image', 'video', 'all'],
|
||||
previewVideoSrc: '',
|
||||
}
|
||||
},
|
||||
mounted: function() {
|
||||
this.$nextTick(function() {
|
||||
// #ifndef VUE3
|
||||
this.uploadLists = this.value;
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.uploadLists = this.modelValue;
|
||||
// #endif
|
||||
if (this.mediaTypeData.indexOf(this.mediaType) == -1) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: 'mediaType参数不正确',
|
||||
showCancel: false,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
//console.log('用户点击确定');
|
||||
} else if (res.cancel) {
|
||||
//console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
// #ifndef VUE3
|
||||
value(val, oldVal) {
|
||||
//console.log('value',val, oldVal)
|
||||
this.uploadLists = val;
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue(val, oldVal) {
|
||||
//console.log('value',val, oldVal)
|
||||
this.uploadLists = val;
|
||||
},
|
||||
// #endif
|
||||
|
||||
},
|
||||
methods: {
|
||||
isVideo(item) {
|
||||
let isPass = false
|
||||
if ((!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(item) && this.dataType == 0) || (this.dataType == 1 && item
|
||||
.type == 1)) {
|
||||
isPass = true
|
||||
}
|
||||
return isPass
|
||||
},
|
||||
getFileUrl(item) {
|
||||
var url = item;
|
||||
if (this.dataType == 1) {
|
||||
url = item.url
|
||||
}
|
||||
//console.log('url', url)
|
||||
return url
|
||||
},
|
||||
previewVideo(src) {
|
||||
this.previewVideoSrc = src;
|
||||
// this.previewVideoSrc =
|
||||
// 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-fbd63a76-dc76-485c-b711-f79f2986daeb/ba804d82-860b-4d1a-a706-5a4c8ce137c3.mp4'
|
||||
},
|
||||
previewVideoClose() {
|
||||
this.previewVideoSrc = ''
|
||||
//console.log('previewVideoClose', this.previewVideoSrc)
|
||||
},
|
||||
imgDel(index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '您确定要删除么?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// this.uploadLists.splice(index, 1)
|
||||
// this.$emit("input", this.uploadLists);
|
||||
// this.$emit("imgDelete", this.uploadLists);
|
||||
let delUrl = this.uploadLists[index]
|
||||
this.uploadLists.splice(index, 1)
|
||||
// #ifndef VUE3
|
||||
this.$emit("input", this.uploadLists);
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", this.uploadLists);
|
||||
// #endif
|
||||
this.$emit("imgDelete", {
|
||||
del: delUrl,
|
||||
tempFilePaths: this.uploadLists
|
||||
});
|
||||
} else if (res.cancel) {}
|
||||
}
|
||||
});
|
||||
},
|
||||
imgPreview(index) {
|
||||
|
||||
var imgData = []
|
||||
|
||||
this.uploadLists.forEach(item => {
|
||||
if (!this.isVideo(item)) {
|
||||
imgData.push(this.getFileUrl(item))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
//console.log('imgPreview', imgData)
|
||||
uni.previewImage({
|
||||
urls: imgData,
|
||||
current: index,
|
||||
loop: true,
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
chooseFile() {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
switch (this.mediaTypeData.indexOf(this.mediaType)) {
|
||||
case 1: //视频
|
||||
this.videoAdd();
|
||||
break;
|
||||
case 2: //全部
|
||||
uni.showActionSheet({
|
||||
itemList: ['相册', '视频'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex == 1) {
|
||||
this.videoAdd();
|
||||
} else if (res.tapIndex == 0) {
|
||||
this.imgAdd();
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res.errMsg);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default: //图片
|
||||
this.imgAdd();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//if(this.mediaType=='image'){
|
||||
|
||||
|
||||
},
|
||||
videoAdd() {
|
||||
//console.log('videoAdd')
|
||||
let nowNum = Math.abs(this.uploadLists.length - this.max);
|
||||
let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
|
||||
uni.chooseVideo({
|
||||
compressed: this.compress,
|
||||
sourceType: this.sourceType,
|
||||
camera: this.camera,
|
||||
maxDuration: this.maxDuration,
|
||||
success: (res) => {
|
||||
// console.log('videoAdd', res)
|
||||
// console.log(res.tempFilePath)
|
||||
this.chooseSuccessMethod([res.tempFilePath], 1)
|
||||
//this.imgUpload([res.tempFilePath]);
|
||||
//console.log('tempFiles', res)
|
||||
// if (this.action == '') { //未配置上传路径
|
||||
// this.$emit("chooseSuccess", res.tempFilePaths);
|
||||
// } else {
|
||||
// if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
|
||||
// this.imgCompress(res.tempFilePaths);
|
||||
// } else {
|
||||
// this.imgUpload(res.tempFilePaths);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
});
|
||||
},
|
||||
imgAdd() {
|
||||
//console.log('imgAdd')
|
||||
let nowNum = Math.abs(this.uploadLists.length - this.max);
|
||||
let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
|
||||
//console.log('nowNum', nowNum)
|
||||
//console.log('thisNum', thisNum)
|
||||
// #ifdef APP-PLUS
|
||||
if (this.sourceType.length > 1) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['拍摄', '从手机相册选择'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex == 1) {
|
||||
this.appGallery(thisNum);
|
||||
} else if (res.tapIndex == 0) {
|
||||
this.appCamera();
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log(res.errMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.sourceType.length == 1 && this.sourceType.indexOf('album') > -1) {
|
||||
this.appGallery(thisNum);
|
||||
}
|
||||
|
||||
if (this.sourceType.length == 1 && this.sourceType.indexOf('camera') > -1) {
|
||||
this.appCamera();
|
||||
}
|
||||
// #endif
|
||||
//#ifndef APP-PLUS
|
||||
uni.chooseImage({
|
||||
count: thisNum,
|
||||
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: this.sourceType,
|
||||
success: (res) => {
|
||||
this.chooseSuccessMethod(res.tempFilePaths, 0)
|
||||
//console.log('tempFiles', res)
|
||||
// if (this.action == '') { //未配置上传路径
|
||||
// this.$emit("chooseSuccess", res.tempFilePaths);
|
||||
// } else {
|
||||
// if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M,进行压缩上传
|
||||
// this.imgCompress(res.tempFilePaths);
|
||||
// } else {
|
||||
// this.imgUpload(res.tempFilePaths);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
appCamera() {
|
||||
var cmr = plus.camera.getCamera();
|
||||
var res = cmr.supportedImageResolutions[0];
|
||||
var fmt = cmr.supportedImageFormats[0];
|
||||
//console.log("Resolution: " + res + ", Format: " + fmt);
|
||||
cmr.captureImage((path) => {
|
||||
//alert("Capture image success: " + path);
|
||||
this.chooseSuccessMethod([path], 0)
|
||||
},
|
||||
(error) => {
|
||||
//alert("Capture image failed: " + error.message);
|
||||
console.log("Capture image failed: " + error.message)
|
||||
}, {
|
||||
resolution: res,
|
||||
format: fmt
|
||||
}
|
||||
);
|
||||
},
|
||||
appGallery(maxNum) {
|
||||
plus.gallery.pick((res) => {
|
||||
this.chooseSuccessMethod(res.files, 0)
|
||||
}, function(e) {
|
||||
//console.log("取消选择图片");
|
||||
}, {
|
||||
filter: "image",
|
||||
multiple: true,
|
||||
maximum: maxNum
|
||||
});
|
||||
},
|
||||
chooseSuccessMethod(filePaths, type) {
|
||||
if (this.action == '') { //未配置上传路径
|
||||
this.$emit("chooseSuccess", filePaths, type); //filePaths 路径 type 0 为图片 1为视频
|
||||
} else {
|
||||
if (type == 1) {
|
||||
this.imgUpload(filePaths, type);
|
||||
} else {
|
||||
if (this.compress) { //设置了需要压缩
|
||||
this.imgCompress(filePaths, type);
|
||||
} else {
|
||||
this.imgUpload(filePaths, type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
imgCompress(tempFilePaths, type) { //type 0 为图片 1为视频
|
||||
uni.showLoading({
|
||||
title: '压缩中...'
|
||||
});
|
||||
|
||||
let compressImgs = [];
|
||||
let results = [];
|
||||
tempFilePaths.forEach((item, index) => {
|
||||
compressImgs.push(new Promise((resolve, reject) => {
|
||||
// #ifndef H5
|
||||
uni.compressImage({
|
||||
src: item,
|
||||
quality: this.quality,
|
||||
success: res => {
|
||||
//console.log('compressImage', res.tempFilePath)
|
||||
results.push(res.tempFilePath);
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
//console.log(err.errMsg);
|
||||
reject(err);
|
||||
},
|
||||
complete: () => {
|
||||
//uni.hideLoading();
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
this.canvasDataURL(item, {
|
||||
quality: this.quality / 100
|
||||
}, (base64Codes) => {
|
||||
//this.imgUpload(base64Codes);
|
||||
results.push(base64Codes);
|
||||
resolve(base64Codes);
|
||||
})
|
||||
// #endif
|
||||
}))
|
||||
})
|
||||
Promise.all(compressImgs) //执行所有需请求的接口
|
||||
.then((results) => {
|
||||
uni.hideLoading();
|
||||
//console.log('imgCompress', type)
|
||||
this.imgUpload(results, type);
|
||||
})
|
||||
.catch((res, object) => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
imgUpload(tempFilePaths, type) { //type 0 为图片 1为视频
|
||||
// if (this.action == '') {
|
||||
// uni.showToast({
|
||||
// title: '未配置上传地址',
|
||||
// icon: 'none',
|
||||
// duration: 2000
|
||||
// });
|
||||
// return false;
|
||||
// }
|
||||
if (this.action == 'uniCloud') {
|
||||
this.uniCloudUpload(tempFilePaths, type)
|
||||
return
|
||||
}
|
||||
uni.showLoading({
|
||||
title: '上传中'
|
||||
});
|
||||
//console.log('imgUpload', tempFilePaths)
|
||||
let uploadImgs = [];
|
||||
tempFilePaths.forEach((item, index) => {
|
||||
uploadImgs.push(new Promise((resolve, reject) => {
|
||||
//console.log(index, item)
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: this.action, //仅为示例,非真实的接口地址
|
||||
filePath: item,
|
||||
name: this.name,
|
||||
fileType: 'image',
|
||||
formData: this.formData,
|
||||
header: this.headers,
|
||||
success: (uploadFileRes) => {
|
||||
//uni.hideLoading();
|
||||
//console.log(typeof this.uploadSuccess)
|
||||
//console.log('')
|
||||
uploadFileRes.fileType = type
|
||||
if (typeof this.uploadSuccess == 'function') {
|
||||
|
||||
let thisUploadSuccess = this.uploadSuccess(
|
||||
uploadFileRes)
|
||||
if (thisUploadSuccess.success) {
|
||||
let keyName = '';
|
||||
// #ifndef VUE3
|
||||
keyName = 'value'
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
keyName = 'modelValue'
|
||||
// #endif
|
||||
if (this.dataType == 0) {
|
||||
this[keyName].push(thisUploadSuccess.url)
|
||||
} else {
|
||||
this[keyName].push({
|
||||
type: type,
|
||||
url: thisUploadSuccess.url,
|
||||
...thisUploadSuccess
|
||||
})
|
||||
}
|
||||
|
||||
//this.$emit("input", this.uploadLists);
|
||||
// #ifndef VUE3
|
||||
this.$emit("input", this.uploadLists);
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", this.uploadLists);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
resolve(uploadFileRes);
|
||||
this.$emit("uploadSuccess", uploadFileRes);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
//uni.hideLoading();
|
||||
reject(err);
|
||||
this.$emit("uploadFail", err);
|
||||
},
|
||||
complete: () => {
|
||||
//uni.hideLoading();
|
||||
}
|
||||
});
|
||||
}))
|
||||
})
|
||||
Promise.all(uploadImgs) //执行所有需请求的接口
|
||||
.then((results) => {
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((res, object) => {
|
||||
uni.hideLoading();
|
||||
this.$emit("uploadFail", res);
|
||||
});
|
||||
// uploadTask.onProgressUpdate((res) => {
|
||||
// //console.log('',)
|
||||
// uni.showLoading({
|
||||
// title: '上传中' + res.progress + '%'
|
||||
// });
|
||||
// if (res.progress == 100) {
|
||||
// uni.hideLoading();
|
||||
// }
|
||||
// });
|
||||
},
|
||||
uniCloudUpload(tempFilePaths, type) {
|
||||
uni.showLoading({
|
||||
title: '上传中'
|
||||
});
|
||||
console.log('uniCloudUpload', tempFilePaths);
|
||||
let uploadImgs = [];
|
||||
tempFilePaths.forEach((item, index) => {
|
||||
uploadImgs.push(new Promise((resolve, reject) => {
|
||||
|
||||
uniCloud.uploadFile({
|
||||
filePath: item,
|
||||
cloudPath: this.guid() + '.' + this.getFileType(item, type),
|
||||
success(uploadFileRes) {
|
||||
if (uploadFileRes.success) {
|
||||
resolve(uploadFileRes.fileID);
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.log(err);
|
||||
reject(err);
|
||||
},
|
||||
complete() {}
|
||||
});
|
||||
|
||||
}))
|
||||
})
|
||||
Promise.all(uploadImgs) //执行所有需请求的接口
|
||||
.then((results) => {
|
||||
uni.hideLoading();
|
||||
// console.log('then', results)
|
||||
|
||||
uniCloud.getTempFileURL({
|
||||
fileList: results,
|
||||
success: (res) => {
|
||||
//console.log('success',res.fileList)
|
||||
res.fileList.forEach(item => {
|
||||
//console.log(item.tempFileURL)
|
||||
//this.value.push(item.tempFileURL)
|
||||
// #ifndef VUE3
|
||||
this.value.push(item.tempFileURL)
|
||||
this.$emit("input", this.value);
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
this.modelValue.push(item.tempFileURL)
|
||||
this.$emit("update:modelValue", this.modelValue);
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
fail() {},
|
||||
complete() {}
|
||||
});
|
||||
})
|
||||
.catch((res, object) => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
getFileType(path, type) { //手机端默认图片为jpg 视频为mp4
|
||||
// #ifdef H5
|
||||
var result = type == 0 ? 'jpg' : 'mp4';
|
||||
// #endif
|
||||
|
||||
|
||||
// #ifndef H5
|
||||
var result = path.split('.').pop().toLowerCase();
|
||||
// #ifdef MP
|
||||
if (this.compress) { //微信小程序压缩完没有后缀
|
||||
result = type == 0 ? 'jpg' : 'mp4';
|
||||
}
|
||||
// #endif
|
||||
// #endif
|
||||
return result;
|
||||
},
|
||||
guid() {
|
||||
return 'xxxxxxxx-date-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0,
|
||||
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
}).replace(/date/g, function(c) {
|
||||
return Date.parse(new Date());
|
||||
});
|
||||
},
|
||||
canvasDataURL(path, obj, callback) {
|
||||
var img = new Image();
|
||||
img.src = path;
|
||||
img.onload = function() {
|
||||
var that = this;
|
||||
// 默认按比例压缩
|
||||
var w = that.width,
|
||||
h = that.height,
|
||||
scale = w / h;
|
||||
w = obj.width || w;
|
||||
h = obj.height || (w / scale);
|
||||
var quality = 0.8; // 默认图片质量为0.8
|
||||
//生成canvas
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
// 创建属性节点
|
||||
var anw = document.createAttribute("width");
|
||||
anw.nodeValue = w;
|
||||
var anh = document.createAttribute("height");
|
||||
anh.nodeValue = h;
|
||||
canvas.setAttributeNode(anw);
|
||||
canvas.setAttributeNode(anh);
|
||||
ctx.drawImage(that, 0, 0, w, h);
|
||||
// 图像质量
|
||||
if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
|
||||
quality = obj.quality;
|
||||
}
|
||||
// quality值越小,所绘制出的图像越模糊
|
||||
var base64 = canvas.toDataURL('image/jpeg', quality);
|
||||
// 回调函数返回base64的值
|
||||
callback(base64);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.preview-full {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
.preview-full video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
.preview-full-close {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
top: 25rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
z-index: 1003;
|
||||
/* background-color: #808080; */
|
||||
color: #fff;
|
||||
font-size: 65rpx;
|
||||
/* font-weight: bold; */
|
||||
text-shadow: 1px 2px 5px rgb(0 0 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* .preview-full-close-before,
|
||||
.preview-full-close-after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
content: '';
|
||||
height: 60rpx;
|
||||
margin-top: -30rpx;
|
||||
width: 6rpx;
|
||||
margin-left: -3rpx;
|
||||
background-color: #FFFFFF;
|
||||
z-index: 20000;
|
||||
}
|
||||
|
||||
.preview-full-close-before {
|
||||
transform: rotate(45deg);
|
||||
|
||||
}
|
||||
|
||||
.preview-full-close-after {
|
||||
transform: rotate(-45deg);
|
||||
|
||||
} */
|
||||
|
||||
.htz-image-upload-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.htz-image-upload-Item {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin: 13rpx;
|
||||
border-radius: 10rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.htz-image-upload-Item image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-video-fixed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
z-index: 996;
|
||||
|
||||
}
|
||||
|
||||
.htz-image-upload-Item video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-add {
|
||||
font-size: 105rpx;
|
||||
/* line-height: 160rpx; */
|
||||
text-align: center;
|
||||
border: 1px dashed #d9d9d9;
|
||||
color: #d9d9d9;
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-del {
|
||||
background-color: #f5222d;
|
||||
font-size: 24rpx;
|
||||
position: absolute;
|
||||
width: 35rpx;
|
||||
height: 35rpx;
|
||||
line-height: 35rpx;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 997;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-del-cover {
|
||||
background-color: #f5222d;
|
||||
font-size: 24rpx;
|
||||
position: absolute;
|
||||
width: 35rpx;
|
||||
height: 35rpx;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: #fff;
|
||||
/* #ifdef APP-PLUS */
|
||||
line-height: 25rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-PLUS */
|
||||
line-height: 35rpx;
|
||||
/* #endif */
|
||||
z-index: 997;
|
||||
|
||||
}
|
||||
|
||||
.htz-image-upload-Item-video-app-poster {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
228
src/components/petOrder/add-service-pay-modal.vue
Normal file
228
src/components/petOrder/add-service-pay-modal.vue
Normal file
@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="调整服务费">
|
||||
<view class="add-pay-container">
|
||||
<view class="top-container">
|
||||
<detail-cell v-if="weightName" title="下单宠物重量区间" :content="weightName"/>
|
||||
<detail-cell title="已支付费用" :content="`¥${price}`"/>
|
||||
<!-- <detail-cell v-if="orderInfo.dikou_id" title="优惠券" :content="`-¥${diKouPrice}`"/>-->
|
||||
<!-- <detail-cell v-if="orderInfo.fuwuquan_id" title="服务券金额" :content="`¥${servicePrice}`"/>-->
|
||||
<view class="top-cell" @click.stop="selectedWeight">
|
||||
<view class="info-view">
|
||||
<text class="app-fc-main fs-30">实际宠物重量区间</text>
|
||||
<text class="app-fc-main fs-30 app-font-bold-500">{{ realWeightName }}</text>
|
||||
</view>
|
||||
<image class="right-arrow" mode="aspectFit" src="/static/images/arrow_right_black.png"/>
|
||||
</view>
|
||||
<view class="top-cell">
|
||||
<view class="info-view">
|
||||
<text v-if="needRefund" class="app-fc-main fs-30">差价退款金额</text>
|
||||
<text v-else class="app-fc-main fs-30">差价补偿金额</text>
|
||||
</view>
|
||||
<text class="fs-30 app-fc-alarm app-font-bold-500">{{needRefund ? '-' : ''}}{{ `¥${diffPrice}` }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="line-view"/>
|
||||
<view class="bottom-container">
|
||||
<view class="price-container">
|
||||
<view class="price-view">
|
||||
<text class="app-fc-main fs-30 app-font-bold-500">¥</text>
|
||||
<text class="app-fc-main fs-40 app-font-bold-700">{{ diffPrice }}</text>
|
||||
</view>
|
||||
<text class="fs-24 app-fc-normal">差价补退</text>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="paymentConfirm">
|
||||
<text v-if="needRefund" class="app-fc-white fs-30">提交</text>
|
||||
<text v-else class="app-fc-white fs-30">确认支付</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import DetailCell from "@/components/petOrder/detail-cell.vue";
|
||||
|
||||
export default {
|
||||
components: { DetailCell, SelectModal },
|
||||
props: {
|
||||
orderInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
newWeight: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
price() {
|
||||
return this.orderInfo.price || '';
|
||||
},
|
||||
diKouPrice() {
|
||||
return this.orderInfo.dikou_price || '';
|
||||
},
|
||||
servicePrice() {
|
||||
return +this.orderInfo.fuwuquan_price || 0;
|
||||
},
|
||||
weightName() {
|
||||
return this.orderInfo.new_weight_name || this.orderInfo?.weight_name || '';
|
||||
},
|
||||
realWeightName() {
|
||||
return this.newWeight?.weight_name || '请选择';
|
||||
},
|
||||
diffPrice() {
|
||||
if (Object.keys(this.newWeight).length > 0) {
|
||||
const newPrice = Number(this.newWeight?.price || 0);
|
||||
const orderPrice = Number(this.orderInfo?.price || 0);
|
||||
const payPrice = Number(this.orderInfo?.pay_price || 0);
|
||||
let d = Math.abs(newPrice - orderPrice);
|
||||
if (d === 0) {
|
||||
return '0'
|
||||
} else if (newPrice > orderPrice) {
|
||||
//需要补交钱
|
||||
return d.toFixed(2);
|
||||
} else {
|
||||
//退款
|
||||
if (this.orderInfo.fuwuquan_id) {
|
||||
//使用服务券
|
||||
if ((orderPrice - newPrice) >= this.servicePrice) {
|
||||
return this.servicePrice.toFixed(2);
|
||||
} else {
|
||||
return d.toFixed(2);
|
||||
}
|
||||
} else {
|
||||
if ((orderPrice - newPrice) >= payPrice) {
|
||||
return payPrice.toFixed(2);
|
||||
} else {
|
||||
return d.toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return '0';
|
||||
},
|
||||
needRefund() {
|
||||
const newPrice = Number(this.newWeight?.price || 0);
|
||||
const orderPrice = Number(this.orderInfo?.price || 0);
|
||||
return orderPrice > newPrice;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
selectedWeight() {
|
||||
this.$emit('changeWeight')
|
||||
},
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
paymentConfirm() {
|
||||
// console.log(this.orderInfo,'--')
|
||||
if (Object.keys(this.newWeight).length === 0) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '请选择实际宠物重量区间'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.$emit('paymentConfirm', {
|
||||
needRefund: this.needRefund,
|
||||
diffPrice:this.diffPrice,
|
||||
order_id:this.orderInfo.order_id
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.add-pay-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.top-container {
|
||||
width: 100%;
|
||||
padding: 30rpx 40rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.top-cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
background-color: #F9F7F9;
|
||||
margin-bottom: 20rpx;
|
||||
margin-top: 12rpx;
|
||||
|
||||
.info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line-view {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
background-color: #ECECEC;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
width: 100%;
|
||||
padding: 20rpx 32rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.price-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.price-view {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
flex-shrink: 0;
|
||||
width: 260rpx;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
118
src/components/petOrder/call-modal.vue
Normal file
118
src/components/petOrder/call-modal.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="call-modal" @click.stop="closeAction">
|
||||
<view class="call-body-view" @click.stop="">
|
||||
<view class="phone-view">
|
||||
<image src="@/static/images/phone.png" mode="aspectFit" class="phone-icon"/>
|
||||
<text class="app-fc-main fs-52 app-font-bold-500">{{ phoneNumber }}</text>
|
||||
</view>
|
||||
|
||||
<view class="handle-container">
|
||||
<view class="handle-btn" @click.stop="closeAction">
|
||||
<text class="app-fc-main fs-32">取消</text>
|
||||
</view>
|
||||
<view class="handle-btn ok-btn" @click.stop="callAction">
|
||||
<text class="app-fc-white fs-32">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
phoneNumber: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
callAction() {
|
||||
if (this.phoneNumber) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: this.phoneNumber,
|
||||
success: (res) => {
|
||||
console.log(res);
|
||||
this.$emit('close');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '电话号码为空',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.call-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.call-body-view {
|
||||
width: calc(100% - 132rpx);
|
||||
padding: 90rpx 0 60rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 10vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
.phone-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.phone-icon {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.handle-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 78rpx;
|
||||
justify-content: space-around;
|
||||
padding: 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.handle-btn {
|
||||
width: 216rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #F2F2F2;
|
||||
}
|
||||
|
||||
.ok-btn {
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
src/components/petOrder/detail-cell.vue
Normal file
103
src/components/petOrder/detail-cell.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="detail-cell-container" @click.stop="clickAction">
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-24" v-if="titleMark">{{ title }}</text>
|
||||
<text class="app-fc-normal fs-24" v-else>{{ title }}</text>
|
||||
</view>
|
||||
<view class="content-view">
|
||||
<text class="app-fc-main fs-24 app-font-bold-500" :style="[customContentStyle]" v-if="contentMark">
|
||||
{{ content }}
|
||||
</text>
|
||||
<text class="app-fc-main fs-24" :style="[customContentStyle]" v-else>{{ content }}</text>
|
||||
</view>
|
||||
<view class="arrow-view" v-if="isShowArrow">
|
||||
<image src="@/static/images/arrow_right_black.png" mode="widthFix" class="arrow-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
titleMark: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
contentMark: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customContentStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
isShowArrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
clickAction() {
|
||||
this.$emit('clickAction')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-cell-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.title-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content-view {
|
||||
margin-left: 40rpx;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.arrow-view {
|
||||
margin-left: 10rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.arrow-img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
541
src/components/petOrder/order-cell.vue
Normal file
541
src/components/petOrder/order-cell.vue
Normal file
@ -0,0 +1,541 @@
|
||||
<template>
|
||||
<view class="order-cell" @click.stop="gotoDetail">
|
||||
<view class="order-title-view">
|
||||
<!-- <view>{{orderInfo.address}}</view> -->
|
||||
<text class="fs-24 app-fc-normal">{{ `订单编号:${orderNumber}` }}</text>
|
||||
<view class="state-btn" :class="{ 'done-state-btn': isCompleted || isCanceled }">
|
||||
<text class="fs-20">{{ orderState }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="line-view" />
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ petsCountLabel }}</text>
|
||||
<text class="info-value">{{ petsNames }}</text>
|
||||
</view>
|
||||
<view class="detail-cell detail-time-cell">
|
||||
<view class="detail-title-cell">
|
||||
<text class="fs-24 app-fc-normal">{{ timeTitle }}</text>
|
||||
</view>
|
||||
<view class="detail-info-cell">
|
||||
<text class="fs-24 app-fc-main">{{ reservationTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-cell detail-time-cell">
|
||||
<view class="detail-title-cell">
|
||||
<text class="fs-24 app-fc-normal">{{ "服务地址" }}</text>
|
||||
</view>
|
||||
<view class="detail-info-cell">
|
||||
<text class="fs-24 app-fc-main">{{ address }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 预检报告提示条 -->
|
||||
<view v-if="isShowReport" class="precheck-report-bar" @click.stop="gotoReport">
|
||||
<text class="precheck-report-text">该订单宠物预检报告已生成</text>
|
||||
<view class="precheck-report-action">
|
||||
<text class="precheck-report-link">立即查看</text>
|
||||
<text class="precheck-report-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-cell detail-time-cell" v-if="orderInfo.fuwuquan_name">
|
||||
<view class="detail-title-cell">
|
||||
<text class="fs-24 app-fc-normal">服务券<text class="fs-28" style="color: transparent">券</text></text>
|
||||
</view>
|
||||
<view class="detail-info-cell">
|
||||
<text class="fs-24 app-fc-main">{{
|
||||
orderInfo.fuwuquan_name || "--"
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="line-view-2" v-if="!isCanceled " /> -->
|
||||
<view class="handle-view" v-if="!isCanceled || isCanceled">
|
||||
<!-- 已取消状态:显示再次预约按钮 -->
|
||||
<view v-if="isCanceled" class="handle-btn" @click.stop="reserveAgain">
|
||||
<text class="app-fc-white fs-24">再次预约</text>
|
||||
</view>
|
||||
<!-- 其他状态的按钮 -->
|
||||
<template v-else>
|
||||
<!-- 未支付状态:显示取消预约和立即支付按钮 -->
|
||||
<template v-if="isUnPay">
|
||||
<view class="cancel-btn" @click.stop="cancelOrder">
|
||||
<text class="app-fc-main fs-24">取消预约</text>
|
||||
</view>
|
||||
<view class="handle-btn" @click.stop="payNow">
|
||||
<text class="app-fc-white fs-24">立即支付</text>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 其他状态 -->
|
||||
<template v-else>
|
||||
<view v-if="orderInfo.status != 5" class="cancel-btn" @click.stop="cancelOrder">
|
||||
<text class="app-fc-main fs-24">取消预约</text>
|
||||
</view>
|
||||
<!-- <view class="handle-btn" v-if="isService" @click.stop="showGoods">
|
||||
<text class="app-fc-main fs-24">随车购商品</text>
|
||||
</view> -->
|
||||
<!-- <view class="handle-btn" v-if="(isReceived || isService) && !isHasNewWeight" @click.stop="clickAddService">
|
||||
<text class="app-fc">调整服务项</text>
|
||||
</view> -->
|
||||
<view class="handle-btn" v-if="isReceived || isService" @click.stop="jumpTo1(orderInfo)">
|
||||
<text class="app-fc">添加附加项</text>
|
||||
</view>
|
||||
<!-- <view class="handle-btn" v-if="isCompleted" @click.stop="callPhone">
|
||||
<text class="app-fc-main fs-24">联系售后</text>
|
||||
</view> -->
|
||||
<view :class="orderInfo.comment_id > 0 ? 'handle-btn' : 'cancel-btn'" v-if="isCompleted" @click.stop="gotoComparisonChart">
|
||||
<text :class="orderInfo.comment_id > 0 ? 'app-fc-white' : 'app-fc-main'" class="fs-24">洗护对比图</text>
|
||||
</view>
|
||||
<view class="handle-btn" v-if="isCompleted && !(orderInfo.comment_id > 0)" @click.stop="gotoEvaluate">
|
||||
<text class="app-fc-white fs-24">去评价</text>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
ORDER_STATUS_CANCELED,
|
||||
ORDER_STATUS_COMPLETED,
|
||||
ORDER_STATUS_RESERVED,
|
||||
ORDER_STATUS_SEND,
|
||||
ORDER_STATUS_SERVICE,
|
||||
ORDER_STATUS_UNPAY,
|
||||
orderStatusList,
|
||||
} from "@/pageHome/constants/home";
|
||||
import {
|
||||
ORDER_TYPE_SITE,
|
||||
PET_TYPE_CAT
|
||||
} from "@/constants/app.business";
|
||||
import moment from "moment/moment";
|
||||
import {
|
||||
gitDiscountfee
|
||||
} from "../../api/login";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
orderInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
isShowReport: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
orderNumber() {
|
||||
return this.orderInfo?.order_no || "--";
|
||||
},
|
||||
orderState() {
|
||||
return (
|
||||
orderStatusList.find(
|
||||
(data) => `${data.value}` === `${this.orderInfo?.status}`
|
||||
)?.label || "--"
|
||||
);
|
||||
},
|
||||
//已预约
|
||||
isReceived() {
|
||||
return (
|
||||
this.orderInfo?.status === ORDER_STATUS_RESERVED ||
|
||||
this.orderInfo?.status === ORDER_STATUS_SEND
|
||||
);
|
||||
},
|
||||
//已完成
|
||||
isCompleted() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_COMPLETED;
|
||||
},
|
||||
//已取消
|
||||
isCanceled() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_CANCELED;
|
||||
},
|
||||
//未支付
|
||||
isUnPay() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_UNPAY;
|
||||
},
|
||||
//服务中
|
||||
isService() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_SERVICE;
|
||||
},
|
||||
// 宠物列表:取 order_list 数组,无则用单宠 pet_id/pet_name 包装成数组
|
||||
petsList() {
|
||||
const list = this.orderInfo?.pet_id;
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
return list;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
// 标签:单宠显示「宠物」,多宠显示「X只宠物」
|
||||
petsCountLabel() {
|
||||
const n = this.petsList.length;
|
||||
return `${n}只宠物`;
|
||||
},
|
||||
// 宠物名称:用 、 拼接
|
||||
petsNames() {
|
||||
const names = this.petsList.map((p) => p.pet_name || "").filter(Boolean);
|
||||
return names.length > 0 ? names.join("、") : "--";
|
||||
},
|
||||
timeTitle() {
|
||||
if (`${this.orderInfo?.order_type}` === `${ORDER_TYPE_SITE}`) {
|
||||
return "下单时间";
|
||||
}
|
||||
return "预约时间";
|
||||
},
|
||||
reservationTime() {
|
||||
if (`${this.orderInfo?.order_type}` === `${ORDER_TYPE_SITE}`) {
|
||||
return this.orderInfo.created_at
|
||||
}
|
||||
const formattedPeriod = this.formatPeriodName(this.orderInfo?.period_name || "");
|
||||
return `${this.orderInfo?.order_date} ${formattedPeriod}`;
|
||||
},
|
||||
address() {
|
||||
return this.orderInfo?.address || "--";
|
||||
},
|
||||
petIcon() {
|
||||
return this.orderInfo?.type === PET_TYPE_CAT ?
|
||||
require("@/static/images/dog.png") :
|
||||
require("@/static/images/dog.png");
|
||||
},
|
||||
isHasNewWeight() {
|
||||
return this.orderInfo?.new_weight_name && this.orderInfo?.weight_chajia;
|
||||
},
|
||||
isNewWeightNoPay() {
|
||||
return (
|
||||
this.isHasNewWeight &&
|
||||
`${this.orderInfo.new_weight_status}` !== `${ORDER_STATUS_RESERVED}`
|
||||
);
|
||||
},
|
||||
isHasWashImgs() {
|
||||
return (
|
||||
this.isCompleted &&
|
||||
(this.orderInfo.json_hou || this.orderInfo.json_qian)
|
||||
);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
methods: {
|
||||
formatPeriodName(periodName) {
|
||||
if (!periodName) return "";
|
||||
// 将 "15:00:00 ~ 18:00:00" 转换成 "15:00 - 18:00"
|
||||
return periodName
|
||||
.replace(/(\d{2}:\d{2}):00/g, "$1") // 去掉秒数,保留时:分格式
|
||||
.replace(/\s*~\s*/g, " - ") // 将 ~ 替换为 -
|
||||
.trim();
|
||||
},
|
||||
jumpTo(url) {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
});
|
||||
},
|
||||
jumpTo1(url) {
|
||||
const data = {
|
||||
region_id: 1,
|
||||
pet_id: url.pet_id.map(item => item.pet_id),
|
||||
order_date : url.order_date
|
||||
};
|
||||
gitDiscountfee(data).then((res) => {
|
||||
const petIdStr = encodeURIComponent(JSON.stringify(url.pet_id));
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/order/additional?id=1&order_id=${url.order_id
|
||||
}&order_no=${url.order_no}&user_id=${url.member_id}&hair=${url.hair}&breed_id=${url.breed_id}&type=${url.type}&weight_id=${url.weight_id}&discount2=${res.data.discount ? res.data.discount / 10 : 0
|
||||
}&pet_id=${petIdStr}`,
|
||||
});
|
||||
// this.discount = res.data.discount/10;
|
||||
});
|
||||
},
|
||||
clickAddService() {
|
||||
this.$emit("addService");
|
||||
},
|
||||
callPhone() {
|
||||
this.$emit("callPhone");
|
||||
},
|
||||
cancelOrder() {
|
||||
this.$emit("cancelOrder", this.orderInfo);
|
||||
},
|
||||
gotoDetail() {
|
||||
this.$emit("gotoDetail");
|
||||
},
|
||||
gotoEvaluate() {
|
||||
this.$emit("gotoEvaluate");
|
||||
},
|
||||
gotoComparisonChart() {
|
||||
const orderId = this.orderInfo?.order_id || this.orderInfo?.pet_order?.order_id;
|
||||
if (!orderId) {
|
||||
uni.showToast({
|
||||
title: "订单ID不存在",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/order/wash-compare?orderid=${orderId}`,
|
||||
});
|
||||
},
|
||||
showGoods() {
|
||||
this.$emit("showGoods");
|
||||
},
|
||||
gotoReport() {
|
||||
const orderId = this.orderInfo?.order_id || this.orderInfo?.pet_order?.order_id;
|
||||
const petsList = this.petsList.filter(item => item.has_precheck) || [];
|
||||
if (!orderId) {
|
||||
uni.showToast({
|
||||
title: "订单ID不存在",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (petsList.length === 0) {
|
||||
uni.showToast({
|
||||
title: "暂无宠物信息",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (petsList.length > 1) {
|
||||
const petsListStr = encodeURIComponent(JSON.stringify(petsList));
|
||||
const createdAt = this.orderInfo?.created_at || '';
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/screening/pet-list?orderId=${orderId}&petsList=${petsListStr}&created_at=${encodeURIComponent(createdAt)}`,
|
||||
});
|
||||
} else {
|
||||
const petId = petsList[0]?.pet_id || petsList[0]?.id || petsList[0]?.chongwu_id || '';
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/screening/details?orderId=${orderId}&petId=${petId}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
reserveAgain() {
|
||||
// 清除当前页面栈,跳转到首页并切换到预约 tab
|
||||
uni.reLaunch({
|
||||
url: '/pages/client/index/index?activePageId=reservationPage'
|
||||
});
|
||||
},
|
||||
payNow() {
|
||||
// 跳转到订单详情页面进行支付
|
||||
// orderInfo 可能直接包含 order_id,也可能在 pet_order 对象中
|
||||
const orderId = this.orderInfo?.order_id || this.orderInfo?.pet_order?.order_id;
|
||||
const source = this.orderInfo?.source || 'pet_order';
|
||||
|
||||
if (!orderId) {
|
||||
uni.showToast({
|
||||
title: '订单信息错误',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/order/order-detail-page?source=${source}&order_id=${orderId}`,
|
||||
events: {
|
||||
refreshList: () => this.$emit('refresh-list'),
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-cell {
|
||||
width: calc(100% - 40rpx);
|
||||
margin: auto;
|
||||
padding: 20rpx;
|
||||
border-radius: 30rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.order-title-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.state-btn {
|
||||
width: 104rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fef6ff;
|
||||
|
||||
text {
|
||||
color: $app_fc_mark;
|
||||
}
|
||||
}
|
||||
|
||||
.done-state-btn {
|
||||
background-color: #f7f7f7;
|
||||
|
||||
text {
|
||||
color: #afa5ae;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line-view {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
background-color: #ececec;
|
||||
margin: 20rpx 0rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.pet-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.detail-title-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-info-cell {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-time-cell {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.precheck-report-bar {
|
||||
margin-top: 12rpx;
|
||||
width: 100%;
|
||||
height: 56rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #ffe6f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
margin-top: 20rpx;
|
||||
.precheck-report-text {
|
||||
font-size: 24rpx;
|
||||
color: #ff19a0;
|
||||
}
|
||||
|
||||
.precheck-report-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.precheck-report-link {
|
||||
font-size: 24rpx;
|
||||
color: #ff19a0;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.precheck-report-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #ff19a0;
|
||||
}
|
||||
}
|
||||
|
||||
.line-view-2 {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
background-color: #ececec;
|
||||
margin-top: 32rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.handle-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.cancel-btn {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #ff19a0;
|
||||
margin-left: 24rpx;
|
||||
border: 1px solid #9B939A;
|
||||
}
|
||||
|
||||
.handle-btn {
|
||||
padding: 12rpx 18rpx;
|
||||
border-radius: 100px;
|
||||
border: 0.5px solid #ff19a0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #ff19a0;
|
||||
margin-left: 24rpx;
|
||||
|
||||
.app-fc {
|
||||
color: #ff19a0;
|
||||
font-family: PingFang SC;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// width: 152rpx;
|
||||
// height: 64rpx;
|
||||
// border-radius: 32rpx;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// border: 2rpx solid #9B939A;
|
||||
// margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.handle-mark-btn {
|
||||
border: 2rpx solid $app_color_main;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
|
||||
// 最后一个按钮的特殊样式(无论是 cancel-btn 还是 handle-btn)
|
||||
>view:last-child {
|
||||
border: 2rpx solid #FF19A0 !important;
|
||||
|
||||
text {
|
||||
color: #FF19A0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
src/components/petOrder/pay-success-modal.vue
Normal file
81
src/components/petOrder/pay-success-modal.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="pay-success-modal" @click.stop="">
|
||||
<view class="body-view" @click.stop="">
|
||||
<image src="@/static/images/success_icon.png" class="pay-success-img" mode="aspectFit"/>
|
||||
<text class="app-fc-main fs-40 app-font-bold-700">提交成功</text>
|
||||
<text class="app-fc-normal fs-28 tip-text">预计0-3个工作日原路退回支付账户</text>
|
||||
<view class="handle-btn" @click.stop="okAction">
|
||||
<text class="app-fc-white fs-32">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
okAction() {
|
||||
this.$emit('ok');
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pay-success-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.body-view {
|
||||
width: calc(100% - 132rpx);
|
||||
padding: 60rpx 10rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-bottom: 10vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.pay-success-img {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 42rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
|
||||
.handle-btn {
|
||||
margin-top: 52rpx;
|
||||
width: 216rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
92
src/components/petOrder/select-weight-modal.vue
Normal file
92
src/components/petOrder/select-weight-modal.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="宠物体重">
|
||||
<view class="select-weight-container">
|
||||
<picker-view :value="selectedIndex" @change="onChangeAction" class="picker-container"
|
||||
:indicator-style="indicatorStyle" :immediate-change="true">
|
||||
<picker-view-column>
|
||||
<view class="item-view" v-for="(item,index) in weightList" :key="item.weight_id">
|
||||
{{ item.weight_name }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view class="submit-btn" @click.stop="changeWeight">
|
||||
<text class="app-fc-white fs-30">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import CustomPickerView from "@/components/dengrq-datetime-picker/customPickerView/index.vue";
|
||||
|
||||
export default {
|
||||
components: { CustomPickerView, SelectModal },
|
||||
props: {
|
||||
petWeight: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
weightList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicatorStyle: `height: 60px;`,
|
||||
selectedIndex: [0]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
console.log("==========this.weightList======>", this.weightList);
|
||||
const index = this.weightList.findIndex((data) => data?.weight_id === this.petWeight?.weight_id)
|
||||
this.selectedIndex = index >= 0 ? [index] : [0]
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
onChangeAction(e) {
|
||||
this.selectedIndex = e.detail.value;
|
||||
},
|
||||
changeWeight() {
|
||||
this.$emit('changeWeight', this.weightList[this.selectedIndex[0]])
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-weight-container {
|
||||
width: 100%;
|
||||
padding: 0 60rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.picker-container {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
|
||||
.item-view {
|
||||
width: 100%;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
89
src/components/select-modal.vue
Normal file
89
src/components/select-modal.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<view class="selected-modal" @click.stop="closeAction">
|
||||
<view class="model-container" @click.stop="">
|
||||
<view class="title-view">
|
||||
<view class="cancel-image"/>
|
||||
<view class="title-info">
|
||||
<text class="fs-36 app-fc-main app-font-bold title-text">{{ title }}</text>
|
||||
</view>
|
||||
<image src="/static/images/close.png" mode="aspectFit" class="cancel-image" @click.stop="closeAction"/>
|
||||
</view>
|
||||
<view class="content-view">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '标题'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.selected-modal {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
z-index: 999;
|
||||
|
||||
.model-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx 40rpx 0px 0px;
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
||||
.title-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 50rpx 52rpx 8rpx 52rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.title-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
.cancel-image {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
119
src/constants/app.business.js
Normal file
119
src/constants/app.business.js
Normal file
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 下载图片的类型
|
||||
*/
|
||||
export const imageType = {
|
||||
homeAd: 1, //首页轮播
|
||||
order: 2, // 下单轮播
|
||||
}
|
||||
|
||||
export const ORDER_TYPE_RESERVATION = '1'; //预约单
|
||||
export const ORDER_TYPE_SITE = '2'; //现场单
|
||||
|
||||
export const COUPON_TYPE_GOODS= '1'; //商品
|
||||
export const COUPON_TYPE_PET = '2'; //宠物
|
||||
|
||||
export const ORDER_TYPE_GOODS = 1; //商品
|
||||
export const ORDER_TYPE_PET_SERVICE = 2; //宠物
|
||||
|
||||
export const PET_TYPE_DOG = 1 //狗
|
||||
export const PET_TYPE_CAT = 2 //猫
|
||||
export const PET_SEX_MALE = 1 //公
|
||||
export const PET_SEX_FEMALE = 2 //母
|
||||
export const PET_HAIR_LONG = 1 //长发
|
||||
export const PET_HAIR_SHORT = 2 //短发
|
||||
|
||||
/**
|
||||
* 是否绝育
|
||||
*/
|
||||
export const PET_IS_STERILIZE_YES = 1 //是
|
||||
export const PET_IS_STERILIZE_NO = 2 //否
|
||||
|
||||
/**
|
||||
* 文章类型
|
||||
* @type {number}
|
||||
*/
|
||||
//关于我们
|
||||
export const ARTICLE_TYPE_ABOUT_US = 1
|
||||
|
||||
//使用帮助
|
||||
export const ARTICLE_TYPE_HELP = 2
|
||||
|
||||
//预约狗详情
|
||||
export const ARTICLE_TYPE_RESERVATION_DOG = 3
|
||||
|
||||
//预约猫详情
|
||||
export const ARTICLE_TYPE_RESERVATION_CAT = 4
|
||||
|
||||
//平台服务协议
|
||||
export const ARTICLE_TYPE_SERVICE_AGREEMENT = 5
|
||||
|
||||
//隐私协议
|
||||
export const ARTICLE_TYPE_PRIVACY_AGREEMENT = 6
|
||||
|
||||
|
||||
/**
|
||||
* 商城订单状态
|
||||
*/
|
||||
export const SHOP_ORDER_UNPAY = 1 // 待支付
|
||||
export const SHOP_ORDER_UNSLIVER = 2 // 待发货
|
||||
export const SHOP_ORDER_UNRECEIVE = 3 // 待收货
|
||||
export const SHOP_ORDER_UNREMARK = 4 // 待评价
|
||||
export const SHOP_ORDER_DONE = 6 // 已完成
|
||||
export const SHOP_ORDER_CANCEL = 5 // 取消订单
|
||||
|
||||
export const SHOP_ORDER_STATUS = {
|
||||
[SHOP_ORDER_UNPAY]: '待支付',
|
||||
[SHOP_ORDER_UNSLIVER]: '待发货',
|
||||
[SHOP_ORDER_UNRECEIVE]: '待收货',
|
||||
[SHOP_ORDER_DONE]: '已完成',
|
||||
[SHOP_ORDER_CANCEL]: '已取消',
|
||||
[SHOP_ORDER_UNREMARK]: '已签收'
|
||||
}
|
||||
|
||||
// 商城订单类型
|
||||
export const ORDER_TYPE_ADDRESS = 1; // 快递
|
||||
export const ORDER_TYPE_BYCAR = 2; // 随车订单
|
||||
export const ORDER_TYPE_BYPET = 3; // 随车购
|
||||
|
||||
/**
|
||||
* 商城订单售后状态
|
||||
*/
|
||||
export const SHOP_ORDER_AFTERSALE = 1 // 售后中
|
||||
export const SHOP_ORDER_AFTERSALE_DONE = 2 // 已退款
|
||||
export const SHOP_ORDER_AFTERSALE_REJECT = 3 // 已驳回
|
||||
|
||||
export const SHOP_ORDER_AFTERSALE_STATUS = {
|
||||
[SHOP_ORDER_AFTERSALE]: '售后中',
|
||||
[SHOP_ORDER_AFTERSALE_DONE]: '已退款',
|
||||
[SHOP_ORDER_AFTERSALE_REJECT]: '已驳回',
|
||||
}
|
||||
|
||||
//服务列表-宠物类型
|
||||
export const PET_TYPE_LIST = [
|
||||
{
|
||||
id: '',
|
||||
name: '全部',
|
||||
type: '',
|
||||
maofa: ''
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '猫(长毛)',
|
||||
type: PET_TYPE_CAT,
|
||||
maofa: PET_HAIR_LONG
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '猫(短毛)',
|
||||
type: PET_TYPE_CAT,
|
||||
maofa: PET_HAIR_SHORT
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
name: '狗',
|
||||
type: PET_TYPE_DOG,
|
||||
maofa: ''
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
55
src/constants/app.config.js
Normal file
55
src/constants/app.config.js
Normal file
@ -0,0 +1,55 @@
|
||||
// 应用的配置页面
|
||||
export default {
|
||||
appName: "Wagoo",
|
||||
appShareName: "Wagoo",
|
||||
appId: "wx00e2dcdc7c02b23a",
|
||||
// apiBaseUrl: 'https://api.wagoo.cc', // 服务端生产地址
|
||||
apiBaseUrl: "https://api.wagoo.me/api/v1", // 服务端测试地址
|
||||
// apiBaseUrl: "https://api.wagoo.pet/api/v1", // 服务端生产地址
|
||||
|
||||
|
||||
// apiBaseUrl: "http:192.168.30.79", //本地接口
|
||||
tencentMapKey: "WSBBZ-7OXK4-46QUC-KFB7B-4N3W7-M2BXM",
|
||||
tencentSecret: "vb7D0PGj7xUvmOLuJz2Jd7ykTMpjiWRJ",
|
||||
qiWeId: "ww285b04a185307dc9",
|
||||
qiWeLink: "https://work.weixin.qq.com/kfid/kfcbb4c893c4d38abf2",
|
||||
router: {
|
||||
/*
|
||||
名词解释:“强制登录页”
|
||||
在打开定义的需强制登录的页面之前会自动检查(前端校验)token的值是否有效,
|
||||
如果无效会自动跳转到登录页面
|
||||
两种模式:
|
||||
1.needLogin:黑名单模式。枚举游客不可访问的页面。
|
||||
2.visitor:白名单模式。枚举游客可访问的页面。
|
||||
* 注意:黑名单与白名单模式二选一
|
||||
*/
|
||||
visitor: [
|
||||
"/", //注意入口页必须直接写 "/"
|
||||
"/pages/client/auth/index",
|
||||
"/pages/client/index/index",
|
||||
"/pages/client/mine/index",
|
||||
"/pages/client/mine/help",
|
||||
"/pages/client/mine/aboutus",
|
||||
"/pages/richText/index",
|
||||
"/pages/client/mine/index",
|
||||
"/pages/client/service/details",
|
||||
"/pages/client/shop/details",
|
||||
"/pageHome/reservation/index",
|
||||
"/pageHome/service/index",
|
||||
"/pageHome/service/feeding",
|
||||
"/pageHome/service/training-booking",
|
||||
"/pageHome/franchise/index",
|
||||
"/pages/client/category/index",
|
||||
"/pages/client/coupon/service-list",
|
||||
"/pages/client/coupon/list",
|
||||
"/pages/client/record/list",
|
||||
"/pages/client/order/list",
|
||||
"/pages/client/collect/list",
|
||||
"/pages/client/order/after-sale",
|
||||
"/pages/client/news/index",
|
||||
"/pages/client/address/index",
|
||||
|
||||
],
|
||||
},
|
||||
debug: false,
|
||||
};
|
||||
45
src/main.js
Normal file
45
src/main.js
Normal file
@ -0,0 +1,45 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import Store from './store'
|
||||
import NavBar from "./components/NavBar.vue";
|
||||
import ListPageTemp from './components/ListPageTemp.vue'
|
||||
import SplitView from './components/SplitView.vue'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.component('NavBar', NavBar)
|
||||
Vue.component('ListPageTemp', ListPageTemp)
|
||||
Vue.component('SplitView', SplitView)
|
||||
|
||||
Vue.prototype.$store = Store;
|
||||
|
||||
Date.prototype.format = function (fmt) {
|
||||
var o = {
|
||||
"M+": this.getMonth() + 1, // 月份
|
||||
"d+": this.getDate(), // 日
|
||||
"h+": this.getHours(), // 小时
|
||||
"m+": this.getMinutes(), // 分
|
||||
"s+": this.getSeconds(), // 秒
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3), // 季度
|
||||
"S": this.getMilliseconds() // 毫秒
|
||||
};
|
||||
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
for (var k in o)
|
||||
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
|
||||
return fmt;
|
||||
}
|
||||
|
||||
// 设置默认字号
|
||||
const fontScale = uni.getStorageSync('fontScale') == '' ? 1 : uni.getStorageSync('fontScale');
|
||||
Store.dispatch('app/setFontScale', fontScale)
|
||||
|
||||
// 设置登录信息
|
||||
const loginInfo = uni.getStorageSync('loginInfo')
|
||||
loginInfo && Store.dispatch('user/setLoginInfo', loginInfo)
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
const app = new Vue({
|
||||
...App,
|
||||
})
|
||||
|
||||
app.$mount()
|
||||
94
src/manifest.json
Normal file
94
src/manifest.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"name" : "Wagoo",
|
||||
"appid" : "__UNI__F0AB43B",
|
||||
"description" : "init",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
/* 5+App特有相关 */
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"modules" : {},
|
||||
/* 模块配置 */
|
||||
"distribute" : {
|
||||
/* 应用发布信息 */
|
||||
"android" : {
|
||||
/* android打包配置 */
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
|
||||
"<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.WRITE_CONTACTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||
"<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.CALL_PHONE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios" : {},
|
||||
/* ios打包配置 */
|
||||
"sdkConfigs" : {},
|
||||
"splashscreen" : {
|
||||
"useOriginalMsgbox" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
/* SDK配置 */
|
||||
"quickapp" : {},
|
||||
/* 快应用特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx00e2dcdc7c02b23a",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"minified" : true,
|
||||
"postcss" : true,
|
||||
"es6" : true,
|
||||
"minifyJS" : true,
|
||||
"minifyWXML" : true,
|
||||
"minifyWXSS" : true
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"lazyCodeLoading" : "requiredComponents",
|
||||
"optimization" : {
|
||||
"subPackages" : true
|
||||
},
|
||||
"requiredPrivateInfos" : [],
|
||||
"permission" : {},
|
||||
"plugins" : {}
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-qq" : {
|
||||
"usingComponents" : true
|
||||
}
|
||||
}
|
||||
356
src/page-reser/components/info-cell.vue
Normal file
356
src/page-reser/components/info-cell.vue
Normal file
@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<view class="order-info-cell" @click.stop="clickAction">
|
||||
<template v-if="cellType === 'text'">
|
||||
<view class="info-top-view">
|
||||
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
|
||||
<text v-if="isRequired" class="required">*</text>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-24">{{ title }}</text>
|
||||
</view>
|
||||
<view class="info-view" v-if="info">
|
||||
<text class="app-fc-main fs-24">{{ info }}</text>
|
||||
</view>
|
||||
<view class="info-view" v-else>
|
||||
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
|
||||
</view>
|
||||
<image v-if="isCanClick" class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix">
|
||||
</image>
|
||||
<view v-else class="right-icon" />
|
||||
</view>
|
||||
</template>
|
||||
<!-- 时间信息-->
|
||||
<template v-if="cellType === 'time'">
|
||||
<view class="info-top-view">
|
||||
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
|
||||
<text class="required">*</text>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-24">{{ title }}</text>
|
||||
</view>
|
||||
<view class="info-bottom-view" v-if="info">
|
||||
<text class="app-fc-main fs-24">{{ info }}</text>
|
||||
</view>
|
||||
<view class="info-view" v-else>
|
||||
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
|
||||
</view>
|
||||
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
|
||||
</view>
|
||||
<text v-if="infoTime == 5" class="s">(该时间段需收取夜间费)</text>
|
||||
</template>
|
||||
|
||||
<!-- 地址信息-->
|
||||
<template v-if="cellType === 'address'">
|
||||
<view class="info-top-view">
|
||||
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
|
||||
<text class="required">*</text>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-24">{{ title }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-bottom-view" style="max-width: 60%;" v-if="addressInfo">
|
||||
<view class="user-info-view">
|
||||
<text class="app-fc-main fs-24 app-font-bold">{{
|
||||
addressInfo.recipient_name
|
||||
}}</text>
|
||||
<text class="app-fc-main fs-24 phone-text app-font-bold">{{
|
||||
addressInfo.phone
|
||||
}}</text>
|
||||
</view>
|
||||
|
||||
<view class="address-view">
|
||||
<text class="app-fc-normal fs-24 address-text">{{
|
||||
`${addressInfo.area_name} ${addressInfo.full_address}`
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-view" v-else>
|
||||
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
|
||||
</view>
|
||||
|
||||
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
|
||||
</view>
|
||||
|
||||
<text class="nightFee">以下区域需收取调度费:奉贤区、嘉定区、青浦区、松江区、崇明区、金山区</text>
|
||||
</template>
|
||||
|
||||
<!-- 价格-->
|
||||
<template v-if="cellType === 'price'">
|
||||
<view class="info-top-view">
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
<text v-if="price" class="app-fc-mark fs-32 app-font-bold-700">{{
|
||||
`¥${price}`
|
||||
}}</text>
|
||||
<text v-else class="app-fc-main fs-28">---</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-if="cellType === 'park'">
|
||||
<view class="info-top-view">
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-24">{{ title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-view">
|
||||
<view class="list-card" v-for="item in park_conditions" :key="item"
|
||||
:class="{ 'selected-list-card': parkState === item }" @click.stop="changeParkState(item)">
|
||||
<text class="fs-24 app-fc-main" :class="{ 'app-fc-mark': parkState === item }">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="input-container" v-if="parkState === '其他'">
|
||||
<textarea :focus="true" :value="otherParkState" class="input-view fs-24 app-fc-main"
|
||||
placeholder="点击输入停车信息" placeholder-class="placeholder-class" @input="onChange" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
imgPrefix
|
||||
} from "@/utils/common";
|
||||
/**
|
||||
* info-cell 预约信息显示
|
||||
* @property {String} cellType - 显示类型 (默认text) text/address/time/price/park
|
||||
* @value text 文本类型
|
||||
* @value address 地址类型
|
||||
* @value time 时间类型
|
||||
* @value price 价格类型
|
||||
* @value park 停车状况
|
||||
* @property {String} infoIcon - 左侧图标
|
||||
* @property {String} title - 标题
|
||||
* @property {String} info - 显示内容
|
||||
* @property {Object} addressInfo - 地址信息
|
||||
* @property {String} price - 价格
|
||||
* @property {String} parkState - 停车状况
|
||||
*
|
||||
* @property {String} placeholder - 占位
|
||||
*
|
||||
* @event {Function} clickAction 点击cell触发事件
|
||||
* @event {Function} changeParkState 停车状况改变触发事件
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
isCanClick: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
cellType: {
|
||||
type: String,
|
||||
default: "info",
|
||||
},
|
||||
infoIcon: {
|
||||
type: String | null,
|
||||
default: require("@/static/images/arrow_right.png"),
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
infoTime: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
info: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
addressInfo: {
|
||||
type: Object | null,
|
||||
default: () => {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
price: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
parkState: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
otherParkState: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
isRequired: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
park_conditions: ["小区", "地库", "路边", "其他"],
|
||||
imgPrefix
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clickAction() {
|
||||
this.isCanClick && this.$emit("clickAction");
|
||||
},
|
||||
changeParkState(state) {
|
||||
this.$emit("changeParkState", state);
|
||||
},
|
||||
onChange(e) {
|
||||
this.$emit("changeOtherParkState", e.detail.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-info-cell {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 36rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
height: 2rpx;
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.info-top-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
.cell-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.title-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.right-icon {
|
||||
margin-left: 16rpx;
|
||||
width: 10rpx;
|
||||
height: 5rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-bottom-view {
|
||||
// width: calc(100% - 56rpx);
|
||||
// margin-top: 32rpx;
|
||||
// margin-left: 56rpx;
|
||||
// padding-right: 104rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
.user-info-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.phone-text {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.address-text {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nightFee {
|
||||
color: #ff19a0;
|
||||
font-family: PingFangSC;
|
||||
font-size: 20rpx;
|
||||
font-weight: normal;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.s {
|
||||
color: #ff19a0;
|
||||
font-family: PingFangSC;
|
||||
font-size: 24rpx;
|
||||
margin: 6rpx 0 0 52rpx;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
margin-top: 16rpx;
|
||||
width: 100%;
|
||||
padding-right: 30rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
|
||||
.list-card {
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 182px;
|
||||
background-color: #f5f5f5;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.selected-list-card {
|
||||
background-color: #fee9f3;
|
||||
}
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
padding: 0 30rpx 0 0;
|
||||
box-sizing: border-box;
|
||||
margin-top: 32rpx;
|
||||
|
||||
.input-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 32rpx;
|
||||
border-radius: 30rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
background-color: #f9f7f9;
|
||||
}
|
||||
|
||||
.placeholder-class {
|
||||
color: #666262;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #FF19A0;
|
||||
}
|
||||
</style>
|
||||
184
src/page-reser/components/order-goods-modal.vue
Normal file
184
src/page-reser/components/order-goods-modal.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="随车购商品" class="order-goods-modal">
|
||||
<view class="goods-container">
|
||||
<scroll-view class="goods-scroll-view" :scroll-y="true">
|
||||
<view class="goods-item" v-for="item in goodsList" :key="item.order_id" @click.stop="gotoDetail(item)">
|
||||
<image mode="aspectFit" class="goods-img" :src="item.goods_pic"/>
|
||||
<view class="goods-info-view">
|
||||
<text class="app-fc-main fs-32 app-font-bold-500 app-text-ellipse">
|
||||
{{ item.goods_name || '--' }}
|
||||
</text>
|
||||
<text class="app-fc-normal fs-24 goods-num-text">{{ `数量X${item.number}` }}</text>
|
||||
<text class="app-fc-main fs-28 goods-price-text" >实付款
|
||||
<text class="fs-28 app-fc-alarm">{{ `¥${item.goods_price}` }}</text>
|
||||
</text>
|
||||
</view>
|
||||
<image src="@/static/images/arrow_right_black.png" mode="aspectFit" class="good-arrow"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="goods-price-container">
|
||||
<view class="goods-left-view">
|
||||
<view class="price-view">
|
||||
<text class="app-fc-main fs-28">¥</text>
|
||||
<text class="app-fc-main app-font-bold-700 fs-50 price-text">{{ price }}</text>
|
||||
</view>
|
||||
<view class="price-tips-view">
|
||||
<image src="/pageHome/static/tips.png" mode="aspectFit" class="tips-img"/>
|
||||
<text class="fs-24">商品总价</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="closeAction">
|
||||
<text class="app-fc-white fs-30">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
goodsList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
options: {
|
||||
styleIsolation: "shared",
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
gotoDetail(item){
|
||||
this.$emit('gotoDetail', item.goods_id);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-goods-modal {
|
||||
|
||||
::v-deep {
|
||||
.selected-modal .model-container {
|
||||
background: #fff0f5;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-container {
|
||||
width: 100%;
|
||||
height: 800rpx;
|
||||
position: relative;
|
||||
|
||||
.goods-scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.goods-item {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-top: 32rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.goods-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.goods-info-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 160rpx - 24rpx - 40rpx);
|
||||
.goods-num-text {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.goods-price-text {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.good-arrow {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-price-container {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 26rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.goods-left-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.price-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
|
||||
.price-text {
|
||||
margin-left: 8rpx;
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.price-tips-view {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.tips-img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 260rpx;
|
||||
height: 92rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #FE019B;
|
||||
border-radius: 46rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
49
src/page-reser/components/price-description-modal.vue
Normal file
49
src/page-reser/components/price-description-modal.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="价格说明">
|
||||
<view class="price-description-container">
|
||||
<view class="price-info">
|
||||
<text class="app-fc-main fs-32">{{priceInfo.desc}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
priceInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.price-description-container {
|
||||
width: 100%;
|
||||
padding: 0 40rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.price-info {
|
||||
width: 100%;
|
||||
padding: 36rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #F7F3F7;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
230
src/page-reser/components/select-address-modal.vue
Normal file
230
src/page-reser/components/select-address-modal.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择服务地址">
|
||||
<view class="select-address-container">
|
||||
<view v-if="!isLoading && addressList.length" class="address-container">
|
||||
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
|
||||
refresher-background="transparent">
|
||||
<view class="address-item" v-for="(item, index) in addressList" :key="item.id"
|
||||
@click.stop="changeAddress(item)">
|
||||
<view class="address-info-view">
|
||||
<view class="address-name-view">
|
||||
<text v-if="item.is_default" class="default-address fs-18">默认</text>
|
||||
<text class="app-fc-main fs-30 app-font-bold name-text">{{ item.recipient_name }}</text>
|
||||
<text class="app-fc-main fs-30 app-font-bold">{{ item.phone }}</text>
|
||||
</view>
|
||||
<text class="app-fc-normal fs-26">{{ getAddressText(item) }}</text>
|
||||
</view>
|
||||
<image @click.stop="goToEditAddress(item)" src="@/static/images/address_edit.png" mode="aspectFit" class="address-edit-icon"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="address-container flex-center" v-if="isLoading">
|
||||
<uni-load-more status="loading" :show-text="false"/>
|
||||
</view>
|
||||
<view v-if="addressList.length === 0 && !isLoading" class="address-container">
|
||||
<image src="https://activity.wagoo.live/no_address.png" class="no-address-img" mode="widthFix"/>
|
||||
<!-- <text class="app-fc-normal fs-32 no-text">暂无地址信息</text> -->
|
||||
</view>
|
||||
<view class="add-btn" @click.stop="gotoAddAddress">
|
||||
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
|
||||
<text class="app-fc-white fs-30">添加地址</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import { getAddressList } from "@/api/address";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
selectAddress: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addressList: [],
|
||||
isLoading: true,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
isLoadMore: false,
|
||||
isNoMore: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
userInfo() {
|
||||
return this.$store.state?.user?.userInfo || {};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changeAddress(address) {
|
||||
this.$emit('changeAddress', address);
|
||||
},
|
||||
loadMoreAction() {
|
||||
if (this.isNoMore || this.isLoadMore) {
|
||||
return;
|
||||
}
|
||||
this.pageNumber++;
|
||||
this.isLoadMore = true;
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
getAddressList({ user_id: this.userInfo.userID }).then((res) => {
|
||||
let list = res?.data || [];
|
||||
if (this.pageNumber === 1) {
|
||||
this.addressList = list;
|
||||
} else {
|
||||
this.addressList = [...this.addressList, ...list];
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
this.isNoMore = list.length < this.pageSize;
|
||||
}).catch(() => {
|
||||
if (this.pageNumber !== 1) {
|
||||
this.pageNumber--;
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
})
|
||||
},
|
||||
getAddressText(item) {
|
||||
// 组合地址信息:省市区 + 详细地址
|
||||
const region = [item.province, item.city, item.district].filter(Boolean).join('');
|
||||
return region ? `${region}${item.full_address}` : item.full_address;
|
||||
},
|
||||
gotoAddAddress() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?isAdd=1`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
goToEditAddress(item){
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?id=${item?.id || ""}`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-address-container {
|
||||
width: 100%;
|
||||
padding: 0 56rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.address-container {
|
||||
width: 100%;
|
||||
height: 444rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 18rpx;
|
||||
|
||||
.no-address-img {
|
||||
margin-top: 30rpx;
|
||||
width: 348rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.no-text {
|
||||
margin: 0 0 24rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 0rpx;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 2rpx solid #F7F3F7;
|
||||
|
||||
.address-info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-right: 30rpx;
|
||||
|
||||
.address-name-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.default-address {
|
||||
padding: 2rpx 6rpx;
|
||||
color: #40ae36;
|
||||
background: rgba(64, 174, 54, 0.2);
|
||||
border-radius: 6rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.address-edit-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
|
||||
.add-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
201
src/page-reser/components/select-pet-modal.vue
Normal file
201
src/page-reser/components/select-pet-modal.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择宠物">
|
||||
<view class="select-pet-container">
|
||||
<view v-if="!isLoading && petList.length" class="pet-container">
|
||||
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
|
||||
refresher-background="transparent">
|
||||
<view class="pet-item" v-for="(item, index) in petList" :key="item.chongwu_id" @click.stop="changePet(item)">
|
||||
<image v-if="item.chongwu_pic_url" class="pet-icon" mode="scaleToFill" :src="item.chongwu_pic_url"/>
|
||||
<image v-else mode="scaleToFill" class="pet-icon" src="https://activity.wagoo.live/record_avator.png"/>
|
||||
<view class="pet-name-view">
|
||||
<text class="app-fc-main fs-32 app-font-bold-500">{{ item.name }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="selectPetInfo.chongwu_id === item.chongwu_id"
|
||||
src="@/static/images/cart_checked.png"
|
||||
mode="widthFix"
|
||||
class="select-icon"
|
||||
/>
|
||||
<image
|
||||
v-else
|
||||
src="@/static/images/unchecked.png"
|
||||
mode="widthFix"
|
||||
class="select-icon"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="pet-container flex-center" v-if="isLoading">
|
||||
<uni-load-more status="loading" :show-text="false"/>
|
||||
</view>
|
||||
<view v-if="petList.length === 0 && !isLoading" class="pet-container" @click.stop="gotoAddPet">
|
||||
<image src="https://activity.wagoo.live/no_pet.png" class="no-pet-img" mode="heightFix" />
|
||||
</view>
|
||||
<view class="add-btn" @click.stop="gotoAddPet">
|
||||
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
|
||||
<text class="app-fc-white fs-30">添加宠物</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import { getPetList } from "@/api/common";
|
||||
import appConfig from '../../constants/app.config';
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
petType: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
selectPetInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
petList: [],
|
||||
isLoadMore: false,
|
||||
isNoMore: false,
|
||||
appConfig,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changePet(pet) {
|
||||
this.$emit('changePet', pet);
|
||||
},
|
||||
loadMoreAction() {
|
||||
if (this.isNoMore || this.isLoadMore) {
|
||||
return;
|
||||
}
|
||||
this.pageNumber++;
|
||||
this.isLoadMore = true;
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
getPetList(null, this.pageNumber, this.pageSize).then((res) => {
|
||||
let list = res?.info || [];
|
||||
if (this.pageNumber === 1) {
|
||||
this.petList = list;
|
||||
} else {
|
||||
this.petList = [...this.petList, ...list];
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
this.isNoMore = list.length < this.pageSize;
|
||||
}).catch(() => {
|
||||
if (this.pageNumber !== 1) {
|
||||
this.pageNumber--;
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
})
|
||||
},
|
||||
gotoAddPet() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/record/edit?type=${this.petType }&typeId=aaa`,
|
||||
events: {
|
||||
addPetSuccess: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-pet-container {
|
||||
width: 100%;
|
||||
padding: 0 60rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.pet-container {
|
||||
width: 100%;
|
||||
height: 444rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 18rpx;
|
||||
|
||||
.no-pet-img {
|
||||
height: calc(1289 / 1363 * 550rpx);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.no-text {
|
||||
margin: 44rpx 0 26rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pet-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 34rpx 22rpx;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 2rpx solid #F7F3F7;
|
||||
|
||||
.pet-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pet-name-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
width: 27rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
|
||||
.add-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
262
src/page-reser/components/select-reservation-time-modal.vue
Normal file
262
src/page-reser/components/select-reservation-time-modal.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择预约时间">
|
||||
<view class="select-time-container">
|
||||
<view class="calendar-container">
|
||||
<uni-calendar
|
||||
:insert="true"
|
||||
class="uni-calendar--hook"
|
||||
:start-date="startDate"
|
||||
:end-date="endDate"
|
||||
:date="selectDate"
|
||||
:showMonth="false"
|
||||
@change="changeDate"/>
|
||||
</view>
|
||||
<view class="tips-view">
|
||||
<image src="/pageHome/static/tips.png" class="tips-img" mode="aspectFit"></image>
|
||||
<text class="fs-24 app-fc-normal">
|
||||
{{isAfternoon? '可以预约的时间范围是后天及之后的 14 天' : '可以预约的时间范围是明天及之后的 14 天' }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="fs-32 app-fc-main time-header-text">可预约时段</text>
|
||||
<view class="time-list-container">
|
||||
<scroll-view v-if="!isLoading && dateList.length" class="list-scroll-view" :scroll-y="true">
|
||||
<view
|
||||
@click.stop="changeTimeAction(item)"
|
||||
class="time-item"
|
||||
:class="{'selected-time-item': selectTimeRange.shiduan_id === item.shiduan_id, 'disabled-time-item': item.num === 0}"
|
||||
v-for="item in dateList"
|
||||
:key="item.shiduan_id">
|
||||
<text class="fs-28 app-fc-main">{{ `${item.start}-${item.end}` }}</text>
|
||||
<text class="fs-28 app-fc-main">{{ item.num === 0 ? '已约满' : `剩余${item.num}个` }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="isLoading" class="loading-view">
|
||||
<uni-load-more status="loading"/>
|
||||
</view>
|
||||
<view v-if="!isLoading && dateList.length === 0" class="loading-view">
|
||||
<text class="fs-28 app-fc-main">无可预约时间</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="changeReservationTime">
|
||||
<text class="app-fc-white fs-30">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import moment from "moment";
|
||||
import "moment/locale/zh-cn";
|
||||
import { getYuYueTimeList } from "@/api/order";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
selectTime: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectTimeRange: {},
|
||||
isLoading: false,
|
||||
selectDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
|
||||
startDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
|
||||
endDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(16, 'days').format('YYYY-MM-DD') : moment().add(15, 'days').format('YYYY-MM-DD'),
|
||||
dateList: [],
|
||||
//是下午
|
||||
isAfternoon: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00'))
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
moment.locale('zh-cn'); // 设置中文本地化
|
||||
if (Object.keys(this.selectTime).length > 0) {
|
||||
this.selectDate = this.selectTime.date;
|
||||
this.selectTimeRange = this.selectTime;
|
||||
this.getTimeArray(this.selectTime.date)
|
||||
} else {
|
||||
this.getTimeArray(this.selectDate)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changeDate(e) {
|
||||
console.log(e,'--')
|
||||
this.selectTimeRange = {};
|
||||
this.selectDate = e.fulldate;
|
||||
this.getTimeArray(e.fulldate)
|
||||
},
|
||||
changeTimeAction(item) {
|
||||
if (item.num === 0) {
|
||||
uni.showToast({
|
||||
title: '当前时间已约满',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
this.selectTimeRange = item;
|
||||
}
|
||||
},
|
||||
changeReservationTime() {
|
||||
if (this.dateList.length === 0) {
|
||||
uni.showToast({
|
||||
title: '暂无可预约时间',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (Object.keys(this.selectTimeRange).length === 0) {
|
||||
uni.showToast({
|
||||
title: '请选择一个预约时段',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
let dateLabel;
|
||||
if (moment().format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `今天${moment(this.selectDate).format('M月D日')}`
|
||||
} else if (moment().add(1, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `明天${moment(this.selectDate).format('M月D日')}`
|
||||
} else if (moment().add(2, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `后天${moment(this.selectDate).format('M月D日')}`
|
||||
} else {
|
||||
dateLabel = `${moment(this.selectDate).format('M月D日')}`
|
||||
}
|
||||
this.$emit('changeReservationTime', {
|
||||
dateLabel,
|
||||
date: this.selectDate,
|
||||
...this.selectTimeRange,
|
||||
});
|
||||
},
|
||||
getTimeArray(t) {
|
||||
this.isLoading = true
|
||||
this.dateList = [];
|
||||
let isCurrent = moment().format('YYYY-MM-DD') === t;
|
||||
return getYuYueTimeList(t).then((res) => {
|
||||
let list = (res?.info || []).map((item) => {
|
||||
return {
|
||||
...item,
|
||||
start: item.start.substring(0, 5),
|
||||
end: item.end.substring(0, 5),
|
||||
};
|
||||
});
|
||||
this.dateList = list.filter((item) => {
|
||||
let end = moment(item.end, 'HH:mm');
|
||||
let now = moment();
|
||||
if (isCurrent) {
|
||||
return end.isAfter(now);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
this.isLoading = false;
|
||||
return Promise.resolve();
|
||||
}).catch(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-time-container {
|
||||
width: 100%;
|
||||
height: 82vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.calendar-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tips-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #F5F5F5;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.tips-img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time-header-text {
|
||||
margin-left: 28rpx;
|
||||
}
|
||||
|
||||
.time-list-container {
|
||||
margin-left: 28rpx;
|
||||
width: calc(100% - 56rpx);
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.loading-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list-scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
.time-item {
|
||||
width: 100%;
|
||||
height: 102rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
background-color: #fff;
|
||||
opacity: 1;
|
||||
margin-bottom: 22rpx;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.selected-time-item {
|
||||
border: 2rpx solid $app_color_main;
|
||||
background-color: $app_color_mark_bg_color;
|
||||
}
|
||||
|
||||
.disabled-time-item {
|
||||
background-color: #FBF7FC;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: calc(100% - 120rpx);
|
||||
margin-left: 60rpx;
|
||||
margin-bottom: 10rpx;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
79
src/page-reser/constants/home.js
Normal file
79
src/page-reser/constants/home.js
Normal file
@ -0,0 +1,79 @@
|
||||
export const ORDER_STATUS_UNPAY = 1; // 未支付
|
||||
export const ORDER_STATUS_RESERVED = 2; // 已预约
|
||||
export const ORDER_STATUS_SEND = 3; // 已派单
|
||||
export const ORDER_STATUS_SERVICE = 4; //服务中
|
||||
export const ORDER_STATUS_COMPLETED = 5; // 已完成
|
||||
export const ORDER_STATUS_CANCELED = 6; // 已取消
|
||||
export const ORDER_STATUS_REFUND = 7; // 退款中
|
||||
|
||||
export const PRICE_DIFF_TYPE_SERVICE = '1'; //差价类型 服务
|
||||
|
||||
|
||||
export const orderStatusList = [
|
||||
{
|
||||
value: ORDER_STATUS_UNPAY,
|
||||
label: '未支付',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_RESERVED,
|
||||
label: '已预约',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SEND,
|
||||
label: '已派单',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SERVICE,
|
||||
label: '服务中',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_COMPLETED,
|
||||
label: '已完成',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_CANCELED,
|
||||
label: '已取消',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_REFUND,
|
||||
label: '退款中',
|
||||
},
|
||||
]
|
||||
|
||||
//展示的状态
|
||||
export const showOrderStatus = [
|
||||
{
|
||||
value: '',
|
||||
label: '全部',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_RESERVED,
|
||||
label: '已预约',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SERVICE,
|
||||
label: '服务中',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_COMPLETED,
|
||||
label: '已完成',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_CANCELED,
|
||||
label: '已取消',
|
||||
},
|
||||
]
|
||||
|
||||
//充值相关
|
||||
export const rechargeStatus = [
|
||||
{
|
||||
value: '1',
|
||||
label: '充值',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '充值记录',
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
851
src/page-reser/order/order-detail-page.vue
Normal file
851
src/page-reser/order/order-detail-page.vue
Normal file
@ -0,0 +1,851 @@
|
||||
<template>
|
||||
<view class="order-detail-container">
|
||||
<view class="detail-container">
|
||||
<uni-load-more v-if="isLoading" status="loading" :show-text="false"/>
|
||||
<scroll-view v-if="!isLoading" class="scroll-view" scroll-y>
|
||||
<view class="info-cell info-cell-guanjia" v-if="carNumber || managerPhoneNum || managerName">
|
||||
<image v-if="managerPic" :src="managerPic" mode="aspectFit" class="head-icon"/>
|
||||
<image v-else src="/pageHome/static/head.png" mode="aspectFit" class="head-icon"/>
|
||||
<view class="guan-jia-info-container">
|
||||
<text class="app-fc-main fs-32 app-font-bold-700">{{ carNumber }}</text>
|
||||
<text class="app-fc-normal fs-24">{{ `${managerName}管家` }}</text>
|
||||
</view>
|
||||
<view class="phone-view" v-if="managerPhoneNum" @click.stop="isShowCallManagerModal = true">
|
||||
<image src="../static/phone.png" mode="aspectFit" class="call-img"/>
|
||||
<text class="app-fc-main fs-24">电话</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<view class="info-title-view">
|
||||
<image src="@/static/images/address.png" mode="aspectFit" class="info-icon"/>
|
||||
<text class="app-fc-main fs-28">{{ addressInfo.name }}</text>
|
||||
<text class="app-fc-main fs-28 phone-text">{{ addressInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="address-view">
|
||||
<view class="info-icon"/>
|
||||
<text class="app-fc-normal fs-24 address-text">
|
||||
{{ orderInfo.address || `${addressInfo.area_name}${addressInfo.address}` || '--' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-cell pet-info-container">
|
||||
<view class="pet-info-cell">
|
||||
<image v-if="petInfo.chongwu_pic" mode="scaleToFill" class="pet-icon"
|
||||
:src="petInfo.chongwu_pic"/>
|
||||
<image v-else mode="scaleToFill" class="pet-icon" src="https://activity.wagoo.live/record_avator.png"/>
|
||||
<view class="pet-info-view">
|
||||
<text class="fs-26 app-fc-main app-font-bold-700">{{ petInfo.name }}</text>
|
||||
<text class="app-fc-normal fs-24">{{ petDesc }}</text>
|
||||
<text class="app-fc-normal fs-24">{{ `出生日期:${petInfo.birthday}` }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-goods-container" v-if="goodsList.length" @click.stop="isShowGoodsModal = true">
|
||||
<view class="order-goods-view">
|
||||
<scroll-view class="goods-scroll-view" scroll-x>
|
||||
<image :src="goods.goods_pic" mode="aspectFit" class="good-icon" v-for="goods in goodsList"
|
||||
:key="goods.goods_id"/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="order-goods-num-view">
|
||||
<text class="app-fc-main fs-24">{{ `随车购商品x${goodsNum}` }}</text>
|
||||
<image class="arrow-icon" src="@/static/images/arrow_right_black.png" mode="aspectFit"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-cell info-cell-gap-20">
|
||||
<detail-cell title="实际宠物重量区间" :content="orderInfo.new_weight_name || orderInfo.weight_name"/>
|
||||
<view class="order-price-view">
|
||||
<detail-cell title="预约支付金额" :content="`¥${orderInfo.price}`" :content-mark="false"/>
|
||||
<detail-cell v-if="orderInfo.dikou_id" title="优惠券" :content="`-¥${couponPrice}`" :content-mark="false"/>
|
||||
<detail-cell v-if="orderInfo.fuwuquan_id" title="服务券金额" :content="`¥${servicePrice}`"
|
||||
:content-mark="false"/>
|
||||
</view>
|
||||
<template v-if="orderInfo.new_weight_name && orderInfo.weight_chajia">
|
||||
<detail-cell v-if="Number(orderInfo.weight_chajia) < 0" title="附加服务费退款金额"
|
||||
:content="`¥${orderInfo.weight_chajia}`"/>
|
||||
<detail-cell v-else title="附加服务费支付金额" :content="`¥${orderInfo.weight_chajia}`"/>
|
||||
</template>
|
||||
<view class="line-view"/>
|
||||
<detail-cell title="金额合计" :content="`¥${totalMoney}`" :title-mark="true" :content-mark="true"
|
||||
:custom-content-style="{'color': '#E70E0E'}"/>
|
||||
</view>
|
||||
|
||||
<view class="info-cell info-cell-gap-20">
|
||||
<detail-cell title="服务订单编号" :content="orderInfo.order_no" :content-mark="false"/>
|
||||
<view class="line-view"/>
|
||||
<detail-cell v-if="isShowReservationTime" title="预约时间" :content="reservationTime" :content-mark="false"/>
|
||||
<detail-cell v-if="!isUnPay && payTime" title="付款时间" :content="payTime" :content-mark="false"/>
|
||||
<detail-cell v-if="serviceStartTime" title="服务开始时间" :content="serviceStartTime" :content-mark="false"/>
|
||||
<detail-cell v-if="serviceEndTime" title="服务完成时间" :content="serviceEndTime" :content-mark="false"/>
|
||||
<view class="line-view"/>
|
||||
<detail-cell title="停车状况" :content="orderInfo.tingche_desc" :content-mark="false"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<add-service-pay-modal
|
||||
v-if="isShowPayModal"
|
||||
@close="hidePayModal"
|
||||
:order-info="orderInfo"
|
||||
:new-weight="otherWeight"
|
||||
@changeWeight="selectWeight"
|
||||
@paymentConfirm="paymentConfirm"
|
||||
/>
|
||||
<select-weight-modal
|
||||
v-if="isShowWeight"
|
||||
:weight-list="weightList"
|
||||
:pet-weight="otherWeight"
|
||||
@close="closeWeightModal"
|
||||
@changeWeight="changeOtherWeight"
|
||||
/>
|
||||
<pay-success-modal
|
||||
v-if="isShowPaySuccessModal"
|
||||
@close="isShowPaySuccessModal = false"
|
||||
@ok="okPayAction"
|
||||
/>
|
||||
</view>
|
||||
<view class="bottom-view" v-if="isHasHandle">
|
||||
<view class="handle-btn" v-if="isCanCancelOrder" @click.stop="clickCancel">
|
||||
<text class="fs-24 app-fc-main">取消预约</text>
|
||||
</view>
|
||||
<view class="handle-btn" v-if="isService" @click.stop="showGoods">
|
||||
<text class="app-fc-main fs-24">随车购商品</text>
|
||||
</view>
|
||||
<view class="handle-btn handle-mark-btn" v-if="isCanAddService" @click.stop="showAddService">
|
||||
<text class="fs-24 app-fc-white">调整服务费</text>
|
||||
</view>
|
||||
<view class="handle-btn" v-if="isUnPay" @click.stop="payOrderAction">
|
||||
<text class="fs-24 app-fc-main">立即支付</text>
|
||||
</view>
|
||||
<view class="handle-btn" v-if="isNeedPayService" @click.stop="payNewWeightOrderAction">
|
||||
<text class="fs-24 app-fc-main">支付服务费</text>
|
||||
</view>
|
||||
<view class="handle-btn" v-if="isCompleted" @click.stop="isShowCallModal = true">
|
||||
<text class="app-fc-main fs-24">联系售后</text>
|
||||
</view>
|
||||
<view class="handle-btn " v-if="isHasWashImgs" @click.stop="isShowCompareModal = true">
|
||||
<text class="app-fc-main fs-24">洗护对比图</text>
|
||||
</view>
|
||||
<view class="handle-btn handle-mark-btn" v-if="isCompleted" @click.stop="gotoEvaluate">
|
||||
<text class="fs-24 app-fc-white">{{ orderInfo.pinglun_id ? '查看评价' : '去评价' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<call-modal
|
||||
:phone-number="orderInfo.phone"
|
||||
v-if="isShowCallModal"
|
||||
@close="isShowCallModal = false"
|
||||
/>
|
||||
<call-modal
|
||||
:phone-number="managerPhoneNum"
|
||||
v-if="isShowCallManagerModal"
|
||||
@close="isShowCallManagerModal = false"
|
||||
/>
|
||||
<order-goods-modal
|
||||
v-if="isShowGoodsModal"
|
||||
@close="isShowGoodsModal = false"
|
||||
@gotoDetail="gotoGoodsDetail"
|
||||
:goods-list="goodsList"
|
||||
:price="goodsPrice"
|
||||
/>
|
||||
<CompareModal
|
||||
v-if="isShowCompareModal"
|
||||
@close="isShowCompareModal = false"
|
||||
:is-can-share="true"
|
||||
:is-can-remark="!orderInfo.pinglun_id"
|
||||
:after-imgs="afterImages"
|
||||
:before-imgs="beforeImages"
|
||||
@add="selectImgsChange"
|
||||
@share="shareToCommunity"
|
||||
/>
|
||||
<pop-up-modal
|
||||
v-if="isShowCancelModal"
|
||||
content="确定要取消预约吗?"
|
||||
@confirm="cancelOrder"
|
||||
@cancel="isShowCancelModal = false"
|
||||
/>
|
||||
<success-modal
|
||||
v-if="showSuccessModal"
|
||||
title="转发成功"
|
||||
message="请前往宠圈查看"
|
||||
@ok="jumpToCommunity"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DetailCell from "@/components/petOrder/detail-cell.vue";
|
||||
import {
|
||||
ORDER_STATUS_CANCELED,
|
||||
ORDER_STATUS_COMPLETED,
|
||||
ORDER_STATUS_RESERVED,
|
||||
ORDER_STATUS_SEND,
|
||||
ORDER_STATUS_SERVICE,
|
||||
ORDER_STATUS_UNPAY,
|
||||
PRICE_DIFF_TYPE_SERVICE
|
||||
} from "@/pageHome/constants/home";
|
||||
import { addServicePay, cancelOrder, getOrderDetail, payOrder } from "@/api/order";
|
||||
import {
|
||||
ORDER_TYPE_PET_SERVICE, ORDER_TYPE_RESERVATION, PET_HAIR_LONG,
|
||||
PET_IS_STERILIZE_YES,
|
||||
PET_SEX_MALE,
|
||||
PET_TYPE_CAT,
|
||||
PET_TYPE_DOG
|
||||
} from "@/constants/app.business";
|
||||
import { shareCommunity } from '../../api/community';
|
||||
import { cancelPetOrderRefund } from "../../api/login";
|
||||
|
||||
import AddServicePayModal from "@/components/petOrder/add-service-pay-modal.vue";
|
||||
import SelectWeightModal from "@/components/petOrder/select-weight-modal.vue";
|
||||
import { getWeightList } from "@/api/common";
|
||||
import appConfig from '@/constants/app.config'
|
||||
import PaySuccessModal from "@/components/petOrder/pay-success-modal.vue";
|
||||
import moment from "moment";
|
||||
import CallModal from "@/components/petOrder/call-modal.vue";
|
||||
import OrderGoodsModal from "@/pageHome/components/order-goods-modal.vue";
|
||||
import CompareModal from "@/components/CompareModal.vue";
|
||||
import PopUpModal from "@/components/PopUpModal.vue";
|
||||
import SuccessModal from "@/components/SuccessModal.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PopUpModal,
|
||||
CompareModal,
|
||||
OrderGoodsModal,
|
||||
CallModal,
|
||||
SelectWeightModal,
|
||||
AddServicePayModal,
|
||||
DetailCell,
|
||||
PaySuccessModal,
|
||||
SuccessModal
|
||||
},
|
||||
onLoad(option) {
|
||||
uni.$on('createRemarkSuccess', this.refreshOrderDetail)
|
||||
if (option.orderId) {
|
||||
this.orderId = option.orderId;
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.getData();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orderId: '',
|
||||
isLoading: true,
|
||||
orderInfo: {},
|
||||
isShowPayModal: false,
|
||||
otherWeight: {},
|
||||
isShowWeight: false,
|
||||
allWeightList: [],
|
||||
appConfig,
|
||||
isShowPaySuccessModal: false,
|
||||
isShowCallModal: false,
|
||||
isShowGoodsModal: false,
|
||||
isShowCallManagerModal: false,
|
||||
isShowCompareModal: false,
|
||||
isShowCancelModal: false,
|
||||
showSuccessModal: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
goodsList() {
|
||||
return this.orderInfo?.order_list || [];
|
||||
},
|
||||
goodsPrice() {
|
||||
const price = this.goodsList.reduce((total, item) => {
|
||||
return total + item.goods_price * item.number;
|
||||
}, 0);
|
||||
return +price.toFixed(2)
|
||||
},
|
||||
goodsNum() {
|
||||
let num = 0
|
||||
this.goodsList.forEach(item => {
|
||||
num = num + item.number;
|
||||
})
|
||||
return num;
|
||||
},
|
||||
addressInfo() {
|
||||
return this.orderInfo?.address_info || {};
|
||||
},
|
||||
petInfo() {
|
||||
return this.orderInfo?.chongwu_info || {};
|
||||
},
|
||||
weightInfo() {
|
||||
return this.orderInfo?.weight_info || {};
|
||||
},
|
||||
varietyInfo() {
|
||||
return this.orderInfo?.pinzhong_info || {};
|
||||
},
|
||||
isShowReservationTime() {
|
||||
return `${this.orderInfo?.order_type}` === `${ORDER_TYPE_RESERVATION}`
|
||||
},
|
||||
reservationTime() {
|
||||
return `${this.orderInfo?.yuyue_date} ${this.orderInfo?.yuyue_time}`
|
||||
},
|
||||
petDesc() {
|
||||
const varietyName = this.varietyInfo?.pinzhong_name || '';
|
||||
const sex = `${this.petInfo.sex}` === `${PET_SEX_MALE}` ? '男生' : '女生';
|
||||
const pet = `${this.petInfo.type}` === `${PET_TYPE_CAT}` ? '猫' : '狗';
|
||||
let hair = '';
|
||||
if (`${this.petInfo.type}` === `${PET_TYPE_CAT}`) {
|
||||
hair = `/${`${this.petInfo.maofa}` === `${PET_HAIR_LONG}` ? '长毛' : '短毛'}`
|
||||
}
|
||||
const sterilize = `${this.petInfo.is_jueyu}` === `${PET_IS_STERILIZE_YES}` ? '已绝育' : '未绝育';
|
||||
const birthDate = new Date(this.petInfo.birthday);
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - birthDate.getFullYear();
|
||||
const monthDiff = today.getMonth() - birthDate.getMonth();
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
const weightName = this.weightInfo?.weight_name || '';
|
||||
return `${varietyName}${hair}/${sex}${pet}/${sterilize}/${age}岁/${weightName}`;
|
||||
},
|
||||
isCompleted() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_COMPLETED
|
||||
},
|
||||
isReceived() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_RESERVED || this.orderInfo?.status === ORDER_STATUS_SEND
|
||||
},
|
||||
//已取消
|
||||
isCanceled() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_CANCELED
|
||||
},
|
||||
//服务中
|
||||
isService() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_SERVICE
|
||||
},
|
||||
isCanCancelOrder() {
|
||||
return this.isReceived
|
||||
},
|
||||
isCanAddService() {
|
||||
return (this.isReceived || this.isService) && !this.isHasNewWeight
|
||||
},
|
||||
isUnPay() {
|
||||
return this.orderInfo?.status === ORDER_STATUS_UNPAY
|
||||
},
|
||||
isNeedPayService() {
|
||||
return this.isReceived && this.isHasNewWeight && `${this.orderInfo.new_weight_status}` !== `${ORDER_STATUS_RESERVED}`
|
||||
},
|
||||
weightList() {
|
||||
if (this.allWeightList.length && this.orderInfo) {
|
||||
//使用优惠券后付款为0后 只增不减
|
||||
if (this.orderInfo.dikou_id && Number(this.orderInfo.pay_price) === 0) {
|
||||
const currentWeightSort = this.allWeightList.find((item) => item.weight_id === this.orderInfo.weight_id)?.sort || 0;
|
||||
if (this.orderInfo.type === PET_TYPE_DOG) {
|
||||
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && Number(item.sort) > Number(currentWeightSort));
|
||||
} else {
|
||||
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && item.maofa === this.petInfo.maofa && Number(item.sort) > Number(currentWeightSort));
|
||||
}
|
||||
} else {
|
||||
if (this.orderInfo.type === PET_TYPE_DOG) {
|
||||
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id);
|
||||
} else {
|
||||
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && item.maofa === this.petInfo.maofa);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
isHasNewWeight() {
|
||||
return this.orderInfo?.new_weight_name && this.orderInfo?.weight_chajia
|
||||
},
|
||||
totalMoney() {
|
||||
let changeMoney = Number(this.orderInfo.weight_chajia || 0);
|
||||
let price = Number(this.orderInfo.pay_price || 0);
|
||||
return (price + changeMoney).toFixed(2);
|
||||
},
|
||||
isHasHandle() {
|
||||
return !this.isLoading && (this.isCanCancelOrder || this.isCanAddService || this.isNeedPayService || this.isUnPay || this.isCompleted || this.isService)
|
||||
},
|
||||
payTime() {
|
||||
if (this.orderInfo.pay_time) {
|
||||
return moment.unix(this.orderInfo.pay_time).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
return ''
|
||||
},
|
||||
couponPrice() {
|
||||
return +this.orderInfo.dikou_price || 0;
|
||||
},
|
||||
servicePrice() {
|
||||
return +this.orderInfo.fuwuquan_price || 0;
|
||||
},
|
||||
carNumber() {
|
||||
return this.orderInfo?.car_info?.car_no || ''
|
||||
},
|
||||
managerPhoneNum() {
|
||||
return this.orderInfo?.guanjia_info?.mobile || ''
|
||||
},
|
||||
managerName() {
|
||||
return this.orderInfo?.guanjia_info?.name || ''
|
||||
},
|
||||
isHasWashImgs() {
|
||||
return this.isCompleted && (this.orderInfo.json_hou || this.orderInfo.json_qian);
|
||||
},
|
||||
afterImages() {
|
||||
const houUrlList = (this.orderInfo?.json_hou || '').split(',')
|
||||
const houFullUrlList = this.orderInfo?.hou_list || []
|
||||
return houUrlList.map((v, i) => ({
|
||||
url: v,
|
||||
fullUrl: houFullUrlList[i]
|
||||
}))
|
||||
},
|
||||
beforeImages() {
|
||||
const qianUrlList = (this.orderInfo?.json_qian || '').split(',')
|
||||
const qianFullUrlList = this.orderInfo?.qian_list || []
|
||||
return qianUrlList.map((v, i) => ({
|
||||
url: v,
|
||||
fullUrl: qianFullUrlList[i]
|
||||
}))
|
||||
},
|
||||
serviceStartTime() {
|
||||
if (this.orderInfo.fuwu_time) {
|
||||
return moment.unix(this.orderInfo.fuwu_time).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
return ''
|
||||
},
|
||||
serviceEndTime() {
|
||||
if (this.orderInfo.over_time) {
|
||||
return moment.unix(this.orderInfo.over_time).format('YYYY-MM-DD HH:mm');
|
||||
}
|
||||
return ''
|
||||
},
|
||||
managerPic() {
|
||||
return this.orderInfo?.guanjia_info?.guanjia_pic || ''
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
uni.$off('createRemarkSuccess', this.refreshOrderDetail);
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
eventChannel.emit('refreshList');
|
||||
},
|
||||
methods: {
|
||||
// 一键转发到宠圈
|
||||
shareToCommunity(data) {
|
||||
const { front = [], end = [] } = data;
|
||||
uni.showLoading({
|
||||
title: "处理中...",
|
||||
mask: true,
|
||||
})
|
||||
shareCommunity({
|
||||
order_id: this.orderInfo.order_id,
|
||||
old_pic: front.join(','),
|
||||
new_pic: end.join(',')
|
||||
}).then(() => {
|
||||
uni.hideLoading()
|
||||
this.isShowCompareModal = false
|
||||
this.showSuccessModal = true
|
||||
})
|
||||
},
|
||||
jumpToCommunity() {
|
||||
this.showSuccessModal = false
|
||||
uni.navigateTo({
|
||||
url: '/pages/client/index/index?activePageId=communityPage'
|
||||
})
|
||||
},
|
||||
refreshOrderDetail() {
|
||||
this.isLoading = true;
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
getWeightList().then((res) => {
|
||||
this.allWeightList = res?.info || [];
|
||||
})
|
||||
getOrderDetail(this.orderId).then((res) => {
|
||||
this.orderInfo = res?.info || {};
|
||||
this.isLoading = false;
|
||||
}).catch(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
},
|
||||
showAddService() {
|
||||
this.isShowPayModal = true;
|
||||
},
|
||||
hidePayModal() {
|
||||
this.isShowPayModal = false;
|
||||
this.otherWeight = {};
|
||||
},
|
||||
selectWeight() {
|
||||
this.isShowPayModal = false;
|
||||
this.isShowWeight = true;
|
||||
},
|
||||
closeWeightModal() {
|
||||
this.isShowWeight = false;
|
||||
this.isShowPayModal = true;
|
||||
},
|
||||
changeOtherWeight(data) {
|
||||
this.otherWeight = data;
|
||||
this.isShowWeight = false;
|
||||
this.isShowPayModal = true;
|
||||
},
|
||||
paymentConfirm(data) {
|
||||
this.isShowPayModal = false;
|
||||
uni.showLoading({
|
||||
title: '处理中',
|
||||
mask: true
|
||||
})
|
||||
addServicePay(this.orderId, this.otherWeight.weight_id)
|
||||
.then((res) => {
|
||||
const { code, msg } = res;
|
||||
if (`${code}` === '-121') {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 4000
|
||||
})
|
||||
} else {
|
||||
this.wxPayAction(data.needRefund, PRICE_DIFF_TYPE_SERVICE)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.showToast({
|
||||
title: err || '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading();
|
||||
})
|
||||
},
|
||||
clickCancel() {
|
||||
this.isShowCancelModal = true;
|
||||
},
|
||||
cancelOrder() {
|
||||
this.isShowCancelModal = false;
|
||||
uni.showLoading({
|
||||
title: '正在取消订单',
|
||||
mask: true
|
||||
});
|
||||
const data = {
|
||||
business_id:this.orderId,
|
||||
business_type:2
|
||||
}
|
||||
cancelPetOrderRefund(data).then(() => {
|
||||
uni.hideLoading();
|
||||
this.isShowPaySuccessModal = true;
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err || '取消订单失败',
|
||||
icon: 'none'
|
||||
});
|
||||
})
|
||||
},
|
||||
payOrderAction() {
|
||||
uni.showLoading({
|
||||
title: '正在支付',
|
||||
mask: true
|
||||
});
|
||||
this.wxPayAction(false);
|
||||
},
|
||||
payNewWeightOrderAction() {
|
||||
uni.showLoading({
|
||||
title: '正在支付',
|
||||
mask: true
|
||||
});
|
||||
this.wxPayAction(false, PRICE_DIFF_TYPE_SERVICE);
|
||||
},
|
||||
wxPayAction(needRefund, chaJiaType) {
|
||||
if (needRefund) {
|
||||
uni.hideLoading();
|
||||
this.isShowPaySuccessModal = true;
|
||||
return;
|
||||
}
|
||||
payOrder(this.orderId, chaJiaType).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
success: (res) => {
|
||||
console.log('success:' + JSON.stringify(res));
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.navigateBack()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('fail:' + JSON.stringify(err));
|
||||
uni.showToast({
|
||||
title: err?.msg || '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
uni.showToast({
|
||||
title: err || '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
},
|
||||
okPayAction() {
|
||||
this.isShowPaySuccessModal = false;
|
||||
uni.navigateBack()
|
||||
},
|
||||
gotoEvaluate() {
|
||||
if (this.orderInfo.pinglun_id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/remark/details?remarkId=${this.orderInfo.pinglun_id}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/order/remark?orderId=${this.orderId}&orderType=${ORDER_TYPE_PET_SERVICE}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
showGoods() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/category/index?petOrderId=${this.orderId}&addressId=${this.addressInfo.address_id}`,
|
||||
});
|
||||
},
|
||||
gotoGoodsDetail(goods_id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/shop/details?id=${goods_id}&petOrderId=${this.orderId}&petOrderAddressId=${this.addressInfo.address_id}`,
|
||||
});
|
||||
},
|
||||
selectImgsChange(imgs) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/order/remark?orderId=${this.orderId}&orderType=${ORDER_TYPE_PET_SERVICE}`,
|
||||
success: (res) => {
|
||||
res.eventChannel.emit('remarkInfo', {
|
||||
remarkImages: imgs,
|
||||
afterImages: this.afterImages,
|
||||
beforeImages: this.beforeImages,
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-detail-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #F7F8FA;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.detail-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin: 20rpx 32rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
.info-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.order-price-view {
|
||||
width: 100%;
|
||||
padding: 20rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.info-title-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.phone-text {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.address-text {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.line-view {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
background-color: #ECECEC;
|
||||
margin: 20rpx 0 18rpx;
|
||||
}
|
||||
|
||||
.head-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
.guan-jia-info-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.phone-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.call-img {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.info-cell-guanjia {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-cell-gap-20 {
|
||||
padding: 20rpx 36rpx;
|
||||
}
|
||||
|
||||
.pet-info-container {
|
||||
padding: 40rpx 36rpx;
|
||||
}
|
||||
|
||||
.pet-info-cell {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
|
||||
.pet-icon {
|
||||
width: 136rpx;
|
||||
height: 136rpx;
|
||||
flex-shrink: 0;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pet-info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.order-goods-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 30rpx;
|
||||
|
||||
.order-goods-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 76rpx;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
|
||||
.goods-scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
.good-icon {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
margin-right: 20rpx;
|
||||
background-color: #E5E7EB;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.order-goods-num-view {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.arrow-icon {
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-view {
|
||||
width: 100%;
|
||||
padding: 36rpx 16rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
.handle-btn {
|
||||
width: 152rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2rpx solid #9B939A;
|
||||
border-radius: 32rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.handle-mark-btn {
|
||||
border: 2rpx solid $app_color_main;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
939
src/page-reser/reservation/index.vue
Normal file
939
src/page-reser/reservation/index.vue
Normal file
@ -0,0 +1,939 @@
|
||||
<template>
|
||||
<view class="reservation-container">
|
||||
<nav-bar title="预约" />
|
||||
<view class="body-container">
|
||||
<scroll-view class="scroll-view" :scroll-y="true">
|
||||
<view class="form-content">
|
||||
<view class="order-tab-list">
|
||||
<view :class="orderType === ORDER_TYPE_RESERVATION ? 'activeItem' : 'tabItem'"
|
||||
@click.stop="selectOrderType(ORDER_TYPE_RESERVATION)">
|
||||
预约单
|
||||
</view>
|
||||
<view :class="orderType === ORDER_TYPE_SITE ? 'activeItem' : 'tabItem'"
|
||||
@click.stop="selectOrderType(ORDER_TYPE_SITE)">
|
||||
现场单
|
||||
</view>
|
||||
</view>
|
||||
<view class="reservation-info-container">
|
||||
<!-- 其他内容 -->
|
||||
<view class="formWrapper">
|
||||
|
||||
|
||||
<view class="info-top-view" v-if="orderType != ORDER_TYPE_RESERVATION">
|
||||
<view class="top">
|
||||
<view class="title-view">
|
||||
<text class="required">*</text>
|
||||
<text class="app-fc-main fs-24">服务码</text>
|
||||
</view>
|
||||
<view class="info-view">
|
||||
<input class="identity-input fs-24" type="number" v-model="xwID"
|
||||
placeholder="请输入或者扫一扫服务码">
|
||||
<image :src='`${imgPrefix}scan.png`' mode="aspectFit" class="scan-img"
|
||||
@click="scanCode" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-section form-section-pet">
|
||||
<view class="form-label-row">
|
||||
<text class="required">*</text>
|
||||
<text class="form-label">选择宠物</text>
|
||||
</view>
|
||||
<view class="add-pet-wrapper">
|
||||
<view v-for="(pet, index) in selectedPetsDisplay" :key="pet.id || index"
|
||||
class="selected-pet-avatar">
|
||||
<image class="pet-avatar-img"
|
||||
:src="(pet.chongwu_pic || pet.pet_avatar || pet.avatar) || `${imgPrefix}record_avator.png`"
|
||||
mode="aspectFill" />
|
||||
<view class="remove-pet-btn" @click.stop="removePet(index)">×</view>
|
||||
<text class="pet-avatar-name">{{ getPetShortName(pet) }}</text>
|
||||
</view>
|
||||
<view class="add-pet-cell" @click="goToSelectPet">
|
||||
<view class="add-pet-btn">
|
||||
<text class="plus-icon">+</text>
|
||||
</view>
|
||||
<text class="add-pet-text">添加宠物</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <info-cell key="weight" cell-type="text" title="宠物重量区间" :info="petInfo.weight_name"
|
||||
placeholder='先选择宠物' :is-can-click="false" />
|
||||
<info-cell v-if="selectedPetType === PET_TYPE_CAT" key="mofa" cell-type="text" title="宠物毛发"
|
||||
:info="petInfo.hairName" placeholder='先选择宠物' :is-can-click="false" /> -->
|
||||
<info-cell v-if="orderType === ORDER_TYPE_RESERVATION" key="time" cell-type="time"
|
||||
title="选择预约时间" :info="Object.keys(reservationTime).length > 0
|
||||
? `${reservationTime.dateLabel} ${reservationTime.start}-${reservationTime.end}`
|
||||
: ''
|
||||
" :infoTime="reservationTime.shiduan_id" @clickAction="goToSelectTime" placeholder='请选择' />
|
||||
<info-cell key="address" cell-type="address"
|
||||
title="选择服务地址" :address-info="address" @clickAction="goToSelectAddress"
|
||||
placeholder='请选择' />
|
||||
<info-cell v-if="orderType != ORDER_TYPE_SITE" key="park" cell-type="park" title="停车状况"
|
||||
:park-state="parkState" :other-park-state="otherParkState"
|
||||
@changeParkState="changeParkState" @changeOtherParkState="changeOtherParkState" />
|
||||
</view>
|
||||
|
||||
<view v-if="tip" class="tip-view">
|
||||
<image class="tip-icon" :src="imgPrefix + 'reservationTime-notice.png'" mode="aspectFit" />
|
||||
<text class="tip-text">{{ tip }}</text>
|
||||
</view>
|
||||
<view class="payFooter">
|
||||
<view class="leftPay">
|
||||
<view class="priceWrapper">
|
||||
<text class="text">预估:</text>
|
||||
<text class="unitText">
|
||||
¥<text class="price">{{ totalDisplayPrice }}</text>
|
||||
</text>
|
||||
</view>
|
||||
<!-- <view class="vipPrice" v-if="discount === 0">
|
||||
<view>
|
||||
<image :src="`${imgPrefix}vipPrice.png`" mode="widthFix"
|
||||
class="vip-price-img" />
|
||||
<text style="font-size: 24rpx;">¥{{ price && discount ? (price *
|
||||
discount).toFixed(2) : (price ? (price *
|
||||
0.8).toFixed(2) : "0.00") }}</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="payBtn" @click.stop="paymentConfirm">
|
||||
下一步
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 广告 -->
|
||||
<view class="ad-container">
|
||||
<image :src='imgPrefix + "1.png"' mode="widthFix" class="ad-image"></image>
|
||||
<image :src='imgPrefix + "2.png"' mode="widthFix" class="ad-image"></image>
|
||||
<image :src='imgPrefix + "3.png"' mode="widthFix" class="ad-image"></image>
|
||||
<image :src='imgPrefix + "4.png"' mode="widthFix" class="ad-image"></image>
|
||||
<image :src='imgPrefix + "5.png"' mode="widthFix" class="ad-image"></image>
|
||||
<image :src='imgPrefix + "6.png"' mode="widthFix" class="ad-image"></image>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import InfoCell from "@/page-reser/components/info-cell.vue";
|
||||
import {
|
||||
gitDiscountfee
|
||||
} from "../../api/login";
|
||||
import {
|
||||
ARTICLE_TYPE_RESERVATION_CAT,
|
||||
ARTICLE_TYPE_RESERVATION_DOG,
|
||||
ORDER_TYPE_RESERVATION,
|
||||
ORDER_TYPE_SITE,
|
||||
PET_HAIR_LONG,
|
||||
PET_TYPE_CAT,
|
||||
PET_TYPE_DOG,
|
||||
} from "@/constants/app.business";
|
||||
import {
|
||||
getArticleDetail
|
||||
} from "@/api/article";
|
||||
import appConfig from "@/constants/app.config";
|
||||
import {
|
||||
getCityIsOpen,
|
||||
checkWaExists
|
||||
} from "@/api/order";
|
||||
import {
|
||||
imgPrefix
|
||||
} from "@/utils/common";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
InfoCell,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgPrefix,
|
||||
xwID: '',
|
||||
PET_TYPE_CAT,
|
||||
PET_TYPE_DOG,
|
||||
ORDER_TYPE_RESERVATION,
|
||||
ORDER_TYPE_SITE,
|
||||
orderType: ORDER_TYPE_RESERVATION,
|
||||
selectedPetType: PET_TYPE_CAT, // 默认选中“猫”
|
||||
petInfo: {}, // 兼容下游,取 selectedPets[0]
|
||||
selectedPets: [], // 多选宠物列表
|
||||
parkState: "",
|
||||
otherParkState: "",
|
||||
price: "",
|
||||
discount: '',
|
||||
discount_price: [], // 接口返回的折扣价数组,展示为数组之和
|
||||
reservationTime: {},
|
||||
address: null,
|
||||
catHtmlData: "",
|
||||
dogHtmlData: "",
|
||||
tip: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initData();
|
||||
},
|
||||
computed: {
|
||||
// 展示价格:discount_price 数组相加的总和
|
||||
totalDisplayPrice() {
|
||||
const arr = Array.isArray(this.discount_price) ? this.discount_price : [];
|
||||
const sum = arr.reduce((s, p) => s + Number(p || 0), 0);
|
||||
return sum.toFixed(2);
|
||||
},
|
||||
selectedPetsDisplay() {
|
||||
return this.selectedPets && this.selectedPets.length > 0 ? this.selectedPets : [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getPetShortName(pet) {
|
||||
const n = pet.name || pet.pet_name || pet.pet_nickname || '';
|
||||
return n.length > 2 ? n.slice(0, 2) + '…' : n.slice(0, 2);
|
||||
},
|
||||
initData() {
|
||||
getArticleDetail(ARTICLE_TYPE_RESERVATION_CAT).then((res) => {
|
||||
this.catHtmlData = this.processHtmlContent(res?.info?.content || "");
|
||||
});
|
||||
},
|
||||
processHtmlContent(html) {
|
||||
return html.replace(/max-width/g, "width");
|
||||
},
|
||||
|
||||
selectOrderType(orderType) {
|
||||
if (this.orderType === orderType) {
|
||||
return;
|
||||
}
|
||||
this.reservationTime = {};
|
||||
this.parkState = "";
|
||||
this.address = null;
|
||||
this.price = "";
|
||||
this.discount_price = [];
|
||||
this.tip = "";
|
||||
this.petInfo = {};
|
||||
this.selectedPets = [];
|
||||
this.orderType = orderType;
|
||||
},
|
||||
changeParkState(state) {
|
||||
this.parkState = state;
|
||||
this.otherParkState = "";
|
||||
},
|
||||
changeOtherParkState(state) {
|
||||
this.otherParkState = state;
|
||||
},
|
||||
goToSelectPet() {
|
||||
// 跳转到选择宠物页面(多选:每次返回添加一只)
|
||||
const selectPetInfo = this.selectedPets.length > 0
|
||||
? encodeURIComponent(JSON.stringify(this.selectedPets[0]))
|
||||
: '';
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/selectPet/index?petType=${this.selectedPetType}${selectPetInfo ? `&selectPetInfo=${selectPetInfo}` : ''}`,
|
||||
events: {
|
||||
changePet: (pet) => {
|
||||
this.changePet(pet);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
changePet(item) {
|
||||
item.hairName = item.hair === 1 ? '长毛' : '短毛';
|
||||
const exists = this.selectedPets.some(p => p.id === item.id);
|
||||
if (!exists) {
|
||||
this.selectedPets.push(item);
|
||||
}
|
||||
this.petInfo = this.selectedPets[0] || {};
|
||||
this.tryFetchDiscount();
|
||||
},
|
||||
removePet(index) {
|
||||
this.selectedPets.splice(index, 1);
|
||||
this.petInfo = this.selectedPets[0] || {};
|
||||
this.tryFetchDiscount();
|
||||
},
|
||||
goToSelectAddress() {
|
||||
const selectAddress = this.address && Object.keys(this.address).length > 0
|
||||
? encodeURIComponent(JSON.stringify(this.address))
|
||||
: '';
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/selectAddress/index${selectAddress ? `?selectAddress=${selectAddress}` : ''}`,
|
||||
events: {
|
||||
changeAddress: (address) => {
|
||||
this.changeAddress(address);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
changeAddress(address) {
|
||||
address.area_name = `${address.province} ${address.city} ${address.district}`;
|
||||
this.address = address;
|
||||
this.tryFetchDiscount();
|
||||
},
|
||||
scanCode() {
|
||||
uni.scanCode({
|
||||
success: (res) => {
|
||||
this.xwID = res.result;
|
||||
uni.showToast({
|
||||
title: '扫码成功',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('扫码失败:', err);
|
||||
uni.showToast({
|
||||
title: '扫码失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
goToSelectTime() {
|
||||
const selectTime = this.reservationTime && Object.keys(this.reservationTime).length > 0 ?
|
||||
encodeURIComponent(JSON.stringify(this.reservationTime)) :
|
||||
'';
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/selectTime/index${selectTime ? `?selectTime=${selectTime}` : ''}`,
|
||||
events: {
|
||||
changeReservationTime: (timeData) => {
|
||||
this.changeReservationTime(timeData);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
changeReservationTime(timeData) {
|
||||
this.reservationTime = timeData;
|
||||
this.tryFetchDiscount();
|
||||
},
|
||||
// 仅当「选择宠物」「选择服务地址」「选择预约时间」三者都选好时调用 gitDiscountfee
|
||||
tryFetchDiscount() {
|
||||
const hasPet = this.selectedPets && this.selectedPets.length > 0;
|
||||
const petIds = hasPet ? this.selectedPets.map(p => +p.id) : [];
|
||||
const hasAddress = this.address && Object.keys(this.address).length > 0;
|
||||
const hasTime = this.reservationTime && Object.keys(this.reservationTime).length > 0;
|
||||
const regionId = this.address?.region_id;
|
||||
// 现场单:只需宠物即可拉取价格
|
||||
if (this.orderType === this.ORDER_TYPE_SITE) {
|
||||
if (hasPet && hasAddress) {
|
||||
gitDiscountfee({
|
||||
pet_id: petIds,
|
||||
region_id: regionId
|
||||
}).then((res) => {
|
||||
const discountArr = Array.isArray(res.data.discount_price) ? res.data.discount_price : [res.data.discount_price];
|
||||
const originArr = Array.isArray(res.data.price) ? res.data.price : [res.data.price];
|
||||
// 按顺序将价格赋值给对应宠物:原价 + 折后价
|
||||
discountArr.forEach((discountPrice, index) => {
|
||||
if (this.selectedPets[index]) {
|
||||
const originPrice = originArr[index] != null ? originArr[index] : discountPrice;
|
||||
this.selectedPets[index].basePrice = Number(originPrice || 0); // 原价
|
||||
this.selectedPets[index].discountBasePrice = Number(discountPrice || 0); // 折后价
|
||||
}
|
||||
});
|
||||
// 计算总价:用折后价数组
|
||||
this.price = discountArr.reduce((sum, p) => sum + Number(p || 0), 0);
|
||||
this.discount = res.data.discount;
|
||||
this.discount_price = Array.isArray(res.data.discount_price) ? res.data.discount_price : (res.data.discount_price != null ? [res.data.discount_price] : []);
|
||||
this.tip = res.data.tip;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 预约单:三个都选了才调用
|
||||
if (hasPet && hasAddress && hasTime) {
|
||||
gitDiscountfee({
|
||||
region_id: regionId,
|
||||
pet_id: petIds,
|
||||
order_date: this.reservationTime.date
|
||||
}).then((res) => {
|
||||
const discountArr = Array.isArray(res.data.discount_price) ? res.data.discount_price : [res.data.discount_price];
|
||||
const originArr = Array.isArray(res.data.price) ? res.data.price : [res.data.price];
|
||||
// 按顺序将价格赋值给对应宠物:原价 + 折后价
|
||||
discountArr.forEach((discountPrice, index) => {
|
||||
if (this.selectedPets[index]) {
|
||||
const originPrice = originArr[index] != null ? originArr[index] : discountPrice;
|
||||
this.selectedPets[index].basePrice = Number(originPrice || 0); // 原价
|
||||
this.selectedPets[index].discountBasePrice = Number(discountPrice || 0); // 折后价
|
||||
}
|
||||
});
|
||||
// 计算总价:用折后价数组
|
||||
this.price = discountArr.reduce((sum, p) => sum + Number(p || 0), 0);
|
||||
this.discount = res.data.discount;
|
||||
this.discount_price = Array.isArray(res.data.discount_price) ? res.data.discount_price : (res.data.discount_price != null ? [res.data.discount_price] : []);
|
||||
this.tip = res.data.tip;
|
||||
});
|
||||
}
|
||||
},
|
||||
paymentConfirm() {
|
||||
if (!this.selectedPets.length) {
|
||||
uni.showToast({
|
||||
title: "请选择宠物",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
Object.keys(this.reservationTime).length === 0 &&
|
||||
this.orderType === ORDER_TYPE_RESERVATION
|
||||
) {
|
||||
uni.showToast({
|
||||
title: "请选择预约时间",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 预约单、现场单均需校验服务地址
|
||||
if (!this.address) {
|
||||
uni.showToast({
|
||||
title: "请选择服务地址",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 仅预约单校验停车状况
|
||||
if (this.orderType !== ORDER_TYPE_SITE) {
|
||||
if (!this.parkState) {
|
||||
uni.showToast({
|
||||
title: "请选择停车状况",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.parkState === "其他" && !this.otherParkState) {
|
||||
uni.showToast({
|
||||
title: "请输入停车信息",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 如果是现场单,需要校验服务码
|
||||
if (this.orderType === ORDER_TYPE_SITE) {
|
||||
if (!this.xwID || !this.xwID.trim()) {
|
||||
uni.showToast({
|
||||
title: "请输入服务码",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 校验服务码是否为数字
|
||||
const waCode = Number(this.xwID.trim());
|
||||
if (isNaN(waCode) || waCode <= 0) {
|
||||
uni.showToast({
|
||||
title: "服务码格式不正确",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 调用接口校验服务码
|
||||
uni.showLoading({
|
||||
title: "校验服务码中...",
|
||||
mask: true,
|
||||
});
|
||||
checkWaExists({ wa_code: waCode })
|
||||
.then((res) => {
|
||||
if (!res.data.exists) {
|
||||
uni.showToast({
|
||||
title: "服务码错误",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.navigateToAdditional();
|
||||
}
|
||||
uni.hideLoading();
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err.msg || "服务码校验失败,请检查服务码是否正确",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 非现场单,直接跳转
|
||||
this.navigateToAdditional();
|
||||
},
|
||||
navigateToAdditional() {
|
||||
uni.navigateTo({
|
||||
url: "/pageHome/order/additional",
|
||||
// url: '/pageHome/reservation/payment-confirm-page',
|
||||
success: (res) => {
|
||||
res.eventChannel.emit("reservationInfo", {
|
||||
petInfo: this.petInfo,
|
||||
selectedPets: this.selectedPets,
|
||||
orderInfo: {
|
||||
parkState: this.otherParkState || this.parkState,
|
||||
orderType: this.orderType,
|
||||
xwID: this.xwID
|
||||
},
|
||||
addresInfo: this.address,
|
||||
reservationTime: this.reservationTime,
|
||||
price: this.totalDisplayPrice, // 全部价格
|
||||
discount: this.discount,
|
||||
discount_price: this.discount_price,
|
||||
});
|
||||
},
|
||||
events: {
|
||||
clearData: () => {
|
||||
this.selectedPetType = PET_TYPE_CAT;
|
||||
this.petInfo = {};
|
||||
this.selectedPets = [];
|
||||
this.parkState = "";
|
||||
this.price = "";
|
||||
this.reservationTime = {};
|
||||
this.address = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
// paymentConfirm() {
|
||||
// if (!this.petInfo.chongwu_id) {
|
||||
// uni.showToast({
|
||||
// title: "请选择宠物",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// if (
|
||||
// Object.keys(this.reservationTime).length === 0 &&
|
||||
// this.orderType === ORDER_TYPE_RESERVATION
|
||||
// ) {
|
||||
// uni.showToast({
|
||||
// title: "请选择预约时间",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// if (!this.address) {
|
||||
// uni.showToast({
|
||||
// title: "请选择服务地址",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// if (!this.parkState) {
|
||||
// uni.showToast({
|
||||
// title: "请选择停车状况",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// if (this.parkState === "其他" && !this.otherParkState) {
|
||||
// uni.showToast({
|
||||
// title: "请输入停车信息",
|
||||
// icon: "none",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// const data = {
|
||||
// user_id:this.petInfo.member_id,
|
||||
// pet_id:this.petInfo.chongwu_id,
|
||||
// };
|
||||
// isPet(data).then((res) => {
|
||||
// if(res.data){
|
||||
// uni.navigateTo({
|
||||
// url: "/pageHome/order/additional",
|
||||
// success: (res) => {
|
||||
// res.eventChannel.emit("reservationInfo", {
|
||||
// petInfo: this.petInfo,
|
||||
// parkState: this.otherParkState || this.parkState,
|
||||
// petWeight: this.petWeight,
|
||||
// reservationTime: this.reservationTime,
|
||||
// address: this.address,
|
||||
// price: this.price,
|
||||
// discount:this.discount,
|
||||
// estimatePrice: this.totalDisplayPrice,
|
||||
// orderType: this.orderType,
|
||||
// chongwu_id: this.petInfo.chongwu_id,
|
||||
// });
|
||||
// },
|
||||
// events: {
|
||||
// clearData: () => {
|
||||
// this.selectedPetType = PET_TYPE_CAT;
|
||||
// this.petInfo = {};
|
||||
// this.parkState = "";
|
||||
// this.petWeight = {};
|
||||
// this.price = "";
|
||||
// this.reservationTime = {};
|
||||
// this.address = null;
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }else{
|
||||
// uni.showModal({
|
||||
// content: '该宠物必须与会员卡绑定才能享受会员折扣',
|
||||
// showCancel: true,
|
||||
// cancelText: '取消',
|
||||
// confirmText: '去绑定',
|
||||
// success: res => {
|
||||
// if (res.confirm) {
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/client/recharge/my-card?user_id=${this.petInfo.member_id}`,
|
||||
// })
|
||||
// } else {
|
||||
// console.log('用户取消操作');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
},
|
||||
onShareAppMessage(res) {
|
||||
return {
|
||||
title: appConfig.appShareName,
|
||||
path: "/pages/client/index/index",
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reservation-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #ffecf3;
|
||||
|
||||
.body-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
.scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.form-content {
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.order-tab-list {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.tabItem {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
background-color: #ffd8e6;
|
||||
border-radius: 16rpx 16rpx 0px 0px;
|
||||
height: 92rpx;
|
||||
line-height: 92rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.activeItem {
|
||||
background-color: #fff;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
height: 92rpx;
|
||||
line-height: 92rpx;
|
||||
border-radius: 16rpx 16rpx 0px 0px;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.reservation-info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.formWrapper {
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
border-radius: 0px 0px 16rpx 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.info-top-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
box-sizing: border-box;
|
||||
height: 104rpx;
|
||||
padding: 0 20rpx;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
height: 2rpx;
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.top {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
// flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
.title-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.identity-input {
|
||||
text-align: right;
|
||||
// direction: rtl;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-icon {
|
||||
margin-left: 16rpx;
|
||||
width: 10rpx;
|
||||
height: 5rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-section-pet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 40rpx);
|
||||
margin: 0 auto;
|
||||
padding: 36rpx 0;
|
||||
border-bottom: 2rpx solid #ececec;
|
||||
}
|
||||
|
||||
.form-label-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.add-pet-wrapper {
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.selected-pet-avatar {
|
||||
position: relative;
|
||||
width: 64rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pet-avatar-img {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.pet-avatar-name {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-top: 8rpx;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 64rpx;
|
||||
}
|
||||
|
||||
.remove-pet-btn {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #FF19A0;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
border: 2rpx solid #fff;
|
||||
}
|
||||
|
||||
.add-pet-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-pet-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border: 1rpx dashed #3D3D3D;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #EBEBEB;
|
||||
}
|
||||
|
||||
.plus-icon {
|
||||
font-size: 60rpx;
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.add-pet-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// .content-section :last-child {
|
||||
// border-bottom:none;
|
||||
// /* CSS 规则 */
|
||||
// }
|
||||
|
||||
.tip-view {
|
||||
background-color: #fff;
|
||||
padding: 12rpx 20rpx;
|
||||
margin-top: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
color: #FF19A0;
|
||||
font-size: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.payFooter {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 16rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
|
||||
.leftPay {
|
||||
.priceWrapper {
|
||||
.text {
|
||||
font-size: 24rpx;
|
||||
color: #272427;
|
||||
}
|
||||
|
||||
.unitText {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #FF19A0;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #FF19A0;
|
||||
font-size: 48rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.vipPrice {
|
||||
margin-top: 14rpx;
|
||||
font-size: 20rpx;
|
||||
color: #9B939A;
|
||||
|
||||
view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vip-price-img {
|
||||
width: 43px;
|
||||
height: 14px;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.payBtn {
|
||||
background-color: #FF19A0;
|
||||
color: #fff;
|
||||
padding: 30rpx 80rpx;
|
||||
border-radius: 100px;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ad-container {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 16rpx;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.ad-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ad-image:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ad-view {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #FF19A0;
|
||||
}
|
||||
|
||||
.scan-img {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
447
src/page-reser/reservation/payment-confirm-page.vue
Normal file
447
src/page-reser/reservation/payment-confirm-page.vue
Normal file
@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<view class="payment-confirm-container" v-if="!initializing">
|
||||
<view class="payment-body-container">
|
||||
<view class="info-cell" v-if="reservationInfo.address">
|
||||
<view class="info-title-view">
|
||||
<image src="@/static/images/address.png" mode="aspectFit" class="info-icon"/>
|
||||
<text class="app-fc-main fs-28">{{ reservationInfo.address.name }}</text>
|
||||
<text class="app-fc-main fs-28 phone-text">{{ reservationInfo.address.phone }}</text>
|
||||
</view>
|
||||
<view class="address-view">
|
||||
<view class="info-icon"/>
|
||||
<text class="app-fc-normal fs-24 address-text">
|
||||
{{ `${reservationInfo.address.area_name} ${reservationInfo.address.address}` }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<view class="info-title-view">
|
||||
<image src="/pageHome/static/time.png" mode="aspectFit" class="info-icon"/>
|
||||
<text class="app-fc-main fs-28">{{ reservationTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-cell info-pet-cell">
|
||||
<detail-cell title="宠物类型" :content="petType"/>
|
||||
<detail-cell v-if="petHair" title="毛型分类" :content="petHair"/>
|
||||
<detail-cell title="宠物姓名" :content="petName"/>
|
||||
<detail-cell title="宠物重量区间" :content="petWeight.weight_name"/>
|
||||
<detail-cell v-if="Object.keys(selectCoupon).length === 0" title="优惠券"
|
||||
:content="couponList.length === 0 ? '暂无可用' : '未选优惠券'" :is-show-arrow="true"
|
||||
:custom-content-style="{'color': '#9B939A'}" @clickAction="isShowCouponModal=true"/>
|
||||
<detail-cell v-else title="优惠券" :content="showCoupon" :is-show-arrow="true"
|
||||
:custom-content-style="{'color': '#FF19A0'}" @clickAction="isShowCouponModal=true"/>
|
||||
<detail-cell v-if="Object.keys(selectService).length === 0" title="服务券"
|
||||
:content="serviceList.length === 0 ? '暂无可用' : '未选服务券'"
|
||||
:is-show-arrow="true"
|
||||
:custom-content-style="{'color': '#9B939A'}" @clickAction="isShowServiceModal=true"/>
|
||||
<detail-cell v-else title="服务券" :content="showService" :is-show-arrow="true"
|
||||
:custom-content-style="{'color': '#FF19A0'}" @clickAction="isShowServiceModal=true"/>
|
||||
<view class="line-view"></view>
|
||||
<detail-cell title="费用预估" :content="`¥${showPrice || '0.00'}`" :title-mark="true"/>
|
||||
</view>
|
||||
<view class="info-cell info-pet-cell">
|
||||
<detail-cell title="停车状况" :content="reservationInfo.parkState" :content-mark="false" :title-mark="true"/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="handle-view">
|
||||
<view class="handle-info">
|
||||
<view class="handle-info-cell">
|
||||
<text class="app-fc-main fs-30">¥</text>
|
||||
<text class="app-fc-main fs-40 app-font-bold">{{ showPrice || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="handle-info-cell tip-cell">
|
||||
<image src="/static/images/notice.png" mode="aspectFit" class="tips-icon"/>
|
||||
<text class="app-fc-normal fs-24">以实际服务宠物的重量为准</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="handle-btn" @click.stop="createPetOrder">
|
||||
<text class="app-fc-white fs-30">确认支付</text>
|
||||
</view>
|
||||
</view>
|
||||
<RechargeCouponModal
|
||||
v-if="isShowCouponModal"
|
||||
:couponList="couponList"
|
||||
:price="reservationInfo.price"
|
||||
:selected-item="selectCoupon"
|
||||
@useCoupon="useCoupon"
|
||||
@close="isShowCouponModal = false"
|
||||
/>
|
||||
<ServiceCouponModal
|
||||
v-if="isShowServiceModal"
|
||||
:service-list="serviceList"
|
||||
@useService="useService"
|
||||
@close="isShowServiceModal = false"
|
||||
:weight-id="petWeight.weight_id"
|
||||
:price="reservationInfo.price"
|
||||
:selected-item="selectService"
|
||||
/>
|
||||
<success-modal
|
||||
v-if="showSuccessModal"
|
||||
:img-src="'https://activity.wagoo.live/order_success.png'"
|
||||
:img-style="{width: '200rpx', height: '200rpx'}"
|
||||
title="预约成功"
|
||||
message="预约状态可在订单中进行查看"
|
||||
@ok="paySuccessAction"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DetailCell from "@/components/petOrder/detail-cell.vue";
|
||||
import {
|
||||
COUPON_TYPE_PET,
|
||||
ORDER_TYPE_SITE,
|
||||
PET_HAIR_LONG,
|
||||
PET_TYPE_CAT
|
||||
} from "@/constants/app.business";
|
||||
import { createOrder, payOrder } from "@/api/order";
|
||||
import {
|
||||
getCouponListByOrderPrice,
|
||||
getServiceCouponListByWeightId
|
||||
} from "@/api/coupon";
|
||||
import RechargeCouponModal from "@/components/coupon/RechargeCouponModal.vue";
|
||||
import ServiceCouponModal from "@/components/coupon/ServiceCouponModal.vue";
|
||||
import SuccessModal from "@/components/SuccessModal.vue";
|
||||
import moment from "moment";
|
||||
import { ORDER_STATUS_RESERVED } from "@/pageHome/constants/home";
|
||||
|
||||
export default {
|
||||
components: { SuccessModal, ServiceCouponModal, RechargeCouponModal, DetailCell },
|
||||
onLoad(option) {
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
eventChannel.on('reservationInfo', (data) => {
|
||||
this.initializing = false;
|
||||
this.reservationInfo = data;
|
||||
Promise.all([this.getMyServiceList(), this.getMyCouponList()]).then((result) => {
|
||||
if (this.serviceList.length) {
|
||||
let selectService = this.serviceList[0];
|
||||
this.serviceList.map((data) => {
|
||||
if (Number(data.price) > Number(selectService.price)) {
|
||||
selectService = data;
|
||||
}
|
||||
});
|
||||
this.selectService = selectService;
|
||||
} else if (this.couponList.length) {
|
||||
let selectCoupon = this.couponList[0]
|
||||
this.couponList.map((data) => {
|
||||
if (Number(data.card_money) > Number(selectCoupon.card_money)) {
|
||||
selectCoupon = data;
|
||||
}
|
||||
});
|
||||
this.selectCoupon = selectCoupon;
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
petType() {
|
||||
return `${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}` ? '喵喵' : '汪汪'
|
||||
},
|
||||
petName() {
|
||||
return this.reservationInfo?.petInfo?.name || '';
|
||||
},
|
||||
petWeight() {
|
||||
return this.reservationInfo?.petWeight || {};
|
||||
},
|
||||
petHair() {
|
||||
if (`${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}`) {
|
||||
return `${this.reservationInfo?.petInfo.maofa}` === `${PET_HAIR_LONG}` ? '长毛' : '短毛';
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
reservationTime() {
|
||||
if (this.reservationInfo.orderType === ORDER_TYPE_SITE) {
|
||||
return '今天 ' + moment().format('M月D日');
|
||||
}
|
||||
return `${this.reservationInfo?.reservationTime?.dateLabel} ${this.reservationInfo?.reservationTime?.start}-${this.reservationInfo?.reservationTime?.end}`
|
||||
},
|
||||
showService() {
|
||||
return this.selectService.name || '';
|
||||
},
|
||||
showCoupon() {
|
||||
return '-¥' + (+this.selectCoupon.card_money || 0);
|
||||
},
|
||||
showPrice() {
|
||||
if (Object.keys(this.selectService).length) {
|
||||
return '0.00'
|
||||
} else {
|
||||
let couponMoney = this.selectCoupon?.card_money || 0;
|
||||
return (Number(this.reservationInfo.price) - couponMoney).toFixed(2);
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reservationInfo: {},
|
||||
initializing: true,
|
||||
serviceList: [],
|
||||
selectService: {},
|
||||
isShowServiceModal: false,
|
||||
couponList: [],
|
||||
selectCoupon: {},
|
||||
isShowCouponModal: false,
|
||||
showSuccessModal: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getMyCouponList() {
|
||||
return getCouponListByOrderPrice(this.reservationInfo.price,COUPON_TYPE_PET).then((res)=>{
|
||||
this.couponList = res?.info || [];
|
||||
return Promise.resolve();
|
||||
}).catch(()=>{
|
||||
this.couponList = [];
|
||||
this.selectCoupon = {};
|
||||
return Promise.resolve();
|
||||
})
|
||||
},
|
||||
getMyServiceList() {
|
||||
return getServiceCouponListByWeightId(this.reservationInfo?.petWeight?.weight_id).then((res)=>{
|
||||
this.serviceList = res?.info || [];
|
||||
return Promise.resolve();
|
||||
}).catch(()=>{
|
||||
this.serviceList = []
|
||||
this.selectService = {};
|
||||
return Promise.resolve();
|
||||
})
|
||||
},
|
||||
useCoupon(item) {
|
||||
if (this.selectCoupon.fafang_id === item.fafang_id) {
|
||||
this.selectCoupon = {};
|
||||
} else {
|
||||
this.selectCoupon = item;
|
||||
this.isShowCouponModal = false;
|
||||
}
|
||||
},
|
||||
useService(item) {
|
||||
if (this.selectService.order_id === item.order_id) {
|
||||
this.selectService = {};
|
||||
} else {
|
||||
this.selectService = item;
|
||||
this.isShowServiceModal = false;
|
||||
}
|
||||
},
|
||||
createPetOrder() {
|
||||
if (Object.keys(this.selectService).length && Object.keys(this.selectCoupon).length) {
|
||||
uni.showToast({
|
||||
title: '服务券和优惠券不能同时使用',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
uni.showLoading({
|
||||
title: '正在创建订单',
|
||||
mask: true
|
||||
});
|
||||
const data = {
|
||||
chongwu_id: this.reservationInfo?.petInfo?.chongwu_id,
|
||||
type: this.reservationInfo?.petInfo?.type,
|
||||
weight_id: this.reservationInfo?.petWeight?.weight_id,
|
||||
address_id: this.reservationInfo?.address?.address_id,
|
||||
shiduan_id: this.reservationInfo.orderType === ORDER_TYPE_SITE ? 0 : this.reservationInfo?.reservationTime?.shiduan_id,
|
||||
yuyue_date: this.reservationInfo.orderType === ORDER_TYPE_SITE ? moment().format('YYYY-MM-DD') : this.reservationInfo?.reservationTime?.date,
|
||||
tingche_desc: this.reservationInfo.parkState,
|
||||
desc: '',
|
||||
order_type: this.reservationInfo.orderType,
|
||||
}
|
||||
if (this.selectCoupon.fafang_id) {
|
||||
data.dikou_id = this.selectCoupon.fafang_id;
|
||||
}
|
||||
if (this.selectService.order_id) {
|
||||
data.fuwuquan_id = this.selectService.order_id;
|
||||
}
|
||||
if (`${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}`) {
|
||||
data.maofa = this.reservationInfo?.petInfo.maofa;
|
||||
}
|
||||
createOrder(data).then((res) => {
|
||||
if (Object.keys(this.selectService).length || `${this.showPrice}` === '0.00') {
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
eventChannel.emit('clearData')
|
||||
uni.hideLoading();
|
||||
this.showSuccessModal = true;
|
||||
} else {
|
||||
this.weixinPay(res.info)
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.showToast({
|
||||
title: '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
weixinPay(orderId) {
|
||||
payOrder(orderId).then((res) => {
|
||||
const payData = res?.info?.pay_data || {};
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payData.timeStamp,
|
||||
nonceStr: payData.nonceStr,
|
||||
package: payData.package,
|
||||
signType: payData.signType,
|
||||
paySign: payData.sign,
|
||||
success: (res) => {
|
||||
uni.hideLoading();
|
||||
const eventChannel = this.getOpenerEventChannel();
|
||||
eventChannel.emit('clearData')
|
||||
this.showSuccessModal = true;
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.showToast({
|
||||
title: '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
uni.showToast({
|
||||
title: '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading();
|
||||
})
|
||||
},
|
||||
paySuccessAction() {
|
||||
uni.redirectTo({
|
||||
url:'/pages/client/petOrder/index',
|
||||
complete() {
|
||||
uni.$emit('changeTabBar', {
|
||||
pageId: 'orderPage',
|
||||
orderState: ORDER_STATUS_RESERVED
|
||||
})
|
||||
}
|
||||
});
|
||||
// uni.navigateBack({
|
||||
// delta: 1,
|
||||
// complete() {
|
||||
// uni.$emit('changeTabBar', {
|
||||
// pageId: 'orderPage',
|
||||
// orderState: ORDER_STATUS_RESERVED
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.payment-confirm-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #FBF8FC;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.payment-body-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 20rpx 32rpx;
|
||||
|
||||
.info-cell {
|
||||
width: 100%;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.info-title-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.phone-text {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.address-text {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.line-view {
|
||||
width: 100%;
|
||||
height: 2rpx;
|
||||
background-color: #ECECEC;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-pet-cell {
|
||||
padding: 20rpx 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.handle-view {
|
||||
width: 100%;
|
||||
padding: 20rpx 36rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.handle-info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.handle-info-cell {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
width: 100%;
|
||||
|
||||
.tips-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tip-cell {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.handle-btn {
|
||||
width: 260rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
299
src/pageHome/components/info-cell.vue
Normal file
299
src/pageHome/components/info-cell.vue
Normal file
@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<view class="order-info-cell" @click.stop="clickAction">
|
||||
<template v-if="cellType === 'text'">
|
||||
<view class="info-top-view">
|
||||
<image :src="infoIcon" mode="aspectFit" class="cell-icon"/>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
<view class="info-view" v-if="info">
|
||||
<text class="app-fc-main fs-28">{{ info }}</text>
|
||||
</view>
|
||||
<image v-if="isCanClick" class="right-icon" src="@/static/images/right_arrow2.png" mode="widthFix"></image>
|
||||
<view v-else class="right-icon"/>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 时间信息-->
|
||||
<template v-if="cellType === 'time'">
|
||||
<view class="info-top-view">
|
||||
<image :src="infoIcon" mode="aspectFit" class="cell-icon"/>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
<image class="right-icon" src="@/static/images/right_arrow2.png" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="info-bottom-view" v-if="info">
|
||||
<text class="app-fc-main fs-28">{{ info }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 地址信息-->
|
||||
<template v-if="cellType === 'address'">
|
||||
<view class="info-top-view">
|
||||
<image :src="infoIcon" mode="aspectFit" class="cell-icon"/>
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
<image class="right-icon" src="@/static/images/right_arrow2.png" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="info-bottom-view bottom-address-container" v-if="addressInfo">
|
||||
<view class="user-info-view">
|
||||
<text class="app-fc-main fs-28 app-font-bold">{{ addressInfo.name }}</text>
|
||||
<text class="app-fc-main fs-28 phone-text app-font-bold">{{ addressInfo.phone }}</text>
|
||||
</view>
|
||||
<view class="address-view">
|
||||
<text class="app-fc-normal fs-24 address-text">{{ `${addressInfo.area_name} ${addressInfo.address}` }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 价格-->
|
||||
<template v-if="cellType === 'price'">
|
||||
<view class="info-top-view">
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
<text v-if="price" class="app-fc-mark fs-32 app-font-bold-700">{{ `¥${price}` }}</text>
|
||||
<text v-else class="app-fc-main fs-28">---</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-if="cellType === 'park'">
|
||||
<view class="info-top-view">
|
||||
<view class="title-view">
|
||||
<text class="app-fc-main fs-28">{{ title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-view">
|
||||
<view class="list-card" v-for="item in park_conditions" :key="item"
|
||||
:class="{'selected-list-card': parkState === item}" @click.stop="changeParkState(item)">
|
||||
<text class="fs-28 app-fc-normal" :class="{'app-fc-mark': parkState === item}">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="input-container" v-if="parkState === '其他'">
|
||||
<textarea
|
||||
:focus="true"
|
||||
:value="otherParkState"
|
||||
class="input-view fs-28 app-fc-main"
|
||||
placeholder="点击输入停车信息"
|
||||
placeholder-class="placeholder-class"
|
||||
@input="onChange"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* info-cell 预约信息显示
|
||||
* @property {String} cellType - 显示类型 (默认text) text/address/time/price/park
|
||||
* @value text 文本类型
|
||||
* @value address 地址类型
|
||||
* @value time 时间类型
|
||||
* @value price 价格类型
|
||||
* @value park 停车状况
|
||||
* @property {String} infoIcon - 左侧图标
|
||||
* @property {String} title - 标题
|
||||
* @property {String} info - 显示内容
|
||||
* @property {Object} addressInfo - 地址信息
|
||||
* @property {String} price - 价格
|
||||
* @property {String} parkState - 停车状况
|
||||
*
|
||||
* @event {Function} clickAction 点击cell触发事件
|
||||
* @event {Function} changeParkState 停车状况改变触发事件
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
isCanClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cellType: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
},
|
||||
infoIcon: {
|
||||
type: String | null,
|
||||
default: require('@/static/images/arrow_right.png'),
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
info: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
addressInfo: {
|
||||
type: Object | null,
|
||||
default: () => {
|
||||
return null
|
||||
}
|
||||
},
|
||||
price: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
parkState: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
otherParkState: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
park_conditions: ['小区', '地库', '路边', '其他'],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clickAction() {
|
||||
this.isCanClick && this.$emit('clickAction')
|
||||
},
|
||||
changeParkState(state) {
|
||||
this.$emit('changeParkState', state)
|
||||
},
|
||||
onChange(e) {
|
||||
this.$emit('changeOtherParkState', e.detail.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-info-cell {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 36rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ECECEC;
|
||||
|
||||
.info-top-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
.cell-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.title-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.right-icon {
|
||||
margin-left: 16rpx;
|
||||
width: 10rpx;
|
||||
height: 5rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.info-bottom-view {
|
||||
width: calc(100% - 56rpx);
|
||||
margin-top: 32rpx;
|
||||
margin-left: 56rpx;
|
||||
padding-right: 104rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
.user-info-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.phone-text {
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.address-text {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-address-container {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
margin-top: 32rpx;
|
||||
width: 100%;
|
||||
padding-right: 30rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.list-card {
|
||||
padding: 18rpx 32rpx 18rpx 30rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.selected-list-card {
|
||||
border: 2rpx solid $app_color_main;
|
||||
background-color: #FEE9F3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.input-container {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
padding: 0 30rpx 0 0;
|
||||
box-sizing: border-box;
|
||||
margin-top: 32rpx;
|
||||
|
||||
.input-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 32rpx;
|
||||
border-radius: 30rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
background-color: #F9F7F9;
|
||||
}
|
||||
|
||||
.placeholder-class {
|
||||
color: #666262;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
184
src/pageHome/components/order-goods-modal.vue
Normal file
184
src/pageHome/components/order-goods-modal.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="随车购商品" class="order-goods-modal">
|
||||
<view class="goods-container">
|
||||
<scroll-view class="goods-scroll-view" :scroll-y="true">
|
||||
<view class="goods-item" v-for="item in goodsList" :key="item.order_id" @click.stop="gotoDetail(item)">
|
||||
<image mode="aspectFit" class="goods-img" :src="item.goods_pic"/>
|
||||
<view class="goods-info-view">
|
||||
<text class="app-fc-main fs-32 app-font-bold-500 app-text-ellipse">
|
||||
{{ item.goods_name || '--' }}
|
||||
</text>
|
||||
<text class="app-fc-normal fs-24 goods-num-text">{{ `数量X${item.number}` }}</text>
|
||||
<text class="app-fc-main fs-28 goods-price-text" >实付款
|
||||
<text class="fs-28 app-fc-alarm">{{ `¥${item.goods_price}` }}</text>
|
||||
</text>
|
||||
</view>
|
||||
<image src="@/static/images/arrow_right_black.png" mode="aspectFit" class="good-arrow"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="goods-price-container">
|
||||
<view class="goods-left-view">
|
||||
<view class="price-view">
|
||||
<text class="app-fc-main fs-28">¥</text>
|
||||
<text class="app-fc-main app-font-bold-700 fs-50 price-text">{{ price }}</text>
|
||||
</view>
|
||||
<view class="price-tips-view">
|
||||
<image src="/pageHome/static/tips.png" mode="aspectFit" class="tips-img"/>
|
||||
<text class="fs-24">商品总价</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="closeAction">
|
||||
<text class="app-fc-white fs-30">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
goodsList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
options: {
|
||||
styleIsolation: "shared",
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
gotoDetail(item){
|
||||
this.$emit('gotoDetail', item.goods_id);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-goods-modal {
|
||||
|
||||
::v-deep {
|
||||
.selected-modal .model-container {
|
||||
background: #fff0f5;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-container {
|
||||
width: 100%;
|
||||
height: 800rpx;
|
||||
position: relative;
|
||||
|
||||
.goods-scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.goods-item {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
margin-top: 32rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.goods-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.goods-info-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 160rpx - 24rpx - 40rpx);
|
||||
.goods-num-text {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.goods-price-text {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.good-arrow {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-price-container {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 26rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.goods-left-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.price-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
|
||||
.price-text {
|
||||
margin-left: 8rpx;
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.price-tips-view {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.tips-img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 260rpx;
|
||||
height: 92rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #FE019B;
|
||||
border-radius: 46rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
49
src/pageHome/components/price-description-modal.vue
Normal file
49
src/pageHome/components/price-description-modal.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="价格说明">
|
||||
<view class="price-description-container">
|
||||
<view class="price-info">
|
||||
<text class="app-fc-main fs-32">{{priceInfo.desc}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
priceInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.price-description-container {
|
||||
width: 100%;
|
||||
padding: 0 40rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.price-info {
|
||||
width: 100%;
|
||||
padding: 36rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #F7F3F7;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
230
src/pageHome/components/select-address-modal.vue
Normal file
230
src/pageHome/components/select-address-modal.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择服务地址">
|
||||
<view class="select-address-container">
|
||||
<view v-if="!isLoading && addressList.length" class="address-container">
|
||||
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
|
||||
refresher-background="transparent">
|
||||
<view class="address-item" v-for="(item, index) in addressList" :key="item.id"
|
||||
@click.stop="changeAddress(item)">
|
||||
<view class="address-info-view">
|
||||
<view class="address-name-view">
|
||||
<text v-if="item.is_default" class="default-address fs-18">默认</text>
|
||||
<text class="app-fc-main fs-30 app-font-bold name-text">{{ item.recipient_name }}</text>
|
||||
<text class="app-fc-main fs-30 app-font-bold">{{ item.phone }}</text>
|
||||
</view>
|
||||
<text class="app-fc-normal fs-26">{{ getAddressText(item) }}</text>
|
||||
</view>
|
||||
<image @click.stop="goToEditAddress(item)" src="@/static/images/address_edit.png" mode="aspectFit" class="address-edit-icon"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="address-container flex-center" v-if="isLoading">
|
||||
<uni-load-more status="loading" :show-text="false"/>
|
||||
</view>
|
||||
<view v-if="addressList.length === 0 && !isLoading" class="address-container">
|
||||
<image src="https://activity.wagoo.live/no_address.png" class="no-address-img" mode="widthFix"/>
|
||||
<!-- <text class="app-fc-normal fs-32 no-text">暂无地址信息</text> -->
|
||||
</view>
|
||||
<view class="add-btn" @click.stop="gotoAddAddress">
|
||||
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
|
||||
<text class="app-fc-white fs-30">添加地址</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import { getAddressList } from "@/api/address";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
selectAddress: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addressList: [],
|
||||
isLoading: true,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
isLoadMore: false,
|
||||
isNoMore: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
userInfo() {
|
||||
return this.$store.state?.user?.userInfo || {};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changeAddress(address) {
|
||||
this.$emit('changeAddress', address);
|
||||
},
|
||||
loadMoreAction() {
|
||||
if (this.isNoMore || this.isLoadMore) {
|
||||
return;
|
||||
}
|
||||
this.pageNumber++;
|
||||
this.isLoadMore = true;
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
getAddressList({ user_id: this.userInfo.userID }).then((res) => {
|
||||
let list = res?.data || [];
|
||||
if (this.pageNumber === 1) {
|
||||
this.addressList = list;
|
||||
} else {
|
||||
this.addressList = [...this.addressList, ...list];
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
this.isNoMore = list.length < this.pageSize;
|
||||
}).catch(() => {
|
||||
if (this.pageNumber !== 1) {
|
||||
this.pageNumber--;
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
})
|
||||
},
|
||||
getAddressText(item) {
|
||||
// 组合地址信息:省市区 + 详细地址
|
||||
const region = [item.province, item.city, item.district].filter(Boolean).join('');
|
||||
return region ? `${region}${item.full_address}` : item.full_address;
|
||||
},
|
||||
gotoAddAddress() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?isAdd=1`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
goToEditAddress(item){
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?id=${item?.id || ""}`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-address-container {
|
||||
width: 100%;
|
||||
padding: 0 56rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.address-container {
|
||||
width: 100%;
|
||||
height: 444rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 18rpx;
|
||||
|
||||
.no-address-img {
|
||||
margin-top: 30rpx;
|
||||
width: 348rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.no-text {
|
||||
margin: 0 0 24rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx 0rpx;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 2rpx solid #F7F3F7;
|
||||
|
||||
.address-info-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-right: 30rpx;
|
||||
|
||||
.address-name-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.default-address {
|
||||
padding: 2rpx 6rpx;
|
||||
color: #40ae36;
|
||||
background: rgba(64, 174, 54, 0.2);
|
||||
border-radius: 6rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.name-text {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.address-edit-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
|
||||
.add-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
201
src/pageHome/components/select-pet-modal.vue
Normal file
201
src/pageHome/components/select-pet-modal.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择宠物">
|
||||
<view class="select-pet-container">
|
||||
<view v-if="!isLoading && petList.length" class="pet-container">
|
||||
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
|
||||
refresher-background="transparent">
|
||||
<view class="pet-item" v-for="(item, index) in petList" :key="item.chongwu_id" @click.stop="changePet(item)">
|
||||
<image v-if="item.chongwu_pic_url" class="pet-icon" mode="scaleToFill" :src="item.chongwu_pic_url"/>
|
||||
<image v-else mode="scaleToFill" class="pet-icon" src="https://activity.wagoo.live/record_avator.png"/>
|
||||
<view class="pet-name-view">
|
||||
<text class="app-fc-main fs-32 app-font-bold-500">{{ item.name }}</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="selectPetInfo.chongwu_id === item.chongwu_id"
|
||||
src="@/static/images/cart_checked.png"
|
||||
mode="widthFix"
|
||||
class="select-icon"
|
||||
/>
|
||||
<image
|
||||
v-else
|
||||
src="@/static/images/unchecked.png"
|
||||
mode="widthFix"
|
||||
class="select-icon"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="pet-container flex-center" v-if="isLoading">
|
||||
<uni-load-more status="loading" :show-text="false"/>
|
||||
</view>
|
||||
<view v-if="petList.length === 0 && !isLoading" class="pet-container" @click.stop="gotoAddPet">
|
||||
<image src="https://activity.wagoo.live/no_pet.png" class="no-pet-img" mode="heightFix" />
|
||||
</view>
|
||||
<view class="add-btn" @click.stop="gotoAddPet">
|
||||
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
|
||||
<text class="app-fc-white fs-30">添加宠物</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import { getPetList } from "@/api/common";
|
||||
import appConfig from '../../constants/app.config';
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
petType: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
selectPetInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
petList: [],
|
||||
isLoadMore: false,
|
||||
isNoMore: false,
|
||||
appConfig,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changePet(pet) {
|
||||
this.$emit('changePet', pet);
|
||||
},
|
||||
loadMoreAction() {
|
||||
if (this.isNoMore || this.isLoadMore) {
|
||||
return;
|
||||
}
|
||||
this.pageNumber++;
|
||||
this.isLoadMore = true;
|
||||
this.getData()
|
||||
},
|
||||
getData() {
|
||||
getPetList(null, this.pageNumber, this.pageSize).then((res) => {
|
||||
let list = res?.info || [];
|
||||
if (this.pageNumber === 1) {
|
||||
this.petList = list;
|
||||
} else {
|
||||
this.petList = [...this.petList, ...list];
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
this.isNoMore = list.length < this.pageSize;
|
||||
}).catch(() => {
|
||||
if (this.pageNumber !== 1) {
|
||||
this.pageNumber--;
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.isLoadMore = false;
|
||||
})
|
||||
},
|
||||
gotoAddPet() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/record/edit?type=${this.petType }&typeId=aaa`,
|
||||
events: {
|
||||
addPetSuccess: () => {
|
||||
this.isLoading = true;
|
||||
this.isNoMore = false;
|
||||
this.isLoadMore = false;
|
||||
this.pageNumber = 1;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-pet-container {
|
||||
width: 100%;
|
||||
padding: 0 60rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.pet-container {
|
||||
width: 100%;
|
||||
height: 444rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 18rpx;
|
||||
|
||||
.no-pet-img {
|
||||
height: calc(1289 / 1363 * 550rpx);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.no-text {
|
||||
margin: 44rpx 0 26rpx;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pet-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 34rpx 22rpx;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 2rpx solid #F7F3F7;
|
||||
|
||||
.pet-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pet-name-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
width: 27rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
|
||||
.add-icon {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
261
src/pageHome/components/select-reservation-time-modal.vue
Normal file
261
src/pageHome/components/select-reservation-time-modal.vue
Normal file
@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<select-modal @close="closeAction" title="选择预约时间">
|
||||
<view class="select-time-container">
|
||||
<view class="calendar-container">
|
||||
<uni-calendar
|
||||
:insert="true"
|
||||
class="uni-calendar--hook"
|
||||
:start-date="startDate"
|
||||
:end-date="endDate"
|
||||
:date="selectDate"
|
||||
:showMonth="false"
|
||||
@change="changeDate"/>
|
||||
</view>
|
||||
<view class="tips-view">
|
||||
<image src="/pageHome/static/tips.png" class="tips-img" mode="aspectFit"></image>
|
||||
<text class="fs-24 app-fc-normal">
|
||||
{{isAfternoon? '可以预约的时间范围是后天及之后的 14 天' : '可以预约的时间范围是明天及之后的 14 天' }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="fs-32 app-fc-main time-header-text">可预约时段</text>
|
||||
<view class="time-list-container">
|
||||
<scroll-view v-if="!isLoading && dateList.length" class="list-scroll-view" :scroll-y="true">
|
||||
<view
|
||||
@click.stop="changeTimeAction(item)"
|
||||
class="time-item"
|
||||
:class="{'selected-time-item': selectTimeRange.shiduan_id === item.shiduan_id, 'disabled-time-item': item.num === 0}"
|
||||
v-for="item in dateList"
|
||||
:key="item.shiduan_id">
|
||||
<text class="fs-28 app-fc-main">{{ `${item.start}-${item.end}` }}</text>
|
||||
<text class="fs-28 app-fc-main">{{ item.num === 0 ? '已约满' : `剩余${item.num}个` }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="isLoading" class="loading-view">
|
||||
<uni-load-more status="loading"/>
|
||||
</view>
|
||||
<view v-if="!isLoading && dateList.length === 0" class="loading-view">
|
||||
<text class="fs-28 app-fc-main">无可预约时间</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="changeReservationTime">
|
||||
<text class="app-fc-white fs-30">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</select-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectModal from "@/components/select-modal.vue";
|
||||
import moment from "moment";
|
||||
import "moment/locale/zh-cn";
|
||||
import { getYuYueTimeList } from "@/api/order";
|
||||
|
||||
export default {
|
||||
components: { SelectModal },
|
||||
props: {
|
||||
selectTime: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectTimeRange: {},
|
||||
isLoading: false,
|
||||
selectDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
|
||||
startDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
|
||||
endDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(16, 'days').format('YYYY-MM-DD') : moment().add(15, 'days').format('YYYY-MM-DD'),
|
||||
dateList: [],
|
||||
//是下午
|
||||
isAfternoon: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00'))
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
moment.locale('zh-cn'); // 设置中文本地化
|
||||
if (Object.keys(this.selectTime).length > 0) {
|
||||
this.selectDate = this.selectTime.date;
|
||||
this.selectTimeRange = this.selectTime;
|
||||
this.getTimeArray(this.selectTime.date)
|
||||
} else {
|
||||
this.getTimeArray(this.selectDate)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeAction() {
|
||||
this.$emit('close');
|
||||
},
|
||||
changeDate(e) {
|
||||
this.selectTimeRange = {};
|
||||
this.selectDate = e.fulldate;
|
||||
this.getTimeArray(e.fulldate)
|
||||
},
|
||||
changeTimeAction(item) {
|
||||
if (item.num === 0) {
|
||||
uni.showToast({
|
||||
title: '当前时间已约满',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
this.selectTimeRange = item;
|
||||
}
|
||||
},
|
||||
changeReservationTime() {
|
||||
if (this.dateList.length === 0) {
|
||||
uni.showToast({
|
||||
title: '暂无可预约时间',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (Object.keys(this.selectTimeRange).length === 0) {
|
||||
uni.showToast({
|
||||
title: '请选择一个预约时段',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
let dateLabel;
|
||||
if (moment().format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `今天${moment(this.selectDate).format('M月D日')}`
|
||||
} else if (moment().add(1, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `明天${moment(this.selectDate).format('M月D日')}`
|
||||
} else if (moment().add(2, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
|
||||
dateLabel = `后天${moment(this.selectDate).format('M月D日')}`
|
||||
} else {
|
||||
dateLabel = `${moment(this.selectDate).format('M月D日')}`
|
||||
}
|
||||
this.$emit('changeReservationTime', {
|
||||
dateLabel,
|
||||
date: this.selectDate,
|
||||
...this.selectTimeRange,
|
||||
});
|
||||
},
|
||||
getTimeArray(t) {
|
||||
this.isLoading = true
|
||||
this.dateList = [];
|
||||
let isCurrent = moment().format('YYYY-MM-DD') === t;
|
||||
return getYuYueTimeList(t).then((res) => {
|
||||
let list = (res?.info || []).map((item) => {
|
||||
return {
|
||||
...item,
|
||||
start: item.start.substring(0, 5),
|
||||
end: item.end.substring(0, 5),
|
||||
};
|
||||
});
|
||||
this.dateList = list.filter((item) => {
|
||||
let end = moment(item.end, 'HH:mm');
|
||||
let now = moment();
|
||||
if (isCurrent) {
|
||||
return end.isAfter(now);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
this.isLoading = false;
|
||||
return Promise.resolve();
|
||||
}).catch(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-time-container {
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.calendar-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tips-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #F5F5F5;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.tips-img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time-header-text {
|
||||
margin-left: 28rpx;
|
||||
}
|
||||
|
||||
.time-list-container {
|
||||
margin-left: 28rpx;
|
||||
width: calc(100% - 56rpx);
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.loading-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.list-scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
.time-item {
|
||||
width: 100%;
|
||||
height: 102rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
background-color: #fff;
|
||||
opacity: 1;
|
||||
margin-bottom: 22rpx;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.selected-time-item {
|
||||
border: 2rpx solid $app_color_main;
|
||||
background-color: $app_color_mark_bg_color;
|
||||
}
|
||||
|
||||
.disabled-time-item {
|
||||
background-color: #FBF7FC;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: calc(100% - 120rpx);
|
||||
margin-left: 60rpx;
|
||||
margin-bottom: 10rpx;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 45rpx;
|
||||
background-color: $app_color_main;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
111
src/pageHome/constants/home.js
Normal file
111
src/pageHome/constants/home.js
Normal file
@ -0,0 +1,111 @@
|
||||
export const ORDER_STATUS_UNPAY = 1; // 未支付
|
||||
export const ORDER_STATUS_RESERVED = 2; // 已预约
|
||||
export const ORDER_STATUS_SEND = 3; // 已派单
|
||||
export const ORDER_STATUS_SERVICE = 4; //服务中
|
||||
export const ORDER_STATUS_COMPLETED = 5; // 已完成
|
||||
export const ORDER_STATUS_CANCELED = 6; // 已取消
|
||||
export const ORDER_STATUS_REFUND = 7; // 退款中
|
||||
|
||||
export const PRICE_DIFF_TYPE_SERVICE = '1'; //差价类型 服务
|
||||
|
||||
|
||||
export const orderStatusList = [{
|
||||
value: ORDER_STATUS_UNPAY,
|
||||
label: '未支付',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_RESERVED,
|
||||
label: '已预约',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SEND,
|
||||
label: '已派单',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SERVICE,
|
||||
label: '服务中',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_COMPLETED,
|
||||
label: '已完成',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_CANCELED,
|
||||
label: '已取消',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_REFUND,
|
||||
label: '退款中',
|
||||
},
|
||||
]
|
||||
|
||||
//展示的状态
|
||||
export const showOrderStatus = [{
|
||||
value: 0,
|
||||
label: '全部',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_UNPAY,
|
||||
label: '未支付',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_RESERVED,
|
||||
label: '已预约',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_SERVICE,
|
||||
label: '服务中',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_COMPLETED,
|
||||
label: '已完成',
|
||||
},
|
||||
{
|
||||
value: ORDER_STATUS_CANCELED,
|
||||
label: '已取消',
|
||||
},
|
||||
]
|
||||
|
||||
//充值相关
|
||||
export const rechargeStatus = [{
|
||||
value: '1',
|
||||
label: '充值',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '充值记录',
|
||||
}
|
||||
]
|
||||
//充值赠送金额
|
||||
export const giftAmount = [{
|
||||
value: '1',
|
||||
label: '100',
|
||||
gift: '10'
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '200',
|
||||
gift: '20'
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
label: '500',
|
||||
gift: '50'
|
||||
},
|
||||
{
|
||||
value: '4',
|
||||
label: '1000',
|
||||
gift: '100'
|
||||
},
|
||||
{
|
||||
value: '5',
|
||||
label: '2000',
|
||||
gift: '200'
|
||||
},
|
||||
{
|
||||
value: '6',
|
||||
label: '5000',
|
||||
gift: '500'
|
||||
}
|
||||
|
||||
]
|
||||
267
src/pageHome/franchise/city-select.vue
Normal file
267
src/pageHome/franchise/city-select.vue
Normal file
@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<view class="city-select-container">
|
||||
<view class="list-wrapper">
|
||||
<uni-indexed-list
|
||||
:options="cityOptions"
|
||||
:show-select="true"
|
||||
@click="onCityClick"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 底部确认按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<view class="selected-info">
|
||||
<text class="selected-text">已选择:{{ selectedCities.length }}个城市</text>
|
||||
</view>
|
||||
<view class="action-buttons">
|
||||
<view class="btn btn-cancel" @click="handleCancel">
|
||||
<text class="btn-text">取消</text>
|
||||
</view>
|
||||
<view class="btn btn-confirm" @click="handleConfirm">
|
||||
<text class="btn-text">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { cityData } from '@/api/areas.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cityOptions: [],
|
||||
selectedCities: [], // 已选中的城市名称数组
|
||||
selectedCityNames: [] // 已选中的城市名称(用于显示)
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
// 如果有传入已选中的城市,解析并设置
|
||||
if (options.selectedCities) {
|
||||
try {
|
||||
this.selectedCityNames = JSON.parse(decodeURIComponent(options.selectedCities));
|
||||
this.selectedCities = [...this.selectedCityNames];
|
||||
} catch (e) {
|
||||
console.error('解析已选城市失败:', e);
|
||||
}
|
||||
}
|
||||
this.loadCityData();
|
||||
},
|
||||
methods: {
|
||||
// 加载城市数据
|
||||
loadCityData() {
|
||||
try {
|
||||
// 使用 cityData 数据源,已经按首字母分组
|
||||
this.cityOptions = cityData.map(group => {
|
||||
// 处理每个字母组的数据
|
||||
const processedData = group.data.map(city => {
|
||||
const cityName = city.name;
|
||||
// 如果城市在已选中列表中,使用对象格式并设置 checked: true
|
||||
// 否则使用字符串格式(组件会自动转换为 { name: '城市名', checked: false })
|
||||
if (this.selectedCities.includes(cityName)) {
|
||||
return {
|
||||
name: cityName,
|
||||
checked: true
|
||||
};
|
||||
} else {
|
||||
return cityName;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
letter: group.letter,
|
||||
data: processedData
|
||||
};
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('加载城市数据失败:', err);
|
||||
uni.showToast({
|
||||
title: '加载城市数据失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 获取城市首字母(简单的拼音首字母映射)
|
||||
getFirstLetter(city) {
|
||||
if (!city) return 'Z';
|
||||
|
||||
// 常用城市拼音首字母映射
|
||||
const pinyinMap = {
|
||||
'北京': 'B', '上海': 'S', '天津': 'T', '重庆': 'C',
|
||||
'河北': 'H', '山西': 'S', '内蒙古': 'N', '辽宁': 'L',
|
||||
'吉林': 'J', '黑龙江': 'H', '江苏': 'J', '浙江': 'Z',
|
||||
'安徽': 'A', '福建': 'F', '江西': 'J', '山东': 'S',
|
||||
'河南': 'H', '湖北': 'H', '湖南': 'H', '广东': 'G',
|
||||
'广西': 'G', '海南': 'H', '四川': 'S', '贵州': 'G',
|
||||
'云南': 'Y', '西藏': 'X', '陕西': 'S', '甘肃': 'G',
|
||||
'青海': 'Q', '宁夏': 'N', '新疆': 'X', '台湾': 'T',
|
||||
'香港': 'X', '澳门': 'A'
|
||||
};
|
||||
|
||||
// 先检查完整匹配
|
||||
if (pinyinMap[city]) {
|
||||
return pinyinMap[city];
|
||||
}
|
||||
|
||||
// 检查是否以某个省份开头
|
||||
for (let key in pinyinMap) {
|
||||
if (city.startsWith(key)) {
|
||||
return pinyinMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 使用首字符的拼音
|
||||
const char = city.charAt(0);
|
||||
const charMap = {
|
||||
'北': 'B', '上': 'S', '天': 'T', '重': 'C',
|
||||
'河': 'H', '山': 'S', '内': 'N', '辽': 'L',
|
||||
'吉': 'J', '黑': 'H', '江': 'J', '浙': 'Z',
|
||||
'安': 'A', '福': 'F', '湖': 'H', '广': 'G',
|
||||
'海': 'H', '四': 'S', '贵': 'G', '云': 'Y',
|
||||
'西': 'X', '陕': 'S', '甘': 'G', '青': 'Q',
|
||||
'宁': 'N', '新': 'X', '台': 'T', '香': 'X', '澳': 'A',
|
||||
'杭': 'H', '南': 'N', '武': 'W', '成': 'C',
|
||||
'西': 'X', '郑': 'Z', '长': 'C', '沈': 'S',
|
||||
'大': 'D', '青': 'Q', '济': 'J', '石': 'S',
|
||||
'哈': 'H', '合': 'H', '福': 'F', '厦': 'X',
|
||||
'昆': 'K', '兰': 'L', '呼': 'H', '太': 'T',
|
||||
'乌': 'W', '银': 'Y', '贵': 'G', '拉': 'L'
|
||||
};
|
||||
|
||||
if (charMap[char]) {
|
||||
return charMap[char];
|
||||
}
|
||||
|
||||
// 如果是英文字母,直接返回大写
|
||||
if (/[A-Za-z]/.test(char)) {
|
||||
return char.toUpperCase();
|
||||
}
|
||||
|
||||
// 默认返回 Z
|
||||
return 'Z';
|
||||
},
|
||||
|
||||
// 城市点击事件
|
||||
// e.select 包含所有已选中的项(组件内部维护的选中状态)
|
||||
onCityClick(e) {
|
||||
// 从组件返回的 select 数组中提取所有选中项的城市名称
|
||||
if (e && e.select && Array.isArray(e.select)) {
|
||||
this.selectedCities = e.select.map(item => item.name || item);
|
||||
} else {
|
||||
// 如果没有 select,说明可能是单选模式,使用 item
|
||||
if (e && e.item) {
|
||||
const cityName = e.item.name || e.item;
|
||||
const index = this.selectedCities.indexOf(cityName);
|
||||
if (index > -1) {
|
||||
this.selectedCities.splice(index, 1);
|
||||
} else {
|
||||
this.selectedCities.push(cityName);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 取消
|
||||
handleCancel() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 确认
|
||||
handleConfirm() {
|
||||
// 通过 eventChannel 返回选中的城市
|
||||
const eventChannel = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (eventChannel) {
|
||||
eventChannel.emit('citySelected', {
|
||||
cities: this.selectedCities,
|
||||
cityNames: this.selectedCities.join('、')
|
||||
});
|
||||
}
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.city-select-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 覆盖 uni-indexed-list 的 bottom 值,避免被底部按钮遮盖
|
||||
// 底部按钮区域高度:选中信息(20+28) + 按钮(88) + padding(40) = 176rpx,加上 safe-area
|
||||
::v-deep .uni-indexed-list {
|
||||
bottom: calc(180rpx + env(safe-area-inset-bottom)) !important;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #eee;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
z-index: 100;
|
||||
|
||||
.selected-info {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.selected-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.btn-cancel {
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.btn-text {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-confirm {
|
||||
background-color: #FF19A0;
|
||||
|
||||
.btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
375
src/pageHome/franchise/index.vue
Normal file
375
src/pageHome/franchise/index.vue
Normal file
@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<view class="franchise-container">
|
||||
<scroll-view class="scroll-content" scroll-y>
|
||||
<view class="form-wrapper">
|
||||
<view class="form-card">
|
||||
<!-- 姓名 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-row">
|
||||
<text class="required-star">*</text>
|
||||
<text class="form-label">姓名</text>
|
||||
|
||||
</view>
|
||||
<input class="form-input" v-model="formData.name" placeholder="填写真实姓名" placeholder-style="color: #999;" />
|
||||
</view>
|
||||
|
||||
<!-- 联系电话 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-row">
|
||||
<text class="required-star">*</text>
|
||||
|
||||
<text class="form-label">联系电话</text>
|
||||
</view>
|
||||
<input class="form-input" v-model="formData.phone" placeholder="请输入" placeholder-style="color: #999;"
|
||||
type="number" />
|
||||
</view>
|
||||
|
||||
<!-- 微信号 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">微信号</text>
|
||||
<input class="form-input" v-model="formData.wechatId" placeholder="请输入" placeholder-style="color: #999;" />
|
||||
</view>
|
||||
|
||||
<!-- 邮箱号 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">邮箱号</text>
|
||||
<input class="form-input" v-model="formData.email" placeholder="请输入" placeholder-style="color: #999;" />
|
||||
</view>
|
||||
|
||||
<!-- 意向城市 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-row">
|
||||
<text class="required-star">*</text>
|
||||
<text class="form-label">意向城市</text>
|
||||
</view>
|
||||
<view class="form-selector" @click="selectCities">
|
||||
<text class="selector-text" :class="{ 'placeholder': !formData.city }">
|
||||
{{ formData.city || '请选择' }}
|
||||
</text>
|
||||
<image v-if="!formData.city" class="arrow-icon" :src="imgPrefix + 'right-arrow.png'" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="form-card">
|
||||
<text class="section-title">备注</text>
|
||||
<textarea class="form-textarea" v-model="formData.remarks" placeholder="请输入备注"
|
||||
placeholder-style="color: #999;" :maxlength="500" />
|
||||
</view>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
<view class="form-card">
|
||||
<text class="section-title">注意事项</text>
|
||||
<view class="notice-content">
|
||||
<text class="notice-item">(1) 此窗体仅限申请加盟意愿用,谢谢您的配合。</text>
|
||||
<text class="notice-item">(2) 第一阶段书面审核将于5个工作天内回复,请留意邮箱及垃圾信件。</text>
|
||||
<text class="notice-item">(3) 为保护个资,未通过之书面审核数据将定期销毁。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-buttons">
|
||||
<view class="btn btn-clear" @click="clearForm">
|
||||
<text class="btn-text">清除</text>
|
||||
</view>
|
||||
<view class="btn btn-submit" @click="submitForm">
|
||||
<text class="btn-text">送出</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { imgPrefix } from '@/utils/common';
|
||||
import { franchiseApply } from '@/api/franchise';
|
||||
|
||||
export default {
|
||||
name: 'Franchise',
|
||||
data() {
|
||||
return {
|
||||
imgPrefix,
|
||||
formData: {
|
||||
name: '',
|
||||
phone: '',
|
||||
wechatId: '',
|
||||
email: '',
|
||||
city: '',
|
||||
selectedCityNames: [],
|
||||
remarks: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 选择城市
|
||||
selectCities() {
|
||||
const selectedCities = this.formData.selectedCityNames || [];
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/franchise/city-select?selectedCities=${encodeURIComponent(JSON.stringify(selectedCities))}`,
|
||||
events: {
|
||||
citySelected: (data) => {
|
||||
this.formData.selectedCityNames = data.cities;
|
||||
this.formData.city = data.cityNames;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
clearForm() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清除所有表单数据吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.formData = {
|
||||
name: '',
|
||||
phone: '',
|
||||
wechatId: '',
|
||||
email: '',
|
||||
city: '',
|
||||
selectedCityNames: [],
|
||||
remarks: ''
|
||||
};
|
||||
uni.showToast({
|
||||
title: '已清除',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
async submitForm() {
|
||||
// 验证必填项
|
||||
if (!this.formData.name.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入姓名',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.formData.phone.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入联系电话',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.formData.city) {
|
||||
uni.showToast({
|
||||
title: '请选择意向城市',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '提交中...'
|
||||
});
|
||||
|
||||
const submitData = {
|
||||
name: this.formData.name.trim(),
|
||||
phone: this.formData.phone.trim(),
|
||||
email: this.formData.email.trim(),
|
||||
Preferredcity: this.formData.city.replace(/、/g, ','),
|
||||
weChatID: this.formData.wechatId.trim(),
|
||||
remarks: this.formData.remarks.trim()
|
||||
};
|
||||
|
||||
await franchiseApply(submitData);
|
||||
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 提交成功后返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: error.message || '提交失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.franchise-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #ffecf3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding-bottom: 180rpx;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required-star {
|
||||
font-size: 28rpx;
|
||||
color: #FF19A0;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-selector {
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
.selector-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.selector-text.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 10rpx;
|
||||
height: 18rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.notice-item {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bottom-buttons {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx 32rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0px -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-clear {
|
||||
background: #fff;
|
||||
border: 2rpx solid #FF19A0;
|
||||
|
||||
.btn-text {
|
||||
color: #FF19A0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background: #FF19A0;
|
||||
|
||||
.btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1754
src/pageHome/order/additional.vue
Normal file
1754
src/pageHome/order/additional.vue
Normal file
File diff suppressed because it is too large
Load Diff
1649
src/pageHome/order/order-detail-page.vue
Normal file
1649
src/pageHome/order/order-detail-page.vue
Normal file
File diff suppressed because it is too large
Load Diff
526
src/pageHome/reservation/index.vue
Normal file
526
src/pageHome/reservation/index.vue
Normal file
@ -0,0 +1,526 @@
|
||||
<template>
|
||||
<view class="reservation-container">
|
||||
<view class="body-container">
|
||||
<scroll-view class="scroll-view" :scroll-y="true">
|
||||
<view class="order-tab-list">
|
||||
<view class="order-type-item" @click.stop="selectOrderType(ORDER_TYPE_RESERVATION)">
|
||||
<text class="order-type-text fs-34"
|
||||
:class="{'order-type-active-text': orderType === ORDER_TYPE_RESERVATION}">
|
||||
预约单
|
||||
</text>
|
||||
<view class="select-line" :class="{'active-line': orderType === ORDER_TYPE_RESERVATION}"/>
|
||||
</view>
|
||||
<view class="order-type-item" @click.stop="selectOrderType(ORDER_TYPE_SITE)">
|
||||
<text class="order-type-text fs-34"
|
||||
:class="{'order-type-active-text': orderType === ORDER_TYPE_SITE}">
|
||||
现场单
|
||||
</text>
|
||||
<view class="select-line" :class="{'active-line': orderType === ORDER_TYPE_SITE}"/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pet-type-tab-view">
|
||||
<view class="pet-type-item-view" @click.stop="selectPetType(PET_TYPE_CAT)">
|
||||
<view class="pet-type-item"
|
||||
:class="{'pet-type-active-item': selectedPetType === PET_TYPE_CAT}">
|
||||
<image :src="selectedPetType === PET_TYPE_CAT ? 'https://activity.wagoo.live/cat_active.png' : 'https://activity.wagoo.live/cat.png' " class="pet-typ-img" mode="aspectFit"/>
|
||||
<text class="fs-32 app-fc-main"
|
||||
:class="{'app-fc-white app-font-bold-700': selectedPetType === PET_TYPE_CAT}">
|
||||
猫
|
||||
</text>
|
||||
</view>
|
||||
<view class="pet-type-triangle" :class="{'pet-type-active-triangle': selectedPetType === PET_TYPE_CAT}"/>
|
||||
</view>
|
||||
<view class="pet-type-item-view" @click.stop="selectPetType(PET_TYPE_DOG)">
|
||||
<view class="pet-type-item"
|
||||
:class="{'pet-type-active-item': selectedPetType === PET_TYPE_DOG}">
|
||||
<image :src="selectedPetType === PET_TYPE_DOG ? 'https://activity.wagoo.live/dog_active.png' : 'https://activity.wagoo.live/dog.png'" class="pet-typ-img" mode="aspectFit"/>
|
||||
<text class="fs-32 app-fc-main"
|
||||
:class="{'app-fc-white app-font-bold-700': selectedPetType === PET_TYPE_DOG}">
|
||||
狗
|
||||
</text>
|
||||
</view>
|
||||
<view class="pet-type-triangle" :class="{'pet-type-active-triangle': selectedPetType === PET_TYPE_DOG}"/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="reservation-info-container app-padding-pageHorizontal">
|
||||
<!-- 其他内容 -->
|
||||
<view class="content-section">
|
||||
<info-cell key="pet" cell-type="text" :info-icon="require('@/static/images/dog.png')" title="选择宠物"
|
||||
:info="petInfo.name || ''" @clickAction="goToSelectPet"/>
|
||||
<info-cell key="weight" cell-type="text" :info-icon="require('@/pageHome/static/weight.png')"
|
||||
title="宠物重量区间" :info="petInfo.weight_name" :is-can-click="false"/>
|
||||
<info-cell v-if="selectedPetType === PET_TYPE_CAT" key="mofa" cell-type="text"
|
||||
:info-icon="require('@/static/images/dog.png')" title="宠物毛发" :info="hair"
|
||||
:is-can-click="false"/>
|
||||
<info-cell v-if="orderType === ORDER_TYPE_RESERVATION" key="time" cell-type="time" :info-icon="require('@/pageHome/static/time.png')"
|
||||
title="选择预约时间"
|
||||
:info="Object.keys(reservationTime).length > 0 ? `${reservationTime.dateLabel} ${reservationTime.start}-${reservationTime.end}` :''"
|
||||
@clickAction="isShowTime = true"/>
|
||||
<info-cell key="address" cell-type="address" :info-icon="require('@/pageHome/static/address.png')"
|
||||
title="选择服务地址" :address-info="address" @clickAction="isShowAddress = true"/>
|
||||
<info-cell key="park" cell-type="park" title="停车状况" :park-state="parkState"
|
||||
:other-park-state="otherParkState"
|
||||
@changeParkState="changeParkState" @changeOtherParkState="changeOtherParkState"/>
|
||||
|
||||
<view class="price-view">
|
||||
<image v-if="petWeight.desc" @click.stop="isShowPriceDes=true" src="/pageHome/static/tips.png" mode="aspectFit"
|
||||
class="tip-img"/>
|
||||
<text @click.stop="isShowPriceDes=true" class="app-fc-main fs-28">预估</text>
|
||||
<text class="app-fc-main fs-52 app-font-bold-700 price-text">{{ price || '0.00' }}</text>
|
||||
<text class="app-fc-main fs-28">元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="submit-btn" @click.stop="paymentConfirm">
|
||||
<text class="app-fc-white fs-28 app-font-bold-500">下一步</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 广告 -->
|
||||
<view class="ad-container">
|
||||
<view class="ad-view" v-if="selectedPetType === PET_TYPE_CAT" v-html="catHtmlData"/>
|
||||
<view class="ad-view" v-else v-html="dogHtmlData"/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<select-address-modal
|
||||
v-if="isShowAddress"
|
||||
:select-address="address"
|
||||
@close="isShowAddress = false"
|
||||
@changeAddress="changeAddress"
|
||||
/>
|
||||
<select-weight-modal
|
||||
v-if="isShowWeight"
|
||||
:pet-weight="petWeight"
|
||||
:weight-list="weightList"
|
||||
@close="isShowWeight = false"
|
||||
@changeWeight="changeWeight"
|
||||
/>
|
||||
<select-reservation-time-modal
|
||||
v-if="isShowTime"
|
||||
:select-time="reservationTime"
|
||||
@close="isShowTime = false"
|
||||
@changeReservationTime="changeReservationTime"
|
||||
/>
|
||||
<price-description-modal
|
||||
v-if="isShowPriceDes"
|
||||
:price-info="petWeight"
|
||||
@close="isShowPriceDes = false"
|
||||
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import InfoCell from "@/pageHome/components/info-cell.vue";
|
||||
import SelectAddressModal from "@/pageHome/components/select-address-modal.vue";
|
||||
import SelectWeightModal from "@/components/petOrder/select-weight-modal.vue";
|
||||
import SelectReservationTimeModal from "@/pageHome/components/select-reservation-time-modal.vue";
|
||||
import { getWeightList } from "@/api/common";
|
||||
import {
|
||||
ARTICLE_TYPE_RESERVATION_CAT,
|
||||
ARTICLE_TYPE_RESERVATION_DOG,
|
||||
ORDER_TYPE_RESERVATION,
|
||||
ORDER_TYPE_SITE,
|
||||
PET_HAIR_LONG,
|
||||
PET_TYPE_CAT,
|
||||
PET_TYPE_DOG
|
||||
} from "@/constants/app.business";
|
||||
import { getArticleDetail } from "@/api/article";
|
||||
import appConfig from '@/constants/app.config';
|
||||
import { getCityIsOpen } from '@/api/order';
|
||||
import PriceDescriptionModal from "@/pageHome/components/price-description-modal.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PriceDescriptionModal,
|
||||
SelectReservationTimeModal,
|
||||
SelectWeightModal,
|
||||
SelectAddressModal,
|
||||
InfoCell
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
PET_TYPE_CAT,
|
||||
PET_TYPE_DOG,
|
||||
ORDER_TYPE_RESERVATION,
|
||||
ORDER_TYPE_SITE,
|
||||
orderType: ORDER_TYPE_RESERVATION,
|
||||
selectedPetType: PET_TYPE_CAT, // 默认选中“猫”
|
||||
petInfo: {},
|
||||
parkState: '',
|
||||
otherParkState: '',
|
||||
allWeightList: [],
|
||||
petWeight: {},
|
||||
price: '',
|
||||
reservationTime: {},
|
||||
address: null,
|
||||
isShowAddress: false,
|
||||
isShowWeight: false,
|
||||
isShowTime: false,
|
||||
catHtmlData: '',
|
||||
dogHtmlData: '',
|
||||
isShowPriceDes: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initData();
|
||||
},
|
||||
computed: {
|
||||
weightList() {
|
||||
return this.allWeightList.filter((item) => item.type === this.selectedPetType);
|
||||
},
|
||||
hair() {
|
||||
if (Object.keys(this.petInfo).length > 0) {
|
||||
if (this.selectedPetType === PET_TYPE_CAT) {
|
||||
return `${this.petInfo.maofa}` === `${PET_HAIR_LONG}` ? '长毛' : '短毛';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
getArticleDetail(ARTICLE_TYPE_RESERVATION_CAT).then((res) => {
|
||||
this.catHtmlData = this.processHtmlContent(res?.info?.content || '');
|
||||
})
|
||||
getArticleDetail(ARTICLE_TYPE_RESERVATION_DOG).then((res) => {
|
||||
this.dogHtmlData = this.processHtmlContent(res?.info?.content || '');
|
||||
})
|
||||
getWeightList().then((res) => {
|
||||
this.allWeightList = res?.info || [];
|
||||
})
|
||||
},
|
||||
processHtmlContent(html) {
|
||||
return html.replace(/max-width/g, 'width');
|
||||
},
|
||||
selectOrderType(orderType) {
|
||||
if (this.orderType === orderType) {
|
||||
return;
|
||||
}
|
||||
this.reservationTime = {};
|
||||
this.parkState = '';
|
||||
this.address = null;
|
||||
this.orderType = orderType;
|
||||
},
|
||||
selectPetType(petType) {
|
||||
if (this.selectedPetType === petType) {
|
||||
return;
|
||||
}
|
||||
this.petInfo = {};
|
||||
this.parkState = '';
|
||||
this.petWeight = {};
|
||||
this.price = '';
|
||||
this.reservationTime = {};
|
||||
this.address = null;
|
||||
this.selectedPetType = petType;
|
||||
},
|
||||
changeParkState(state) {
|
||||
this.parkState = state;
|
||||
this.otherParkState = '';
|
||||
},
|
||||
changeOtherParkState(state) {
|
||||
this.otherParkState = state;
|
||||
},
|
||||
goToSelectPet() {
|
||||
const selectPetInfo = this.petInfo && Object.keys(this.petInfo).length > 0
|
||||
? encodeURIComponent(JSON.stringify(this.petInfo))
|
||||
: '';
|
||||
uni.navigateTo({
|
||||
url: `/pageHome/selectPet/index?petType=${this.selectedPetType}${selectPetInfo ? `&selectPetInfo=${selectPetInfo}` : ''}`,
|
||||
events: {
|
||||
changePet: (pet) => {
|
||||
this.changePet(pet);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
changePet(item) {
|
||||
if (!item || item.chongwu_id === this.petInfo.chongwu_id) {
|
||||
return
|
||||
}
|
||||
this.petInfo = item;
|
||||
this.selectedPetType = item.type;
|
||||
if (item.weight_id || item.weight_id === 0) {
|
||||
this.petWeight = this.allWeightList.find((w) => w.weight_id === item.weight_id) || {}
|
||||
this.price = this.petWeight.price || '';
|
||||
}
|
||||
},
|
||||
changeAddress(address) {
|
||||
// 若当前地址未开通服务,则不允许选择
|
||||
getCityIsOpen(address.qu_id)
|
||||
.then(() => {
|
||||
this.address = address;
|
||||
this.isShowAddress = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.showToast({
|
||||
title: "暂未开通该区域的预约服务",
|
||||
icon: "none",
|
||||
});
|
||||
});
|
||||
},
|
||||
changeWeight(weight) {
|
||||
this.petWeight = weight;
|
||||
this.price = this.petWeight.price || '';
|
||||
this.isShowWeight = false;
|
||||
},
|
||||
changeReservationTime(timeData) {
|
||||
this.reservationTime = timeData;
|
||||
this.isShowTime = false;
|
||||
},
|
||||
paymentConfirm() {
|
||||
if (!this.petInfo.chongwu_id) {
|
||||
uni.showToast({
|
||||
title: '请选择宠物',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (Object.keys(this.reservationTime).length === 0 && this.orderType === ORDER_TYPE_RESERVATION) {
|
||||
uni.showToast({
|
||||
title: '请选择预约时间',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (!this.address) {
|
||||
uni.showToast({
|
||||
title: '请选择服务地址',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (!this.parkState) {
|
||||
uni.showToast({
|
||||
title: '请选择停车状况',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (this.parkState === '其他' && !this.otherParkState) {
|
||||
uni.showToast({
|
||||
title: '请输入停车信息',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pageHome/reservation/payment-confirm-page',
|
||||
success: (res) => {
|
||||
res.eventChannel.emit('reservationInfo', {
|
||||
petInfo: this.petInfo,
|
||||
parkState: this.otherParkState || this.parkState,
|
||||
petWeight: this.petWeight,
|
||||
reservationTime: this.reservationTime,
|
||||
address: this.address,
|
||||
price: this.price,
|
||||
orderType: this.orderType,
|
||||
})
|
||||
},
|
||||
events: {
|
||||
clearData: () => {
|
||||
this.selectedPetType = PET_TYPE_CAT;
|
||||
this.petInfo = {};
|
||||
this.parkState = '';
|
||||
this.petWeight = {};
|
||||
this.price = '';
|
||||
this.reservationTime = {};
|
||||
this.address = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onShareAppMessage(res) {
|
||||
return {
|
||||
title: appConfig.appShareName,
|
||||
path: '/pages/client/index/index'
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reservation-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #F7F8FA;
|
||||
|
||||
.body-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
.scroll-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
.order-tab-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
|
||||
.order-type-item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.select-line {
|
||||
margin-top: 14rpx;
|
||||
width: 24rpx;
|
||||
height: 10rpx;
|
||||
background-color: transparent;
|
||||
border-radius: 5rpx;
|
||||
}
|
||||
|
||||
.active-line {
|
||||
background-color: #FE019B;
|
||||
}
|
||||
}
|
||||
|
||||
.order-type-text {
|
||||
color: #AFA5AE
|
||||
}
|
||||
|
||||
.order-type-active-text {
|
||||
color: #272427
|
||||
}
|
||||
}
|
||||
|
||||
.pet-type-tab-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 42rpx 0 24rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.pet-type-item-view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.pet-type-item {
|
||||
width: 300rpx;
|
||||
height: 104rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.pet-typ-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.pet-type-active-item {
|
||||
background-color: #FE019B;
|
||||
}
|
||||
|
||||
.pet-type-triangle {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 20rpx solid transparent;
|
||||
border-right: 20rpx solid transparent;
|
||||
border-top: 20rpx solid transparent;
|
||||
}
|
||||
|
||||
.pet-type-active-triangle {
|
||||
border-top: 20rpx solid #FE019B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reservation-info-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.content-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 36rpx 36rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.price-view {
|
||||
margin-top: 36rpx;
|
||||
width: 100%;
|
||||
height: 164rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #FFEEF6;
|
||||
border-radius: 40rpx;
|
||||
|
||||
.tip-img {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
margin-left: 16rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 40rpx;
|
||||
margin-bottom: 90rpx;
|
||||
margin-left: 5%;
|
||||
width: 90%;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $app_color_main;
|
||||
box-shadow: 12rpx 16rpx 40rpx 0 rgba(240, 135, 228, 0.52);
|
||||
}
|
||||
}
|
||||
|
||||
.ad-container {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.ad-view {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
2329
src/pageHome/reservation/payment-confirm-page.vue
Normal file
2329
src/pageHome/reservation/payment-confirm-page.vue
Normal file
File diff suppressed because it is too large
Load Diff
434
src/pageHome/selectAddress/index.vue
Normal file
434
src/pageHome/selectAddress/index.vue
Normal file
@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<view class="address-container" :class="{ 'empty-background': addressList.length === 0 && !isLoading }">
|
||||
<!-- 地址列表 -->
|
||||
<scroll-view class="address-list" scroll-y>
|
||||
<view v-if="!isLoading && addressList.length">
|
||||
<view v-for="(item, index) in addressList" :key="item.id" class="address-item"
|
||||
@click="selectAddress(item)">
|
||||
<!-- 地址信息 -->
|
||||
<view class="address-info">
|
||||
<view class="address-header">
|
||||
<text class="address-name">{{ item.recipient_name }}</text>
|
||||
<text class="address-phone">{{ item.phone }}</text>
|
||||
<view v-if="item.is_default" class="default-tag">默认</view>
|
||||
</view>
|
||||
<view class="address-detail">{{ getAddressText(item) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="address-actions">
|
||||
<!-- 左侧:默认地址开关 -->
|
||||
<view class="action-left" @click.stop="() => { }">
|
||||
<switch class="custom-switch" v-model="item.is_default"
|
||||
@change="(val) => handleSwitchChange(val, item, index)" color="#FF19A0" :checked="item.is_default"></switch>
|
||||
|
||||
<text class="default-text">设为默认地址</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧:编辑和删除 -->
|
||||
<view class="action-right">
|
||||
<view class="action-btn" @click.stop="goToEditAddress(item)">
|
||||
<image class="img" :src="`${imgPrefix}mall-pen.png`"></image>
|
||||
<text class="action-text">编辑</text>
|
||||
</view>
|
||||
<view class="action-btn" @click.stop="deleteAddress(item, index)">
|
||||
<image class="img" :src="`${imgPrefix}mall-remove.png`"></image>
|
||||
<text class="action-text">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view v-if="isLoading" class="loading-view">
|
||||
<uni-load-more status="loading" :show-text="false" />
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="addressList.length === 0 && !isLoading" class="empty-state">
|
||||
<image :src="`${imgPrefix}norAddress.png`" class="empty-img"></image>
|
||||
<view class="empty-text">
|
||||
暂无地址信息
|
||||
</view>
|
||||
<view class="empty-btn" @click="gotoAddAddress">
|
||||
去添加地址
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部添加按钮 -->
|
||||
<view class="footer" v-if="addressList.length > 0">
|
||||
<view class="footerBtn" @click="gotoAddAddress">新增地址</view>
|
||||
<view class="bottom-safe-area"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getAddressList,
|
||||
deleteAddress,
|
||||
updateAddress
|
||||
} from "@/api/address";
|
||||
import {
|
||||
imgPrefix
|
||||
} from "@/utils/common.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imgPrefix,
|
||||
addressList: [],
|
||||
isLoading: true,
|
||||
typeSelect: false, // 从订单页跳转过来选择地址的标识
|
||||
selectAddressId: "", // 选中的地址id
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
userInfo() {
|
||||
return this.$store.state?.user?.userInfo || {};
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 如果有传入的选中地址,可以在这里处理
|
||||
const { typeSelect, addressId } = options;
|
||||
this.typeSelect = typeSelect;
|
||||
this.selectAddressId = addressId;
|
||||
},
|
||||
onShow() {
|
||||
// 从编辑页面返回时刷新列表
|
||||
this.isLoading = true;
|
||||
this.getData();
|
||||
},
|
||||
methods: {
|
||||
selectAddress(address) {
|
||||
// 通过 eventChannel 传递数据回原页面
|
||||
const eventChannel = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (eventChannel) {
|
||||
eventChannel.emit('changeAddress', address);
|
||||
}
|
||||
// 同时使用 uni.$emit 方式,兼容原有监听方式
|
||||
uni.$emit('selectAddress', address);
|
||||
uni.navigateBack();
|
||||
},
|
||||
handleSwitchChange(val, address) {
|
||||
// 如果打开开关,设置该地址为默认地址,并取消其他地址的默认状态
|
||||
this.setDefaultAddressRequest(val.detail.value,address);
|
||||
},
|
||||
setDefaultAddressRequest(val,address) {
|
||||
const userInfo = this.userInfo;
|
||||
const addressParam = {
|
||||
user_id: userInfo.userID,
|
||||
id: address.id || address.address_id,
|
||||
recipient_name: address.recipient_name || address.name,
|
||||
phone: address.phone,
|
||||
is_default: val, // 设置为默认地址
|
||||
province: address.province || address.sheng,
|
||||
province_id: address.province_id || address.provinceId || address.sheng_id,
|
||||
city: address.city || address.shi,
|
||||
city_id: address.city_id || address.cityId || address.shi_id,
|
||||
district: address.district || address.area || address.qu,
|
||||
district_id: address.district_id || address.areaId || address.qu_id,
|
||||
full_address: address.full_address || address.address,
|
||||
region_id: 1
|
||||
};
|
||||
|
||||
uni.showLoading({ title: "处理中..." });
|
||||
updateAddress(addressParam).then(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '设置成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 刷新列表
|
||||
this.isLoading = true;
|
||||
this.getData();
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
console.error('设置默认地址失败:', err);
|
||||
// 恢复开关状态
|
||||
address.is_default = !val;
|
||||
uni.showToast({
|
||||
title: err?.msg || err?.message || '设置失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
deleteAddress(address, index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该地址吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 调用删除接口
|
||||
this.deleteAddressRequest(address.id, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteAddressRequest(addressId, index) {
|
||||
uni.showLoading({
|
||||
title: '删除中...'
|
||||
});
|
||||
deleteAddress({
|
||||
user_id: this.userInfo.userID,
|
||||
id: addressId
|
||||
}).then(() => {
|
||||
uni.hideLoading();
|
||||
// 删除成功后,从列表中移除
|
||||
this.addressList.splice(index, 1);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err?.message || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
goToEditAddress(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?id=${item?.id || ""}`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
gotoAddAddress() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/address/edit?isAdd=1`,
|
||||
events: {
|
||||
refreshAddress: () => {
|
||||
this.isLoading = true;
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
getData() {
|
||||
getAddressList({
|
||||
user_id: this.userInfo.userID
|
||||
}).then((res) => {
|
||||
let list = (res?.data || []).map(v => ({
|
||||
...v,
|
||||
is_default: !!v.is_default, // 确保默认地址开关状态正确
|
||||
}));
|
||||
this.addressList = list;
|
||||
this.isLoading = false;
|
||||
}).catch(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
},
|
||||
getAddressText(item) {
|
||||
// 组合地址信息:省市区 + 详细地址
|
||||
const region = [item.province, item.city, item.district].filter(Boolean).join('');
|
||||
return region ? `${region}${item.full_address}` : item.full_address;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.address-container {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: #F7F8FA;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.empty-background {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.address-list {
|
||||
flex: 1;
|
||||
padding: 20rpx 20rpx 240rpx;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-view {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.address-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.address-info {
|
||||
flex: 1;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.address-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.address-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.address-phone {
|
||||
font-size: 28rpx;
|
||||
color: #666262;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.default-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #ffecf3;
|
||||
color: #FF19A0;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.address-detail {
|
||||
font-size: 28rpx;
|
||||
color: #666262;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.address-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #F0F0F0;
|
||||
}
|
||||
|
||||
.action-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
.default-text {
|
||||
font-size: 24rpx;
|
||||
color: #666262;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
|
||||
.action-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.action-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
|
||||
.empty-img {
|
||||
width: 320rpx;
|
||||
height: 320rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #9B939A;
|
||||
}
|
||||
|
||||
.empty-btn {
|
||||
background-color: #ff19a0;
|
||||
color: #fff;
|
||||
width: calc(100vw - 208rpx);
|
||||
margin: auto;
|
||||
margin-top: 40rpx;
|
||||
padding: 32rpx 0rpx;
|
||||
text-align: center;
|
||||
border-radius: 80rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fff;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding: 12rpx 24rpx 0;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
.footerBtn {
|
||||
background-color: #ff19a0;
|
||||
color: #fff;
|
||||
width: calc(100vw - 48rpx);
|
||||
margin: auto;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 32rpx 0rpx;
|
||||
text-align: center;
|
||||
border-radius: 80rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.bottom-safe-area {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.custom-switch {
|
||||
transform: scale(0.7)
|
||||
}
|
||||
</style>
|
||||
482
src/pageHome/selectPet/index.vue
Normal file
482
src/pageHome/selectPet/index.vue
Normal file
@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<view :class="petList.length === 0 ? 'chosePetContainer' : 'listContainer'">
|
||||
<!-- 无宠物占位 -->
|
||||
<view class="notPetWrapper" v-if="!isLoading && petList.length === 0">
|
||||
<image :src="`${imgPrefix}record-placeholder.png`" mode="widthFix" class="notPetImg"></image>
|
||||
<view class="tips">未添加过宠物</view>
|
||||
<view class="addPetBtn" @click="gotoAddPet">去添加宠物</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view class="petListview" v-else>
|
||||
<view v-if="isLoading" class="loading-view">
|
||||
<uni-load-more status="loading" :show-text="false" />
|
||||
</view>
|
||||
<scroll-view v-else class="pet-scroll" scroll-y :style="{ height: scrollViewHeight }">
|
||||
<view v-for="(item, index) in petList" :key="item.id" @click="changePet(item)"
|
||||
:class="['petItemView', { petItemViewActive: selectPetInfo.id === item.id, itemMarginTop: index > 0 }]">
|
||||
<view class="leftView">
|
||||
<image class="petImg" :src="item.avatar || `${imgPrefix}record_avator.png`" mode="aspectFill" />
|
||||
<view class="petContent">
|
||||
<view class="petInfoView">
|
||||
<view class="petName fs-28">{{ item.name }}</view>
|
||||
<image class="petGenderImg "
|
||||
:src="`${imgPrefix}${item.gender == 'male' ? 'record-maleImg.png' : 'record-femaleImg.png'}`"></image>
|
||||
</view>
|
||||
<view class="petVariety fs-24">{{ item.breed_name || '' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rightView">
|
||||
<view class="editView" @click.stop="toDetails(item)">
|
||||
<image class="img" :src="`${imgPrefix}mall-pen.png`"></image>
|
||||
<view class="fs-24" style="color: #3d3d3d;">编辑</view>
|
||||
</view>
|
||||
<!-- <view class="removeView" @click.stop="deletePet(item)">
|
||||
<image class="img" :src="`${imgPrefix}mall-remove.png`"></image>
|
||||
<view class="fs-24" style="color: #3d3d3d;">删除</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="footer" v-if="petList.length !== 0">
|
||||
<view class="footerBtn" @click="gotoAddPet">添加宠物</view>
|
||||
<view class="bottom-safe-area"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPetList, switchPetArchive } from "@/api/common";
|
||||
import { deletePet } from "@/api/record";
|
||||
import appConfig from '@/constants/app.config';
|
||||
import { imgPrefix } from '@/utils/common.js';
|
||||
import { PET_TYPE_CAT, PET_TYPE_DOG } from '@/constants/app.business';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
petList: [],
|
||||
appConfig,
|
||||
imgPrefix,
|
||||
petType: 0,
|
||||
selectPetInfo: {},
|
||||
from: '', // 来源标记,用于判断是否从宠物档案页面进入
|
||||
serviceType: '', // 服务类型:feeding(上门喂宠) 或 walking(上门遛宠)
|
||||
scrollViewHeight: 'calc(100vh - 200rpx)' // 默认高度
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
// 从页面参数获取 petType 和 selectPetInfo
|
||||
if (options.petType) {
|
||||
this.petType = parseInt(options.petType);
|
||||
}
|
||||
if (options.selectPetInfo) {
|
||||
try {
|
||||
this.selectPetInfo = JSON.parse(decodeURIComponent(options.selectPetInfo));
|
||||
} catch (e) {
|
||||
console.error('解析 selectPetInfo 失败:', e);
|
||||
}
|
||||
}
|
||||
// 获取来源标记
|
||||
if (options.from) {
|
||||
this.from = options.from;
|
||||
}
|
||||
// 获取服务类型
|
||||
if (options.serviceType) {
|
||||
this.serviceType = options.serviceType;
|
||||
}
|
||||
// 计算 scroll-view 的精确高度
|
||||
this.calculateScrollViewHeight();
|
||||
this.getData();
|
||||
},
|
||||
onShow() {
|
||||
// 重新计算 scroll-view 高度(底部按钮区域可能会根据宠物列表显示/隐藏)
|
||||
this.calculateScrollViewHeight();
|
||||
|
||||
// 页面显示时刷新列表(从编辑页面返回时会触发)
|
||||
// 先移除监听,避免重复注册
|
||||
uni.$off('refreshPetList');
|
||||
// 使用 uni.$on 监听全局刷新事件
|
||||
uni.$on('refreshPetList', () => {
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
onUnload() {
|
||||
// 页面卸载时移除事件监听
|
||||
uni.$off('refreshPetList');
|
||||
},
|
||||
computed: {
|
||||
userInfo() {
|
||||
return this.$store.state?.user?.userInfo || {};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
calculateScrollViewHeight() {
|
||||
this.$nextTick(() => {
|
||||
try {
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const windowHeight = systemInfo.windowHeight; // 窗口高度(px)
|
||||
|
||||
// 使用查询选择器获取 footer 的实际高度
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('.footer').boundingClientRect((rect) => {
|
||||
if (rect) {
|
||||
// rect.height 是 px 单位,需要转换为 rpx
|
||||
// 屏幕宽度通常是 750rpx,对应 windowWidth px
|
||||
const pixelRatio = 750 / systemInfo.windowWidth; // rpx 与 px 的转换比例
|
||||
const footerHeightRpx = rect.height * pixelRatio;
|
||||
|
||||
// 窗口高度转换为 rpx
|
||||
const windowHeightRpx = windowHeight * pixelRatio;
|
||||
|
||||
// 计算 scroll-view 高度(rpx)
|
||||
// 减去 footer 高度和 petListview 的 padding-top (20rpx)
|
||||
const scrollViewHeightRpx = windowHeightRpx - footerHeightRpx - 40;
|
||||
|
||||
// 设置高度
|
||||
this.scrollViewHeight = `${scrollViewHeightRpx}rpx`;
|
||||
} else {
|
||||
// 如果底部按钮区域不存在(无宠物时),使用全屏高度减去 padding
|
||||
const pixelRatio = 750 / systemInfo.windowWidth;
|
||||
const windowHeightRpx = windowHeight * pixelRatio;
|
||||
this.scrollViewHeight = `${windowHeightRpx - 40}rpx`;
|
||||
}
|
||||
}).exec();
|
||||
} catch (e) {
|
||||
console.error('计算 scroll-view 高度失败:', e);
|
||||
// 失败时使用默认值
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const pixelRatio = 750 / systemInfo.windowWidth;
|
||||
const windowHeightRpx = systemInfo.windowHeight * pixelRatio;
|
||||
// 默认减去 200rpx(底部按钮区域 + 安全区域)
|
||||
this.scrollViewHeight = `${windowHeightRpx - 200}rpx`;
|
||||
}
|
||||
});
|
||||
},
|
||||
changePet(pet) {
|
||||
// 如果是从宠物档案页面进入的,需要调用切换接口
|
||||
if (this.from === 'petProfile') {
|
||||
uni.showLoading({
|
||||
title: '切换中...',
|
||||
mask: true
|
||||
});
|
||||
switchPetArchive({ pet_id: pet.id })
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code === 0 || res.result === 'success') {
|
||||
uni.showToast({
|
||||
title: '切换成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 通过 eventChannel 通知上一页刷新
|
||||
const eventChannel = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (eventChannel) {
|
||||
eventChannel.emit('switchPetSuccess');
|
||||
}
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 200);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || res.message || '切换失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err?.msg || err?.message || err || '切换失败,请稍后重试',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 如果有服务类型,先验证宠物类型
|
||||
if (this.serviceType) {
|
||||
const petType = pet.type || pet.pet_type;
|
||||
let isValid = true;
|
||||
|
||||
if (this.serviceType === 'feeding') {
|
||||
// 上门喂宠只能选择猫
|
||||
if (Number(petType) !== PET_TYPE_CAT) {
|
||||
uni.showToast({
|
||||
title: '上门喂宠服务只能选择猫',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
isValid = false;
|
||||
}
|
||||
} else if (this.serviceType === 'walking') {
|
||||
// 上门遛宠只能选择狗
|
||||
if (Number(petType) !== PET_TYPE_DOG) {
|
||||
uni.showToast({
|
||||
title: '上门遛宠服务只能选择狗',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果验证失败,不返回
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 通过 eventChannel 返回数据,避免直接访问上一页实例
|
||||
const eventChannel = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (eventChannel) {
|
||||
eventChannel.emit('changePet', pet);
|
||||
}
|
||||
// 验证通过后,延迟返回,让提示框有时间显示(如果是成功的情况)
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
getData() {
|
||||
getPetList(this.userInfo.userID).then((res) => {
|
||||
let list = res?.data || [];
|
||||
this.petList = list;
|
||||
this.isLoading = false;
|
||||
// 数据更新后重新计算 scroll-view 高度(底部按钮区域会根据 petList.length 显示/隐藏)
|
||||
this.calculateScrollViewHeight();
|
||||
}).catch(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
},
|
||||
gotoAddPet() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/record/edit?type=${this.petType}&typeId=aaa`,
|
||||
events: {
|
||||
addPetSuccess: () => {
|
||||
// 不需要在这里调用 getData,因为 record/edit.vue 已经触发了 refreshPetList 全局事件
|
||||
// this.isLoading = true;
|
||||
// this.getData();
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
toDetails(data) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/client/record/edit?id=${data?.id || ""
|
||||
}&typeId=bbb`,
|
||||
events: {
|
||||
addPetSuccess: () => {
|
||||
// 不需要在这里调用 getData,因为 record/edit.vue 已经触发了 refreshPetList 全局事件
|
||||
// this.getData();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
deletePet(item) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该宠物吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({
|
||||
title: '删除中...'
|
||||
});
|
||||
deletePet(item.id, this.userInfo.userID)
|
||||
.then(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 刷新列表
|
||||
this.getData();
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: err?.message || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.listContainer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #ffecf3;
|
||||
}
|
||||
|
||||
.chosePetContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.notPetWrapper {
|
||||
text-align: center;
|
||||
|
||||
.notPetImg {
|
||||
width: 380rpx;
|
||||
height: 320rpx;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 22rpx;
|
||||
margin-bottom: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #9B939A;
|
||||
}
|
||||
|
||||
.addPetBtn {
|
||||
padding: 32rpx 0rpx;
|
||||
width: calc(100vw - 208rpx);
|
||||
background-color: #ff19a0;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 80rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.petListview {
|
||||
padding: 20rpx 0 0;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
|
||||
.loading-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
|
||||
.pet-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.petItemView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
width: calc(100vw - 40rpx);
|
||||
margin: auto;
|
||||
padding: 24rpx 20rpx;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
|
||||
.leftView {
|
||||
display: flex;
|
||||
|
||||
.petImg {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.petContent {
|
||||
margin-left: 16rpx;
|
||||
|
||||
.petInfoView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.petName {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.petGenderImg {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.petVariety {
|
||||
font-size: 24rpx;
|
||||
color: #666262;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
.rightView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666262;
|
||||
|
||||
.img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.editView {
|
||||
// margin-right: 50rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.removeView {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.petItemViewActive {
|
||||
border: 1px solid #ff19a0;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fff;
|
||||
border-radius: 32rpx 32rpx 0px 0px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.footerBtn {
|
||||
color: #fff;
|
||||
background-color: #FF19A0;
|
||||
width: calc(100% - 48rpx);
|
||||
margin: auto;
|
||||
margin-bottom: 24rpx;
|
||||
margin-top: 12rpx;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
padding: 32rpx 0rpx;
|
||||
}
|
||||
|
||||
.bottom-safe-area {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
.itemMarginTop {
|
||||
margin-top: 16rpx !important;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user