This commit is contained in:
lanzhihui 2026-04-07 14:53:58 +08:00
commit 8979172976
132 changed files with 55990 additions and 0 deletions

3
.browserslistrc Normal file
View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

23
.env.development Normal file
View File

@ -0,0 +1,23 @@
# 只在开发模式中被载入
# 网站前缀
VITE_BASE_URL = /
VITE__APP_VERSION__ = ''
# base api
VITE_BASE_API_VERSION = '1.0.001'
VITE_BASE_API_go = 'https://health.ufutx.com/go/api'
VITE_BASE_API = '//health.ufutx.net/api'
# 是否兼容旧的浏览器
VITE_LEGACY = false
# mock api
VITE_MOCK_API = '/mock-api/'
# 是否删除console
VITE_DROP_CONSOLE = false

22
.env.production Normal file
View File

@ -0,0 +1,22 @@
# 只在开发模式中被载入
# 网站前缀
VITE_BASE_URL = /h5/
VITE__APP_VERSION__ = ''
# base api
VITE_BASE_API_VERSION = '1.0.001'
VITE_BASE_API_go = '//health.ufutx.com/go/api'
VITE_BASE_API = '//health.ufutx.com/api'
# 是否兼容旧的浏览器
VITE_LEGACY = false
# mock api
VITE_MOCK_API = '/mock-api/'
# 是否删除console
VITE_DROP_CONSOLE = true

16
.eslintignore Normal file
View File

@ -0,0 +1,16 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
/src/mock
Dockerfile

108
.eslintrc.js Normal file
View File

@ -0,0 +1,108 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
plugins: ['@typescript-eslint', 'prettier', 'import'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended', 'prettier'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue', '*d.ts'],
rules: {
'prefer-const': 0,
'no-undef': 'off',
},
},
],
rules: {
// js/ts
// 'no-console': ['warn', { allow: ['error'] }],
'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'],
camelcase: ['error', { properties: 'never' }],
'no-var': 'error',
'no-empty': ['error', { allowEmptyCatch: true }],
'no-void': 'error',
'prefer-const': ['warn', { destructuring: 'all', ignoreReadBeforeAssign: true }],
'prefer-template': 'error',
'object-shorthand': ['error', 'always', { ignoreConstructors: false, avoidQuotes: true }],
'block-scoped-var': 'error',
'no-constant-condition': ['error', { checkLoops: false }],
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
// '@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
// vue
'vue/no-v-html': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/multi-word-component-names': 'off',
// prettier
'prettier/prettier': 'error',
// import
'import/first': 'error',
'import/no-duplicates': 'error',
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
pathGroups: [
{
pattern: 'vue',
group: 'external',
position: 'before',
},
{
pattern: '@vue/**',
group: 'external',
position: 'before',
},
{
pattern: 'ant-design-vue',
group: 'internal',
},
],
pathGroupsExcludedImportTypes: ['type'],
},
],
},
};

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
* text=auto eol=lf
*.ts linguist-detectable=false
*.css linguist-detectable=false
*.scss linguist-detectable=false
*.js linguist-detectable=true
*.vue linguist-detectable=true

57
.github/workflows/gh-pages.yml vendored Normal file
View File

@ -0,0 +1,57 @@
name: syncToGitee
env:
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
on:
push:
branches: [main]
jobs:
repo-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js v14.x
uses: actions/setup-node@v1
with:
node-version: '14.x'
- name: Install
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: ./dist
personal_token: ${{ secrets.PERSONAL_TOKEN }}
commit_message: Update ghPages
force_orphan: true
- name: Mirror the Github organization repos to Gitee.
uses: Yikun/hub-mirror-action@master
with:
src: 'github/buqiyuan'
dst: 'gitee/buqiyuan'
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
dst_token: ${{ secrets.GITEE_TOKEN }}
static_list: 'vite-vue3-h5'
force_update: true
debug: true
- name: Build Gitee Pages
uses: yanglbme/gitee-pages-action@main
with:
# 注意替换为你的 Gitee 用户名
gitee-username: buqiyuan
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
gitee-password: ${{ secrets.GITEE_PASSWORD }}
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
gitee-repo: buqiyuan/vite-vue3-h5
# 是否强制使用 HTTPS
https: false
# 要部署的分支,默认是 master若是其他分支则需要指定指定的分支必须存在
branch: gh-pages

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
node_modules
components.d.ts
.DS_Store
dist
.npmrc
.cache
public/version.json
tests/server/static
tests/server/static/upload
.local
# local env files
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*pnpm-debug.log*
# Editor directories and files
.idea
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

11
.prettierignore Normal file
View File

@ -0,0 +1,11 @@
/dist/*
.local
.output.js
/node_modules/**
.yarnrc
**/*.svg
**/*.sh
/public/*

2
.stylelintignore Normal file
View File

@ -0,0 +1,2 @@
/dist/*
/public/*

13
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"recommendations": [
"vue.volar",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"esbenp.prettier-vscode",
"mrmlnc.vscode-less",
"lokalise.i18n-ally",
"antfu.iconify",
"mikestead.dotenv",
"heybourn.headwind"
]
}

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:3100",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true
}
]
}

104
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,104 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"volar.tsPlugin": true,
"volar.tsPluginStatus": false,
"npm.packageManager": "yarn",
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
"**/*.log": true,
"**/*.log*": true,
"**/bower_components": true,
"**/dist": true,
"**/elehukouben": true,
"**/.git": true,
"**/.gitignore": true,
"**/.svn": true,
"**/.DS_Store": true,
"**/.idea": true,
"**/.vscode": false,
"**/yarn.lock": true,
"**/tmp": true,
"out": true,
"dist": true,
"node_modules": true,
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.vscode/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true,
"**/yarn.lock": true
},
"stylelint.enable": true,
"stylelint.packageManager": "yarn",
"path-intellisense.mappings": {
"@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
}
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"]
}

8
.yarnrc Normal file
View File

@ -0,0 +1,8 @@
registry "https://registry.npmmirror.com"
sass_binary_site "https://npmmirror.com/mirrors/node-sass/"
phantomjs_cdnurl "http://cnpmjs.org/downloads"
electron_mirror "https://npmmirror.com/mirrors/electron/"
sqlite3_binary_host_mirror "https://foxgis.oss-cn-shanghai.aliyuncs.com/"
profiler_binary_host_mirror "https://npmmirror.com/mirrors/node-inspector/"
chromedriver_cdnurl "https://cdn.npm.taobao.org/dist/chromedriver"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 bqy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

39
README.md Normal file
View File

@ -0,0 +1,39 @@
## 移动端布局方案
移动端 vant3.x + vue3.x + vite2.x + ts.4.x + REM 布局 + Viewport (VW) 布局的实例运用
## 在线预览
http://buqiyuan.gitee.io/vite-vue3-h5
提供三个布局方案
**1. REM 布局**
使用 js 动态设置 html 的 font-sizecss 使用 rem 单位,文本大小可选择使用 px
js 设置 viewport 的 scale 以支持高清设备的 1px
可设置页面最大最小宽度
**2. VW 布局**
css 使用 vw 单位,文本大小可选择使用 px
使用 transform 以支持高清设备的边框 1px包括圆角使用 @mixin `./vw/scss/_border.scss`
可设置容器固定纵横比,不可设置页面最大最小宽度
**3. REM + VW 布局**
html 的 font-size 使用 vw 单位css 使用 rem 单位,文本大小可选择使用 px
使用 transform 以支持高清设备的边框 1px包括圆角使用 @mixin `./vw-rem/scss/_border.scss`
可设置容器固定纵横比,可设置页面最大最小宽度
## 使用
1. yarn dev
2. 业务代码中样式的调用方式可参考 `./rem/scss/rem.scss``./vw/scss/vw.scss``./vw-rem/scss/vw-rem.scss` 三个文件;可在 html 文件相应位置配置 `data-content-max` 属性来限制容器最大最小宽

8
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
interface Window {
WeixinJSBridge: any;
}
}
export {};

13
commitlint.config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [2, 'always', ['feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release']],
},
};

25
index.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html data-content-max>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="./src/styles/common.scss" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>友福健康</title>
</head>
<style>
body {
overflow: hidden;
transform: initial;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
<body data-content-max>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,20 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
const modules = import.meta.globEager('./**/*.ts');
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {
console.log('mockModules', mockModules);
createProdMockServer(mockModules);
}

56
mock/_util.ts Normal file
View File

@ -0,0 +1,56 @@
// Interface data format used to return a unified format
export function resultSuccess<T = Recordable>(data: T, { message = 'ok' } = {}) {
return {
code: 200,
data,
message,
type: 'success',
};
}
export function resultPageSuccess<T = any>(page: number, pageSize: number, list: T[], { message = 'ok' } = {}) {
const pageData = pagination(page, pageSize, list);
return {
...resultSuccess({
list: pageData,
pagination: {
page: ~~page,
size: ~~pageSize,
total: list.length,
},
}),
message,
};
}
export function resultError(message = 'Request failed', { code = -1, result = null } = {}) {
return {
code,
result,
message,
type: 'error',
};
}
export function pagination<T = any>(page: number, pageSize: number, array: T[]): T[] {
const offset = (page - 1) * Number(pageSize);
const ret = offset + Number(pageSize) >= array.length ? array.slice(offset, array.length) : array.slice(offset, offset + Number(pageSize));
return ret;
}
export interface requestParams {
method: string;
body: any;
headers?: { authorization?: string };
query: any;
}
/**
* @description request数据中获取token
*
*/
export function getRequestToken({ headers }: requestParams): string | undefined {
return headers?.authorization;
}

12453
mock/log/_cateList.json Normal file

File diff suppressed because it is too large Load Diff

17
mock/log/index.ts Normal file
View File

@ -0,0 +1,17 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
import data from './_cateList.json';
export default [
{
url: '/mock-api/category/list',
timeout: 0,
method: 'get',
response: ({ query }) => {
console.log('query', query);
return resultSuccess(data);
},
},
] as MockMethod[];

10467
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

123
package.json Normal file
View File

@ -0,0 +1,123 @@
{
"name": "vite-vue3-h5",
"version": "0.0.0",
"homepage": "git@buqiyuan.github.io/vite-vue3-h5",
"scripts": {
"serve": "npm run dev",
"dev": "vite --host=0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"deploy": "gh-pages -d dist",
"format": "prettier --write ./src",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged",
"postversion": "git push && git push origin --tags",
"reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run dev",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
"test:gzip": "npx http-server dist --cors --gzip -c-1",
"test:br": "npx http-server dist --cors --brotli -c-1"
},
"dependencies": {
"@vant/area-data": "^1.5.1",
"@vant/touch-emulator": "^1.3.2",
"@vueuse/core": "^8.7.5",
"axios": "^0.27.2",
"bankcardinfo": "^2.0.6",
"dayjs": "^1.11.3",
"html2canvas": "^1.4.1",
"js-md5": "^0.7.3",
"jsbarcode": "^3.11.6",
"lodash-es": "^4.17.21",
"pinia": "^2.0.13",
"qrcode": "^1.5.3",
"qs": "^6.11.0",
"vant": "^4.6.0",
"vconsole": "^3.14.6",
"vue": "^3.2.37",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.0.16",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3",
"@types/lodash": "^4.14.182",
"@types/node": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.30.0",
"@vitejs/plugin-legacy": "^1.8.2",
"@vitejs/plugin-vue": "^2.3.3",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/compiler-sfc": "3.2.33",
"@vue/eslint-config-typescript": "^11.0.0",
"commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.2.2",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.1.0",
"eslint-plugin-vue": "^9.1.1",
"gh-pages": "^4.0.0",
"lint-staged": "^13.0.3",
"mockjs": "^1.1.0",
"postcss": "^8.4.14",
"postcss-html": "^1.4.1",
"postcss-scss": "^4.0.4",
"prettier": "^2.7.1",
"sass": "^1.53.0",
"stylelint": "^14.9.1",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^8.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^26.0.0",
"stylelint-order": "^5.0.0",
"stylelint-scss": "^4.2.0",
"swiper": "^8.0.3",
"typescript": "^4.7.4",
"unplugin-vue-components": "^0.20.1",
"unplugin-vue-define-options": "^0.6.1",
"vite": "^2.9.13",
"vite-plugin-checker": "^0.4.6",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-style-import": "^1.4.1",
"vue-eslint-parser": "^9.0.3",
"vue-tsc": "^0.38.2"
},
"license": "MIT",
"engines": {
"node": "^12 || >=14"
},
"lint-staged": {
"./src/**/*.{js,jsx,ts,tsx,vue}": [
"eslint --fix",
"git add ."
],
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write"
],
"package.json": [
"prettier --write"
],
"*.vue": [
"eslint --fix",
"prettier --write",
"stylelint --fix"
],
"*.{scss,less,styl,html}": [
"stylelint --fix",
"prettier --write"
],
"*.md": [
"prettier --write"
]
}
}

11
prettier.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
printWidth: 800,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
useTabs: false,
};

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

151
src/App.vue Normal file
View File

@ -0,0 +1,151 @@
<template>
<div class="ui-app">
<router-view #="{ Component }">
<keep-alive :include="include">
<component :is="Component"></component>
</keep-alive>
</router-view>
<div v-if="showHome">
<van-floating-bubble class="ui-bubble ui-home-bubble" axis="xy" magnetic="x" :gap="20">
<template #default>
<img class="ui-mgt-entrance" src="./assets/backHome.png" alt="" @click="jumpHome" />
</template>
</van-floating-bubble>
</div>
<div v-if="showContact">
<van-floating-bubble class="ui-bubble ui-service-bubble" axis="xy" magnetic="x" :gap="20">
<template #default>
<img class="ui-mgt-entrance" src="./assets/contact.png" alt="" @click="contactService" />
</template>
</van-floating-bubble>
</div>
<van-tabbar v-show="isTabBarDemo" route active-color="#2a63b2" inactive-color="#333333">
<template v-for="item in main" :key="item.name">
<van-tabbar-item :to="item.path" replace>
<span>{{ item.meta?.title }}</span>
<template #icon="props">
<img :src="props.active ? item.meta?.active : item.meta?.inactive" />
</template>
</van-tabbar-item>
</template>
</van-tabbar>
<van-tabbar v-show="isTabBarDemoV2" route active-color="#2a63b2" inactive-color="#333333">
<template v-for="item in mainV2" :key="item.name">
<van-tabbar-item :to="item.path" replace>
<span>{{ item.meta?.title }}</span>
<template #icon="props">
<img :src="props.active ? item.meta?.active : item.meta?.inactive" />
</template>
</van-tabbar-item>
</template>
</van-tabbar>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { main } from '@/router/modules/main';
import { mainV2 } from '@/router/modules/mainV2';
import { contactService } from '@/plugins/public';
import router from '@/router';
//
const include = ref<any[]>(['AgentHome', 'TestControler', 'User', 'ShopOrderList', 'LeaseShopOrderList', 'LeaseShopDetail', 'appShopDetail']);
//
const notArrContact = ref<any[]>(['agentHome', 'testControler', 'user', 'appShopDetail', 'appShopOrderConfirm', 'appMyAddress', 'appAddAddress', 'longPicture', 'appShopAgreement', 'appUserLogin', 'dietaryHabitTest']);
//
const notArrHome = ref<any[]>(['shopRegister', 'agentHome', 'shopOrderConfirm', 'testControler', 'user', 'appShopDetail', 'appShopOrderConfirm', 'appMyAddress', 'appAddAddress', 'longPicture', 'appShopAgreement', 'appUserLogin', 'dietaryHabitTest']);
// tabBar
const isTabBarDemo = ref(false);
const isTabBarDemoV2 = ref(false);
const showContact = ref(false);
const showHome = ref(false);
const route = ref<any>(null);
watch(
() => router.currentRoute.value.path,
(newRoute) => {
//
if (notArrContact.value.includes(newRoute.split('/')[1])) {
showContact.value = false;
} else {
showContact.value = true;
}
//
if (notArrHome.value.includes(newRoute.split('/')[1])) {
showHome.value = false;
} else {
showHome.value = true;
}
},
);
watch(
() => router.currentRoute.value,
(e) => {
if (e.name === 'agentHome' || e.name === 'testControler' || e.name === 'user') {
isTabBarDemo.value = true;
} else {
isTabBarDemo.value = false;
}
console.log(e, '888888888');
if (e.name === 'agentHomeV2' || e.name === 'userV2') {
isTabBarDemoV2.value = true;
} else {
isTabBarDemoV2.value = false;
}
},
{ immediate: true, deep: true },
);
//
const jumpHome = () => {
router.replace({
name: 'agentHome',
});
};
onMounted(() => {
route.value = router.currentRoute.value;
});
</script>
<style lang="scss">
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
html {
@include root-font-size();
}
.ui-app {
--container-height: 100vh;
height: 100vh;
overflow: auto;
}
.van-pull-refresh {
min-height: 100vh;
overflow: auto;
}
.ui-bubble {
overflow: initial;
background: none;
}
.ui-home-bubble {
top: px2rem(-320);
}
.ui-service-bubble {
top: px2rem(-160);
}
</style>

63
src/api/demo.ts Normal file
View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import requestGo from '@/utils/requestGo';
// 登录
export function getLogin() {
return requestGo({
hideLoading: true,
url: '/h5/v1/auth/login',
method: 'post',
});
}
// 微信、企业微信config配置
export function getWxConfig() {
let data = { url: window.location.href.split('#')[0] };
return request({
hideLoading: true,
data,
url: '/h5/work/js/sdk/config/v2',
method: 'post',
});
}
// 授权失败后传递cookie和session给后端
export function postCookie(e, i, j) {
let data = `openid=${j}${JSON.stringify(e)}${i}`;
return request({
hideLoading: true,
url: `/h5/report/error/data?data=${data}`,
method: 'post',
});
}
// 阿里云上传配置
export function getALiYun(token) {
let data = token;
return request({
data,
hideLoading: true,
url: 'get/oss/config',
method: 'get',
});
}
// 获取版本号
export function getVersion() {
return request({
hideLoading: true,
url: 'get/version?type=0',
method: 'get',
});
}
// // 企业微信config配置
// export function getAuthTicket() {
// let data = { url: window.location.href.split('#')[0], type: 'app' };
// return weChat({
// hideLoading: true,
// data,
// url: '/h5/work/js/sdk/config',
// method: 'post',
// });
// }

View File

@ -0,0 +1,14 @@
// 用户登录参数
export interface LoginParams {
account: string;
password: string;
}
/**
* @description
*/
export interface LoginResult {
username: string;
avatar: string;
token: string;
}

15
src/api/user.ts Normal file
View File

@ -0,0 +1,15 @@
// import { LoginParams, LoginResult } from './models/userModel';
// import request from '@/utils/request';
// enum Api {
// getUserId = '/base/appLogin', // 获取用户userid
// }
// 根据code获取userid / token
// export function getUserId(data: LoginParams) {
// return request<LoginResult>({
// url: Api.getUserId,
// method: 'post',
// data,
// });
// }

BIN
src/assets/backHome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/contact.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,111 @@
<template>
<div>
<div v-for="(spec, index) in currentSpecs" :key="index" class="ui-sku-list">
<div class="font_28 color3 bold">{{ spec.name }}</div>
<div class="f-fcl f-wrap">
<template v-if="!props.showOnlySelected">
<!-- 正常模式显示所有选项 -->
<div v-for="(item, itemIndex) in spec.spec" :key="itemIndex" class="font_26 f-fbc color3 ui-pt-16" :class="[props.disabled && !isSelected([...path, itemIndex]) ? 'ui-sku-item-disabled' : '']" @click="!props.disabled && selectSpec([...path, itemIndex])">
<div class="ui-sku-item" :class="isSelected([...path, itemIndex]) ? 'ui-sku-item-active colorF' : 'color3'">
{{ item.name }}
</div>
</div>
</template>
<template v-else>
<!-- 只显示最终层级的选中项 -->
<template v-if="isFinalLevel">
<div v-for="(item, itemIndex) in spec.spec" :key="itemIndex" class="font_26 f-fbc color3 ui-pt-16">
<div v-if="isSelected([...path, itemIndex])" class="ui-sku-item ui-sku-item-active colorF">
{{ item.name }}
</div>
</div>
</template>
</template>
</div>
<!-- 子规格处理 - 只在非最终层级且有选中项时继续递归 -->
<template v-if="!isFinalLevel">
<template v-for="(item, itemIndex) in spec.spec" :key="`sub-${itemIndex}`">
<div v-if="hasSubSpec(item) && isSelected([...path, itemIndex])">
<dynamic-sku-selector :specs="getSubSpecs(item)" :disabled="props.disabled" :show-only-selected="props.showOnlySelected" :path="[...path, itemIndex]" :selected-path="selectedPath" @select="!props.disabled && $emit('select', $event)" />
</div>
</template>
</template>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
specs: Array,
path: {
type: Array,
default: () => [],
},
selectedPath: Array,
disabled: {
type: Boolean,
default: false,
},
showOnlySelected: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['select']);
const currentSpecs = computed(() => props.specs);
//
const isFinalLevel = computed(() => {
if (!props.showOnlySelected) return false;
// selectedPath
return props.path.length >= props.selectedPath.length - 1;
});
const selectSpec = (fullPath) => {
console.log(fullPath, 'fullPath===');
emit('select', fullPath);
};
const isSelected = (checkPath) => {
return props.selectedPath.slice(0, checkPath.length).every((val, i) => val === checkPath[i]);
};
const hasSubSpec = (item) => {
return item.sub_spec && item.sub_spec.spec && Array.isArray(item.sub_spec.spec) && item.sub_spec.spec.length > 0;
};
const getSubSpecs = (item) => {
return hasSubSpec(item) ? [item.sub_spec] : [];
};
</script>
<style lang="scss" scoped>
.ui-sku-list {
.ui-sku-item,
.ui-sku-item-active {
padding: px2rem(12) px2rem(24);
background: #f4f4f4;
border-radius: px2rem(8);
margin-right: px2rem(20);
margin-bottom: px2rem(20);
}
.ui-sku-item-active {
background: #2a63b2;
color: #fff;
}
.ui-sku-item-disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<div class="richTextParsing">
<div class="rich_text" @click="imagePreview($event)" v-html="richText"></div>
</div>
</template>
<script lang="ts" setup>
import { showImagePreview } from 'vant';
defineProps({
richText: {
type: String,
default: '',
},
});
const imagePreview = (e) => {
if (e.target.tagName === 'IMG') {
showImagePreview({
images: [e.target.currentSrc],
showIndex: false,
loop: false,
});
}
};
</script>
<style lang="scss" scoped>
.richTextParsing {
padding-bottom: px2rem(60);
}
.rich_text {
::v-deep(img) {
max-width: 100% !important;
max-height: 100% !important;
display: block;
vertical-align: top;
}
}
</style>
<style lang="scss">
.rich_text {
font-size: px2rem(30);
::v-deep(img) {
width: 100% !important;
max-width: 100% !important;
}
}
.van-image-preview {
z-index: 99999999999999 !important;
background: rgba(0, 0, 0, 0.9);
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<div class="ui-sharePoster">
<slot name="sharePoster"></slot>
<!--海报生成后容器-->
<div ref="wrapper" class="ui-wrapper"></div>
<!--展示海报图片-->
<van-popup v-model:show="show" :duration="0.5" :close-on-click-overlay="false">
<div class="ui-posters-box">
<img class="ui-posters-img" :src="posterImg" alt="" />
<div class="font_28 colorF bold text-center ui-mt-20">长按保存图片分享给好友或朋友圈</div>
<img class="ui-cancel-icon" src="https://image.fulllinkai.com/202108/27/78698e898cd570e5e7751c323e4a5cb7.png" alt="" @click="remove" />
</div>
</van-popup>
</div>
</template>
<script lang="ts" setup>
import { defineExpose, nextTick, ref } from 'vue';
import html2canvas from 'html2canvas';
import { closeToast, showLoadingToast } from 'vant';
const show = ref(false); //
const wrapper = ref<any>(null); //
const lock = ref(1); //
const posterImg = ref<any>(''); //
const isShow = () => {
showLoadingToast('');
html2canvas(document.querySelector('#capture') as any, {
backgroundColor: 'rgba(255, 255, 255, 0)',
scale: window.devicePixelRatio,
})
.then((canvas) => {
canvas.style.width = `600px`;
canvas.style.height = `1068px`;
posterImg.value = canvas.toDataURL();
wrapper.value.appendChild(canvas);
lock.value = 0;
show.value = true;
closeToast();
})
.catch(() => {
closeToast();
});
};
const remove = () => {
show.value = false;
if (!lock.value) {
lock.value = 1;
wrapper.value.innerHTML = '';
}
};
//
defineExpose({ isShow, remove });
nextTick(() => {});
</script>
<style lang="scss">
.ui-sharePoster {
.van-popup {
background: rgba(255, 255, 255, 0);
overflow-y: initial;
}
.van-popup--center {
top: 46%;
}
.van-popup--round {
border-radius: px2rem(16);
}
}
</style>
<style lang="scss" scoped>
.ui-wrapper {
position: fixed;
top: px2rem(-100000);
left: 0;
}
.ui-posters-box {
width: px2rem(600);
height: px2rem(1068);
border-radius: px2rem(12);
position: relative;
.ui-posters-img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
}
.ui-cancel-icon {
width: px2rem(60);
height: px2rem(60);
top: px2rem(10);
position: absolute;
right: px2rem(-24);
transform: translate(-50%);
z-index: 9999;
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<div class="ui-sharePoster">
<slot name="sharePoster"></slot>
<!--海报生成后容器-->
<div ref="wrapper" class="ui-wrapper"></div>
<!--展示海报图片-->
<van-popup v-model:show="show" :duration="0.5" :close-on-click-overlay="false">
<div class="ui-posters-box">
<img class="ui-posters-img" :src="posterImg" alt="" />
</div>
</van-popup>
<div v-if="show" class="ui-cancel-box f-fcc font_32 color3" @click="remove">取消</div>
</div>
</template>
<script lang="ts" setup>
import { defineExpose, nextTick, ref } from 'vue';
import html2canvas from 'html2canvas';
import { closeToast, showLoadingToast } from 'vant';
const show = ref(false); //
const wrapper = ref<any>(null); //
const lock = ref(1); //
const posterImg = ref<any>(''); //
const isShow = () => {
showLoadingToast('');
html2canvas(document.querySelector('#capture') as any, {
backgroundColor: 'rgba(255, 255, 255, 0)',
scale: window.devicePixelRatio,
})
.then((canvas) => {
canvas.style.width = `600px`;
canvas.style.height = `1068px`;
posterImg.value = canvas.toDataURL();
wrapper.value.appendChild(canvas);
lock.value = 0;
show.value = true;
closeToast();
})
.catch(() => {
closeToast();
});
};
const remove = () => {
show.value = false;
if (!lock.value) {
lock.value = 1;
wrapper.value.innerHTML = '';
}
};
//
defineExpose({ isShow, remove });
nextTick(() => {});
</script>
<style lang="scss">
.ui-sharePoster {
.van-popup {
background: rgba(255, 255, 255, 0);
overflow-y: initial;
}
.van-popup--center {
top: 46%;
}
.van-popup--round {
border-radius: px2rem(16);
}
}
</style>
<style lang="scss" scoped>
.ui-wrapper {
position: fixed;
top: px2rem(-100000);
left: 0;
}
.ui-posters-box {
width: px2rem(482);
height: px2rem(806);
border-radius: px2rem(16);
position: relative;
.ui-posters-img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
}
.ui-cancel-box {
width: 100vw;
padding: px2rem(36) 0 px2rem(90) 0;
background: #ffffff;
position: fixed;
bottom: 0;
left: 0;
z-index: 10000000;
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<van-uploader :after-read="afterRead" :multiple="multiple" :max-count="maxCount" accept="image/*">
<slot></slot>
</van-uploader>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { showToast } from 'vant';
import requestGo from '@/utils/requestGo';
defineProps({
multiple: {
type: Boolean,
default: false,
},
maxCount: {
type: Number,
default: 1,
},
});
const state = ref(false);
const emit = defineEmits(['onSuccess']);
const afterRead = (file) => {
state.value = false;
console.log(file, '7777');
if (file && file.length > 1) {
file.forEach((item) => {
if (item.status === 'failed') {
showToast('网络异常,请重试');
state.value = true;
}
if (item.file.size >= 10400 * 1024) {
state.value = true;
showToast('文件大小不能超过 10M');
return;
}
});
if (!state.value) {
file.forEach((item) => {
submit(item.file);
});
}
} else {
if (file.status === 'failed') {
showToast('网络异常,请重试');
}
if (file.file.size >= 10400 * 1024) {
showToast('文件大小不能超过 10M');
return;
}
submit(file.file);
}
};
const submit = (e) => {
let data = {
file: e,
};
requestGo({
url: `h5/v1/common/oss/uploadfile`,
data,
method: 'post',
headers: {
'content-type': 'multipart/form-data',
},
})
.then((res) => {
let result = res.data;
emit('onSuccess', result);
})
.catch((err) => {
console.log(err);
});
};
</script>
<style lang="scss" scoped>
.custom-image .van-empty__image {
width: px2rem(180);
height: px2rem(180);
}
</style>

10
src/definition.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
declare global {
interface Window {
getIosToken: any; // 苹果全局变量名
getAndroidPhone: any; // 安卓全局变量名
webAppInterface: any; // 安卓全局变量名
webkit: any; // ios app全局变量名
}
}
export {};

49
src/layout/index.vue Normal file
View File

@ -0,0 +1,49 @@
<template>
<van-sticky ref="stickyRef">
<van-nav-bar :title="$route.meta?.title">
<template #left>
<van-icon name="wap-nav" size="18" />
</template>
<template #right>
<van-icon name="search" size="18" />
</template>
</van-nav-bar>
</van-sticky>
<div class="container">
<router-view #="{ Component }">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
</div>
<van-tabbar ref="tabbarRef" route>
<template v-for="item in main" :key="item.name">
<van-tabbar-item :to="item.path" :icon="item.meta?.icon"> {{ item.meta?.title }} </van-tabbar-item>
</template>
</van-tabbar>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { Sticky, Tabbar } from 'vant';
import { main } from '@/router/modules/main';
defineOptions({ name: 'Layout' });
const stickyRef = ref<InstanceType<typeof Sticky>>();
const tabbarRef = ref<InstanceType<typeof Tabbar>>();
const containerHeight = ref('');
nextTick(() => {
containerHeight.value = `${stickyRef.value?.$el?.offsetHeight + tabbarRef.value?.$el.offsetHeight}px`;
});
</script>
<style lang="scss" scoped>
.container {
--height: v-bind('containerHeight');
--container-height: calc(100vh - var(--height));
height: calc(100vh - var(--height));
overflow: auto;
}
</style>

16
src/main.ts Normal file
View File

@ -0,0 +1,16 @@
import { createApp } from 'vue';
import App from './App.vue';
import { setupPlugins } from './plugins';
import { setupStore } from './store';
import { setupRouter } from './router';
const app = createApp(App);
// 安装插件vant-ui等,若使用了 vite-plugin-components 插件,则需要手动引入组件
setupPlugins(app);
// 安装vuex
setupStore(app);
// 安装router
setupRouter(app);
app.mount('#app');

39
src/plugins/compress.ts Normal file
View File

@ -0,0 +1,39 @@
// 图片压缩
const fileToDataURL = (file: Blob): Promise<any> => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = (e) => resolve((e.target as FileReader).result);
reader.readAsDataURL(file);
});
};
const dataURLToImage = (dataURL: string): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = dataURL;
});
};
const canvastoFile = (canvas: HTMLCanvasElement, type: string, quality: number): Promise<Blob | null> => {
return new Promise((resolve) => canvas.toBlob((blob) => resolve(blob), type, quality));
};
export const compressionFile = async (file, type = 'image/jpeg', quality = 0.5) => {
const fileName = file.name;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d') as CanvasRenderingContext2D;
const base64 = await fileToDataURL(file);
const img = await dataURLToImage(base64);
canvas.width = img.width;
canvas.height = img.height;
context.clearRect(0, 0, img.width, img.height);
context.drawImage(img, 0, 0, img.width, img.height);
const blob = (await canvastoFile(canvas, type, quality)) as Blob; // quality:0.5可根据实际状况核算
const newFile = await new File([blob], fileName, {
type,
});
return newFile;
};
export default compressionFile;

8
src/plugins/index.ts Normal file
View File

@ -0,0 +1,8 @@
import { App } from 'vue';
import './vant';
// import { vantPlugins } from './vant';
export const setupPlugins = (app: App) => {
// app.use(vantPlugins);
console.log('app', app);
};

248
src/plugins/public.ts Normal file
View File

@ -0,0 +1,248 @@
import { showToast } from 'vant';
import wx from 'weixin-js-sdk';
import router from '@/router';
// 回填登录数据
export const backFillLoginData = (result, userStore) => {
userStore.userID = result.id;
userStore.agentName = result.name || '----';
userStore.agentAvatar = result.avatar || 'https://image.fulllinkai.com/202203/09/cc1c73eb1a4941fef25a15cd1ff2f9df.png';
userStore.agentMobile = result.mobile || '';
userStore.token = result.token;
localStorage.setItem('rt_token', result.token);
localStorage.setItem('rt_openid', result.openid as any);
// userStore.isAgent = result.is_agent;
// userStore.registerAgent = result.mobile ? 1 : 0;
localStorage.setItem('userStore', JSON.stringify(result));
};
export const examineRegister = (to) => {
// 批发商自定义分享页面进入后不是批发商先去注册,注册后回到分享的指定页面
localStorage.setItem('jumpRtPathName', to.name as any);
localStorage.setItem('jumpRtPathQuery', JSON.stringify(to.query));
localStorage.setItem('jumpRtPathParams', JSON.stringify(to.params));
router.replace({
name: 'shopRegister',
});
};
// 图片转base64
export const imageConversion = (url) => {
return new Promise((resolve) => {
let img = new Image();
img.setAttribute('crossOrigin', 'anonymous'); // 解决iOS微信端报错 securityError...insource啥的、
img.crossOrigin = 'Anonymous';
img.onload = () => {
// 一定要在onload后去压缩转码否则可能因图片未加载传入报错或者undefind之类的
let base64 = getBase64Image(img);
resolve(base64);
};
img.src = `${url}?v=${Math.random()}`; // 处理缓存
});
};
const getBase64Image = (img) => {
let canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
let ctx = canvas.getContext('2d') as any;
ctx.drawImage(img, 0, 0, img.width, img.height);
let dataURL = canvas.toDataURL('image/jpeg'); // 可选其他值 image/jpeg
return dataURL;
};
// 联系客服
export const contactService = () => {
window.location.href = `https://work.weixin.qq.com/kfid/kfcb38dad0cf7512ec9`;
};
// 禁止微信分享
export const hideShare = (wxConfig) => {
wx.config(wxConfig.wxConfig);
wx.checkJsApi({
jsApiList: wxConfig.wxConfig.jsApiList, // 需要检测的JS接口列表
success(res) {
console.log(res);
},
});
wx.ready(function () {
wx.hideMenuItems({
menuList: ['menuItem:copyUrl', 'menuItem:share:appMessage', 'menuItem:share:timeline', 'menuItem:share:qq', 'menuItem:share:weiboApp', 'menuItem:favorite', 'menuItem:share:facebook', 'menuItem:share:QZone'], // 屏蔽复制链接和相关分享
});
});
};
// 截取路由?号后参数去重重新赋值type正常跳转为query || 授权跳转为url
export const CutOutQuery = (e, type) => {
let analyze = decodeURIComponent(e);
// let analyzeT = analyze.split('?#')[1];
let str = analyze.replace('?', '&').split(/[? &]/);
let newStr = [...new Set(str)] as any;
if (type == 'query') {
let query = {};
newStr.forEach((item) => {
if (item.includes('=')) {
let queryArray = item.split('=');
query[queryArray[0]] = queryArray[1];
}
});
console.log(query, '-------------------');
return query;
} else {
let query = ``;
newStr.forEach((item, index) => {
if (item.includes('=') && index == 1) {
query += `?${item}`;
} else if (item.includes('=')) {
query += `&${item}`;
} else {
query += `${item}`;
}
});
console.log(query, '-------------------');
return query;
}
};
// 获取当前日期
export const currentDate = () => {
let year = new Date().getFullYear();
let month = new Date().getMonth() + 1;
let day = new Date().getDate();
month = (month < 10 ? `0${month}` : month) as any;
day = (day < 10 ? `0${day}` : day) as any;
return `${year}-${month}-${day}`;
};
// 日期转换
export const formatData = (time) => {
let timestamp = time.replace(/-/g, '/');
timestamp = new Date(timestamp).getTime().toString(); // 补全为13位
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
const month = day * 30;
const now = new Date().getTime();
const diffValue = now - timestamp;
// 计算差异的时间
const monthC = diffValue / month;
const weekC = diffValue / (7 * day);
const dayC = diffValue / day;
const hourC = diffValue / hour;
const minC = diffValue / minute;
// 数值补0方法
const zero = function (value) {
if (value < 10) {
return `0${value}`;
}
return value;
};
// 超过1年直接显示年月日
if (monthC > 12) {
return (function () {
const date = new Date(time);
return `${date.getFullYear()}${zero(date.getMonth() + 1)}${zero(date.getDate())}`;
})();
} else if (monthC >= 1) {
return `${parseInt(monthC)}月前`;
} else if (weekC >= 1) {
return `${parseInt(weekC)}周前`;
} else if (dayC >= 1) {
return `${parseInt(dayC)}天前`;
} else if (hourC >= 1) {
return `${parseInt(hourC)}小时前`;
} else if (minC >= 1) {
return `${parseInt(minC)}分钟前`;
}
return '刚刚';
};
// 倒计时
export const countDown = (time) => {
let startDateV2 = new Date(); // 开始时间
let endTimeArrV2 = time.replace(/-/g, '/');
let endDateV2 = new Date(endTimeArrV2); // 结束时间
let t = endDateV2.getTime() - startDateV2.getTime(); // 时间差
let d = 0;
let h = 0;
let m = 0;
let s = 0;
if (t >= 0) {
d = Math.floor(t / 1000 / 3600 / 24);
h = Math.floor((t / 1000 / 60 / 60) % 24);
m = Math.floor((t / 1000 / 60) % 60);
s = Math.floor((t / 1000) % 60);
}
// 数值补0方法
const zero = function (value) {
if (value < 10) {
return `0${value}`;
}
return value;
};
// 修改小时格式
if (h >= 0 && h <= 9) {
h = zero(h);
}
// 修改分钟格式
if (m >= 0 && m <= 9) {
m = zero(m);
}
// 修改秒格式
if (s >= 0 && s <= 9) {
s = zero(s);
}
return { d, h, m, s };
};
// 复制内容
export const copy = async (val, state) => {
//创建input标签
const input = document.createElement('input');
//将input的值设置为需要复制的内容
input.value = val;
//添加input标签
document.body.appendChild(input);
//选中input标签
input.select();
//执行复制
document.execCommand('copy');
if (!state) {
if (document.execCommand('copy')) {
showToast('复制成功');
} else {
showToast('该浏览器暂不支持复制');
}
}
//移除input标签
document.body.removeChild(input);
};
//封装防抖
export const debounce = (fn, delay) => {
let timer: null | ReturnType<typeof setTimeout> = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(1);
}, delay);
};
};
//封装节流
export const throttle = (fn, delay) => {
let valid = true;
return function () {
if (!valid) {
return false;
}
valid = false;
setTimeout(() => {
fn.apply(2);
valid = true;
}, delay);
};
};

