初始化

This commit is contained in:
lanzhihui 2026-04-08 09:51:49 +08:00
commit f32d7b0493
158 changed files with 59661 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 = /
# base api
VITE_BASE_API_VERSION = '1.0.002'
VITE_BASE_API_go = 'https://health.ufutx.com/go/api'
VITE_BASE_API = 'https://health.ufutx.com/api'
VITE_BASE_API_APP = '//app.health.ufutx.com/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 = /work/
# base api
VITE_BASE_API_VERSION = '1.0.002'
VITE_BASE_API_go = '//health.ufutx.com/go/api'
VITE_BASE_API = '//health.ufutx.com/api'
VITE_BASE_API_APP = '//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": true
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
},
"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` 属性来限制容器最大最小宽

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

@ -0,0 +1,4 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {}
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']],
},
};

51
index.html Normal file
View File

@ -0,0 +1,51 @@
<!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,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<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;
}
</style>
<body data-content-max>
<div id="app"></div>
<!-- 企业微信开发 引入 jwxwork sdk -->
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js" referrerpolicy="origin"></script>
<script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js" referrerpolicy="origin"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.2/echarts.min.js" referrerpolicy="origin"></script>
<script type="module" src="/src/main.ts"></script>
<script>
(function () {
// if (location.href.includes('localhost')) {
// return;
// }
// ('use strict');
// const SCRIPT_URL = 'https://dldir1.qq.com/WechatWebDev/devPlatform/px.min.js';
// const param = {
// maskMode: 'no-mask', // 隐私策略, all-mask 或 no-mask, 详见https://www.npmjs.com/package/@wxobs/miniprogram-sdk#%E6%96%87%E6%A1%A3
// recordCanvas: false, // 若要采集canvas, 设为true
// projectId: 'wxee293b036a1af0c2-DiDfLdsVO-JsRI7',
// attrs: {}, // 对回放添加自定义属性,在管理端可通过属性筛选回放 (对象中 key 和 value 类型必须为 string),
// iframe: false, // 是否采集 iframe 页面
// };
// const scriptEle = document.createElement('script');
// scriptEle.type = 'text/javascript';
// scriptEle.async = true;
// scriptEle.onload = function () {
// window.__startPX(param); // 启动采集,返回 Promise<{ sessionId: string }>
// };
// scriptEle.src = SCRIPT_URL;
// document.getElementsByTagName('script')[0].parentNode.appendChild(scriptEle);
})();
</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[];

11585
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

117
package.json Normal file
View File

@ -0,0 +1,117 @@
{
"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.3.1",
"@vant/touch-emulator": "^1.3.2",
"@vueuse/core": "^8.7.5",
"axios": "^0.27.2",
"dayjs": "^1.11.3",
"js-md5": "^0.7.3",
"lodash-es": "^4.17.21",
"pinia": "^2.0.13",
"qs": "^6.11.0",
"vant": "^4.0.0-alpha.4",
"vconsole": "^3.14.6",
"vue": "^3.2.37",
"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",
"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.2 KiB

50
src/App.vue Normal file
View File

@ -0,0 +1,50 @@
<template>
<div class="ui-app">
<router-view #="{ Component }">
<keep-alive :include="include">
<component :is="Component"></component>
</keep-alive>
</router-view>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
//
const include = ref<any[]>(['DiscussList', 'WorkOrder', 'Assess', 'Remarks', 'Surveys', 'GroupOrders', 'MeasurementRecord', 'DiscussList', 'WineRecords']);
onMounted(() => {
console.log('11');
});
</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 !important;
background: none !important;
}
</style>

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