11
src/plugins/vant.ts Normal file
View File

@ -0,0 +1,11 @@
// https://youzan.github.io/vant/v4/#/zh-CN/quickstart#4.-yin-ru-han-shu-zu-jian-de-yang-shi
// Toast
import 'vant/es/toast/style';
// Dialog
import 'vant/es/dialog/style';
// Notify
import 'vant/es/notify/style';
// ImagePreview
import 'vant/es/image-preview/style';
import '@vant/touch-emulator'; // 桌面端touch适配

85
src/plugins/wxShare.ts Normal file
View File

@ -0,0 +1,85 @@
import wx from 'weixin-js-sdk';
import { useUserStore } from '@/store/modules/user';
export const weXinShare = (imgUrl: string, link: string, title: string, desc: string) => {
let finalLink = link;
try {
// 使用当前 origin 作为 base 解析 link
const url = new URL(link, location.origin);
// 提取 hash包含 # 和 ?query
const hash = url.hash; // 例如 "#/agentHome?from_user_id=123&from_type=rt_home"
// 构造新路径:/go_html/store_common (注意:结尾无 /
const newPath = '/go_html/store_common';
// 拼接最终链接
finalLink = location.origin + newPath + hash;
} catch (e) {
// 如果 link 是纯 hash 路径,如 "#/agentHome?..."
if (link.startsWith('#/')) {
finalLink = `${location.origin}/go_html/store_common${link}`;
} else if (link.startsWith('/store/')) {
// 处理相对路径如 "/store/#/xxx"
const hashPart = link.substring(link.indexOf('#'));
finalLink = `${location.origin}/go_html/store_common${hashPart}`;
} else if (link.startsWith('http') || link.startsWith('//')) {
// 兜底:尝试手动提取 hash
const hashIndex = link.indexOf('#');
if (hashIndex !== -1) {
const hash = link.substring(hashIndex);
finalLink = `${location.origin}/go_html/store_common${hash}`;
} else {
// 没有 #,可能是错误输入,保留原样(或按需处理)
finalLink = link;
}
} else {
// 其他情况,保守处理
finalLink = link;
}
}
console.log('finalLink:', finalLink);
const userStore = useUserStore();
wx.config(userStore.wxConfig);
wx.ready(() => {
const shareData = {
title,
desc,
link: finalLink,
imgUrl,
success() {
console.log('分享成功');
},
cancel() {
console.log('分享取消');
},
};
wx.updateAppMessageShareData(shareData);
wx.updateTimelineShareData({
title,
link: finalLink,
imgUrl,
success() {
console.log('朋友圈分享成功');
},
cancel() {
console.log('朋友圈分享取消');
},
});
setTimeout(() => {
wx.showMenuItems({
menuList: ['menuItem:share:appMessage', 'menuItem:share:timeline'],
});
}, 300);
});
wx.error((err) => {
console.error('微信分享 error:', err);
});
};

250
src/router/index.ts Normal file
View File

@ -0,0 +1,250 @@
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { createRouterGuards } from './routerGuards';
import { main } from './modules/main';
import { mainV2 } from './modules/mainV2';
export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'agentHome',
redirect: '/agentHome',
children: [
...main,
...mainV2,
{
path: '/shopSearch',
name: 'shopSearch',
component: () => import('@/views/subDir/shopSearch.vue'),
meta: {
title: '搜索商品',
},
},
{
path: '/shopDetail/:id',
name: 'shopDetail',
component: () => import('@/views/subDir/shopDetail.vue'),
meta: {
title: '商品详情',
},
},
{
path: '/shopDetailV2/:id',
name: 'shopDetailV2',
component: () => import('@/views/subDir/shopDetailV2.vue'),
meta: {
title: '商品详情',
},
},
{
path: '/shopDetailV3/:id',
name: 'shopDetailV3',
component: () => import('@/views/subDir/shopDetailV3.vue'),
meta: {
title: '商品详情',
},
},
{
path: '/shopOrderConfirm',
name: 'shopOrderConfirm',
component: () => import('@/views/subDir/shopOrderConfirm.vue'),
meta: {
title: '确认订单',
},
},
// //dma确认订单
// {
// path: '/shopOrderConfirmV2',
// name: 'shopOrderConfirmV2',
// component: () => import('@/views/subDir/shopOrderConfirmV2.vue'),
// meta: {
// title: '确认订单',
// },
// },
{
path: '/appUserLogin',
name: 'appUserLogin',
component: () => import('@/views/subDir/appUserLogin.vue'),
meta: {
title: '登录',
},
},
{
path: '/shopRegister',
name: 'shopRegister',
component: () => import('@/views/subDir/shopRegister.vue'),
meta: {
title: '个人资料',
},
},
{
path: '/shopOrderDetail/:id',
name: 'shopOrderDetail',
component: () => import('@/views/subDir/shopOrderDetail.vue'),
meta: {
title: '订单详情',
},
},
// 租赁商品详情
{
path: '/leaseShopDetail/:id',
name: 'leaseShopDetail',
component: () => import('@/views/subDir/leaseShopDetail.vue'),
meta: {
title: '商品详情',
},
},
// 租赁商品确认订单
{
path: '/leaseShopOrderConfirm',
name: 'leaseShopOrderConfirm',
component: () => import('@/views/subDir/leaseShopOrderConfirm.vue'),
meta: {
title: '确认订单',
},
},
{
path: '/leaseShopOrderList',
name: 'leaseShopOrderList',
component: () => import('@/views/subDir/leaseShopOrderList.vue'),
meta: {
title: '测试官订单',
},
},
// 租赁商品订单详情
{
path: '/leaseShopOrderDetail/:id',
name: 'leaseShopOrderDetail',
component: () => import('@/views/subDir/leaseShopOrderDetail.vue'),
meta: {
title: '订单详情',
},
},
{
path: '/shopOrderList',
name: 'shopOrderList',
component: () => import('@/views/subDir/shopOrderList.vue'),
meta: {
title: '我的订单',
},
},
{
path: '/returnGoods/:id',
name: 'returnGoods',
component: () => import('@/views/subDir/returnGoods.vue'),
meta: {
title: '',
},
},
{
path: '/returnGoodsDetail/:id',
name: 'returnGoodsDetail',
component: () => import('@/views/subDir/returnGoodsDetail.vue'),
meta: {
title: '',
},
},
{
path: '/shopPrizeDrawCode',
name: 'shopPrizeDrawCode',
component: () => import('@/views/subDir/shopPrizeDrawCode.vue'),
meta: {
title: '我的抽奖码',
},
},
{
path: '/shopAgreement/:id',
name: 'shopAgreement',
component: () => import('@/views/subDir/shopAgreement.vue'),
meta: {
title: '',
},
},
{
path: '/testPage',
name: 'testPage',
component: () => import('@/views/userDir/testPage.vue'),
meta: {
title: '测试',
},
},
{
path: '/appShopDetail/:id',
name: 'appShopDetail',
component: () => import('@/views/appDir/appShopDetail.vue'),
meta: {
title: '商品详情',
},
},
{
path: '/appShopOrderConfirm',
name: 'appShopOrderConfirm',
component: () => import('@/views/appDir/appShopOrderConfirm.vue'),
meta: {
title: '确认订单',
},
},
{
path: '/appMyAddress',
name: 'appMyAddress',
component: () => import('@/views/appDir/appMyAddress.vue'),
meta: {
title: '我的地址',
},
},
{
path: '/appAddAddress',
name: 'appAddAddress',
component: () => import('@/views/appDir/appAddAddress.vue'),
meta: {
title: '添加地址',
},
},
{
path: 'appShopAgreement/:id',
name: 'appShopAgreement',
component: () => import('@/views/appDir/appShopAgreement.vue'),
meta: {
title: '',
},
},
{
path: 'longPicture',
name: 'longPicture',
component: () => import('@/views/appDir/longPicture.vue'),
meta: {
title: '',
},
},
{
path: '/dietaryHabitTest',
name: 'dietaryHabitTest',
component: () => import('@/views/subDir/dietaryHabitTest.vue'),
meta: {
title: '心理测试',
},
},
{
path: '/dietaryHabitList',
name: 'dietaryHabitList',
component: () => import('@/views/subDir/dietaryHabitList.vue'),
meta: {
title: '心理测试列表',
},
},
],
},
];
const router = createRouter({
// process.env.BASE_URL
history: createWebHashHistory(''),
routes,
});
export function setupRouter(app: App) {
app.use(router);
// 创建路由守卫
createRouterGuards(router);
}
export default router;

View File

@ -0,0 +1,37 @@
import { RouteRecordRaw } from 'vue-router';
export const main: Array<RouteRecordRaw> = [
{
path: '/agentHome',
name: 'agentHome',
component: () => import('@/views/tabBar/agentHome.vue'),
meta: {
title: '友福商城',
icon: 'home-o',
active: 'https://image.fulllinkai.com/202405/21/56d993df3bd2d5d21f10dfff63326ead.png',
inactive: 'https://image.fulllinkai.com/202405/21/ae7d84d2b86dcba73107373e460f3762.png',
},
},
// {
// path: '/testControler',
// name: 'testControler',
// component: () => import('@/views/tabBar/testControler.vue'),
// meta: {
// title: '测试官',
// icon: 'home-o',
// active: 'https://image.fulllinkai.com/202406/07/40c9501378795c3ef38223e95cd98fbb.png',
// inactive: 'https://image.fulllinkai.com/202406/07/d5bbd9cd97c8672415d343465a88860c.png',
// },
// },
{
path: '/user',
name: 'user',
component: () => import('@/views/tabBar/user.vue'),
meta: {
title: '我的',
icon: 'cart-o',
active: 'https://image.fulllinkai.com/202406/12/1a0d9053c7a3124f5e1feca9d8ff8f77.png',
inactive: 'https://image.fulllinkai.com/202406/12/1ad2a21afa02dbfa65893693edaa4dae.png',
},
},
];

View File

@ -0,0 +1,26 @@
import { RouteRecordRaw } from 'vue-router';
export const mainV2: Array<RouteRecordRaw> = [
{
path: '/agentHomeV2',
name: 'agentHomeV2',
component: () => import('@/views/tabBar/agentHomeV2.vue'),
meta: {
title: '友福商城',
icon: 'home-o',
active: 'https://image.fulllinkai.com/202405/21/56d993df3bd2d5d21f10dfff63326ead.png',
inactive: 'https://image.fulllinkai.com/202405/21/ae7d84d2b86dcba73107373e460f3762.png',
},
},
{
path: '/userV2',
name: 'userV2',
component: () => import('@/views/tabBar/userV2.vue'),
meta: {
title: '我的',
icon: 'cart-o',
active: 'https://image.fulllinkai.com/202406/12/1a0d9053c7a3124f5e1feca9d8ff8f77.png',
inactive: 'https://image.fulllinkai.com/202406/12/1ad2a21afa02dbfa65893693edaa4dae.png',
},
},
];

124
src/router/routerGuards.ts Normal file
View File

@ -0,0 +1,124 @@
import { Router } from 'vue-router';
import { getLogin } from '@/api/demo';
import { useUserStore } from '@/store/modules/user';
import { backFillLoginData, hideShare } from '@/plugins/public';
import requestGo from '@/utils/requestGo';
// import routerV2 from '@/router';
let pathUrls = ['shopReceipt', 'testPage', 'appAddAddress', 'appMyAddress', 'appShopDetail', 'appShopOrderConfirm', 'appShopAgreement', 'longPicture']; // 不需要登录的页面
let initLogin = 1;
export function createRouterGuards(router: Router) {
router.beforeEach((to, _from, next) => {
document.title = (to?.meta?.title as string) || document.title;
const userStore = useUserStore();
setTimeout(() => {
// 判断是否需要登录的页面
let through = false;
pathUrls.forEach((item) => {
if (to.path.includes(item)) {
through = true;
}
});
if (to.query.openid) {
localStorage.removeItem('rt_openid');
localStorage.setItem('rt_openid', to.query.openid as any);
history.back();
}
window.onpageshow = function (e) {
if (e.persisted) {
window.location.reload();
}
};
// 分享人ID
if (to.query.from_user_id) {
localStorage.removeItem('rt_from_user_id');
localStorage.setItem('rt_from_user_id', to.query.from_user_id as any);
}
// 分享人ID
if (to.query.form_open_id) {
localStorage.removeItem('rt_form_open_id');
localStorage.setItem('rt_form_open_id', to.query.form_open_id as any);
}
// 邀请人ID
if (to.query.invite_user_id) {
localStorage.removeItem('rt_invite_user_id');
localStorage.setItem('rt_invite_user_id', to.query.invite_user_id as any);
}
// 推荐人ID
if (to.query.referrer_user_id) {
localStorage.removeItem('rt_referrer_user_id');
localStorage.setItem('rt_referrer_user_id', to.query.referrer_user_id as any);
}
// 来源
if (to.query.from_type) {
localStorage.removeItem('rt_from_type');
localStorage.setItem('rt_from_type', to.query.from_type as any);
}
// 来源
if (to.query.app_token) {
localStorage.removeItem('app_token');
localStorage.setItem('app_token', to.query.app_token as any);
}
if (to.query.token) {
localStorage.removeItem('rt_token');
localStorage.setItem('rt_token', to.query.token as any);
}
// 不是指定分享页面禁止分享
if ((userStore.wxConfig as any).init != 'true' && localStorage.getItem('user_id')) {
hideShare(useUserStore());
}
// 无需登录的页面直接进入
if (through) {
next();
} else {
// 检测是否存在token没有就登录
if (initLogin <= 1) {
getLogin().then((res) => {
if (res.code === 0) {
let result = res.data;
// 微信分享配置接口
const url = encodeURIComponent(location.href.split('#')[0]);
requestGo({ url: `h5/v1/common/jssdk/config?url=${url}`, hideLoading: true, method: 'get' })
.then((res) => {
userStore.wxConfig = res.data;
localStorage.setItem('user_id', result.id);
hideShare(useUserStore());
})
.catch((err) => {
console.log(err);
});
initLogin++;
backFillLoginData(result, userStore);
// if (!res.data.id) {
// examineRegister(routerV2.currentRoute.value);
// }
next();
}
});
} else {
next();
}
}
}, 50);
});
router.onError((error) => {
const fetchResourcesErrors = ['Failed to fetch dynamically imported module', 'Importing a module script failed'];
if (fetchResourcesErrors.some((item) => error?.message && error.message?.includes(item))) {
window.location.reload();
}
});
}

10
src/store/index.ts Normal file
View File

@ -0,0 +1,10 @@
import { createPinia } from 'pinia';
import type { App } from 'vue';
const store = createPinia();
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };

80
src/store/modules/user.ts Normal file
View File

@ -0,0 +1,80 @@
import { defineStore } from 'pinia';
interface UserState {
init: number;
token: string;
partnerID: string;
fromUserID: string;
userID: string;
wineMobile: string;
wineName: string;
name: string;
agentName: string;
avatar: string;
agentAvatar: string;
agentMobile: string;
invitationCode: string;
weChatBindState: string;
chatId: string;
userOfficeId: string;
isAgent: string;
registerAgent: string;
userType: string;
admin: string;
dataState: string;
partner: string;
collaborator: string;
service: string;
emcee: string;
coach: string;
chef: string;
chiefCoach: string;
takeFood: number;
uploadData: object;
wallets: object;
wxConfig: object;
agentConfig: object;
signature: object;
}
export const useUserStore = defineStore({
id: 'user',
state: (): UserState => ({
init: 0,
token: '',
partnerID: '',
fromUserID: '',
userID: '',
wineMobile: '', // 绑定酒所需回填手机号
wineName: '', // 绑定酒所需回填用户名
name: '',
agentName: '', // 批发商姓名
avatar: '',
agentAvatar: '', // 批发商头像
agentMobile: '', // 批发商手机
invitationCode: '', // 邀请码
weChatBindState: '',
chatId: '',
userOfficeId: '', // 用户绑定的所属办公室id
isAgent: '', // 是否批发商 01
registerAgent: '', // 是否注册了批发商 01接口获取值不同
userType: '', // 用户角色类型 0: 客户1: 兼职2: 全职
admin: '1', // 是否管理员
dataState: '', // 资料状态
partner: '', // 是否合伙人
collaborator: '', // 是否合作商(加入友福)
service: '', // 是否客服
emcee: '', // 是否主持人
coach: '', // 是否副教练
chef: '', // 是否餐饮人员
chiefCoach: '', // 是否主教练
takeFood: 1,
uploadData: {}, // 阿里云上传配置信息
wallets: {}, // 钱包信息
wxConfig: { init: 'true' }, // 储存微信凭证
agentConfig: {}, // 储存企业微信凭证
signature: {}, // 储存上传凭证
}),
getters: {},
actions: {},
});

808
src/styles/common.scss Normal file
View File