@ -0,0 +1,54 @@
import weChat from '@/utils/weChat';
// 登录
export function getLogin() {
let data = { url: window.location.href.split('#')[0] };
return weChat({
hideLoading: true,
data,
url: '/h5/wechat/work/login',
method: 'post',
});
}
// 获取版本号
export function getVersion() {
return weChat({
hideLoading: true,
url: 'get/version?type=2',
method: 'get',
});
}
// 微信、企业微信config配置
export function getWxConfig() {
let data = { url: window.location.href.split('#')[0] };
return weChat({
hideLoading: true,
data,
url: '/h5/work/js/sdk/config/v2',
method: 'post',
});
}
// 阿里云上传配置
export function getALiYun(token) {
let data = token;
return weChat({
data,
hideLoading: true,
url: 'get/oss/config',
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/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,326 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
const props = defineProps({
detail: {
type: Object,
default: () => {
return {
id: 0,
name: '',
mode: '',
date: '',
avatar: '',
};
},
},
});
const emit = defineEmits(['update:date', 'update:mode']);
const activeModal = ref(-1);
const initialDate = ref('--');
const date = ref('--');
const showDate = ref(false);
const dateValues = ref<any[]>([`${new Date().getFullYear()}`, `${new Date().getMonth() + 1 < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth() + 1}`, `${new Date().getDate()}`]);
const minDate = new Date(new Date().getFullYear() - 120, 0, 1);
const maxDate = (() => {
const d = new Date();
d.setDate(1);
d.setMonth(d.getMonth() + 1);
d.setDate(Math.min(new Date().getDate(), new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate()));
d.setHours(0, 0, 0, 0);
return d;
})();
//
const getModalText = (mode: number) => {
if (mode === 1) return '减肥型';
if (mode === 2) return '其他型';
return '--';
};
const modalText = ref('未选择');
let hasUserChangedMode = false;
let hasUserChangedDate = false;
const showChange = ref(false);
const showModalText = ref('');
//
const changeModal = (type) => {
activeModal.value = type;
modalText.value = getModalText(type);
hasUserChangedMode = true;
maybeEmitMode();
showModal.value = false;
};
let initialModeValue = '';
let initialDateValue = '';
//
watch(
() => props.detail,
(newDetail) => {
if (!newDetail) return;
// log.id log.model model
let realModel = 0;
if (newDetail.log && newDetail.log.id > 0) {
realModel = newDetail.log.model ?? 0;
} else {
realModel = newDetail.model ?? 0;
}
activeModal.value = realModel;
modalText.value = getModalText(realModel);
initialModeValue = modalText.value;
//
const startDate = newDetail.log?.start_date_str || '--';
date.value = startDate;
initialDateValue = startDate;
if (startDate !== '--') {
const [year, month, day] = startDate.split('-').map(Number);
dateValues.value = [String(year), String(month).padStart(2, '0'), String(day).padStart(2, '0')];
}
},
{ immediate: true, deep: true },
);
// emit
const maybeEmitMode = () => {
if (modalText.value !== initialModeValue) {
emit('update:mode', modalText.value, date.value);
}
};
const maybeEmitDate = () => {
if (date.value !== initialDateValue) {
emit('update:date', date.value, modalText.value);
}
};
const toShowDate = () => {};
//
const onConfirm = ({ selectedValues }) => {
date.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
showDate.value = false;
hasUserChangedDate = true;
maybeEmitDate();
};
const showModal = ref(false);
//
const executedDays = computed(() => {
const startDateStr = props.detail?.log?.start_date_str;
if (!startDateStr || startDateStr === '--') return 0;
const startDate = new Date(startDateStr);
const today = new Date();
startDate.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
const diffTime = today.getTime() - startDate.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
return Math.max(0, diffDays);
});
</script>
<template>
<div class="menu-setting-top" @click="showModal = false">
<div class="menu-setting-card">
<img class="menu-setting-card-bg" src="https://images.health.ufutx.com/202511/18/fb38114f13f3065021c3bd6843d06989.png" alt="" />
<div class="menu-setting-message-box">
<img :src="detail.avatar || 'https://image.fulllinkai.com/202203/09/cc1c73eb1a4941fef25a15cd1ff2f9df.png'" alt="" class="menu-setting-message-avatar" />
<div class="menu-setting-message-name text-center">{{ detail.name || '未知用户' }}</div>
<div class="menu-setting-message-sub">ID· {{ 52330000 + detail.id }}</div>
</div>
<div class="menu-setting-message-list">
<div class="menu-setting-message-item" @click.stop="showModal = !showModal">
<div>当前模式</div>
<div class="menu-setting-message-item-right">
<div>{{ modalText }}</div>
<svg class="menu-setting-message-item-r-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none">
<path d="M12 2L7 7L2 2" stroke="#66676C" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
<div v-if="showModal" class="menu-setting-message-modal" @click.stop>
<div class="menu-setting-message-m-item" :class="{ 'menu-setting-message-m-i-active': activeModal === 1 }" @click.stop="changeModal(1)">
<div class="menu-setting-message-m-i-title">
<div>减肥型</div>
<svg v-if="activeModal === 1" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22" fill="none">
<path d="M5.2998 12L9.16254 15.8627L16.5498 8.47547" stroke="#18CA6E" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<div class="menu-setting-message-m-i-sub">适用减脂需求强化热量缺口与代谢调节</div>
<div v-if="activeModal === 1" class="menu-setting-message-m-i-default">当前选中</div>
</div>
<div class="menu-setting-message-m-item" :class="{ 'menu-setting-message-m-i-active': activeModal === 2 }" @click.stop="changeModal(2)">
<div class="menu-setting-message-m-i-title">
<div>其他型</div>
<svg v-if="activeModal === 2" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22" fill="none">
<path d="M5.2998 12L9.16254 15.8627L16.5498 8.47547" stroke="#18CA6E" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<div class="menu-setting-message-m-i-sub">按照特殊规则在一定范围内生成</div>
<div v-if="activeModal === 2" class="menu-setting-message-m-i-default">当前选中</div>
</div>
</div>
</div>
<div class="menu-setting-message-item">
<div>方案周期</div>
<div class="menu-setting-message-i-two">共49天 已执行{{ executedDays }}</div>
</div>
<div class="menu-setting-message-item" @click="showDate = true">
<div>开始时间</div>
<div class="menu-setting-message-item-right" @click="toShowDate">
<div>{{ date }}</div>
<svg class="menu-setting-message-item-r-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none">
<path d="M12 2L7 7L2 2" stroke="#66676C" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
</div>
</div>
</div>
<van-popup v-model:show="showDate" round position="bottom" :duration="0.5">
<van-date-picker v-model="dateValues" title="选择日期" :min-date="minDate" :max-date="maxDate" @cancel="showDate = false" @confirm="onConfirm" />
</van-popup>
</div>
</template>
<style scoped lang="scss">
.menu-setting-top {
border-radius: 0 0 16px 16px;
background: #fff;
}
.menu-setting-card {
position: relative;
margin: 51px 10px 0;
z-index: 10;
flex-shrink: 0;
.menu-setting-card-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
}
}
.menu-setting-message-box {
padding: 40px 6px 0;
}
.menu-setting-message-avatar {
position: absolute;
left: 50%;
top: -30px;
transform: translateX(-50%);
width: 60px;
height: 60px;
border-radius: 30px;
border: 2px solid #fff;
}
.menu-setting-message-name {
margin-top: 10px;
margin-bottom: 3px;
color: #0e0e0e;
text-align: center;
font-size: 18px;
font-weight: 600;
}
.menu-setting-message-sub {
margin-bottom: 20px;
color: #66676c;
text-align: center;
font-size: 12px;
}
.menu-setting-message-list {
padding: 10px 16px 16px;
.menu-setting-message-item {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
color: #0e0e0e;
font-size: 14px;
border-bottom: 1px solid #f2f2f2;
.menu-setting-message-item-right {
display: flex;
align-items: center;
.menu-setting-message-item-r-icon {
margin-left: 10px;
}
}
.menu-setting-message-i-two {
color: #18ca6e;
font-weight: bold;
}
.menu-setting-message-modal {
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
width: 311px;
padding: 16px;
flex-direction: column;
gap: 16px;
border-radius: 16px;
background: #fff;
box-shadow: 0 3px 20px 0 rgba(0, 0, 0, 0.25);
z-index: 1;
.menu-setting-message-m-item {
display: flex;
padding: 16px;
flex-direction: column;
gap: 6px;
border-radius: 10px;
background: #f8f8f8;
.menu-setting-message-m-i-title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
}
.menu-setting-message-m-i-sub {
color: #66676c;
font-size: 14px;
}
.menu-setting-message-m-i-default {
padding: 3px 6px;
color: #18ca6e;
font-size: 14px;
border-radius: 6px;
background: #e8faf1;
}
}
.menu-setting-message-m-i-active {
background: #fff;
border: 0.3px solid #18ca6e;
}
}
}
.menu-setting-message-item:nth-child(1) {
padding-top: 0;
}
.menu-setting-message-item:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<van-uploader :after-read="afterRead" :multiple="multiple" :max-count="maxCount" accept="image/*">
<slot></slot>
</van-uploader>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { showToast } from 'vant';
import md5 from 'js-md5';
import service from '@/utils/service';
import { useUserStore } from '@/store/modules/user';
import { getALiYun } from '@/api/demo';
const props = defineProps({
multiple: {
type: Boolean,
default: false,
},
maxCount: {
type: Number,
default: 1,
},
type: {
type: String,
default: '',
},
});
const state = ref(false);
const userStore = useUserStore() as any;
const emit = defineEmits(['onSuccess']);
const afterRead = (file) => {
state.value = false;
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 = (file) => {
const uploadData = userStore.uploadData;
if (uploadData) {
const formData = new FormData();
const fileName = `${md5(file.name) + Math.random()}.${file.type.split('/').pop().toLowerCase()}`;
const filePath = `${uploadData.host}/${uploadData.dir}${fileName}`;
formData.append('name', uploadData.dir + fileName);
formData.append('key', uploadData.dir + fileName);
formData.append('policy', uploadData.policy);
formData.append('OSSAccessKeyId', uploadData.access_id);
formData.append('success_action_status', '200');
formData.append('signature', uploadData.signature);
formData.append('file', file);
formData.append('filename', file.name);
service
.post(uploadData.host, formData, { headers: { 'Content-Type': 'multipart/form-data' } })
.then((data) => {
console.log(data);
console.log(props.type, filePath, '7777777');
if (!props.type) {
emit('onSuccess', filePath);
} else {
emit('onSuccess', filePath, props.type);
}
})
.catch((err) => {
showToast('网络异常,请重试');
console.log(err);
});
} else {
showToast('网络环境异常,请稍后重试');
getALiYun(userStore.token).then((value) => {
if (value.code == 0) {
userStore.uploadData = value.data;
}
});
}
};
onMounted(() => {});
</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);
};

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

@ -0,0 +1,232 @@
import { showToast } from 'vant';
import { getALiYun } from '@/api/demo';
// import wx from 'weixin-js-sdk';
// import { useUserStore } from '@/store/modules/user';
// 回填登录数据
export const backFillLoginData = (result, userStore) => {
localStorage.setItem('workToken', result.user ? result.user.token : '');
localStorage.setItem('work_id', result.user ? result.user.work_id : '');
userStore.token = result.user ? result.user.token : '';
userStore.name = result.user ? result.user.name : '';
userStore.mobile = result.user ? result.user.mobile : '';
userStore.avatar = result.user ? result.user.avatar : 'http://images.ufutx.com/201905/13/599151d27fc07ba1bc4cc57a291525e5.jpeg';
userStore.wxConfig = result.work_config;
userStore.agentConfig = result.app_config;
localStorage.setItem('userStore', JSON.stringify(result));
if (userStore.uploadData && userStore.uploadData.access_id) {
return;
}
getALiYun('53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen').then((value) => {
if (value.code == 0) {
userStore.uploadData = value.data;
}
});
};
// 获取当前日期
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 timestamp = (time) => {
let date = new Date(time * 1000) as any;
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
let day = String(date.getDate()).padStart(2, '0');
let hours = String(date.getHours()).padStart(2, '0');
let minutes = String(date.getMinutes()).padStart(2, '0');
let seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 复制内容
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 weXinShare = async (img, link, title, desc) => {
// const wxConfig = useUserStore();
// wx.config(wxConfig.wxConfig);
// wx.ready(function () {
// wx.hideMenuItems({
// menuList: ['menuItem:copyUrl'], // 屏蔽复制链接
// });
// wx.updateAppMessageShareData({
// title, // 分享标题
// desc, // 分享描述
// link, // 分享链接
// img, // 分享图标
// success() {
// console.log('分享成功');
// },
// cancel() {
// console.log('分享失败');
// },
// });
// // 微信分享菜单测试
// wx.updateTimelineShareData({
// title, // 分享标题
// desc, // 分享描述
// link, // 分享链接
// img, // 分享图标
// success() {
// console.log('分享成功');
// },
// cancel() {
// console.log('分享失败');
// },
// });
// wx.onMenuShareAppMessage({
// title, // 分享标题
// desc, // 分享描述
// link, // 分享链接
// img, // 分享图标
// success() {
// console.log('分享成功-企业'); // 用户确认分享后执行的回调函数
// },
// cancel() {
// // 用户取消分享后执行的回调函数
// },
// });
// });
// wx.error(function (err) {
// console.log(JSON.stringify(err), '7777777777777');
// });
// };
//封装防抖
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适配

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

@ -0,0 +1,676 @@
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { createRouterGuards } from './routerGuards';
import { main } from './modules/main';
export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'boundEnterprise',
redirect: '/h5/boundEnterprise',
children: [
...main,
// 企业微信友福客服中心
{
path: '/h5/serviceCentre',
name: 'serviceCentre',
component: () => import('@/views/weChatPage/serviceCentre.vue'),
meta: {
title: '友福客服',
},
},
// 企业微信查看用户信息
{
path: '/h5/viewUserInfo',
name: 'viewUserInfo',
component: () => import('@/views/weChatPage/viewUserInfo.vue'),
meta: {
title: '查看信息',
},
},
// 企业微信查看用户信息
{
path: '/h5/viewUserInfoV2',
name: 'viewUserInfoV2',
component: () => import('@/views/weChatPage/viewUserInfoV2.vue'),
meta: {
title: '查看信息',
},
},
// 企业微信报告补充资料
{
path: '/h5/replenishData',
name: 'replenishData',
component: () => import('@/views/weChatPage/replenishData.vue'),
meta: {
title: '补充资料',
},
},
// 企业微信用户报告上传
{
path: '/h5/uploadReport',
name: 'uploadReport',
component: () => import('@/views/weChatPage/uploadReport.vue'),
meta: {
title: '报告上传',
},
},
// 企业微信用户测量记录
{
path: '/h5/measurementRecord',
name: 'measurementRecord',
component: () => import('@/views/weChatPage/measurementRecord.vue'),
meta: {
title: '测量记录',
},
},
// 企业微信用户测量记录详情
{
path: '/h5/measuringRecordDetail',
name: 'measuringRecordDetail',
component: () => import('@/views/weChatPage/measuringRecordDetail.vue'),
meta: {
title: '记录详情',
},
},
// 企业微信测量记录图表统计
{
path: '/h5/statistics',
name: 'statistics',
component: () => import('@/views/weChatPage/statistics.vue'),
meta: {
title: '图表统计',
},
},
// 企业微信备注记录
{
path: '/h5/remarks',
name: 'remarks',
component: () => import('@/views/weChatPage/remarks.vue'),
meta: {
title: '备注列表',
},
},
// 企业微信备注详情
{
path: '/h5/remarkDetail',
name: 'remarkDetail',
component: () => import('@/views/weChatPage/remarkDetail.vue'),
meta: {
title: '备注详情',
},
},
// 企业微信未绑定订单
{
path: '/h5/groupOrders',
name: 'groupOrders',
component: () => import('@/views/weChatPage/groupOrders.vue'),
meta: {
title: '订单列表',
},
},
// 企业微信创建虚拟订单
{
path: '/h5/addOrder',
name: 'addOrder',
component: () => import('@/views/weChatPage/addOrder.vue'),
meta: {
title: '创建订单',
},
},
// 企业微信评估人员列表
{
path: '/h5/assess',
name: 'assess',
component: () => import('@/views/weChatPage/assess.vue'),
meta: {
title: '评估人员',
},
},
// 企业微信评估详情
{
path: '/h5/assessDetail',
name: 'assessDetail',
component: () => import('@/views/weChatPage/assessDetail.vue'),
meta: {
title: '评估详情',
},
},
// 企业微信用户餐单
{
path: '/h5/userMenu',
name: 'userMenu',
component: () => import('@/views/weChatPage/userMenu.vue'),
meta: {
title: '用户餐单',
},
},
// 企业微信用户餐单营养元素余量
{
path: '/h5/nutrientElement',
name: 'nutrientElement',
component: () => import('@/views/weChatPage/nutrientElement.vue'),
meta: {
title: '营养元素',
},
},
// 企业微信工单
{
path: '/h5/workOrder',
name: 'workOrder',
component: () => import('@/views/weChatPage/workOrder.vue'),
meta: {
title: '工单',
},
},
// 企业微信工单
{
path: '/h5/addWorkOrder',
name: 'addWorkOrder',
component: () => import('@/views/weChatPage/addWorkOrder.vue'),
meta: {
title: '创建工单',
},
},
// 企业微信调查问卷列表
{
path: '/h5/surveys',
name: 'surveys',
component: () => import('@/views/weChatPage/surveys.vue'),
meta: {
title: '问卷列表',
},
},
// 企业微信填写调查问卷
{
path: '/h5/questionnaire',
name: 'questionnaire',
component: () => import('@/views/weChatPage/questionnaire.vue'),
meta: {
title: '调查问卷',
},
},
// 企业微信订单切换用户
{
path: '/h5/switchUser',
name: 'switchUser',
component: () => import('@/views/weChatPage/switchUser.vue'),
meta: {
title: '切换用户',
},
},
// 企业微信测试页面
{
path: '/h5/weChatTest',
name: 'weChatTest',
component: () => import('@/views/weChatPage/weChatTest.vue'),
meta: {
title: '测试页面',
},
},
// 企业微信跳转小程序中转
{
path: '/h5/enterpriseTransfer',
name: 'enterpriseTransfer',
component: () => import('@/views/weChatPage/enterpriseTransfer.vue'),
meta: {
title: '小程序中转',
},
},
// 企业微信跳转小程序中转
{
path: '/h5/serviceTransfer',
name: 'serviceTransfer',
component: () => import('@/views/weChatPage/serviceTransfer.vue'),
meta: {
title: '小程序中转',
},
},
// APP跳转中转
{
path: '/h5/bindEnterprise',
name: 'bindEnterprise',
component: () => import('@/views/appDir/bindEnterprise.vue'),
meta: {
title: '',
},
},
// APP跳转个人中心
{
path: '/h5/personalCenter',
name: 'personalCenter',
component: () => import('@/views/appDir/personalCenter.vue'),
meta: {
title: '友福客服',
},
},
// APP跳转查看用户信息
{
path: '/h5/appViewUserInfo',
name: 'appViewUserInfo',
component: () => import('@/views/appDir/appViewUserInfo.vue'),
meta: {
title: '查看信息',
},
},
// APP跳转报告上传
{
path: '/h5/appReplenishData',
name: 'appReplenishData',
component: () => import('@/views/appDir/appReplenishData.vue'),
meta: {
title: '补充资料',
},
},
// APP跳转报告上传
{
path: '/h5/appUploadReport',
name: 'appUploadReport',
component: () => import('@/views/appDir/appUploadReport.vue'),
meta: {
title: '报告上传',
},
},
// APP跳转查看用户测量记录
{
path: '/h5/appMeasurementRecord',
name: 'appMeasurementRecord',
component: () => import('@/views/appDir/appMeasurementRecord.vue'),
meta: {
title: '测量记录',
},
},
// APP跳转查看用户测量记录
{
path: '/h5/appMeasuringRecordDetail',
name: 'appMeasuringRecordDetail',
component: () => import('@/views/appDir/appMeasuringRecordDetail.vue'),
meta: {
title: '记录详情',
},
},
// APP跳转调查问卷
{
path: '/h5/appSurveys',
name: 'appSurveys',
component: () => import('@/views/appDir/appSurveys.vue'),
meta: {
title: '问卷列表',
},
},
// APP跳转调查问卷
{
path: '/h5/appQuestionnaire',
name: 'appQuestionnaire',
component: () => import('@/views/appDir/appQuestionnaire.vue'),
meta: {
title: '方案后服务问卷',
},
},
// APP跳转图片统计
{
path: '/h5/appStatistics',
name: 'appStatistics',
component: () => import('@/views/appDir/appStatistics.vue'),
meta: {
title: '图表统计',
},
},
// APP跳转用户备注管理
{
path: '/h5/appRemarks',
name: 'appRemarks',
component: () => import('@/views/appDir/appRemarks.vue'),
meta: {
title: '备注列表',
},
},
// APP跳转用户备注详情
{
path: '/h5/appRemarkDetail',
name: 'appRemarkDetail',
component: () => import('@/views/appDir/appRemarkDetail.vue'),
meta: {
title: '备注详情',
},
},
// APP跳转复盘评估表
{
path: '/h5/appAssess',
name: 'appAssess',
component: () => import('@/views/appDir/appAssess.vue'),
meta: {
title: '评估人员',
},
},
// APP跳转评估详情
{
path: '/h5/appAssessDetail',
name: 'appAssessDetail',
component: () => import('@/views/appDir/appAssessDetail.vue'),
meta: {
title: '评估详情',
},
},
// APP跳转切换用户
{
path: '/h5/appSwitchUser',
name: 'appSwitchUser',
component: () => import('@/views/appDir/appSwitchUser.vue'),
meta: {
title: '切换用户',
},
},
// APP跳转查看用户餐单
{
path: '/h5/appUserMenu',
name: 'appUserMenu',
component: () => import('@/views/appDir/appUserMenu.vue'),
meta: {
title: '用户餐单',
},
},
// APP跳转预设用户餐单日历
{
path: '/h5/appUserMenuAll',
name: 'appUserMenuAll',
component: () => import('@/views/appDir/appUserMenuAll.vue'),
meta: {
title: '用户餐单',
},
},
// APP跳转设置餐单
{
path: '/h5/appEditUserMenu',
name: 'appEditUserMenu',
component: () => import('@/views/appDir/appEditUserMenu.vue'),
meta: {
title: '设置餐单',
},
},
// APP跳转服务流程
{
path: '/h5/appServiceFlow',
name: 'appServiceFlow',
component: () => import('@/views/appDir/appServiceFlow.vue'),
meta: {
title: '服务流程',
},
},
// APP跳转工单
{
path: '/h5/appWorkOrder',
name: 'appWorkOrder',
component: () => import('@/views/appDir/appWorkOrder.vue'),
meta: {
title: '工单',
},
},
// APP跳转创建工单
{
path: '/h5/appAddWorkOrder',
name: 'appAddWorkOrder',
component: () => import('@/views/appDir/appAddWorkOrder.vue'),
meta: {
title: '创建工单',
},
},
// APP跳转订单列表
{
path: '/h5/appGroupOrders',
name: 'appGroupOrders',
component: () => import('@/views/appDir/appGroupOrders.vue'),
meta: {
title: '订单列表',
},
},
// APP跳转创建订单
{
path: '/h5/appAddOrder',
name: 'appAddOrder',
component: () => import('@/views/appDir/appAddOrder.vue'),
meta: {
title: '创建订单',
},
},
// APP跳转创建订单
{
path: '/h5/appSleepStatistics',
name: 'appSleepStatistics',
component: () => import('@/views/appDir/appSleepStatistics.vue'),
meta: {
title: '睡眠统计',
},
},
// 小程序webview数据统计页面
{
path: '/h5/mpStatistics',
name: 'mpStatistics',
component: () => import('@/views/otherDir/mpStatistics.vue'),
meta: {
title: '数据统计',
},
},
// 客服绑定酒
{
path: '/h5/bindingWine',
name: 'bindingWine',
component: () => import('@/views/otherDir/bindingWine.vue'),
meta: {
title: '友福渣酒',
},
},
// 客服绑定酒的详情
{
path: '/h5/wineDetail',
name: 'wineDetail',
component: () => import('@/views/otherDir/wineDetail.vue'),
meta: {
title: '友福渣酒',
},
},
// 客服绑定酒记录
{
path: '/h5/wineRecords',
name: 'wineRecords',
component: () => import('@/views/otherDir/wineRecords.vue'),
meta: {
title: '友福渣酒',
},
},
// 友福邀约
{
path: '/h5/invite',
name: 'invite',
component: () => import('@/views/otherDir/invite.vue'),
meta: {
title: '友福邀约',
},
},
// 查看友福邀约全部
{
path: '/h5/viewInvite',
name: 'viewInvite',
component: () => import('@/views/otherDir/viewInvite.vue'),
meta: {
title: '友福邀约',
},
},
// 友福邀约 v2
{
path: '/h5/inviteV2',
name: 'inviteV2',
component: () => import('@/views/otherDir/inviteV2.vue'),
meta: {
title: '年三十围炉火锅团聚夜',
},
},
// 查看友福邀约全部 v2
{
path: '/h5/viewInviteV2',
name: 'viewInviteV2',
component: () => import('@/views/otherDir/viewInviteV2.vue'),
meta: {
title: '年三十火锅人员',
},
},
// 友福邀约 v3
{
path: '/h5/inviteV3',
name: 'inviteV3',
component: () => import('@/views/otherDir/inviteV3.vue'),
meta: {
title: '友福批发商大会',
},
},
// 查看友福邀约全部 v3
{
path: '/h5/viewInviteV3',
name: 'viewInviteV3',
component: () => import('@/views/otherDir/viewInviteV3.vue'),
meta: {
title: '友福批发商大会',
},
},
// 友福邀约
{
path: '/h5/invite',
name: 'invite',
component: () => import('@/views/otherDir/invite.vue'),
meta: {
title: '友福邀约',
},
},
// 查看友福邀约全部
{
path: '/h5/viewInvite',
name: 'viewInvite',
component: () => import('@/views/otherDir/viewInvite.vue'),
meta: {
title: '友福邀约',
},
},
// 友福批发商联谊会
{
path: '/h5/inviteV4',
name: 'inviteV4',
component: () => import('@/views/otherDir/inviteV4.vue'),
meta: {
title: '批发商联谊会',
},
},
// 查看友福批发商联谊会全部
{
path: '/h5/viewInviteV4',
name: 'viewInviteV4',
component: () => import('@/views/otherDir/viewInviteV4.vue'),
meta: {
title: '批发商联谊会',
},
},
// 友福邀约 配置性 - 暂没用上
{
path: '/h5/inviteMerchant',
name: 'inviteMerchant',
component: () => import('@/views/otherDir/inviteMerchant.vue'),
meta: {
title: '',
},
},
// 查看友福邀约全部 配置性 - 暂没用上
{
path: '/h5/inviteMerchantList',
name: 'inviteMerchantList',
component: () => import('@/views/otherDir/inviteMerchantList.vue'),
meta: {
title: '',
},
},
// 友福年会
{
path: '/h5/addAnnualMeeting',
name: 'addAnnualMeeting',
component: () => import('@/views/otherDir/addAnnualMeeting.vue'),
meta: {
title: '友福年会',
},
},
// 查看友福年会邀约全部
{
path: '/h5/allAnnualMeeting',
name: 'allAnnualMeeting',
component: () => import('@/views/otherDir/allAnnualMeeting.vue'),
meta: {
title: '友福年会',
},
},
// saas活动订单接龙
{
path: '/h5/activityOrder',
name: 'activityOrder',
component: () => import('@/views/otherDir/activityOrder.vue'),
meta: {
title: '活动订单',
},
},
// 健康活动订单接龙
{
path: '/h5/activityOrderV2',
name: 'activityOrderV2',
component: () => import('@/views/otherDir/activityOrderV2.vue'),
meta: {
title: '活动订单',
},
},
// 讨论组话题列表
{
path: '/h5/discussList',
name: 'discussList',
component: () => import('@/views/otherDir/discussList.vue'),
meta: {
title: '话题列表',
},
},
// 讨论组话题详情
{
path: '/h5/discussDetail',
name: 'discussDetail',
component: () => import('@/views/otherDir/discussDetail.vue'),
meta: {
title: '话题详情',
},
},
// 404页面未找到
{
path: '/h5/notFound',
name: 'notFound',
component: () => import('@/views/otherDir/notFound.vue'),
meta: {
title: 'Not Found',
},
},
// 测试
{
path: '/h5/test',
name: 'test',
component: () => import('@/views/otherDir/test.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,15 @@
import { RouteRecordRaw } from 'vue-router';
export const main: Array<RouteRecordRaw> = [
{
path: '/h5/boundEnterprise',
name: 'boundEnterprise',
component: () => import('@/views/weChatPage/boundEnterprise.vue'),
meta: {
title: '个人中心',
icon: 'home-o',
active: 'https://image.fulllinkai.com/202304/14/7c216ca4337d8ebcd84279f8002c20dd.png',
inactive: 'https://image.fulllinkai.com/202304/14/ffb35939710ce4c98062f85392a2c005.png',
},
},
];

View File

@ -0,0 +1,81 @@
import { Router } from 'vue-router';
import { getALiYun, getLogin } from '@/api/demo';
import { useUserStore } from '@/store/modules/user';
let pathUrls = ['invite', 'viewInvite', 'inviteV4', 'viewInviteV4', 'inviteMerchant', 'inviteMerchantList', 'addAnnualMeeting', 'allAnnualMeeting', 'enterpriseTransfer', 'questionnaire', 'activityOrder', 'activityOrderV2', 'mpStatistics', 'appSurveys', 'appQuestionnaire', 'appUserMenu', 'appEditUserMenu', 'test', 'appSleepStatistics']; // 不需要登录的页面
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();
// 判断是否需要登录的页面
let through = false;
pathUrls.forEach((item) => {
if (to.path.includes(item)) {
through = true;
}
});
if (to.query.service_user_id) {
userStore.serviceUserId = to.query.service_user_id as any;
}
if (to.query.work_id) {
localStorage.removeItem('work_id');
localStorage.setItem('work_id', to.query.work_id as any);
}
if (through) {
next();
} else if (to.path.includes('bindEnterprise')) {
getALiYun('53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen').then((value) => {
if (value.code == 0) {
userStore.uploadData = value.data;
}
});
next();
} else {
if (initLogin <= 1) {
// 获取微信、企业微信凭证、token
getLogin().then((res) => {
if (res.code === 0) {
let result = res.data;
initLogin++;
if (result.user && result.user.work_id) {
localStorage.setItem('work_id', result.user ? result.user.work_id : '');
}
localStorage.setItem('workToken', result.user ? result.user.token : '');
userStore.token = result.user ? result.user.token : '';
userStore.name = result.user ? result.user.name : '';
userStore.mobile = result.user ? result.user.mobile : '';
userStore.userID = result.user ? result.user.id : '';
userStore.avatar = result.user ? result.user.avatar : 'http://images.ufutx.com/201905/13/599151d27fc07ba1bc4cc57a291525e5.jpeg';
userStore.wxConfig = result.work_config;
userStore.agentConfig = result.app_config;
getALiYun('53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen').then((value) => {
if (value.code == 0) {
userStore.uploadData = value.data;
}
});
setTimeout(() => {
next();
});
}
});
} else {
next();
}
}
});
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))) {
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 };

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

@ -0,0 +1,48 @@
import { defineStore } from 'pinia';
interface UserState {
init: number;
token: string;
name: string;
avatar: string;
mobile: string;
userID: string;
weChatBindState: string;
chatId: string;
serviceUserId: string;
dataState: string;
orderState: string;
service: string;
coach: string;
chiefCoach: string;
uploadData: object;
wxConfig: object;
agentConfig: object;
signature: object;
}
export const useUserStore = defineStore({
id: 'user',
state: (): UserState => ({
init: 0,
token: '',
name: '',
avatar: '',
mobile: '',
userID: '',
weChatBindState: '',
chatId: '', // 群ID
serviceUserId: '', // 客服人员ID
dataState: '', // 服务状态
orderState: '', // 订单状态
service: '', // 是否客服
coach: '', // 是否副教练
chiefCoach: '', // 是否主教练
uploadData: {}, // 阿里云上传配置信息
wxConfig: { init: 'true' }, // 储存微信凭证
agentConfig: {}, // 储存企业微信凭证
signature: {}, // 储存上传凭证
}),
getters: {},
actions: {},
});

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

@ -0,0 +1,472 @@
@charset "UTF-8";
@use './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-fbc {
display: flex;
justify-content: space-between;
align-items: center;
}
.f-fcl {
display: flex;
justify-content: left;
align-items: center;
}
.f-fl {
display: flex;
justify-content: left;
}
.f-fcc {
display: flex;
justify-content: center;
align-items: center;
}
.f-fc {
display: flex;
justify-content: center;
}
.f-fcr {
display: flex;
justify-content: right;
align-items: center;
}
.f-fr {
display: flex;
justify-content: right;
}
.f-wrap {
flex-wrap: wrap;
}
.ellipsis_1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ellipsis_2 {
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
}
.ellipsis_3 {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
}
.backCover {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.ui-relative { position: relative; }
.ui-overflow { overflow: hidden; }
.flo_l { float: left; }
.flo_r { float: right; }
.bold { font-weight: bold; }
.color0 {
color: #000 !important;
}
.color0E {
color: #0e0e0e !important;
}
.color054 {
color: #054725 !important;
}
.color08 {
color: #080f1e !important;
}
.color76c {
color: #66676c !important;
}
.color76 {
color: #767676 !important;
}
.color73 {
color: #734236 !important;
}
.color043 {
color: #043d20 !important;
}
.colorB2 {
color: #b2b3b5 !important;
}
.colorF { color: #fff; }
.color3 { color: #333; }
.color6 { color: #666; }
.color9 { color: #999; }
.colorF5 { color: #f5f5f5; }
.colorF8 { color: #f8f8f8; }
.colorC2 { color: #c2c2c2; }
.colorTheme { color: #5ac7a0; }
.colorPrice { color: #ff5959; }
.bold100 {
font-weight: 100 !important;
}
.bold200 {
font-weight: 200 !important;
}
.bold400 {
font-weight: 400 !important;
}
.bold500 {
font-weight: 500 !important;
}
.bold600 {
font-weight: 600 !important;
}
.bold700 {
font-weight: 700 !important;
}
.bcTheme { background: linear-gradient(90deg, #8c9bff 0%, #707ffa 100%); }
.font_20 {
font-size: px2rem(20);
-webkit-text-size-adjust: none;
}
.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-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-20 { padding-right: px2rem(20); }
.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-20 { padding-bottom: px2rem(20); }
.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-20 { padding-left: px2rem(20); }
.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-20 { margin-top: px2rem(20); }
.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-20 { margin-right: px2rem(20); }
.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-20 { margin-bottom: px2rem(20); }
.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-20 { margin-left: px2rem(20); }
.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-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"
}

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

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>REM布局</title>
<meta charset="utf-8" />
<meta lang="zh-CN" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<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" />
<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>
<style>
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
<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,64 @@
<!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.0,maximum-scale=1.0,user-scalable=0" />
<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" />
<link rel="stylesheet" href="./css/vw-rem.css" />
</head>
<style>
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
<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

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

@ -0,0 +1,64 @@
<!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.0,maximum-scale=1.0,user-scalable=0" />
<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" />
<link rel="stylesheet" href="./css/vw.css" />
</head>
<style>
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
<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;
};

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

@ -0,0 +1,153 @@
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';
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: `${href.value}${import.meta.env.VITE_BASE_API as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 15000, // 请求超时时间
});
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() as any;
if (import.meta.env.MODE === 'development') {
userStore.chatId = 1;
}
if ((localStorage.getItem('workToken') || userStore.token) && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('workToken') || userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
if (config.url?.includes('?')) {
config.url = `${config.url}&work_id=${localStorage.getItem('work_id') || ''}`;
} else {
config.url = `${config.url}?work_id=${localStorage.getItem('work_id') || ''}`;
}
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);
const errorV3 = ref(1);
service.interceptors.response.use(
(response) => {
closeToast();
const userStore = useUserStore();
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) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('workToken');
localStorage.removeItem('work_id');
getLogin().then((res) => {
let result = res.data;
localStorage.setItem('workToken', result.user ? result.user.token : '');
localStorage.setItem('work_id', result.user ? result.user.work_id : '');
userStore.token = result.user ? result.user.token : '';
userStore.name = result.user ? result.user.name : '';
userStore.mobile = result.user ? result.user.mobile : '';
userStore.avatar = result.user ? result.user.avatar : 'http://images.ufutx.com/201905/13/599151d27fc07ba1bc4cc57a291525e5.jpeg';
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
errorV3.value += 1;
if (errorV3.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
// master环境
if (location.href.split('/#/')[1]) {
window.location.replace(`https://health.ufutx.com/api/h5/order/bound/enterprise/auth/v2?target_path=/work/#/${location.href.split('/#/')[1]}`);
} else {
window.location.replace(`https://health.ufutx.com/api/h5/order/bound/enterprise/auth/v2?target_path=/work/#/h5/boundEnterprise`);
}
}
}
return Promise.resolve(response);
} else {
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;

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

@ -0,0 +1,151 @@
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';
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: `${href.value}${import.meta.env.VITE_BASE_API_APP as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 15000, // 请求超时时间
});
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() as any;
if (import.meta.env.MODE === 'development') {
userStore.chatId = 1;
}
if (localStorage.getItem('appToken') && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('appToken')}`;
}
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) => {
// 处理请求错误
console.log(error);
return Promise.reject(error);
},
);
// response相应拦截器
const error = ref(1);
const errorV3 = 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('workToken');
localStorage.removeItem('work_id');
getLogin().then((res) => {
let result = res.data;
localStorage.setItem('workToken', result.user ? result.user.token : '');
localStorage.setItem('work_id', result.user ? result.user.work_id : '');
userStore.token = result.user ? result.user.token : '';
userStore.name = result.user ? result.user.name : '';
userStore.mobile = result.user ? result.user.mobile : '';
userStore.avatar = result.user ? result.user.avatar : 'http://images.ufutx.com/201905/13/599151d27fc07ba1bc4cc57a291525e5.jpeg';
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
closeToast();
errorV3.value += 1;
if (errorV3.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
// master环境
if (location.href.split('/#/')[1]) {
window.location.replace(`https://health.ufutx.com/api/h5/order/bound/enterprise/auth/v2?target_path=/work/#/${location.href.split('/#/')[1]}`);
} else {
window.location.replace(`https://health.ufutx.com/api/h5/order/bound/enterprise/auth/v2?target_path=/work/#/h5/boundEnterprise`);
}
}
}
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;

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

@ -0,0 +1,111 @@
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';
// 超文本传输协议
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: 50000, // 请求超时时间
});
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: '加载中...',
duration: 0,
overlayClass: 'ui-van-overlay',
overlay: true,
});
}
const userStore = useUserStore() as any;
// if (import.meta.env.MODE === 'development') {
// userStore.chatId = 31146624805;
// userStore.serviceUserId = 31;
// }
if ((localStorage.getItem('workToken') || userStore.token) && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('workToken') || userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
if (config.url?.includes('?')) {
config.url = `${config.url}&work_id=${localStorage.getItem('work_id') || ''}&service_user_id=${userStore.serviceUserId || ''}`;
} else {
config.url = `${config.url}?work_id=${localStorage.getItem('work_id') || ''}&service_user_id=${userStore.serviceUserId || ''}`;
}
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相应拦截器
service.interceptors.response.use(
(response) => {
const res = response.data;
if (res.code === 1) {
closeToast();
// 服务器访问出错了~
showToast(res.message);
return Promise.reject(res.message || 'code: 1');
} 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;

176
src/utils/weChat.ts Normal file
View File

@ -0,0 +1,176 @@
import { ref } from 'vue';
import axios, { AxiosRequestConfig } from 'axios';
import qs from 'qs';
import { showLoadingToast, closeToast, showDialog } from 'vant';
import { ContentTypeEnum } from './httpEnum';
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 as string}`,
withCredentials: false, // 当跨域请求时发送cookie
timeout: 15000, // 请求超时时间
});
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
hideLoading?: boolean;
}
interface BaseResponse<T = any> {
code: number;
data: T;
content: T;
msg: string;
}
// request请求拦截器
service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
// 不传递默认开启loading
if (!config.hideLoading) {
showLoadingToast({
message: '加载中...',
forbidClick: true,
});
}
// 192.168.31.30:8090
const userStore = useUserStore() as any;
// if (import.meta.env.MODE === 'development') {
// userStore.chatId = 31146624805;
// userStore.serviceUserId = 31;
// }
if (userStore.token && config.headers) {
config.headers['Authorization'] = `Bearer ${localStorage.getItem('workToken') || userStore.token}`;
}
if (config.headers) {
config.headers['Version'] = `${import.meta.env.VITE_BASE_API_VERSION}`;
}
if (config.url?.includes('?')) {
config.url = `${config.url}&work_id=${localStorage.getItem('work_id') || ''}&service_user_id=${userStore.serviceUserId || ''}`;
} else {
config.url = `${config.url}?work_id=${localStorage.getItem('work_id') || ''}&service_user_id=${userStore.serviceUserId || ''}`;
}
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);
const errorV3 = ref(1);
service.interceptors.response.use(
(response) => {
closeToast();
const userStore = useUserStore();
const res = response.data;
if (res.code === 1) {
// console.log(router.currentRoute.value.name, `${res.message}222`, '77777777777777777');
// 服务器访问出错了~
if (res.message == 'data is empty') {
router.replace({
name: 'notFound',
});
} else {
showDialog({
title: '温馨提示',
overlayStyle: { background: '#333333', opacity: 0.8 },
message: res.message,
}).then(() => {});
}
return Promise.reject(res.message || 'code: 1');
} else if (res.code === 2) {
// 登录超时,重新登录
error.value += 1;
if (error.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
localStorage.removeItem('workToken');
localStorage.removeItem('work_id');
getLogin().then((res) => {
let result = res.data;
localStorage.setItem('workToken', result.user ? result.user.token : '');
localStorage.setItem('work_id', result.user ? result.user.work_id : '');
userStore.token = result.user ? result.user.token : '';
userStore.name = result.user ? result.user.name : '';
userStore.mobile = result.user ? result.user.mobile : '';
userStore.avatar = result.user ? result.user.avatar : 'http://images.ufutx.com/201905/13/599151d27fc07ba1bc4cc57a291525e5.jpeg';
setTimeout(() => {
window.location.replace(location.href);
}, 200);
});
}
}
return Promise.reject(res.message);
} else if (res.code === 3) {
errorV3.value += 1;
if (errorV3.value <= 2) {
// 本地环境
if (import.meta.env.MODE === 'development') {
userStore.token = '53|SPv38Y7yBN7AFglo82Cze3hU1qVqJPL8QUG4UDen';
} else {
// master环境
if (location.href.split('?')[1]) {
window.location.replace(`https://health.ufutx.com/api/h5/talk/auth?${location.href.split('?')[1]}`);
} else {
window.location.replace(`https://health.ufutx.com/api/h5/order/bound/enterprise/auth/v2?target_path=boundEnterprise`);
}
}
}
return Promise.resolve(response);
} else {
return Promise.resolve(response);
}
},
(error: Error) => {
if (error.message?.includes('timeout')) {
showDialog({
title: '温馨提示',
overlayStyle: { background: '#333333', opacity: 0.8 },
message: '网络环境异常,请稍后重试!',
}).then(() => {
router.replace({
name: 'boundEnterprise',
});
router.go(-1);
});
}
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;

View File

@ -0,0 +1,225 @@
<template>
<div class="ui-appAddOrder">
<div class="ui-data-box">
<div class="font_28 color3 ui-pl-20 ui-pb-20">昵称</div>
<van-field v-model="name" class="ui-data-input f-fcc font_30 color3 bold" placeholder="请填写昵称" />
<div class="font_28 color3 ui-pl-20 ui-pb-20 ui-pt-40">联系电话</div>
<div class="ui-area-code-box f-fcl">
<div class="f-fcl ui-area-code" @click.stop="switchChoose">
<div class="font_26 color3">{{ AreaValue }}</div>
<img v-if="showChooseArea" class="Angle_icon" src="https://image.fulllinkai.com/202112/10/697cbb5933196bbc21165cb974f2e343.png" alt="" />
<img v-else class="Angle_icon" src="https://image.fulllinkai.com/202112/10/02d4c797f6de6494643f506c5d2b85b7.png" alt="" />
</div>
<van-field v-model="mobile" type="tel" class="ui-mobile-input f-fcc font_30 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-btn font_30 colorF bold f-fcc" @click="addOrder">确定创建</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
import requestGo from '@/utils/requestGo';
defineOptions({ name: 'AppAddOrder' });
const userStore = useUserStore(); // pinia
const throttle = ref(true);
const name = ref('');
const mobile = ref('');
const areaList = ref<any[]>([{ area_code: 86, label: '中国大陆' }]);
const AreaValue = ref('中国大陆 86');
const showChooseArea = ref(false);
const AreaIndex = ref(0);
const addOrder = () => {
let data = {
chat_id: userStore.chatId,
name: name.value,
area_code: areaList.value[AreaIndex.value].area_code,
mobile: mobile.value,
};
if (!name.value) {
showToast('请填写昵称');
return;
}
if (!mobile.value) {
showToast('请填写联系电话');
return;
}
if (AreaIndex.value == 0 && !/^1(3|4|5|6|7|8|9)\d{9}$/.test(mobile.value)) {
showToast('电话号码格式错误');
return;
}
if (throttle.value) {
throttle.value = false;
weChat({ url: `h5/offline/order`, data, method: 'post' })
.then(() => {
showToast('创建成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'personalCenter',
});
router.go(-2);
}, 1200);
})
.catch((err) => {
throttle.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 selectedArea = (e, index) => {
AreaValue.value = `${e.label} ${e.area_code}`;
AreaIndex.value = index;
showChooseArea.value = !showChooseArea.value;
};
//
const switchChoose = () => {
showChooseArea.value = !showChooseArea.value;
};
onMounted(() => {
getAreaCode();
});
</script>
<style lang="scss" scoped>
.ui-appAddOrder {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-data-box {
padding: px2rem(40);
// 线
::v-deep(.van-cell:after) {
position: relative;
}
.ui-data-input {
width: 100%;
background: #ffffff;
border-radius: px2rem(16);
height: px2rem(102);
}
.ui-area-code-box {
width: 100%;
height: px2rem(102);
background: #ffffff;
position: relative;
.ui-area-code {
padding-left: px2rem(30);
padding-right: px2rem(20);
white-space: nowrap;
word-break: break-all;
}
.Angle_icon {
width: px2rem(20);
height: px2rem(12);
display: block;
margin-left: px2rem(10);
}
.ui-mobile-input {
padding-left: 0;
}
.ui-area-mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 21;
}
.area_code_choose_box {
position: absolute;
left: px2rem(66);
top: px2rem(86);
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-btn {
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
margin: px2rem(140) auto;
}
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<div class="ui-appAddWorkOrder">
<div class="font_30 color333 bold f-fcl">添加工单</div>
<div class="ui-describe-box">
<van-field v-model="describe" class="ui-desc-input font_30 color3" rows="5" type="textarea" maxlength="500" show-word-limit placeholder="请输入工单描述内容" autosize />
</div>
<div class="ui-upload-box">
<div v-for="(item, index) in pics" :key="index" class="ui-upload-icon-box flo_l">
<img class="ui-upload-icon" :src="item" mode="aspectFill" alt="" @click="ImagePreview(pics, index)" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" mode="widthFix" alt="" @click="clearPic(index)" />
</div>
<div v-if="pics.length < 9">
<uploadPicture :multiple="true" :max-count="9" @on-success="onSuccess" @click="takePhone">
<div class="ui-upload">
<img class="ui-upload-icon flo_l" src="https://image.fulllinkai.com/202301/07/6c9bc853bed42c9871f56156d1b8f31c.png" alt="" />
</div>
</uploadPicture>
</div>
</div>
<div class="ui-next-btn font_30 f-fcc colorF" @click="changeData">保存</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showImagePreview, showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AddWorkOrder' });
const userStore = useUserStore() as any;
const throttle = ref(true);
const describe = ref<any>('');
const pics = ref<any[]>([]);
//
const clearPic = (index) => {
pics.value.splice(index, 1);
};
const changeData = () => {
let data = {
chat_id: userStore.chatId,
images: pics.value,
desc: describe.value,
};
if (!describe.value) {
showToast('请输入工单描述内容');
return;
}
if (pics.value.length === 0) {
showToast('请上传最少一张图片');
return;
}
if (throttle.value) {
throttle.value = false;
weChat({ url: `/h5/add/work/order`, data, method: 'post' })
.then(() => {
throttle.value = true;
showToast('保存成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'appWorkOrder',
query: { state: 1 },
});
router.go(-1);
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const ImagePreview = (e, index) => {
showImagePreview({
images: e,
showIndex: false,
startPosition: index,
loop: false,
});
};
//
const onSuccess = (val) => {
if (pics.value.length < 9) {
pics.value.push(val);
}
};
window.getAndroidPhone = (type, e) => {
if (e && e.length > 0) {
e.forEach((item) => {
if (pics.value && pics.value.length < 9) {
pics.value.push(item);
}
});
}
};
const takePhone = () => {
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
// app
window.webAppInterface.takePhoto('multiple', 9);
}
};
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appAddWorkOrder {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
padding: px2rem(30);
}
.ui-describe-box {
padding-bottom: px2rem(40);
.ui-desc-input {
background: #ffffff;
border-radius: px2rem(24);
margin-top: px2rem(20);
}
.inputColor {
color: #c2c2c2;
}
}
.ui-upload-box {
overflow: hidden;
margin-bottom: px2rem(120);
.ui-upload-icon-box {
position: relative;
.ui-upload-clear-icon {
position: absolute;
right: px2rem(30);
top: px2rem(10);
width: px2rem(36);
height: px2rem(36);
}
}
.ui-upload-icon {
width: px2rem(200);
height: px2rem(200);
display: block;
border-radius: px2rem(16);
margin-right: px2rem(20);
margin-bottom: px2rem(20);
object-fit: cover;
object-position: center;
}
}
.ui-next-btn {
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<div class="ui-appAssess">
<div v-if="!loadingState && list.length == 0">
<img class="ui-empty-data-icon" src="https://image.fulllinkai.com/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div v-if="roles.length > 0" class="color6 font_30 text-center">
当前职责为
<span v-for="(item, index) in roles" :key="index">{{ item }}<span v-if="index + 1 != roles.length"></span></span>
,暂无可评估人员
</div>
<div v-else-if="roles.length == 0" class="color6 font_30 text-center">
当前并未绑定职责,
<span class="colorTheme" @click="contactService">联系</span>
工作人员
</div>
</div>
<div v-else>
<div class="ui-list-box">
<div v-for="(item, index) in list" :key="index" class="ui-list-item f-fbc">
<div class="">
<div class="ui-name font_30 color3 bold ellipsis_1">{{ item.service_user.name }}</div>
<div class="ui-responsibility f-fcl">
<img v-if="item.role.name == '主教练'" class="ui-icon" src="https://image.fulllinkai.com/202306/25/103763ce22be9a1bd964080a6b45bdf0.png" alt="" />
<img v-else-if="item.role.name == '副教练'" class="ui-icon" src="https://image.fulllinkai.com/202306/25/b73e57901f943fa31eec78b21c8e8eac.png" alt="" />
<img v-else class="ui-icon" src="https://image.fulllinkai.com/202306/25/6a96db6ec2a77503b84e6213f9d905a6.png" alt="" />
<div class="font_26 color6">{{ item.role.name }}</div>
</div>
</div>
<div v-if="item.reappraises_count == 1" class="ui-assess-btn-v2 font_26 f-fcc" @click="jumpPath(item)">查看详情</div>
<div v-else class="ui-assess-btn font_26 f-fcc" @click="jumpPath(item)">立即评估</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onActivated } from 'vue';
import { closeToast } from 'vant';
import router from '@/router';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppAssess' });
const userStore = useUserStore() as any;
const list = ref<any[]>([]); //
const roles = ref<any[]>([]); //
const loadingState = ref(true); //
//
const getList = () => {
weChat({ url: `/h5/order/other/roles?chat_id=${userStore.chatId}&is_im=1`, method: 'get' })
.then((res) => {
const result = res.data;
list.value = result;
if (list.value && list.value.length === 0) {
loadingState.value = false;
}
})
.catch((err) => {
console.log(err);
});
};
//
const getRoles = () => {
weChat({ url: `/h5/work/group/roles?chat_id=${userStore.chatId}&is_im=1`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
roles.value = result.roles;
console.log(result);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
const contactService = () => {
window.location.href = `https://work.weixin.qq.com/kfid/kfc967090f765f69300`;
};
const jumpPath = (e) => {
let typeName;
if (e.role.name === '主教练') {
typeName = 1;
} else if (e.role.name === '副教练') {
typeName = 2;
} else {
typeName = 3;
}
router.push({
name: 'appAssessDetail',
query: { ID: e.id, state: e.reappraises_count, type: typeName },
});
};
onActivated(() => {
let route = router.currentRoute.value.query;
//
if (route.state) {
getList();
getRoles();
}
});
onMounted(() => {
console.log('222');
let route = router.currentRoute.value.query;
if (route.chat_id) {
userStore.chatId = route.chat_id;
}
getList();
getRoles();
});
</script>
<style lang="scss" scoped>
.ui-appAssess {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 25vh auto px2rem(10) auto;
}
.ui-list-box {
padding-top: px2rem(30);
padding-bottom: 16vh;
.ui-list-item {
margin: 0 px2rem(30) px2rem(30) px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(16);
.ui-name {
max-width: px2rem(420);
}
.ui-responsibility {
padding-top: px2rem(20);
.ui-icon {
width: px2rem(28);
height: px2rem(28);
display: block;
margin-right: px2rem(8);
}
}
.ui-assess-btn,
.ui-assess-btn-v2 {
width: px2rem(144);
height: px2rem(56);
border-radius: px2rem(30);
border: px2rem(2) solid #ffe3c1;
color: #ffa438;
}
.ui-assess-btn-v2 {
color: #5ac7a0;
border: px2rem(2) solid #b2e3d2;
}
}
}
</style>

View File

@ -0,0 +1,354 @@
<template>
<div class="ui-appAssessDetail">
<div class="ui-assess-data-box">
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">1. 用户方案配合度</div>
<div class="f-fbc ui-item-rate">
<van-rate v-model="coordinate" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />
<div class="font_28 color6">{{ coordinate * 2 }}</div>
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">2. {{ firstProblem }}</div>
<div class="f-fbc ui-item-rate">
<van-rate v-model="firstAnswer" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />
<div class="font_28 color6">{{ firstAnswer * 2 }}</div>
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">3. {{ secondProblem }}</div>
<div class="f-fbc ui-item-rate">
<van-rate v-model="secondAnswer" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />
<div class="font_28 color6">{{ secondAnswer * 2 }}</div>
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">4. {{ thirdProblem }}</div>
<div class="f-fbc ui-item-rate">
<van-rate v-model="thirdAnswer" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />
<div class="font_28 color6">{{ thirdAnswer * 2 }}</div>
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">5. {{ fourthProblem }}</div>
<div class="f-fbc ui-item-rate">
<van-rate v-model="fourthAnswer" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />
<div class="font_28 color6">{{ fourthAnswer * 2 }}</div>
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">6. 你会将该工作人员推荐给下一位用户吗</div>
<div class="f-fcl">
<div v-for="(item, index) in selectList" :key="index" class="f-fcl ui-item-select" @click="selectChange(item)">
<img v-show="selectValue != item" class="ui-item-select-icon" src="https://image.fulllinkai.com/202306/25/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-show="selectValue == item" class="ui-item-select-icon" src="https://image.fulllinkai.com/202306/25/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_28 color3">{{ item }}</div>
</div>
</div>
<div class="ui-item-reason">
<van-field v-model="recommendReason" :readonly="readonly" class="ui-item-data-input font_30 color3" rows="5" type="textarea" placeholder="请描述你的理由" autosize />
</div>
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">7. 本次服务优点表现</div>
<van-field v-model="advantage" :readonly="readonly" class="ui-item-data-input font_30 color3" rows="5" type="textarea" placeholder="请描述本次服务优点表现" autosize />
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">8. 需要改进与建议</div>
<!-- <div class="f-fbc ui-item-rate">-->
<!-- <van-rate v-model="rateNum" gutter="10" :size="21" :readonly="readonly" color="#ffc629" void-icon="star" void-color="#e8e8e8" allow-half />-->
<!-- <div class="font_28 color6">{{ rateNum * 2 }}</div>-->
<!-- </div>-->
<van-field v-model="suggest" :readonly="readonly" class="ui-item-data-input font_30 color3" rows="5" type="textarea" placeholder="请留下你的建议" autosize />
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">9. 特殊案例与解决方案分享</div>
<van-field v-model="shareCase" :readonly="readonly" class="ui-item-data-input font_30 color3" rows="5" type="textarea" placeholder="请描述解决方案分享" autosize maxlength="500" />
</div>
<div class="ui-assess-data-item">
<div class="font_30 color3 bold ui-item-title">10. 请分享您的想法与改善建议以便我们更好的服务</div>
<van-field v-model="shareSuggest" :readonly="readonly" class="ui-item-data-input font_30 color3" rows="5" type="textarea" placeholder="请描述您的想法与改善建议" autosize maxlength="500" />
</div>
</div>
<div v-if="state != 1" class="ui-save-btn f-fcc colorF font_30 bold" @click="submitCheck">提交</div>
<van-popup v-model:show="showTips" round :close-on-click-overlay="false" :lock-scroll="true" :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 text-center ui-tips-text">提交后不可修改确定提交该评估表吗</div>
<div class="ui-btn-box f-fbc">
<div class="ui-btn font_32 f-fcc color6" @click="showTips = false">取消</div>
<div class="ui-btn-v2 font_32 f-fcc colorF" @click="save">确定</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
defineOptions({ name: 'AppAssessDetail' });
const ID = ref<any>('');
const isApp = ref<any>('');
const isIOS = ref<any>(true);
const coordinate = ref(0); //
const firstProblem = ref<any>(''); //
const firstAnswer = ref<any>(0); //
const secondProblem = ref<any>(''); //
const secondAnswer = ref<any>(0); //
const thirdProblem = ref<any>(''); //
const thirdAnswer = ref<any>(0); //
const fourthProblem = ref<any>(''); //
const fourthAnswer = ref<any>(0); //
const selectValue = ref<any>(''); //
const recommendReason = ref<any>(''); //
const selectList = ref<any[]>(['推荐', '不推荐', '建议学习升级后']);
const shareSuggest = ref<any>(''); //
const shareCase = ref<any>(''); //
const state = ref<any>(''); //
const type = ref<any>(''); // type123
const readonly = ref<any>(false); //
const advantage = ref<any>(''); //
const rateNum = ref<any>(0); //
const suggest = ref<any>(''); //
const throttle = ref(true);
const showTips = ref(false);
//
const getDetail = () => {
weChat({ url: `h5/roles/${ID.value}/reappraise?is_im=1`, method: 'get' })
.then((res) => {
let result = res.data;
coordinate.value = result.content.coordinate || 0;
firstAnswer.value = result.content.firstAnswer; //
secondAnswer.value = result.content.secondAnswer; //
thirdAnswer.value = result.content.thirdAnswer; //
fourthAnswer.value = result.content.fourthAnswer; //
selectValue.value = result.content.selectValue; //
recommendReason.value = result.content.recommendReason; //
advantage.value = result.content.advantage; //
rateNum.value = result.content.rateNum; //
suggest.value = result.content.suggest; //
shareSuggest.value = result.content.shareSuggest || ''; //
shareCase.value = result.content.shareCase || ''; //
console.log(result, '77');
})
.catch((err) => {
console.log(err);
});
};
//
const submitCheck = () => {
if (!selectValue.value) {
showToast('请选择是否推荐给下一位用户');
return;
}
if (!recommendReason.value) {
showToast('请描述是否推荐给下一位用户理由');
return;
}
if (!advantage.value) {
showToast('请描述本次服务优点表现');
return;
}
if (!suggest.value) {
showToast('请留下你的建议');
return;
}
if (!shareCase.value) {
showToast('请描述解决方案分享');
return;
}
if (!shareSuggest.value) {
showToast('请描述您的想法与改善建议');
return;
}
showTips.value = true;
};
//
const save = () => {
if (throttle.value) {
let data = {
is_im: 1,
content: { firstAnswer: firstAnswer.value, secondAnswer: secondAnswer.value, thirdAnswer: thirdAnswer.value, fourthAnswer: fourthAnswer.value, coordinate: coordinate.value, selectValue: selectValue.value, recommendReason: recommendReason.value, advantage: advantage.value, rateNum: rateNum.value, suggest: suggest.value, shareSuggest: shareSuggest.value, shareCase: shareCase.value },
};
console.log(data);
throttle.value = false;
weChat({ url: `h5/reappraise/roles/${ID.value}/v2`, data, method: 'post' })
.then(() => {
showTips.value = false;
showToast('提交成功');
setTimeout(() => {
throttle.value = true;
// app
if (isApp.value) {
close();
} else {
router.replace({
name: 'appAssess',
query: { state: 1 },
});
router.go(-1);
}
}, 1000);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const selectChange = (e) => {
if (state.value * 1 === 1) {
return;
}
selectValue.value = e;
};
const close = () => {
// ios app
if (isIOS.value) {
window.webkit.messageHandlers.goBack.postMessage(null);
} else {
// app
window.webAppInterface.goBack();
}
};
onMounted(() => {
let route = router.currentRoute.value.query;
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;
}
ID.value = route.ID;
state.value = route.state;
type.value = route.type;
if (route.isApp) {
isApp.value = route.isApp;
}
// type 1 2 3
if (type.value * 1 === 1) {
firstProblem.value = '积极主动带领服务团队帮助客户顺利执行方案0-10分';
secondProblem.value = '认真指导副教练学习并积极主动带领副教练完成服务工作0-10分';
thirdProblem.value = '认真审核餐单及时解决服务过程中的突发疑难问题0-10分';
fourthProblem.value = '着装整洁体态标准仪容端庄言谈得体0-10分';
} else if (type.value * 1 === 2) {
firstProblem.value = '明确工作岗位职责熟练服务流程每天按时发餐单正确指导客户使用餐单0-10分';
secondProblem.value = '积极主动与主教练、客服沟通、商讨客户遇到的问题并及时反馈并遵行主教练指导0-10分';
thirdProblem.value = '每天主动关心客户生活化服务关注数据变化并及时沟通及时回复客户信息0-10分';
fourthProblem.value = '着装整洁体态标准仪容端庄言谈得体0-10分';
} else {
firstProblem.value = '及时收集客户资料跟进客户合同签订、方案设计进程按时发放餐单0-10分';
secondProblem.value = '主动协助主教练指导副教练服务工作0-10分';
thirdProblem.value = '积极主动提醒或者辅助回复服务群客户的信息问答0-10分';
fourthProblem.value = '着装整洁体态标准仪容端庄言谈得体0-10分';
}
// state 1 0
if (state.value * 1 === 1) {
readonly.value = true;
getDetail();
}
});
</script>
<style lang="scss" scoped>
.ui-appAssessDetail {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-assess-data-box {
padding: px2rem(30) px2rem(30) px2rem(180) px2rem(30);
.ui-assess-data-item {
padding-bottom: px2rem(60);
.ui-item-title {
padding-bottom: px2rem(30);
}
.ui-item-select {
margin-right: px2rem(68);
.ui-item-select-icon {
width: px2rem(28);
height: px2rem(28);
display: block;
margin-right: px2rem(16);
}
}
.ui-item-reason {
padding-top: px2rem(20);
}
.ui-item-rate {
padding-bottom: px2rem(24);
}
.ui-item-data-input {
padding: px2rem(30);
background: #f8f8f8;
border-radius: px2rem(16);
line-height: px2rem(40);
}
textarea::-webkit-input-placeholder {
color: #999999;
}
}
}
.ui-save-btn {
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
position: fixed;
bottom: 6vh;
left: 50%;
transform: translateX(-50%);
}
.ui-tips-box {
position: relative;
width: px2rem(520);
padding: px2rem(92) px2rem(40) px2rem(50) px2rem(40);
background: #ffffff;
border-radius: px2rem(24);
.ui-tips-text {
padding: 0 px2rem(60);
}
.ui-btn-box {
padding: 0 px2rem(36);
.ui-btn,
.ui-btn-v2 {
width: px2rem(192);
height: px2rem(68);
border-radius: px2rem(34);
margin-top: px2rem(52);
}
.ui-btn {
border: px2rem(2) solid #d8d8d8;
}
.ui-btn-v2 {
background: #5ac7a0;
border-radius: px2rem(34);
border: px2rem(2) solid #5ac7a0;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
<template>
<div ref="scrollDistance" class="ui-appGroupOrders" @scroll="handleScroll">
<div class="ui-top-box">
<div class="ui-search-box">
<van-field v-model="searchValue" class="ui-search-input font_28 color3 f-fcc" placeholder="搜索订单" />
<img class="ui-search-icon" src="https://image.fulllinkai.com/202304/07/38e32f1948ad951b1f66b404380648f3.png" alt="" />
</div>
</div>
<div class="ui-placeholder"></div>
<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-empty-data-icon" src="https://image.fulllinkai.com/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div class="color6 font_30 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">
<div class="f-fbc">
<div class="ui-name font_30 color3 ellipsis_1">下单人昵称{{ item.name }}</div>
<div class="ui-btn f-fcc font_26 colorTheme" @click="(showTips = true), (orderId = item.id)">绑定此订单</div>
</div>
<div class="ui-line-between"></div>
<div class="ui-data color6 font_28">
联系电话<span class="color3">{{ item.mobile }}</span>
</div>
<div class="ui-data color6 font_28">
订单金额<span class="colorPrice">¥{{ item.price }}</span>
</div>
<div class="ui-data color6 font_28">
订单编号<span class="color3">{{ item.trade_no }}</span>
</div>
<div class="ui-data color6 font_28">
订单描述<span class="color3">{{ item.desc }}</span>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
<div class="ui-noBind-btn font_32 colorF f-fcc" @click="unbind">暂不绑定订单</div>
<img class="ui-add-order" src="https://image.fulllinkai.com/202307/04/c124672b8eae0ecf8ce62a94c30c69df.png" alt="" @click="jumpPath" />
<van-popup v-model:show="showTips" round :close-on-click-overlay="false" :lock-scroll="true" :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 text-center">确定绑定此订单吗</div>
<div class="ui-btn-box f-fbc">
<div class="ui-btn font_32 f-fcc color6" @click="showTips = false">取消</div>
<div class="ui-btn-v2 font_32 f-fcc colorF" @click="bindOrder">确认</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { watch, ref, onMounted, onActivated } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppGroupOrders' });
const userStore = useUserStore(); // pinia
const loadingState = ref<any>(false); //
const throttle = ref(true);
const orderId = ref<any>(''); // ID
const showTips = ref(false); //
const timer = ref<any>(null);
const searchValue = ref<any>('');
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 unbind = () => {
let data = {
chat_id: userStore.chatId,
};
if (throttle.value) {
throttle.value = false;
weChat({ url: `h5/unbind/work/group/orders`, data, method: 'post' })
.then(() => {
userStore.weChatBindState = '3';
throttle.value = true;
router.replace({
name: 'personalCenter',
});
router.go(-1);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const getList = () => {
weChat({ url: `/h5/no/group/orders?page=${page.value}&keyword=${searchValue.value}`, method: 'get' })
.then((res) => {
const result = res.data;
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;
}
loadingState.value = true;
page.value++;
})
.catch((err) => {
console.log(err);
});
};
//
const bindOrder = () => {
let data = {
chat_id: userStore.chatId,
};
if (throttle.value) {
throttle.value = false;
weChat({ url: `h5/bind/work/group/orders/${orderId.value}`, data, method: 'post' })
.then(() => {
showToast('绑定成功');
userStore.weChatBindState = '2';
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'personalCenter',
});
router.go(-1);
}, 1000);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const initList = () => {
page.value = 1;
getList();
};
//
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
const jumpPath = () => {
router.push({
name: 'appAddOrder',
});
};
//
watch(searchValue, () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
initList();
}, 800);
});
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
scrollDistance.value.scrollTop = scrollValue.value;
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appGroupOrders {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-top-box {
width: 100vw;
position: fixed;
top: 0;
left: 0;
z-index: 99;
background: #f8f8f8;
.ui-search-box {
max-width: 100%;
padding: px2rem(30);
height: px2rem(80);
background: #f8f8f8;
position: relative;
.ui-search-input {
width: 100%;
padding: 0 px2rem(70);
height: px2rem(80);
background: #ffffff;
border-radius: px2rem(40);
margin: 0 auto;
}
::v-deep(input::-webkit-input-placeholder) {
font-size: px2rem(28);
color: #999999;
}
.ui-search-icon {
width: px2rem(28);
height: px2rem(28);
position: absolute;
top: px2rem(56);
left: px2rem(60);
}
}
}
.ui-placeholder {
width: 100vw;
height: px2rem(140);
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 25vh auto 0 auto;
}
.ui-list-box {
padding-bottom: 16vh;
.ui-list-item {
margin: 0 px2rem(30) px2rem(30) px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(30);
.ui-name {
max-width: px2rem(300);
}
.ui-btn {
width: px2rem(164);
height: px2rem(60);
border-radius: px2rem(30);
border: px2rem(2) solid #91dcc2;
}
.ui-line-between {
width: 100%;
height: px2rem(2);
background: #f5f5f5;
margin: px2rem(20) 0 px2rem(4) 0;
}
.ui-data {
padding-top: px2rem(20);
}
}
}
.ui-tips-box {
position: relative;
width: px2rem(520);
padding: px2rem(92) px2rem(40) px2rem(50) px2rem(40);
background: #ffffff;
border-radius: px2rem(24);
.ui-btn-box {
padding: 0 px2rem(36);
.ui-btn,
.ui-btn-v2 {
width: px2rem(192);
height: px2rem(68);
border-radius: px2rem(34);
margin-top: px2rem(80);
}
.ui-btn {
border: px2rem(2) solid #d8d8d8;
}
.ui-btn-v2 {
background: #5ac7a0;
border-radius: px2rem(34);
border: px2rem(2) solid #5ac7a0;
}
}
}
.ui-noBind-btn {
position: fixed;
bottom: 8vh;
left: 50%;
transform: translateX(-50%);
width: px2rem(480);
height: px2rem(76);
background: #5ac7a0;
border-radius: px2rem(40);
}
.ui-add-order {
position: fixed;
bottom: 16vh;
right: px2rem(10);
width: px2rem(140);
height: px2rem(140);
}
</style>

View File

@ -0,0 +1,239 @@
<template>
<div ref="scrollDistance" class="ui-appMeasurementRecord" @scroll="handleScroll">
<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">
<div v-if="loadingState" class="text-center">
<img class="ui-no-data-icon" src="https://image.fulllinkai.com/202212/01/f06371b76ab686752486c8b99fa7669a.png" alt="" />
<div class="color6 font_30">还没有测量数据哦~</div>
</div>
</div>
<div v-else>
<div v-for="(item, index) in list" :key="index" class="ui-record-list">
<div class="font_28 color6 ui-record-date">{{ item.yearMonth }}</div>
<div v-for="(itemSub, indexSub) in item.subList" :key="indexSub" style="background: #ffffff" @click="jumpPath('appMeasuringRecordDetail', itemSub)">
<div class="ui-record-item">
<div class="ui-content-box {{itemSub.isTouchMove ? 'shopCon-active' : ''}}">
<div class="f-fbc ui-content">
<div>
<div class="color3 font_30">{{ itemSub.score }}{{ itemSub.unit }}</div>
<div class="color6 font_24 ui-pt-6">体脂秤类型{{ itemSub.type == 1 ? '新版秤' : '旧版秤' }}</div>
</div>
<div class="f-fcr">
<div class="font_30 color9">{{ itemSub.dayHour }}</div>
<img class="ui-record-item-icon" src="https://image.fulllinkai.com/202211/22/8cf3979193db3796fe4ca3b3b9be00f7.png" alt="" />
</div>
</div>
</div>
</div>
<div v-if="indexSub + 1 != item.subList.length" class="ui-record-item-line"></div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onActivated, onMounted, ref } from 'vue';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppMeasurementRecord' });
const userStore = useUserStore() as any;
const loadingState = ref(false);
const cacheList = 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 scrollValue = ref(0); //
const scrollDistance = ref<any>(null);
//
const getList = () => {
weChat({ url: `/h5/get/fat/logs?page=${page.value}&chat_id=${userStore.chatId}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
if (cacheList.value.length === 0 || page.value === 1) {
cacheList.value = result.data;
setTimeout(() => {
list.value = transition(cacheList.value);
}, 100);
} else if (cacheList.value.length >= 15) {
result.data.forEach((item) => {
cacheList.value.push(item);
});
setTimeout(() => {
list.value = transition(cacheList.value);
}, 100);
}
refreshing.value = false;
loading.value = false;
if (cacheList.value.length < 15 || result.data.length < 15) {
finished.value = true;
noMore.value = true;
}
page.value++;
setTimeout(() => {
loadingState.value = true;
}, 200);
})
.catch((err) => {
console.log(err);
});
};
//
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
//
const transition = (list) => {
let Arr = [] as any;
list.forEach((e) => {
let index = -1;
let state = Arr.some((item, i) => {
if (e.yearMonth == item.yearMonth) {
index = i;
return true;
}
});
if (!state) {
Arr.push({ yearMonth: e.yearMonth, subList: [{ dayHour: e.dayHour, e_name: e.fat_name || e.e_name, id: e.id, isTouchMove: false, score: e.fat_data || e.score, created_at: e.created_at, unit: e.unit, yearMonth: e.yearMonth, type: e.type }] });
} else {
Arr[index].subList.push({ dayHour: e.dayHour, e_name: e.fat_name || e.e_name, id: e.id, isTouchMove: false, score: e.fat_data || e.score, created_at: e.created_at, unit: e.unit, yearMonth: e.yearMonth, type: e.type });
}
});
return Arr;
};
const jumpPath = (url, e) => {
router.push({
name: url,
query: { tested_at: e.created_at, type: e.type, id: e.id },
});
};
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
scrollDistance.value.scrollTop = scrollValue.value;
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appMeasurementRecord {
background: #f6f7f8;
overflow-y: auto;
height: 100vh;
}
.ui-tabBar-box {
position: fixed;
top: 0;
left: 0;
z-index: 8;
width: 100vw;
height: px2rem(90);
background: #f8f8f8;
.ui-tabBar-list {
padding: px2rem(24) px2rem(180) px2rem(0) px2rem(180);
}
.ui-tabBar-line {
position: absolute;
bottom: px2rem(2);
width: 100%;
height: px2rem(10);
background: linear-gradient(90deg, rgba(95, 226, 175, 0.1) 0%, #41d5a9 100%);
border-radius: px2rem(5);
}
}
.ui-placeholder {
width: 100vw;
height: px2rem(70);
}
.ui-no-data-icon {
width: px2rem(500);
height: px2rem(360);
border-radius: 50%;
margin-top: 20vh;
}
.ui-record-list {
.ui-record-date {
padding: px2rem(20) px2rem(30);
}
.ui-record-item {
display: flex;
width: 100%;
position: relative;
.ui-content-box {
width: 100%;
padding: px2rem(24) 0 px2rem(24) px2rem(30);
margin-right: 0;
transition: all 0.4s;
display: flex;
position: relative;
left: 0;
.ui-content {
width: 100%;
.ui-record-item-icon {
width: px2rem(16);
display: block;
margin: 0 px2rem(30);
}
}
}
.ui-del {
background-color: #f1013d;
width: px2rem(180);
height: 100%;
color: #fff;
position: absolute;
z-index: 222;
right: px2rem(-180);
top: 0;
transition: all 0.4s;
}
}
.shopCon-active {
left: px2rem(-180);
}
.shopDel-active {
right: 0 !important;
}
.ui-record-item-line {
width: 100%;
height: px2rem(2);
background: #f7f7f7;
margin-left: px2rem(30);
}
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<div class="ui-appMeasuringRecordDetail">
<div class="f-fbc ui-date">
<div class="color6 font_30">{{ showTime }}</div>
<div class="colorTheme font_26">体脂秤类型{{ type == 1 ? '新版秤' : '旧版秤' }}</div>
</div>
<div v-if="type == 1">
<div class="ui-indicators-box">
<div v-for="(item, index) in list" :key="index">
<div @click="switchIndicators(index)">
<div class="ui-indicators-item f-fbc">
<div class="f-fcl">
<img class="ui-indicators-icon" :src="item.icon" alt="" />
<div class="color333 font_28 ui-indicators-name">{{ item.fat_name }}</div>
<div v-if="item.fat_name != '体型'" class="font_32 color333 ui-indicators-value text-center bold"
>{{ item.fat_data }}<span class="font_20">{{ item.unit }}</span></div
>
</div>
<div class="f-fcr ui-indicators-r-icon">
<div v-if="item.level" class="ui-indicators-item-icon font_20 colorF f-fcc" :style="{ background: item.level.color }">{{ item.level.name }}</div>
<img class="ui-triangle-icon" :class="indicatorsIndex == index ? 'ui-triangle-active' : ''" src="https://image.fulllinkai.com/202211/28/6c196ecbe7899a15320fd6c3ab48b64b.png" alt="" />
</div>
</div>
<div v-if="item.control" class="font_26 color999 ui-indicators-sub-name">{{ item.control }}</div>
<div v-if="indicatorsIndex == index" class="ui-indicators-view color666 font_24">
<div v-if="item.boundaries.length != 0 && item.levels.length != 0" class="f-fcl ui-relative">
<div v-for="(itemV2, indexV2) in item.levels" :key="indexV2" class="ui-indicators-data-box text-center" :style="{ width: 100 / item.levels.length + '%' }">
<div v-if="item.boundaries.length != 0">
<div v-if="indexV2 == 0" class="color333 font_20">&nbsp;&nbsp;</div>
<div v-else class="ui-indicators-num color333 font_20">{{ item.boundaries[indexV2 - 1] }}</div>
</div>
<div class="ui-indicators-color" :class="[indexV2 == 0 ? 'ui-indicators-radius' : '', indexV2 + 1 == item.levels.length ? 'ui-indicators-radiusV2' : '']" :style="{ background: itemV2.color }"></div>
<div class="color666 font_22 ui-indicators-text" :class="item.level.name == itemV2.name ? 'bold' : ''" :style="{ color: item.level.name == itemV2.name ? item.level.color : '' }">{{ itemV2.name }}</div>
</div>
</div>
<div v-if="item.level" class="ui-indicators-desc color999 font_24">{{ item.level.desc || '' }}</div>
</div>
<div class="ui-indicators-line" :class="indicatorsIndex == index ? 'ui-indicators-active-line' : ''"></div>
</div>
</div>
</div>
</div>
<div v-else class="ui-indicators-box">
<div v-for="(item, index) in list" :key="index">
<div class="ui-indicators-item f-fbc">
<div class="f-fcl">
<img class="ui-indicators-icon" :src="item.icon" alt="" />
<div class="color3 font_28 ui-indicators-name">{{ item.fat_name }}</div>
<div class="font_32 color3 ui-indicators-value text-center bold">
{{ item.fat_data }}
<span class="font_20">{{ item.unit || '' }}</span>
</div>
</div>
<div class="f-fcr ui-indicators-r-icon">
<div v-if="item.fat_state" class="ui-indicators-item-icon font_20 colorF f-fcc" :style="{ background: item.color }">{{ item.fat_state }}</div>
</div>
</div>
<div v-if="item.control" class="font_26 color9 ui-indicators-sub-name">{{ item.control }}</div>
<div v-if="index + 1 != list.length" class="ui-indicators-line"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import router from '@/router';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppMeasuringRecordDetail' });
const userStore = useUserStore() as any;
const type = ref<any>('');
const time = ref<any>('');
const id = ref<any>('');
const indicatorsIndex = ref<any>(null);
const showTime = ref<any>('');
const list = ref<any[]>([]);
//
const getList = () => {
weChat({ url: `/h5/get/fat/log/detail?chat_id=${userStore.chatId}&id=${id.value}`, hideLoading: true, method: 'get' })
.then((res) => {
let result = res.data;
list.value = result;
if (type.value == 0) {
if (list.value && list.value.length > 0) {
list.value.forEach((item) => {
if (item.fat_name === '体重' || item.fat_name === 'BMI' || item.fat_name === '脂肪占比' || item.fat_name === '脂肪控制' || item.fat_name === '体脂重量' || item.fat_name === '肥胖度') {
if (item.fat_state === '偏瘦') {
item.color = '#00C1E4';
} else if (item.fat_state === '标准') {
item.color = '#A7CB40';
} else if (item.fat_state === '偏胖') {
item.color = '#FBC13D';
} else {
item.color = '#F74142';
}
} else if (item.fat_name === '体脂率' || item.fat_name === '蛋白质' || item.fat_name === '蛋白质率') {
if (item.fat_state === '偏低') {
item.color = '#00C1E4';
} else if (item.fat_state === '标准') {
item.color = '#A7CB40';
} else if (item.fat_state === '偏高') {
item.color = '#FBC13D';
} else {
item.color = '#F74142';
}
} else if (item.fat_name === '去脂体重') {
if (item.fat_state === '偏弱') {
item.color = '#00C1E4';
} else {
item.color = '#A7CB40';
}
} else if (item.fat_name === '肌肉占比' || item.fat_name === '肌肉重量' || item.fat_name === '体内水分' || item.fat_name === '水含量' || item.fat_name === '骨量') {
if (item.fat_state === '不足') {
item.color = '#00C1E4';
} else if (item.fat_state === '标准') {
item.color = '#A7CB40';
} else {
item.color = '#A7CB40';
}
} else if (item.fat_name === '基础代谢') {
if (item.fat_state === '偏低') {
item.color = '#00C1E4';
} else {
item.color = '#A7CB40';
}
} else if (item.fat_name === '内脏脂肪') {
if (item.fat_state === '标准') {
item.color = '#A7CB40';
} else if (item.fat_state === '警惕') {
item.color = '#FBC13D';
} else {
item.color = '#F74142';
}
} else if (item.fat_name === '身体年龄') {
if (item.fat_state === '偏大') {
item.color = '#FBC13D';
} else if (item.fat_state === '标准') {
item.color = '#A7CB40';
} else {
item.color = '#A7CB40';
}
} else {
item.color = '#A7CB40';
}
});
}
}
})
.catch((err) => {
console.log(err);
});
};
const switchIndicators = (index) => {
console.log(index);
if (indicatorsIndex.value === index) {
indicatorsIndex.value = null;
return;
}
indicatorsIndex.value = index;
};
onMounted(() => {
// userStore.chatId = 1;
let route = router.currentRoute.value.query;
type.value = route.type;
id.value = route.id;
if (route.tested_at) {
time.value = route.tested_at;
showTime.value = `${time.value.split(/[- ' ' :]/)[0]}${time.value.split(/[- ' ' :]/)[1]}${time.value.split(/[- ' ' :]/)[2]}` + ` ${time.value.split(/[- ' ' :]/)[3]}:${time.value.split(/[- ' ' :]/)[4]}`;
getList();
}
});
</script>
<style lang="scss" scoped>
.ui-appMeasuringRecordDetail {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-date {
background: #f4f4f4;
padding: px2rem(20) px2rem(30);
}
.ui-indicators-box {
position: relative;
padding-bottom: px2rem(140);
.ui-indicators-item {
padding: px2rem(20) px2rem(30);
.ui-indicators-r-icon {
//margin-bottom: px2rem(-10);
.ui-indicators-item-icon {
padding: 0 px2rem(18);
height: px2rem(36);
border-radius: px2rem(6);
margin-right: px2rem(16);
}
.ui-triangle-icon {
width: px2rem(28);
height: px2rem(16);
display: block;
transition: all 0.4s;
}
.ui-triangle-active {
transform: rotate(-180deg);
}
}
.ui-indicators-name {
width: px2rem(270);
margin-left: px2rem(20);
white-space: nowrap;
overflow: hidden;
}
.ui-indicators-value {
width: px2rem(140);
}
.ui-indicators-icon {
width: px2rem(44);
height: px2rem(44);
display: block;
}
}
.ui-indicators-view {
//width: 100%;
padding: px2rem(10) px2rem(18) px2rem(20) px2rem(18);
position: relative;
.ui-indicators-data-box {
margin: px2rem(10) 0 px2rem(16) 0;
position: relative;
}
.ui-indicators-color {
width: 100%;
height: px2rem(10);
margin-bottom: px2rem(4);
}
.ui-indicators-text {
line-height: initial;
}
.ui-indicators-radius {
border-radius: px2rem(20) 0 0 px2rem(20);
}
.ui-indicators-radiusV2 {
border-radius: 0 px2rem(20) px2rem(20) 0;
}
.ui-indicators-num {
position: relative;
right: 50%;
}
.ui-indicators-desc {
word-break: break-all;
padding: 0 px2rem(30);
}
}
.ui-indicators-sub-name {
margin-left: px2rem(94);
margin-bottom: px2rem(16);
}
.ui-indicators-line,
.ui-indicators-active-line {
height: px2rem(2);
margin: 0 px2rem(8) px2rem(6) px2rem(94);
background: #f5f5f5;
}
.ui-indicators-active-line {
margin: 0 px2rem(20) px2rem(6) px2rem(20);
}
.ui-view-depth-report {
width: px2rem(600);
height: px2rem(82);
border-radius: px2rem(50);
margin: px2rem(60) auto 0 auto;
background: #5ac7a0;
}
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div class="ui-appQuestionnaire">
<img class="ui-top-bg" src="https://images.health.ufutx.com/202412/12/4148e5295233d207e97ef6bfa7c92f6f.jpg" alt="" />
<div class="ui-data-fill-in-box">
<div v-for="(item, index) in list" :key="index" :class="index + 1 != list.length ? 'ui-data-fill-in-item' : ''">
<div class="font_30 color3 bold">{{ index + 1 }}.{{ item.title }}</div>
<div v-if="item.choice == 0">
<van-radio-group v-model="answerList[index].reply[0]">
<van-radio v-for="(itemV2, indexV2) in item.answer" :key="indexV2" :name="itemV2" class="ui-relative ui-radio-box" :disabled="readonly" @click="changeRadio(index)">
<div class="font_28 color6">{{ itemV2 }}</div>
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
<div v-if="item.other_answer_title">
<van-radio :name="item.other_answer_title" class="ui-relative ui-radio-box" :disabled="readonly">
<div class="font_28 color6">{{ item.other_answer_title }}</div>
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
</div>
</van-radio-group>
<div v-if="item.other_answer_title && answerList[index].reply[0] == item.other_answer_title">
<van-field v-model="answerList[index].other_answer_text" :readonly="readonly" class="ui-data-input font_28 color3" rows="5" type="textarea" :placeholder="`请填写${item.other_answer_title}内容`" autosize />
</div>
</div>
<div v-if="item.choice == 1">
<van-checkbox-group v-model="answerList[index].reply" @change="changeCheckBox(index, item.other_answer_title)">
<van-checkbox v-for="(itemV2, indexV2) in item.answer" :key="indexV2" :name="itemV2" :disabled="readonly" class="ui-radio-box ui-relative">
<div class="font_28 color6">{{ itemV2 }}</div>
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-checkbox>
<div v-if="item.other_answer_title">
<van-checkbox :name="item.other_answer_title" :disabled="readonly" class="ui-radio-box ui-relative">
<div class="font_28 color6">{{ item.other_answer_title }}</div>
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-checkbox>
</div>
</van-checkbox-group>
<div v-if="answerList[index].reply.includes(item.other_answer_title)">
<van-field v-model="answerList[index].other_answer_text" :readonly="readonly" class="ui-data-input font_28 color3" rows="5" type="textarea" :placeholder="`请填写${item.other_answer_title}内容`" autosize />
</div>
</div>
<div v-if="item.choice == 2">
<van-field v-model="answerList[index].reply[0]" :readonly="readonly" class="ui-data-input font_28 color3" rows="5" type="textarea" :placeholder="`请填写${item.title}`" autosize />
</div>
<div v-if="item.choice == 3" style="line-height: 0">
<van-rate v-model="answerList[index].reply[0]" :readonly="readonly" class="ui-pt-20" gutter="10" :size="21" color="#ffc629" void-icon="star" void-color="#e8e8e8" />
</div>
</div>
<div v-if="!readonly" class="ui-btn f-fcc font_32 colorF" @click="submit">提交</div>
</div>
<van-popup v-model:show="showTips" round :close-on-click-overlay="false" :lock-scroll="true" :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 text-center">感谢您的填写</div>
<div class="ui-tips-btn-box f-fbc">
<div class="ui-tips-btn font_32 f-fcc color6" @click="showTips = false">取消</div>
<div class="ui-tips-btn-v2 font_32 f-fcc colorTheme" @click="close">返回聊天</div>
</div>
</div>
</van-popup>
<img class="ui-close-icon" src="https://image.fulllinkai.com/202306/21/3cbfe438ad3792436d26443920878ff4.png" alt="" @click="close" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
defineOptions({ name: 'AppQuestionnaire' });
const chatId = ref<any>('');
const loading = ref(false);
const surveyId = ref<any>('');
const role = ref<any>('');
const isIOS = ref<any>(true);
const readonly = ref<any>(false);
const showTips = ref<any>(false);
const throttle = ref(true);
const list = ref<any[]>([]);
const answerList = ref<any[]>([]);
const activeIcon = ref('https://image.fulllinkai.com/202306/07/c270666b969b9e844496f05b43e3a29d.png');
const inactiveIcon = ref('https://image.fulllinkai.com/202306/07/3cb0e29392208a1a28363ff25ca14169.png');
//
const getDetail = () => {
weChat({ url: `h5/order/surveys/${surveyId.value}?chat_id=${chatId.value}&is_im=1`, method: 'get' })
.then((res) => {
let result = res.data;
list.value = result.content;
if (list.value && list.value[0].reply) {
readonly.value = true;
}
if (role.value != 0) {
showToast('该问卷暂不支持工作人员填写');
readonly.value = true;
}
for (let i = 0; i < list.value.length; i++) {
answerList.value.push({
title: list.value[i].title,
choice: list.value[i].choice,
answer: list.value[i].answer,
other_answer_title: list.value[i].other_answer_title || '',
other_answer_text: list.value[i].other_answer_text || '',
reply: list.value[i].reply ? list.value[i].reply : [],
});
}
if (list.value && list.value.length > 0) {
setTimeout(() => {
loading.value = true;
});
}
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
const submit = () => {
let state = false;
let data = {
chat_id: chatId.value,
content: answerList.value,
is_im: 1,
};
if (!loading.value) {
return;
}
answerList.value.forEach((item) => {
if (!item.reply || !item.reply[0]) {
state = true;
}
//
if (item.other_answer_title && !item.other_answer_text && item.reply.includes(item.other_answer_title)) {
state = true;
console.log(item.other_answer_title, !item.reply.includes(item.other_answer_title), '77');
}
});
if (state) {
showToast('请完善调查问卷后提交');
return;
}
if (throttle.value) {
throttle.value = false;
}
weChat({ url: `h5/order/survey/${surveyId.value}`, data, method: 'post' })
.then(() => {
showTips.value = true;
readonly.value = true;
setTimeout(() => {
throttle.value = true;
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
};
//
const changeRadio = (index) => {
answerList.value[index].other_answer_text = '';
};
//
const changeCheckBox = (index, e) => {
if (!answerList.value[index].reply.includes(e)) {
answerList.value[index].other_answer_text = '';
}
};
const close = () => {
// ios app
if (isIOS.value) {
window.webkit.messageHandlers.goBack.postMessage(null);
} else {
// app
window.webAppInterface.goBack();
}
};
onMounted(() => {
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;
}
let route = router.currentRoute.value.query;
if (route.chat_id) {
chatId.value = route.chat_id;
}
role.value = route.role;
if (route.survey_id) {
surveyId.value = route.survey_id;
getDetail();
}
});
</script>
<style lang="scss" scoped>
.ui-appQuestionnaire {
background: #005cf9;
overflow-y: auto;
min-height: 100vh;
}
.ui-top-bg {
width: 100vw;
height: px2rem(1176);
}
.ui-data-fill-in-box {
position: relative;
z-index: 1;
margin: px2rem(-766) px2rem(30) px2rem(120) px2rem(30);
padding: px2rem(40) px2rem(30) px2rem(50) px2rem(30);
background: #ffffff;
border-radius: px2rem(24);
.ui-data-fill-in-item {
padding-bottom: px2rem(80);
}
.ui-data-input {
margin-top: px2rem(20);
padding: px2rem(16);
background: #f6f9ff;
border-radius: px2rem(16);
}
.ui-radio-box {
padding-top: px2rem(20);
.img-icon {
position: absolute;
top: px2rem(26);
width: px2rem(28);
height: px2rem(28);
}
::v-deep(.van-radio__label),
::v-deep(.van-checkbox__label) {
margin-left: px2rem(40);
}
}
}
.ui-btn {
width: px2rem(292);
height: px2rem(76);
background: linear-gradient(90deg, #7bafff 0%, #556cfd 100%);
border-radius: px2rem(38);
margin: px2rem(80) auto 0 auto;
}
.ui-close-icon {
width: px2rem(160);
height: px2rem(160);
position: fixed;
right: px2rem(-10);
bottom: 10vh;
z-index: 999;
}
.ui-tips-box {
position: relative;
width: px2rem(600);
padding-top: px2rem(62);
background: #ffffff;
border-radius: px2rem(24);
.ui-tips-btn-box {
.ui-tips-btn,
.ui-tips-btn-v2 {
border-top: px2rem(2) solid #f5f5f5;
width: 49.9%;
height: px2rem(90);
margin-top: px2rem(80);
}
.ui-tips-btn-v2 {
border-left: px2rem(2) solid #f5f5f5;
}
}
}
</style>

View File

@ -0,0 +1,279 @@
<template>
<div class="ui-appRemarkDetail">
<div class="ui-remark-input-box">
<div class="ui-pb-28">
<div class="font_30 color3 bold ui-pb-20">备注类型</div>
<van-field v-model="selectType" readonly class="ui-user-input f-fcc font_28 color3 bold" placeholder="请选择备注类型" @click="showType = true" />
</div>
<div class="font_30 color3 bold">{{ commentId ? '编辑' : '填写' }}备注</div>
<div class="ui-upload-box">
<div v-for="(item, index) in pics" :key="index" class="ui-upload-icon-box flo_l">
<img class="ui-upload-icon" :src="item" mode="aspectFill" alt="" @click="ImagePreview(pics, index)" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" mode="widthFix" alt="" @click="clearPic(index)" />
</div>
<div v-if="pics.length < 9">
<uploadPicture :multiple="true" :max-count="9" @on-success="onSuccess" @click="takePhone">
<div class="ui-upload">
<img class="ui-upload-icon flo_l" src="https://image.fulllinkai.com/202301/07/6c9bc853bed42c9871f56156d1b8f31c.png" alt="" />
</div>
</uploadPicture>
</div>
</div>
<div class="ui-remark-data">
<van-field v-model="remark" class="ui-remark-input font_30 color3" rows="8" type="textarea" maxlength="500" show-word-limit placeholder="请您填写备注内容,记录每一位用户" autosize />
</div>
<div class="ui-btn font_32 colorF f-fcc" @click="save">保存</div>
</div>
<van-popup v-model:show="showType" round position="bottom" :duration="0.5">
<van-picker v-model="TypeValues" title="选择备注类型" :columns="columns" @confirm="onConfirm" @cancel="showType = false" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showImagePreview, showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
defineOptions({ name: 'AppRemarkDetail' });
const throttle = ref(true);
const pics = ref<any[]>([]);
const chatId = ref<any>('');
const commentId = ref<any>('');
const remark = ref<any>('');
const showType = ref(false);
const selectType = ref<any>('');
const selectTypeId = ref<any>('');
const columns = ref<any[]>([]); //
const TypeValues = ref<any[]>([]); //
//
const getDetail = () => {
weChat({ url: `h5/order/comments/${commentId.value}?is_im=1`, method: 'get' })
.then((res) => {
let result = res.data;
remark.value = result.comment;
pics.value = result.images || [];
if (result.type_id) {
TypeValues.value = [result.type_id * 1];
selectType.value = result.type_name;
selectTypeId.value = result.type_id;
}
})
.catch((err) => {
console.log(err);
});
};
//
const getType = () => {
weChat({ url: `h5/get/comment/type?is_im=1`, method: 'get' })
.then((res) => {
let result = res.data;
if (result && result.length > 0) {
columns.value = [];
result.forEach((item) => {
columns.value.push({
text: item.name,
value: item.id,
});
});
}
if (commentId.value) {
getDetail();
}
})
.catch((err) => {
console.log(err);
});
};
const save = () => {
if (throttle.value) {
let data = {
images: pics.value,
comment: remark.value,
chat_id: chatId.value,
type_id: selectTypeId.value,
is_im: 1,
};
if (!selectTypeId.value) {
showToast('请选择备注类型');
return;
}
if (!remark.value) {
showToast('请填写备注内容');
return;
}
throttle.value = false;
if (commentId.value) {
weChat({ url: `h5/order/comments/${commentId.value}`, data, method: 'put' })
.then(() => {
showToast('编辑成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'appRemarks',
query: { state: 1 },
});
router.go(-1);
}, 1000);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
} else {
weChat({ url: `h5/comment/orders`, data, method: 'post' })
.then(() => {
showToast('保存成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'appRemarks',
query: { state: 1 },
});
router.go(-1);
}, 1000);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
}
};
//
const onSuccess = (val) => {
if (pics.value.length < 9) {
pics.value.push(val);
}
};
//
const clearPic = (index) => {
pics.value.splice(index, 1);
};
window.getAndroidPhone = (type, e) => {
if (e && e.length > 0) {
e.forEach((item) => {
if (pics.value && pics.value.length < 9) {
pics.value.push(item);
}
});
}
};
const takePhone = () => {
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
// app
window.webAppInterface.takePhoto('multiple', 9);
}
};
const ImagePreview = (e, index) => {
showImagePreview({
images: e,
showIndex: false,
startPosition: index,
loop: false,
});
};
//
const onConfirm = ({ selectedOptions }) => {
showType.value = false;
selectType.value = `${selectedOptions[0].text}`;
selectTypeId.value = `${selectedOptions[0].value}`;
console.log(selectType.value);
};
onMounted(() => {
let route = router.currentRoute.value.query;
if (route.chatId) {
chatId.value = route.chatId;
}
if (route.comment_id) {
commentId.value = route.comment_id;
document.title = '编辑备注';
} else {
document.title = '填写备注';
}
getType();
});
</script>
<style lang="scss" scoped>
.ui-appRemarkDetail {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-remark-input-box {
padding: px2rem(30);
.ui-user-input {
box-sizing: border-box;
border: px2rem(2) solid #f5f5f5;
border-radius: px2rem(16);
padding: px2rem(16) px2rem(30);
}
}
.ui-upload-box {
overflow: hidden;
border-radius: px2rem(16);
.ui-upload-icon-box {
position: relative;
.ui-upload-clear-icon {
position: absolute;
right: px2rem(30);
top: px2rem(30);
width: px2rem(36);
height: px2rem(36);
}
}
.ui-upload-icon {
width: px2rem(200);
height: px2rem(200);
display: block;
border-radius: px2rem(16);
margin-right: px2rem(20);
margin-top: px2rem(20);
object-fit: cover;
object-position: center;
}
}
.ui-remark-data {
background: #ffffff;
margin-top: px2rem(20);
border-radius: px2rem(26);
.ui-remark-input {
padding: px2rem(20) px2rem(24);
border-radius: px2rem(30);
line-height: px2rem(40);
}
textarea::-webkit-input-placeholder {
color: #999999;
}
}
.ui-btn {
width: px2rem(480);
height: px2rem(76);
background: #5ac7a0;
border-radius: px2rem(40);
margin: px2rem(150) auto;
}
</style>

View File

@ -0,0 +1,191 @@
<template>
<div ref="scrollDistance" class="ui-appRemarks" @scroll="handleScroll">
<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://image.fulllinkai.com/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div class="color6 font_30 text-center">暂无备注</div>
</div>
<div v-else>
<div class="ui-remarks-box">
<div v-for="(item, index) in list" :key="index" class="ui-remarks-item">
<div v-if="item.images && item.images.length > 0" class="f-fcl ui-remarks-pic-box">
<img v-for="(itemV2, indexV2) in item.images" :key="indexV2" class="ui-remarks-pic" :src="itemV2" alt="" @click="ImagePreview(item.images, indexV2)" />
</div>
<div class="font_30 bold color3">{{ item.comment }}</div>
<div class="ui-line-between"></div>
<div class="font_30 color6 ui-pb-8">
备注人<span class="color3">{{ item.user ? item.user.name || '--' : '--' }}</span>
</div>
<div class="font_30 color6 ui-pb-8">
备注类型<span class="color3">{{ item.type_name || '--' }}</span>
</div>
<div class="f-fbc">
<div class="color6 font_30">创建时间{{ item.created_at }}</div>
<div class="font_30 colorTheme" @click="jumpPath(`appRemarkDetail`, item.id)">编辑</div>
</div>
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
<div class="ui-btn font_32 colorF f-fcc" @click="jumpPath(`appRemarkDetail`, '')">填写备注</div>
</div>
</template>
<script setup lang="ts">
import { onActivated, onMounted, ref } from 'vue';
import { showImagePreview } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppRemarks' });
const userStore = useUserStore() as 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 scrollValue = ref(0); //
const scrollDistance = ref<any>(null);
//
const getList = () => {
weChat({ url: `/h5/order/comments?page=${page.value}&chat_id=${userStore.chatId}&status=${userStore.weChatBindState}&is_im=1`, method: 'get' })
.then((res) => {
const result = res.data;
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) => {
console.log(err);
});
};
//
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
const ImagePreview = (e, index) => {
showImagePreview({
images: e,
showIndex: false,
startPosition: index,
loop: false,
});
};
const jumpPath = (url, id) => {
router.push({
name: url,
query: { chatId: userStore.chatId, comment_id: id },
});
};
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
let route = router.currentRoute.value.query;
//
if (route.state) {
onRefresh();
} else {
//
scrollDistance.value.scrollTop = scrollValue.value;
}
});
onMounted(() => {
let route = router.currentRoute.value.query;
if (route.chat_id) {
userStore.chatId = route.chat_id;
}
});
</script>
<style lang="scss" scoped>
.ui-appRemarks {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 25vh auto 0 auto;
}
.ui-remarks-box {
padding-bottom: 16vh;
.ui-remarks-item {
margin: px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(30);
.ui-remarks-pic-box {
flex-flow: wrap;
.ui-remarks-pic {
width: px2rem(186);
height: px2rem(186);
border: px2rem(2) solid #f8f8f8;
border-radius: px2rem(16);
display: block;
object-fit: cover;
object-position: center;
margin-right: px2rem(20);
margin-bottom: px2rem(20);
}
}
.ui-remarks-pic-box::after {
width: px2rem(140);
content: '';
}
.ui-line-between {
width: 100%;
height: px2rem(2);
background: #f5f5f5;
margin: px2rem(20) 0 px2rem(18) 0;
}
}
}
.ui-btn {
position: fixed;
bottom: 8vh;
left: 50%;
transform: translateX(-50%);
width: px2rem(480);
height: px2rem(76);
background: #5ac7a0;
border-radius: px2rem(40);
}
</style>

View File

@ -0,0 +1,177 @@
<template>
<div class="ui-appReplenishData">
<div class="font_30 color333 bold f-fcl">补充资料</div>
<div class="ui-describe-box">
<van-field v-model="describe" class="ui-desc-input font_30 color3" rows="5" type="textarea" maxlength="500" show-word-limit placeholder="请输入补充资料内容" autosize />
</div>
<div class="ui-upload-box">
<div v-for="(item, index) in pics" :key="index" class="ui-upload-icon-box flo_l">
<img class="ui-upload-icon" :src="item" mode="aspectFill" alt="" @click="ImagePreview(pics, index)" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" mode="widthFix" alt="" @click="clearPic(index)" />
</div>
<div v-if="pics.length < 9">
<uploadPicture :multiple="true" :max-count="9" @on-success="onSuccess" @click="takePhone">
<div class="ui-upload">
<img class="ui-upload-icon flo_l" src="https://image.fulllinkai.com/202301/07/6c9bc853bed42c9871f56156d1b8f31c.png" alt="" />
</div>
</uploadPicture>
</div>
</div>
<div class="ui-next-btn font_30 f-fcc colorF" @click="changeData">保存</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { showImagePreview, showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppReplenishData' });
const userStore = useUserStore() as any;
const throttle = ref(true);
const describe = ref<any>('');
const reportType = ref<any>('0');
const pics = ref<any[]>([]);
//
const clearPic = (index) => {
pics.value.splice(index, 1);
};
const changeData = () => {
let data = {
chat_id: userStore.chatId,
images: pics.value,
type: reportType.value,
text: describe.value,
};
if (!describe.value) {
showToast('请输入补充资料内容');
return;
}
if (pics.value.length === 0) {
showToast('请上传最少一张图片');
return;
}
if (throttle.value) {
throttle.value = false;
weChat({ url: `h5/add/other/health/file`, data, method: 'post' })
.then(() => {
throttle.value = true;
showToast('保存成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'appViewUserInfo',
query: { currentTab: 2 },
});
router.go(-1);
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const ImagePreview = (e, index) => {
showImagePreview({
images: e,
showIndex: false,
startPosition: index,
loop: false,
});
};
//
const onSuccess = (val) => {
if (pics.value.length < 9) {
pics.value.push(val);
}
};
window.getAndroidPhone = (e) => {
if (e && e.length > 0) {
e.forEach((item) => {
if (pics.value && pics.value.length < 9) {
pics.value.push(item);
}
});
}
};
const takePhone = () => {
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
// app
window.webAppInterface.takePhoto();
}
};
onMounted(() => {
let route = router.currentRoute.value.query;
reportType.value = route.reportType;
// userStore.chatId = 1;
});
</script>
<style lang="scss" scoped>
.ui-appReplenishData {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
padding: px2rem(30);
}
.ui-describe-box {
padding-bottom: px2rem(40);
.ui-desc-input {
background: #ffffff;
border-radius: px2rem(24);
margin-top: px2rem(20);
}
.inputColor {
color: #c2c2c2;
}
}
.ui-upload-box {
overflow: hidden;
margin-bottom: px2rem(120);
.ui-upload-icon-box {
position: relative;
.ui-upload-clear-icon {
position: absolute;
right: px2rem(30);
top: px2rem(10);
width: px2rem(36);
height: px2rem(36);
}
}
.ui-upload-icon {
width: px2rem(200);
height: px2rem(200);
display: block;
border-radius: px2rem(16);
margin-right: px2rem(20);
margin-bottom: px2rem(20);
object-fit: cover;
object-position: center;
}
}
.ui-next-btn {
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<div class="ui-appServiceFlow">
<img class="ui-top-bg" src="https://image.fulllinkai.com/202410/28/6cf27a143e98c2a20be56f06772feea8.png" alt="" />
<div class="ui-container-box">
<img class="ui-before-icon" src="https://image.fulllinkai.com/202410/28/4526365c9f10d3e26441858b23be9302.png" alt="" />
<div class="f-fbc f-wrap">
<div v-for="(item, index) in beforeList" :key="index" class="ui-before-item f-fbc" :class="item.state ? 'ui-active' : ''">
<div>
<div class="font_28 color0E bold400">
<span class="bold500">{{ index + 1 }}</span>
{{ item.title }}
</div>
<div class="font_22 color76c ui-pt-8 bold400">{{ item.subTitle }}</div>
</div>
<img v-if="!item.state" class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/c1ce13747114460dd4cb8eb838f9dbd6.png" alt="" />
<img v-else class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/4df1d6292a1a3a499f279d8625948cf9.png" alt="" />
</div>
</div>
<img class="ui-middle-icon" src="https://image.fulllinkai.com/202410/28/4526365c9f10d3e26441858b23be9302.png" alt="" />
<div class="f-fbc f-wrap">
<div v-for="(item, index) in middleList" :key="index" class="ui-before-item f-fbc" :class="item.state ? 'ui-active' : ''">
<div>
<div class="font_28 color0E bold400">
<span class="bold500">{{ index + 1 }}</span>
{{ item.title }}
</div>
<div class="font_22 color76c ui-pt-8 bold400">{{ item.subTitle }}</div>
</div>
<img v-if="!item.state" class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/c1ce13747114460dd4cb8eb838f9dbd6.png" alt="" />
<img v-else class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/4df1d6292a1a3a499f279d8625948cf9.png" alt="" />
</div>
</div>
<img class="ui-after-icon" src="https://image.fulllinkai.com/202410/28/4526365c9f10d3e26441858b23be9302.png" alt="" />
<div class="f-fbc f-wrap">
<div v-for="(item, index) in afterList" :key="index" class="ui-before-item f-fbc" :class="item.state ? 'ui-active' : ''">
<div>
<div class="font_28 color0E bold400">
<span class="bold500">{{ index + 1 }}</span>
{{ item.title }}
</div>
<div class="font_22 color76c ui-pt-8 bold400">{{ item.subTitle }}</div>
</div>
<img v-if="!item.state" class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/c1ce13747114460dd4cb8eb838f9dbd6.png" alt="" />
<img v-else class="ui-state-icon" src="https://image.fulllinkai.com/202410/28/4df1d6292a1a3a499f279d8625948cf9.png" alt="" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
defineOptions({ name: 'Test' });
const beforeList = ref<any[]>([
{ title: '填写问卷', subTitle: '方案前健康调查问卷', state: 1 },
{ title: '缴费成功', subTitle: '系统审核', state: 1 },
{ title: '签署合同', subTitle: '依法成立的合同', state: 1 },
{ title: '健康档案', subTitle: '填写健康档案', state: 1 },
{ title: '确认收货', subTitle: '未开始', state: 0 },
]);
const middleList = ref<any[]>([{ title: '查看餐单', subTitle: '未开始', state: 0 }]);
const afterList = ref<any[]>([{ title: '调查问卷', subTitle: '未开始', state: 0 }]);
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appServiceFlow {
background: #ffffff;
overflow-y: auto;
min-height: 100vh;
}
.ui-top-bg {
width: 100vw;
height: px2rem(514);
display: block;
object-fit: cover;
object-position: top;
margin-top: px2rem(-176);
}
.ui-container-box {
margin-top: px2rem(-100);
padding: px2rem(12) px2rem(20) px2rem(100) px2rem(20);
border-radius: px2rem(40) px2rem(40) 0 0;
background: #ffffff;
position: relative;
z-index: 1;
.ui-before-icon,
.ui-middle-icon,
.ui-after-icon {
width: px2rem(164);
height: px2rem(74);
display: block;
}
.ui-before-item {
width: px2rem(344);
padding: px2rem(40) px2rem(12) px2rem(40) px2rem(20);
box-sizing: border-box;
background: #fcfcfc;
border-radius: px2rem(20);
margin-bottom: px2rem(12);
.ui-state-icon {
width: px2rem(96);
height: px2rem(96);
display: block;
}
}
.ui-active {
background: #f8fcf9;
}
}
</style>

View File

@ -0,0 +1,663 @@
<template>
<div class="ui-appSleepStatistics">
<div class="ui-placeholder"></div>
<div class="ui-echarts-data">
<div class="f-fbc ui-pb-32">
<img class="ui-triangle-icon" src="https://image.fulllinkai.com/202405/14/30bdbac6230ebcbe36731049a8d43936.png" alt="" @click="prevDate" />
<div class="font_30 bold color3 f-fcc">
<!-- <div class="ui-pr-36 ui-pl-36">{{ startDate }}</div>-->
<!-- <img class="ui-triangle-icon" src="https://image.fulllinkai.com/202405/14/27dd2272bf0fa82980c7bd58789c42df.png" alt="" />-->
<div class="ui-pl-36 ui-pr-36">{{ endDate }}</div>
</div>
<img class="ui-triangle-icon" src="https://image.fulllinkai.com/202405/14/e813aafcc67964766a29d1ce2d33a805.png" alt="" @click="nextDate" />
</div>
<div v-if="selectData.seriesName && initArr && initArr.length > 0" class="ui-top-show-data colorF font_28 text-center bold" :style="{ background: initArr[selectIndex].color }">
<div>{{ initArr[selectIndex].date }}</div>
<div class="ui-pt-10">{{ selectData.seriesName }}</div>
</div>
<div class="ui-echarts-box">
<div ref="main" class="ui-echarts"></div>
</div>
<div class="f-fbc">
<div class="font_24 bold color3 ui-startTime">{{ showStartTime }}</div>
<div class="font_24 bold color3 ui-endTime">{{ showEndTime }}</div>
</div>
</div>
<div class="ui-echarts-data">
<div class="ui-type-box">
<div v-if="sleepTotalTime" class="f-fbc color3 bold ui-pb-10">
<div class="font_28">睡眠时长 {{ sleepTotalTime }}</div>
<div class="ui-score">{{ sleepTotalScore }}</div>
</div>
<div v-for="(item, index) in typeList" :key="index" class="f-fbc ui-mt-32">
<div class="ui-type-item" :style="{ background: item.bgColor }">
<div class="ui-type-item-label" :style="{ width: item.percent + '%', background: item.color }"></div>
</div>
<div class="text-right color3 bold">
<div class="font_24">{{ item.name }} {{ item.value }}</div>
<div class="font_26 ui-pt-6">{{ item.percent }}%</div>
</div>
</div>
</div>
</div>
<!-- <div class="ui-sleep-data-box">-->
<!-- <div class="f-fcl ui-pb-24">-->
<!-- <div class="ui-title-line"></div>-->
<!-- <div class="font_30 bold">详细数据</div>-->
<!-- </div>-->
<!-- <div v-if="initArr && initArr.length > 0" ref="scrollContainer" class="ui-detail-box">-->
<!-- <div v-for="(item, index) in initArr" v-show="item.value !== -1" :key="index" ref="setItemRef" class="f-fbc ui-pb-16 ui-pt-16 ui-pl-28 ui-pr-28" :class="index == selectIndex ? 'ui-select-detail' : ''">-->
<!-- <div class="f-fcl">-->
<!-- <div class="ui-detail-label" :style="{ background: item.color }"></div>-->
<!-- <div class="font_30 color3">{{ item.date }}</div>-->
<!-- </div>-->
<!-- <div class="font_28 bold color3">{{ item.seriesName }}({{ item.origin_value || '' }})</div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <img class="ui-empty-data-icon" src="https://image.fulllinkai.com/202301/08/939c529ca16a91e3ee3b22bef2fe92ca.png" alt="" />-->
<!-- <div class="color9 font_28 text-center ui-mt-16">暂无数据</div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, shallowRef } from 'vue';
import { showToast, closeToast } from 'vant';
// import vConsole from 'vconsole';
import requestApp from '@/utils/requestApp';
import { timestamp } from '@/plugins/public';
defineOptions({ name: 'AppSleepStatistics' });
const main = ref<any>(null);
const myChart = shallowRef<any>(null);
// const setItemRef = ref<any>(null);
// const scrollContainer = ref<any>(null); //
const selectData = ref<any>({ seriesName: '' }); //
const selectIndex = ref<any>(0); //
const typeList = ref<any[]>([
{ name: '清醒', value: '0', percent: '0.0', color: '#FFCB9B', bgColor: '#FEE9D6' },
{ name: '快速', value: '0', percent: '0.0', color: '#D6C4F8', bgColor: '#EFE7FE' },
{ name: '浅睡', value: '0', percent: '0.0', color: '#B08BF4', bgColor: '#DFD0F9' },
{ name: '深睡', value: '0', percent: '0.0', color: '#7148EF', bgColor: '#C5B4F8' },
]);
const objectTime = ref<any>(420);
const token = ref<any>('');
const startDate = ref<any>(''); //
const endDate = ref<any>(''); //
const startTime = ref<any>('18:00:00'); //
const endTime = ref<any>(''); //
const currentDate = ref<any>(''); //
const showStartTime = ref<any>(''); //
const showEndTime = ref<any>(''); //
const initArr = ref<any[]>([]);
const sleepTotalTime = ref<any>('');
const sleepTotalScore = ref<any>('');
const lightTime = ref<any>(0); //
const lightIdeal = ref<any>(0); //
const lightScore = ref<any>(0); //
const remTime = ref<any>(0); //
const remIdeal = ref<any>(0); //
const remScore = ref<any>(0); //
const deepTime = ref<any>(0); //
const deepIdeal = ref<any>(0); //
const deepScore = ref<any>(0); //
const loading = ref(false);
const throttle = ref<any>(false);
const timer = ref<any>(null);
const one = ref<any>([]); //
const two = ref<any>([]); //
const three = ref<any>([]); //
const four = ref<any>([]); //
const echartsArr = ref<any>([]);
const getAppSleepData = () => {
throttle.value = false;
if (myChart.value) {
myChart.value.dispose();
}
selectData.value = { seriesName: '' };
selectIndex.value = 0;
// scrollContainer.value = null;
showStartTime.value = '';
showEndTime.value = '';
loading.value = false;
requestApp({ url: `/app/daily/sleep?start_time=${startDate.value} ${startTime.value}&end_time=${endDate.value} ${endTime.value}`, method: 'get' })
// requestApp({ url: `/app/daily/sleep?start_time=2024-05-10 18:00:00&end_time=2024-05-11 18:00:00`, method: 'get' })
.then((res) => {
const result = res.data;
let oneTimeTotal = 0; //
let oneDataTotal = 0; //
let twoTimeTotal = 0; //
let twoDataTotal = 0; //
let threeTimeTotal = 0; //
let threeDataTotal = 0; //
let fourTimeTotal = 0; //
let fourDataTotal = 0; //
one.value = [];
two.value = [];
three.value = [];
four.value = [];
echartsArr.value = [];
typeList.value = [
{ name: '清醒', value: '0', percent: '0.0', color: '#FFCB9B', bgColor: '#FEE9D6' },
{ name: '快速', value: '0', percent: '0.0', color: '#D6C4F8', bgColor: '#EFE7FE' },
{ name: '浅睡', value: '0', percent: '0.0', color: '#B08BF4', bgColor: '#DFD0F9' },
{ name: '深睡', value: '0', percent: '0.0', color: '#7148EF', bgColor: '#C5B4F8' },
];
let arr = result;
if (arr && arr.length > 0) {
for (let index = 0; index < arr.length; index++) {
if (index > 0 && index + 1 != arr.length) {
const date1 = new Date(result[index].date.replace(/-/g, '/')) as any;
const date2 = new Date(result[index + 1].date.replace(/-/g, '/')) as any;
const differenceInMilliseconds = Math.abs(date2 - date1);
if (differenceInMilliseconds > 90000) {
result.splice(index + 1, 0, {
date: timestamp(Date.parse(date1) / 1000 + 60),
value: -1,
seriesName: '',
origin_value: '',
});
}
}
}
}
setTimeout(() => {
if (result && result.length > 0) {
result.forEach((item) => {
if (item.value == 0) {
one.value.push(50);
two.value.push('-');
three.value.push('-');
four.value.push('-');
echartsArr.value.push(150);
item.seriesName = '清醒';
item.color = '#FFCB9B';
oneTimeTotal += item.unit;
oneDataTotal++;
} else if (item.value == 1) {
one.value.push('-');
two.value.push(50);
three.value.push('-');
four.value.push('-');
echartsArr.value.push(100);
item.seriesName = '快速';
item.color = '#D6C4F8';
twoTimeTotal += item.unit;
twoDataTotal++;
} else if (item.value == 2) {
one.value.push('-');
two.value.push('-');
three.value.push(50);
four.value.push('-');
echartsArr.value.push(50);
item.seriesName = '浅睡';
item.color = '#B08BF4';
threeTimeTotal += item.unit;
threeDataTotal++;
} else if (item.value == 3) {
one.value.push('-');
two.value.push('-');
three.value.push('-');
four.value.push(50);
echartsArr.value.push(0);
item.seriesName = '深睡';
item.color = '#7148EF';
fourTimeTotal += item.unit;
fourDataTotal++;
} else {
one.value.push('-');
two.value.push('-');
three.value.push('-');
four.value.push('-');
echartsArr.value.push(200);
item.seriesName = '';
}
});
initArr.value = result;
console.log(initArr.value, '7777');
loading.value = true;
setTimeout(() => {
// selectData.value.seriesName = initArr.value[selectIndex.value].seriesName;
showStartTime.value = `${initArr.value[selectIndex.value].date.split(/[: ' ' ]/)[1]}:${initArr.value[selectIndex.value].date.split(/[: ' ' ]/)[2]}`;
showEndTime.value = `${initArr.value[initArr.value.length - 1].date.split(/[: ' ']/)[1]}:${initArr.value[initArr.value.length - 1].date.split(/[: ' ']/)[2]}`;
remTime.value = twoTimeTotal;
remIdeal.value = Math.floor(objectTime.value * 0.2);
remScore.value = (remTime.value / remIdeal.value) * 100 > 100 ? 100 : Math.floor((remTime.value / remIdeal.value) * 100);
lightTime.value = threeTimeTotal;
lightIdeal.value = Math.floor(objectTime.value * 0.5);
lightScore.value = (lightTime.value / lightIdeal.value) * 100 > 100 ? 100 : Math.floor((lightTime.value / lightIdeal.value) * 100);
deepTime.value = fourTimeTotal;
deepIdeal.value = Math.floor(objectTime.value * 0.2);
let deep = (deepTime.value / deepIdeal.value) * 100 > 150 ? 150 : Math.floor((deepTime.value / deepIdeal.value) * 100);
deepScore.value = deep <= 100 ? Math.floor(deep * 0.9) : Math.floor((deep - 100) / 5) + 90;
let score = Math.floor(remScore.value * 0.25) + Math.floor(lightScore.value * 0.25) + Math.floor(deepScore.value * 0.5); //
// console.log(Math.floor(deepScore.value * 0.5), Math.floor(lightScore.value * 0.25), Math.floor(remScore.value * 0.25), '88');
let base = ((oneTimeTotal + twoTimeTotal + threeTimeTotal + fourTimeTotal) / objectTime.value) * 100 > 100 ? 100 : Math.floor(((oneTimeTotal + twoTimeTotal + threeTimeTotal + fourTimeTotal) / objectTime.value) * 100); //
sleepTotalScore.value = Math.floor(0.4 * base) + Math.floor(0.6 * score);
sleepTotalTime.value = TimeConversion(oneTimeTotal + twoTimeTotal + threeTimeTotal + fourTimeTotal);
// console.log(remTime.value, remIdeal.value, remScore.value, '1');
// console.log(lightTime.value, lightIdeal.value, lightScore.value, '2');
// console.log(deepTime.value, deepIdeal.value, deepScore.value, '3');
// console.log(deep, score, base, sleepTotalScore.value, '4');
//
typeList.value[0].value = TimeConversion(oneTimeTotal);
typeList.value[0].percent = `${(numFilter(oneDataTotal / initArr.value.length) * 100).toFixed(2)}`;
typeList.value[1].value = TimeConversion(twoTimeTotal);
typeList.value[1].percent = `${(numFilter(twoDataTotal / initArr.value.length) * 100).toFixed(2)}`;
typeList.value[2].value = TimeConversion(threeTimeTotal);
typeList.value[2].percent = `${(numFilter(threeDataTotal / initArr.value.length) * 100).toFixed(2)}`;
typeList.value[3].value = TimeConversion(fourTimeTotal);
typeList.value[3].percent = `${(numFilter(fourDataTotal / initArr.value.length) * 100).toFixed(2)}`;
// 10
let scale = 0;
let scaleIndex = 0;
typeList.value.forEach((item, index) => {
scale += item.percent * 1;
if (item.percent * 1) {
scaleIndex = index;
}
});
if (scale < 100) {
typeList.value[scaleIndex].percent = typeList.value[scaleIndex].percent * 1 + (100 - scale);
}
}, 100);
} else {
sleepTotalTime.value = '';
sleepTotalScore.value = '';
initArr.value = [];
}
}, 100);
setTimeout(() => {
initEcharts();
}, 200);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
const initEcharts = () => {
myChart.value = echarts.init(main.value);
let option = {
grid: {
top: '10%',
left: '-30px',
right: '0',
bottom: '-8px',
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: '#5ac7a0',
},
},
formatter(params) {
let tar;
if (!loading.value) {
return;
}
tar = params.filter((item) => {
return item.data != '-';
});
throttle.value = false;
clearTimeout(timer.value);
timer.value = setTimeout(() => {
throttle.value = true;
}, 500);
if (tar.length > 1) {
selectData.value = tar[1];
selectIndex.value = tar[1].dataIndex;
console.log(selectData.value, '7777');
// scrollContainer.value.scrollTop = setItemRef.value[selectIndex.value].offsetTop - scrollContainer.value.offsetTop - 80;
}
},
},
xAxis: {
type: 'category',
data: (function () {
let list = [] as any;
for (let i = 1; i <= echartsArr.value.length; i++) {
list.push(`${echartsArr.value[i]}`);
}
return list;
})(),
show: false,
},
yAxis: {
type: 'value',
show: false,
},
series: [
{
name: 'Placeholder',
type: 'bar',
stack: 'Total',
silent: false,
itemStyle: {
borderColor: 'transparent',
color: 'transparent',
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent',
},
},
data: echartsArr.value,
},
{
name: '清醒',
type: 'bar',
stack: 'Total',
itemStyle: {
color: '#FFCB9B',
},
label: {
show: false,
},
data: one.value,
},
{
name: '快速',
type: 'bar',
stack: 'Total',
itemStyle: {
color: '#D6C4F8',
},
label: {
show: false,
},
data: two.value,
},
{
name: '浅睡',
type: 'bar',
stack: 'Total',
itemStyle: {
color: '#B08BF4',
},
label: {
show: false,
},
data: three.value,
},
{
name: '深睡',
type: 'bar',
stack: 'Total',
itemStyle: {
color: '#7148EF',
},
label: {
show: false,
},
data: four.value,
},
],
};
myChart.value.setOption(option);
setTimeout(() => {
throttle.value = true;
}, 200);
};
//
const numFilter = (value) => {
let realVal;
let tempVal = parseFloat(value).toFixed(3);
realVal = tempVal.substring(0, tempVal.length - 1);
return realVal;
};
//
const TimeConversion = (time) => {
let h;
if (time >= 60) {
h = Math.floor(time / 60);
time -= h * 60;
return `${h}${time == 0 ? '' : `${time}`}`;
} else {
return `${time}`;
}
};
//
const prevDate = () => {
if (!throttle.value) {
return;
}
let odata = new Date(new Date(startDate.value.replace(/-/g, '/')).getTime() - 24 * 60 * 60 * 1000); // -1
endDate.value = startDate.value;
startDate.value = transferTime(odata);
endTime.value = '18:00:00';
getAppSleepData();
};
//
const nextDate = () => {
if (endDate.value == currentDate.value) {
showToast('请先睡个好觉,明天再来查看吧');
return;
}
if (!throttle.value) {
return;
}
let h = new Date().getHours();
let m = new Date().getMinutes();
let s = new Date().getSeconds();
h = (h < 10 ? `0${h}` : h) as any;
m = (m < 10 ? `0${m}` : m) as any;
s = (s < 10 ? `0${s}` : s) as any;
let odata = new Date(new Date(endDate.value.replace(/-/g, '/')).getTime() + 24 * 60 * 60 * 1000); // +1
startDate.value = endDate.value;
endDate.value = transferTime(odata);
if (endDate.value == currentDate.value) {
endTime.value = `${h}:${m}:${s}`;
}
getAppSleepData();
};
//
const transferTime = (e) => {
let date = new Date(e);
let y = date.getFullYear();
let m = (date.getMonth() + 1) as any;
let d = date.getDate() as any;
m = m < 10 ? `0${m}` : m;
d = d < 10 ? `0${d}` : d;
return `${y}-${m}-${d}`;
};
// ios apptoken
window.getIosToken = (e) => {
objectTime.value = e.time * 1;
token.value = e.token;
localStorage.setItem('appToken', token.value);
let year = new Date().getFullYear();
let month = new Date().getMonth() + 1;
let day = new Date().getDate();
let h = new Date().getHours();
let m = new Date().getMinutes();
let s = new Date().getSeconds();
let c = `${year}-${month}-${day}`;
let yesterday = new Date(new Date(c.replace(/-/g, '/')).getTime() - 24 * 60 * 60 * 1000);
month = (month < 10 ? `0${month}` : month) as any;
day = (day < 10 ? `0${day}` : day) as any;
h = (h < 10 ? `0${h}` : h) as any;
m = (m < 10 ? `0${m}` : m) as any;
s = (s < 10 ? `0${s}` : s) as any;
startDate.value = transferTime(yesterday);
endDate.value = `${year}-${month}-${day}`;
currentDate.value = `${year}-${month}-${day}`;
endTime.value = `${h}:${m}:${s}`;
setTimeout(() => {
getAppSleepData();
});
};
onMounted(() => {
// new vConsole();
window.webkit.messageHandlers.getCurTokenWithUid.postMessage(null);
// localStorage.setItem('appToken', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJDcmVhdGVkQXQiOjE3MTUxMzQxNjcsIkV4cGlyZWRBdCI6MTcxNzcyNjE2NywiVmVyc2lvbiI6MTAwLCJVc2VyaWQiOjEwMywiZXhwIjoyNTc5MTM0MTY3fQ.3CjxKG-yC1u8xgEIkoqz0R3FDTVHNCw4x_uOhpagzPg');
// let year = new Date().getFullYear();
// let month = new Date().getMonth() + 1;
// let day = new Date().getDate();
// let h = new Date().getHours();
// let m = new Date().getMinutes();
// let s = new Date().getSeconds();
// let c = `${year}-${month}-${day}`;
// let yesterday = new Date(new Date(c.replace(/-/g, '/')).getTime() - 24 * 60 * 60 * 1000);
// month = (month < 10 ? `0${month}` : month) as any;
// day = (day < 10 ? `0${day}` : day) as any;
// h = (h < 10 ? `0${h}` : h) as any;
// m = (m < 10 ? `0${m}` : m) as any;
// s = (s < 10 ? `0${s}` : s) as any;
// startDate.value = transferTime(yesterday);
// endDate.value = `${year}-${month}-${day}`;
// currentDate.value = `${year}-${month}-${day}`;
// endTime.value = `${h}:${m}:${s}`;
// setTimeout(() => {
// getAppSleepData();
// });
});
</script>
<style lang="scss" scoped>
.ui-appSleepStatistics {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-placeholder {
width: 100vw;
height: px2rem(30);
}
.ui-echarts-data {
background: #ffffff;
padding: px2rem(30);
margin: 0 px2rem(30) px2rem(24) px2rem(30);
border-radius: px2rem(12);
.ui-triangle-icon {
width: px2rem(32);
height: px2rem(32);
display: block;
}
.ui-top-show-data {
width: fit-content;
padding: px2rem(20) px2rem(50);
margin: 0 auto;
border-radius: px2rem(20);
}
.ui-echarts-box {
height: px2rem(260);
.ui-echarts {
width: 100%;
height: 100%;
}
}
.ui-startTime {
margin-left: px2rem(-2);
}
.ui-endTime {
margin-right: px2rem(-2);
}
.ui-type-box {
.ui-score {
font-size: px2rem(48);
}
.ui-type-item {
width: 56%;
height: px2rem(34);
border-radius: px2rem(30);
overflow: hidden;
.ui-type-item-label {
height: px2rem(34);
border-radius: px2rem(30);
}
}
}
}
.ui-sleep-data-box {
margin: px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(12);
.ui-title-line {
width: px2rem(8);
height: px2rem(32);
border-radius: px2rem(20);
background: #5ac7a0;
margin-right: px2rem(24);
}
.ui-detail-box {
height: px2rem(440);
overflow-y: scroll;
.ui-select-detail {
border: px2rem(2) solid #5ac7a0;
border-radius: px2rem(6);
}
.ui-detail-label {
width: px2rem(42);
height: px2rem(22);
border-radius: px2rem(4);
margin-right: px2rem(20);
}
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: px2rem(30) auto 0 auto;
}
}
</style>

View File

@ -0,0 +1,343 @@
<template>
<div class="ui-appStatistics">
<div class="ui-label-box">
<div v-for="(item, index) in labelList" :key="index" @click="selectLabel(item, index)">
<div class="text-center ui-label-item" :class="index == labelIndex ? 'ui-active-label' : ''">
<img class="ui-label-icon" :src="index == labelIndex ? item.h_icon : item.icon" alt="" />
<div class="font_24" :class="index == labelIndex ? 'colorTheme' : 'color6'">{{ item.fat_name || item.name }}</div>
</div>
</div>
</div>
<div class="ui-screening-time">
<div class="color3 font_30 bold">时间筛选</div>
<div class="f-fbc">
<div class="ui-examine-time" @click="showDate = true">
<van-field v-model="date" class="ui-date-input f-fcc font_30 color3" readonly placeholder="选择开始时间" />
<img class="ui-time-icon" src="https://image.fulllinkai.com/202301/07/b2ac8981bfc3ab790f4ffabff8887988.png" alt="" />
</div>
<div class="font_30 ui-ml-12 ui-mr-12">~</div>
<div class="ui-examine-time" @click="showEndDate = true">
<van-field v-model="dateEnd" class="ui-date-input f-fcc font_30 color3" readonly placeholder="选择结束时间" />
<img class="ui-time-icon" src="https://image.fulllinkai.com/202301/07/b2ac8981bfc3ab790f4ffabff8887988.png" alt="" />
</div>
</div>
</div>
<view class="ui-unit color6 font_28 f-fbc">
<div class="f-fcl">
<div class="ui-vertical-line"></div>
<div>单位{{ unit || '--' }}</div>
</div>
<div class="colorTheme font_24">体脂秤类型{{ fatDevice == 1 ? '新版秤' : '旧版秤' }}</div>
</view>
<div class="ui-echarts-box">
<div ref="main" class="ui-echarts"></div>
</div>
<van-popup v-model:show="showDate" round position="bottom" :duration="0.5">
<van-date-picker v-model="dateValues" title="选择日期" :min-date="minDate" :max-date="maxDate" @cancel="(showDate = false), (date = '')" @confirm="onConfirm" />
</van-popup>
<van-popup v-model:show="showEndDate" round position="bottom" :duration="0.5">
<van-date-picker v-model="dateEndValues" title="选择日期" :min-date="minDate" :max-date="maxDate" @cancel="(showEndDate = false), (dateEnd = '')" @confirm="onEndConfirm" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, shallowRef } from 'vue';
// import * as echarts from 'echarts';
import { closeToast, showToast } from 'vant';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppStatistics' });
const userStore = useUserStore() as any;
const main = ref();
const fatDevice = ref<any>('');
const myChart = shallowRef<any>(null);
const showDate = ref(false);
const showEndDate = ref(false);
const date = ref<any>('');
const dateEnd = ref<any>('');
const minDate = new Date(new Date().getFullYear() - 120, 0, 1);
const maxDate = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());
const dateValues = ref<any[]>([`${new Date().getFullYear()}`, `${new Date().getMonth() + 1 < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth()}`, `${new Date().getDate()}`]); //
const dateEndValues = ref<any[]>([`${new Date().getFullYear()}`, `${new Date().getMonth() < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth()}`, `${new Date().getDate()}`]); //
const chartTime = ref<any[]>([]);
const chartData = ref<any[]>([]);
const chartMinData = ref<any[]>([]);
const labelList = ref<any[]>([]);
const labelIndex = ref(0);
const chartName = ref<any>('体重');
const chartNameV2 = ref<any>('Weight');
const unit = ref<any>('--');
const getIcon = () => {
weChat({ url: `/h5/get/fat/kinds?chat_id=${userStore.chatId}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
labelList.value = result;
if (labelList.value && labelList.value.length > 0) {
unit.value = labelList.value[0].unit;
}
console.log(result);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
const getDate = () => {
weChat({ url: `/h5/get/fat/stat?chat_id=${userStore.chatId}&fat_name=${chartName.value}&start_date=${date.value}&end_date=${dateEnd.value}`, method: 'get' })
.then((res) => {
const result = res.data;
fatDevice.value = result.fat_device;
chartTime.value = result.x_arr;
chartData.value = result.y_arr;
chartMinData.value = result.z_arr;
console.log(result);
myChart.value = null;
setTimeout(() => {
initEcharts();
}, 100);
})
.catch((err) => {
closeToast();
console.log(err);
});
};
const initEcharts = () => {
myChart.value = echarts.init(main.value);
let option = {
grid: {
top: '12%',
left: '5%',
right: '7%',
bottom: '18%',
containLabel: true,
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.56)',
borderColor: 'gray',
textStyle: {
color: '#ffffff',
},
},
xAxis: {
data: chartTime.value,
},
yAxis: {
type: 'value',
},
dataZoom: [
{
type: 'inside',
moveOnMouseWheel: false,
preventDefaultMouseMove: false,
xAxisIndex: [0],
start: 0,
end: 100,
},
{
xAxisIndex: [0],
start: 0,
end: 100,
width: '88.6%',
left: '5%',
height: 20,
handleSize: 24,
borderColor: 'none',
handleIcon: 'M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5M36.9,35.8h-1.3z M27.8,35.8 h-1.3H27L27.8,35.8L27.8,35.8z',
backgroundColor: '#e5f7f1',
color: '#BFCCE3',
showDetail: false,
// moveOnMouseWheel: false,
// preventDefaultMouseMove: false,
},
],
series: [
{
name: '测量最小值',
type: 'line',
symbol: 'none',
lineStyle: {
color: '#f3aa20',
},
data: chartMinData.value,
},
{
name: '测量最大值',
type: 'line',
symbol: 'none',
lineStyle: {
color: '#5ac7a0',
},
data: chartData.value,
},
],
};
myChart.value.setOption(option);
};
//
const onConfirm = ({ selectedValues }) => {
let event = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
if (event && dateEnd.value) {
if ((Date.parse(dateEnd.value) || Date.parse(dateEnd.value.replace(/-/g, '/'))) < (Date.parse(event) || Date.parse(event.replace(/-/g, '/')))) {
showToast('开始时间不能大于结束时间');
return;
}
date.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
getDate();
}
showDate.value = false;
date.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
};
//
const onEndConfirm = ({ selectedValues }) => {
let event = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
if (date.value && event) {
if ((Date.parse(event) || Date.parse(event.replace(/-/g, '/'))) < (Date.parse(date.value) || Date.parse(date.value.replace(/-/g, '/')))) {
showToast('开始时间不能大于结束时间');
return;
}
dateEnd.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
getDate();
}
showEndDate.value = false;
dateEnd.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
};
const selectLabel = (e, index) => {
if (labelIndex.value === index) {
return;
}
labelIndex.value = index;
chartName.value = e.fat_name;
chartNameV2.value = e.e_name;
unit.value = e.unit;
getDate();
};
onMounted(() => {
// userStore.chatId = 1;
getIcon();
getDate();
});
</script>
<style lang="scss" scoped>
.ui-appStatistics {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-tabBar-box {
width: 100vw;
height: px2rem(90);
background: #f8f8f8;
.ui-tabBar-list {
padding: px2rem(24) px2rem(180) px2rem(0) px2rem(180);
}
.ui-tabBar-line {
position: absolute;
bottom: px2rem(2);
width: 100%;
height: px2rem(10);
background: linear-gradient(90deg, rgba(95, 226, 175, 0.1) 0%, #41d5a9 100%);
border-radius: px2rem(5);
}
}
.ui-label-box {
padding: px2rem(30) 0 0 px2rem(30);
display: flex;
justify-content: left;
overflow-x: scroll;
white-space: nowrap;
.ui-label-item {
min-width: px2rem(120);
height: px2rem(112);
background: #ffffff;
border-radius: px2rem(8);
margin-right: px2rem(10);
padding-top: px2rem(22);
.ui-label-icon {
width: px2rem(48);
height: px2rem(48);
display: block;
margin: 0 auto px2rem(8) auto;
}
}
.ui-active-label {
background: #e8fff7;
}
}
.ui-label-box::-webkit-scrollbar {
display: none;
}
.ui-screening-time {
padding: px2rem(40) px2rem(30) 0 px2rem(30);
.ui-examine-time {
padding: 0 px2rem(30);
margin: px2rem(20) 0 px2rem(30) 0;
border-radius: px2rem(24);
background: #ffffff;
position: relative;
.ui-date-input {
//width: px2rem(500);
height: px2rem(80);
padding: initial;
background: #ffffff;
}
.ui-time-icon {
width: px2rem(28);
height: px2rem(28);
display: block;
position: absolute;
right: px2rem(30);
top: px2rem(26);
}
}
}
.ui-unit {
padding: px2rem(30) px2rem(30) 0 px2rem(30);
margin: px2rem(20) px2rem(30) 0 px2rem(30);
border-radius: px2rem(24) px2rem(24) 0 0;
background: #ffffff;
.ui-vertical-line {
width: px2rem(4);
height: px2rem(20);
background: #5ac7a0;
border-radius: px2rem(4);
margin-top: px2rem(-4);
margin-right: px2rem(10);
}
}
.ui-echarts-box {
margin: 0 px2rem(30);
background: #ffffff;
border-radius: 0 0 px2rem(24) px2rem(24);
height: px2rem(560);
.ui-echarts {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,267 @@
<template>
<div ref="scrollDistance" class="ui-appSurveys" @scroll="handleScroll">
<div class="ui-top-box">
<div class="ui-transmit-tips font_26 colorTheme f-fcl">转发说明转发到群聊用户可填写或查看调查问卷</div>
<div class="ui-search-box">
<van-field v-model="searchValue" class="ui-search-input font_28 color3 f-fcc" placeholder="搜索问卷" />
<img class="ui-search-icon" src="https://image.fulllinkai.com/202304/07/38e32f1948ad951b1f66b404380648f3.png" alt="" />
</div>
</div>
<div class="ui-placeholder"></div>
<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://image.fulllinkai.com/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div class="color6 font_30 text-center">暂无问卷</div>
</div>
<div v-else>
<div class="ui-surveys-box">
<div v-for="(item, index) in list" :key="index" class="ui-surveys-item f-fbc" @click="jumpPath('appQuestionnaire', item.id)">
<div class="ui-surveys-title-box">
<img v-if="!item.is_write" class="ui-write-icon" src="https://image.fulllinkai.com/202306/07/ac2c34d999de6e46272cd62499559816.png" alt="" />
<img v-else class="ui-write-icon" src="https://image.fulllinkai.com/202306/07/ef51a24dd282f1bb00027eccb687cfa8.png" alt="" />
<div class="font_30 bold color3 ui-surveys-title">{{ item.title }}</div>
</div>
<div class="ui-btn font_26 colorTheme f-fcc" @click.stop="retransmission(item.id)">转发</div>
<!--<div v-else class="ui-btn font_26 colorTheme f-fcc" @click.stop="jumpPath('appQuestionnaire', item.id)">查看</div>-->
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onActivated, onMounted, ref, watch } from 'vue';
import { showConfirmDialog, showToast } from 'vant';
import router from '@/router';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppSurveys' });
const userStore = useUserStore();
const isIOS = ref<any>(null);
const searchValue = ref<any>('');
const timer = ref<any>(null);
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 scrollValue = ref(0); //
const scrollDistance = ref<any>(null);
const getList = () => {
weChat({ url: `/h5/surveys?page=${page.value}&chat_id=${userStore.chatId}&keyword=${searchValue.value}`, method: 'get' })
.then((res) => {
const result = res.data;
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) => {
console.log(err);
});
};
//
const initList = () => {
page.value = 1;
getList();
};
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
//
const retransmission = (id) => {
showConfirmDialog({
title: '',
message: '是否确认转发调查问卷到群聊天?',
})
.then(() => {
let data = {
chat_id: userStore.chatId,
type: 'survey',
is_im: 1,
};
weChat({ url: `send/im/msg`, data, method: 'post' })
.then(() => {
showToast('通知已发送');
setTimeout(() => {
close();
}, 1200);
})
.catch((err) => {
console.log(err);
});
})
.catch(() => {
// on cancel
});
};
const jumpPath = (url, id) => {
console.log(userStore.chatId, '777777777777');
router.push({
name: url,
query: { chat_id: userStore.chatId, survey_id: id },
});
};
const close = () => {
// ios app
if (isIOS.value) {
window.webkit.messageHandlers.goBack.postMessage(null);
} else {
// app
window.webAppInterface.goBack();
}
};
//
watch(searchValue, () => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
initList();
}, 800);
});
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
scrollDistance.value.scrollTop = scrollValue.value;
});
onMounted(() => {
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;
}
});
</script>
<style lang="scss" scoped>
.ui-appSurveys {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-top-box {
width: 100vw;
position: fixed;
top: 0;
left: 0;
z-index: 101;
background: #f8f8f8;
.ui-transmit-tips {
padding: 0 px2rem(30);
height: px2rem(80);
background: #e8fff7;
}
.ui-search-box {
max-width: 100%;
padding: px2rem(20) px2rem(30) px2rem(30) px2rem(30);
height: px2rem(80);
background: #f8f8f8;
position: relative;
.ui-search-input {
width: 100%;
padding: 0 px2rem(70);
height: px2rem(80);
background: #ffffff;
border-radius: px2rem(40);
margin: 0 auto;
}
::v-deep(input::-webkit-input-placeholder) {
font-size: px2rem(28);
color: #999999;
}
.ui-search-icon {
width: px2rem(28);
height: px2rem(28);
position: absolute;
top: px2rem(46);
left: px2rem(60);
}
}
}
.ui-placeholder {
width: 100vw;
height: px2rem(210);
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 25vh auto 0 auto;
}
.ui-surveys-box {
padding-bottom: 16vh;
.ui-surveys-item {
margin: 0 px2rem(30) px2rem(30) px2rem(30);
padding: px2rem(34) px2rem(30);
background: #ffffff;
border-radius: px2rem(16);
.ui-surveys-title-box {
position: relative;
max-width: px2rem(474);
.ui-write-icon {
position: absolute;
left: 0;
top: 0;
width: px2rem(84);
height: px2rem(40);
}
.ui-surveys-title {
text-indent: px2rem(94);
}
}
.ui-btn {
position: relative;
z-index: 99;
width: px2rem(126);
height: px2rem(52);
border-radius: px2rem(30);
border: px2rem(2) solid #b2e3d2;
}
}
}
</style>

View File

@ -0,0 +1,269 @@
<template>
<div class="ui-appSwitchUser">
<div>
<div class="ui-title font_48 color3 bold">切换订单用户</div>
<div class="ui-mobile f-fcl">
<div class="f-fcl ui-area-code" @click.stop="switchChoose">
<div class="font_30 color3">{{ AreaValue }}</div>
<img v-if="showChooseArea" class="Angle_icon" src="https://image.fulllinkai.com/202112/10/697cbb5933196bbc21165cb974f2e343.png" alt="" />
<img v-else class="Angle_icon" src="https://image.fulllinkai.com/202112/10/02d4c797f6de6494643f506c5d2b85b7.png" alt="" />
</div>
<van-field v-model="mobile" type="tel" 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-btn f-fcc font_30 colorF bold" @click="save">确认绑定</div>
<div class="font_24 color9 text-center">绑定后可使用该帐号</div>
</div>
<img class="ui-icon" src="https://image.fulllinkai.com/202306/26/05b70e1b55dfed082b28fda935653343.png" alt="" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
import requestGo from '@/utils/requestGo';
defineOptions({ name: 'AppSwitchUser' });
const userStore = useUserStore() as any;
const mobile = ref<any>('');
const throttle = ref<any>(true);
const showChooseArea = ref(false);
const AreaIndex = ref<any>(0);
const areaList = ref<any[]>([{ area_code: 86, label: '中国大陆' }]);
const AreaValue = ref<any>('中国大陆 86');
//
const save = () => {
if (throttle.value) {
let data = {
mobile: mobile.value,
chat_id: userStore.chatId,
area_code: areaList.value[AreaIndex.value].area_code,
};
if (!mobile.value) {
showToast('请输入手机号');
return;
}
if (AreaIndex.value == 0 && !/^1(3|4|5|6|7|8|9)\d{9}$/.test(mobile.value)) {
showToast('手机号码格式错误');
return;
}
throttle.value = false;
weChat({ url: `h5/orders/update/user`, data, method: 'post' })
.then(() => {
throttle.value = true;
showToast('订单用户切换成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'personalCenter',
});
router.go(-1);
}, 2000);
})
.catch((err) => {
throttle.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 switchChoose = () => {
showChooseArea.value = !showChooseArea.value;
};
//
const selectedArea = (e, index) => {
AreaValue.value = `${e.label} ${e.area_code}`;
AreaIndex.value = index;
showChooseArea.value = !showChooseArea.value;
};
onMounted(() => {
getAreaCode();
});
</script>
<style lang="scss" scoped>
.ui-appSwitchUser {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-title {
padding: px2rem(120) px2rem(0) px2rem(70) px2rem(50);
}
.ui-item-select {
position: relative;
z-index: 99;
padding: 0 px2rem(30);
.ui-duty-icon {
width: px2rem(40);
height: px2rem(40);
display: block;
margin-right: px2rem(4);
}
.ui-item-select-icon {
width: px2rem(36);
height: px2rem(36);
display: block;
margin-right: px2rem(10);
}
.ui-item-select-text {
line-height: px2rem(40);
}
}
.ui-mobile {
position: relative;
z-index: 10001;
margin: px2rem(0) px2rem(50);
border-bottom: px2rem(2) solid #d8d8d8;
.ui-area-code {
padding-left: px2rem(30);
padding-right: px2rem(20);
white-space: nowrap;
word-break: break-all;
z-index: 2;
}
.Angle_icon {
width: px2rem(20);
height: px2rem(12);
display: block;
margin-left: px2rem(10);
}
.ui-area-mask {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 21;
}
.area_code_choose_box {
position: absolute;
left: px2rem(66);
top: px2rem(82);
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-mobile-text {
line-height: px2rem(44);
}
.ui-user-input {
width: px2rem(480);
font-size: px2rem(32);
padding: px2rem(24) px2rem(12);
border-radius: px2rem(16);
background: initial;
line-height: initial;
}
// 线
::v-deep(.van-cell:after) {
position: relative;
}
::v-deep(input::-webkit-input-placeholder) {
color: #999999;
}
}
.ui-no-role-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: px2rem(40) auto px2rem(20) auto;
}
.ui-btn {
position: relative;
z-index: 33;
width: px2rem(650);
height: px2rem(88);
background: #5ac7a0;
border-radius: px2rem(40);
margin: px2rem(100) auto px2rem(30) auto;
}
.ui-icon {
width: 100vw;
height: px2rem(946);
position: fixed;
bottom: 0;
left: 0;
}
</style>

View File

@ -0,0 +1,688 @@
<template>
<div class="ui-appUploadReport">
<div class="font_30 color333 bold f-fcl">上传{{ reportType == 0 ? '体检' : '复检' }}报告</div>
<div class="ui-report-upload-box">
<div v-for="(item, index) in pics" :key="index" class="ui-upload-icon-box flo_l">
<img class="ui-upload-icon" :src="item" mode="aspectFill" alt="" @click="ImagePreview(pics, index)" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" mode="widthFix" alt="" @click="clearPic(index)" />
</div>
<div v-if="pics.length < 9">
<uploadPicture :multiple="true" :max-count="9" @on-success="onSuccess" @click="takePhone">
<div class="ui-upload">
<img class="ui-upload-icon flo_l" src="https://image.fulllinkai.com/202301/07/6c9bc853bed42c9871f56156d1b8f31c.png" alt="" />
</div>
</uploadPicture>
</div>
</div>
<div class="font_30 color333 bold f-fcl">上传时间</div>
<div class="ui-examine-time" @click="showTime = true">
<div class="m_basLst f-fbc">
<div class="font_28 color3 f-fcl">
<span v-if="time">{{ time }}</span>
<span v-else class="color9">请选择你的{{ reportType == 0 ? '体检' : '报告' }}时间</span>
</div>
<img class="ui-triangle-icon" src="https://image.fulllinkai.com/202301/06/d491373f21f5a0c5810f2167f7c961f0.png" alt="" />
</div>
</div>
<div v-if="type == '1'">
<div class="ui-body-container">
<div class="font_30 color3 bold ui-body-container-title">请上传全身照侧面各一张</div>
<div class="ui-body-container-item">
<div v-if="!bodyPic.bodyFront" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'1'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">正面照</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.bodyFront, bodyPic.bodyProfile], 0)">
<img class="ui-body-pic" :src="bodyPic.bodyFront" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('1', 0, '正面照')" />
</div>
<div v-if="!bodyPic.bodyProfile" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'2'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">侧面照</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.bodyProfile, bodyPic.bodyFront], 0)">
<img class="ui-body-pic" :src="bodyPic.bodyProfile" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('2', 0, '侧面照')" />
</div>
</div>
<div class="font_30 color3 bold ui-body-container-title">请上传大头照正面一张</div>
<div class="ui-body-container-item">
<div v-if="!bodyPic.bigHead" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'3'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">大头照</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.bigHead], 0)">
<img class="ui-body-pic" :src="bodyPic.bigHead" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('3', 0, '大头照')" />
</div>
</div>
<div class="font_30 color3 bold ui-body-container-title">请上传手掌照左右各一张</div>
<div class="ui-body-container-item">
<div v-if="!bodyPic.leftPalm" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'4'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">左手掌</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.leftPalm, bodyPic.rightPalm], 0)">
<img class="ui-body-pic" :src="bodyPic.leftPalm" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('4', 0, '左手掌照')" />
</div>
<div v-if="!bodyPic.rightPalm" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'5'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">右手掌</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.rightPalm, bodyPic.leftPalm], 0)">
<img class="ui-body-pic" :src="bodyPic.rightPalm" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('5', 0, '右手掌照')" />
</div>
</div>
<div class="font_30 color3 bold ui-body-container-title">请上传伸舌照正脸伸舌头一张</div>
<div class="ui-body-container-item">
<div v-if="!bodyPic.tongue" class="ui-upload-box">
<uploadPicture :max-count="1" :type="'6'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">伸舌照</div>
</div>
</uploadPicture>
</div>
<div v-else class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.tongue], 0)">
<img class="ui-body-pic" :src="bodyPic.tongue" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('6', 0, '伸舌照')" />
</div>
</div>
<div class="font_30 color3 bold ui-body-container-title">身体其他问题照最多九张</div>
<div class="ui-body-container-item f-wrap">
<div v-if="bodyPic.other_img.length < 9" class="ui-upload-box">
<uploadPicture :max-count="9 - bodyPic.other_img.length" :type="'7'" @on-success="bodyOnSuccess">
<div class="ui-add-box">
<img class="ui-add-icon" src="https://image.fulllinkai.com/202310/30/c8524b7eb04ac3d525a37aabe283d2eb.png" alt="" />
<div class="font_24 color9">其他</div>
</div>
</uploadPicture>
</div>
<div v-for="(item, index) in bodyPic.other_img" :key="index" class="ui-body-pic ui-relative" @click="ImagePreview([bodyPic.other_img], index)">
<img class="ui-body-pic" :src="item" alt="" />
<img class="ui-upload-clear-icon" src="https://image.fulllinkai.com/202301/07/a069d2437562e00d298a9bcd253a86bd.png" alt="" @click.stop="clearBodyPic('7', index, '其他照')" />
</div>
</div>
</div>
<div class="color3 font_30 bold">标记异常项</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ bloodPressure.title }}</div>
<div v-for="(item, index) in bloodPressure.list" :key="index" class="ui-symptom-item" @click="selectSymptom('bloodPressure', 'pitchOnBloodPressure', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ sugar.title }}</div>
<div v-for="(item, index) in sugar.list" :key="index" class="ui-symptom-item" @click="selectSymptom('sugar', 'pitchOnSugar', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ renal.title }}</div>
<div v-for="(item, index) in renal.list" :key="index" class="ui-symptom-item" @click="selectSymptom('renal', 'pitchOnRenal', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ bloodFat.title }}</div>
<div v-for="(item, index) in bloodFat.list" :key="index" class="ui-symptom-item" @click="selectSymptom('bloodFat', 'pitchOnBloodFat', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ bloodRoutine.title }}</div>
<div v-for="(item, index) in bloodRoutine.list" :key="index" class="ui-symptom-item" @click="selectSymptom('bloodRoutine', 'pitchOnBloodRoutine', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
<div class="ui-symptom-box">
<div class="font_28 color3 bold ui-symptom-title">{{ liver.title }}</div>
<div v-for="(item, index) in liver.list" :key="index" class="ui-symptom-item" @click="selectSymptom('liver', 'pitchOnLiver', item, index)">
<div class="f-fcl">
<img v-if="item.state == 0" class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-else class="ui-symptom-icon" src="https://image.fulllinkai.com/202301/07/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
<div class="font_26 color3">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div class="ui-describe-box">
<div class="f-fbc">
<div class="font_30 color3 bold">补充描述</div>
<div class="font_26 color9">{{ describe.length }}/220</div>
</div>
<van-field v-model="describe" class="ui-desc-input font_30 color3" rows="5" type="textarea" maxlength="220" show-word-limit placeholder="请您详细描述内容,我们会用心服务每一位用户" autosize />
</div>
<div class="ui-next-btn font_30 f-fcc colorF" @click="changeData">保存</div>
<van-popup v-model:show="showTime" round position="bottom" :duration="0.5">
<van-date-picker v-model="timeValues" title="请选择你的体检时间" :min-date="minDate" :max-date="maxDate" @cancel="showTime = false" @confirm="onConfirm" />
</van-popup>
<van-popup v-model:show="showTips" round :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 bold text-center">系统扫描体检报告</div>
<img class="ui-tips-icon" src="https://image.fulllinkai.com/202305/08/7ef114a85a9476bcbff02d3123246941.png" alt="" />
<div class="color6 font_30">系统扫描会自动标记异常项系统识别需较长时间完成后会通知您</div>
<div class="f-fbc ui-tips-btn-box">
<div class="ui-tips-btn font_32 color6 f-fcc" @click="(type = '1'), (showTips = false)">手动标记</div>
<div class="ui-tips-btn-v2 font_32 colorF f-fcc" @click="(type = '2'), (showTips = false)">系统识别</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { showConfirmDialog, showImagePreview, showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'AppUploadReport' });
const userStore = useUserStore() as any;
const throttle = ref(true);
const pics = ref<any[]>([]);
const minDate = new Date(new Date().getFullYear() - 120, 0, 1);
const maxDate = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());
const showTime = ref(false); // key
const time = ref('');
const timeValues = ref<any[]>([]); //
const showTips = ref(false);
const num = ref(1);
const type = ref('1');
const reportType = ref<any>('0'); // 01
const bodyPic = ref<any>({
bodyFront: '',
bodyProfile: '',
bigHead: '',
leftPalm: '',
rightPalm: '',
tongue: '',
other_img: [],
}); //
const pitchOnBloodPressure = ref<any[]>([]);
const bloodPressure = ref({
title: '血压',
list: [
{ name: '收缩压', state: 0, value: 'sbp' },
{ name: '舒张压', state: 0, value: 'dbp' },
],
});
const pitchOnSugar = ref<any[]>([]);
const sugar = ref({
title: '糖检测',
list: [{ name: '空腹血糖', state: 0, value: 'fbg' }],
});
const pitchOnRenal = ref<any[]>([]);
const renal = ref({
title: '肾功能检测',
list: [
{ name: '尿素', state: 0, value: 'urea' },
{ name: '肌酐', state: 0, value: 'cre' },
{ name: '尿酸', state: 0, value: 'ua' },
{ name: '胱抑素C', state: 0, value: 'cvsc' },
],
});
const pitchOnBloodFat = ref<any[]>([]);
const bloodFat = ref({
title: '血脂四项检查',
list: [
{ name: '总胆固醇', state: 0, value: 'tc' },
{ name: '甘油三脂', state: 0, value: 'tg' },
{ name: '高密度脂蛋白', state: 0, value: 'hdl' },
{ name: '低密度脂蛋白', state: 0, value: 'ldl' },
],
});
const pitchOnBloodRoutine = ref<any[]>([]);
const bloodRoutine = ref({
title: '血常规/五分类',
list: [
{ name: '白细胞计数', state: 0, value: 'wbc' },
{ name: '平均RBC血红蛋白量', state: 0, value: 'mch' },
{ name: '红细胞计数', state: 0, value: 'rbc' },
{ name: 'RBC分布宽度标准差', state: 0, value: 'rdw' },
{ name: '红细胞比积', state: 0, value: 'hct' },
{ name: '平均RBC血红蛋白浓度', state: 0, value: 'mchc' },
{ name: '血小板计数', state: 0, value: 'plt' },
{ name: 'RBC分布宽度变异系数', state: 0, value: 'rdwcv' },
{ name: '血小板比积', state: 0, value: 'pct' },
{ name: '血小板体积分布宽带', state: 0, value: 'pdw' },
{ name: '淋巴细胞计数', state: 0, value: 'ly' },
{ name: '嗜酸粒细胞计数', state: 0, value: 'eos' },
{ name: '大血小板比率', state: 0, value: 'plcr' },
{ name: '嗜酸睡粒细胞比值', state: 0, value: 'eos_p' },
{ name: '单核细胞计数', state: 0, value: 'mono' },
{ name: '中性粒细胞计数', state: 0, value: 'gr' },
{ name: '单核细胞比值', state: 0, value: 'mono_p' },
{ name: '中性粒细胞比值', state: 0, value: 'gr_p' },
{ name: '淋巴细胞比值', state: 0, value: 'ly_p' },
{ name: '嗜碱性粒细胞计数', state: 0, value: 'baso' },
{ name: '平均RBC体积', state: 0, value: 'mcv' },
{ name: '嗜碱性粒细胞比值', state: 0, value: 'baso_p' },
{ name: '血红蛋白', state: 0, value: 'hgb' },
{ name: '平均血小板体积', state: 0, value: 'mpv' },
],
});
const pitchOnLiver = ref<any[]>([]);
const liver = ref({
title: '肝功三项',
list: [
{ name: '谷丙转氨酶', state: 0, value: 'alt' },
{ name: '谷草转氨酶', state: 0, value: 'ast' },
{ name: '总胆红素', state: 0, value: 'tbil' },
{ name: '直接胆红素', state: 0, value: 'dbil' },
],
});
const obj = reactive({ bloodPressure, pitchOnBloodPressure, sugar, pitchOnSugar, renal, pitchOnRenal, bloodFat, pitchOnBloodFat, bloodRoutine, pitchOnBloodRoutine, liver, pitchOnLiver });
const describe = ref('');
const changeData = () => {
let data = {
chat_id: userStore.chatId,
medical_report: pics.value,
medical_date: time.value,
desc: describe.value,
body_images: bodyPic.value,
type: reportType.value,
anomaly_type: type.value,
anomaly: type.value === '1' ? [pitchOnBloodPressure.value, pitchOnSugar.value, pitchOnRenal.value, pitchOnBloodFat.value, pitchOnBloodRoutine.value, pitchOnLiver.value] : [[], [], [], [], [], []],
};
if (pics.value.length === 0) {
showToast('请上传最少一张报告');
return;
}
if (!time.value) {
showToast('请选择体检时间');
return;
}
console.log(data, '7777');
if (throttle.value) {
throttle.value = false;
weChat({ url: `h5/order/user/health/report`, data, method: 'post' })
.then(() => {
throttle.value = true;
showToast('编辑成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'appViewUserInfo',
query: { currentTab: reportType.value == 0 ? 3 : 4 },
});
router.go(-1);
}, 1200);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const onSuccess = (val) => {
if (pics.value.length < 9) {
pics.value.push(val);
}
if (num.value === 1) {
num.value++;
showTips.value = true;
}
};
//
const clearPic = (index) => {
pics.value.splice(index, 1);
};
const ImagePreview = (e, index) => {
showImagePreview({
images: e,
showIndex: false,
startPosition: index,
loop: false,
});
};
//
const selectSymptom = (type, store, e, index) => {
if (e.state === 0) {
obj[store].push(e.value);
obj[type].list[index].state = 1;
} else {
obj[store] = obj[store].filter((item) => {
return item !== e.value;
});
obj[type].list[index].state = 0;
}
};
//
const onConfirm = ({ selectedValues }) => {
showTime.value = false;
time.value = `${selectedValues[0]}-${selectedValues[1]}-${selectedValues[2]}`;
};
//
const bodyOnSuccess = (val, type) => {
console.log(val, type);
if (type == 1) {
bodyPic.value.bodyFront = val;
} else if (type == 2) {
bodyPic.value.bodyProfile = val;
} else if (type == 3) {
bodyPic.value.bigHead = val;
} else if (type == 4) {
bodyPic.value.leftPalm = val;
} else if (type == 5) {
bodyPic.value.rightPalm = val;
} else if (type == 6) {
bodyPic.value.tongue = val;
} else {
if (bodyPic.value.other_img.length < 9) {
bodyPic.value.other_img.push(val);
}
}
};
//
const clearBodyPic = (type, index, tips) => {
showConfirmDialog({
title: '温馨提示',
message: `是否确认删除${tips}`,
})
.then(() => {
// on confirm
if (type == 1) {
bodyPic.value.bodyFront = '';
} else if (type == 2) {
bodyPic.value.bodyProfile = '';
} else if (type == 3) {
bodyPic.value.bigHead = '';
} else if (type == 4) {
bodyPic.value.leftPalm = '';
} else if (type == 5) {
bodyPic.value.rightPalm = '';
} else if (type == 6) {
bodyPic.value.tongue = '';
} else {
bodyPic.value.other_img.splice(index, 1);
}
})
.catch(() => {
// on cancel
});
};
window.getAndroidPhone = (e) => {
if (e && e.length > 0) {
e.forEach((item) => {
if (pics.value && pics.value.length < 9) {
pics.value.push(item);
}
});
}
};
const takePhone = () => {
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1 || ua.indexOf('android') != -1) {
// app
window.webAppInterface.takePhoto();
}
};
onMounted(() => {
let route = router.currentRoute.value.query;
reportType.value = route.reportType;
let date = new Date();
let m = date.getMonth() > 9 ? `${date.getMonth() + 1}` : `0${date.getMonth() + 1}`;
let d = date.getDate() > 9 ? `${date.getDate()}` : `0${date.getDate()}`;
timeValues.value = [`${date.getFullYear()}`, m, d];
});
</script>
<style lang="scss" scoped>
.ui-appUploadReport {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
padding: px2rem(30);
}
.ui-report-upload-box {
overflow: hidden;
margin-bottom: px2rem(30);
.ui-upload-icon-box {
position: relative;
.ui-upload-clear-icon {
position: absolute;
right: px2rem(30);
top: px2rem(30);
width: px2rem(36);
height: px2rem(36);
}
}
.ui-upload-icon {
width: px2rem(180);
height: px2rem(180);
display: block;
border-radius: px2rem(16);
margin-right: px2rem(20);
margin-top: px2rem(20);
object-fit: cover;
object-position: center;
}
}
.ui-examine-time {
padding: 0 px2rem(30);
margin-bottom: px2rem(50);
border-radius: px2rem(24);
background: #ffffff;
.m_basLst {
height: px2rem(100);
margin-top: px2rem(20);
.u_name_inp {
height: 100%;
flex: 1;
}
.inputColor {
color: #999999;
}
.ui-triangle-icon {
width: px2rem(10);
height: px2rem(20);
display: block;
margin-left: px2rem(20);
}
}
}
.ui-body-container {
.ui-body-container-title {
padding-bottom: px2rem(20);
}
.ui-body-container-item {
display: flex;
justify-content: left;
padding-bottom: px2rem(32);
.ui-upload-box {
overflow: hidden;
flex-flow: wrap;
}
.ui-body-pic,
.ui-add-box {
width: px2rem(180);
height: px2rem(180);
display: block;
border: px2rem(2) dashed #dadada;
border-radius: px2rem(24);
margin-right: px2rem(24);
margin-bottom: px2rem(18);
object-fit: cover;
object-position: center;
}
.ui-add-box {
background: #ffffff;
text-align: center;
.ui-add-icon {
width: px2rem(44);
height: px2rem(44);
display: block;
margin: px2rem(42) auto px2rem(14) auto;
}
}
.ui-upload-clear-icon {
position: absolute;
right: px2rem(12);
top: px2rem(12);
width: px2rem(28);
height: px2rem(28);
z-index: 99;
}
}
}
.ui-symptom-box {
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(24);
margin: px2rem(20) 0 px2rem(30) 0;
.ui-symptom-title {
margin-bottom: px2rem(-14);
}
.ui-symptom-item {
display: inline-block;
margin-top: px2rem(36);
margin-right: px2rem(40);
.ui-symptom-icon {
width: px2rem(28);
height: px2rem(28);
display: block;
margin-right: px2rem(10);
}
}
}
.ui-describe-box {
padding: px2rem(20) 0 px2rem(200) 0;
.ui-desc-input {
background: #ffffff;
border-radius: px2rem(24);
margin-top: px2rem(20);
}
.inputColor {
color: #c2c2c2;
}
}
.ui-next-btn {
position: fixed;
bottom: px2rem(90);
left: 50%;
transform: translateX(-50%);
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
z-index: 999;
}
.ui-tips-box {
width: px2rem(476);
padding: px2rem(60) px2rem(62) px2rem(50) px2rem(62);
background: #ffffff;
border-radius: px2rem(24);
.ui-tips-icon {
width: px2rem(192);
height: px2rem(192);
display: block;
margin: px2rem(30) auto px2rem(40) auto;
}
.ui-tips-btn-box {
padding: px2rem(50) px2rem(20) 0 px2rem(20);
.ui-tips-btn,
.ui-tips-btn-v2 {
width: px2rem(200);
height: px2rem(80);
border-radius: px2rem(40);
border: px2rem(2) solid #c2c2c2;
}
.ui-tips-btn-v2 {
background: #5ac7a0;
border: px2rem(2) solid #5ac7a0;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,771 @@
<template>
<div class="ui-appUserMenuAll">
<div style="background: #f8f8f8">
<menuSettingTop :detail="topDetail" @update:date="handleDateChange" @update:mode="handleModeChange"></menuSettingTop>
</div>
<div class="font_28 color76c ui-pt-16 ui-pb-16 text-center ui-menu-hint">请选择餐单类型后再点击日期设置餐单*</div>
<div class="ui-menu-date-box">
<div class="ui-select-box f-fcc">
<div v-for="(item, index) in typeList" :key="index" class="ui-select-item f-fcc" :style="{ background: index == typeIndex ? item.bg : '#ffffff' }">
<div class="ui-select-li ui-relative colorF" :class="[index === 0 ? 'ui-select-li-one' : index === 1 ? 'ui-select-li-two' : 'ui-select-li-three', index == typeIndex ? item.class : '']" @click="changeType(item, index)">
<div class="ui-pl-20 bold" :class="index == typeIndex ? 'ui-pt-28 font_32' : 'ui-pt-32 font_28'">{{ item.name }}</div>
<div class="font_24 ui-pl-20 ui-pt-24">已设置{{ item.day }}</div>
<img v-if="index == typeIndex && typeIndex == 0" class="ui-select-icon" src="https://image.fulllinkai.com/202411/05/ac350a6d384e13515829a50e92809632.png" alt="" />
<img v-if="index == typeIndex && typeIndex == 1" class="ui-select-icon" src="https://image.fulllinkai.com/202411/07/0ecb164256dd97c0eddc3a0e73528fc3.png" alt="" />
<img v-if="index == typeIndex && typeIndex == 2" class="ui-select-icon" src="https://image.fulllinkai.com/202411/07/0a75f467ed97ff30277eac153f8d3d4b.png" alt="" />
</div>
</div>
</div>
<div class="ui-calendar-box">
<div class="f-fbc">
<div class="font_36 color0E bold">{{ typeData.name }}</div>
<div class="font_28 color76c">{{ typeData.day }}</div>
</div>
<div class="ui-calendar-date">
<div class="calendar_title f-fbc">
<img class="triangle_icon" src="https://image.fulllinkai.com/202411/05/fd04e833fb45ca7c14d25a877d4c9581.png" alt="" @click="upMonth" />
<div class="font_28 color0E">{{ year }}{{ month }}</div>
<img class="triangle_icon_v2" src="https://image.fulllinkai.com/202411/05/358000854fe0ad24249b85f64640f9a4.png" alt="" @click="nextMonth" />
</div>
<div class="week_box">
<div v-for="(item, index) in weekList" :key="index" class="font_28 text-center week_num">{{ item }} </div>
</div>
</div>
<div class="calendar_day">
<div v-for="index in nbsp" :key="index + '-only'" class="day_number font_28 colorF"></div>
<div v-for="(item, index) in signInList" :key="index" class="day_number">
<div v-if="item == 1 || item == 2" class="number_box menu text-center" @click.stop="getMenu(item, index + 1)">
<div class="bold font_32 ui-pt-10">{{ index + 1 }}</div>
<div class="font_20 ui-text-pt">准备</div>
</div>
<div v-else-if="item == 3" class="number_box menuV2 text-center" @click.stop="getMenu(item, index + 1)">
<div class="bold font_32 ui-pt-10">{{ index + 1 }}</div>
<div class="font_20 ui-text-pt">装修</div>
</div>
<div v-else-if="item == 4" class="number_box menuV3 text-center" @click.stop="getMenu(item, index + 1)">
<div class="bold font_32 ui-pt-10">{{ index + 1 }}</div>
<div class="font_20 ui-text-pt">清洁</div>
</div>
<div v-else class="number_box text-center" @click.stop="getMenu(item, index + 1)">
<div class="bold font_32 ui-pt-10">{{ index + 1 }}</div>
<div class="font_20 colorF ui-text-pt">--</div>
</div>
</div>
</div>
</div>
</div>
<div class="ui-btn-box">
<div v-if="!modelText && !modeDate" class="ui-save-btn font_32 f-fcc colorF" @click="addMenuAffirm(1)">保存</div>
<div v-else class="ui-save-btn font_32 f-fcc colorF" @click="addMenuModal(1)">保存模式并重新生成餐单</div>
<div class="ui-save-list" @click="toCheckList">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
<path d="M8.88867 1.55566C12.9388 1.55566 16.2217 4.83858 16.2217 8.88867C16.2217 12.9388 12.9388 16.2217 8.88867 16.2217C4.83858 16.2217 1.55566 12.9388 1.55566 8.88867C1.55566 4.83858 4.83858 1.55566 8.88867 1.55566Z" stroke="#66676C" stroke-width="1.33333" />
<path d="M8.88867 5.77783V9.33339L10.6664 11.1112" stroke="#66676C" stroke-width="1.33333" stroke-linecap="round" />
</svg>
<div>变更历史</div>
</div>
</div>
<van-popup v-model:show="showCheck" round :duration="0.5">
<div class="menu-setting-change-modal-v2">
<div class="menu-setting-change-m-title">模式变更历史</div>
<div class="menu-setting-change-m-big-card">
<div v-for="(item, index) in operatorList" :key="index" class="menu-setting-change-m-card">
<div class="menu-setting-change-m-c-title">修改为{{ item.model == 1 ? '3+4模式' : '1+6模式' }}</div>
<div class="menu-setting-change-m-c-operator">操作人{{ item.service_user_name }}</div>
<div class="menu-setting-change-m-c-operator">时间{{ item.create_time }}</div>
</div>
</div>
<svg class="close-box" xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none" @click="showCheck = false">
<g clip-path="url(#clip0_9806_26105)">
<path opacity="0.2" d="M13 26C20.1797 26 26 20.1797 26 13C26 5.8203 20.1797 0 13 0C5.8203 0 0 5.8203 0 13C0 20.1797 5.8203 26 13 26Z" fill="white" />
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M7.64777 7.61515C7.821 7.45127 8.09291 7.45406 8.26273 7.62147L18.4382 17.6524C18.6005 17.8124 18.6024 18.0738 18.4424 18.2362C18.439 18.2396 18.4356 18.2429 18.4321 18.2462C18.2588 18.4101 17.9869 18.4073 17.8171 18.2399L7.64166 8.20903C7.4793 8.04897 7.47743 7.78759 7.63749 7.62522C7.64086 7.62181 7.64428 7.61845 7.64777 7.61515Z" fill="white" stroke="white" stroke-width="0.3" />
<path opacity="0.8" fill-rule="evenodd" clip-rule="evenodd" d="M18.3542 7.61515C18.181 7.45127 17.909 7.45406 17.7392 7.62147L7.56378 17.6524C7.40141 17.8124 7.39954 18.0738 7.5596 18.2362C7.56297 18.2396 7.5664 18.2429 7.56988 18.2462C7.74311 18.4101 8.01502 18.4073 8.18485 18.2399L18.3603 8.20903C18.5227 8.04897 18.5245 7.78759 18.3645 7.62522C18.3611 7.62181 18.3577 7.61845 18.3542 7.61515Z" fill="white" stroke="white" stroke-width="0.3" />
</g>
<defs>
<clipPath id="clip0_9806_26105">
<rect width="26" height="26" fill="white" />
</clipPath>
</defs>
</svg>
</div>
</van-popup>
<van-popup v-model:show="showChange" round :duration="0.5">
<div class="menu-setting-change-modal">
<div class="menu-setting-change-m-title">确认生成新餐单</div>
<div class="menu-setting-change-m-sub">切换至{{ modelText }}历史的餐单记录会被更新</div>
<div class="f-fbc color76c ui-examine-box">
<div class="ui-examine-btn f-fcc" @click="showChange = false">取消</div>
<div class="ui-examine-btn-v2 f-fcc" @click="modalSave()">确认通过</div>
</div>
</div>
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { showLoadingToast, showToast, showSuccessToast } from 'vant';
import weChat from '@/utils/weChat';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
import menuSettingTop from '@/components/menuSettingTop.vue';
import requestGo from '@/utils/requestGo';
defineOptions({ name: 'AppUserMenuAll' });
const userStore = useUserStore() as any;
const role = ref<any>(0);
const isEdit = ref<any>(false);
const chiefCoach = ref(false); //
const viceCoach = ref(false); //
const service = ref(false); //
const schemeStatus = ref<any>(''); //
const throttle = ref(true);
const isAudit = ref<any>(false); //
const loading = ref(false);
const typeList = ref<any>([
{ name: '准备日', class: 'ui-select-li-one-act', bg: '#ffeedb', type: 0, day: 0 },
{ name: '清洁日', class: 'ui-select-li-two-act', bg: '#e2fcee', type: 2, day: 0 },
{ name: '装修日', class: 'ui-select-li-three-act', bg: '#edeaff', type: 1, day: 0 },
]);
const typeData = ref<any>(typeList.value[0]);
const typeIndex = ref<any>(0);
const selectList = ref<any[]>([]);
const signInList = ref<any[]>([]); //
const weekList = ref<any[]>(['日', '一', '二', '三', '四', '五', '六']);
const currentDate = ref<any>(''); //
const year = ref<any>(''); //
const day = ref<any>(''); //
const month = ref<any>(''); //
const date = ref<any>(''); //
const nbsp = ref<any>([]); //
//
const getRecipe = () => {
signInList.value = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
if (!loading.value) {
loading.value = true;
weChat({ url: `/h5/user/guides?date=${year.value}-${month.value}&chat_id=${userStore.chatId}&is_im=1&role=1`, method: 'get' })
.then((res) => {
const result = res.data;
signInList.value = result.date_arr;
schemeStatus.value = result.scheme_status;
isAudit.value = result.template_is_check;
if (result.guides && result.guides.length > 0) {
result.guides.forEach((item) => {
JSON.parse(
JSON.stringify(
selectList.value.push({
date: item.phase_date,
type: item.phase_title == '准备日' ? 0 : item.phase_title == '装修日' ? 1 : 2,
}),
),
);
signInList.value.forEach((j, index) => {
if (item.phase_date) {
if (index == item.phase_date.split('-')[2] * 1 - 1) {
if (item.phase_title == '准备日') {
signInList.value[index] = 1;
} else if (item.phase_title == '装修日') {
signInList.value[index] = 3;
} else if (item.phase_title == '清洁日') {
signInList.value[index] = 4;
}
}
}
});
});
}
let arr = selectList.value.map((item) => {
return item.date;
});
selectList.value = selectList.value.filter((item, index) => {
return arr.indexOf(item.date) == index;
});
loading.value = false;
})
.catch((err) => {
loading.value = false;
console.log(err);
});
}
};
const getSettings = () => {
weChat({ url: `/h5/user/guides?date=${year.value}-${month.value}&chat_id=${userStore.chatId}&is_im=1&role=1`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
typeList.value[0].day = result.prepare_count;
typeList.value[1].day = result.clean_count;
typeList.value[2].day = result.trim_count;
typeData.value = typeList.value[typeIndex.value];
})
.catch((err) => {
console.log(err);
});
};
const addMenuAffirm = (state) => {
let m = month.value > 9 ? month.value : `0${month.value}`;
let date = `${year.value}-${m}`;
let data = {
chat_id: userStore.chatId,
guide_arr: selectList.value,
month: date,
is_send: isEdit.value ? state : 0,
};
if (throttle.value) {
throttle.value = false;
if (state) {
showLoadingToast('保存中...');
}
weChat({ url: `h5/batch/set/user/guide`, data, hideLoading: true, method: 'post' })
.then(() => {
throttle.value = true;
if (state) {
showToast('保存成功');
isEdit.value = false;
}
getSettings();
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
//
const initCalendar = () => {
currentDate.value = new Date(); //
year.value = currentDate.value.getFullYear(); //
month.value = currentDate.value.getMonth() + 1; //
date.value = currentDate.value.getDate(); //
day.value = currentDate.value.getDay() + 1; //
let store = `${year.value}-${month.value}-01`;
day.value = new Date(store.replace(/-/g, '/')).getDay(); // 1
if (day.value == 0) {
nbsp.value = 0;
} else {
nbsp.value = day.value;
}
getRecipe();
getSettings();
};
//
const upMonth = () => {
if (loading.value) {
showToast('操作频率过快');
return;
}
year.value = month.value < 2 ? year.value - 1 : year.value;
month.value = month.value < 2 ? 12 : month.value - 1;
currentDate.value = new Date(year.value, month.value - 1, date.value);
let store = `${year.value}-${month.value}-01`;
day.value = new Date(store.replace(/-/g, '/')).getDay(); // 1
if (day.value == 0) {
nbsp.value = 0;
} else {
nbsp.value = day.value;
}
getRecipe();
getSettings();
};
//
const nextMonth = () => {
if (loading.value) {
showToast('操作频率过快');
return;
}
year.value = month.value > 11 ? year.value + 1 : year.value;
month.value = month.value > 11 ? 1 : month.value + 1;
currentDate.value = new Date(year.value, month.value - 1, date.value);
day.value = currentDate.value.getDay() + 1;
let store = `${year.value}-${month.value}-01`;
day.value = new Date(store.replace(/-/g, '/')).getDay(); // 1
if (day.value == 0) {
nbsp.value = 0;
} else {
nbsp.value = day.value;
}
getRecipe();
getSettings();
};
//
const getMenu = (item, index) => {
let m = month.value > 9 ? month.value : `0${month.value}`;
console.log(month.value, '-==');
let d = index > 9 ? index : `0${index}`;
let t = `${year.value}-${m}-${d}`;
//
if ((signInList.value[index - 1] == 1 || signInList.value[index - 1] == 2) && typeIndex.value == 0) {
signInList.value[index - 1] = 0;
} else if (signInList.value[index - 1] == 3 && typeIndex.value == 2) {
signInList.value[index - 1] = 0;
} else if (signInList.value[index - 1] == 4 && typeIndex.value == 1) {
signInList.value[index - 1] = 0;
} else {
if (typeIndex.value == 0) {
signInList.value[index - 1] = 1;
} else if (typeIndex.value == 1) {
signInList.value[index - 1] = 4;
} else {
signInList.value[index - 1] = 3;
}
let state = false;
//
selectList.value.forEach((item, idx) => {
if (item.date == t) {
state = true;
selectList.value[idx] = { date: t, type: typeIndex.value == 0 ? typeIndex.value : typeIndex.value == 1 ? 2 : 1 };
}
});
//
if (!state) {
selectList.value.push({ date: t, type: typeIndex.value == 0 ? typeIndex.value : typeIndex.value == 1 ? 2 : 1 });
}
if (item != signInList.value[index - 1]) {
isEdit.value = true;
}
addMenuAffirm(0);
return;
}
if (item != signInList.value[index - 1]) {
isEdit.value = true;
}
//
selectList.value.forEach((item, index) => {
if (item.date == t) {
selectList.value.splice(index, 1);
}
});
addMenuAffirm(0);
};
const changeType = (e, index) => {
typeData.value = e;
typeIndex.value = index;
};
//
const showCheck = ref(false);
const toCheckList = () => {
showCheck.value = true;
};
const modelText = ref('');
const modeDate = ref('');
const showChange = ref(false); //
const handleDateChange = (e, e2) => {
console.log(e, 'e1--');
modeDate.value = e;
modelText.value = e2;
};
const handleModeChange = (e, e2) => {
console.log(e, 'e2--');
modelText.value = e;
modeDate.value = e2;
};
const addMenuModal = () => {
showChange.value = true;
};
const modalSave = async () => {
if (!modelText.value) {
showToast('请选择模式');
return;
}
if (!modeDate.value) {
showToast('请选择日期');
return;
}
let data = {
model: modelText.value == '减肥型' ? 1 : 2,
start_date: modeDate.value,
service_user_id: userStore.serviceUserId - 0,
};
console.log(data, 'd===');
let res = await requestGo({ url: `app/server/model/set/guide?chat_id=${userStore.chatId}`, method: 'post', data });
if (res.code == 0) {
showSuccessToast('修改成功');
showChange.value = false;
initCalendar();
}
};
const topDetail = ref();
const operatorList = ref([]);
const getUserDetail = async () => {
let res = await requestGo({ url: `app/server/model/guide/info?chat_id=${userStore.chatId}` });
topDetail.value = res.data;
};
const getOperatorList = async () => {
let res = await requestGo({ url: `app/server/model/guide/log?chat_id=${userStore.chatId}` });
console.log(res, 'rrr===');
operatorList.value = res.data;
};
onMounted(() => {
let route = router.currentRoute.value.query;
if (role.value) {
role.value.split(',').forEach((item) => {
if (item == '1') {
chiefCoach.value = true;
}
if (item == '2') {
viceCoach.value = true;
}
if (item == '3') {
service.value = true;
}
});
}
if (route.chat_id) {
userStore.chatId = route.chat_id;
}
initCalendar();
getUserDetail();
getOperatorList();
});
</script>
<style lang="scss">
.ui-appUserMenuAll {
.van-popup {
overflow-y: initial;
}
}
</style>
<style lang="scss" scoped>
.ui-appUserMenuAll {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-menu-hint {
background: #f8f8f8;
}
.ui-menu-date-box {
padding: px2rem(32) 0 px2rem(260) 0;
background: #ffffff;
.ui-select-box {
.ui-select-item {
width: px2rem(236);
height: px2rem(198);
border-radius: px2rem(28) px2rem(92) px2rem(28) px2rem(28);
background: #ffffff;
.ui-select-li {
width: px2rem(196);
height: px2rem(160);
border-radius: px2rem(20) px2rem(72) px2rem(20) px2rem(20);
.ui-select-icon {
width: px2rem(36);
height: px2rem(36);
position: absolute;
right: 0;
top: 0;
}
}
.ui-select-li-one {
background: #ffa949;
}
.ui-select-li-two {
background: #18ca6e;
}
.ui-select-li-three {
background: #9679ff;
}
.ui-select-li-one-act:before {
content: '';
width: 0;
height: 0;
position: absolute;
bottom: px2rem(-26);
left: 50%;
transform: translate(-50%);
border-top: solid px2rem(14) #ffa949;
border-left: solid px2rem(14) transparent;
border-right: solid px2rem(14) transparent;
border-bottom: solid px2rem(14) transparent;
}
.ui-select-li-two-act:before {
content: '';
width: 0;
height: 0;
position: absolute;
bottom: px2rem(-26);
left: 50%;
transform: translate(-50%);
border-top: solid px2rem(14) #18ca6e;
border-left: solid px2rem(14) transparent;
border-right: solid px2rem(14) transparent;
border-bottom: solid px2rem(14) transparent;
}
.ui-select-li-three-act:before {
content: '';
width: 0;
height: 0;
position: absolute;
bottom: px2rem(-26);
left: 50%;
transform: translate(-50%);
border-top: solid px2rem(14) #9679ff;
border-left: solid px2rem(14) transparent;
border-right: solid px2rem(14) transparent;
border-bottom: solid px2rem(14) transparent;
}
}
}
.ui-title {
color: #fdae18;
}
.ui-title-v2 {
color: #148a5f;
}
.ui-title-v3 {
color: #00c897;
}
.ui-calendar-box {
height: 100%;
padding: 0 px2rem(30);
background: #ffffff;
border-radius: px2rem(24) px2rem(24) 0 0;
margin-top: px2rem(40);
.ui-calendar-date {
background: linear-gradient(to bottom, #f8f8f8 0%, #ffffff 100%);
height: px2rem(174);
border-radius: px2rem(32) px2rem(32) 0 0;
margin: px2rem(20) 0 px2rem(40) 0;
.calendar_title {
padding: 0 px2rem(20);
height: px2rem(94);
.triangle_icon,
.triangle_icon_v2 {
width: px2rem(54);
height: px2rem(54);
display: block;
}
}
.week_box {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding-top: px2rem(40);
.week_num {
width: 14.2%;
}
}
}
.calendar_day {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.day_number {
width: 14.2%;
padding-bottom: px2rem(20);
.number_box {
width: px2rem(76);
height: px2rem(88);
border-radius: px2rem(12);
background: #ffffff;
margin: 0 auto;
.ui-text-pt {
padding-top: px2rem(2);
}
}
.menu {
color: #ffa949;
background: #ffeedb;
}
.menuV2 {
color: #8670ff;
background: #f3f1ff;
}
.menuV3 {
color: #18ca6e;
background: #e2fcee;
}
}
}
.calendar_day:after {
content: '';
flex: auto;
}
}
}
.ui-save-btn {
width: px2rem(686);
height: px2rem(100);
border-radius: px2rem(50);
background: #18ca6e;
}
.ui-btn-box {
position: fixed;
bottom: 0;
left: 0;
width: -webkit-fill-available;
display: flex;
padding: 10px 10px 30px 10px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
background: #fff;
}
.ui-save-list {
display: flex;
align-items: center;
gap: 6px;
color: #66676c;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.menu-setting-change-modal {
width: 284px;
padding-top: 26px;
gap: 20px;
border-radius: 16px;
background: #fff;
backdrop-filter: blur(18px);
.menu-setting-change-m-title {
margin-bottom: 12px;
text-align: center;
color: #0e0e0e;
font-size: 18px;
font-style: normal;
font-weight: bold;
line-height: normal;
}
.menu-setting-change-m-sub {
padding: 0 12px 20px;
color: #0e0e0e;
text-align: justify;
font-size: 15px;
font-style: normal;
font-weight: 400;
line-height: 25px;
}
.ui-examine-box {
height: 50px; /* 原px2rem(100) */
border-top: 1px solid #eeeeee; /* 原px2rem(2) */
font-size: 16px;
.ui-examine-btn {
position: relative;
width: 50%;
}
.ui-examine-btn:after {
content: '';
position: absolute;
right: 0;
width: 1px; /* 原px2rem(2) */
height: 50px; /* 原px2rem(100) */
background: #eeeeee;
}
.ui-examine-btn-v2 {
width: 50%;
color: #16ca6e;
}
.ui-examine-btn-v3 {
width: 50%;
color: #ff2946;
}
}
}
.menu-setting-change-modal-v2 {
position: relative;
display: flex;
padding: 26px 20px;
flex-direction: column;
align-items: center;
width: 284px;
.menu-setting-change-m-title {
margin-bottom: 12px;
color: #0e0e0e;
font-size: 18px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.menu-setting-change-m-big-card {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
gap: 16px;
max-height: 400px;
overflow-y: scroll;
}
.menu-setting-change-m-card {
display: flex;
padding: 16px;
flex-direction: column;
align-items: flex-start;
gap: 6px;
align-self: stretch;
border-radius: 10px;
border: 0.3px solid #ddd;
.menu-setting-change-m-c-title {
color: #0e0e0e;
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.menu-setting-change-m-c-operator {
color: #66676c;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
}
.close-box {
position: absolute;
bottom: -40px;
left: 50%;
transform: translate(-50%);
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,196 @@
<template>
<div ref="scrollDistance" class="ui-appWorkOrder" @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/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div class="color6 font_30 text-center">暂无工单</div>
</div>
<div v-else>
<div v-for="(item, index) in list" :key="index" class="ui-work-order-box">
<div class="ui-work-order-item">
<div class="f-fbc">
<div class="color3 font_26">工单号{{ item.work_order }}</div>
<div v-if="item.status == 0" class="font_26 f-fcc colorTheme">已提交</div>
<div v-else-if="item.status == 1" class="font_26 f-fcc color3">处理中</div>
<div v-else-if="item.status == 2" class="font_26 f-fcc colorTheme">已处理</div>
<div v-else class="font_26 f-fcc colorPrice">已取消</div>
</div>
<div class="ui-line-between"></div>
<div class="ui-container">
<div class="font_28 color3 bold">工单说明</div>
<div class="ui-explain-text font_28 color3">{{ item.desc }}</div>
<div v-if="item.images && item.images.length > 0" class="ui-explain-pic-box f-fcl">
<img v-for="(itemV2, indexV2) in item.images" :key="indexV2" class="ui-explain-pic" :src="itemV2" alt="" @click="ImagePreview(item.images, indexV2)" />
</div>
<div v-if="item.deal_remark">
<div class="font_28 color3 bold ui-pt-40">解决方案</div>
<div class="ui-explain-text font_28 color3">{{ item.deal_remark }}</div>
<div v-if="item.deal_images && item.deal_images.length > 0" class="ui-explain-pic-box f-fcl">
<img v-for="(itemV2, indexV2) in item.deal_images" :key="indexV2" class="ui-explain-pic" :src="itemV2" alt="" @click="ImagePreview(item.deal_images, indexV2)" />
</div>
</div>
</div>
</div>
</div>
<div v-if="noMore" class="ui-no-more font_24 color6 text-center">我也是有底线的</div>
</div>
<div class="ui-add-btn f-fcc colorF font_30 bold" @click="jumpPath('appAddWorkOrder')">添加工单</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, onActivated } from 'vue';
import { useUserStore } from '@/store/modules/user';
import weChat from '@/utils/weChat';
import router from '@/router';
defineOptions({ name: 'AppWorkOrder' });
const userStore = useUserStore() as any; // pinia
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 = () => {
weChat({ url: `/h5/get/work/order?page=${page.value}&chat_id=${userStore.chatId}`, method: 'get' })
.then((res) => {
const result = res.data;
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;
}
loadingState.value = true;
page.value++;
})
.catch((err) => {
loadingState.value = true;
console.log(err);
});
};
//
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
const jumpPath = (url) => {
router.push({
name: url,
});
};
//
const handleScroll = (event) => {
scrollValue.value = event.target.scrollTop;
};
onActivated(() => {
let route = router.currentRoute.value.query;
//
if (route.state) {
onRefresh();
} else {
//
scrollDistance.value.scrollTop = scrollValue.value;
}
});
onMounted(() => {});
</script>
<style lang="scss" scoped>
.ui-appWorkOrder {
background: #f8f8f8;
overflow-y: auto;
height: 100vh;
}
.ui-work-order-box {
margin: px2rem(24) px2rem(30) px2rem(30) px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(24);
.ui-work-order-item {
background: #ffffff;
border-radius: px2rem(24);
.ui-line-between {
width: 100%;
height: px2rem(2);
background: #f8f8f8;
margin-top: px2rem(24);
}
.ui-container {
padding-top: px2rem(30);
.ui-explain-text {
white-space: pre-wrap;
word-wrap: break-word;
word-break: normal;
padding-top: px2rem(16);
}
.ui-explain-pic-box {
flex-flow: wrap;
.ui-explain-pic {
width: px2rem(140);
height: px2rem(140);
display: block;
object-fit: cover;
object-position: center;
margin-right: px2rem(16);
margin-top: px2rem(16);
border-radius: px2rem(12);
}
}
}
}
}
.ui-no-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 24vh auto px2rem(20) auto;
}
.ui-no-more {
padding: px2rem(20) px2rem(20) px2rem(220) px2rem(20);
}
.ui-add-btn {
position: fixed;
bottom: px2rem(80);
left: 50%;
transform: translateX(-50%);
width: px2rem(560);
height: px2rem(80);
background: #5ac7a0;
border-radius: px2rem(40);
}
</style>

View File

@ -0,0 +1,289 @@
<template>
<div v-if="loading" class="ui-boundEnterprise">
<div>
<div class="ui-title font_48 color3 bold">绑定订单职责</div>
<div class="font_28 color3 ui-pl-32 ui-pb-32">当前可绑定职责</div>
<div v-if="typeList && typeList.length > 0" class="ui-item-select">
<div v-for="(item, index) in typeList" :key="index" class="f-fbc ui-pb-36" @click="selectChange(item)">
<div class="f-fcl">
<img v-if="item.name == '主教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202305/06/103763ce22be9a1bd964080a6b45bdf0.png" alt="" />
<img v-else-if="item.name == '副教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202305/06/b73e57901f943fa31eec78b21c8e8eac.png" alt="" />
<img v-else-if="item.name == '客服'" class="ui-duty-icon" src="https://image.fulllinkai.com/202305/06/6a96db6ec2a77503b84e6213f9d905a6.png" alt="" />
<img v-else class="ui-duty-icon" src="https://image.fulllinkai.com/202305/06/697595eb7b086798bef1706563ce3b92.png" alt="" />
<div class="font_32 color3 ui-item-select-text">{{ item.name }}</div>
</div>
<div>
<img v-show="!item.state" class="ui-item-select-icon" src="https://image.fulllinkai.com/202306/25/ba1565f2cf6d59945ec6f522c8caaa5d.png" alt="" />
<img v-show="item.state" class="ui-item-select-icon" src="https://image.fulllinkai.com/202306/25/1c57dd3dd3d4062255c159d73a437b83.png" alt="" />
</div>
</div>
<div class="ui-btn f-fcc font_30 colorF bold" @click="saveRole">确认绑定</div>
<div class="font_24 color9 text-center">绑定后可使用该职责</div>
</div>
<div v-else>
<img class="ui-no-role-icon" src="https://image.fulllinkai.com/202306/07/247d8ae6b90334457d1b39129cd5c490.png" alt="" />
<div class="font_30 color9 text-center">暂无可绑定的职责</div>
</div>
</div>
</div>
<div v-else class="ui-skeleton">
<div class="f-fcl">
<div class="ui-skeleton-photo"></div>
<div>
<div class="ui-skeleton-name"></div>
<div class="ui-skeleton-name-v2"></div>
</div>
</div>
<div class="ui-skeleton-line"></div>
<div v-for="(item, index) in skeleton" :key="index" class="f-fcl">
<div class="ui-skeleton-item">{{ item }}</div>
<div class="ui-skeleton-item-v2"></div>
</div>
<div class="font_28 color9 text-center">-- 加载中 --</div>
</div>
<img class="ui-icon" src="https://image.fulllinkai.com/202306/26/05b70e1b55dfed082b28fda935653343.png" alt="" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { showToast } from 'vant';
import weChat from '@/utils/weChat';
import router from '@/router';
import { useUserStore } from '@/store/modules/user';
defineOptions({ name: 'BoundEnterprise' });
const userStore = useUserStore() as any;
const loading = ref(false);
const isBind = ref<any>(''); //
const isRole = ref<any>(''); //
const isRoleIdList = ref<any[]>([]); //
const typeList = ref<any[]>([]);
const throttle = ref<any>(true);
const skeleton = ref<any[]>(['', '', '', '', '', '']);
// is_bind: true, false. is_role: true, false. role_list
const getState = () => {
weChat({ url: `/h5/work/${userStore.chatId}/group/orders/status`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
userStore.weChatBindState = result.status;
userStore.chiefCoach = result.is_main_coach;
userStore.coach = result.is_coach;
userStore.service = result.is_customer;
isBind.value = result.is_bind;
isRole.value = result.is_role;
if (isRole.value) {
router.replace({
name: 'personalCenter',
});
return;
}
if (result.role_list && result.role_list.length > 0) {
result.role_list.forEach((item) => {
typeList.value.push({
id: item.id,
name: item.name,
state: 0,
});
});
}
loading.value = true;
})
.catch((err) => {
console.log(err);
});
};
//
const saveRole = () => {
if (throttle.value) {
let data = {
chat_id: userStore.chatId,
role_ids: isRoleIdList.value,
};
if (isRoleIdList.value.length === 0) {
showToast('请选择需要绑定的职责');
return;
}
throttle.value = false;
weChat({ url: `h5/order/bind/role`, data, method: 'post' })
.then((res) => {
let result = res.data;
console.log(result, '7777');
showToast('绑定成功');
setTimeout(() => {
throttle.value = true;
router.replace({
name: 'personalCenter',
});
}, 1000);
})
.catch((err) => {
throttle.value = true;
console.log(err);
});
}
};
const selectChange = (e) => {
console.log(e, '7777');
if (e.state === 0) {
isRoleIdList.value.push(e.id);
e.state = 1;
} else {
isRoleIdList.value = isRoleIdList.value.filter((id) => {
return id !== e.id;
});
e.state = 0;
}
console.log(isRoleIdList.value);
};
onMounted(() => {
let route = router.currentRoute.value.query;
console.log(route);
if (route.chat_id) {
userStore.chatId = route.chat_id;
}
userStore.serviceUserId = route.service_user_id;
getState();
});
</script>
<style lang="scss" scoped>
.ui-boundEnterprise {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-title {
padding: px2rem(120) px2rem(0) px2rem(70) px2rem(50);
}
.ui-item-select {
position: relative;
z-index: 99;
padding: 0 px2rem(30);
.ui-duty-icon {
width: px2rem(40);
height: px2rem(40);
display: block;
margin-right: px2rem(4);
}
.ui-item-select-icon {
width: px2rem(36);
height: px2rem(36);
display: block;
margin-right: px2rem(10);
}
.ui-item-select-text {
line-height: px2rem(40);
}
}
.ui-mobile {
position: relative;
z-index: 33;
margin: px2rem(0) px2rem(50);
border-bottom: px2rem(2) solid #d8d8d8;
.ui-mobile-text {
line-height: px2rem(44);
}
.ui-user-input {
width: px2rem(480);
font-size: px2rem(32);
padding: px2rem(24) px2rem(12);
border-radius: px2rem(16);
background: initial;
line-height: initial;
}
// 线
::v-deep(.van-cell:after) {
position: relative;
}
::v-deep(input::-webkit-input-placeholder) {
color: #999999;
}
}
.ui-no-role-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: px2rem(40) auto px2rem(20) auto;
}
.ui-btn {
position: relative;
z-index: 33;
width: px2rem(650);
height: px2rem(88);
background: #5ac7a0;
border-radius: px2rem(40);
margin: px2rem(100) auto px2rem(30) auto;
}
.ui-icon {
width: 100vw;
height: px2rem(946);
position: fixed;
bottom: 0;
left: 0;
}
.ui-skeleton {
position: relative;
z-index: 2;
padding: px2rem(60);
.ui-skeleton-photo {
width: px2rem(120);
height: px2rem(120);
border-radius: 50%;
background: #f5f5f5;
display: block;
margin-right: px2rem(40);
}
.ui-skeleton-name,
.ui-skeleton-name-v2 {
width: px2rem(160);
height: px2rem(30);
border-radius: px2rem(16);
background: #f5f5f5;
}
.ui-skeleton-name-v2 {
width: px2rem(240);
margin-top: px2rem(16);
}
.ui-skeleton-line {
width: 100%;
height: px2rem(2);
background: #f5f5f5;
margin: px2rem(30) 0 px2rem(60) 0;
}
.ui-skeleton-item,
.ui-skeleton-item-v2 {
width: px2rem(52);
height: px2rem(52);
border-radius: px2rem(26);
background: #f5f5f5;
margin-bottom: px2rem(60);
}
.ui-skeleton-item-v2 {
width: px2rem(480);
margin-left: px2rem(22);
}
}
</style>

View File

@ -0,0 +1,460 @@
<template>
<div class="ui-serviceCentre">
<div class="ui-service-list-box">
<div class="ui-service-top">
<div v-if="!name && roles.length == 0" class="f-fcl">
<img class="ui-logo-icon" src="https://image.fulllinkai.com/202306/26/b1e72421e6ff68eacbdb43816794048c.png" alt="" />
<div class="font_32 color3 bold">还未绑定角色<span class="colorTheme" @click="contactService">联系</span>管理员</div>
</div>
<div v-else-if="!name">
<div class="f-fcl">
<img class="ui-logo-icon" src="https://image.fulllinkai.com/202306/26/b1e72421e6ff68eacbdb43816794048c.png" alt="" />
<div>
<div class="font_32 color3 bold">友福同享 客服系统</div>
<div v-if="roles && roles.length > 0" class="f-fcl">
<div v-for="(item, index) in roles" :key="index" class="ui-duty-box f-fcl">
<img v-if="item == '主教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/103763ce22be9a1bd964080a6b45bdf0.png" alt="" />
<img v-else-if="item == '副教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/b73e57901f943fa31eec78b21c8e8eac.png" alt="" />
<img v-else class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/6a96db6ec2a77503b84e6213f9d905a6.png" alt="" />
<div class="font_24 color6">{{ item }}</div>
</div>
</div>
<div v-if="userStore.weChatBindState == 2" class="font_22 color9 ui-pt-8">订单号{{ orderNum || '--' }}</div>
</div>
</div>
</div>
<div v-else class="ui-user-box">
<img class="ui-logo-icon" src="https://image.fulllinkai.com/202306/26/b1e72421e6ff68eacbdb43816794048c.png" alt="" />
<div>
<div class="font_32 bold color3">{{ name }}</div>
<div class="font_28 color6 ui-pt-6">手机号{{ mobile }}</div>
<div v-if="roles && roles.length > 0" class="f-fcl">
<div v-for="(item, index) in roles" :key="index" class="ui-duty-box f-fcl">
<img v-if="item == '主教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/103763ce22be9a1bd964080a6b45bdf0.png" alt="" />
<img v-else-if="item == '副教练'" class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/b73e57901f943fa31eec78b21c8e8eac.png" alt="" />
<img v-else class="ui-duty-icon" src="https://image.fulllinkai.com/202306/26/6a96db6ec2a77503b84e6213f9d905a6.png" alt="" />
<div class="font_24 color6">{{ item }}</div>
</div>
</div>
<div v-else class="font_24 color9 ui-pt-6">未绑定角色<span class="colorTheme" @click="contactService">联系</span>管理员</div>
<div v-if="userStore.weChatBindState == 2" class="font_22 color9 ui-pt-8">订单号{{ orderNum || '--' }}</div>
</div>
</div>
</div>
<div v-for="(item, index) in list" :key="index" class="ui-service-item f-fbc" @click="jumpPath(item)">
<div class="f-fcl">
<img class="ui-service-item-icon" :src="item.icon" alt="" />
<div class="font_30 color3">{{ index + 1 }}. {{ item.name }}</div>
</div>
<div class="f-fcr">
<div v-if="item.name == '查看用户信息' && dataState" class="font_26 color6 ui-jump-mp-text">
<span v-if="dataState == 'NOINFO'">信息未完善</span>
<span v-else-if="dataState == 'COMPLETEINFO'">方案待设置</span>
<span v-else-if="dataState == 'NOSCHEME'">方案待设置</span>
<span v-else-if="dataState == 'SCHEME'">配送待完成</span>
<span v-else-if="dataState == 'FINISHED'">配送已完成</span>
</div>
<div v-if="isNew && item.name == '用户备注管理'" class="font_26 colorPrice ui-jump-mp-text">{{ isNew }}条新备注</div>
<img class="ui-service-item-triangle-icon" src="https://image.fulllinkai.com/202306/13/d491373f21f5a0c5810f2167f7c961f0.png" alt="" />
</div>
</div>
</div>
<van-popup v-model:show="showTips" round :close-on-click-overlay="false" :lock-scroll="true" :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 text-center">请先绑定{{ orderReal }}订单后操作</div>
<div class="ui-btn-box f-fbc">
<div class="ui-btn font_32 f-fcc color6" @click="showTips = false">取消</div>
<div class="ui-btn-v2 font_32 f-fcc colorF" @click="jumpBindPath('groupOrders')">去绑定</div>
</div>
</div>
</van-popup>
<van-popup v-model:show="showTipsV2" round :close-on-click-overlay="false" :lock-scroll="true" :duration="0.5">
<div class="ui-tips-box">
<div class="color3 font_32 text-center">请先绑定企业微信后操作</div>
<div class="ui-btn-box f-fbc">
<div class="ui-btn font_32 f-fcc color6" @click="showTipsV2 = false">取消</div>
<div class="ui-btn-v2 font_32 f-fcc colorF" @click="jumpBindPath('bindEnterprise')">去绑定</div>
</div>
</div>
</van-popup>
<van-popup v-model:show="showOrderState" round position="bottom" :duration="0.5">
<van-picker v-model="orderStateValues" title="标记订单状态" :columns="orderStateColumns" @confirm="onConfirmOrderState" @cancel="showOrderState = false" />
</van-popup>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { showConfirmDialog, showToast } from 'vant';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
import weChat from '@/utils/weChat';
defineOptions({ name: 'ServiceCentre' });
const userStore = useUserStore() as any;
const loading = ref(false);
const isIOS = ref<any>(null);
const isNew = ref<any>(null);
const roles = ref<any[]>([]); //
const orderNum = ref<any>('');
const dataState = ref<any>(''); //
const name = ref<any>(''); //
const mobile = ref<any>(''); //
const orderReal = ref<any>(''); //
const isBind = ref<any>(''); //
const list = ref<any[]>([
{ name: '邀请用户完善信息', icon: 'https://image.fulllinkai.com/202306/13/854c78986ae76f992f65e77f77b261b0.png', path: '' },
{ name: '查看用户信息', icon: 'https://image.fulllinkai.com/202306/13/f94eaf7e474fde2c0b486d0d95c64d01.png', path: 'appViewUserInfo' },
{ name: '查看用户测量记录(随时可查看)', icon: 'https://image.fulllinkai.com/202306/13/856424fce58d0862b88a5d3b4adb0bfa.png', path: 'appMeasurementRecord' },
{ name: '图表统计', icon: 'https://image.fulllinkai.com/202306/29/e730b3fa925f819e94dab2c32dcdf0a9.png', path: 'appStatistics' },
{ name: '方案结束后问卷调查', icon: 'https://image.fulllinkai.com/202306/13/70e934d1992521dd2ec76d6a948351d1.png', path: 'appSurveys' },
{ name: '用户备注管理', icon: 'https://image.fulllinkai.com/202306/13/4e14f3d00b326fca0b8a2737c956902b.png', path: 'appRemarks' },
{ name: '切换用户', icon: 'https://image.fulllinkai.com/202308/16/84f881b246a0d2755b53a80db1b4ba24.png', path: 'appSwitchUser' },
{ name: '复盘评估表', icon: 'https://image.fulllinkai.com/202306/25/3369eb56357331fdfd450750033ad7fe.png', path: 'appAssess' },
{ name: '设置用户餐单', icon: 'https://image.fulllinkai.com/202306/25/40b9d5fdfa39d1fd0fa761ece211d59e.png', path: 'appEditUserMenu' },
{ name: '设置餐单日期', icon: 'https://image.fulllinkai.com/202306/25/40b9d5fdfa39d1fd0fa761ece211d59e.png', path: 'appUserMenu' },
{ name: '工单', icon: 'https://image.fulllinkai.com/202308/16/397ce39edf70f2a05daadff6acbbd50c.png', path: 'appWorkOrder' },
]);
const showTips = ref(false);
const showTipsV2 = ref(false);
const selectOrderState = ref<any>('');
const orderStateValues = ref<any[]>([]); //
const orderStateColumns = ref<any[]>([
{ text: '进行中', value: 'STARTING' },
{ text: '暂停中', value: 'SUSPEND' },
{ text: '已结束', value: 'FINISHED' },
]); //
const showOrderState = ref(false);
// status1 23
const getState = () => {
weChat({ url: `/h5/work/${userStore.chatId}/group/orders/status`, method: 'get' })
.then((res) => {
const result = res.data;
userStore.weChatBindState = result.status;
userStore.chiefCoach = result.is_main_coach;
userStore.coach = result.is_coach;
userStore.service = result.is_customer;
isBind.value = result.is_bind;
isNew.value = result.is_comment;
//
if (result.status * 1 === 2 || result.status * 1 === 3) {
getDataState();
// getRoles();
}
loading.value = true;
if (result.status * 1 === 1 || result.status * 1 === 3) {
list.value.splice(11, 0, {
path: 'appGroupOrders',
icon: 'https://image.fulllinkai.com/202306/13/60898757ebb4a400cd8be6d2b1519aa4.png',
name: '绑定订单',
});
}
if ((result.status * 1 === 1 || result.status * 1 === 3) && !result.is_bind) {
list.value.splice(12, 0, {
path: 'boundEnterprise',
icon: 'https://image.fulllinkai.com/202306/26/f4de8b69f3eb31a9cfe0ec56d9647232.png',
name: '绑定企业微信',
});
} else if (!result.is_bind) {
list.value.splice(11, 0, {
path: 'boundEnterprise',
icon: 'https://image.fulllinkai.com/202306/26/f4de8b69f3eb31a9cfe0ec56d9647232.png',
name: '绑定企业微信',
});
}
})
.catch((err) => {
loading.value = true;
console.log(err);
});
};
//
const getRoles = () => {
weChat({ url: `/h5/work/group/roles?chat_id=${userStore.chatId}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
roles.value = result.roles;
name.value = result.service_user ? result.service_user.name : '';
mobile.value = result.service_user ? result.service_user.mobile : '';
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
//
const getDataState = () => {
weChat({ url: `/h5/order/user/service/status?chat_id=${userStore.chatId}`, hideLoading: true, method: 'get' })
.then((res) => {
const result = res.data;
orderNum.value = result.order_id;
dataState.value = result.status;
userStore.dataState = result.status;
//
if (result.status == 'FINISHED') {
list.value.splice(list.value.length + 1, 0, {
path: '',
icon: 'https://image.fulllinkai.com/202306/13/70e934d1992521dd2ec76d6a948351d1.png',
name: '标记订单状态',
});
}
})
.catch((err) => {
console.log(err);
});
};
//
const retransmission = () => {
showConfirmDialog({
title: '',
message: '是否确认转发邀请用户完善信息到群聊天?',
})
.then(() => {
let data = {
chat_id: userStore.chatId,
type: 'health',
is_im: 1,
};
weChat({ url: `/send/im/msg`, data, method: 'post' })
.then(() => {
showToast('通知已发送');
setTimeout(() => {
// ios app
if (isIOS.value) {
window.webkit.messageHandlers.goBack.postMessage(null);
} else {
// app
window.webAppInterface.goBack();
}
}, 1200);
})
.catch((err) => {
console.log(err);
});
})
.catch(() => {
// on cancel
});
};
//
const markersOption = () => {
let data = {
chat_id: userStore.chatId,
order_status: selectOrderState.value,
};
showConfirmDialog({
title: '温馨提示',
message: `是标记订单为${selectOrderState.value == 'STARTING' ? '进行中' : selectOrderState.value == 'SUSPEND' ? '暂停中' : '已结束'}`,
})
.then(() => {
// on confirm
weChat({ url: `h5/mark/user/order/status`, data, method: 'post' })
.then(() => {
showToast('标记成功');
userStore.orderState = selectOrderState.value;
if (selectOrderState.value == 'FINISHED') {
// list.value.splice(list.value.length - 1, 1);
list.value.splice(1, 0, {
path: '',
icon: 'https://image.fulllinkai.com/202306/13/70e934d1992521dd2ec76d6a948351d1.png',
name: '邀请用户上传方案后体检报告',
});
}
})
.catch((err) => {
console.log(err);
});
})
.catch(() => {
// on cancel
});
};
const jumpPath = (e) => {
if (!loading.value) {
return;
}
if (e.name == '标记订单状态') {
if (userStore.chiefCoach == 1) {
showOrderState.value = true;
} else {
showToast('非常抱歉,该功能只限主教练');
}
return;
}
if (e.name == '邀请用户完善信息') {
retransmission();
return;
}
if (dataState.value != 'SCHEME' && dataState.value != 'FINISHED' && (e.name == '设置餐单日期' || e.name == '设置用户餐单')) {
showToast('用户餐单方案待设置');
return;
}
if (e.name !== '绑定订单' && e.name !== '绑定企业微信' && e.name !== '绑定企业微信') {
if (userStore.weChatBindState * 1 === 1) {
showTips.value = true;
return;
}
}
if (userStore.weChatBindState * 1 === 3 && (e.name == '查看用户信息' || e.name == '查看用户测量记录(随时可查看)' || e.name == '图表统计' || e.name == '切换用户' || e.name == '设置餐单日期' || e.name == '设置用户餐单' || e.name == '工单')) {
orderReal.value = '真实';
showTips.value = true;
return;
}
if (!isBind.value && (e.name == '用户备注管理' || e.name == '复盘评估表')) {
showTipsV2.value = true;
return;
}
let roleId = '' as any;
if (userStore.chiefCoach) {
roleId = 1;
} else if (userStore.service) {
roleId = 3;
} else if (userStore.coach) {
roleId = 2;
}
router.push({
name: e.path,
query: { role: roleId },
});
};
const jumpBindPath = (url) => {
router.push({
name: url,
});
};
const contactService = () => {
window.location.href = `https://work.weixin.qq.com/kfid/kfc967090f765f69300`;
};
//
const onConfirmOrderState = ({ selectedOptions }) => {
showOrderState.value = false;
selectOrderState.value = `${selectedOptions[0].value}`;
console.log(selectOrderState.value);
markersOption();
};
onMounted(() => {
let route = router.currentRoute.value.query;
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;
}
if (route.chat_id) {
userStore.chatId = route.chat_id;
}
getState();
getRoles();
});
</script>
<style lang="scss" scoped>
.ui-serviceCentre {
background: #f8f8f8;
overflow-y: auto;
min-height: 100vh;
}
.ui-service-list-box {
margin: px2rem(30);
padding: px2rem(30);
background: #ffffff;
border-radius: px2rem(24);
}
.ui-service-top {
padding-bottom: px2rem(30);
margin-bottom: px2rem(-30);
border-bottom: px2rem(2) solid #f5f5f5;
.ui-logo-icon {
width: px2rem(120);
height: px2rem(120);
display: block;
margin-right: px2rem(18);
}
.ui-user-box {
display: flex;
justify-content: left;
}
.ui-duty-box {
width: fit-content;
padding: px2rem(4) px2rem(16) px2rem(6) px2rem(12);
border-radius: px2rem(24);
border: px2rem(2) solid #d8d8d8;
margin-top: px2rem(12);
margin-right: px2rem(20);
.ui-duty-icon {
width: px2rem(24);
height: px2rem(24);
display: block;
margin-right: px2rem(4);
}
}
}
.ui-service-item {
margin-top: px2rem(60);
.ui-service-item-icon {
width: px2rem(40);
height: px2rem(40);
display: block;
margin-right: px2rem(20);
}
.ui-jump-mp-text {
line-height: px2rem(36);
}
.ui-service-item-triangle-icon {
width: px2rem(10);
height: px2rem(20);
display: block;
margin-left: px2rem(16);
}
}
.ui-tips-box {
position: relative;
width: px2rem(520);
padding: px2rem(92) px2rem(40) px2rem(50) px2rem(40);
background: #ffffff;
border-radius: px2rem(24);
.ui-btn-box {
padding: 0 px2rem(36);
.ui-btn,
.ui-btn-v2 {
width: px2rem(192);
height: px2rem(68);
border-radius: px2rem(34);
margin-top: px2rem(80);
}
.ui-btn {
border: px2rem(2) solid #d8d8d8;
}
.ui-btn-v2 {
background: #5ac7a0;
border-radius: px2rem(34);
border: px2rem(2) solid #5ac7a0;
}
}
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<div class="ui-activityOrder">
<div class="ui-placeholder"></div>
<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" class="ui-empty-data-box">
<img class="ui-empty-data-icon" src="https://image.fulllinkai.com/202301/08/939c529ca16a91e3ee3b22bef2fe92ca.png" alt="" />
<div class="color6 font_30 text-center">暂无数据</div>
</div>
<div v-else-if="loadingState" class="ui-container">
<div class="ui-packaging">
<div v-for="(item, index) in list" :key="index" class="font_28 color3 f-fbc ui-mb-20 ui-mt-20">
<div class="ui-input">
<div class="f-fcl font_30 color3">{{ index + 1 }}{{ item.desc }}</div>
<div class="ui-mt-12 f-fbc">
<div>购买数量</div>
<div class="color3 bold">x{{ item.linkmen_count }}</div>
</div>
<div class="ui-mt-12 f-fbc">
<div>购买时间</div>
<div class="color3 bold">{{ item.created_at }}</div>
</div>
<div class="ui-mt-12 f-fbc">
<div class="font_28">用户姓名</div>
<div class="color3 bold ui-name ellipsis_1">{{ item.name }}</div>
</div>
<div class="ui-mt-12 f-fbc">
<div>推荐人</div>
<div v-if="item.introduce_name" class="color3 bold">{{ item.introduce_name || '无推荐人' }}</div>
<div v-else class="color9 bold">无推荐人</div>
</div>
</div>
</div>
</div>
<div v-if="noMore" class="ui-no-more font_24 color6 text-center">我也是有底线的</div>
</div>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import weChat from '@/utils/weChat';
import router from '@/router';
defineOptions({ name: 'ActivityOrder' });
const loadingState = ref(false); //
const id = 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 = () => {
weChat({ url: `h5/activities/${id.value}/orders?page=${page.value}`, method: 'get' })
.then((res) => {
const result = res.data;
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;
}
loadingState.value = true;
page.value++;
})
.catch((err) => {
console.log(err);
});
};
//
const onRefresh = () => {
page.value = 1;
noMore.value = false;
finished.value = false;
loading.value = true;
getList();
};
onMounted(() => {
let route = router.currentRoute.value.query;
id.value = route.id;
});
</script>
<style lang="scss" scoped>
.ui-activityOrder {
background: #ffffff;
overflow-y: auto;
height: 100vh;
}
.ui-title-box {
width: 100%;
box-sizing: border-box;
background: #ffffff;
position: fixed;
top: 0;
z-index: 20;
padding: px2rem(30);
}
.ui-placeholder {
width: 100%;
height: px2rem(20);
}
.ui-empty-data-box {
position: relative;
z-index: 15;
}
.ui-empty-data-icon {
width: px2rem(270);
height: px2rem(200);
display: block;
margin: 26vh auto px2rem(20) auto;
}
.ui-name {
max-width: px2rem(420);
text-align: right;
word-break: break-all;
}
.ui-container {
position: relative;
z-index: 2;
padding: 0 px2rem(30);
background: #ffffff;
.ui-packaging {
padding: px2rem(2) px2rem(24);
background: #f8f8f8;
border-radius: px2rem(24);
margin-bottom: px2rem(30);
.ui-input {
width: 100%;
box-sizing: border-box;
padding: px2rem(24);
background: #ffffff;
border-radius: px2rem(16);
}
}
}
.ui-no-more {
padding: px2rem(10) px2rem(0) px2rem(120) px2rem(0);
}
</style>

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