@ -0,0 +1,808 @@
@charset "UTF-8";
@import './rem/scss/util';
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.break-all {
word-break: break-all;
}
.f-fb {
display: flex;
justify-content: space-between;
}
.f-fbc {
display: flex;
justify-content: space-between;
align-items: center;
}
.f-fl {
display: flex;
justify-content: left;
}
.f-fcl {
display: flex;
justify-content: left;
align-items: center;
}
.f-fc {
display: flex;
justify-content: center;
}
.f-fcc {
display: flex;
justify-content: center;
align-items: center;
}
.f-fr {
display: flex;
justify-content: right;
}
.f-fcr {
display: flex;
justify-content: right;
align-items: center;
}
.f-wrap {
flex-wrap: wrap;
}
.ellipsis_1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ellipsis_2 {
display: -webkit-box; /* Safari */
-webkit-line-clamp: 2; /* Safari and Chrome */
-webkit-box-orient: vertical; /* Safari and Chrome */
overflow: hidden;
}
.ellipsis_3 {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
}
.backCover {
background-size: cover !important;
background-repeat: no-repeat !important;
background-position: center !important;
}
.ui-relative {
position: relative;
}
.ui-overflow {
overflow: hidden;
}
.flo_l {
float: left;
}
.flo_r {
float: right;
}
.bold {
font-weight: bold;
}
.colorF {
color: #fff;
}
.color3 {
color: #333;
}
.color6 {
color: #666;
}
.color9 {
color: #999;
}
.colorF5 {
color: #f5f5f5;
}
.colorF8 {
color: #f8f8f8;
}
.colorC2 {
color: #c2c2c2;
}
.colorTheme {
color: #5ac7a0;
}
.colorBlue {
color: #2a63b2;
}
.colorAppGreen {
color: #18CA6E;
}
.colorPrice {
color: #ff5959;
}
.bcTheme {
background: linear-gradient(90deg, #8c9bff 0%, #707ffa 100%);
}
.font_14 {
font-size: px2rem(14);
}
.font_16 {
font-size: px2rem(16);
}
.font_18 {
font-size: px2rem(18);
}
.font_20 {
font-size: px2rem(20);
}
.font_22 {
font-size: px2rem(22);
}
.font_24 {
font-size: px2rem(24);
}
.font_26 {
font-size: px2rem(26);
}
.font_28 {
font-size: px2rem(28);
}
.font_30 {
font-size: px2rem(30);
}
.font_32 {
font-size: px2rem(32);
}
.font_34 {
font-size: px2rem(34);
}
.font_36 {
font-size: px2rem(36);
}
.font_38 {
font-size: px2rem(38);
}
.font_40 {
font-size: px2rem(40);
}
.font_42 {
font-size: px2rem(42);
}
.font_44 {
font-size: px2rem(44);
}
.font_46 {
font-size: px2rem(46);
}
.font_48 {
font-size: px2rem(48);
}
.ui-pt-4 {
padding-top: px2rem(4);
}
.ui-pt-6 {
padding-top: px2rem(6);
}
.ui-pt-8 {
padding-top: px2rem(8);
}
.ui-pt-10 {
padding-top: px2rem(10);
}
.ui-pt-12 {
padding-top: px2rem(12);
}
.ui-pt-14 {
padding-top: px2rem(14);
}
.ui-pt-16 {
padding-top: px2rem(16);
}
.ui-pt-18 {
padding-top: px2rem(18);
}
.ui-pt-20 {
padding-top: px2rem(20);
}
.ui-pt-22 {
padding-top: px2rem(22);
}
.ui-pt-24 {
padding-top: px2rem(24);
}
.ui-pt-26 {
padding-top: px2rem(26);
}
.ui-pt-28 {
padding-top: px2rem(28);
}
.ui-pt-30 {
padding-top: px2rem(30);
}
.ui-pt-32 {
padding-top: px2rem(32);
}
.ui-pt-36 {
padding-top: px2rem(36);
}
.ui-pt-40 {
padding-top: px2rem(40);
}
.ui-pr-4 {
padding-right: px2rem(4);
}
.ui-pr-6 {
padding-right: px2rem(6);
}
.ui-pr-8 {
padding-right: px2rem(8);
}
.ui-pr-10 {
padding-right: px2rem(10);
}
.ui-pr-12 {
padding-right: px2rem(12);
}
.ui-pr-14 {
padding-right: px2rem(14);
}
.ui-pr-16 {
padding-right: px2rem(16);
}
.ui-pr-18 {
padding-right: px2rem(18);
}
.ui-pr-20 {
padding-right: px2rem(20);
}
.ui-pr-22 {
padding-right: px2rem(22);
}
.ui-pr-24 {
padding-right: px2rem(24);
}
.ui-pr-26 {
padding-right: px2rem(26);
}
.ui-pr-28 {
padding-right: px2rem(28);
}
.ui-pr-30 {
padding-right: px2rem(30);
}
.ui-pr-32 {
padding-right: px2rem(32);
}
.ui-pr-36 {
padding-right: px2rem(36);
}
.ui-pr-40 {
padding-right: px2rem(40);
}
.ui-pb-4 {
padding-bottom: px2rem(4);
}
.ui-pb-6 {
padding-bottom: px2rem(6);
}
.ui-pb-8 {
padding-bottom: px2rem(8);
}
.ui-pb-10 {
padding-bottom: px2rem(10);
}
.ui-pb-12 {
padding-bottom: px2rem(12);
}
.ui-pb-14 {
padding-bottom: px2rem(14);
}
.ui-pb-16 {
padding-bottom: px2rem(16);
}
.ui-pb-18 {
padding-bottom: px2rem(18);
}
.ui-pb-20 {
padding-bottom: px2rem(20);
}
.ui-pb-22 {
padding-bottom: px2rem(22);
}
.ui-pb-24 {
padding-bottom: px2rem(24);
}
.ui-pb-26 {
padding-bottom: px2rem(26);
}
.ui-pb-28 {
padding-bottom: px2rem(28);
}
.ui-pb-30 {
padding-bottom: px2rem(30);
}
.ui-pb-32 {
padding-bottom: px2rem(32);
}
.ui-pb-36 {
padding-bottom: px2rem(36);
}
.ui-pb-40 {
padding-bottom: px2rem(40);
}
.ui-pl-4 {
padding-left: px2rem(4);
}
.ui-pl-6 {
padding-left: px2rem(6);
}
.ui-pl-8 {
padding-left: px2rem(8);
}
.ui-pl-10 {
padding-left: px2rem(10);
}
.ui-pl-12 {
padding-left: px2rem(12);
}
.ui-pl-14 {
padding-left: px2rem(14);
}
.ui-pl-16 {
padding-left: px2rem(16);
}
.ui-pl-18 {
padding-left: px2rem(18);
}
.ui-pl-20 {
padding-left: px2rem(20);
}
.ui-pl-22 {
padding-left: px2rem(22);
}
.ui-pl-24 {
padding-left: px2rem(24);
}
.ui-pl-26 {
padding-left: px2rem(26);
}
.ui-pl-28 {
padding-left: px2rem(28);
}
.ui-pl-30 {
padding-left: px2rem(30);
}
.ui-pl-32 {
padding-left: px2rem(32);
}
.ui-pl-36 {
padding-left: px2rem(36);
}
.ui-pl-40 {
padding-left: px2rem(40);
}
.ui-mt-4 {
margin-top: px2rem(4);
}
.ui-mt-6 {
margin-top: px2rem(6);
}
.ui-mt-8 {
margin-top: px2rem(8);
}
.ui-mt-10 {
margin-top: px2rem(10);
}
.ui-mt-12 {
margin-top: px2rem(12);
}
.ui-mt-14 {
margin-top: px2rem(14);
}
.ui-mt-16 {
margin-top: px2rem(16);
}
.ui-mt-18 {
margin-top: px2rem(18);
}
.ui-mt-20 {
margin-top: px2rem(20);
}
.ui-mt-22 {
margin-top: px2rem(22);
}
.ui-mt-24 {
margin-top: px2rem(24);
}
.ui-mt-26 {
margin-top: px2rem(26);
}
.ui-mt-28 {
margin-top: px2rem(28);
}
.ui-mt-30 {
margin-top: px2rem(30);
}
.ui-mt-32 {
margin-top: px2rem(32);
}
.ui-mt-36 {
margin-top: px2rem(36);
}
.ui-mt-40 {
margin-top: px2rem(40);
}
.ui-mr-4 {
margin-right: px2rem(4);
}
.ui-mr-6 {
margin-right: px2rem(6);
}
.ui-mr-8 {
margin-right: px2rem(8);
}
.ui-mr-10 {
margin-right: px2rem(10);
}
.ui-mr-12 {
margin-right: px2rem(12);
}
.ui-mr-14 {
margin-right: px2rem(14);
}
.ui-mr-16 {
margin-right: px2rem(16);
}
.ui-mr-18 {
margin-right: px2rem(18);
}
.ui-mr-20 {
margin-right: px2rem(20);
}
.ui-mr-22 {
margin-right: px2rem(22);
}
.ui-mr-24 {
margin-right: px2rem(24);
}
.ui-mr-26 {
margin-right: px2rem(26);
}
.ui-mr-28 {
margin-right: px2rem(28);
}
.ui-mr-30 {
margin-right: px2rem(30);
}
.ui-mr-32 {
margin-right: px2rem(32);
}
.ui-mr-36 {
margin-right: px2rem(36);
}
.ui-mr-40 {
margin-right: px2rem(40);
}
.ui-mb-4 {
margin-bottom: px2rem(4);
}
.ui-mb-6 {
margin-bottom: px2rem(6);
}
.ui-mb-8 {
margin-bottom: px2rem(8);
}
.ui-mb-10 {
margin-bottom: px2rem(10);
}
.ui-mb-12 {
margin-bottom: px2rem(12);
}
.ui-mb-14 {
margin-bottom: px2rem(14);
}
.ui-mb-16 {
margin-bottom: px2rem(16);
}
.ui-mb-18 {
margin-bottom: px2rem(18);
}
.ui-mb-20 {
margin-bottom: px2rem(20);
}
.ui-mb-22 {
margin-bottom: px2rem(22);
}
.ui-mb-24 {
margin-bottom: px2rem(24);
}
.ui-mb-26 {
margin-bottom: px2rem(26);
}
.ui-mb-28 {
margin-bottom: px2rem(28);
}
.ui-mb-30 {
margin-bottom: px2rem(30);
}
.ui-mb-32 {
margin-bottom: px2rem(32);
}
.ui-mb-36 {
margin-bottom: px2rem(36);
}
.ui-mb-40 {
margin-bottom: px2rem(40);
}
.ui-ml-4 {
margin-left: px2rem(4);
}
.ui-ml-6 {
margin-left: px2rem(6);
}
.ui-ml-8 {
margin-left: px2rem(8);
}
.ui-ml-10 {
margin-left: px2rem(10);
}
.ui-ml-12 {
margin-left: px2rem(12);
}
.ui-ml-14 {
margin-left: px2rem(14);
}
.ui-ml-16 {
margin-left: px2rem(16);
}
.ui-ml-18 {
margin-left: px2rem(18);
}
.ui-ml-20 {
margin-left: px2rem(20);
}
.ui-ml-22 {
margin-left: px2rem(22);
}
.ui-ml-24 {
margin-left: px2rem(24);
}
.ui-ml-26 {
margin-left: px2rem(26);
}
.ui-ml-28 {
margin-left: px2rem(28);
}
.ui-ml-30 {
margin-left: px2rem(30);
}
.ui-ml-32 {
margin-left: px2rem(32);
}
.ui-ml-36 {
margin-left: px2rem(36);
}
.ui-ml-40 {
margin-left: px2rem(40);
}
.ui-suspension-box {
position: fixed;
right: px2rem(20);
bottom: px2rem(166);
z-index: 22;
}
.ui-mgt-entrance {
width: px2rem(140);
height: px2rem(140);
display: block;
margin-top: px2rem(20);
}
body {
height: 100vh;
transform: translate(0);
}
/* 滚动条凹槽的颜色,还可以设置边框属性 */
*::-webkit-scrollbar-track-piece {
background-color: #f8f8f8;
border-radius: 2em;
}
/* 滚动条 */
*::-webkit-scrollbar {
width: 0;
}

129
src/styles/func.scss Normal file
View File

@ -0,0 +1,129 @@
@use 'sass:meta';
@function rpx($value) {
@return $value * 1rpx;
}
/**
* 点击文本状态
*/
@mixin click-text-active($color: rgba(0, 0, 0, 0.2)) {
&:active {
color: $color;
transition: color 0.01s;
}
}
/**
* @param $color
* 点击背景状态
*/
@mixin click-bg-active($color: rgba(0, 0, 0, 0.06)) {
&:active {
background-color: $color;
transition: background-color 0.01s;
}
}
/**
* @param $rpx
* 将元素设置成圆形
*/
@mixin el-to-circle($rpx) {
width: rpx($rpx);
height: rpx($rpx);
border-radius: rpx($rpx);
}
/**
* @description 绝对定位居中显示
*/
@mixin position-center() {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/**
* @description flex布局垂直水平居中
*/
@mixin flex-center($args...) {
@each $key, $value in meta.keywords($args) {
& {
#{$key}: $value;
}
}
display: flex;
justify-content: center;
align-items: center;
}
/**
* @description 多行文本省略号
*/
@mixin text-ellipsis($line: 1) {
@if ($line == 1) {
word-break: break-all;
text-overflow: ellipsis;
}
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
}
/**
* @description 三角气泡框
*/
@mixin popover($placement, $args...) {
@each $key, $value in meta.keywords($args) {
&::after {
#{$key}: $value;
}
}
&::after {
position: absolute;
width: 0;
height: 0;
color: #fff;
border-color: transparent;
border-style: solid;
border-width: 6px;
content: '';
}
&::after {
@if ($placement == 'top') {
border-bottom-width: 0;
border-top-color: currentColor;
bottom: 0;
left: 50%;
transform: translate(-50%, 100%);
}
@if ($placement == 'bottom') {
border-top-width: 0;
border-bottom-color: currentColor;
top: 0;
left: 50%;
transform: translate(-50%, -100%);
}
@if ($placement == 'left') {
border-right-width: 0;
border-left-color: currentColor;
right: 0;
top: 50%;
transform: translate(100%, -50%);
}
@if ($placement == 'right') {
border-left-width: 0;
border-right-color: currentColor;
left: 0;
top: 50%;
transform: translate(-100%, -50%);
}
}
}

168
src/styles/rem/css/rem.css Normal file
View File

@ -0,0 +1,168 @@
.f-p-0 {
padding: 0 !important;
}
html,
body {
padding: 0;
margin: 0;
}
html body {
min-width: 320px;
margin-right: auto;
margin-left: auto;
}
@media (-webkit-device-pixel-ratio: 2) {
html body {
min-width: 640px;
}
}
@media (-webkit-device-pixel-ratio: 3) {
html body {
min-width: 960px;
}
}
html body[data-content-max] {
max-width: 540px;
margin-right: auto;
margin-left: auto;
}
@media (-webkit-device-pixel-ratio: 2) {
html body[data-content-max] {
max-width: 1080px;
}
}
@media (-webkit-device-pixel-ratio: 3) {
html body[data-content-max] {
max-width: 1620px;
}
}
html[data-dpr='1'] body {
min-width: 320px;
}
body {
font-family: 'Microsoft YaHei';
font-size: 0.3733333333rem;
background-color: #f8f8f8;
border-width: 45px;
}
.container {
background-color: #fff;
}
header {
height: 4rem;
line-height: 4rem;
text-align: center;
background-color: #f2f2f2;
}
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
}
nav ul li {
display: flex;
flex-wrap: wrap;
width: 2.6666666667rem;
justify-content: center;
}
nav ul .icon {
width: 1.6rem;
height: 1.6rem;
margin-bottom: 0.2666666667rem;
line-height: 1.6rem;
text-align: center;
background-color: #f2f2f2;
}
main {
padding: 0.2666666667rem;
}
main h3 {
position: relative;
margin-top: 0.6666666667rem;
margin-left: 0.3466666667rem;
font-size: 0.4rem;
}
main h3::before {
position: absolute;
left: -0.2666666667rem;
width: 0.16rem;
height: 100%;
background-color: #fc8200;
content: '';
}
.info-items {
margin-top: 0.2666666667rem;
margin-bottom: 0.2666666667rem;
}
.info-item {
display: flex;
padding: 0.4rem;
padding-left: 0;
margin-top: 0.2666666667rem;
border: 1px solid #ddd;
}
.info-item span {
min-width: 1.6rem;
text-align: center;
border-right: 1px solid #ddd;
}
.info-item input {
width: 100%;
font-size: 0.3733333333rem;
border: none;
outline: none;
caret-color: #fc8200;
}
.info-item textarea {
width: 100%;
height: 3.3333333333rem;
padding: 0.2666666667rem;
font-family: 'Microsoft YaHei';
font-size: 0.3733333333rem;
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
.info-confirm {
margin-bottom: 0.5333333333rem;
text-align: center;
}
.info-confirm__btn {
display: inline-block;
width: 2.6666666667rem;
height: 1.0666666667rem;
margin-top: 1.0666666667rem;
line-height: 1.0666666667rem;
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
footer {
height: 2rem;
line-height: 2rem;
text-align: center;
background-color: #f2f2f2;
}

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAIA,MAAO,CACH,OAAO,CAAE,YAAY,CAGzB,SACK,CACD,MAAM,CAAE,CAAC,CACT,OAAO,CAAE,CAAC,CCsBV,SAAK,CAgBL,YAAY,CAAE,IAAI,CAClB,WAAW,CAAE,IAAI,CACjB,SAAS,CA1CM,KAAK,CA4CpB,sCAAuC,CApBvC,SAAK,CAqBD,SAAS,CAAE,KAAqB,EAGpC,sCAAuC,CAxBvC,SAAK,CAyBD,SAAS,CAAE,KAAqB,EAtBhC,2BAAoB,CA4BxB,YAAY,CAAE,IAAI,CAClB,WAAW,CAAE,IAAI,CACjB,SAAS,CAvDM,KAAK,CAyDpB,sCAAuC,CAhCnC,2BAAoB,CAiCpB,SAAS,CAAE,MAAqB,EAGpC,sCAAuC,CApCnC,2BAAoB,CAqCpB,SAAS,CAAE,MAAqB,EA/BpC,uBAAqB,CACjB,SAAS,CAlCE,KAAK,CDSxB,IAAK,CAED,YAAY,CCQJ,IAAmC,CDN3C,gBAAgB,CAAE,OAAO,CACzB,SAAS,CCAD,cAAmC,CDC3C,WAAW,CAAE,iBAAiB,CAGlC,UAAW,CACP,gBAAgB,CAAE,IAAI,CAG1B,MAAO,CACH,MAAM,CCTE,IAAmC,CDU3C,WAAW,CCVH,IAAmC,CDW3C,UAAU,CAAE,MAAM,CAClB,gBAAgB,CAAE,OAAO,CAG7B,MAAO,CACH,OAAO,CAAE,IAAI,CACb,eAAe,CAAE,YAAY,CAC7B,OAAO,CAAE,CAAC,CAEV,SAAG,CACC,OAAO,CAAE,IAAI,CACb,SAAS,CAAE,IAAI,CACf,KAAK,CCvBD,eAAmC,CDwBvC,eAAe,CAAE,MAAM,CAG3B,YAAM,CACF,aAAa,CC5BT,cAAmC,CD6BvC,KAAK,CC7BD,MAAmC,CD8BvC,MAAM,CC9BF,MAAmC,CD+BvC,WAAW,CC/BP,MAAmC,CDgCvC,UAAU,CAAE,MAAM,CAClB,gBAAgB,CAAE,OAAO,CAIjC,IAAK,CACD,OAAO,CCtCC,cAAmC,CDwC3C,OAAG,CACC,QAAQ,CAAE,QAAQ,CAClB,UAAU,CC1CN,cAAmC,CD2CvC,WAAW,CC3CP,cAAmC,CD4CvC,SAAS,CC5CL,KAAmC,CDkDvC,cAAS,CACL,OAAO,CAAE,EAAE,CACX,QAAQ,CAAE,QAAQ,CAClB,IAAI,CCrDJ,eAAmC,CDsDnC,KAAK,CCtDL,MAAmC,CDuDnC,MAAM,CAAE,IAAI,CACZ,gBAAgB,CAAE,OAAO,CAKrC,WAAY,CACR,UAAU,CC9DF,cAAmC,CD+D3C,aAAa,CC/DL,cAAmC,CDkE/C,UAAW,CACP,UAAU,CCnEF,cAAmC,CDoE3C,OAAO,CCpEC,KAAmC,CDqE3C,YAAY,CAAE,CAAC,CACf,MAAM,CAAE,cAAc,CAEtB,OAAO,CAAE,IAAI,CAEb,eAAK,CACD,SAAS,CC3EL,MAAmC,CD4EvC,UAAU,CAAE,MAAM,CAClB,YAAY,CAAE,cAAc,CAGhC,gBAAM,CACF,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,SAAS,CCnFL,cAAmC,CDoFvC,WAAW,CAAE,OAAO,CACpB,OAAO,CAAE,IAAI,CAGjB,mBAAS,CACL,OAAO,CCzFH,cAAmC,CD0FvC,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,MAAM,CC5FF,eAAmC,CD6FvC,SAAS,CC7FL,cAAmC,CD8FvC,WAAW,CAAE,iBAAiB,CAC9B,WAAW,CAAE,OAAO,CACpB,wBAAwB,CAAE,IAAI,CAC9B,gBAAgB,CAAE,IAAI,CAI9B,aAAc,CACV,aAAa,CCtGL,cAAmC,CDuG3C,UAAU,CAAE,MAAM,CAElB,kBAAO,CACH,OAAO,CAAE,YAAY,CACrB,UAAU,CC3GN,eAAmC,CD4GvC,KAAK,CC5GD,eAAmC,CD6GvC,MAAM,CC7GF,eAAmC,CD8GvC,WAAW,CC9GP,eAAmC,CD+GvC,UAAU,CAAE,MAAM,CAClB,gBAAgB,CAAE,OAAO,CACzB,eAAe,CAAE,eAAe,CAChC,KAAK,CAAE,eAAe,CAK9B,MAAO,CACH,MAAM,CCxHE,IAAmC,CDyH3C,WAAW,CCzHH,IAAmC,CD0H3C,UAAU,CAAE,MAAM,CAClB,gBAAgB,CAAE,OAAO",
"sources": ["../scss/rem.scss","../scss/_util.scss"],
"names": [],
"file": "rem.css"
}

61
src/styles/rem/index.html Normal file
View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>REM布局</title>
<meta charset="utf-8" />
<meta lang="zh-CN" />
<meta
name="viewport"
data-content-max
content="width=device-width,initial-scale=1,user-scalable=no"
/>
<link rel="stylesheet" href="./css/rem.css" />
<script src="./js/rem.min.js"></script>
<!-- <script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script> -->
</head>
<body data-content-max>
<section class="container">
<header>375 * 150</header>
<nav>
<ul>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
</ul>
</nav>
<main>
<h3>填写信息</h3>
<div class="info-items">
<p class="info-item">
<span>姓名</span>
<input type="text" class="info-item__name" placeholder="请填写姓名" />
</p>
<p class="info-item">
<span>手机</span>
<input type="number" class="info-item__tel" placeholder="请填写手机号" />
</p>
</div>
<h3>个人介绍</h3>
<div class="info-items">
<p class="info-item f-p-0">
<textarea class="info-item__intro" placeholder="请填写一段简要的自我介绍"></textarea>
</p>
</div>
<div class="info-confirm">
<a href="javascript:;" class="info-confirm__btn">确认</a>
</div>
</main>
<footer>375 * 75</footer>
</section>
</body>
</html>

70
src/styles/rem/js/rem.js Normal file
View File

@ -0,0 +1,70 @@
!(function () {
let docElem = document.documentElement,
metaElem = document.querySelector('meta[name="viewport"]'),
dpr = window.devicePixelRatio || 1,
// 将页面分为10块
blocks = 10,
// 需要限制的最小宽度
defaultMinWidth = 320,
// 需要限制的最大宽度
defaultMaxWidth = 540,
// 计算的基准值
calcMaxWidth = 9999999;
if (!metaElem) {
metaElem = initMetaViewport();
}
if (metaElem.getAttribute('data-content-max') !== null) {
calcMaxWidth = defaultMaxWidth;
}
// 确保meta[name="viewport"]存在
function initMetaViewport() {
const meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width,initial-scale=1,user-scalable=no');
document.head.appendChild(meta);
return meta;
}
// 大部分dpr为2以下的安卓机型不识别scale需设置不缩放
if (navigator.appVersion.match(/android/gi) && dpr <= 2) {
dpr = 1;
}
setScale(dpr);
// 企业QQ设置了scale后不能完全识别scale此时clientWidth未收到缩放的影响而翻倍需设置不缩放
if (navigator.appVersion.match(/qq\//gi) && docElem.clientWidth <= 360) {
dpr = 1;
setScale(dpr);
}
docElem.setAttribute('data-dpr', dpr);
// 设置缩放
function setScale(dpr) {
metaElem.setAttribute('content', `initial-scale=${1 / dpr},maximum-scale=${1 / dpr},minimum-scale=${1 / dpr},user-scalable=no`);
}
// 设置docElem字体大小
function setFontSize() {
let clientWidth = docElem.clientWidth;
clientWidth = Math.max(clientWidth, defaultMinWidth * dpr);
// 调整计算基准值
if (calcMaxWidth === defaultMaxWidth) {
clientWidth = Math.min(clientWidth, defaultMaxWidth * dpr);
}
docElem.style.fontSize = `${clientWidth / blocks}px`;
}
setFontSize();
window.addEventListener(window.orientationchange ? 'orientationchange' : 'resize', setFontSize, false);
})();

44
src/styles/rem/js/rem.min.js vendored Normal file
View File

@ -0,0 +1,44 @@
!(function () {
let c = document.documentElement,
j = document.querySelector('meta[name="viewport"]'),
h = window.devicePixelRatio || 1,
a = 10,
f = 320,
b = 540,
i = 9999999;
if (!j) {
j = e();
}
if (j.getAttribute('data-content-max') !== null) {
i = b;
}
function e() {
const k = document.createElement('meta');
k.setAttribute('name', 'viewport');
k.setAttribute('content', 'width=device-width,initial-scale=1,user-scalable=no');
document.head.appendChild(k);
return k;
}
if (navigator.appVersion.match(/android/gi) && h <= 2) {
h = 1;
}
g(h);
if (navigator.appVersion.match(/qq\//gi) && c.clientWidth <= 360) {
h = 1;
g(h);
}
c.setAttribute('data-dpr', h);
function g(k) {
j.setAttribute('content', `initial-scale=${1 / k},maximum-scale=${1 / k},minimum-scale=${1 / k},user-scalable=no`);
}
function d() {
let k = c.clientWidth;
k = Math.max(k, f * h);
if (i === b) {
k = Math.min(k, b * h);
}
c.style.fontSize = `${k / a}px`;
}
d();
window.addEventListener(window.orientationchange ? 'orientationchange' : 'resize', d, false);
})();

View File

@ -0,0 +1,95 @@
@charset "UTF-8";
@use 'sass:math';
/* 移动端页面设计稿宽度 */
$design-width: 750;
/* 移动端页面设计稿dpr基准值 */
$design-dpr: 2;
/* 将移动端页面分为10块 */
$blocks: 10;
/* 缩放所支持的设备最小宽度 */
$min-device-width: 320px;
/* 缩放所支持的设备最大宽度 */
$max-device-width: 540px;
/*
rem与px对应关系1rem代表在JS中设置的html font-size值为一块的宽度$rem即为$px对应占多少块
$px $rem
------------- === ------------
$design-width $blocks
*/
/* 单位px转化为rem */
@function px2rem($px) {
@return #{math.div($px, $design-width) * $blocks}rem;
}
/* 单位rem转化为px可用于根据rem单位快速计算原px */
@function rem2px($rem) {
@return #{math.div($rem, $blocks) * $design-width}px;
}
/* html根的宽度定义 */
@mixin root-width() {
body {
@include container-min-width();
&[data-content-max] {
@include container-max-width();
}
}
/* 某些机型虽然设备dpr大于1但识别不了scale缩放这里需要重新设置最小宽度防止出现横向滚动条 */
&[data-dpr='1'] body {
min-width: $min-device-width;
}
}
/* 设置容器拉伸的最小宽度 */
@mixin container-min-width() {
min-width: $min-device-width;
margin-right: auto;
margin-left: auto;
@media (-webkit-device-pixel-ratio: 2) {
min-width: $min-device-width * 2;
}
@media (-webkit-device-pixel-ratio: 3) {
min-width: $min-device-width * 3;
}
}
/* 设置容器拉伸的最大宽度 */
@mixin container-max-width() {
max-width: $max-device-width;
margin-right: auto;
margin-left: auto;
@media (-webkit-device-pixel-ratio: 2) {
max-width: $max-device-width * 2;
}
@media (-webkit-device-pixel-ratio: 3) {
max-width: $max-device-width * 3;
}
}
/* 设置字体大小不使用rem单位 根据dpr值分段调整 */
@mixin font-size($fontSize) {
font-size: $fontSize / $design-dpr;
[data-dpr='2'] & {
font-size: $fontSize / $design-dpr * 2;
}
[data-dpr='3'] & {
font-size: $fontSize / $design-dpr * 3;
}
}

View File

@ -0,0 +1,146 @@
@charset "UTF-8";
@import './util';
.f-p-0 {
padding: 0 !important;
}
html,
body {
padding: 0;
margin: 0;
}
html {
@include root-width();
}
body {
font-family: 'Microsoft YaHei';
font-size: px2rem(28);
background-color: #f8f8f8;
/* rem2px的使用方式仅用于临时计算 */
border-width: rem2px(0.6);
}
.container {
background-color: #fff;
}
header {
height: px2rem(300);
line-height: px2rem(300);
text-align: center;
background-color: #f2f2f2;
}
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
li {
display: flex;
flex-wrap: wrap;
width: px2rem(200);
justify-content: center;
}
.icon {
width: px2rem(120);
height: px2rem(120);
margin-bottom: px2rem(20);
line-height: px2rem(120);
text-align: center;
background-color: #f2f2f2;
}
}
main {
padding: px2rem(20);
h3 {
position: relative;
margin-top: px2rem(50);
margin-left: px2rem(26);
font-size: px2rem(30);
/* 字体也可以选择不使用rem
@include font-size(30px);
*/
&::before {
position: absolute;
left: px2rem(-20);
width: px2rem(12);
height: 100%;
background-color: #fc8200;
content: '';
}
}
}
.info-items {
margin-top: px2rem(20);
margin-bottom: px2rem(20);
}
.info-item {
display: flex;
padding: px2rem(30);
padding-left: 0;
margin-top: px2rem(20);
border: 1px solid #ddd;
span {
min-width: px2rem(120);
text-align: center;
border-right: 1px solid #ddd;
}
input {
width: 100%;
font-size: px2rem(28);
border: none;
outline: none;
caret-color: #fc8200;
}
textarea {
width: 100%;
height: px2rem(250);
padding: px2rem(20);
font-family: 'Microsoft YaHei';
font-size: px2rem(28);
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
}
.info-confirm {
margin-bottom: px2rem(40);
text-align: center;
&__btn {
display: inline-block;
width: px2rem(200);
height: px2rem(80);
margin-top: px2rem(80);
line-height: px2rem(80);
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
}
footer {
height: px2rem(150);
line-height: px2rem(150);
text-align: center;
background-color: #f2f2f2;
}

View File

@ -0,0 +1,230 @@
@charset "UTF-8";
/**
* 获取边框某项对应的值
* @example getBorderItemValue(10px, 2)
* @param {string|list} $item 某一项或多个项的列表
* @param {number} $index 下标
* @return {string} 项值
*/
@function getBorderItemValue($item, $index) {
@if (type-of($item) == list) {
@if ($index > length($item)) {
$index: 1;
}
@return nth($item, $index);
} @else {
@return $item;
}
}
/**
* 判断是否为百分比
* @param {number} $value
* @return {boolean} 是否为百分比
*/
@function is-percentage($value) {
@return type-of($value) == number and unit($value) == '%';
}
/**
* 边框圆角支持单个值与多个值在高清设备下px圆角加倍
* @param {number|list} $radius 圆角值
* @param {number} $ratio 设备像素比
*/
@mixin border-radius($radius: 0, $ratio: 1) {
$border-radius-corner: (top-left, top-right, bottom-right, bottom-left);
/* 列表 按照四个角的顺序匹配 */
@if (type-of($radius) == list) {
@for $i from 1 through length($radius) {
$item: nth($radius, $i);
$corner: nth($border-radius-corner, $i);
/* 普通设备,或者为百分比则直接使用圆角值 */
@if $ratio == 1 or is-percentage($item) {
border-#{$corner}-radius: $item;
}
/* 否则翻$ratio倍 */
@else {
border-#{$corner}-radius: $item * $ratio;
}
}
}
/* 单个值 */
@else {
@if $ratio == 1 or is-percentage($radius) {
border-radius: $radius;
} @else {
border-radius: $radius * $ratio;
}
}
}
/**
* 元素边框
* @param {string|list} $direction: all all或列表时表示多个方向的边框
* @param {string|list} $size: 1px direction的顺序取值
* @param {string|list} $style: solid solid
* @param {string|list} $color: #ddd
* @param {string} $position: relative relative即可
* @param {string} $radius: 0
*/
@mixin border(
$direction: all,
$size: 1px,
$style: solid,
$color: #ddd,
$position: relative,
$radius: 0
) {
/* 多个边框 */
@if $direction == all or type-of($direction) == list {
/* 普通设备 */
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-radius($radius);
@if $direction == all {
border: $size $style $color;
} @else {
@for $i from 1 through length($direction) {
$item: nth($direction, $i);
border-#{$item}: getborderitemvalue($size, $i)
getborderitemvalue($style, $i)
getborderitemvalue($color, $i);
}
}
}
/* 高清设备 */
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-multiple(
$direction: $direction,
$size: $size,
$color: $color,
$position: $position,
$radius: $radius
);
}
}
/* 单个边框 */
@else {
/* 普通设备 */
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
border-#{$direction}: $size $style $color;
}
/* 高清设备 */
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-single(
$direction: $direction,
$size: $size,
$color: $color,
$position: $position
);
}
}
}
/* 实现1物理像素的单条边框线 */
@mixin border-single($direction: bottom, $size: 1px, $color: #ddd, $position: relative) {
position: $position;
&::after {
/* 上下 */
@if ($direction == top or $direction == bottom) {
left: 0;
width: 100%;
height: $size;
@media only screen and (-webkit-device-pixel-ratio: 2) {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
-webkit-transform: scaleY(0.333333333333);
transform: scaleY(0.333333333333);
}
}
/* 左右 */
@else if ($direction == left or $direction == right) {
top: 0;
width: $size;
height: 100%;
@media only screen and (-webkit-device-pixel-ratio: 2) {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
-webkit-transform: scaleX(0.333333333333);
transform: scaleX(0.333333333333);
}
}
position: absolute;
pointer-events: none;
background-color: $color;
content: '';
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
#{$direction}: 0;
}
}
/* 实现1物理像素的多条边框线 */
@mixin border-multiple($direction: all, $size: 1px, $color: #ddd, $position: relative, $radius: 0) {
@include border-radius($radius);
position: $position;
&::after {
@if $direction == all {
border: $size solid $color;
} @else {
@for $i from 1 through length($direction) {
$item: nth($direction, $i);
border-#{$item}: getborderitemvalue($size, $i) solid getborderitemvalue($color, $i);
}
}
position: absolute;
top: 0;
left: 0;
pointer-events: none;
content: '';
box-sizing: border-box;
transform-origin: top left;
@media only screen and (-webkit-device-pixel-ratio: 2) {
@include border-radius($radius, 2);
width: 200%;
height: 200%;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
@include border-radius($radius, 3);
width: 300%;
height: 300%;
-webkit-transform: scale(0.333333333333, 0.333333333333);
transform: scale(0.333333333333, 0.333333333333);
}
}
}

View File

@ -0,0 +1,115 @@
@charset "UTF-8";
@use 'sass:math';
/* 移动端页面设计稿宽度 */
$design-width: 750;
/* 移动端页面设计稿dpr基准值 */
$design-dpr: 2;
/* 将移动端页面分为10块 */
$blocks: 10;
/* 缩放所支持的设备最小宽度 */
$min-device-width: 320px;
/* 缩放所支持的设备最大宽度 */
$max-device-width: 540px;
/*
rem与px对应关系1rem代表html font-size值为一块的宽度$rem即为$px对应占多少块
$px $rem
------------- === ------------
$design-width $blocks
*/
/* html根元素的font-size定义简单地将页面分为$blocks块方便计算 */
@mixin root-font-size() {
font-size: math.div(100vw, $blocks);
/* 最小宽度定义 */
@media screen and (max-width: $min-device-width) {
font-size: math.div($min-device-width, $blocks);
}
body {
@include container-min-width();
}
/* 最大宽度定义 */
&[data-content-max] {
@media screen and (min-width: $max-device-width) {
font-size: math.div($max-device-width, $blocks);
}
body[data-content-max] {
@include container-max-width();
}
}
}
/* 单位px转化为rem */
@function px2rem($px) {
@return #{math.div($px, $design-width) * $blocks}rem;
}
/* 单位rem转化为px可用于根据rem单位快速计算原px */
@function rem2px($rem) {
@return #{math.div($px, $design-width) * $design-width}px;
}
/**
* 实现固定宽高比
* @param {string} $position: relative
* @param {string} $width: 100%
* @param {string} $sub: null
* @param {number} $aspectX: 1
* @param {number} $aspectY: 1
*/
@mixin aspect-ratio($position: relative, $width: 100%, $sub: null, $aspectX: 1, $aspectY: 1) {
@if $sub == null {
$sub: '*';
}
position: $position;
width: $width;
height: 0;
padding-top: percentage($aspectY / $aspectX);
overflow: hidden;
& > #{$sub} {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
/* 设置容器拉伸的最小宽度 */
@mixin container-min-width() {
min-width: $min-device-width;
margin-right: auto;
margin-left: auto;
}
/* 设置容器拉伸的最大宽度 */
@mixin container-max-width() {
max-width: $max-device-width;
margin-right: auto;
margin-left: auto;
}
/* 设置字体大小不使用rem单位 根据dpr值分段调整 */
@mixin font-size($fontSize) {
font-size: $fontSize / $design-dpr;
[data-dpr='2'] & {
font-size: $fontSize / $design-dpr * 2;
}
[data-dpr='3'] & {
font-size: $fontSize / $design-dpr * 3;
}
}

View File

@ -0,0 +1,577 @@
.f-p-0 {
padding: 0 !important;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border {
border: 1px solid #ddd;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border {
position: relative;
border-radius: 0;
}
.f-border::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom {
border-bottom: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom {
position: relative;
}
.f-border-bottom::after {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border-bottom::after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border-bottom::after {
-webkit-transform: scaleY(0.3333333333);
transform: scaleY(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-radius {
border: 1px solid #ddd;
border-top-right-radius: 20px;
border-bottom-right-radius: 30px;
border-bottom-left-radius: 40px;
border-top-left-radius: 10px;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-radius {
position: relative;
border-top-right-radius: 20px;
border-bottom-right-radius: 30px;
border-bottom-left-radius: 40px;
border-top-left-radius: 10px;
}
.f-border-radius::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border-radius::after {
width: 200%;
height: 200%;
border-top-right-radius: 40px;
border-bottom-right-radius: 60px;
border-bottom-left-radius: 80px;
border-top-left-radius: 20px;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border-radius::after {
width: 300%;
height: 300%;
border-top-right-radius: 60px;
border-bottom-right-radius: 90px;
border-bottom-left-radius: 120px;
border-top-left-radius: 30px;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
html,
body {
padding: 0;
margin: 0;
}
html {
font-size: 10vw;
}
html body {
min-width: 320px;
margin-right: auto;
margin-left: auto;
}
@media screen and (max-width: 320px) {
html {
font-size: 32px;
}
}
html[data-content-max] body[data-content-max] {
max-width: 540px;
margin-right: auto;
margin-left: auto;
}
@media screen and (min-width: 540px) {
html[data-content-max] {
font-size: 54px;
}
}
body {
font-family: 'Microsoft YaHei';
font-size: 0.3733333333rem;
background-color: #f8f8f8;
border-width: 45px;
}
.container {
background-color: #fff;
}
header {
height: 4rem;
line-height: 4rem;
text-align: center;
background-color: #f2f2f2;
}
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
}
nav ul li {
display: flex;
flex-wrap: wrap;
width: 2.6666666667rem;
justify-content: center;
}
nav ul .icon {
width: 1.6rem;
height: 1.6rem;
margin-bottom: 0.2666666667rem;
line-height: 1.6rem;
text-align: center;
background-color: #f2f2f2;
}
main {
padding: 0.2666666667rem;
}
main h3 {
position: relative;
margin-top: 0.6666666667rem;
margin-left: 0.3466666667rem;
font-size: 0.4rem;
}
main h3::before {
position: absolute;
left: -0.2666666667rem;
width: 0.16rem;
height: 100%;
background-color: #fc8200;
content: '';
}
.info-items {
margin-top: 0.2666666667rem;
margin-bottom: 0.2666666667rem;
}
.info-item {
display: flex;
padding: 0.4rem;
padding-left: 0;
margin-top: 0.2666666667rem;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel) {
border: 1px solid #ddd;
border-radius: 50px;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel) {
position: relative;
border-radius: 50px;
}
.info-item:not(.info-item__tel)::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel)::after {
width: 200%;
height: 200%;
border-radius: 100px;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item:not(.info-item__tel)::after {
width: 300%;
height: 300%;
border-radius: 150px;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel {
border-bottom: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel {
position: relative;
}
.info-item.info-item__tel::after {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item.info-item__tel::after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item.info-item__tel::after {
-webkit-transform: scaleY(0.3333333333);
transform: scaleY(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:only-of-type {
border: 1px solid #ddd;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:only-of-type {
position: relative;
border-radius: 0;
}
.info-item:only-of-type::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item:only-of-type::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item:only-of-type::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.hover {
border-top: 3px dotted #0f0;
border-right: 2px dotted #ddd;
border-bottom: 1px dotted #0f0;
border-left: 3px dotted #0f0;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.hover {
position: relative;
border-radius: 0;
}
.info-item.hover::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border-top: 3px solid #0f0;
border-right: 2px solid #ddd;
border-bottom: 1px solid #0f0;
border-left: 3px solid #0f0;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item.hover::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item.hover::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
.info-item span {
min-width: 1.6rem;
text-align: center;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span {
border-right: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span {
position: relative;
}
.info-item span::after {
position: absolute;
top: 0;
right: 0;
width: 1px;
height: 100%;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item span::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item span::after {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item span::after {
-webkit-transform: scaleX(0.3333333333);
transform: scaleX(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover {
border-right: 5px solid #0f0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover {
position: relative;
}
.info-item span.hover::after {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
pointer-events: none;
background-color: #0f0;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item span.hover::after {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item span.hover::after {
-webkit-transform: scaleX(0.3333333333);
transform: scaleX(0.3333333333);
}
}
.info-item input {
width: 100%;
font-size: 0.3733333333rem;
border: none;
outline: none;
caret-color: #fc8200;
}
.info-item textarea {
width: 100%;
height: 3.3333333333rem;
padding: 0.2666666667rem;
font-family: 'Microsoft YaHei';
font-size: 0.3733333333rem;
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
.info-confirm {
margin-bottom: 0.5333333333rem;
text-align: center;
}
.info-confirm__btn {
display: inline-block;
width: 2.6666666667rem;
height: 1.0666666667rem;
margin-top: 1.0666666667rem;
line-height: 1.0666666667rem;
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
footer {
height: 2rem;
line-height: 2rem;
text-align: center;
background-color: #f2f2f2;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html data-content-max>
<head>
<title>VW-REM布局</title>
<meta charset="utf-8" />
<meta lang="zh-CN" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<link rel="stylesheet" href="./css/vw-rem.css" />
</head>
<body data-content-max>
<section class="container">
<!-- 此处为固定宽高比的例子 -->
<header class="header">
<div class="header-content">375 * 150</div>
</header>
<nav>
<ul>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon f-border-radius">圆角</span>
<span class="f-border">导航入口</span>
</li>
</ul>
</nav>
<main>
<h3>填写信息</h3>
<div class="info-items">
<p class="info-item">
<span>姓名</span>
<input type="text" class="info-item__name" placeholder="请填写姓名" />
</p>
<p class="info-item info-item__tel">
<span>手机</span>
<input type="number" class="info-item__tel" placeholder="请填写手机号" />
</p>
</div>
<h3>个人介绍</h3>
<div class="info-items">
<p class="info-item f-p-0">
<textarea class="info-item__intro" placeholder="请填写一段简要的自我介绍"></textarea>
</p>
</div>
<div class="info-confirm">
<a href="javascript:;" class="info-confirm__btn">确认</a>
</div>
</main>
<footer>375 * 75</footer>
</section>
</body>
</html>

View File

@ -0,0 +1,222 @@
@charset "UTF-8";
@import 'util';
@import 'border';
.f-p-0 {
padding: 0 !important;
}
.f-border {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid);
}
.f-border-bottom {
@include border($direction: bottom, $size: 1px, $color: #ddd, $style: solid);
}
/* 圆角边框自定义多个角,顺序 */
.f-border-radius {
@include border(
$radius: (
10px,
20px,
30px,
40px,
)
);
}
html,
body {
padding: 0;
margin: 0;
}
html {
@include root-font-size();
}
body {
font-family: 'Microsoft YaHei';
font-size: px2rem(28);
background-color: #f8f8f8;
/* rem2px的使用方式仅用于临时计算 */
border-width: rem2px(0.6);
}
.container {
background-color: #fff;
}
header {
height: px2rem(300);
line-height: px2rem(300);
text-align: center;
background-color: #f2f2f2;
}
/* 容器宽高比 */
// .header {
// @include aspect-ratio(
// // $width: px2rem(600),
// // $sub: ".header-content",
// $aspectX: 375,
// $aspectY: 150
// )
// }
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
li {
display: flex;
flex-wrap: wrap;
width: px2rem(200);
justify-content: center;
}
.icon {
width: px2rem(120);
height: px2rem(120);
margin-bottom: px2rem(20);
line-height: px2rem(120);
text-align: center;
background-color: #f2f2f2;
}
}
main {
padding: px2rem(20);
h3 {
position: relative;
margin-top: px2rem(50);
margin-left: px2rem(26);
font-size: px2rem(30);
/* 字体也可以选择不使用rem
@include font-size(30px);
*/
&::before {
position: absolute;
left: px2rem(-20);
width: px2rem(12);
height: 100%;
background-color: #fc8200;
content: '';
}
}
}
.info-items {
margin-top: px2rem(20);
margin-bottom: px2rem(20);
}
.info-item {
// border: 1px solid #ddd;
display: flex;
padding: px2rem(30);
padding-left: 0;
margin-top: px2rem(20);
/* 多个边框调用 */
&:not(.info-item__tel) {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid, $radius: 50px);
}
&.info-item__tel {
@include border($direction: bottom, $size: 1px, $color: #ddd, $style: solid);
}
&:only-of-type {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid);
}
/* 多个边框的动态更新 */
&.hover {
@include border(
$direction: (
top,
right,
bottom,
left,
),
$size: (
3px,
2px,
1px,
),
$color: (
#0f0,
#ddd,
),
$style: dotted
);
}
span {
/* 单个边框调用 */
@include border($direction: right);
min-width: px2rem(120);
text-align: center;
/* 单个边框的动态更新 */
&.hover {
@include border($direction: right, $size: 5px, $color: #0f0);
}
// border-right: 1px solid #ddd;
}
input {
width: 100%;
font-size: px2rem(28);
border: none;
outline: none;
caret-color: #fc8200;
}
textarea {
width: 100%;
height: px2rem(250);
padding: px2rem(20);
font-family: 'Microsoft YaHei';
font-size: px2rem(28);
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
}
.info-confirm {
margin-bottom: px2rem(40);
text-align: center;
&__btn {
display: inline-block;
width: px2rem(200);
height: px2rem(80);
margin-top: px2rem(80);
line-height: px2rem(80);
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
}
footer {
height: px2rem(150);
line-height: px2rem(150);
text-align: center;
background-color: #f2f2f2;
}

513
src/styles/vw/css/vw.css Normal file
View File

@ -0,0 +1,513 @@
.f-p-0 {
padding: 0 !important;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border {
border: 1px solid #ddd;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border {
position: relative;
border-radius: 0;
}
.f-border::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom {
border-bottom: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom {
position: relative;
}
.f-border-bottom::after {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.f-border-bottom::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border-bottom::after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border-bottom::after {
-webkit-transform: scaleY(0.3333333333);
transform: scaleY(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-radius {
border: 1px solid #ddd;
border-radius: 50%;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.f-border-radius {
position: relative;
border-radius: 50%;
}
.f-border-radius::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.f-border-radius::after {
width: 200%;
height: 200%;
border-radius: 50%;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.f-border-radius::after {
width: 300%;
height: 300%;
border-radius: 50%;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
html,
body {
padding: 0;
margin: 0;
}
body {
font-family: 'Microsoft YaHei';
font-size: 3.7333333333vw;
background-color: #f8f8f8;
border-width: 120px;
}
.container {
background-color: #fff;
}
header {
height: 40vw;
line-height: 40vw;
text-align: center;
background-color: #f2f2f2;
}
.header {
position: relative;
width: 100%;
height: 0;
padding-top: 40%;
overflow: hidden;
}
.header > * {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
}
nav ul li {
display: flex;
flex-wrap: wrap;
width: 26.6666666667vw;
justify-content: center;
}
nav ul .icon {
width: 16vw;
height: 16vw;
margin-bottom: 2.6666666667vw;
line-height: 16vw;
text-align: center;
background-color: #f2f2f2;
}
main {
padding: 2.6666666667vw;
}
main h3 {
position: relative;
margin-top: 6.6666666667vw;
margin-left: 3.4666666667vw;
font-size: 4vw;
}
main h3::before {
position: absolute;
left: -2.6666666667vw;
width: 1.6vw;
height: 100%;
background-color: #fc8200;
content: '';
}
.info-items {
margin-top: 2.6666666667vw;
margin-bottom: 2.6666666667vw;
}
.info-item {
display: flex;
padding: 4vw;
padding-left: 0;
margin-top: 2.6666666667vw;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel) {
border: 1px solid #ddd;
border-radius: 50px;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel) {
position: relative;
border-radius: 50px;
}
.info-item:not(.info-item__tel)::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item:not(.info-item__tel)::after {
width: 200%;
height: 200%;
border-radius: 100px;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item:not(.info-item__tel)::after {
width: 300%;
height: 300%;
border-radius: 150px;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel {
border-bottom: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel {
position: relative;
}
.info-item.info-item__tel::after {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item.info-item__tel::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item.info-item__tel::after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item.info-item__tel::after {
-webkit-transform: scaleY(0.3333333333);
transform: scaleY(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:only-of-type {
border: 1px solid #ddd;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item:only-of-type {
position: relative;
border-radius: 0;
}
.info-item:only-of-type::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border: 1px solid #ddd;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item:only-of-type::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item:only-of-type::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.hover {
border-top: 3px dotted #0f0;
border-right: 2px dotted #ddd;
border-bottom: 1px dotted #0f0;
border-left: 3px dotted #0f0;
border-radius: 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item.hover {
position: relative;
border-radius: 0;
}
.info-item.hover::after {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
border-top: 3px solid #0f0;
border-right: 2px solid #ddd;
border-bottom: 1px solid #0f0;
border-left: 3px solid #0f0;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item.hover::after {
width: 200%;
height: 200%;
border-radius: 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item.hover::after {
width: 300%;
height: 300%;
border-radius: 0;
-webkit-transform: scale(0.3333333333, 0.3333333333);
transform: scale(0.3333333333, 0.3333333333);
}
}
.info-item span {
min-width: 16vw;
text-align: center;
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span {
border-right: 1px solid #ddd;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span {
position: relative;
}
.info-item span::after {
position: absolute;
top: 0;
right: 0;
width: 1px;
height: 100%;
pointer-events: none;
background-color: #ddd;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item span::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item span::after {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item span::after {
-webkit-transform: scaleX(0.3333333333);
transform: scaleX(0.3333333333);
}
}
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover {
border-right: 5px solid #0f0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover {
position: relative;
}
.info-item span.hover::after {
position: absolute;
top: 0;
right: 0;
width: 5px;
height: 100%;
pointer-events: none;
background-color: #0f0;
content: '';
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-min-device-pixel-ratio: 2) {
.info-item span.hover::after {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 2) {
.info-item span.hover::after {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-device-pixel-ratio: 3) {
.info-item span.hover::after {
-webkit-transform: scaleX(0.3333333333);
transform: scaleX(0.3333333333);
}
}
.info-item input {
width: 100%;
font-size: 3.7333333333vw;
border: none;
outline: none;
caret-color: #fc8200;
}
.info-item textarea {
width: 100%;
height: 33.3333333333vw;
padding: 2.6666666667vw;
font-family: 'Microsoft YaHei';
font-size: 3.7333333333vw;
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
.info-confirm {
margin-bottom: 5.3333333333vw;
text-align: center;
}
.info-confirm__btn {
display: inline-block;
width: 26.6666666667vw;
height: 10.6666666667vw;
margin-top: 10.6666666667vw;
line-height: 10.6666666667vw;
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
footer {
height: 20vw;
line-height: 20vw;
text-align: center;
background-color: #f2f2f2;
}

File diff suppressed because one or more lines are too long

58
src/styles/vw/index.html Normal file
View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>VW布局</title>
<meta charset="utf-8" />
<meta lang="zh-CN" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<link rel="stylesheet" href="./css/vw.css" />
</head>
<body>
<section class="container">
<!-- 此处为固定宽高比的例子 -->
<header class="header">
<div class="header-content">固定纵横比 375 * 150</div>
</header>
<nav>
<ul>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon">60 * 60</span>
<span>导航入口</span>
</li>
<li>
<span class="icon f-border-radius">圆角</span>
<span class="f-border">导航入口</span>
</li>
</ul>
</nav>
<main>
<h3>填写信息</h3>
<div class="info-items">
<p class="info-item">
<span>姓名</span>
<input type="text" class="info-item__name" placeholder="请填写姓名" />
</p>
<p class="info-item info-item__tel">
<span>手机</span>
<input type="number" class="info-item__tel" placeholder="请填写手机号" />
</p>
</div>
<h3>个人介绍</h3>
<div class="info-items">
<p class="info-item f-p-0">
<textarea class="info-item__intro" placeholder="请填写一段简要的自我介绍"></textarea>
</p>
</div>
<div class="info-confirm">
<a href="javascript:;" class="info-confirm__btn">确认</a>
</div>
</main>
<footer>375 * 75</footer>
</section>
</body>
</html>

View File

@ -0,0 +1,230 @@
@charset "UTF-8";
/**
* 获取边框某项对应的值
* @example getBorderItemValue(10px, 2)
* @param {string|list} $item 某一项或多个项的列表
* @param {number} $index 下标
* @return {string} 项值
*/
@function getBorderItemValue($item, $index) {
@if (type-of($item) == list) {
@if ($index > length($item)) {
$index: 1;
}
@return nth($item, $index);
} @else {
@return $item;
}
}
/**
* 判断是否为百分比
* @param {number} $value
* @return {boolean} 是否为百分比
*/
@function is-percentage($value) {
@return type-of($value) == number and unit($value) == '%';
}
/**
* 边框圆角支持单个值与多个值在高清设备下px圆角加倍
* @param {number|list} $radius 圆角值
* @param {number} $ratio 设备像素比
*/
@mixin border-radius($radius: 0, $ratio: 1) {
$border-radius-corner: (top-left, top-right, bottom-right, bottom-left);
/* 列表 按照四个角的顺序匹配 */
@if (type-of($radius) == list) {
@for $i from 1 through length($radius) {
$item: nth($radius, $i);
$corner: nth($border-radius-corner, $i);
/* 普通设备,或者为百分比则直接使用圆角值 */
@if $ratio == 1 or is-percentage($item) {
border-#{$corner}-radius: $item;
}
/* 否则翻$ratio倍 */
@else {
border-#{$corner}-radius: $item * $ratio;
}
}
}
/* 单个值 */
@else {
@if $ratio == 1 or is-percentage($radius) {
border-radius: $radius;
} @else {
border-radius: $radius * $ratio;
}
}
}
/**
* 元素边框
* @param {string|list} $direction: all all或列表时表示多个方向的边框
* @param {string|list} $size: 1px direction的顺序取值
* @param {string|list} $style: solid solid
* @param {string|list} $color: #ddd
* @param {string} $position: relative relative即可
* @param {string} $radius: 0
*/
@mixin border(
$direction: all,
$size: 1px,
$style: solid,
$color: #ddd,
$position: relative,
$radius: 0
) {
/* 多个边框 */
@if $direction == all or type-of($direction) == list {
/* 普通设备 */
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-radius($radius);
@if $direction == all {
border: $size $style $color;
} @else {
@for $i from 1 through length($direction) {
$item: nth($direction, $i);
border-#{$item}: getborderitemvalue($size, $i)
getborderitemvalue($style, $i)
getborderitemvalue($color, $i);
}
}
}
/* 高清设备 */
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-multiple(
$direction: $direction,
$size: $size,
$color: $color,
$position: $position,
$radius: $radius
);
}
}
/* 单个边框 */
@else {
/* 普通设备 */
@media not screen and (-webkit-min-device-pixel-ratio: 2) {
border-#{$direction}: $size $style $color;
}
/* 高清设备 */
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
@include border-single(
$direction: $direction,
$size: $size,
$color: $color,
$position: $position
);
}
}
}
/* 实现1物理像素的单条边框线 */
@mixin border-single($direction: bottom, $size: 1px, $color: #ddd, $position: relative) {
position: $position;
&::after {
/* 上下 */
@if ($direction == top or $direction == bottom) {
left: 0;
width: 100%;
height: $size;
@media only screen and (-webkit-device-pixel-ratio: 2) {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
-webkit-transform: scaleY(0.333333333333);
transform: scaleY(0.333333333333);
}
}
/* 左右 */
@else if ($direction == left or $direction == right) {
top: 0;
width: $size;
height: 100%;
@media only screen and (-webkit-device-pixel-ratio: 2) {
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
-webkit-transform: scaleX(0.333333333333);
transform: scaleX(0.333333333333);
}
}
position: absolute;
pointer-events: none;
background-color: $color;
content: '';
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
#{$direction}: 0;
}
}
/* 实现1物理像素的多条边框线 */
@mixin border-multiple($direction: all, $size: 1px, $color: #ddd, $position: relative, $radius: 0) {
@include border-radius($radius);
position: $position;
&::after {
@if $direction == all {
border: $size solid $color;
} @else {
@for $i from 1 through length($direction) {
$item: nth($direction, $i);
border-#{$item}: getborderitemvalue($size, $i) solid getborderitemvalue($color, $i);
}
}
position: absolute;
top: 0;
left: 0;
pointer-events: none;
content: '';
box-sizing: border-box;
-webkit-transform-origin: top left;
@media only screen and (-webkit-device-pixel-ratio: 2) {
@include border-radius($radius, 2);
width: 200%;
height: 200%;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
}
@media only screen and (-webkit-device-pixel-ratio: 3) {
@include border-radius($radius, 3);
width: 300%;
height: 300%;
-webkit-transform: scale(0.333333333333, 0.333333333333);
transform: scale(0.333333333333, 0.333333333333);
}
}
}

View File

@ -0,0 +1,66 @@
@charset "UTF-8";
/* 移动端页面设计稿宽度 */
$design-width: 750;
/* 移动端页面设计稿dpr基准值 */
$design-dpr: 2;
/*
vw与px对应关系100vw为视窗宽度$vw即为$px对应占多宽
$px $vw
------------- === ------------
$design-width 100vw
*/
/* 单位px转化为vw */
@function px2vw($px) {
@return ($px / $design-width) * 100vw;
}
/* 单位vw转化为px可用于根据vw单位快速计算原px */
@function vw2px($vw) {
@return #{($vw / 100) * $design-width}px;
}
/**
* 实现固定宽高比
* @param {string} $position: relative
* @param {string} $width: 100%
* @param {string} $sub: null
* @param {number} $aspectX: 1
* @param {number} $aspectY: 1
*/
@mixin aspect-ratio($position: relative, $width: 100%, $sub: null, $aspectX: 1, $aspectY: 1) {
@if $sub == null {
$sub: '*';
}
position: $position;
width: $width;
height: 0;
padding-top: percentage($aspectY / $aspectX);
overflow: hidden;
& > #{$sub} {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}
/* 设置字体大小不使用rem单位 根据dpr值分段调整 */
@mixin font-size($fontSize) {
font-size: $fontSize / $design-dpr;
[data-dpr='2'] & {
font-size: $fontSize / $design-dpr * 2;
}
[data-dpr='3'] & {
font-size: $fontSize / $design-dpr * 3;
}
}

211
src/styles/vw/scss/vw.scss Normal file
View File

@ -0,0 +1,211 @@
@charset "UTF-8";
@import './util';
@import './border';
.f-p-0 {
padding: 0 !important;
}
.f-border {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid);
}
.f-border-bottom {
@include border($direction: bottom, $size: 1px, $color: #ddd, $style: solid);
}
/* 圆角边框百分比 */
.f-border-radius {
@include border($direction: all, $radius: 50%);
}
html,
body {
padding: 0;
margin: 0;
}
body {
font-family: 'Microsoft YaHei';
font-size: px2vw(28);
background-color: #f8f8f8;
/* vw2px的使用方式仅用于临时计算 */
border-width: vw2px(16);
}
.container {
background-color: #fff;
}
header {
height: px2vw(300);
line-height: px2vw(300);
text-align: center;
background-color: #f2f2f2;
}
/* 容器宽高比 */
.header {
@include aspect-ratio(
// $width: px2vw(600),
// $sub: ".header-content",
$aspectX: 375,
$aspectY: 150
);
}
nav ul {
display: flex;
justify-content: space-around;
padding: 0;
li {
display: flex;
flex-wrap: wrap;
width: px2vw(200);
justify-content: center;
}
.icon {
width: px2vw(120);
height: px2vw(120);
margin-bottom: px2vw(20);
line-height: px2vw(120);
text-align: center;
background-color: #f2f2f2;
}
}
main {
padding: px2vw(20);
h3 {
position: relative;
margin-top: px2vw(50);
margin-left: px2vw(26);
font-size: px2vw(30);
/* 字体也可以选择不使用rem
@include font-size(30px);
*/
&::before {
position: absolute;
left: px2vw(-20);
width: px2vw(12);
height: 100%;
background-color: #fc8200;
content: '';
}
}
}
.info-items {
margin-top: px2vw(20);
margin-bottom: px2vw(20);
}
.info-item {
// border: 1px solid #ddd;
display: flex;
padding: px2vw(30);
padding-left: 0;
margin-top: px2vw(20);
/* 多个边框调用 */
&:not(.info-item__tel) {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid, $radius: 50px);
}
&.info-item__tel {
@include border($direction: bottom, $size: 1px, $color: #ddd, $style: solid);
}
&:only-of-type {
@include border($direction: all, $size: 1px, $color: #ddd, $style: solid);
}
/* 多个边框的动态更新 */
&.hover {
@include border(
$direction: (
top,
right,
bottom,
left,
),
$size: (
3px,
2px,
1px,
),
$color: (
#0f0,
#ddd,
),
$style: dotted
);
}
span {
/* 单个边框调用 */
@include border($direction: right);
min-width: px2vw(120);
text-align: center;
/* 单个边框的动态更新 */
&.hover {
@include border($direction: right, $size: 5px, $color: #0f0);
}
// border-right: 1px solid #ddd;
}
input {
width: 100%;
font-size: px2vw(28);
border: none;
outline: none;
caret-color: #fc8200;
}
textarea {
width: 100%;
height: px2vw(250);
padding: px2vw(20);
font-family: 'Microsoft YaHei';
font-size: px2vw(28);
-webkit-text-size-adjust: none;
text-size-adjust: none;
border: none;
caret-color: #fc8200;
}
}
.info-confirm {
margin-bottom: px2vw(40);
text-align: center;
&__btn {
display: inline-block;
width: px2vw(200);
height: px2vw(80);
margin-top: px2vw(80);
line-height: px2vw(80);
color: #fff !important;
text-align: center;
text-decoration: none !important;
background-color: #fc8200;
}
}
footer {
height: px2vw(150);
line-height: px2vw(150);
text-align: center;
background-color: #f2f2f2;
}

128
src/utils/Storage.ts Normal file
View File

@ -0,0 +1,128 @@
// 默认缓存期限为7天
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
/**
*
* @param {string=} prefixKey -
* @param {Object} [storage=localStorage] - sessionStorage | localStorage
*/
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
/**
*
* @class Storage
*/
const Storage = class {
private storage = storage;
private prefixKey?: string = prefixKey;
private getKey(key: string) {
return `${this.prefixKey}${key}`.toUpperCase();
}
/**
* @description
* @param {string} key
* @param {*} value
* @param expire
*/
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
const stringData = JSON.stringify({
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
});
this.storage.setItem(this.getKey(key), stringData);
}
/**
*
* @param {string} key
* @param {*=} def
*/
get<T = any>(key: string, def: any = null): T {
const item = this.storage.getItem(this.getKey(key));
if (item) {
try {
const data = JSON.parse(item);
const { value, expire } = data;
// 在有效期内直接返回
if (expire === null || expire >= Date.now()) {
return value;
}
this.remove(this.getKey(key));
} catch (e) {
return def;
}
}
return def;
}
/**
*
* @param {string} key
*/
remove(key: string) {
console.log(key, '搜索');
this.storage.removeItem(this.getKey(key));
}
/**
*
* @memberOf Cache
*/
clear(): void {
this.storage.clear();
}
/**
* cookie
* @param {string} name cookie
* @param {*} value cookie
* @param {number=} expire
*
* @example
*/
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
}
/**
* cookie值
* @param name
*/
getCookie(name: string): string {
const cookieArr = document.cookie.split('; ');
for (let i = 0, length = cookieArr.length; i < length; i++) {
const kv = cookieArr[i].split('=');
if (kv[0] === this.getKey(name)) {
return kv[1];
}
}
return '';
}
/**
* cookie
* @param {string} key
*/
removeCookie(key: string) {
this.setCookie(key, 1, -1);
}
/**
* cookie使cookie失效
*/
clearCookie(): void {
const keys = document.cookie.match(/[^ =;]+(?==)/g);
if (keys) {
for (let i = keys.length; i--; ) {
document.cookie = `${keys[i]}=0;expire=${new Date(0).toUTCString()}`;
}
}
}
};
return new Storage();
};
export const Storage = createStorage();
export default Storage;

32
src/utils/httpEnum.ts Normal file
View File

@ -0,0 +1,32 @@
/**
* @description:
*/
export enum ResultEnum {
SUCCESS = 0,
ERROR = 7,
}
/**
* @description:
*/
export enum RequestEnum {
GET = 'GET',
POST = 'POST',
PATCH = 'PATCH',
PUT = 'PUT',
DELETE = 'DELETE',
}
/**
* @description: contentTyp类型
*/
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// json
TEXT = 'text/plain;charset=UTF-8',
// form-data 一般配合qs
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
// form-data 上传
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}

13
src/utils/importAll.ts Normal file
View File

@ -0,0 +1,13 @@
/**
* @description
* @param pathRule eg: pathRule = './modules/*.ts'
*/
export const importAll = (pathRule: string) => {
const allModules = import.meta.globEager(pathRule);
const modules = {} as any;
Object.keys(allModules).forEach((path) => {
const fileName = path.replace(/(.*\/)*([^.]+).*/gi, '$2');
modules[fileName] = allModules[path].default;
});
return modules;
};

12
src/utils/index.ts Normal file
View File

@ -0,0 +1,12 @@
//秒转化成 时分秒
export function second(second: number) {
second = second || 0;
if (second === 0 || second === Infinity || second.toString() === 'NaN') {
return '00:00';
}
const add0 = (num: number) => (num < 10 ? `0${num}` : `${num}`);
const hour = Math.floor(second / 3600);
const min = Math.floor((second - hour * 3600) / 60);
const sec = Math.floor(second - hour * 3600 - min * 60);
return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':');
}

139
src/utils/request.ts Normal file
View File

@ -0,0 +1,139 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showToast, showLoadingToast, closeToast } from 'vant';
import { ContentTypeEnum } from './httpEnum';
import { backFillLoginData } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
import { getLogin } from '@/api/demo';
// 超文本传输协议
let href = ref('http:');
if (location.href.includes('https:')) {
href.value = 'https:';
}
// 创建一个axios实例
const service = axios.create({
baseURL: `${href.value}${import.meta.env.VITE_BASE_API as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 20000, // 请求超时时间
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
interface BaseResponse<T = any> {
code: number;
data: T;
msg: string;
}
// request请求拦截器
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
}
const userStore = useUserStore();
if ((localStorage.getItem('rt_token') || userStore.token) && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('rt_token') || userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
let parameter = `open_id=${localStorage.getItem('rt_openid') || ''}&from_user_id=${localStorage.getItem('rt_from_user_id') || ''}&from_open_id=${localStorage.getItem('rt_from_open_id') || ''}&invite_user_id=${localStorage.getItem('rt_invite_user_id') || ''}&from_source=oa&from_type=${localStorage.getItem('rt_from_type') || ''}`;
if (config.url?.includes('?')) {
config.url = `${config.url}&${parameter}`;
} else {
config.url = `${config.url}?${parameter}`;
}
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
const data = config.data;
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
if (ContentTypeEnum.FORM_DATA == contentType) {
const fd = new FormData();
Object.keys(data).forEach((key) => fd.append(key, data[key]));
config.data = fd;
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
config.data = qs.stringify(config.data);
}
}
return config;
},
(error) => {
// 处理请求错误
console.log(error, '*--------*');
return Promise.reject(error);
},
);
// response相应拦截器
const error = ref(1);
service.interceptors.response.use(
(response) => {
const userStore = useUserStore();
const res = response.data;
if (res.code === 1) {
closeToast();
// 服务器访问出错了~
showToast(res.message);
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
closeToast();
error.value += 1;
if (error.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('rt_token');
getLogin().then((res) => {
let result = res.data;
backFillLoginData(result, userStore);
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
closeToast();
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
window.location.replace(`https://health.ufutx.com/go/api/h5/v1/wechat/auth?forwardurl=${encodeURIComponent(location.href)}`);
}
return Promise.resolve(response);
} else {
closeToast();
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showToast('网络异常,请稍后重试!');
}
console.log(`err${error}`);
return Promise.reject(error);
},
);
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => {
return new Promise((resolve, reject) => {
service
.request<BaseResponse<T>>(config)
.then((res) => resolve(res.data))
.catch((err) => reject(err));
});
};
export default request;

141
src/utils/requestApp.ts Normal file
View File

@ -0,0 +1,141 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showToast, showLoadingToast, closeToast } from 'vant';
import { ContentTypeEnum } from './httpEnum';
import { backFillLoginData, CutOutQuery } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
import { getLogin } from '@/api/demo';
// 超文本传输协议
let href = ref('http:');
if (location.href.includes('https:')) {
href.value = 'https:';
}
// 创建一个axios实例
const service = axios.create({
baseURL: `${href.value}${import.meta.env.VITE_BASE_API_go as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 20000, // 请求超时时间
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
interface BaseResponse<T = any> {
code: number;
data: T;
msg: string;
}
// request请求拦截器
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
}
// const userStore = useUserStore();
if ((localStorage.getItem('app_token') || import.meta.env.MODE === 'development') && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('app_token') || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJDcmVhdGVkQXQiOjE3MjMxOTA0MDIsIkV4cGlyZWRBdCI6MTcyNTc4MjQwMiwiVmVyc2lvbiI6MTAwLCJVc2VyaWQiOjIzNSwiZXhwIjoyNTg3MTkwNDAyfQ.AloagIYDshky0jxn-aGUwWloU8GWbRAQR0cKURIZqNk'}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
let parameter = `from_user_id=${localStorage.getItem('rt_from_user_id') || ''}&invite_user_id=${localStorage.getItem('rt_invite_user_id') || ''}&referrer_user_id=${localStorage.getItem('rt_referrer_user_id') || ''}&from_source=rt&from_type=${localStorage.getItem('rt_from_type') || ''}`;
if (config.url?.includes('?')) {
config.url = `${config.url}&${parameter}`;
} else {
config.url = `${config.url}?${parameter}`;
}
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
const data = config.data;
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
if (ContentTypeEnum.FORM_DATA == contentType) {
const fd = new FormData();
Object.keys(data).forEach((key) => fd.append(key, data[key]));
config.data = fd;
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
config.data = qs.stringify(config.data);
}
}
return config;
},
(error) => {
// 处理请求错误
console.log(error, '*--------*');
return Promise.reject(error);
},
);
// response相应拦截器
const error = ref(1);
service.interceptors.response.use(
(response) => {
const userStore = useUserStore();
const res = response.data;
if (res.code === 1) {
closeToast();
// 服务器访问出错了~
showToast(`${res.message}`);
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
closeToast();
error.value += 1;
if (error.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('rt_token');
getLogin().then((res) => {
let result = res.data;
backFillLoginData(result, userStore);
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
closeToast();
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
localStorage.setItem('rt_openid', 'oHGap6DNY15882HSxh00rtqxaTHU');
} else {
let url = `${CutOutQuery(location.href, 'url')}`;
window.location.replace(`https://health.ufutx.com/go/api/h5/v1/wechat/auth?forwardurl=${encodeURIComponent(url)}`);
}
return Promise.resolve(response);
} else {
closeToast();
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showToast('网络异常,请稍后重试!');
}
console.log(`err${error}`);
return Promise.reject(error);
},
);
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => {
return new Promise((resolve, reject) => {
service
.request<BaseResponse<T>>(config)
.then((res) => resolve(res.data))
.catch((err) => reject(err));
});
};
export default request;

141
src/utils/requestAppPHP.ts Normal file
View File

@ -0,0 +1,141 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showToast, showLoadingToast, closeToast } from 'vant';
import { ContentTypeEnum } from './httpEnum';
import { backFillLoginData, CutOutQuery } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
import { getLogin } from '@/api/demo';
// 超文本传输协议
let href = ref('http:');
if (location.href.includes('https:')) {
href.value = 'https:';
}
// 创建一个axios实例
const service = axios.create({
baseURL: `${href.value}${import.meta.env.VITE_BASE_API as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 20000, // 请求超时时间
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
interface BaseResponse<T = any> {
code: number;
data: T;
msg: string;
}
// request请求拦截器
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
}
// const userStore = useUserStore();
if ((localStorage.getItem('app_token') || import.meta.env.MODE === 'development') && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('app_token') || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJDcmVhdGVkQXQiOjE3MjMxOTA0MDIsIkV4cGlyZWRBdCI6MTcyNTc4MjQwMiwiVmVyc2lvbiI6MTAwLCJVc2VyaWQiOjIzNSwiZXhwIjoyNTg3MTkwNDAyfQ.AloagIYDshky0jxn-aGUwWloU8GWbRAQR0cKURIZqNk'}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
let parameter = `from_user_id=${localStorage.getItem('rt_from_user_id') || ''}&invite_user_id=${localStorage.getItem('rt_invite_user_id') || ''}&referrer_user_id=${localStorage.getItem('rt_referrer_user_id') || ''}&from_source=rt&from_type=${localStorage.getItem('rt_from_type') || ''}`;
if (config.url?.includes('?')) {
config.url = `${config.url}&${parameter}`;
} else {
config.url = `${config.url}?${parameter}`;
}
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
const data = config.data;
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
if (ContentTypeEnum.FORM_DATA == contentType) {
const fd = new FormData();
Object.keys(data).forEach((key) => fd.append(key, data[key]));
config.data = fd;
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
config.data = qs.stringify(config.data);
}
}
return config;
},
(error) => {
// 处理请求错误
console.log(error, '*--------*');
return Promise.reject(error);
},
);
// response相应拦截器
const error = ref(1);
service.interceptors.response.use(
(response) => {
const userStore = useUserStore();
const res = response.data;
if (res.code === 1) {
closeToast();
// 服务器访问出错了~
showToast(`${res.message}`);
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
closeToast();
error.value += 1;
if (error.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('rt_token');
getLogin().then((res) => {
let result = res.data;
backFillLoginData(result, userStore);
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
closeToast();
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
localStorage.setItem('rt_openid', 'oHGap6DNY15882HSxh00rtqxaTHU');
} else {
let url = `${CutOutQuery(location.href, 'url')}`;
window.location.replace(`https://health.ufutx.com/go/api/h5/v1/wechat/auth?forwardurl=${encodeURIComponent(url)}`);
}
return Promise.resolve(response);
} else {
closeToast();
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showToast('网络异常,请稍后重试!');
}
console.log(`err${error}`);
return Promise.reject(error);
},
);
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => {
return new Promise((resolve, reject) => {
service
.request<BaseResponse<T>>(config)
.then((res) => resolve(res.data))
.catch((err) => reject(err));
});
};
export default request;

152
src/utils/requestGo.ts Normal file
View File

@ -0,0 +1,152 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showToast, showLoadingToast, closeToast } from 'vant';
import { ContentTypeEnum } from './httpEnum';
import { backFillLoginData, CutOutQuery, examineRegister } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
import { getLogin } from '@/api/demo';
import router from '@/router';
// 超文本传输协议
let href = ref('http:');
if (location.href.includes('https:')) {
href.value = 'https:';
}
// 创建一个axios实例
const service = axios.create({
baseURL: `${import.meta.env.VITE_BASE_API_go as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 20000, // 请求超时时间
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
interface BaseResponse<T = any> {
code: number;
data: T;
msg: string;
}
// request请求拦截器
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
}
const userStore = useUserStore();
if ((localStorage.getItem('rt_token') || userStore.token) && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('rt_token') || userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
let parameter = `open_id=${localStorage.getItem('rt_openid') || ''}&from_user_id=${localStorage.getItem('rt_from_user_id') || ''}&from_open_id=${localStorage.getItem('rt_from_open_id') || ''}&invite_user_id=${localStorage.getItem('rt_invite_user_id') || ''}&referrer_user_id=${localStorage.getItem('rt_referrer_user_id') || ''}&from_source=rt&from_type=${localStorage.getItem('rt_from_type') || ''}`;
if (config.url?.includes('?')) {
config.url = `${config.url}&${parameter}`;
} else {
config.url = `${config.url}?${parameter}`;
}
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
const data = config.data;
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
if (ContentTypeEnum.FORM_DATA == contentType) {
const fd = new FormData();
Object.keys(data).forEach((key) => fd.append(key, data[key]));
config.data = fd;
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
config.data = qs.stringify(config.data);
}
}
return config;
},
(error) => {
// 处理请求错误
console.log(error, '*--------*');
return Promise.reject(error);
},
);
// response相应拦截器
const error = ref(1);
service.interceptors.response.use(
(response) => {
const userStore = useUserStore();
const res = response.data;
if (res.code === 1) {
closeToast();
// 服务器访问出错了~
showToast(res.message);
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
closeToast();
error.value += 1;
if (error.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('rt_token');
getLogin().then((res) => {
let result = res.data;
// if (!res.data.id) {
// examineRegister(router.currentRoute.value);
// return;
// }
backFillLoginData(result, userStore);
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
closeToast();
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
localStorage.setItem('rt_openid', 'oHGap6DNY15882HSxh00rtqxaTHU');
} else {
let url = `${CutOutQuery(location.href, 'url')}`;
console.log(url, 'u---');
if (res.message == '微信授权失败, 缺少openid') {
window.location.replace(`https://health.ufutx.cn/go/api/h5/v1/wechat/auth?forwardurl=${encodeURIComponent(url)}`);
} else {
examineRegister(router.currentRoute.value);
return;
}
}
return Promise.resolve(response);
} else {
closeToast();
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showToast('网络异常,请稍后重试!');
}
console.log(`err${error}`);
return Promise.reject(error);
},
);
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => {
return new Promise((resolve, reject) => {
service
.request<BaseResponse<T>>(config)
.then((res) => resolve(res.data))
.catch((err) => reject(err));
});
};
export default request;

86
src/utils/service.ts Normal file
View File

@ -0,0 +1,86 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showToast, showLoadingToast, closeToast } from 'vant';
import { ContentTypeEnum } from './httpEnum';
import { useUserStore } from '@/store/modules/user';
const service = axios.create({
// VITE_API_URL || VITE_BASE_API
baseURL: import.meta.env.VITE_BASE_API as string, // url = base api url + request url
withCredentials: false, // send cookies when cross-domain requests
timeout: 15000, // request timeout
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
// request拦截器 request interceptor
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '上传中...',
forbidClick: true,
duration: 0,
});
}
const userStore = useUserStore();
if (userStore.token && config.headers) {
config.headers['Authorization'] = `Bearer ${userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
const data = config.data;
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
if (ContentTypeEnum.FORM_DATA == contentType) {
const fd = new FormData();
Object.keys(data).forEach((key) => fd.append(key, data[key]));
config.data = fd;
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
config.data = qs.stringify(config.data);
}
}
return config;
},
(error) => {
// do something with request error
console.log(error); // for debug
return Promise.reject(error);
},
);
const error = ref(1);
// respone拦截器
service.interceptors.response.use(
(response) => {
closeToast();
const res = response.data;
if (res.code === 1) {
// 服务器访问出错了~
showToast(res.message);
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
error.value += 1;
if (error.value <= 2) {
console.log(error.value, '登录失效,重新登录');
}
return Promise.reject(res.message);
} else {
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showToast('请求超时!');
}
console.log(`err${error}`); // for debug
return Promise.reject(error);
},
);
export default service;

View File

@ -0,0 +1,213 @@
<template>
<div class="ui-add-address">
<div class="ui-mb-40">
<div class="ui-address-box">
<div class="ui-address-list">
<div class="ui-address-name color3 font_28 bold">姓名</div>
<van-field v-model="myAddress.name" class="ui-input f-fcc font_28 color3" placeholder="收货人姓名" />
</div>
<div class="ui-address-list">
<div class="ui-address-name color3 font_28 bold">电话号码</div>
<van-field v-model="myAddress.mobile" type="number" class="ui-input f-fcc font_28 color3" placeholder="收货人手机号" />
</div>
<div class="ui-address-list">
<div class="ui-address-name color3 font_28 bold">所在地址</div>
<van-field v-model="myAddress.more_address" readonly class="ui-input f-fcc font_28 color3" placeholder="请选择地址" @click="showAddress = true" />
</div>
<div class="ui-address-list ui-address-list-none">
<div class="ui-address-name ui-address-nameV2 color3 font_28 bold">详情地址</div>
<van-field v-model="myAddress.detail_address" type="textarea" rows="2" class="ui-input f-fcc font_28 color3" placeholder="街道门牌、楼层房间号等信息" />
</div>
</div>
</div>
<div class="ui-add-btn font_32 colorF text-center" @click="toAddAddress">保存</div>
<van-popup v-model:show="showAddress" round position="bottom" :duration="0.5">
<van-picker v-model="payValue" title="选择地址" :columns="options" @confirm="onConfirm" @cancel="showAddress = false" />
<!-- <van-cascader-->
<!-- v-model="cascaderValue"-->
<!-- title="请选择所在地区"-->
<!-- :options="options"-->
<!-- @close="show = false"-->
<!-- @finish="onFinish"-->
<!-- />-->
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useCascaderAreaData } from '@vant/area-data';
import { showToast } from 'vant';
import requestGo from '@/utils/requestApp';
import request from '@/utils/request';
import router from '@/router';
defineOptions({ name: 'AppMyAddress' });
const id = ref<string>('');
const throttle = ref(true);
const myAddress = ref<any>({});
const addressList = ref([]);
const showAddress = ref(false);
const payValue = ref<any[]>([]);
const options = useCascaderAreaData();
//
const getDetail = () => {
requestGo({ url: `/app/user/address/get/detail/${id.value}`, method: 'get' })
.then((res) => {
let result = res.data;
myAddress.value = result;
if (myAddress.value.province) {
myAddress.value.more_address = `${myAddress.value.province}/${myAddress.value.city}/${myAddress.value.county}`;
}
payValue.value = [`${myAddress.value.province_pid}`, `${myAddress.value.city_pid}`, `${myAddress.value.county_pid}`];
console.log(payValue.value, ' payValue.value');
})
.catch((err) => {
console.log(err);
});
};
const toAddAddress = () => {
let data = {
name: myAddress.value.name,
area_code: 86,
mobile: myAddress.value.mobile,
province: myAddress.value.province,
city: myAddress.value.city,
county: myAddress.value.county,
province_pid: myAddress.value.province_pid - 0,
city_pid: myAddress.value.city_pid - 0,
county_pid: myAddress.value.county_pid - 0,
detail_address: myAddress.value.detail_address,
};
if (id.value == '0') {
data.is_default = 0;
requestGo({ url: `/app/user/address/add`, data, method: 'post' })
.then((res) => {
showToast('添加成功');
setTimeout(() => {
router.go(-1);
}, 1200);
})
.catch((err) => {
console.log(err);
});
} else {
data.is_default = myAddress.value.is_default;
requestGo({ url: `/app/user/address/update/${id.value}`, data, method: 'put' })
.then((res) => {
showToast('修改成功');
setTimeout(() => {
router.go(-1);
}, 1200);
})
.catch((err) => {
console.log(err);
});
}
};
const getAddress = () => {
request({ url: `get/china`, method: 'get' }).then((res) => {
addressList.value = res.data.map((item) => {
return {
value: item.id,
text: item.name,
children: getChildren(item.city),
};
});
});
};
const getChildren = (child) => {
let children = child.map((item) => {
return {
value: item.id,
text: item.name,
children: getChildrenV2(item.county),
};
});
return children;
};
const getChildrenV2 = (child) => {
let children = child.map((item) => {
return {
value: item.id,
text: item.name,
};
});
return children;
};
//
const onConfirm = ({ selectedValues, selectedOptions }) => {
console.log(selectedOptions, 'selectedValues');
console.log(selectedValues, 'selectedValues');
myAddress.value.province = selectedOptions[0].text;
myAddress.value.province_pid = selectedOptions[0].value;
myAddress.value.city = selectedOptions[1].text;
myAddress.value.city_pid = selectedOptions[1].value;
myAddress.value.county = selectedOptions[2].text;
myAddress.value.county_pid = selectedOptions[2].value;
myAddress.value.more_address = selectedOptions.map((option) => option.text).join('/');
showAddress.value = false;
// payType.value = selectedValues[0];
};
onMounted(() => {
let route = router.currentRoute.value.query as any;
id.value = route.id;
if (id.value !== '0') {
getDetail();
}
getAddress();
});
</script>
<style scoped lang="scss">
.ui-add-address {
background: #f6f6f6;
overflow-y: auto;
height: 100vh;
}
.ui-address-box {
margin: px2rem(16) px2rem(32);
padding: 0 px2rem(24);
width: px2rem(638);
background: #ffffff;
border-radius: px2rem(16);
.ui-address-list {
padding: px2rem(22) 0;
display: flex;
align-items: center;
border-bottom: px2rem(2) solid #d8d8d8;
width: 100%;
.ui-address-name {
//padding-top: px2rem(32);
//padding-bottom: px2rem(32);
width: px2rem(112);
line-height: px2rem(28);
}
.ui-input {
flex: 1;
border-radius: px2rem(16);
line-height: px2rem(28);
//padding-top: px2rem(26);
//padding-bottom: px2rem(26);
}
}
.ui-address-list-none {
border: none;
align-items: flex-start;
.ui-address-nameV2 {
padding-top: px2rem(20);
}
}
}
.ui-add-btn {
position: fixed;
bottom: px2rem(84);
left: 50%;
transform: translateX(-50%);
width: px2rem(686);
height: px2rem(88);
line-height: px2rem(88);
background: linear-gradient(140deg, #18ca6e 0%, #0aa555 100%);
border-radius: px2rem(340);
}
</style>

View File

@ -0,0 +1,165 @@
<template>
<div class="ui-my-address">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" @load="getList">
<div class="ui-mb-40">
<div v-for="(item, index) in myAddress" :key="index" class="ui-address-box" @click="selectAddress(item, '1')">
<img v-if="item.is_default" class="ui-select-icon" src="https://image.fulllinkai.com/202408/09/1d8c1f01ee3e197e38e37f456bd5238f.png" alt="" />
<img v-else class="ui-select-icon" src="https://image.fulllinkai.com/202408/09/d7d23faeba28ecfabf21df4a8aa79cce.png" alt="" />
<div class="ui-address-message">
<div class="ui-address-use f-fc">
<div class="ui-address-name ui-mr-32 font_32 ellipsis_1">{{ item.name }}</div>
<div class="font_32 color3 ui-mt-4">{{ item.mobile }}</div>
</div>
<div class="ui-address-content font_24 ellipsis_1">{{ item.province + item.city + item.county + item.detail_address }}</div>
</div>
<img class="ui-edit-icon" src="https://image.fulllinkai.com/202408/09/cc1fc5018f9def521b5d6d2f8fe4be86.png" alt="" @click.stop="toAddAddress(item.id)" />
</div>
</div>
</van-list>
</van-pull-refresh>
<div class="ui-add-address font_32 colorF text-center" @click="toAddAddress('0')">添加地址</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showToast } from 'vant';
import requestGo from '@/utils/requestApp';
import router from '@/router';
defineOptions({ name: 'AppMyAddress' });
const refreshing = ref(false); // false
const finished = ref(false); // true
const loading = ref(false); // false
const throttle = ref(true);
const myAddress = ref<any[]>([]);
const selectAddress = (e, type) => {
if (throttle.value) {
let data = {
name: e.name,
area_code: 86,
mobile: e.mobile,
province: e.province,
city: e.city,
county: e.county,
province_pid: e.province_pid - 0,
city_pid: e.city_pid - 0,
county_pid: e.county_pid - 0,
detail_address: e.detail_address,
is_default: 1,
};
throttle.value = false;
requestGo({ url: `/app/user/address/update/${e.id}`, data, method: 'put' })
.then((res) => {
if (type != '0') {
// showToast('');
setTimeout(() => {
localStorage.setItem('logsiticsAddress', JSON.stringify(data));
router.go(-1);
}, 300);
}
// getList();
})
.catch((err) => {
console.log(err);
})
.finally(() => {
throttle.value = true;
});
}
};
//
const getList = () => {
requestGo({ url: `/app/user/address/get`, method: 'get' })
.then((res) => {
let result = res.data;
myAddress.value = result.data;
if (myAddress.value && myAddress.value.length == 1 && myAddress.value.is_default == 0) {
selectAddress(myAddress.value[0], '0');
}
console.log(result);
finished.value = true;
refreshing.value = false;
loading.value = false;
})
.catch((err) => {
console.log(err);
});
};
const toAddAddress = (id) => {
router.push({
name: 'appAddAddress',
query: { id },
});
};
//
const onRefresh = () => {
// loadingState.value = false;
myAddress.value = [];
// page.value = 1;
// noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
onMounted(() => {
console.log(router.currentRoute.value, 'router.currentRoute.value===');
// getList();
});
</script>
<style scoped lang="scss">
.ui-my-address {
background: #f6f6f6;
overflow-y: auto;
height: 100vh;
}
.ui-address-box {
display: flex;
justify-content: flex-start;
align-items: center;
margin: px2rem(16) px2rem(32);
padding: px2rem(32) px2rem(24);
width: px2rem(638);
height: px2rem(80);
background: #ffffff;
border-radius: px2rem(16);
.ui-select-icon {
width: px2rem(40);
height: px2rem(40);
}
.ui-address-message {
flex: 1;
margin: px2rem(24) px2rem(16);
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
//width: px2rem(500);
.ui-address-name {
max-width: px2rem(200);
color: #3d3d3d;
}
.ui-address-content {
max-width: px2rem(520);
color: #888888;
}
}
.ui-edit-icon {
width: px2rem(36);
height: px2rem(36);
}
}
.ui-add-address {
position: fixed;
bottom: px2rem(84);
left: 50%;
transform: translateX(-50%);
width: px2rem(686);
height: px2rem(88);
line-height: px2rem(88);
background: linear-gradient(140deg, #18ca6e 0%, #0aa555 100%);
border-radius: px2rem(340);
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<div class="ui-shopAgreement">
<div v-if="policyTitle" class="ui-content">
<div class="font_32 bold text-center ui-title">{{ policyTitle }}</div>
<richTextParsing :rich-text="policy"></richTextParsing>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import router from '@/router';
import requestGo from '@/utils/requestApp';
defineOptions({ name: 'AppShopAgreement' });
const id = ref<any>('');
const policy = ref<any>('');
const policyTitle = ref<any>('');
//
const getDetail = () => {
requestGo({ url: `/app/v2/policy/detail/${id.value}`, method: 'get' })
.then((res) => {
const result = res.data;
policy.value = result.content;
policyTitle.value = result.title;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
let route = router.currentRoute.value.params;
let policyTitle = router.currentRoute.value.query;
id.value = route.id;
getDetail();
document.title = policyTitle.title as any;
});
</script>
<style lang="scss" scoped>
.ui-shopAgreement {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-content {
padding: px2rem(30);
.ui-title {
padding: 0 px2rem(50) px2rem(30) px2rem(50);
}
}
</style>

View File

@ -0,0 +1,892 @@
<template>
<div v-if="loading" class="ui-shopDetail">
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item v-for="image in banner" :key="image" class="ui-relative">
<img class="ui-mall-pic" :src="image" alt="" />
</van-swipe-item>
<template #indicator="{ active, total }">
<div class="custom-indicator font_26 colorF">{{ active + 1 }}/{{ total }}</div>
</template>
</van-swipe>
<div class="ui-shop-detail-container">
<div class="ui-shop-data-box">
<div class="f-fcl">
<span class="font_28 ui-price-symbol bold">¥</span>
<div class="font_44 ui-show-price bold"><span v-html="htmlPrice"></span></div>
<div class="font_28 ui-original-price color6">¥{{ originPrice }}</div>
</div>
<div class="font_32 color3 bold ui-pt-10">{{ title }}</div>
<!-- <div class="font_24 color9 ui-pt-20">通过无抗生素产品认证个个可...</div>-->
</div>
<div class="ui-ExpressDelivery-data-box">
<div>
<div class="f-fcl font_28">
<span class="color9 ui-pr-24">配送</span>
<span class="ui-pr-24">广东深圳</span>
<span v-if="freightPrice != '0.00' || freightPrice != 0" class="ui-pl-24 ui-relative ui-distribution-address">运费{{ freightPrice }}</span>
</div>
<!-- <div class="font_28 ui-pt-24">-->
<!-- <span class="color9 ui-pr-24">服务</span>-->
<!-- <span class="color3">支持7天无理由退货</span>-->
<!-- </div>-->
</div>
</div>
</div>
<div v-if="describe" class="ui-relative ui-pt-40">
<img class="ui-line-icon" src="https://image.fulllinkai.com/202405/15/266a22038e0122c70fc282f00c78dbc7.png" alt="" />
<div class="font_26 color3 ui-line-text">商品详情</div>
</div>
<div v-if="describe" class="font_28 color3 ui-detail-introduce">
<richTextParsing :rich-text="describe"></richTextParsing>
</div>
<div class="ui-bottom-operation text-center">
<div class="ui-buy-btn font_30 colorF" @click="showBug = true">立即购买</div>
</div>
<sharePosterV2 ref="openSharePoster">
<template #sharePoster>
<!--生成海报html容器-->
<div id="capture" class="ui-poster-container">
<div class="ui-user-avatar-box f-fcc">
<div class="ui-user-avatar backCover" :style="{ background: 'url(' + canvasData.avatar + ')' }"></div>
<div class="color3 font_22 ui-ml-8 ui-user-name-box f-fcc">
<div v-if="shareName">{{ shareName }}</div>
<div>推荐一个好物给你</div>
</div>
</div>
<div class="ui-poster-pic-box">
<img class="ui-poster-pic" :src="canvasData.pic" alt="" />
</div>
<div class="ui-poster-data-box">
<div class="ui-mt-20 ui-ml-20">
<div class="f-fcl">
<span class="font_24 ui-poster-price-symbol">¥</span>
<div class="font_40 ui-poster-price"><span v-html="htmlSharePrice"></span></div>
</div>
<div class="font_24 ellipsis_2 ui-poster-title color3">{{ title }}</div>
</div>
<div class="ui-poster-qrcode-box">
<img class="ui-poster-qrcode" :src="canvasData.qrcode" alt="" />
<div class="font_20 color6 text-center">长按保存图片至相册</div>
</div>
</div>
</div>
</template>
</sharePosterV2>
<van-popup v-model:show="showBug" round position="bottom" :duration="0.5">
<div class="ui-bug-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="showBug = false" />
<div class="ui-buy-detail-box">
<img class="ui-buy-pic" :src="icon" alt="" />
<div>
<div class="ellipsis_2 font_32 color333 bold ui-buy-title">{{ title }}</div>
<div class="f-fcl ui-buy-price color3 font_24"> 已选{{ spuData.spec[spuIndex].name }} {{ spuSubIndex !== null ? spuSubData.spec[spuSubIndex].name : '' }} </div>
</div>
</div>
<div v-if="spuData.name" class="ui-sku-box">
<div class="ui-sku-list">
<div class="font_28 color3 bold">{{ spuData.name }}</div>
<div class="f-fcl f-wrap">
<div v-for="(item, index) in spuData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSku(index)">
<div class="ui-sku-item" :class="spuIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>
</div>
</div>
<div v-if="spuSubData.name" class="ui-pt-36">
<div class="font_28 color3 bold">{{ spuSubData.name }}</div>
<div class="f-fcl f-wrap">
<div v-for="(item, index) in spuSubData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSubSku(index)">
<div class="ui-sku-item" :class="spuSubIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-num-box f-fbc ui-pt-28">
<div class="font_24 color6"><span class="font_28 color3 ui-mr-16">数量</span>库存{{ stock }}</div>
<div class="f-fcr ui-num-input-box">
<div class="ui-relative f-fcl">
<div class="font_40 color6 ui-num-input-minus" @click="numMinus()">
<div class="ui-num-minus">-</div>
</div>
<div class="ui-select-input font_28">{{ num }}</div>
<div class="font_40 color6 ui-num-input-add" @click="numAdd()">
<div class="ui-num-minus-v2">+</div>
</div>
</div>
</div>
</div>
</div>
<div class="f-fbc ui-total-price-box">
<div class="f-fcl">
<div class="font_24 color6">合计金额</div>
<div v-if="calculating" class="font_26 color6 bold">计算中...</div>
<div v-else-if="num == 0" class="font_26 color6 bold">库存不足</div>
<div v-else class="f-fcl bold ui-total-price">
<span class="font_28 ui-buy-price-symbol">¥</span>
<div class="font_44"><span v-html="htmlTotalPrice"></span></div>
</div>
</div>
</div>
</div>
<div v-if="policyTitle" class="ui-confirm-bottom-box">
<div class="f-fcc">
<img v-show="!isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202405/16/fae039c670338fe6743b7f6a8803ec43.png" alt="" @click="isAgreement = !isAgreement" />
<img v-show="isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202408/15/e536a423a145ae12ab4f6dc57f58588d.png" alt="" @click="isAgreement = !isAgreement" />
<div class="font_24 color6" @click="isAgreement = !isAgreement">
已阅读并同意<span class="ui-agreement-text" @click.stop="jumpAgreement">{{ policyTitle }}</span>
</div>
</div>
</div>
<div class="ui-confirm-btn font_32 f-fcc colorF bold" @click="payment">立即支付</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onDeactivated, onActivated, onUnmounted } from 'vue';
import { showToast } from 'vant';
import router from '@/router';
import requestGo from '@/utils/requestApp';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'ShopDetail' });
const userStore = useUserStore() as any;
const loading = ref(false);
const showBug = ref(false);
const timer = ref<any>(null);
const isAgreement = ref(false); //
const id = ref<any>('');
const title = ref<any>('');
const icon = ref<any>('');
const policyId = ref<any>('');
const policyTitle = ref<any>('');
const banner = ref<any[]>([]); //
const describe = ref<any>(); //
const num = ref<any>(0); //
const skuIds = ref<any>('');
const skuIdDataList = ref<any[]>([]);
const stock = ref<any>(0); // sku
const calculating = ref<any>(false); //
const spuData = ref<any>({}); // spu
const spuSubData = ref<any>({}); // spu
const spuIndex = ref(0); // spu
const spuSubIndex = ref<any>(null); // spu
const defaultSkuId = ref<any>(0); // sku_id
const querySkuId = ref<any>(0); // sku_id
const htmlPrice = ref<any>(''); //
const htmlSharePrice = ref<any>(''); //
const shareName = ref<any>('');
const htmlTotalPrice = ref<any>('0'); //
const originPrice = ref<any>(0); //
const freightPrice = ref<any>(0); //
const canvasData = ref<any>({ background: '', avatar: '', pic: '', qrcode: '' }); //
const openSharePoster = ref<any>(); //
//
const getDetail = () => {
requestGo({ url: `/app/v2/shop/common/spu/detail/${id.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
banner.value = [];
result.spu.banner = JSON.parse(result.spu.banner);
if (result.spu.banner && result.spu.banner.length > 0) {
banner.value = result.spu.banner;
} else {
banner.value.push(result.spu.icon);
}
describe.value = result.spu.description;
title.value = result.spu.title;
icon.value = result.spu.icon;
policyId.value = result.spu.policy_id;
policyTitle.value = result.spu.policy_title;
result.spu.spec = JSON.parse(result.spu.spec);
spuData.value = result.spu.spec;
spuSubData.value = {};
// sku
if (spuData.value.name && spuData.value.spec[spuIndex.value].sub_spec) {
spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
spuSubIndex.value = 0;
}
// sku_id
defaultSkuId.value = result.sku_default?.id || 0;
skuIds.value = '';
// skuid
spuData.value.spec.forEach((item) => {
if (item.skuid) {
skuIds.value += `${skuIds.value ? ',' : ''}${item.skuid}`;
}
if (item.sub_spec) {
recursion(item);
}
});
if (`${result.sku_default.price}`.includes('.')) {
let splitPrice = result.sku_default.price.split('.');
htmlSharePrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlSharePrice.value = result.sku_default.price;
}
shareName.value = userStore.agentName ? (userStore.agentName.length > 4 ? `${userStore.agentName.slice(0, 4)}...` : userStore.agentName) : '';
// skuidskuid
getSkuIdStock();
})
.catch((err) => {
console.log(err);
});
};
// skuid
const getSkuIdStock = () => {
requestGo({ url: `/app/v2/shop/common/sku/${skuIds.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '77777');
skuIdDataList.value = result;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
// sku_idsku
skuIdDataList.value.map((item) => {
if (item.id == querySkuId.value) {
defaultSkuId.value = querySkuId.value;
}
});
spuData.value.spec.map((item, index) => {
if (item.sub_spec) {
item.sub_spec.spec.map((itemV2, indexV2) => {
if (defaultSkuId.value == itemV2.skuid) {
spuIndex.value = index;
spuSubIndex.value = indexV2;
spuSubData.value = item.sub_spec;
}
});
} else {
if (defaultSkuId.value == item.skuid) {
spuIndex.value = index;
}
}
});
gainSelectStock();
}
num.value = 1;
prepay('init');
})
.catch((err) => {
console.log(err);
});
};
// sku
const gainSelectStock = () => {
let id;
if (spuSubIndex.value !== null) {
id = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
id = spuData.value.spec[spuIndex.value].skuid;
}
skuIdDataList.value.forEach((item) => {
if (item.id == id) {
stock.value = item.stock;
originPrice.value = item.price;
}
});
};
// skuid
const recursion = (e) => {
e.sub_spec.spec.forEach((item) => {
if (item.skuid) {
skuIds.value += `${skuIds.value ? ',' : ''}${item.skuid}`;
}
if (item.sub_spec) {
recursion(item.sub_spec.spec);
}
});
};
//
const prepay = (e) => {
let id = '';
if (spuSubIndex.value !== null) {
id = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
id = spuData.value.spec[spuIndex.value].skuid;
}
clearTimeout(timer.value);
timer.value = setTimeout(() => {
requestGo({ url: `/app/v2/shop/common/sku/prepay/${id}/${e == 'init' ? 1 : num.value}/1`, method: 'get' })
.then((res) => {
const result = res.data;
// originPrice.value = result.price_origin;
freightPrice.value = result.freight;
loading.value = true;
if (e == 'init') {
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlPrice.value = result.price;
}
if (!stock.value) {
num.value = 0;
}
}
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlTotalPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
htmlPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlTotalPrice.value = result.price;
htmlPrice.value = result.price;
}
calculating.value = false;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
// Sku
const numMinus = () => {
if (num.value <= 1) {
return;
}
num.value--;
calculating.value = true;
prepay('calculate');
};
// Sku
const numAdd = () => {
if (num.value >= stock.value) {
showToast('超出限购或商品库存上限');
return;
}
num.value++;
calculating.value = true;
prepay('calculate');
};
//
const payment = () => {
let data = {} as any;
let spuId;
if (spuSubIndex.value !== null) {
spuId = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
spuId = spuData.value.spec[spuIndex.value].skuid;
}
data.id = id.value;
data.skuId = spuId;
data.spuNum = num.value;
data.spuName = spuData.value.spec[spuIndex.value].name;
data.spuSubName = spuSubIndex.value !== null ? spuSubData.value.spec[spuSubIndex.value].name : '';
if (num.value <= 0) {
showToast('请先选择购买数量');
return;
}
if (policyTitle.value && !isAgreement.value) {
showToast('请先阅读并同意协议');
return;
}
router.push({
name: 'appShopOrderConfirm',
query: { orderData: JSON.stringify(data) },
});
};
const selectSku = (index) => {
if (spuIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuIndex.value = index;
if (spuData.value.spec[index].sub_spec) {
spuSubData.value = spuData.value.spec[index].sub_spec;
spuSubIndex.value = 0;
} else {
spuSubData.value = {};
spuSubIndex.value = null;
}
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
if (stock.value) {
num.value = 1;
calculating.value = true;
prepay('calculate');
}
};
const selectSubSku = (index) => {
if (spuSubIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
spuSubIndex.value = index;
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
if (stock.value) {
num.value = 1;
calculating.value = true;
prepay('calculate');
}
};
const jumpAgreement = () => {
router.push({
name: 'appShopAgreement',
params: { id: policyId.value },
query: { title: policyTitle.value },
});
};
//
onDeactivated(() => {
// window.removeEventListener('popstate', handleBackButton);
// window.removeEventListener('backbutton', handleBackButton, false);
});
onActivated(() => {
let route = router.currentRoute.value.params;
let query = router.currentRoute.value.query;
if (id.value != (route.id || 102)) {
id.value = route.id;
loading.value = false;
showBug.value = false;
spuIndex.value = 0;
spuSubIndex.value = null;
querySkuId.value = query?.sku_id || 0;
getDetail();
}
});
// const handleBackButton = () => {
// //
// alert('1');
// if (window.history.length > 1) {
// window.webAppInterface.goBack();
// } else {
// alert('2');
// //
// document.addEventListener(
// 'WeixinJSBridgeReady',
// function () {
// WeixinJSBridge.call('closeWindow');
// },
// false,
// );
// // 退
// // navigator.app.exitApp() Cordova
// }
// };
onMounted(() => {
// window.addEventListener('popstate', handleBackButton);
// window.addEventListener('backbutton', handleBackButton, false);
});
</script>
<style lang="scss" scoped>
.ui-shopDetail {
background: #f4f4f4;
overflow-y: auto;
min-height: 100vh;
}
.ui-mall-pic {
width: 100vw;
height: px2rem(750);
display: block;
object-fit: cover;
object-position: center;
}
.custom-indicator {
position: absolute;
right: px2rem(20);
bottom: px2rem(20);
border-radius: px2rem(8);
padding: px2rem(8) px2rem(14);
letter-spacing: px2rem(4);
background: #aaaaaa;
}
.ui-shop-detail-container {
padding: px2rem(24);
.ui-shop-data-box,
.ui-ExpressDelivery-data-box {
padding: px2rem(30) px2rem(24);
background: #ffffff;
border-radius: px2rem(16);
margin-bottom: px2rem(24);
.ui-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-show-price,
.ui-price-symbol {
color: #cc352f;
}
.ui-original-price {
text-decoration: line-through;
margin-left: px2rem(16);
}
}
.ui-ExpressDelivery-data-box {
.ui-distribution-address:after {
content: '';
width: px2rem(2);
height: px2rem(22);
background: #d8d8d8;
position: absolute;
left: 0;
top: px2rem(10);
}
}
}
.ui-line-icon {
width: px2rem(310);
height: px2rem(12);
display: block;
margin: 0 auto;
}
.ui-line-text {
position: absolute;
left: 50%;
top: px2rem(28);
transform: translateX(-50%);
}
.ui-detail-introduce {
padding: px2rem(20) 0 px2rem(120) 0;
background: #ffffff;
margin-top: px2rem(30);
}
.ui-bottom-operation {
width: 100%;
background: #ffffff;
padding: px2rem(18) 0 px2rem(42) 0;
box-sizing: border-box;
position: fixed;
bottom: 0;
left: 0;
border-top: px2rem(2) solid #f5f5f5;
.ui-operation-icon-box {
flex: 1;
.ui-operation-icon {
width: px2rem(44);
height: px2rem(44);
display: block;
margin: 0 auto px2rem(2) auto;
}
}
.ui-buy-btn {
margin: 0 auto;
width: px2rem(520);
height: px2rem(88);
line-height: px2rem(88);
background: linear-gradient(140deg, #18ca6e 0%, #0aa555 100%);
border-radius: px2rem(44);
}
}
.ui-poster-container {
position: relative;
position: fixed;
top: -100000;
left: 0;
z-index: -99999;
border-radius: px2rem(16);
width: px2rem(482);
height: px2rem(806);
padding: 0 px2rem(30) px2rem(30) px2rem(30);
background: #f7f7f7;
box-sizing: border-box;
background-size: cover !important;
background-position: center !important;
background-repeat: no-repeat !important;
.ui-user-avatar-box {
width: px2rem(440);
height: px2rem(48);
background: #ffffff;
border-radius: px2rem(40);
margin-top: px2rem(8);
position: absolute;
left: 50%;
transform: translateX(-50%);
top: px2rem(10);
z-index: 10;
.ui-user-avatar {
width: px2rem(28);
height: px2rem(28);
border-radius: 50%;
margin-left: px2rem(18);
}
.ui-user-name-box {
margin-top: px2rem(-6);
}
}
.ui-poster-pic-box,
.ui-poster-data-box {
width: px2rem(450);
height: px2rem(450);
border-radius: px2rem(16);
background: #ffffff;
position: absolute;
top: px2rem(80);
left: px2rem(16);
display: flex;
justify-content: space-between;
.ui-poster-pic {
width: 100%;
height: 100%;
border-radius: px2rem(16);
object-fit: cover;
object-position: center;
display: block;
vertical-align: top;
}
}
.ui-poster-data-box {
top: px2rem(546);
height: px2rem(244);
.ui-poster-title {
width: px2rem(226);
line-height: px2rem(36);
max-width: px2rem(226);
margin-top: px2rem(4);
}
.ui-poster-price,
.ui-poster-price-symbol {
color: #cc352f;
}
.ui-poster-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(6);
}
.ui-poster-qrcode-box {
.ui-poster-qrcode {
width: px2rem(200);
height: px2rem(200);
border-radius: px2rem(16);
display: block;
object-fit: cover;
object-position: center;
margin-bottom: px2rem(-8);
}
}
}
}
.ui-bug-box {
position: relative;
background: #ffffff;
padding-bottom: px2rem(60);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-buy-detail-box {
display: flex;
justify-content: left;
height: px2rem(160);
position: relative;
padding: px2rem(30) px2rem(30) 0 px2rem(30);
.ui-buy-pic {
width: px2rem(160);
min-width: px2rem(160);
height: px2rem(160);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
margin-right: px2rem(16);
border-radius: px2rem(8);
border: px2rem(2) solid #f8f8f8;
}
.ui-buy-title {
max-width: px2rem(440);
line-height: px2rem(48);
}
.ui-buy-price {
position: absolute;
bottom: px2rem(-12);
}
}
.ui-buy-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-total-price-box {
padding-top: px2rem(40);
.ui-total-price {
color: #cc352f;
}
}
.ui-user-input {
width: px2rem(532);
box-sizing: border-box;
border: px2rem(2) solid #f5f5f5;
border-radius: px2rem(16);
padding: px2rem(16) px2rem(30);
}
::v-deep(input::-webkit-input-placeholder) {
font-weight: initial;
color: #999999;
}
// 线
::v-deep(.van-cell:after) {
position: relative;
}
.ui-sku-box {
padding: px2rem(40) px2rem(30) px2rem(24) px2rem(30);
.ui-sku-list {
.ui-sku-item,
.ui-sku-item-active {
padding: px2rem(12) px2rem(24);
background: #f4f4f4;
border-radius: px2rem(8);
margin-right: px2rem(20);
}
.ui-sku-item-active {
background: linear-gradient(140deg, #18ca6e 0%, #0aa555 100%);
}
.ui-active-sku {
background: #e8fff7;
}
}
}
.ui-num-box {
.ui-num-input-box {
border-radius: px2rem(8);
border: px2rem(2) solid #ececec;
.ui-num-input-minus,
.ui-num-input-add {
width: px2rem(56);
height: px2rem(56);
text-align: center;
position: relative;
.ui-num-minus,
.ui-num-minus-v2 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ui-num-minus {
top: 43%;
}
}
}
.ui-num-input-minus {
border-right: px2rem(2) solid #ececec;
}
.ui-num-input-add {
border-left: px2rem(2) solid #ececec;
}
.ui-select-input {
width: px2rem(44);
border: none;
text-align: center;
padding: px2rem(4) px2rem(20);
background: initial;
line-height: px2rem(40);
}
}
.ui-confirm-btn {
width: px2rem(638);
height: px2rem(88);
background: linear-gradient(140deg, #18ca6e 0%, #0aa555 100%);
border-radius: px2rem(24);
margin: px2rem(28) auto 0 auto;
}
.ui-confirm-bottom-box {
padding-top: px2rem(28);
border-top: px2rem(2) solid #eaeaea;
.ui-agreement-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
vertical-align: top;
margin-right: px2rem(8);
}
.ui-agreement-text {
color: #18ca6e;
}
}
}
</style>

View File

@ -0,0 +1,633 @@
<template>
<div v-if="loading" class="ui-shopOrderConfirm">
<div class="ui-m-inf">
<div class="ui-relative">
<div class="f-fbc">
<div v-for="(item, index) in receivingList" :key="index" class="font_28 f-fcc ui-receiving-item" :class="receivingIndex == index ? 'colorAppGreen font_30 bold' : 'color3'" @click="selectSymptom(index)">
<div class="ui-receiving-text">
{{ item }}
</div>
</div>
</div>
<img v-show="receivingIndex == 0" class="ui-receiving-icon" src="https://image.fulllinkai.com/202404/09/122fe1183af7e8e15dfa3ea4b5ba861f.png" alt="" @click="selectSymptom(1)" />
<img v-show="receivingIndex == 1" class="ui-receiving-icon-v2" src="https://image.fulllinkai.com/202404/09/64e3443b623f73dd341389fc53e303e8.png" alt="" @click="selectSymptom(0)" />
</div>
<div class="ui-address-box">
<div v-if="receivingIndex == 0">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提地址:</div>
<div class="font_28 color3 ui-address-content">广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">联系人:</div>
<div class="font_28 color3 ui-address-content">友福客服17788799376</div>
</div>
</div>
</div>
<div v-else @click="openWx">
<div v-if="detailedAddress">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货地址:</div>
<div class="font_28 color3 ui-address-content">{{ detailedAddress }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货人:</div>
<div class="font_28 color3 ui-address-content">{{ address.userName }}{{ address.telNumber }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
<div v-else>
<div class="ui-address-item ui-pt-6 ui-pb-32">
<div class="font_28 color3 bold">点击添加收货地址</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
<!-- <img class="ui-decoration-icon" src="https://image.fulllinkai.com/202309/20/e8a30e7308236eb3894fec737b66657d.png" alt="" />-->
</div>
<div v-if="receivingIndex == 0" class="ui-remark-box ui-address-box font_28">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提人:</div>
<div class="font_28 color3 ui-address-content">{{ userDetail.name }}{{ userDetail.mobile }}</div>
</div>
</div>
</div>
<div class="ui-inf-box">
<div class="ui-m-avt-lst ui-relative">
<img class="ui-pic" :src="icon" alt="" />
<div class="ui-m-lst-ri">
<div class="font_32 ellipsis_2 bold ui-title">{{ title }}</div>
<div class="font_28 ui-product-spu-name color6">已选{{ orderData.spuName }}{{ orderData.spuSubName ? '' + orderData.spuSubName : '' }}</div>
<div class="font_28 color3 ui-product-num">X {{ orderData.spuNum }}</div>
</div>
</div>
<div class="f-fbc ui-mt-40">
<div class="font_28 color3">商品总额</div>
<div class="font_28 bold"><span class="ui-buy-price-symbol">¥</span>{{ totalPrices }}</div>
</div>
<div v-if="receivingIndex != 0" class="f-fbc ui-mt-20">
<div class="font_28 color3">快递费用</div>
<div class="font_28"><span class="ui-buy-price-symbol">¥</span>{{ freightPrice }}</div>
</div>
<!-- <div v-if="related.share_user_name || related.invite_user_name || related.referrer_user_name">-->
<!-- <div v-if="related.share_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">分享用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.share_user_name }}</div>-->
<!-- </div>-->
<!-- <div v-if="related.invite_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">邀请用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.invite_user_name }}</div>-->
<!-- </div>-->
<!-- <div v-if="related.referrer_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">推荐用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.referrer_user_name }}</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <div class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">关联信息</div>-->
<!-- <div class="font_28 bold color6">暂无关联信息</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="f-fbc ui-mt-20">-->
<!-- <div class="font_28 color3">服务</div>-->
<!-- <div class="font_28">七天无理由退货</div>-->
<!-- </div>-->
<div class="ui-discounts-box f-fbc">
<div></div>
<div class="f-fcr">
<div class="font_28 color3">应付金额</div>
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_32 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
</div>
</div>
<div class="ui-remark-box font_28 f-fbc" @click="showRemark = true">
<div class="color3">订单备注</div>
<div class="ui-remark-text">
<div class="f-fcr text-right">
<div class="ui-mr-10 ellipsis_1" :class="remark ? 'color3' : 'color6'">{{ remark ? remark : '请填写备注' }}</div>
<img class="ui-remark-triangle-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
</div>
<div class="ui-operation-box f-fbc">
<div class="font_24 color3 f-fcl">
合计
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_44 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
<div class="ui-buy-btn f-fcc colorF font_30 bold" @click="payment">确认支付</div>
</div>
<van-popup v-model:show="showRemark" round position="bottom" :duration="0.5">
<div class="ui-remark-input-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="(showRemark = false), (remark = '')" />
<div class="text-center color3 font_32 bold">订单备注</div>
<van-field v-model="remark" type="textarea" autosize rows="5" show-word-limit class="ui-remark-input f-fcc font_40 color3 bold" :maxlength="200" placeholder="请填写备注" />
<div class="font_32 colorF f-fcc ui-remark-btn" @click="showRemark = false">确认</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { closeToast, showToast, showDialog } from 'vant';
import wx from 'weixin-js-sdk';
import router from '@/router';
import requestGo from '@/utils/requestApp';
import request from '@/utils/requestAppPHP';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'ShopOrderConfirm' });
const isIOS = ref<any>(null);
const userStore = useUserStore() as any;
const throttle = ref(true);
const orderData = ref<any>({});
const loading = ref<any>(false);
const timer = ref<any>(null);
const receivingIndex = ref(1);
const receivingList = ref<any[]>(['线下自提', '物流配送']);
const related = ref<any>({}); //
const title = ref<any>('');
const icon = ref<any>('');
const showRemark = ref(false);
const remark = ref<any>('');
const totalPrices = ref<any>(0); //
const htmlTotalPrices = ref<any>(0); //
const freightPrice = ref<any>(0); //
const totalNumber = ref<any>(0);
const wxPay = ref<any>(null); // config
const tradeId = ref<any>(''); // ID
const tradeNo = ref<any>(''); //
const detailedAddress = ref<any>('');
const address = ref<any>({
userName: '',
postalCode: '',
provinceName: '',
cityName: '',
countyName: '',
detailInfo: '',
nationalCode: '',
telNumber: '',
});
//
const getDetail = () => {
requestGo({ url: `/app/v2/shop/common/spu/detail/${orderData.value.id}`, method: 'get' })
.then((res) => {
const result = res.data;
title.value = result.spu.title;
icon.value = result.spu.icon;
related.value = result.related_person_detail;
//
if (result.recive && result.recive.address) {
address.value.userName = result.recive.name;
address.value.telNumber = result.recive.mobile;
detailedAddress.value = result.recive.address;
}
})
.catch((err) => {
console.log(err);
});
};
//
const prepay = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
requestGo({ url: `/app/v2/shop/common/sku/prepay/${orderData.value.skuId}/${orderData.value.spuNum}/${receivingIndex.value == 0 ? 1 : 2}`, method: 'get' })
.then((res) => {
const result = res.data;
freightPrice.value = result.freight;
totalNumber.value = result.total_number;
loading.value = true;
//
if (`${result.total}`.includes('.')) {
let splitPrice = result.total.split('.');
htmlTotalPrices.value = `${splitPrice[0]}.<span class="font_28">${splitPrice[1]}</span>`;
} else {
htmlTotalPrices.value = result.total;
}
totalPrices.value = result.price;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
//
const payment = () => {
let data = {
num: orderData.value.spuNum * 1,
remark: remark.value,
total_number: totalNumber.value,
spu_id: orderData.value.id * 1,
sku_id: orderData.value.skuId * 1,
ship: receivingIndex.value == 0 ? 1 : 2,
name: receivingIndex.value == 0 ? userDetail.value.name : address.value.userName,
mobile: receivingIndex.value == 0 ? userDetail.value.mobile : address.value.telNumber,
address: receivingIndex.value == 0 ? '广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)' : detailedAddress.value,
pay_type: 1,
};
if (receivingIndex.value == 1) {
if (!detailedAddress.value) {
showToast('请填写收货地址');
return;
}
if (!address.value.userName) {
showToast('请填写你的姓名');
return;
}
if (!address.value.telNumber) {
showToast('请填写你的手机号');
return;
}
if (!/^1(3|4|5|6|7|8|9)\d{9}$/.test(address.value.telNumber)) {
showToast('请输入正确的手机号码');
return;
}
}
// let newData = JSON.stringify(window.webAppInterface);
// alert(newData);
// let trade_no = 's2common_1723261299061930';
// window.location.href = `myapp://data?data=${trade_no}`;
if (throttle.value) {
throttle.value = false;
requestGo({ url: `app/v2/shop/common/buy`, data, method: 'post' })
.then((res) => {
if (res.code == 7) {
showDialog({
title: '温馨提示',
message: '价格已经发生变化,请重新确认',
}).then(() => {
throttle.value = true;
prepay();
});
return;
}
throttle.value = true;
let result = JSON.stringify(res.data);
if (isIOS.value) {
//iOS app
window.webkit.messageHandlers.ImmediatePayment.postMessage(result);
} else {
// app
window.location.href = `myapp://data?data=${result}`;
}
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const getMailData = () => {
if (localStorage.getItem('logsiticsAddress')) {
let result = JSON.parse(localStorage.getItem('logsiticsAddress'));
console.log(result, '===');
address.value.userName = result.name; //
address.value.telNumber = result.mobile; //
detailedAddress.value = `${result.province}` + `${result.city}` + `${result.county}` + `${result.detail_address}`;
} else {
requestGo({ url: `/app/v2/shop/common/order/express/last`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
if (result && result.mobile) {
detailedAddress.value = result.address;
address.value.userName = result.name;
address.value.telNumber = result.mobile;
}
})
.catch((err) => {
console.log(err);
});
}
};
const callBack = () => {
requestGo({ url: `app/v2/shop/common/wechatpay/callback/${tradeNo.value}`, hideLoading: true, method: 'post' })
.then((res) => {
console.log(res);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
//
const openWx = () => {
if (remark.value) {
let setRemark = JSON.stringify({ id: orderData.value.id, remark: remark.value });
localStorage.setItem('setRemark', setRemark);
}
router.push({
name: 'appMyAddress',
});
// if (receivingIndex.value == 0) {
// return;
// }
// wx.openAddress({
// success(res) {
// address.value.userName = res.userName; //
// address.value.postalCode = res.postalCode; //
// address.value.provinceName = res.provinceName; //
// address.value.cityName = res.cityName; //
// address.value.countryName = res.countryName; //
// address.value.detailInfo = res.detailInfo; //
// address.value.nationalCode = res.nationalCode; //
// address.value.telNumber = res.telNumber; //
// detailedAddress.value = `${res.provinceName}` + `${res.cityName}` + `${res.countryName}` + `${res.detailInfo}`;
// },
// });
};
//
const selectSymptom = (index) => {
receivingIndex.value = index;
prepay();
};
const userDetail = ref({
name: '',
mobile: '',
});
const getUserDetail = () => {
request({ url: `/app/get/user/info`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
userDetail.value.name = result.name;
userDetail.value.mobile = result.mobile;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
let route = router.currentRoute.value.query;
orderData.value = JSON.parse(route.orderData as any);
console.log(router.currentRoute.value, 'router.currentRoute.value===');
getUserDetail();
prepay();
getMailData();
getDetail();
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
isIOS.value = false;
} else {
isIOS.value = true;
}
console.log(isIOS.value, 'isIOS.value==');
if (localStorage.getItem('setRemark')) {
let setRemark = JSON.parse(localStorage.getItem('setRemark'));
if (orderData.value.id == setRemark.id) remark.value = setRemark.remark;
localStorage.removeItem('setRemark');
}
});
</script>
<style lang="scss" scoped>
.ui-shopOrderConfirm {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-m-inf {
padding: px2rem(30);
.ui-receiving-item {
width: 50%;
height: px2rem(88);
margin-top: px2rem(-6);
.ui-receiving-text {
position: relative;
width: fit-content;
white-space: nowrap;
z-index: 99;
}
}
.ui-receiving-icon,
.ui-receiving-icon-v2 {
width: 100%;
height: px2rem(88);
position: absolute;
top: 0;
left: 0;
}
.ui-receiving-icon-v2 {
right: 0;
}
.ui-address-box {
padding: px2rem(24) px2rem(24) px2rem(12) px2rem(24);
margin-bottom: px2rem(24);
background: #ffffff;
border-radius: 0 0 px2rem(16) px2rem(16);
position: relative;
.ui-address-item {
display: flex;
justify-content: space-between;
.ui-address-title-box {
display: flex;
justify-content: left;
.ui-address-title {
width: px2rem(132);
text-align: justify;
text-align-last: justify;
}
.ui-address-content {
width: px2rem(440);
max-width: px2rem(440);
margin-left: px2rem(20);
}
}
.ui-address-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
margin-top: px2rem(4);
}
}
.ui-decoration-icon {
width: px2rem(664);
height: px2rem(4);
position: absolute;
bottom: px2rem(2);
left: px2rem(14);
}
}
.ui-inf-box,
.ui-remark-box {
padding: px2rem(24);
margin-bottom: px2rem(20);
background: #ffffff;
border-radius: px2rem(16);
box-sizing: border-box;
.ui-m-avt-lst {
display: flex;
align-items: stretch;
.ui-pic {
width: px2rem(160);
height: px2rem(160);
border: px2rem(2) solid #f8f8f8;
display: block;
object-position: center;
object-fit: cover;
border-radius: px2rem(8);
margin-right: px2rem(16);
}
.ui-m-lst-ri {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ui-title {
max-width: px2rem(462);
line-height: px2rem(48);
}
.ui-product-spu-name {
position: relative;
bottom: 0;
}
.ui-product-num {
position: absolute;
bottom: 0;
right: 0;
}
.ui-total-price {
position: absolute;
right: 0;
top: px2rem(6);
.ui-total-original-price {
text-decoration-line: line-through;
}
}
}
}
.ui-discounts-box {
padding-top: px2rem(20);
border-top: px2rem(2) solid #eaeaea;
margin-top: px2rem(24);
}
.ui-buy-num-box {
padding: 0 px2rem(22);
height: px2rem(56);
background: #f7f7f7;
border-radius: px2rem(8);
}
}
.ui-remark-box {
.ui-remark-text {
max-width: px2rem(400);
}
.ui-remark-triangle-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
}
}
}
.ui-buy-price {
color: #cc352f;
}
.ui-buy-price-symbol {
margin-right: px2rem(4);
margin-top: px2rem(2);
}
.ui-operation-box {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #ffffff;
padding: px2rem(16) px2rem(24) px2rem(50) px2rem(30);
box-sizing: border-box;
.ui-buy-btn {
width: px2rem(400);
height: px2rem(88);
border-radius: px2rem(44);
background: linear-gradient(151deg, #18ca6e 0%, #0aa555 100%);
}
}
.ui-remark-input-box {
position: relative;
background: #ffffff;
padding: px2rem(30) px2rem(30) px2rem(60) px2rem(30);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-remark-input {
background: #f4f4f4;
border-radius: px2rem(8);
margin: px2rem(40) 0 px2rem(24) 0;
}
.ui-remark-btn {
width: 100%;
height: px2rem(88);
border-radius: px2rem(8);
background: linear-gradient(151deg, #18ca6e 0%, #0aa555 100%);
}
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<div class="ui-longPicture">
<img class="ui-pic" :src="imgUrl" alt="" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import router from '@/router';
defineOptions({ name: 'LongPicture' });
const imgUrl = ref<any>('https://image.fulllinkai.com/202408/30/978ba8c872b24be1558afd845cd0b606.jpeg');
onMounted(() => {
let route = router.currentRoute.value.query;
if (route.imgUrl) {
imgUrl.value = route.imgUrl;
}
});
</script>
<style lang="scss" scoped>
.ui-longPicture {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-pic {
width: 100vw;
display: block;
}
</style>

View File

@ -0,0 +1,633 @@
<template>
<div v-if="loading" class="ui-shopOrderConfirm">
<div class="ui-m-inf">
<div class="ui-relative">
<div class="f-fbc">
<div v-for="(item, index) in receivingList" :key="index" class="font_28 f-fcc ui-receiving-item" :class="receivingIndex == index ? 'colorAppGreen font_30 bold' : 'color3'" @click="selectSymptom(index)">
<div class="ui-receiving-text">
{{ item }}
</div>
</div>
</div>
<img v-show="receivingIndex == 0" class="ui-receiving-icon" src="https://image.fulllinkai.com/202404/09/122fe1183af7e8e15dfa3ea4b5ba861f.png" alt="" @click="selectSymptom(1)" />
<img v-show="receivingIndex == 1" class="ui-receiving-icon-v2" src="https://image.fulllinkai.com/202404/09/64e3443b623f73dd341389fc53e303e8.png" alt="" @click="selectSymptom(0)" />
</div>
<div class="ui-address-box">
<div v-if="receivingIndex == 0">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提地址:</div>
<div class="font_28 color3 ui-address-content">广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">联系人:</div>
<div class="font_28 color3 ui-address-content">友福客服17788799376</div>
</div>
</div>
</div>
<div v-else @click="openWx">
<div v-if="detailedAddress">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货地址:</div>
<div class="font_28 color3 ui-address-content">{{ detailedAddress }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货人:</div>
<div class="font_28 color3 ui-address-content">{{ address.userName }}{{ address.telNumber }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
<div v-else>
<div class="ui-address-item ui-pt-6 ui-pb-32">
<div class="font_28 color3 bold">点击添加收货地址</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
<!-- <img class="ui-decoration-icon" src="https://image.fulllinkai.com/202309/20/e8a30e7308236eb3894fec737b66657d.png" alt="" />-->
</div>
<div v-if="receivingIndex == 0" class="ui-remark-box ui-address-box font_28">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提人:</div>
<div class="font_28 color3 ui-address-content">{{ userDetail.name }}{{ userDetail.mobile }}</div>
</div>
</div>
</div>
<div class="ui-inf-box">
<div class="ui-m-avt-lst ui-relative">
<img class="ui-pic" :src="icon" alt="" />
<div class="ui-m-lst-ri">
<div class="font_32 ellipsis_2 bold ui-title">{{ title }}</div>
<div class="font_28 ui-product-spu-name color6">已选{{ orderData.spuName }}{{ orderData.spuSubName ? '' + orderData.spuSubName : '' }}</div>
<div class="font_28 color3 ui-product-num">X {{ orderData.spuNum }}</div>
</div>
</div>
<div class="f-fbc ui-mt-40">
<div class="font_28 color3">商品总额</div>
<div class="font_28 bold"><span class="ui-buy-price-symbol">¥</span>{{ totalPrices }}</div>
</div>
<div v-if="receivingIndex != 0" class="f-fbc ui-mt-20">
<div class="font_28 color3">快递费用</div>
<div class="font_28"><span class="ui-buy-price-symbol">¥</span>{{ freightPrice }}</div>
</div>
<!-- <div v-if="related.share_user_name || related.invite_user_name || related.referrer_user_name">-->
<!-- <div v-if="related.share_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">分享用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.share_user_name }}</div>-->
<!-- </div>-->
<!-- <div v-if="related.invite_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">邀请用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.invite_user_name }}</div>-->
<!-- </div>-->
<!-- <div v-if="related.referrer_user_name" class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">推荐用户</div>-->
<!-- <div class="font_28 bold color6">{{ related.referrer_user_name }}</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <div class="f-fbc ui-mt-22">-->
<!-- <div class="font_28 color3">关联信息</div>-->
<!-- <div class="font_28 bold color6">暂无关联信息</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="f-fbc ui-mt-20">-->
<!-- <div class="font_28 color3">服务</div>-->
<!-- <div class="font_28">七天无理由退货</div>-->
<!-- </div>-->
<div class="ui-discounts-box f-fbc">
<div></div>
<div class="f-fcr">
<div class="font_28 color3">应付金额</div>
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_32 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
</div>
</div>
<div class="ui-remark-box font_28 f-fbc" @click="showRemark = true">
<div class="color3">订单备注</div>
<div class="ui-remark-text">
<div class="f-fcr text-right">
<div class="ui-mr-10 ellipsis_1" :class="remark ? 'color3' : 'color6'">{{ remark ? remark : '请填写备注' }}</div>
<img class="ui-remark-triangle-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
</div>
<div class="ui-operation-box f-fbc">
<div class="font_24 color3 f-fcl">
合计
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_44 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
<div class="ui-buy-btn f-fcc colorF font_30 bold" @click="payment">确认支付</div>
</div>
<van-popup v-model:show="showRemark" round position="bottom" :duration="0.5">
<div class="ui-remark-input-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="(showRemark = false), (remark = '')" />
<div class="text-center color3 font_32 bold">订单备注</div>
<van-field v-model="remark" type="textarea" autosize rows="5" show-word-limit class="ui-remark-input f-fcc font_40 color3 bold" :maxlength="200" placeholder="请填写备注" />
<div class="font_32 colorF f-fcc ui-remark-btn" @click="showRemark = false">确认</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { closeToast, showToast, showDialog } from 'vant';
import wx from 'weixin-js-sdk';
import router from '@/router';
import requestGo from '@/utils/requestApp';
import request from '@/utils/requestAppPHP';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'ShopOrderConfirm' });
const isIOS = ref<any>(null);
const userStore = useUserStore() as any;
const throttle = ref(true);
const orderData = ref<any>({});
const loading = ref<any>(false);
const timer = ref<any>(null);
const receivingIndex = ref(1);
const receivingList = ref<any[]>(['线下自提', '物流配送']);
const related = ref<any>({}); //
const title = ref<any>('');
const icon = ref<any>('');
const showRemark = ref(false);
const remark = ref<any>('');
const totalPrices = ref<any>(0); //
const htmlTotalPrices = ref<any>(0); //
const freightPrice = ref<any>(0); //
const totalNumber = ref<any>(0);
const wxPay = ref<any>(null); // config
const tradeId = ref<any>(''); // ID
const tradeNo = ref<any>(''); //
const detailedAddress = ref<any>('');
const address = ref<any>({
userName: '',
postalCode: '',
provinceName: '',
cityName: '',
countyName: '',
detailInfo: '',
nationalCode: '',
telNumber: '',
});
//
const getDetail = () => {
requestGo({ url: `/app/v2/shop/common/spu/detail/${orderData.value.id}`, method: 'get' })
.then((res) => {
const result = res.data;
title.value = result.spu.title;
icon.value = result.spu.icon;
related.value = result.related_person_detail;
//
if (result.recive && result.recive.address) {
address.value.userName = result.recive.name;
address.value.telNumber = result.recive.mobile;
detailedAddress.value = result.recive.address;
}
})
.catch((err) => {
console.log(err);
});
};
//
const prepay = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
requestGo({ url: `/app/v2/shop/common/sku/prepay/${orderData.value.skuId}/${orderData.value.spuNum}/${receivingIndex.value == 0 ? 1 : 2}`, method: 'get' })
.then((res) => {
const result = res.data;
freightPrice.value = result.freight;
totalNumber.value = result.total_number;
loading.value = true;
//
if (`${result.total}`.includes('.')) {
let splitPrice = result.total.split('.');
htmlTotalPrices.value = `${splitPrice[0]}.<span class="font_28">${splitPrice[1]}</span>`;
} else {
htmlTotalPrices.value = result.total;
}
totalPrices.value = result.price;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
//
const payment = () => {
let data = {
num: orderData.value.spuNum * 1,
remark: remark.value,
total_number: totalNumber.value,
spu_id: orderData.value.id * 1,
sku_id: orderData.value.skuId * 1,
ship: receivingIndex.value == 0 ? 1 : 2,
name: receivingIndex.value == 0 ? userDetail.value.name : address.value.userName,
mobile: receivingIndex.value == 0 ? userDetail.value.mobile : address.value.telNumber,
address: receivingIndex.value == 0 ? '广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)' : detailedAddress.value,
pay_type: 1,
};
if (receivingIndex.value == 1) {
if (!detailedAddress.value) {
showToast('请填写收货地址');
return;
}
if (!address.value.userName) {
showToast('请填写你的姓名');
return;
}
if (!address.value.telNumber) {
showToast('请填写你的手机号');
return;
}
if (!/^1(3|4|5|6|7|8|9)\d{9}$/.test(address.value.telNumber)) {
showToast('请输入正确的手机号码');
return;
}
}
// let newData = JSON.stringify(window.webAppInterface);
// alert(newData);
// let trade_no = 's2common_1723261299061930';
// window.location.href = `myapp://data?data=${trade_no}`;
if (throttle.value) {
throttle.value = false;
requestGo({ url: `app/v2/shop/common/buy`, data, method: 'post' })
.then((res) => {
if (res.code == 7) {
showDialog({
title: '温馨提示',
message: '价格已经发生变化,请重新确认',
}).then(() => {
throttle.value = true;
prepay();
});
return;
}
throttle.value = true;
let result = JSON.stringify(res.data);
if (isIOS.value) {
//iOS app
window.webkit.messageHandlers.ImmediatePayment.postMessage(result);
} else {
// app
window.location.href = `myapp://data?data=${result}`;
}
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const getMailData = () => {
if (localStorage.getItem('logsiticsAddress')) {
let result = JSON.parse(localStorage.getItem('logsiticsAddress'));
console.log(result, '===');
address.value.userName = result.name; //
address.value.telNumber = result.mobile; //
detailedAddress.value = `${result.province}` + `${result.city}` + `${result.county}` + `${result.detail_address}`;
} else {
requestGo({ url: `/app/v2/shop/common/order/express/last`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
if (result && result.mobile) {
detailedAddress.value = result.address;
address.value.userName = result.name;
address.value.telNumber = result.mobile;
}
})
.catch((err) => {
console.log(err);
});
}
};
const callBack = () => {
requestGo({ url: `app/v2/shop/common/wechatpay/callback/${tradeNo.value}`, hideLoading: true, method: 'post' })
.then((res) => {
console.log(res);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
//
const openWx = () => {
if (remark.value) {
let setRemark = JSON.stringify({ id: orderData.value.id, remark: remark.value });
localStorage.setItem('setRemark', setRemark);
}
router.push({
name: 'appMyAddress',
});
// if (receivingIndex.value == 0) {
// return;
// }
// wx.openAddress({
// success(res) {
// address.value.userName = res.userName; //
// address.value.postalCode = res.postalCode; //
// address.value.provinceName = res.provinceName; //
// address.value.cityName = res.cityName; //
// address.value.countryName = res.countryName; //
// address.value.detailInfo = res.detailInfo; //
// address.value.nationalCode = res.nationalCode; //
// address.value.telNumber = res.telNumber; //
// detailedAddress.value = `${res.provinceName}` + `${res.cityName}` + `${res.countryName}` + `${res.detailInfo}`;
// },
// });
};
//
const selectSymptom = (index) => {
receivingIndex.value = index;
prepay();
};
const userDetail = ref({
name: '',
mobile: '',
});
const getUserDetail = () => {
request({ url: `/app/get/user/info`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
userDetail.value.name = result.name;
userDetail.value.mobile = result.mobile;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
let route = router.currentRoute.value.query;
orderData.value = JSON.parse(route.orderData as any);
console.log(router.currentRoute.value, 'router.currentRoute.value===');
getUserDetail();
prepay();
getMailData();
getDetail();
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
isIOS.value = false;
} else {
isIOS.value = true;
}
console.log(isIOS.value, 'isIOS.value==');
if (localStorage.getItem('setRemark')) {
let setRemark = JSON.parse(localStorage.getItem('setRemark'));
if (orderData.value.id == setRemark.id) remark.value = setRemark.remark;
localStorage.removeItem('setRemark');
}
});
</script>
<style lang="scss" scoped>
.ui-shopOrderConfirm {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-m-inf {
padding: px2rem(30);
.ui-receiving-item {
width: 50%;
height: px2rem(88);
margin-top: px2rem(-6);
.ui-receiving-text {
position: relative;
width: fit-content;
white-space: nowrap;
z-index: 99;
}
}
.ui-receiving-icon,
.ui-receiving-icon-v2 {
width: 100%;
height: px2rem(88);
position: absolute;
top: 0;
left: 0;
}
.ui-receiving-icon-v2 {
right: 0;
}
.ui-address-box {
padding: px2rem(24) px2rem(24) px2rem(12) px2rem(24);
margin-bottom: px2rem(24);
background: #ffffff;
border-radius: 0 0 px2rem(16) px2rem(16);
position: relative;
.ui-address-item {
display: flex;
justify-content: space-between;
.ui-address-title-box {
display: flex;
justify-content: left;
.ui-address-title {
width: px2rem(132);
text-align: justify;
text-align-last: justify;
}
.ui-address-content {
width: px2rem(440);
max-width: px2rem(440);
margin-left: px2rem(20);
}
}
.ui-address-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
margin-top: px2rem(4);
}
}
.ui-decoration-icon {
width: px2rem(664);
height: px2rem(4);
position: absolute;
bottom: px2rem(2);
left: px2rem(14);
}
}
.ui-inf-box,
.ui-remark-box {
padding: px2rem(24);
margin-bottom: px2rem(20);
background: #ffffff;
border-radius: px2rem(16);
box-sizing: border-box;
.ui-m-avt-lst {
display: flex;
align-items: stretch;
.ui-pic {
width: px2rem(160);
height: px2rem(160);
border: px2rem(2) solid #f8f8f8;
display: block;
object-position: center;
object-fit: cover;
border-radius: px2rem(8);
margin-right: px2rem(16);
}
.ui-m-lst-ri {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ui-title {
max-width: px2rem(462);
line-height: px2rem(48);
}
.ui-product-spu-name {
position: relative;
bottom: 0;
}
.ui-product-num {
position: absolute;
bottom: 0;
right: 0;
}
.ui-total-price {
position: absolute;
right: 0;
top: px2rem(6);
.ui-total-original-price {
text-decoration-line: line-through;
}
}
}
}
.ui-discounts-box {
padding-top: px2rem(20);
border-top: px2rem(2) solid #eaeaea;
margin-top: px2rem(24);
}
.ui-buy-num-box {
padding: 0 px2rem(22);
height: px2rem(56);
background: #f7f7f7;
border-radius: px2rem(8);
}
}
.ui-remark-box {
.ui-remark-text {
max-width: px2rem(400);
}
.ui-remark-triangle-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
}
}
}
.ui-buy-price {
color: #cc352f;
}
.ui-buy-price-symbol {
margin-right: px2rem(4);
margin-top: px2rem(2);
}
.ui-operation-box {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #ffffff;
padding: px2rem(16) px2rem(24) px2rem(50) px2rem(30);
box-sizing: border-box;
.ui-buy-btn {
width: px2rem(400);
height: px2rem(88);
border-radius: px2rem(44);
background: linear-gradient(151deg, #18ca6e 0%, #0aa555 100%);
}
}
.ui-remark-input-box {
position: relative;
background: #ffffff;
padding: px2rem(30) px2rem(30) px2rem(60) px2rem(30);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-remark-input {
background: #f4f4f4;
border-radius: px2rem(8);
margin: px2rem(40) 0 px2rem(24) 0;
}
.ui-remark-btn {
width: 100%;
height: px2rem(88);
border-radius: px2rem(8);
background: linear-gradient(151deg, #18ca6e 0%, #0aa555 100%);
}
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div class="ui-accessDenied">
<div class="ui-container">
<img class="ui-service-icon" src="https://image.fulllinkai.com/202312/28/93493e89f7196fa765f4587c9e28ed0d.jpeg" alt="" />
<div class="font_28 color6 text-center ui-tips" @click.stop="jumpPath">对不起您还没有当前操作的权限长按识别二维码联系客服</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import router from '@/router';
defineOptions({ name: 'AccessDenied' });
const id = ref<any>('');
const jumpPath = () => {
router.replace({
name: 'partnerInfo',
});
router.go(-1);
};
onMounted(() => {
let route = router.currentRoute.value.query;
id.value = route.id;
});
</script>
<style lang="scss" scoped>
.ui-accessDenied {
background: #ffffff;
overflow-y: auto;
min-height: 100vh;
}
.ui-container {
position: absolute;
top: 20vh;
left: 50%;
transform: translateX(-50%);
.ui-service-icon {
width: px2rem(640);
height: px2rem(640);
display: block;
margin: px2rem(-100) auto px2rem(-70) auto;
object-fit: cover;
object-position: center;
}
.ui-tips {
position: relative;
z-index: 22;
padding: 0 px2rem(86);
line-height: px2rem(48);
}
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<div class="ui-notFound">
<div class="">
<img class="" src="https://image.fulllinkai.com/202108/19/7067c44ad027325039f5c75fc92176d0.png" alt="" />
<div class="font_30 color9 text-center">页面已删除</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
defineOptions({ name: 'NotFound' });
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-notFound {
background: #ffffff;
overflow-y: auto;
min-height: 100vh;
}
</style>

View File

@ -0,0 +1,658 @@
<template>
<div class="ui-appDownloadPageV2">
<!-- <div style="position: relative; top: 0; z-index: 999">-->
<!-- <div class="appWatchBox">-->
<!-- <van-image v-if="isWechatDevice" class="appWatchTop-pic" src="https://images.health.ufutx.com/202406/12/d96d8221ef6070f547723282b5b3923f.jpeg" />-->
<!-- </div>-->
<!-- </div>-->
<img class="ui-top-logo" src="https://image.fulllinkai.com/202408/24/15e99ac9778827d49d39d50b6361b08e.png" alt="" />
<div class="ui-data-box">
<!-- -&#45;&#45;-->
<van-tabs v-model:active="active">
<!-- <van-tab title="">-->
<div class="mobile ui-mt-60">
<div class="f-fcl ui-relative">
<!-- <div class="font_32 color76 ui-title">手机号</div>-->
<!-- <div class="f-fcl ui-area-code" @click.stop="showChooseArea = !showChooseArea">-->
<!-- <div class="font_30 color3">{{ AreaValue }}</div>-->
<!-- <img v-if="showChooseArea" class="Angle_icon" src="https://image.fulllinkai.com/202405/17/f2d6c2fcda5d4a01fb28fe4a894d7f7a.png" alt="" />-->
<!-- <img v-else class="Angle_icon" src="https://image.fulllinkai.com/202405/17/fb15a98cfa7c515075a5a4f9d7c3e589.png" alt="" />-->
<!-- </div>-->
<!-- <van-search v-model="mobile" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入手机号" />-->
<van-field v-model="mobile" type="number" class="ui-user-input font_32 color3" placeholder="请输入手机号" />
<div v-if="showChooseArea" class="ui-relative area_code_choose_box">
<div class="area_code_choose_list">
<div v-for="(item, index) in areaList" :key="index" class="alignment area_code_choose" @click="selectedArea(item, index)">
<div class="font_32 color3 area_code_choose_item" :class="AreaIndex == index ? 'colorPrice' : ''"> {{ item.area_code }} {{ item.label }} </div>
</div>
</div>
</div>
<div v-if="showChooseArea" class="ui-area-mask" @click="showChooseArea = false"></div>
</div>
<div class="ui-line"></div>
<div class="f-fbc">
<div class="f-fcl">
<!-- <div class="font_32 color76 ui-title">验证码</div>-->
<!-- <van-search v-model="code" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input-v2 font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入验证码" />-->
<van-field v-model="code" type="number" maxlength="6" class="ui-user-input font_32 color3" placeholder="请输入验证码" />
</div>
<div class="ui-code-btn font_28 f-fcc" :class="time == 60 ? 'colorF' : 'set colorBussnessTheme'" @click="getCode">{{ time == 60 ? '获取验证码' : `${time}s` }} </div>
</div>
<div class="ui-line-v2"></div>
<!-- <div class="f-fcl ui-relative">-->
<!-- &lt;!&ndash; <div class="font_32 color76 ui-title">密码</div>&ndash;&gt;-->
<!-- &lt;!&ndash; <van-search v-model="mobile" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入手机号" />&ndash;&gt;-->
<!-- <van-field v-model="pwd" type="number" class="ui-user-input font_32 color3" placeholder="请输入密码" />-->
<!-- <div v-if="showChooseArea" class="ui-area-mask" @click="showChooseArea = false"></div>-->
<!-- </div>-->
<!-- <div class="ui-line-v2"></div>-->
<div class="font_26" style="color: #b2b3b5">{{ areaList[AreaIndex].tips }}</div>
</div>
<!-- </van-tab>-->
<!-- <van-tab title="密码登录">-->
<!-- <div class="mobile ui-mt-60">-->
<!-- <div class="f-fcl ui-relative">-->
<!-- &lt;!&ndash; <div class="font_32 color76 ui-title">手机号</div>&ndash;&gt;-->
<!-- &lt;!&ndash; <div class="f-fcl ui-area-code" @click.stop="showChooseArea = !showChooseArea">&ndash;&gt;-->
<!-- &lt;!&ndash; <div class="font_30 color3">{{ AreaValue }}</div>&ndash;&gt;-->
<!-- &lt;!&ndash; <img v-if="showChooseArea" class="Angle_icon" src="https://image.fulllinkai.com/202405/17/f2d6c2fcda5d4a01fb28fe4a894d7f7a.png" alt="" />&ndash;&gt;-->
<!-- &lt;!&ndash; <img v-else class="Angle_icon" src="https://image.fulllinkai.com/202405/17/fb15a98cfa7c515075a5a4f9d7c3e589.png" alt="" />&ndash;&gt;-->
<!-- &lt;!&ndash; </div>&ndash;&gt;-->
<!-- &lt;!&ndash; <van-search v-model="mobile" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入手机号" />&ndash;&gt;-->
<!-- <van-field v-model="mobile" type="number" class="ui-user-input font_32 color3" placeholder="请输入手机号" />-->
<!-- <div v-if="showChooseArea" class="ui-relative area_code_choose_box">-->
<!-- <div class="area_code_choose_list">-->
<!-- <div v-for="(item, index) in areaList" :key="index" class="alignment area_code_choose" @click="selectedArea(item, index)">-->
<!-- <div class="font_32 color3 area_code_choose_item" :class="AreaIndex == index ? 'colorPrice' : ''"> {{ item.area_code }} {{ item.label }} </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-if="showChooseArea" class="ui-area-mask" @click="showChooseArea = false"></div>-->
<!-- </div>-->
<!-- &lt;!&ndash; <van-search v-model="mobile" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入手机号" />&ndash;&gt;-->
<!-- &lt;!&ndash; <van-field v-model="mobile" type="number" class="ui-user-input font_32 color3" placeholder="请输入手机号" />&ndash;&gt;-->
<!-- <div v-if="showChooseArea" class="ui-relative area_code_choose_box">-->
<!-- <div class="area_code_choose_list">-->
<!-- <div v-for="(item, index) in areaList" :key="index" class="alignment area_code_choose" @click="selectedArea(item, index)">-->
<!-- <div class="font_32 color3 area_code_choose_item" :class="AreaIndex == index ? 'colorPrice' : ''"> {{ item.area_code }} {{ item.label }} </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- - -->
<!-- </div>-->
<!-- <div v-if="showChooseArea" class="ui-area-mask" @click="showChooseArea = false"></div>-->
<!-- <div class="ui-line"></div>-->
<!-- &lt;!&ndash; <div class="f-fbc">&ndash;&gt;-->
<!-- &lt;!&ndash; <div class="f-fcl">&ndash;&gt;-->
<!-- &lt;!&ndash; <van-field v-model="emailCode" type="number" maxlength="6" class="ui-user-input font_32 color3" placeholder="请输入验证码" />&ndash;&gt;-->
<!-- &lt;!&ndash; </div>&ndash;&gt;-->
<!-- &lt;!&ndash; <div class="ui-code-btn font_28 f-fcc" :class="timeV2 == 60 ? 'colorF' : 'set colorBussnessTheme'" @click="getEmailCode">{{ timeV2 == 60 ? '获取验证码' : `${timeV2}s` }} </div>&ndash;&gt;-->
<!-- &lt;!&ndash; </div>&ndash;&gt;-->
<!-- &lt;!&ndash; <div class="ui-line-v2"></div>&ndash;&gt;-->
<!-- <div class="f-fcl ui-relative">-->
<!-- &lt;!&ndash; <div class="font_32 color76 ui-title">密码</div>&ndash;&gt;-->
<!-- &lt;!&ndash; <van-search v-model="mobile" clear-icon="https://image.fulllinkai.com/202408/24/ef7ecf7fe24f4d5cafce2b54d82f86fc.png" class="ui-user-input font_32 color3" left-icon="" clear-trigger="always" placeholder="请输入手机号" />&ndash;&gt;-->
<!-- <van-field v-model="pwd" type="number" class="ui-user-input font_32 color3" placeholder="请输入密码" />-->
<!-- <div v-if="showChooseArea" class="ui-area-mask" @click="showChooseArea = false"></div>-->
<!-- </div>-->
<!-- <div class="ui-line-v2"></div>-->
<!-- </div>-->
<!-- </van-tab>-->
</van-tabs>
</div>
<div class="">
<div v-if="active == 0" class="ui-signIn-btn f-fcc colorF font_36 bold" @click="save">登录</div>
<!-- <div v-else class="ui-signIn-btn f-fcc colorF font_36 bold" @click="saveV2">登录</div>-->
<!-- <div class="font_28 ui-pt-20 text-center color76" @click="active = active == 0 ? 1 : 0">-->
<!-- {{ active == 1 ? '使用验证码登录' : '使用密码登录' }}-->
<!-- &lt;!&ndash; <span class="colorBussnessTheme" @click="active == 1 ? 0: 1"> 去登录</span>&ndash;&gt;-->
<!-- </div>-->
<!-- <div class="f-fcc">-->
<!-- <div class="ui-pt-32 ui-agreement-box">-->
<!-- <img v-if="!isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202408/23/1e9f28b0187c276ee3bfc2137e8880e1.png" alt="" @click="isAgreement = !isAgreement" />-->
<!-- &lt;!&ndash; <img v-else class="ui-agreement-icon" src="https://image.fulllinkai.com/202408/23/0cd995a8298d57b6e3ae7c0f6d61ceac.png" alt="" @click="isAgreement = !isAgreement" />&ndash;&gt;-->
<!-- <svg v-else class="ui-agreement-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" @click="isAgreement = !isAgreement">-->
<!-- <circle cx="8" cy="8" r="7.5" stroke="#3d56bd" />-->
<!-- <path d="M4 7.82705L6.61392 10.5862L12 5.2002" stroke="#3d56bd" stroke-linecap="round" />-->
<!-- </svg>-->
<!-- <div class="font_24 color76 ui-agreement-text" @click="isAgreement = !isAgreement">-->
<!-- 我已阅读并同意-->
<!-- <span class="colorBussnessTheme" @click.stop="jumpAgreement('appPrivacyAgreement')">隐私协议</span>-->
<!-- -->
<!-- <span class="colorBussnessTheme" @click.stop="jumpAgreement('appServeAgreement')">服务协议</span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { closeToast, showDialog, showLoadingToast, showToast } from 'vant';
import router from '@/router';
import requestGo from '@/utils/requestGo';
import { useUserStore } from '@/store/modules/user';
// import { backFillLoginData } from '@/plugins/public';
defineOptions({ name: 'ProfitLogin' });
const areaList = ref<any[]>([{ area_code: 86, label: '中国大陆', tips: '1开头, 11位手机号' }]);
const AreaValue = ref('+86');
const showChooseArea = ref(false);
const AreaIndex = ref(0);
const active = ref<any>(0);
const openId = ref<any>('');
const email = ref<any>('');
const mobile = ref<any>('');
const code = ref<any>('');
const emailCode = ref<any>('');
const pwd = ref<any>('');
const time = ref<any>(60);
const timeV2 = ref<any>(60); //
const timer = ref<any>(null);
const timerV2 = ref<any>(null); //
const throttle = ref<any>(true);
const codeThrottle = ref<any>(true);
const isAgreement = ref(false); //
const isWechatDevice = ref(false); //
const isWeiXinBrowser = /micromessenger/i.test(navigator.userAgent);
const argument = ref<any>({
videoid: '',
userid: '',
from_user_id: 0,
});
const save = () => {
let data = {
mobile: mobile.value,
area_code: 86,
code: code.value,
openid: localStorage.getItem('rt_openid'),
};
if (!mobile.value) {
showToast('请输入手机号');
return;
}
if (!code.value) {
showToast('请输入验证码');
return;
}
// if (!isAgreement.value) {
// showToast('');
// return;
// }
if (throttle.value) {
throttle.value = false;
showLoadingToast('');
requestGo({ url: `/h5/v1/auth/register`, data, method: 'post' })
.then((res) => {
showToast('登录成功');
throttle.value = true;
let result = res.data;
const userStore = useUserStore();
userStore.userID = result.id;
userStore.agentName = result.name || '----';
userStore.agentAvatar = result.avatar || 'https://image.fulllinkai.com/202203/09/cc1c73eb1a4941fef25a15cd1ff2f9df.png';
userStore.agentMobile = result.mobile || '';
userStore.token = result.token;
localStorage.setItem('rt_token', result.token);
localStorage.setItem('rt_openid', result.openid as any);
// backFillLoginData(result, userStore);
setTimeout(() => {
throttle.value = true;
if (localStorage.getItem('jumpRtPathName') as any) {
let queryData = JSON.parse(localStorage.getItem('jumpRtPathQuery') as any);
let paramsData = JSON.parse(localStorage.getItem('jumpRtPathParams') as any);
router.replace({
name: localStorage.getItem('jumpRtPathName') as any,
params: paramsData,
query: queryData,
});
} else {
router.replace({
name: 'user',
});
}
setTimeout(() => {
localStorage.removeItem('jumpRtPathName');
localStorage.removeItem('jumpRtPathQuery');
localStorage.removeItem('jumpRtPathParams');
}, 50);
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const saveV2 = () => {
let data = {
mobile: mobile.value,
password: pwd.value,
};
if (!mobile.value) {
showToast('请输入手机号');
return;
}
if (!pwd.value) {
showToast('请输入密码');
return;
}
if (!isAgreement.value) {
showToast('请先阅读并同意协议');
return;
}
if (throttle.value) {
throttle.value = false;
showLoadingToast('');
requestGo({ url: `/go/api/app/server/mobile/pwd/login`, data, method: 'post' })
.then((res) => {
showToast('登录成功');
throttle.value = true;
const userStore = useUserStore();
userStore.token = res.data.token;
localStorage.setItem('rt_token', res.data.token);
setTimeout(() => {
throttle.value = true;
if (localStorage.getItem('jumpPLPathName') as any) {
let queryData = JSON.parse(localStorage.getItem('jumpPLPathQuery') as any);
let paramsData = JSON.parse(localStorage.getItem('jumpPLPathParams') as any);
router.replace({
name: localStorage.getItem('jumpPLPathName') as any,
params: paramsData,
query: queryData,
});
} else {
console.log('3320');
router.replace({
name: 'appProfitUser',
});
}
setTimeout(() => {
localStorage.removeItem('jumpPLPathName');
localStorage.removeItem('jumpPLPathQuery');
localStorage.removeItem('jumpPLPathParams');
}, 50);
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const getCode = () => {
let data = {
mobile: mobile.value,
area_code: areaList.value[AreaIndex.value].area_code,
};
if (!mobile.value) {
showToast('请输入手机号');
return;
}
if (codeThrottle.value) {
codeThrottle.value = false;
showLoadingToast('');
requestGo({ url: `/h5/v2/user/smscode`, data, hideLoading: true, method: 'post' })
.then(() => {
timer.value = setInterval(() => {
time.value--;
}, 1000);
setTimeout(() => {
showToast('验证码已发送');
}, 1000);
})
.catch((err) => {
codeThrottle.value = true;
console.log(err);
});
}
};
//
const getEmailCode = () => {
let data = {
email: email.value,
};
if (!email.value) {
showToast('请输入邮箱');
return;
}
if (codeThrottle.value) {
codeThrottle.value = false;
showLoadingToast('');
requestGo({ url: `h5/v2/user/emailcode`, data, hideLoading: true, method: 'post' })
.then(() => {
timerV2.value = setInterval(() => {
timeV2.value--;
}, 1000);
setTimeout(() => {
showToast('验证码已发送');
}, 1000);
})
.catch((err) => {
codeThrottle.value = true;
console.log(err);
});
}
};
const getAreaCode = () => {
requestGo({ url: `/h5/v2/user/areacode/list`, hideLoading: true, method: 'get' })
.then((res) => {
let result = res.data;
areaList.value = result;
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
// const getQrcode = () => {
// const u = navigator.userAgent;
// const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
// let data = {
// from_user_id: argument.value.from_user_id,
// open_id: openId.value,
// platform: isAndroid ? 'Android' : 'IOS',
// };
// requestGo({ url: `/h5/v1/other/login/scan/qrcode`, data, hideLoading: true, method: 'post' })
// .then(() => {})
// .catch((err) => {
// console.log(err);
// });
// };
// const openAppFn = () => {
// if (isWechatDevice.value) {
// showDialog({
// title: '',
// confirmButtonText: '',
// overlayStyle: { background: '#333333', opacity: 0.4 },
// message: '',
// })
// .then(() => {})
// .catch(() => {});
// return;
// }
// showLoadingToast('');
// const userAgent = navigator.userAgent || navigator.vendor || window.opera;
// // iOS
// const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
// if (isIOS) {
// // iOS
// const appUrl = 'yfheal://';
// const appStoreUrl = 'https://apps.apple.com/cn/app/%E5%8F%8B%E7%A6%8F%E5%90%8C%E4%BA%AB/id6470103042';
// setTimeout(function () {
// if (!document.hidden) {
// // window.location.href = appStoreUrl;
// }
// }, 2000);
// // window.location.href = appUrl;
// window.location.href = appStoreUrl;
// } else if (/android/i.test(userAgent)) {
// // Android
// // const appUrl = 'intent://yourapp/#Intent;scheme=yourappscheme;package=com.yourapp.package;end';
// const appUrl = 'gqkcqn://';
// const androidDownload = 'https://images.health.ufutx.com/apk/lovehealth.apk';
// setTimeout(function () {
// if (!document.hidden) {
// window.location.href = androidDownload;
// }
// }, 800);
// // window.location.href = appUrl;
// } else {
// console.log('');
// }
// };
//
const selectedArea = (e, index) => {
AreaValue.value = `+${e.area_code}`;
AreaIndex.value = index;
showChooseArea.value = !showChooseArea.value;
};
const jumpAgreement = (url) => {
router.push({
name: url,
});
};
watch(time, () => {
if (time.value == 0) {
clearInterval(timer.value);
timer.value = null;
codeThrottle.value = true;
time.value = 60;
}
});
watch(timeV2, () => {
if (timeV2.value == 0) {
clearInterval(timerV2.value);
timerV2.value = null;
codeThrottle.value = true;
timeV2.value = 60;
}
});
const isWechatBrowser = () => {
return /MicroMessenger/i.test(navigator.userAgent);
};
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appDownloadPageV2 {
background: #ffffff;
overflow-y: auto;
min-height: 100vh;
padding-bottom: px2rem(160);
}
.ui-top-logo {
width: px2rem(320);
height: px2rem(58);
display: block;
margin: px2rem(142) auto px2rem(80) auto;
}
.ui-data-box {
padding: 0 px2rem(74);
}
.ui-area-code {
padding-right: px2rem(20);
white-space: nowrap;
word-break: break-all;
z-index: 2;
}
.Angle_icon {
width: px2rem(24);
height: px2rem(24);
display: block;
margin-left: px2rem(4);
vertical-align: top;
}
.ui-area-mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 21;
}
.area_code_choose_box {
position: absolute;
left: px2rem(90);
top: px2rem(66);
background: #ffffff;
box-shadow: 0 px2rem(4) px2rem(28) 0 rgba(0, 0, 0, 0.08);
border-radius: px2rem(8);
z-index: 22;
.area_code_choose_list {
max-height: px2rem(420);
overflow-y: scroll;
.area_code_choose {
padding: px2rem(24) px2rem(30) 0 px2rem(30);
.area_code_choose_item {
word-break: break-all;
white-space: nowrap;
}
.selected_icon {
width: px2rem(36);
height: px2rem(36);
display: block;
}
}
.area_code_choose:last-child {
padding-bottom: px2rem(30);
}
}
}
.area_code_choose_box:before {
content: '';
width: 0;
height: 0;
position: absolute;
top: px2rem(-24);
left: 50%;
transform: translate(-50%);
border-top: solid px2rem(12) transparent;
border-left: solid px2rem(12) transparent;
border-right: solid px2rem(12) transparent;
border-bottom: solid px2rem(12) #ffffff;
}
.ui-title {
width: px2rem(130);
height: px2rem(40);
margin-right: px2rem(20);
}
.ui-user-input,
.ui-user-input-v2 {
width: 100%;
padding: 0;
background: initial;
font-size: px2rem(40);
}
.ui-user-input-v2 {
width: px2rem(260);
}
.ui-user-input-v3 {
width: px2rem(600);
}
::v-deep(.van-search__content) {
background: initial;
padding: 0;
}
::v-deep(input::-webkit-input-placeholder) {
color: #cccccc;
font-size: px2rem(32);
}
// 线
::v-deep(.van-cell:after) {
position: relative;
}
.ui-code-btn {
width: px2rem(170);
padding: px2rem(14) 0;
border-radius: px2rem(40);
background: #18ca6e;
}
.set {
background: rgba(24, 202, 110, 0.1);
}
.ui-line,
.ui-line-v2 {
width: 100%;
height: px2rem(2);
background: #f0f0f0;
margin: px2rem(40) 0 px2rem(40) 0;
}
.ui-line-v2 {
margin: px2rem(40) 0 px2rem(40) 0;
}
.ui-agreement-box {
white-space: nowrap;
display: flex;
padding-top: px2rem(120);
.ui-agreement-icon {
width: px2rem(28);
height: px2rem(28);
display: block;
margin-right: px2rem(12);
margin-top: px2rem(2);
}
.ui-agreement-text {
flex: 1;
}
}
.colorBussnessTheme {
color: #18ca6e;
}
.ui-signIn-btn {
width: px2rem(640);
height: px2rem(112);
border-radius: px2rem(60);
background: #18ca6e;
margin: px2rem(140) auto 0 auto;
}
:deep(.van-tab) {
font-size: 20px;
}
:deep(.van-tabs__line) {
width: 20px;
height: 4px;
background: #18ca6e;
//background: linear-gradient(90deg, #18ca6e 0%, rgba(24, 202, 110, 0) 100%);
}
.appWatchTop-pic {
width: 100%;
height: auto;
display: inline;
}
.appWatchBox {
position: relative;
animation: slide-down 1s ease forwards;
@keyframes slide-down {
from {
top: -100px; /* 初始位置在上方,超出屏幕外 */
}
to {
top: 0;
}
}
}
</style>

View File

@ -0,0 +1,241 @@
<template>
<div class="ui-newSurveyAuditList">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" @load="getList">
<div v-if="!refreshing && list.length == 0">
<img class="ui-empty-data-icon" src="https://images.health.ufutx.com/202505/20/259f20509b57b36199ef7619f8d63733.png" alt="" />
<div class="color6 font_15 text-center">暂无数据</div>
</div>
<div v-else>
<div class="ui-list-box">
<div v-for="(item, index) in list" :key="index" class="ui-list-item" @click="jumpPath(item)">
<div class="f-fbc">
<div class="f-fcl">
<img class="ui-user-pic" :src="item.user.avatar || 'https://image.fulllinkai.com/202307/18/449c3253ca2bbed9314d39977a486d0e.png'" alt="" />
<div>
<div class="f-fcl">
<div class="font_15 color0E bold ellipsis_1 ui-user-name">{{ item.user.name }}</div>
<img v-if="item.user.is_privacy == 1" alt="" class="ui-privacy" src="https://images.health.ufutx.com/202510/24/f5f69e5439e632defe963f6c9d0a9e36.png" />
</div>
<div class="font_12 color76c ui-pt-4">{{ item.create_time }}</div>
</div>
</div>
<div class="ui-btn-v4 f-fcc font_12">详情</div>
</div>
<div v-if="item.status > 0" class="ui-audit-list">
<div>{{ item.operate_user_name }}</div>
<div :class="item.status > 1 ? 'errorStatus' : 'successStatus'">{{ statusFn(item.status, 1) }}</div>
<div>方案前健康调查表单</div>
</div>
<div v-if="item.other_status > 0" class="ui-audit-list">
<div>{{ item.other_operate_user_name }}</div>
<div :class="item.other_status > 1 ? 'errorStatus' : 'successStatus'">{{ statusFn(item.other_status, 1) }}</div>
<div>方案前健康调查表单</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onActivated, onMounted, ref } from 'vue';
import requestSurvey from '@/utils/request';
import router from '@/router';
defineOptions({ name: 'NewSurveyAuditList' });
const serviceUserId = ref<any>('');
const list = ref<any[]>([]); //
const noMore = ref(false); //
const refreshing = ref(true); // false
const finished = ref(false); // true
const loading = ref(false); // false
const page = ref(1); //
const getList = () => {
requestSurvey({ url: `/go/api/app/v1/other/get/before/dma/question/list?page=${page.value}`, method: 'get' })
.then((res) => {
const result = res.data;
if (list.value.length === 0 || page.value === 1) {
list.value = result.data.map((item, index) => {
item.listV2 = [
{
name: '蓝色',
status: index > 1 ? 2 : 1,
},
];
return item;
});
} else if (list.value.length >= 15) {
result.data.forEach((item, index) => {
item.listV2 = {
name: '蓝色',
status: index > 1 ? 2 : 1,
};
list.value.push(item);
});
}
refreshing.value = false;
loading.value = false;
if (list.value.length < 15 || result.data.length < 15) {
finished.value = true;
noMore.value = true;
}
page.value++;
})
.catch((err) => {
console.log(err);
});
};
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
const jumpPath = (e) => {
router.push({
name: 'appSurveyExamine',
query: { id: e.id, service_user_id: serviceUserId.value },
});
};
const statusFn = (status, type) => {
let statusText = '';
let typeText = '';
switch (status) {
case 1:
statusText = '已通过';
typeText = '通过原因';
break;
case 2:
statusText = '已拒绝';
typeText = '拒绝原因';
break;
case 3:
statusText = '需要补充资料';
typeText = '需要补充资料原因';
break;
}
if (type == 1) {
return statusText;
} else if (type == 2) {
return statusText;
}
};
onActivated(() => {
if (list.value && list.value.length && localStorage.getItem('examine')) {
let examine = JSON.parse(localStorage.getItem('examine') as any);
list.value.forEach((item) => {
if (item.id == examine.id) {
item.status = examine.status;
localStorage.removeItem('examine');
}
});
}
});
onMounted(() => {
let route = router.currentRoute.value.query;
serviceUserId.value = route.service_user_id;
// let data = {
// mobile: '15622316024',
// area_code: 86,
// code: '009527',
// };
// requestSurvey({ url: `/api/app/mobile/register/login`, data, method: 'post' });
});
</script>
<style lang="scss" scoped>
.ui-newSurveyAuditList {
background: #ffffff;
overflow-y: auto;
height: 100vh;
* {
box-sizing: content-box;
}
}
.ui-empty-data-icon {
width: 135px; /* 原px2rem(270) */
height: 100px; /* 原px2rem(200) */
display: block;
margin: 25vh auto 0 auto;
}
.ui-list-box {
padding: 16px 16px 100px 16px; /* 原px2rem(32) px2rem(32) px2rem(200) px2rem(32) */
.ui-list-item {
padding-bottom: 20px; /* 原px2rem(40) */
margin-bottom: 20px; /* 原px2rem(40) */
border-bottom: 1px solid #dddddd; /* 原px2rem(2) */
.ui-user-pic {
width: 40px; /* 原px2rem(80) */
height: 40px; /* 原px2rem(80) */
border-radius: 50%;
display: block;
object-fit: cover;
object-position: center;
margin-right: 10px; /* 原px2rem(20) */
}
.ui-user-name {
max-width: 200px; /* 原px2rem(400) */
}
.ui-privacy {
margin-left: 10px;
width: 59.4px;
height: 22px;
}
.ui-btn,
.ui-btn-v2,
.ui-btn-v3,
.ui-btn-v4 {
padding: 0 10px;
//width: 66px; /* px2rem(132) */
height: 32px; /* 原px2rem(64) */
border-radius: 20px; /* 原px2rem(40) */
background: #3554c5;
}
.ui-btn-v2 {
color: #3554c5;
background: #eff3ff;
}
.ui-btn-v3 {
color: #ff2946;
background: #fff2f2;
}
.ui-btn-v4 {
color: #66676c;
background: #ddd;
}
}
.ui-audit-list {
margin-top: 10px;
padding: 4px 5px;
display: flex;
border-radius: 4px;
background: #f8f8f8;
font-size: 12px;
.successStatus {
color: #3554c5;
}
.errorStatus {
color: #ff2946;
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,893 @@
<template>
<div v-if="loading" class="ui-leaseShopDetail">
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item v-for="image in banner" :key="image" class="ui-relative">
<img class="ui-mall-pic" :src="image" alt="" />
</van-swipe-item>
<template #indicator="{ active, total }">
<div class="custom-indicator font_26 colorF">{{ active + 1 }}/{{ total }}</div>
</template>
</van-swipe>
<div class="ui-shop-detail-container">
<div class="ui-shop-data-box">
<div class="f-fcl">
<span class="font_28 ui-price-symbol bold">¥</span>
<div class="font_44 ui-show-price bold"><span v-html="htmlPrice"></span></div>
<div class="font_28 ui-original-price color6">¥{{ originPrice }}</div>
</div>
<div class="font_32 color3 bold ui-pt-10">{{ title }}</div>
</div>
<div class="ui-ExpressDelivery-data-box">
<div>
<div class="f-fcl font_28">
<span class="color9 ui-pr-24">配送</span>
<span class="ui-pr-24">广东深圳</span>
<span v-if="freightPrice != '0.00' || freightPrice != 0" class="ui-pl-24 ui-relative ui-distribution-address">运费{{ freightPrice }}</span>
</div>
<!-- <div class="font_28 ui-pt-24">-->
<!-- <span class="color9 ui-pr-24">服务</span>-->
<!-- <span class="color3">支持7天无理由退货</span>-->
<!-- </div>-->
</div>
</div>
</div>
<div v-if="describe" class="ui-relative ui-pt-40">
<img class="ui-line-icon" src="https://image.fulllinkai.com/202405/15/266a22038e0122c70fc282f00c78dbc7.png" alt="" />
<div class="font_26 color3 ui-line-text">商品详情</div>
</div>
<div class="font_28 color3 ui-detail-introduce">
<richTextParsing :rich-text="describe"></richTextParsing>
</div>
<div class="ui-bottom-operation f-fbc">
<div class="ui-operation-icon-box" @click="isShowPoster">
<img class="ui-operation-icon" src="https://image.fulllinkai.com/202405/15/b18fc23a9956849ddbead6882fa60bb9.png" alt="" />
<div class="font_20 color6 text-center">分享</div>
</div>
<div class="ui-buy-btn f-fcc font_30 colorF" @click="showBug = true">立即购买</div>
</div>
<sharePosterV2 ref="openSharePoster">
<template #sharePoster>
<!--生成海报html容器-->
<div id="capture" class="ui-poster-container">
<div class="ui-user-avatar-box f-fcc">
<div class="ui-user-avatar backCover" :style="{ background: 'url(' + canvasData.avatar + ')' }"></div>
<div class="color3 font_22 ui-ml-8 ui-user-name-box f-fcc">
<div v-if="shareName">{{ shareName }}</div>
<div>推荐一个好物给你</div>
</div>
</div>
<div class="ui-poster-pic-box">
<img class="ui-poster-pic" :src="canvasData.pic" alt="" />
</div>
<div class="ui-poster-data-box">
<div class="ui-mt-20 ui-ml-20">
<div class="f-fcl">
<span class="font_24 ui-poster-price-symbol">¥</span>
<div class="font_40 ui-poster-price"><span v-html="htmlSharePrice"></span></div>
</div>
<div class="font_24 ellipsis_2 ui-poster-title color3">{{ title }}</div>
<!-- <div class="font_24 ellipsis_2 ui-poster-title color9 ui-mt-10">{{ title }}</div>-->
</div>
<div class="ui-poster-qrcode-box">
<img class="ui-poster-qrcode" :src="canvasData.qrcode" alt="" />
<div class="font_20 color6 text-center">长按保存图片至相册</div>
</div>
</div>
</div>
</template>
</sharePosterV2>
<van-popup v-model:show="showBug" round position="bottom" :duration="0.5">
<div class="ui-bug-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="showBug = false" />
<div class="ui-buy-detail-box">
<img class="ui-buy-pic" :src="icon" alt="" />
<div>
<div class="ellipsis_2 font_32 color333 bold ui-buy-title">{{ title }}</div>
<div class="f-fcl ui-buy-price color3 font_24"> 已选{{ spuData.spec[spuIndex].name }} {{ spuSubIndex !== null ? spuSubData.spec[spuSubIndex].name : '' }} </div>
</div>
</div>
<div v-if="spuData.name" class="ui-sku-box">
<div class="ui-sku-list">
<div class="font_28 color3 bold">{{ spuData.name }}</div>
<div class="f-fcl f-wrap">
<div v-for="(item, index) in spuData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSku(index)">
<div class="ui-sku-item" :class="spuIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>
</div>
</div>
<div v-if="spuSubData.name" class="ui-pt-36">
<div class="font_28 color3 bold">{{ spuSubData.name }}</div>
<div class="f-fcl f-wrap">
<div v-for="(item, index) in spuSubData.spec" :key="index" class="font_26 f-fbc color3 ui-pt-16" @click="selectSubSku(index)">
<div class="ui-sku-item" :class="spuSubIndex == index ? 'ui-sku-item-active colorF' : 'color3'">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-num-box f-fbc ui-pt-28">
<div class="font_24 color6"><span class="font_28 color3 ui-mr-16">数量</span>库存{{ stock }}</div>
<div class="f-fcr ui-num-input-box">
<div class="ui-relative f-fcl">
<div class="font_40 color6 ui-num-input-minus" @click="numMinus()">
<div class="ui-num-minus">-</div>
</div>
<div class="ui-select-input font_28">{{ num }}</div>
<div class="font_40 color6 ui-num-input-add" @click="numAdd()">
<div class="ui-num-minus-v2">+</div>
</div>
</div>
</div>
</div>
</div>
<div class="f-fbc ui-total-price-box">
<div class="f-fcl">
<div class="font_24 color6">合计金额</div>
<div v-if="calculating" class="font_24 color6 bold">计算中...</div>
<div v-else-if="num == 0" class="font_24 color6">库存不足</div>
<div v-else class="f-fcl bold ui-total-price">
<span class="font_28 ui-buy-price-symbol">¥</span>
<div class="font_44"><span v-html="htmlTotalPrice"></span></div>
</div>
</div>
</div>
</div>
<div v-if="policyTitle" class="ui-confirm-bottom-box">
<div class="f-fcc">
<img v-show="!isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202405/16/fae039c670338fe6743b7f6a8803ec43.png" alt="" @click="isAgreement = !isAgreement" />
<img v-show="isAgreement" class="ui-agreement-icon" src="https://image.fulllinkai.com/202405/16/c27533426d24451aceba94e4fe316e88.png" alt="" @click="isAgreement = !isAgreement" />
<div class="font_24 color6" @click="isAgreement = !isAgreement">
已阅读并同意<span class="ui-agreement-text" @click.stop="jumpAgreement">{{ policyTitle }}</span>
</div>
</div>
</div>
<div class="ui-confirm-btn font_32 f-fcc colorF bold" @click="payment">立即支付</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onDeactivated, onActivated } from 'vue';
import qrcode from 'qrcode';
import { showToast } from 'vant';
import router from '@/router';
import requestGo from '@/utils/requestGo';
import { weXinShare } from '@/plugins/wxShare';
import { examineRegister, imageConversion } from '@/plugins/public';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'LeaseShopDetail' });
const userStore = useUserStore() as any;
const loading = ref(false);
const showBug = ref(false);
const timer = ref<any>(null);
const isAgreement = ref(false); //
const id = ref<any>('');
const title = ref<any>('');
const icon = ref<any>('');
const policyId = ref<any>('');
const policyTitle = ref<any>('');
const banner = ref<any[]>([]); //
const describe = ref<any>(); //
const num = ref<any>(0); //
const skuIds = ref<any>('');
const skuIdDataList = ref<any[]>([]);
const stock = ref<any>(0); // sku
const calculating = ref<any>(false); //
const spuData = ref<any>({}); // spu
const spuSubData = ref<any>({}); // spu
const spuIndex = ref(0); // spu
const spuSubIndex = ref<any>(null); // spu
const htmlPrice = ref<any>(''); //
const htmlSharePrice = ref<any>(''); //
const shareName = ref<any>('');
const htmlTotalPrice = ref<any>('0'); //
const originPrice = ref<any>(0); //
const freightPrice = ref<any>(0); //
const canvasData = ref<any>({ background: '', avatar: '', pic: '', qrcode: '' }); //
const openSharePoster = ref<any>(); //
//
const getDetail = () => {
requestGo({ url: `/h5/v2/shop/rental/spu/detail/${id.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
banner.value = [];
result.spu.banner = JSON.parse(result.spu.banner);
if (result.spu.banner && result.spu.banner.length > 0) {
banner.value = result.spu.banner;
} else {
banner.value.push(result.spu.icon);
}
describe.value = result.spu.description;
title.value = result.spu.title;
icon.value = result.spu.icon;
policyId.value = result.spu.policy_id;
policyTitle.value = result.spu.policy_title;
result.spu.spec = JSON.parse(result.spu.spec);
spuData.value = result.spu.spec;
spuSubData.value = {};
// sku
if (spuData.value.name && spuData.value.spec[spuIndex.value].sub_spec) {
spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
spuSubIndex.value = 0;
}
skuIds.value = '';
// skuid
spuData.value.spec.forEach((item) => {
if (item.skuid) {
skuIds.value += `${skuIds.value ? ',' : ''}${item.skuid}`;
}
if (item.sub_spec) {
recursion(item);
}
});
if (`${result.sku_default.price}`.includes('.')) {
let splitPrice = result.sku_default.price.split('.');
htmlSharePrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlSharePrice.value = result.sku_default.price;
}
shareName.value = userStore.agentName ? (userStore.agentName.length > 4 ? `${userStore.agentName.slice(0, 4)}...` : userStore.agentName) : '';
// skuidskuid
getSkuIdStock();
weXinShare('https://image.fulllinkai.com/202310/28/88e931a50ec0a8094fb46191b389457e.png', `https://health.ufutx.com/store/#/leaseShopDetail/${id.value}?from_user_id=${userStore.userID}&from_type=rt_${id.value}`, '友福商城', '邀请您进入友福商城');
let text = `https://health.ufutx.com/store/#/leaseShopDetail/${id.value}?from_user_id=${userStore.userID}&from_type=rt_${id.value}`;
qrcode.toDataURL(text, (err, url) => {
if (err) {
console.log(err);
} else {
// base64
canvasData.value.qrcode = url;
}
});
// base64
imageConversion('https://image.fulllinkai.com/202111/02/f23994d8fd3088c833a8bbf2bfdf6de5.png').then((res) => {
canvasData.value.background = res;
});
// base64
imageConversion(`${userStore.agentAvatar}`).then((res) => {
canvasData.value.avatar = res;
});
// base64
imageConversion(`${result.spu.icon}`).then((res) => {
canvasData.value.pic = res;
});
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
// skuid
const getSkuIdStock = () => {
requestGo({ url: `/h5/v2/shop/rental/sku/${skuIds.value}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '77777');
skuIdDataList.value = result;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
num.value = 1;
prepay('init');
})
.catch((err) => {
console.log(err);
});
};
// sku
const gainSelectStock = () => {
let id;
if (spuSubIndex.value !== null) {
id = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
id = spuData.value.spec[spuIndex.value].skuid;
}
skuIdDataList.value.forEach((item) => {
if (item.id == id) {
stock.value = item.stock;
originPrice.value = item.price;
}
});
};
// skuid
const recursion = (e) => {
e.sub_spec.spec.forEach((item) => {
if (item.skuid) {
skuIds.value += `${skuIds.value ? ',' : ''}${item.skuid}`;
}
if (item.sub_spec) {
recursion(item.sub_spec.spec);
}
});
};
//
const prepay = (e) => {
let id = '';
if (spuSubIndex.value !== null) {
id = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
id = spuData.value.spec[spuIndex.value].skuid;
}
clearTimeout(timer.value);
timer.value = setTimeout(() => {
requestGo({ url: `/h5/v2/shop/rental/sku/prepay/${id}/${e == 'init' ? 1 : num.value}/1`, method: 'get' })
.then((res) => {
const result = res.data;
// originPrice.value = result.price_origin;
freightPrice.value = result.freight;
loading.value = true;
if (e == 'init') {
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlPrice.value = result.price;
}
if (!stock.value) {
num.value = 0;
}
}
if (`${result.price}`.includes('.')) {
let splitPrice = result.price.split('.');
htmlTotalPrice.value = `${splitPrice[0]}.<span class="font_32">${splitPrice[1]}</span>`;
} else {
htmlTotalPrice.value = result.price;
}
calculating.value = false;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
// Sku
const numMinus = () => {
if (num.value <= 1) {
return;
}
num.value--;
calculating.value = true;
prepay('calculate');
};
// Sku
const numAdd = () => {
if (num.value >= stock.value) {
showToast('超出限购或商品库存上限');
return;
}
num.value++;
calculating.value = true;
prepay('calculate');
};
//
const payment = () => {
let data = {} as any;
let spuId;
if (spuSubIndex.value !== null) {
spuId = spuSubData.value.spec[spuSubIndex.value].skuid;
} else {
spuId = spuData.value.spec[spuIndex.value].skuid;
}
data.id = id.value;
data.skuId = spuId;
data.spuNum = num.value;
data.spuName = spuData.value.spec[spuIndex.value].name;
data.spuSubName = spuSubIndex.value !== null ? spuSubData.value.spec[spuSubIndex.value].name : '';
//
if (userStore.registerAgent == '0') {
examineRegister(router.currentRoute.value);
return;
}
if (num.value <= 0) {
showToast('请先选择购买数量');
return;
}
if (policyTitle.value && !isAgreement.value) {
showToast('请先阅读并同意协议');
return;
}
router.push({
name: 'leaseShopOrderConfirm',
query: { orderData: JSON.stringify(data) },
});
};
const selectSku = (index) => {
if (spuIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuIndex.value = index;
if (spuData.value.spec[index].sub_spec) {
spuSubData.value = spuData.value.spec[index].sub_spec;
spuSubIndex.value = 0;
} else {
spuSubData.value = {};
spuSubIndex.value = null;
}
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
if (stock.value) {
num.value = 1;
calculating.value = true;
prepay('calculate');
}
};
const selectSubSku = (index) => {
if (spuSubIndex.value == index) {
return;
}
htmlTotalPrice.value = '0';
spuSubData.value = spuData.value.spec[spuIndex.value].sub_spec;
spuSubIndex.value = index;
num.value = 0;
if (skuIdDataList.value && skuIdDataList.value.length > 0) {
gainSelectStock();
}
if (stock.value) {
num.value = 1;
calculating.value = true;
prepay('calculate');
}
};
const isShowPoster = () => {
if (canvasData.value.background && canvasData.value.avatar && canvasData.value.pic && canvasData.value.qrcode) {
openSharePoster.value.isShow();
}
};
const jumpAgreement = () => {
router.push({
name: 'shopAgreement',
params: { id: policyId.value },
query: { title: policyTitle.value },
});
};
//
onDeactivated(() => {
openSharePoster.value.remove();
});
onActivated(() => {
let route = router.currentRoute.value.params;
if (id.value != route.id) {
id.value = route.id;
loading.value = false;
showBug.value = false;
spuIndex.value = 0;
spuSubIndex.value = null;
getDetail();
} else {
// base64
imageConversion(`${userStore.agentAvatar}`).then((res) => {
canvasData.value.avatar = res;
});
}
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-leaseShopDetail {
background: #f4f4f4;
overflow-y: scroll;
height: 100vh;
}
.ui-mall-pic {
width: 100vw;
height: px2rem(750);
display: block;
object-fit: cover;
object-position: center;
}
.custom-indicator {
position: absolute;
right: px2rem(20);
bottom: px2rem(20);
border-radius: px2rem(8);
padding: px2rem(8) px2rem(14);
letter-spacing: px2rem(4);
background: #aaaaaa;
}
.ui-shop-detail-container {
padding: px2rem(24);
.ui-shop-data-box,
.ui-ExpressDelivery-data-box {
padding: px2rem(30) px2rem(24);
background: #ffffff;
border-radius: px2rem(16);
margin-bottom: px2rem(24);
.ui-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-show-price,
.ui-price-symbol {
color: #cc352f;
}
.ui-original-price {
text-decoration: line-through;
margin-left: px2rem(16);
}
}
.ui-ExpressDelivery-data-box {
.ui-distribution-address:after {
content: '';
width: px2rem(2);
height: px2rem(22);
background: #d8d8d8;
position: absolute;
left: 0;
top: px2rem(10);
}
}
}
.ui-line-icon {
width: px2rem(310);
height: px2rem(12);
display: block;
margin: 0 auto;
}
.ui-line-text {
position: absolute;
left: 50%;
top: px2rem(28);
transform: translateX(-50%);
}
.ui-detail-introduce {
padding: px2rem(20) 0 px2rem(120) 0;
background: #ffffff;
margin-top: px2rem(30);
.ui-introduce-height {
width: 100vw;
height: px2rem(260);
background: #ffffff;
}
}
.ui-bottom-operation {
width: 100%;
background: #ffffff;
padding: px2rem(18) px2rem(24) px2rem(18) 0;
box-sizing: border-box;
position: fixed;
bottom: 0;
left: 0;
border-top: px2rem(2) solid #f5f5f5;
.ui-operation-icon-box {
flex: 1;
.ui-operation-icon {
width: px2rem(44);
height: px2rem(44);
display: block;
margin: 0 auto px2rem(2) auto;
}
}
.ui-buy-btn {
width: px2rem(480);
height: px2rem(88);
background: #2a63b2;
border-radius: px2rem(24);
}
}
.ui-poster-container {
position: relative;
position: fixed;
top: -100000;
left: 0;
z-index: -99999;
border-radius: px2rem(16);
width: px2rem(482);
height: px2rem(806);
padding: 0 px2rem(30) px2rem(30) px2rem(30);
background: #f7f7f7;
box-sizing: border-box;
background-size: cover !important;
background-position: center !important;
background-repeat: no-repeat !important;
.ui-user-avatar-box {
width: px2rem(440);
height: px2rem(48);
background: #ffffff;
border-radius: px2rem(40);
margin-top: px2rem(8);
position: absolute;
left: 50%;
transform: translateX(-50%);
top: px2rem(10);
z-index: 10;
.ui-user-avatar {
width: px2rem(28);
height: px2rem(28);
border-radius: 50%;
margin-left: px2rem(18);
}
.ui-user-name-box {
margin-top: px2rem(-6);
}
}
.ui-poster-pic-box,
.ui-poster-data-box {
width: px2rem(450);
height: px2rem(450);
border-radius: px2rem(16);
background: #ffffff;
position: absolute;
top: px2rem(80);
left: px2rem(16);
display: flex;
justify-content: space-between;
.ui-poster-pic {
width: 100%;
height: 100%;
border-radius: px2rem(16);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
}
}
.ui-poster-data-box {
top: px2rem(546);
height: px2rem(244);
.ui-poster-title {
width: px2rem(226);
line-height: px2rem(36);
max-width: px2rem(226);
margin-top: px2rem(4);
}
.ui-poster-price,
.ui-poster-price-symbol {
color: #cc352f;
}
.ui-poster-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(6);
}
.ui-poster-qrcode-box {
.ui-poster-qrcode {
width: px2rem(200);
height: px2rem(200);
border-radius: px2rem(16);
display: block;
object-fit: cover;
object-position: center;
margin-bottom: px2rem(-8);
}
}
}
}
.ui-bug-box {
position: relative;
background: #ffffff;
padding-bottom: px2rem(60);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-buy-detail-box {
display: flex;
justify-content: left;
height: px2rem(160);
position: relative;
padding: px2rem(30) px2rem(30) 0 px2rem(30);
.ui-buy-pic {
width: px2rem(160);
min-width: px2rem(160);
height: px2rem(160);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
margin-right: px2rem(16);
border-radius: px2rem(8);
border: px2rem(2) solid #f8f8f8;
}
.ui-buy-title {
max-width: px2rem(440);
line-height: px2rem(48);
}
.ui-buy-price {
position: absolute;
bottom: px2rem(-12);
}
}
.ui-buy-price-symbol {
margin-right: px2rem(2);
margin-top: px2rem(2);
}
.ui-total-price-box {
padding-top: px2rem(40);
.ui-total-price {
color: #cc352f;
}
}
.ui-user-input {
width: px2rem(532);
box-sizing: border-box;
border: px2rem(2) solid #f5f5f5;
border-radius: px2rem(16);
padding: px2rem(16) px2rem(30);
}
::v-deep(input::-webkit-input-placeholder) {
font-weight: initial;
color: #999999;
}
// 线
::v-deep(.van-cell:after) {
position: relative;
}
.ui-sku-box {
padding: px2rem(40) px2rem(30) px2rem(24) px2rem(30);
.ui-sku-list {
.ui-sku-item,
.ui-sku-item-active {
padding: px2rem(12) px2rem(24);
background: #f4f4f4;
border-radius: px2rem(8);
margin-right: px2rem(20);
}
.ui-sku-item-active {
background: #2a63b2;
}
.ui-active-sku {
background: #e8fff7;
}
}
}
.ui-num-box {
.ui-num-input-box {
border-radius: px2rem(8);
border: px2rem(2) solid #ececec;
.ui-num-input-minus,
.ui-num-input-add {
width: px2rem(56);
height: px2rem(56);
text-align: center;
position: relative;
.ui-num-minus,
.ui-num-minus-v2 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ui-num-minus {
top: 43%;
}
}
}
.ui-num-input-minus {
border-right: px2rem(2) solid #ececec;
}
.ui-num-input-add {
border-left: px2rem(2) solid #ececec;
}
.ui-select-input {
width: px2rem(44);
border: none;
text-align: center;
padding: px2rem(4) px2rem(20);
background: initial;
line-height: px2rem(40);
}
}
.ui-confirm-btn {
width: px2rem(638);
height: px2rem(88);
background: #2a63b2;
border-radius: px2rem(24);
margin: px2rem(28) auto 0 auto;
}
.ui-confirm-bottom-box {
padding-top: px2rem(28);
border-top: px2rem(2) solid #eaeaea;
.ui-agreement-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
vertical-align: top;
margin-right: px2rem(8);
}
.ui-agreement-text {
color: #2a63b2;
}
}
}
</style>

View File

@ -0,0 +1,640 @@
<template>
<div v-if="loading" class="ui-leaseShopOrderConfirm">
<div class="ui-m-inf">
<div class="ui-relative">
<div class="f-fbc">
<div v-for="(item, index) in receivingList" :key="index" class="font_28 f-fcc ui-receiving-item" :class="receivingIndex == index ? 'colorBlue font_30 bold' : 'color3'" @click="selectSymptom(index)">
<div class="ui-receiving-text">
{{ item }}
</div>
</div>
</div>
<img v-show="receivingIndex == 0" class="ui-receiving-icon" src="https://image.fulllinkai.com/202404/09/122fe1183af7e8e15dfa3ea4b5ba861f.png" alt="" @click="selectSymptom(1)" />
<img v-show="receivingIndex == 1" class="ui-receiving-icon-v2" src="https://image.fulllinkai.com/202404/09/64e3443b623f73dd341389fc53e303e8.png" alt="" @click="selectSymptom(0)" />
</div>
<div class="ui-address-box">
<div v-if="receivingIndex == 0">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提地址:</div>
<div class="font_28 color3 ui-address-content">广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">自提人:</div>
<div class="font_28 color3 ui-address-content">{{ userStore.agentName }}{{ userStore.agentMobile }}</div>
</div>
</div>
</div>
<div v-else @click="openWx">
<div v-if="detailedAddress">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货地址:</div>
<div class="font_28 color3 ui-address-content">{{ detailedAddress }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/3b4a522e6bdb081a14186712f87cb20b.png" alt="" />
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">收货人:</div>
<div class="font_28 color3 ui-address-content">{{ address.userName }}{{ address.telNumber }}</div>
</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
<div v-else>
<div class="ui-address-item ui-pt-6 ui-pb-32">
<div class="font_28 color3 bold">点击添加收货地址</div>
<img class="ui-address-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
<img class="ui-decoration-icon" src="https://image.fulllinkai.com/202309/20/e8a30e7308236eb3894fec737b66657d.png" alt="" />
</div>
<div class="ui-inf-box">
<div class="ui-m-avt-lst ui-relative">
<img class="ui-pic" :src="icon" alt="" />
<div class="ui-m-lst-ri">
<div class="font_32 ellipsis_2 bold ui-title">{{ title }}</div>
<div class="font_28 ui-product-spu-name color6">已选{{ orderData.spuName }}{{ orderData.spuSubName ? '' + orderData.spuSubName : '' }}</div>
<div class="font_28 color3 ui-product-num">X {{ orderData.spuNum }}</div>
</div>
</div>
<div class="f-fbc ui-mt-40">
<div class="font_28 color3">商品总额</div>
<div class="font_28 bold"><span class="ui-buy-price-symbol">¥</span>{{ totalPrices }}</div>
</div>
<div v-if="receivingIndex != 0" class="f-fbc ui-mt-20">
<div class="font_28 color3">快递费用</div>
<div class="font_28"><span class="ui-buy-price-symbol">¥</span>{{ freightPrice }}</div>
</div>
<div v-if="related.share_user_name || related.invite_user_name || related.referrer_user_name">
<div v-if="related.share_user_name" class="f-fbc ui-mt-22">
<div class="font_28 color3">分享用户</div>
<div class="font_28 bold color6">{{ related.share_user_name }}</div>
</div>
<div v-if="related.invite_user_name" class="f-fbc ui-mt-22">
<div class="font_28 color3">邀请用户</div>
<div class="font_28 bold color6">{{ related.invite_user_name }}</div>
</div>
<div v-if="related.referrer_user_name" class="f-fbc ui-mt-22">
<div class="font_28 color3">推荐用户</div>
<div class="font_28 bold color6">{{ related.referrer_user_name }}</div>
</div>
</div>
<div v-else>
<div class="f-fbc ui-mt-22">
<div class="font_28 color3">关联信息</div>
<div class="font_28 bold color6">暂无关联信息</div>
</div>
</div>
<!-- <div class="f-fbc ui-mt-20">-->
<!-- <div class="font_28 color3">服务</div>-->
<!-- <div class="font_28">七天无理由退货</div>-->
<!-- </div>-->
<div class="ui-discounts-box f-fbc">
<div></div>
<div class="f-fcr">
<div class="font_28 color3">应付金额</div>
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_32 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
</div>
</div>
<div class="ui-remark-box font_28 f-fbc" @click="showRemark = true">
<div class="color3">订单备注</div>
<div class="ui-remark-text">
<div class="f-fcr text-right">
<div class="ui-mr-10 ellipsis_1" :class="remark ? 'color3' : 'color6'">{{ remark ? remark : '请填写备注' }}</div>
<img class="ui-remark-triangle-icon" src="https://image.fulllinkai.com/202405/16/c7baecb6a93780e115b54d9602ff06b3.png" alt="" />
</div>
</div>
</div>
</div>
<div class="ui-operation-box f-fbc">
<div class="font_24 color3 f-fcl">
合计
<div class="f-fcl ui-buy-price">
<span class="font_28 ui-buy-price-symbol bold">¥</span>
<div class="font_44 bold"><span v-html="htmlTotalPrices"></span></div>
</div>
</div>
<div class="ui-buy-btn f-fcc colorF font_30 bold" @click="payment">确认支付</div>
</div>
<van-popup v-model:show="showRemark" round position="bottom" :duration="0.5">
<div class="ui-remark-input-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="(showRemark = false), (remark = '')" />
<div class="text-center color3 font_32 bold">订单备注</div>
<van-field v-model="remark" type="textarea" autosize rows="5" show-word-limit class="ui-remark-input f-fcc font_40 color3 bold" :maxlength="200" placeholder="请填写备注" />
<div class="font_32 colorF f-fcc ui-remark-btn" @click="showRemark = false">确认</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { closeToast, showToast, showDialog } from 'vant';
import wx from 'weixin-js-sdk';
import router from '@/router';
import requestGo from '@/utils/requestGo';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'LeaseShopOrderConfirm' });
const userStore = useUserStore() as any;
const throttle = ref(true);
const orderData = ref<any>({});
const loading = ref<any>(false);
const timer = ref<any>(null);
const receivingIndex = ref(1);
const receivingList = ref<any[]>(['线下自提', '物流配送']);
const related = ref<any>({}); //
const title = ref<any>('');
const icon = ref<any>('');
const showRemark = ref(false);
const remark = ref<any>('');
const totalPrices = ref<any>(0); //
const htmlTotalPrices = ref<any>(0); //
const freightPrice = ref<any>(0); //
const totalNumber = ref<any>(0);
const wxPay = ref<any>(null); // config
const tradeId = ref<any>(''); // ID
const tradeNo = ref<any>(''); //
const detailedAddress = ref<any>('');
const address = ref<any>({
userName: '',
postalCode: '',
provinceName: '',
cityName: '',
countyName: '',
detailInfo: '',
nationalCode: '',
telNumber: '',
});
//
const getDetail = () => {
requestGo({ url: `/h5/v2/shop/rental/spu/detail/${orderData.value.id}`, method: 'get' })
.then((res) => {
const result = res.data;
title.value = result.spu.title;
icon.value = result.spu.icon;
related.value = result.related_person_detail;
//
if (result.recive && result.recive.address) {
address.value.userName = result.recive.name;
address.value.telNumber = result.recive.mobile;
detailedAddress.value = result.recive.address;
}
})
.catch((err) => {
console.log(err);
});
};
//
const prepay = () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
requestGo({ url: `/h5/v2/shop/rental/sku/prepay/${orderData.value.skuId}/${orderData.value.spuNum}/${receivingIndex.value == 0 ? 1 : 2}`, method: 'get' })
.then((res) => {
const result = res.data;
freightPrice.value = result.freight;
totalNumber.value = result.total_number;
loading.value = true;
//
if (`${result.total}`.includes('.')) {
let splitPrice = result.total.split('.');
htmlTotalPrices.value = `${splitPrice[0]}.<span class="font_28">${splitPrice[1]}</span>`;
} else {
htmlTotalPrices.value = result.total;
}
totalPrices.value = result.price;
})
.catch((err) => {
console.log(err);
});
}, 300);
};
//
const payment = () => {
let data = {
num: orderData.value.spuNum * 1,
remark: remark.value,
total_number: totalNumber.value,
from_source: 'rt',
spu_id: orderData.value.id * 1,
sku_id: orderData.value.skuId * 1,
ship: receivingIndex.value == 0 ? 1 : 2,
name: receivingIndex.value == 0 ? userStore.agentName : address.value.userName,
mobile: receivingIndex.value == 0 ? userStore.agentMobile : address.value.telNumber,
address: receivingIndex.value == 0 ? '广东省深圳市南山区南新路阳光科创中心B座33楼3301(荔林地铁站C口步行170米)' : detailedAddress.value,
pay_type: 1,
};
if (receivingIndex.value == 1) {
if (!detailedAddress.value) {
showToast('请填写收货地址');
return;
}
if (!address.value.userName) {
showToast('请填写你的姓名');
return;
}
if (!address.value.telNumber) {
showToast('请填写你的手机号');
return;
}
if (!/^1(3|4|5|6|7|8|9)\d{9}$/.test(address.value.telNumber)) {
showToast('请输入正确的手机号码');
return;
}
}
if (throttle.value) {
throttle.value = false;
requestGo({ url: `h5/v2/shop/rental/buy`, data, method: 'post' })
.then((res) => {
if (res.code == 7) {
showDialog({
title: '温馨提示',
message: '价格已经发生变化,请重新确认',
}).then(() => {
throttle.value = true;
prepay();
});
return;
}
let result = res.data;
wxPay.value = result.config;
tradeId.value = result.order.id;
tradeNo.value = result.order.trade_no;
if (wxPay.value && (wxPay.value.appid || wxPay.value.appId)) {
wx.config({
beta: true, // wx.invokejsapi
debug: false, // ,apialertpclogpc
appId: wxPay.value.appid || wxPay.value.appId, // appID
timestamp: wxPay.value.timeStamp, //
nonceStr: wxPay.value.nonceStr, //
signature: wxPay.value.paySign, // -JS-SDK使
jsApiList: ['chooseWXPay'], // 使JS
});
wx.checkJsApi({
jsApiList: ['chooseWXPay'], // JS
success(res) {
console.log(res);
},
});
wx.ready(function () {
throttle.value = true;
wx.chooseWXPay({
timestamp: wxPay.value.timeStamp,
nonceStr: wxPay.value.nonceStr,
package: wxPay.value.package,
signType: wxPay.value.signType,
paySign: wxPay.value.paySign,
//
success(res) {
if (res.errMsg === 'chooseWXPay:ok') {
console.log(res);
callBack();
if (userStore.isAgent == 2) {
userStore.isAgent = 1;
}
showToast('支付成功');
setTimeout(() => {
throttle.value = true;
}, 1200);
}
},
//
cancel() {
throttle.value = true;
showToast('取消支付');
},
//
fail() {
throttle.value = true;
showToast('支付失败');
},
});
});
wx.error(function (res) {
throttle.value = true;
console.log(res);
});
} else if (result.prepay_code == 4) {
showToast('支付成功');
if (userStore.isAgent == 2) {
userStore.isAgent = 1;
}
setTimeout(() => {
throttle.value = true;
router.push({
name: 'shopOrderDetail',
params: { id: tradeId.value },
});
}, 1200);
} else {
showToast('支付失败');
}
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const getMailData = () => {
requestGo({ url: `/h5/v2/shop/rental/order/express/last`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
if (result && result.mobile) {
detailedAddress.value = result.address;
address.value.userName = result.name;
address.value.telNumber = result.mobile;
}
})
.catch((err) => {
console.log(err);
});
};
const callBack = () => {
requestGo({ url: `h5/v2/shop/rental/wechatpay/callback/${tradeNo.value}`, hideLoading: true, method: 'post' })
.then((res) => {
console.log(res);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
//
const openWx = () => {
if (receivingIndex.value == 0) {
return;
}
wx.openAddress({
success(res) {
address.value.userName = res.userName; //
address.value.postalCode = res.postalCode; //
address.value.provinceName = res.provinceName; //
address.value.cityName = res.cityName; //
address.value.countryName = res.countryName; //
address.value.detailInfo = res.detailInfo; //
address.value.nationalCode = res.nationalCode; //
address.value.telNumber = res.telNumber; //
detailedAddress.value = `${res.provinceName}` + `${res.cityName}` + `${res.countryName}` + `${res.detailInfo}`;
},
});
};
//
const selectSymptom = (index) => {
receivingIndex.value = index;
prepay();
};
onMounted(() => {
let route = router.currentRoute.value.query;
orderData.value = JSON.parse(route.orderData as any);
prepay();
getMailData();
getDetail();
});
</script>
<style lang="scss" scoped>
.ui-leaseShopOrderConfirm {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-m-inf {
padding: px2rem(30);
.ui-receiving-item {
width: 50%;
height: px2rem(88);
margin-top: px2rem(-6);
.ui-receiving-text {
position: relative;
width: fit-content;
white-space: nowrap;
z-index: 99;
}
}
.ui-receiving-icon,
.ui-receiving-icon-v2 {
width: 100%;
height: px2rem(88);
position: absolute;
top: 0;
left: 0;
}
.ui-receiving-icon-v2 {
right: 0;
}
.ui-address-box {
padding: px2rem(24) px2rem(24) px2rem(12) px2rem(24);
margin-bottom: px2rem(24);
background: #ffffff;
border-radius: 0 0 px2rem(16) px2rem(16);
position: relative;
.ui-address-item {
display: flex;
justify-content: space-between;
.ui-address-title-box {
display: flex;
justify-content: left;
.ui-address-title {
width: px2rem(132);
text-align: justify;
text-align-last: justify;
white-space: normal;
}
.ui-address-content {
width: px2rem(440);
max-width: px2rem(440);
margin-left: px2rem(20);
}
}
.ui-address-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
margin-top: px2rem(4);
}
}
.ui-decoration-icon {
width: px2rem(664);
height: px2rem(4);
position: absolute;
bottom: px2rem(2);
left: px2rem(14);
}
}
.ui-inf-box,
.ui-remark-box {
padding: px2rem(24);
margin-bottom: px2rem(20);
background: #ffffff;
border-radius: px2rem(16);
box-sizing: border-box;
.ui-m-avt-lst {
display: flex;
align-items: stretch;
.ui-pic {
width: px2rem(160);
height: px2rem(160);
border: px2rem(2) solid #f8f8f8;
display: block;
object-position: center;
object-fit: cover;
border-radius: px2rem(8);
margin-right: px2rem(16);
}
.ui-m-lst-ri {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ui-title {
max-width: px2rem(462);
line-height: px2rem(48);
}
.ui-product-spu-name {
position: relative;
bottom: 0;
}
.ui-product-num {
position: absolute;
bottom: 0;
right: 0;
}
.ui-total-price {
position: absolute;
right: 0;
top: px2rem(6);
.ui-total-original-price {
text-decoration-line: line-through;
}
}
}
}
.ui-discounts-box {
padding-top: px2rem(20);
border-top: px2rem(2) solid #eaeaea;
margin-top: px2rem(24);
}
.ui-buy-num-box {
padding: 0 px2rem(22);
height: px2rem(56);
background: #f7f7f7;
border-radius: px2rem(8);
}
}
.ui-remark-box {
.ui-remark-text {
max-width: px2rem(400);
}
.ui-remark-triangle-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
}
}
}
.ui-buy-price {
color: #cc352f;
}
.ui-buy-price-symbol {
margin-right: px2rem(4);
margin-top: px2rem(2);
}
.ui-operation-box {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #ffffff;
padding: px2rem(16) px2rem(24) px2rem(16) px2rem(30);
box-sizing: border-box;
.ui-buy-btn {
width: px2rem(400);
height: px2rem(88);
border-radius: px2rem(24);
background: #2a63b2;
}
}
.ui-remark-input-box {
position: relative;
background: #ffffff;
padding: px2rem(30) px2rem(30) px2rem(60) px2rem(30);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-remark-input {
background: #f4f4f4;
border-radius: px2rem(8);
margin: px2rem(40) 0 px2rem(24) 0;
}
.ui-remark-btn {
width: 100%;
height: px2rem(88);
border-radius: px2rem(8);
background: #2a63b2;
}
}
</style>

View File

@ -0,0 +1,501 @@
<template>
<div class="ui-leaseShopOrderDetail">
<div class="ui-m-inf">
<div v-if="ship" class="ui-address-box">
<div class="ui-address-item">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">{{ ship == 1 ? '自提地址:' : '邮寄地址:' }}</div>
<div class="font_28 color3 ui-address-content">{{ detailedAddress }}</div>
</div>
</div>
<div class="ui-address-item ui-pt-32 ui-pb-32">
<div class="ui-address-title-box">
<div class="ui-address-title font_28 color3 bold">{{ ship == 1 ? '自提人:' : '收货人:' }}</div>
<div class="font_28 color3 ui-address-content">{{ address.userName }}{{ address.telNumber }}</div>
</div>
</div>
<img class="ui-decoration-icon" src="https://image.fulllinkai.com/202309/20/e8a30e7308236eb3894fec737b66657d.png" alt="" />
</div>
<div class="ui-inf-box">
<div class="ui-m-avt-lst ui-relative">
<img class="ui-pic" :src="orderData.icon" alt="" />
<div class="ui-m-lst-ri">
<div class="font_32 ellipsis_2 bold ui-title">{{ orderData.title }}</div>
<div class="font_28 ui-product-num color6">X {{ orderData.num }}</div>
</div>
</div>
<div class="f-fbc ui-mt-40">
<div class="font_28 color3">商品总额</div>
<div class="font_28 bold"><span class="ui-buy-price-symbol">¥</span>{{ orderData.price_final }}</div>
</div>
<div class="f-fbc ui-mt-20">
<div class="font_28 color3">快递费用</div>
<div class="font_28"><span class="ui-buy-price-symbol">¥</span>{{ orderData.freight }}</div>
</div>
<!-- <div class="f-fbc ui-mt-20">-->
<!-- <div class="font_28 color3">服务</div>-->
<!-- <div class="font_28">七天无理由退货</div>-->
<!-- </div>-->
<div class="ui-discounts-box f-fbc">
<div></div>
<div class="f-fcr">
<div class="font_28 color3">应付金额</div>
<div class="font_32 bold ui-buy-price"><span class="ui-buy-price-symbol">¥</span>{{ orderData.total }}</div>
</div>
</div>
</div>
<div class="ui-inf-box">
<div class="font_28 color3 bold ui-pb-12">订单信息</div>
<div class="f-fcl font_28 ui-pt-16">
<div class="color6 ui-pr-24 ui-inf-title">订单编号</div>
<div class="ui-tradeNo ui-pr-24 ui-relative color3 ellipsis_1 text-right">{{ orderData.trade_no }}</div>
<div class="colorBlue ui-pl-24 ui-inf-title" @click="onCopy(orderData.trade_no)">复制</div>
</div>
<div class="f-fcl font_28 ui-pt-16">
<div class="color6 ui-pr-24 ui-inf-title">下单时间</div>
<div class="color3">{{ orderData.created_at }}</div>
</div>
<div class="f-fcl font_28 ui-pt-16">
<div class="color6 ui-pr-24 ui-inf-title">支付方式</div>
<div class="color3">线上支付</div>
</div>
<div v-if="orderData.remark" class="f-fl font_28 ui-pt-16">
<div class="color6 ui-pr-24 ui-inf-title">订单备注</div>
<div ref="remarkText" class="ui-remark-text color3">{{ orderData.remark }}</div>
</div>
</div>
</div>
<div class="ui-operation-box f-fbc">
<div></div>
<div class="f-fcr">
<div v-if="orderData.invoice_pics" class="ui-state-btn f-fcc color3 font_28" @click="imagePreview()">查看发票</div>
<div class="ui-state-btn f-fcc color3 font_28" @click="showStatus = true">订单状态</div>
<div class="ui-state-btn f-fcc color3 font_28 ui-ml-20" @click="jumpPath()">再次购买</div>
<div v-if="expressStatus != 5 && expressStatus != 6" class="ui-again-buy-btn f-fcc colorF font_28 ui-ml-20" @click="signFor">确认收货</div>
</div>
</div>
<van-popup v-model:show="showStatus" round position="bottom" :duration="0.5">
<div class="ui-status-box">
<img class="ui-close-icon" src="https://image.fulllinkai.com/202405/16/20161b3e74af1eacd328181ff40b0358.png" alt="" @click="showStatus = false" />
<div class="font_32 color3 text-center bold ui-pt-32 ui-status-title">订单状态</div>
<div class="ui-status-data-box">
<div v-for="(item, index) in statusList" :key="index" class="f-fbc ui-status-data-item ui-relative">
<div class="f-fcl">
<div class="ui-status-big-circle f-fcc" :class="index == 0 ? 'ui-status-big-circle-bg' : ''">
<div class="ui-status-circle" :class="index == 0 ? 'ui-status-circle-bg' : ''"></div>
</div>
<div v-if="item.status == 1" class="font_28 color6 ui-pl-24">未支付</div>
<div v-else-if="item.status == 2" class="font_28 color6 ui-pl-24">已支付</div>
<div v-else-if="item.status == 3" class="font_28 color6 ui-pl-24">退款中</div>
<div v-else-if="item.status == 4" class="font_28 color6 ui-pl-24">已退款</div>
<div v-else-if="item.status == 5" class="font_28 color6 ui-pl-24">退款被拒绝</div>
<div v-else-if="item.status == 11" class="font_28 color6 ui-pl-24">待审核</div>
<div v-else-if="item.status == 12" class="font_28 color6 ui-pl-24">审核不通过</div>
<div v-else-if="item.status == 13" class="font_28 color6 ui-pl-24">待发货</div>
<div v-else-if="item.status == 14" class="font_28 color6 ui-pl-24">已发货</div>
<div v-else-if="item.status == 15" class="font_28 color6 ui-pl-24">已签收</div>
<div v-else-if="item.status == 16" class="font_28 color6 ui-pl-24">已退款</div>
</div>
<div class="font_28 color6">{{ item.created_at }}</div>
<div v-if="index + 1 != statusList.length" class="ui-status-line"></div>
</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showConfirmDialog, showImagePreview, showLoadingToast, showToast } from 'vant';
import requestGo from '@/utils/requestGo';
import router from '@/router';
import { copy } from '@/plugins/public';
defineOptions({ name: 'LeaseShopOrderDetail' });
const orderData = ref<any>({});
const id = ref<any>('');
const showStatus = ref(false);
const ship = ref<any>('');
const expressId = ref<any>('');
const expressStatus = ref<any>('');
const remarkText = ref<any>(null);
// const showExpand = ref(false);
const statusList = ref<any[]>([]);
const detailedAddress = ref<any>('');
const address = ref<any>({
userName: '',
postalCode: '',
provinceName: '',
cityName: '',
countyName: '',
detailInfo: '',
nationalCode: '',
telNumber: '',
});
//
const getOrderDetail = () => {
requestGo({ url: `/h5/v2/shop/rental/order/detail/${id.value}`, method: 'get' })
.then((res) => {
const result = res.data;
orderData.value = result;
orderData.value.freight = (result.freight / 100).toFixed(2);
orderData.value.price_final = (result.price_final / 100).toFixed(2);
orderData.value.total = orderData.value.freight * 1 + orderData.value.price_final * 1;
})
.catch((err) => {
console.log(err);
});
};
//
const getPickUp = () => {
requestGo({ url: `h5/v2/shop/rental/order/express/logs/${id.value}`, method: 'get' })
.then((res) => {
const result = res.data;
statusList.value = result;
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
//
const getLogistics = () => {
requestGo({ url: `/h5/v2/shop/rental/order/express/list/${id.value}`, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '8888');
if (result && result.length > 0) {
detailedAddress.value = result[0].address;
address.value.userName = result[0].name;
address.value.telNumber = result[0].mobile;
ship.value = result[0].ship;
expressId.value = result[0].id;
expressStatus.value = result[0].status;
}
})
.catch((err) => {
console.log(err);
});
};
//
const signFor = () => {
showConfirmDialog({
title: '温馨提示',
message: `是否确认签收?`,
})
.then(() => {
showLoadingToast('');
requestGo({ url: `h5/v2/shop/rental/express/sign/${expressId.value}`, method: 'post' })
.then(() => {
showToast('签收成功');
expressStatus.value = 5;
})
.catch((err) => {
console.log(err);
});
})
.catch(() => {
// on cancel
});
};
const jumpPath = () => {
if (orderData.value.spu_id == 0) {
showToast('该商品已经下架');
return;
}
router.push({
name: 'leaseShopDetail',
params: { id: orderData.value.spu_id },
});
};
//
const imagePreview = () => {
let images = JSON.parse(orderData.value.invoice_pics);
showImagePreview({
images,
showIndex: false,
loop: false,
});
};
const onCopy = (e) => {
copy(e, 0);
};
onMounted(() => {
let route = router.currentRoute.value.params;
id.value = route.id;
getOrderDetail();
getPickUp();
getLogistics();
});
</script>
<style lang="scss" scoped>
.ui-leaseShopOrderDetail {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-m-inf {
padding: px2rem(30) px2rem(30) px2rem(200) px2rem(30);
.ui-address-box {
padding: px2rem(24) px2rem(24) px2rem(4) px2rem(24);
margin-bottom: px2rem(24);
background: #ffffff;
border-radius: 0 0 px2rem(16) px2rem(16);
position: relative;
.ui-address-item {
display: flex;
justify-content: space-between;
.ui-address-title-box {
display: flex;
justify-content: left;
.ui-address-title {
width: px2rem(132);
text-align: justify;
text-align-last: justify;
}
.ui-address-content {
width: px2rem(498);
max-width: px2rem(498);
margin-left: px2rem(20);
}
}
}
.ui-decoration-icon {
width: px2rem(664);
height: px2rem(4);
position: absolute;
bottom: px2rem(2);
left: px2rem(14);
}
}
.ui-inf-box {
padding: px2rem(24);
margin-bottom: px2rem(20);
background: #ffffff;
border-radius: px2rem(16);
box-sizing: border-box;
.ui-inf-title {
white-space: nowrap;
}
.ui-sku-list {
padding: px2rem(24);
background: #f4f4f4;
border-radius: px2rem(8);
margin-bottom: px2rem(12);
}
.ui-m-avt-lst {
display: flex;
align-items: stretch;
.ui-pic {
width: px2rem(160);
height: px2rem(160);
border: px2rem(2) solid #f8f8f8;
display: block;
object-position: center;
object-fit: cover;
border-radius: px2rem(8);
margin-right: px2rem(16);
}
.ui-m-lst-ri {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ui-title {
max-width: px2rem(320);
line-height: px2rem(38);
}
.ui-product-num {
position: relative;
bottom: px2rem(-2);
}
.ui-total-price {
position: absolute;
right: 0;
top: px2rem(6);
.ui-total-original-price {
text-decoration-line: line-through;
}
}
}
}
.ui-discounts-box {
padding-top: px2rem(20);
border-top: px2rem(2) solid #eaeaea;
margin-top: px2rem(24);
}
.ui-show-more-box {
width: 100%;
background: #ffffff;
z-index: 22;
position: absolute;
bottom: 0;
left: 0;
.ui-take-btn {
width: px2rem(152);
height: px2rem(56);
background: #2a63b2;
border-radius: px2rem(8);
}
}
.ui-triangle-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
margin-left: px2rem(4);
}
.ui-tradeNo {
width: px2rem(380);
}
.ui-tradeNo:before {
content: '';
width: px2rem(4);
height: px2rem(20);
background: #d8d8d8;
position: absolute;
right: 0;
top: px2rem(6);
}
.ui-address {
width: px2rem(440);
text-align-last: right;
}
.ui-remark-text {
width: px2rem(440);
word-break: break-all;
}
}
.ui-buy-price {
color: #cc352f;
}
.ui-buy-price-symbol {
margin-right: px2rem(4);
margin-top: px2rem(2);
}
}
.ui-operation-box {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #ffffff;
padding: px2rem(24) px2rem(15) px2rem(50) px2rem(15);
box-sizing: border-box;
z-index: 999;
.ui-again-buy-btn {
width: px2rem(168);
height: px2rem(64);
border-radius: px2rem(8);
background: #2a63b2;
}
.ui-state-btn {
width: px2rem(164);
height: px2rem(60);
border-radius: px2rem(8);
border: px2rem(2) solid #d8d8d8;
}
}
.ui-status-box {
position: relative;
background: #ffffff;
padding-bottom: px2rem(80);
border-radius: px2rem(24);
.ui-close-icon {
position: absolute;
right: px2rem(24);
top: px2rem(24);
z-index: 22;
width: px2rem(48);
height: px2rem(48);
}
.ui-status-title {
padding-bottom: px2rem(48);
}
.ui-status-data-box {
padding: 0 px2rem(30) px2rem(30) px2rem(30);
min-height: px2rem(160);
max-height: px2rem(480);
overflow-y: scroll;
.ui-status-data-item {
padding-bottom: px2rem(50);
.ui-status-big-circle {
width: px2rem(36);
height: px2rem(36);
border-radius: 50%;
.ui-status-circle {
width: px2rem(12);
height: px2rem(12);
border-radius: 50%;
background: #d8d8d8;
}
.ui-status-circle-bg {
background: #2a63b2;
}
}
.ui-status-big-circle-bg {
background: rgba(42, 99, 178, 0.2);
}
.ui-status-line {
width: px2rem(4);
height: px2rem(34);
position: absolute;
bottom: px2rem(2);
left: px2rem(16);
background: #d8d8d8;
}
}
}
}
</style>

View File

@ -0,0 +1,289 @@
<template>
<div ref="scrollDistance" class="ui-leaseShopOrderList" @scroll="handleScroll">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" @load="getList">
<div v-if="loadingState && list.length == 0">
<img class="ui-no-data-icon" src="https://image.fulllinkai.com/202406/03/3e513a3dac7b6d19d5c0bfc73a7d2c4c.png" alt="" />
<div class="color6 font_30 text-center">暂无数据</div>
</div>
<div v-else class="ui-m-inf">
<div v-for="(item, index) in list" :key="index" class="ui-inf-box" @click="jumpPath('leaseShopOrderDetail', item.id)">
<div class="f-fbc">
<div class="font_28 color3 bold">{{ item.created_at }}</div>
<div v-if="item.status == 6" class="font_28 color6">已完成</div>
<div v-else-if="item.express_status == 4" class="font_28 color6">已发货</div>
<div v-else-if="item.express_status == 5" class="font_28 color6">已签收</div>
<div v-else-if="item.express_status == 6" class="font_28 colorPrice">已退款</div>
<div v-else class="font_28 color6">待发货</div>
</div>
<div class="ui-separate-line"></div>
<div class="ui-m-avt-lst ui-relative">
<img class="ui-pic" :src="item.icon" alt="" />
<div class="ui-m-lst-ri font_28 color3">
<div class="ellipsis_2 bold ui-title">{{ item.title }}</div>
<div class="ui-product-num">X {{ item.num }}</div>
<div class="bold text-right ui-total-price">¥{{ item.price_final / 100 }}</div>
</div>
</div>
<div class="f-fbc font_28 ui-pt-24">
<div></div>
<div class="f-fcr">
<div v-if="item.status != 6" class="ui-state-btn f-fcc color3" @click.stop="jumpPath('', '')">申请退货</div>
<!-- <div v-if="item.status == 6" class="ui-state-btn f-fcc color3" @click.stop="jumpReturn('returnGoods', item.id)">申请退货</div>-->
<!-- <div v-if="item.status == 3" class="ui-state-btn-v2 f-fcc colorBlue" @click.stop="jumpReturn('returnGoodsDetail', item.id)">退货详情</div>-->
<div class="ui-state-btn f-fcc color3 ui-ml-20" @click.stop="jumpPath('leaseShopDetail', item.spu_id)">再次购买</div>
<div v-if="item.express_status == 4" class="ui-again-buy-btn f-fcc colorF ui-ml-20" @click.stop="signFor(item, index)">确认收货</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onActivated, onMounted, ref } from 'vue';
import { showConfirmDialog, showLoadingToast, showToast } from 'vant';
import requestGo from '@/utils/requestGo';
import { contactService } from '@/plugins/public';
import router from '@/router';
defineOptions({ name: 'LeaseShopOrderList' });
const loadingState = ref<any>(false); //
const list = ref<any[]>([]); //
const noMore = ref(false); //
const refreshing = ref(false); // false
const finished = ref(false); // true
const loading = ref(false); // false
const page = ref(1); //
const scrollValue = ref(0); //
const scrollDistance = ref<any>(null);
//
const getList = () => {
requestGo({ url: `/h5/v2/shop/rental/order/list?page=${page.value}`, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '8888');
loadingState.value = true;
if (list.value.length === 0 || page.value === 1) {
list.value = result.data;
} else if (list.value.length >= 15) {
result.data.forEach((item) => {
list.value.push(item);
});
}
refreshing.value = false;
loading.value = false;
if (list.value.length < 15 || result.data.length < 15) {
finished.value = true;
noMore.value = true;
}
page.value++;
})
.catch((err) => {
loadingState.value = true;
console.log(err);
});
};
//
const signFor = (e, index) => {
showConfirmDialog({
title: '温馨提示',
message: `是否确认签收?`,
})
.then(() => {
showLoadingToast('');
requestGo({ url: `h5/v2/shop/rental/express/sign/${e.express_id}`, method: 'post' })
.then(() => {
showToast('签收成功');
list.value[index].express_status = 5;
})
.catch((err) => {
console.log(err);
});
})
.catch(() => {
// on cancel
});
};
//
const onRefresh = () => {
loadingState.value = false;
list.value = [];
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
// const jumpReturn = (url, id) => {
// if (id == 0) {
// showToast('');
// return;
// }
// router.push({
// name: url,
// params: { id },
// query: { type: '0', path: 'leaseShopOrderList' },
// });
// };
const jumpPath = (url, id) => {
if (!url) {
showConfirmDialog({
title: '退款申请',
message: `申请退款需联系客服操作`,
})
.then(() => {
contactService();
})
.catch(() => {
// on cancel
});
return;
}
if (id == 0) {
showToast('该商品已经下架');
return;
}
router.push({
name: url,
params: { id },
});
};
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
//
scrollDistance.value.scrollTop = scrollValue.value;
// 退退退
if (list.value && list.value.length > 0) {
if (localStorage.getItem('refundGoods')) {
let refundGoods = localStorage.getItem('refundGoods') as any;
let state = refundGoods.split(',')[0];
let orderId = refundGoods.split(',')[1];
list.value.forEach((item) => {
if (item.id == orderId) {
if (state == 0) {
item.status = 6;
} else {
item.status = 3;
}
}
});
}
} else {
localStorage.removeItem('refundGoods');
}
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-leaseShopOrderList {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-no-data-icon {
width: px2rem(300);
height: px2rem(164);
display: block;
margin: 25vh auto px2rem(20) auto;
}
.ui-m-inf {
padding: px2rem(30) px2rem(30) px2rem(200) px2rem(30);
.ui-inf-box {
padding: px2rem(24);
margin-bottom: px2rem(20);
background: #ffffff;
border-radius: px2rem(16);
box-sizing: border-box;
.ui-m-avt-lst {
display: flex;
align-items: stretch;
.ui-pic {
width: px2rem(120);
height: px2rem(120);
border: px2rem(2) solid #f8f8f8;
display: block;
object-position: center;
object-fit: cover;
border-radius: px2rem(8);
margin-right: px2rem(24);
}
.ui-m-lst-ri {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ui-title {
max-width: px2rem(320);
line-height: px2rem(38);
}
.ui-product-num {
position: absolute;
right: 0;
bottom: px2rem(-2);
}
.ui-total-price {
position: absolute;
right: 0;
top: px2rem(6);
}
}
}
.ui-separate-line {
width: 100%;
padding: 0 px2rem(24);
margin: px2rem(26) 0 px2rem(32) px2rem(-24);
height: px2rem(2);
background: #eaeaea;
}
}
.ui-again-buy-btn {
width: px2rem(148);
height: px2rem(52);
border-radius: px2rem(8);
border: px2rem(2) solid #2a63b2;
background: #2a63b2;
}
.ui-state-btn,
.ui-state-btn-v2 {
width: px2rem(148);
height: px2rem(52);
border-radius: px2rem(8);
border: px2rem(2) solid #d8d8d8;
}
.ui-state-btn-v2 {
border: px2rem(2) solid #2a63b2;
}
.ui-no-more {
margin-top: px2rem(40);
}
}
</style>

View File

@ -0,0 +1,698 @@
<template>
<div ref="agentHome" class="ui-agentHome">
<div ref="topBox" class="ui-top-box">
<div class="ui-relative ui-pt-16 ui-pb-20">
<img class="ui-search-icon" src="https://image.fulllinkai.com/202405/13/629916bd281b7afa1a61ed099db182cf.png" alt="" />
<div class="ui-pr-28 ui-pl-28" @click="jumpPath('shopSearch', {})">
<van-field readonly class="ui-nutrient-input font_28 color3" placeholder="搜索商品" />
</div>
<div class="ui-search-right font_26">搜索</div>
</div>
<div class="ui-pl-28 ui-pr-28 ui-advertising">
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item v-for="(item, index) in bannerList" :key="index" class="ui-relative" @click.stop="jumpPath('banner', { id: item.shop_id })">
<img class="ui-pics-item" :src="item.icon" alt="" />
<div class="ui-mask"></div>
</van-swipe-item>
</van-swipe>
</div>
<div class="ui-type-pos ui-pt-24">
<div class="ui-type-select ui-pb-12">
<div v-for="(item, index) in typeList" :key="index" class="ui-pr-40 text-center" @click="changeType(item.id, index)">
<!-- <img class="ui-type-pic" :src="item.icon" alt="" :class="typeIndex == index ? 'ui-type-pic-select' : ''" />-->
<div class="ui-type-name font_28" :class="typeIndex == index ? 'ui-type-name-select font_32' : ''">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div class="ui-placeholder"></div>
<div class="ui-tree-select" :style="{ height: treeSelectHeight }">
<div style="padding: 1px"></div>
<!-- <div class="ui-sidebar">-->
<!-- <div v-for="(item, index) in typeList" :key="index" class="font_26 color3 ui-sidebar-item ui-relative" :class="typeIndex == index ? 'ui-sidebar-item-select bold' : ''" @click="changeType(item.id, index)">-->
<!-- {{ item.name }}-->
<!-- <div v-if="typeIndex + 1 == index" class="ui-sidebar-item-select-next"></div>-->
<!-- <div v-if="typeIndex + 1 == typeList.length" class="ui-sidebar-item-select-not"></div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="ui-type-pos">-->
<!-- <div class="ui-type-select ui-pb-12">-->
<!-- <div v-for="(item, index) in typeList" :key="index" class="ui-pr-32" @click="changeType(item.id, index)">-->
<!-- &lt;!&ndash; <img class="ui-type-pic" :src="item.icon" alt="" :class="typeIndex == index ? 'ui-type-pic-select' : ''" />&ndash;&gt;-->
<!-- <div class="ui-type-name font_26 color3" :class="typeIndex == index ? 'ui-type-name-select' : ''">{{ item.name }}</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div ref="productList" class="ui-tab-content" @scroll="handleScroll">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list v-model:loading="loading" :finished="finished" @load="getList">
<div v-if="loadingState && list.length == 0">
<img class="ui-no-data-icon" src="https://image.fulllinkai.com/202406/03/3e513a3dac7b6d19d5c0bfc73a7d2c4c.png" alt="" />
<div class="color6 font_30 text-center">暂无数据</div>
</div>
<div v-else class="ui-product-box">
<div class="ui-product-list">
<div v-for="(item, index) in list" :key="index" @click="jumpPath('shopDetail', { id: item.id })">
<!-- <div v-if="item.typeTitle" ref="typeTitle" class="font_28 bold color3 ui-pl-28 ui-pb-20 ui-pt-16">{{ item.typeTitle }}</div>-->
<div v-if="item.typeTitle" ref="typeTitle" :key="item.typeTitle"></div>
<div class="ui-product-item">
<div style="display: flex">
<img class="ui-product-pic" :src="item.pic || item.icon" alt="" />
<div class="ui-title-box">
<div class="font_30 ellipsis_2 ui-product-title">{{ item.title }}</div>
<div class="f-fbc">
<div class="ui-buy-data">
<div v-if="item.price == '0.00' || item.price == '0'" class="font_34 bold ui-price">免费</div>
<div v-else class="font_36 bold ui-price-v2"><span class="font_24 ui-price-symbol">¥</span>{{ item.price }}</div>
</div>
<div class="ui-buy-btn font_24 f-fcc colorF">立即购买</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onActivated, onDeactivated } from 'vue';
import router from '@/router';
import requestGo from '@/utils/requestGo';
import { weXinShare } from '@/plugins/wxShare';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AgentHome' });
const agentHome = ref<any>(null);
const userStore = useUserStore() as any;
const topBox = ref<any>(null);
const typeList = ref<any[]>([]);
const typeIndex = ref(0);
const treeSelectHeight = ref<any>('100vh');
const bannerList = ref<any[]>([]);
const loadingState = ref<any>(false); //
const list = ref<any[]>([]); //
const refreshing = ref(false); // false
const finished = ref(true); // true
const loading = ref(false); // false
const page = ref(1); //
const scrollValue = ref(0); //
const productList = ref<any>(null);
const typeTitle = ref<any>(null);
const scrollLoading = ref(false);
const anchors = ref<any[]>([]); //
//
const getList = () => {
requestGo({ url: `/h5/v2/shop/common/spu/list?page=${page.value}`, method: 'get' })
.then((res) => {
const result = res.data;
if (result.total_pages < page.value) {
return;
}
if (result.data && result.data.length > 0) {
result.data.forEach((item) => {
item.types = item.category_ids.split(',')[0];
});
}
if (list.value.length === 0 || page.value === 1) {
list.value = result.data;
list.value.map((item) => {
if (item.id == 110) {
getSPSkuPrice(item.skuid);
}
});
} else if (list.value.length >= 15) {
result.data.forEach((item) => {
list.value.push(item);
});
result.value.map((item) => {
if (item.id == 110) {
getSPSkuPrice(item.skuid);
}
});
}
refreshing.value = false;
loading.value = false;
if (list.value.length < 15 || result.data.length < 15) {
finished.value = true;
}
setTimeout(() => {
//
typeList.value.forEach((i) => {
list.value.forEach((item) => {
if (item.types == i.id) {
item.typeName = i.name;
}
});
});
//
list.value.forEach((item, index) => {
if (index == 0) {
item.typeTitle = item.typeName;
}
if (index + 1 != list.value.length && item.typeName != list.value[index + 1].typeName) {
list.value[index + 1].typeTitle = list.value[index + 1].typeName;
}
});
page.value++;
if (!loadingState.value) {
weXinShare('https://image.fulllinkai.com/202310/28/88e931a50ec0a8094fb46191b389457e.png', `https://health.ufutx.com/store/#/agentHome?from_user_id=${userStore.userID}&from_type=rt_home`, '友福商城', '邀请您进入友福商城');
}
loadingState.value = true;
anchors.value = [];
setTimeout(() => {
for (let i = 0; i < typeList.value.length; i++) {
let status = false;
console.log(typeTitle.value, 'typeTitle.value=');
typeTitle.value.map((item, index) => {
console.log(item.__vnode.key, 'item====333');
if (!status && typeList.value[i].name == item.__vnode.key) {
status = true;
anchors.value.push(item.offsetTop);
} else if (!status && index == typeTitle.value.length - 1) {
anchors.value.push('');
}
});
// console.log(typeTitle.value[i].textContent, 'ff');
// if (typeTitle.value[i]) {
// anchors.value.push(typeTitle.value[i].offsetTop);
// } else {
// anchors.value.push(typeTitle.value[i - 1].offsetTop);
// }
}
console.log(anchors.value, '8888');
});
});
})
.catch((err) => {
loadingState.value = true;
console.log(err);
});
};
//
const getSPSkuPrice = (skuid) => {
requestGo({ url: `/h5/v2/shop/common/sku/prepay/${skuid}/1/1`, method: 'get' })
.then((res) => {
const result = res.data;
console.log(result, '77777');
list.value = list.value.map((item) => {
console.log(item.id, 'item.id');
if (item.id == 110) {
item.price = result.price;
}
return item;
});
console.log(list, 'list==');
})
.catch((err) => {
console.log(err);
});
};
//
const getType = () => {
requestGo({ url: `/h5/v2/shop/common/category/list`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
typeList.value = result;
getList();
})
.catch((err) => {
console.log(err);
});
};
// 广
const getBanner = () => {
requestGo({ url: `/app/user/carousel/list?place=SHOP_HOME`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
bannerList.value = result.filter((item) => {
return !item.program;
});
})
.catch((err) => {
console.log(err);
});
};
const jumpPath = (url: string, e: any) => {
if (url == 'banner') {
console.log('e', e);
if (e) {
router.push({
name: 'shopDetail',
params: e,
});
} else {
location.href = e.url;
}
} else {
router.push({
name: url,
params: e,
});
}
};
//
const changeType = (id, idx) => {
console.log('444===');
if (scrollLoading.value) {
return;
}
console.log(id, idx, '666');
typeIndex.value = idx;
let state = false;
if (list.value && list.value.length > 0) {
list.value.forEach((item) => {
console.log(item.types, id, '3333===');
if (!state && item.types == id) {
console.log('3333===');
typeTitle.value.map((itemV2) => {
console.log(item.typeName, itemV2, '3333=gggg==');
console.log(item.typeName, itemV2.__vnode.key, '3333=gggg==');
if (!state && item.typeName == itemV2.__vnode.key) {
state = true;
console.log(itemV2.offsetTop, 'itemV2.offsetTop');
scrollValue.value = itemV2.offsetTop;
console.log(scrollValue.value, 'scrollValue');
productList.value.scrollTop = scrollValue.value - 16;
console.log(productList.value.scrollTop, 'productList.value.scrollTop=');
}
});
// state = true;
// scrollValue.value = typeTitle.value[idx].offsetTop;
// productList.value.scrollTop = scrollValue.value - 16;
}
});
console.log('ggg=');
}
};
//
const handleScroll = (event) => {
scrollLoading.value = true;
anchors.value.findIndex((e, index) => {
if (e && event.target.scrollTop + 80 >= e) {
typeIndex.value = index;
setTimeout(() => {
scrollLoading.value = false;
}, 100);
}
});
};
//
const onRefresh = () => {
page.value = 1;
list.value = [];
finished.value = false;
loading.value = true;
getList();
};
onActivated(() => {
//
productList.value.scrollTop = scrollValue.value;
});
onDeactivated(() => {});
onMounted(() => {
getType();
getBanner();
});
</script>
<style lang="scss">
.ui-agentHome {
.van-popup {
overflow-y: initial;
}
.van-swipe__indicators {
//left: px2rem(262);
bottom: px2rem(10);
}
.van-swipe__indicator--active {
background-color: #ffffff;
}
.van-swipe__indicator {
width: px2rem(20);
height: px2rem(8);
border-radius: px2rem(20);
}
.van-field {
height: px2rem(48);
line-height: px2rem(48);
background: #f8f8f8;
}
}
</style>
<style lang="scss" scoped>
.ui-agentHome {
background: #ffffff;
overflow: hidden;
max-height: 100vh;
position: relative;
}
.ui-top-box {
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 99;
background: #ffffff;
.ui-search-icon {
width: px2rem(36);
height: px2rem(36);
position: absolute;
left: px2rem(52);
top: px2rem(32);
z-index: 2;
}
.ui-search-right {
position: absolute;
right: px2rem(52);
top: px2rem(32);
padding-left: px2rem(20);
border-left: px2rem(1) solid #dddddd; /* 设置左边框的宽度 */
margin-left: auto; /* 左边距设置为自动 */
margin-right: auto; /* 右边距也设置为自动以使元素水平居中 */
}
.ui-nutrient-input {
display: flex;
align-items: center;
padding: 0 px2rem(72);
height: px2rem(68);
//border: px2rem(2) solid #2a63b2;
border-radius: px2rem(200);
}
.ui-search-cancel-icon {
width: px2rem(28);
height: px2rem(28);
position: absolute;
right: px2rem(60);
top: px2rem(50);
z-index: 98;
}
.ui-advertising {
width: 100%;
position: fixed;
top: px2rem(100);
padding-top: 0;
padding-bottom: px2rem(20);
z-index: 33;
overflow: hidden;
transform: translateY(0);
background: #ffffff;
//padding-bottom: px2rem(124);
}
.ui-pics-item {
width: px2rem(686);
height: px2rem(200);
border-radius: px2rem(32);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
}
.ui-type-pos {
position: fixed;
width: 100vw;
height: px2rem(60);
top: px2rem(320);
background: #f8f8f8;
border-radius: px2rem(24) px2rem(24) 0 0;
z-index: 44;
}
.ui-type-select {
display: flex;
justify-content: left;
align-items: center;
overflow: hidden;
overflow-x: auto;
white-space: nowrap;
margin: 0 px2rem(24);
.ui-type-pic,
.ui-type-pic-select {
width: px2rem(80);
height: px2rem(80);
border-radius: px2rem(16);
margin: 0 auto;
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
}
.ui-type-pic-select {
width: px2rem(76);
height: px2rem(76);
border: px2rem(4) solid #2a63b2;
}
.ui-type-name,
.ui-type-name-select {
padding: px2rem(4) px2rem(14);
//border-radius: px2rem(8);
//background: #ffffff;
//margin-top: px2rem(8);
color: #b2b3b5;
}
.ui-type-name-select {
color: #2a63b2;
font-weight: bold;
}
}
.ui-type-select::-webkit-scrollbar {
display: none;
}
}
.ui-placeholder {
width: 100%;
height: px2rem(260);
}
.ui-tree-select {
width: 100vw;
background: linear-gradient(180deg, #fafafa 0%, #ffffff 100%);
position: relative;
//overflow-y: scroll;
//display: flex;
//position: absolute;
//top: px2rem(160);
//height: px2rem(800);
//z-index: 100;
.ui-placeholder-v2 {
width: 100%;
height: px2rem(160);
}
.ui-type-select {
display: flex;
justify-content: left;
overflow: hidden;
overflow-x: auto;
white-space: nowrap;
margin: 0 px2rem(24);
.ui-type-pic,
.ui-type-pic-select {
width: px2rem(80);
height: px2rem(80);
border-radius: px2rem(16);
margin: 0 auto;
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
}
.ui-type-pic-select {
width: px2rem(76);
height: px2rem(76);
border: px2rem(4) solid #2a63b2;
}
.ui-type-name,
.ui-type-name-select {
padding: px2rem(4) px2rem(14);
//border-radius: px2rem(8);
//background: #ffffff;
margin-top: px2rem(8);
}
.ui-type-name-select {
color: #2a63b2;
font-weight: bold;
}
}
.ui-type-select::-webkit-scrollbar {
display: none;
}
//.ui-sidebar {
// width: px2rem(168);
// padding-bottom: px2rem(420);
// background: #f4f4f4;
// overflow-y: auto;
// -webkit-overflow-scrolling: touch;
// text-align: center;
//
// .ui-sidebar-item {
// background: #f4f4f4;
// padding: px2rem(28) 0;
// }
//
// .ui-sidebar-item-select {
// color: #2a63b2;
// background: #ffffff;
// }
//
// .ui-sidebar-item-select:before {
// width: px2rem(6);
// height: px2rem(48);
// position: absolute;
// top: 50%;
// left: 0;
// transform: translateY(-50%);
// background: #2a63b2;
// border-radius: px2rem(30);
// content: '';
// }
//
// .ui-sidebar-item-select-next,
// .ui-sidebar-item-select-not {
// width: px2rem(16);
// height: px2rem(16);
// position: absolute;
// right: 0;
// top: 0;
// background: radial-gradient(circle at left bottom, transparent 0, transparent px2rem(16), #ffffff px2rem(16));
// z-index: 2;
// }
//
// .ui-sidebar-item-select-not {
// right: 0;
// top: initial;
// bottom: px2rem(-16);
// }
//}
.ui-tab-content {
position: absolute;
top: px2rem(160);
left: 0;
width: 100vw;
height: 100%;
padding-bottom: px2rem(240);
overflow-y: scroll;
}
}
.ui-product-list {
padding: 0 px2rem(20) px2rem(630);
background: #f8f8f8;
//overflow-y: auto;
.ui-product-item {
display: flex;
justify-content: space-between;
align-items: flex-end;
height: px2rem(260);
margin-bottom: px2rem(20);
padding: px2rem(20);
border-radius: px2rem(32);
background: #ffffff;
.ui-product-pic {
width: px2rem(266);
height: px2rem(266);
border-radius: px2rem(32);
margin-right: px2rem(20);
display: block;
object-fit: cover;
object-position: center;
vertical-align: top;
}
.ui-title-box {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.ui-product-title {
width: px2rem(390);
max-width: px2rem(390);
color: #0e0e0e;
}
.ui-buy-data {
//position: absolute;
//bottom: px2rem(0);
.ui-price-symbol {
margin-right: px2rem(4);
}
.ui-price,
.ui-price-v2 {
position: relative;
bottom: px2rem(-2);
color: #cc352f;
}
.ui-price-v2 {
bottom: px2rem(-6);
}
}
.ui-buy-btn {
width: px2rem(96);
height: px2rem(34);
background: #2a63b2;
border-radius: px2rem(56);
padding: px2rem(12) px2rem(20);
//position: absolute;
//bottom: 0;
//right: 0;
}
}
}
.ui-no-data-icon {
width: px2rem(300);
height: px2rem(164);
display: block;
margin: 11vh auto px2rem(20) auto;
}
</style>

Some files were not shown because too many files have changed in this diff Show More