Browse Source

feat: 🚀 竖版

pull/1/head
姜鑫 9 months ago
commit
5242ac621d
  1. 193
      .drone.yml
  2. 12
      .editorconfig
  3. 2
      .env.development
  4. 2
      .env.production
  5. 4
      .eslintignore
  6. 173
      .eslintrc.cjs
  7. 1
      .gitattributes
  8. 30
      .gitignore
  9. 4
      .husky/commit-msg
  10. 4
      .husky/pre-commit
  11. 1
      .nvmrc
  12. 4
      .prettierignore
  13. 14
      .prettierrc.json
  14. 32
      .releaserc
  15. 3
      .stylelintignore
  16. 151
      .stylelintrc.cjs
  17. 9
      .vscode/extensions.json
  18. 12
      .vscode/settings.json
  19. 444
      README.md
  20. 90
      commitlint.config.cjs
  21. 10
      env.d.ts
  22. 13
      index.html
  23. 134
      iot_upload.py
  24. BIN
      logo.jpg
  25. 9098
      package-lock.json
  26. 87
      package.json
  27. 23
      postcss.config.cjs
  28. 1
      public/static/offline/JSON/getBackTime.json
  29. 16
      public/static/offline/JSON/getConfig.json
  30. 16
      public/static/offline/JSON/getCustomerQr.json
  31. 29
      public/static/offline/JSON/getDevCoordinateByIP.json
  32. 16
      public/static/offline/JSON/getLogo.json
  33. 26
      public/static/offline/JSON/getWeather.json
  34. 57
      src/App.vue
  35. BIN
      src/assets/font/HarmonyOS_Sans_SC_Bold.ttf
  36. BIN
      src/assets/font/HarmonyOS_Sans_SC_Regular.ttf
  37. 533
      src/assets/iconfont/iconfont.scss
  38. BIN
      src/assets/iconfont/iconfont.ttf
  39. BIN
      src/assets/iconfont/iconfont.woff
  40. BIN
      src/assets/iconfont/iconfont.woff2
  41. 5
      src/assets/images/a.svg
  42. BIN
      src/assets/images/bg_v.png
  43. BIN
      src/assets/images/hand.png
  44. 3
      src/assets/images/heart.svg
  45. 41
      src/assets/images/logo.svg
  46. 5
      src/assets/images/nodata.svg
  47. 4
      src/assets/images/q.svg
  48. 10
      src/assets/scss/base.scss
  49. 9
      src/assets/scss/font.scss
  50. 15
      src/assets/scss/index.scss
  51. 148
      src/assets/scss/reset.scss
  52. 53
      src/base/AutoBackNotification/AutoBackNotification.vue
  53. 126
      src/base/Loading/Loading.vue
  54. 133
      src/base/ScrollView/ScrollView.vue
  55. 41
      src/components/Header/Header.vue
  56. 86
      src/components/List/List.vue
  57. 60
      src/components/ListItem/ListItem.vue
  58. 11
      src/composables/useDay.ts
  59. 93
      src/composables/useHandleScreen.ts
  60. 19
      src/composables/useInitBaseData.ts
  61. 34
      src/composables/useTime.ts
  62. 37
      src/composables/useWeather.ts
  63. 22
      src/enums/index.ts
  64. 25
      src/http/api.ts
  65. 120
      src/http/http.ts
  66. 19
      src/http/types.ts
  67. 15
      src/main.ts
  68. 7
      src/stores/index.ts
  69. 26
      src/stores/root/actions.ts
  70. 18
      src/stores/root/getters.ts
  71. 14
      src/stores/root/index.ts
  72. 13
      src/stores/root/state.ts
  73. 11
      src/stores/types.ts
  74. 6
      src/types/base.d.ts
  75. 5
      src/types/config.d.ts
  76. 40
      src/types/customer.d.ts
  77. 20
      src/types/device.d.ts
  78. 55
      src/types/native.d.ts
  79. 14
      src/types/weather.d.ts
  80. 171
      src/utils/utils.ts
  81. 184
      tailwind.config.ts
  82. 14
      tsconfig.app.json
  83. 11
      tsconfig.json
  84. 19
      tsconfig.node.json
  85. 34
      vite.config.ts

193
.drone.yml

@ -0,0 +1,193 @@
kind: pipeline
type: docker
name: 程序部署
host_aliases:
- ip: 192.168.0.2
hostnames:
- git.1000my.com
clone:
pull: if-not-exists
depth: 1
trigger:
branch:
- test
- master
event:
- push
volumes:
- name: dockersock
host:
path: /data/drone/docker/sock
- name: node
host:
path: /data/drone/node_modules-navigation-hang
- name: localtime
host:
path: /etc/localtime
steps:
- name: 生成Release版本
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/tgabc-namespace/tgabc:semantic-release-v21.0.1
environment:
GITEA_TOKEN:
from_secret: "gitea_token"
USER:
from_secret: "gitea_username"
PASSWD:
from_secret: "gitea_password"
obs_key:
from_secret: "obs_key"
obs_secret:
from_secret: "obs_secret"
settings:
git_login:
from_secret: gitea_username
git_password:
from_secret: gitea_password
- name: 获取tag数据
image: alpine/git:v2.36.2
environment:
GITEA_TOKEN:
from_secret: "gitea_token"
USER:
from_secret: "gitea_username"
PASSWD:
from_secret: "gitea_password"
obs_key:
from_secret: "obs_key"
obs_secret:
from_secret: "obs_secret"
commands:
- git config --global credential.helper store
- echo "https://$USER:$PASSWD@git.1000my.com" >> ~/.git-credentials
- env
- git fetch --tags
- git submodule init
- git submodule update --recursive --remote
# 获取当前分支的tag
- git describe --abbrev=0 --tags > .tags
- cat .tags
# - git fetch --tags
# - git describe --tags `git rev-list --tags --max-count=1` > .tags
# - cat .tags
- name: 代码构建
pull: if-not-exists
# image: registry.k8s.1000my.com/library/node:16.15.0-slim
image: node:20-buster
environment:
GITEA_TOKEN:
from_secret: "gitea_token"
USER:
from_secret: "gitea_username"
PASSWD:
from_secret: "gitea_password"
obs_key:
from_secret: "obs_key"
obs_secret:
from_secret: "obs_secret"
volumes:
- name: node
path: /drone/src/node_modules
settings:
mirror: https://docker.mirrors.ustc.edu.cn
commands:
# npm打包
- npm config set registry https://registry.npmmirror.com/
- npm install
- npm run build
- chown -R 1000:1000 ./
when:
branch:
- test
- master
- name: zip构建
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/qmiot/init:tizen
settings:
mirror: https://docker.mirrors.ustc.edu.cn
commands:
# zip打包
- mv dist target
- zip_name=$DRONE_REPO_NAME.$(cat .tags).zip
- cd ./target
- rm -rf static/offline
- zip -q -r $zip_name ./
- ls $zip_name
- ls
- pwd
when:
branch:
- test
- master
- name: 上传zip到iot测试接口platformProgramAdd
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/qmiot/init:apisix-init-base
environment:
url_for_iotplatform: 'http://192.168.0.142:31667'
url_for_iotfile: 'http://192.168.0.142:31666'
logo: '/drone/src/logo.jpg'
cdn_url: 'https://test-598d.1000my.com'
# genre: "Samsung"
commands:
# # 自动授权
# - export projectCode="projectCode=project-inluar4ppcxvciefgb_ila"
- export name="长沙永旺顾客心声大屏展示端_测试版" # 应用名称(自行填写 必填)
- export version=$(cat .tags) # 应用版本
- export sourceId="8" # 应用来源id(默认为8 即测试环境专用) //如需更改查看测试环境平台级下应用管理新增应用的/api/platform/v1/platformProgram/platformProgramAdd接口
- export genre="localpack" # 应用类型 本地资源包 [outchain, localpack]
- export resolution="1920*1080横屏" # 分辨率(自行填写)[1080*1920竖屏, 1920*1080横屏, 3840*2160横屏, 2160*3840竖屏]
- export package="/drone/src/target/$DRONE_REPO_NAME.$(cat .tags).zip" # zip包名称
- export des=$CI_COMMIT_MESSAGE # 应用描述
- python3 iot_upload.py
when:
branch:
- test
- name: 上传zip到iot正式接口platformProgramAdd
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/qmiot/init:apisix-init-base
environment:
url_for_iotplatform: 'http://192.168.0.91:31667'
url_for_iotfile: 'http://192.168.0.91:31666'
logo: '/drone/src/logo.jpg'
cdn_url: 'https://qianmu-iot.1000my.com'
# genre: "Samsung"
commands:
- export name="长沙永旺顾客心声大屏展示端_正式版" # 应用名称(自行填写)
- export version=$(cat .tags) # 应用版本
- export sourceId="3" # 应用来源id
- export genre="localpack" # 应用类型 本地资源包
- export resolution="1920*1080横屏" # 分辨率(自行填写)[1080*1920竖屏, 1920*1080横屏, 3840*2160横屏, 2160*3840竖屏]
- export package="/drone/src/target/$DRONE_REPO_NAME.$(cat .tags).zip" # zip包名称
- export des=$CI_COMMIT_MESSAGE # 应用描述
- python3 iot_upload.py
when:
branch:
- master
- name: dingTalk notification
pull: if-not-exists
image: lddsb/drone-dingtalk-message
failure: ignore
settings:
token: e54a8c0876f5fbe1b2dd9c78a7b56d4fcd7ea4d5a59cc2f516fb8b1ed199edd4 #具体项目钉钉群机器人token
type: markdown
message_color: true
message_pic: true
sha_link: true
tips_title: "${CI_REPO_NAME}"
debug: true
success_color: "008000"
failure_color: "FF0000"
tpl_repo_short_name: "${CI_REPO_NAME}"
msg_at_mobiles: "@${CI_COMMIT_AUTHOR_NAME}"
success_pic: "https://raw.githubusercontent.com/Ethan-Liuu/picture/master/success.png"
failure_pic: "https://raw.githubusercontent.com/Ethan-Liuu/picture/master/failed.png"
tpl: '/drone/src/.tpl'
when:
status: [failure, success]

12
.editorconfig

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 2
trim_trailing_whitespace = true

2
.env.development

@ -0,0 +1,2 @@
VITE_SENTRY_DSN_URL: http://936ea86f40634800857a65401ade2112@192.168.1.222:8000/3
VITE_SENTRY_ENVIRONMENT: development

2
.env.production

@ -0,0 +1,2 @@
VITE_SENTRY_DSN_URL: VITE_SENTRY_DSN_URL: http://936ea86f40634800857a65401ade2112@192.168.1.222:8000/3
VITE_SENTRY_ENVIRONMENT: production

4
.eslintignore

@ -0,0 +1,4 @@
node_modules
dist
public
.vscode

173
.eslintrc.cjs

@ -0,0 +1,173 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 1,
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'prettier/prettier': 'error',
'vue/multi-word-component-names': 'off',
'vue/attributes-order': [
'error',
{
order: [
'DEFINITION',
'LIST_RENDERING',
'CONDITIONALS',
'RENDER_MODIFIERS',
'GLOBAL',
['UNIQUE', 'SLOT'],
'TWO_WAY_BINDING',
'OTHER_DIRECTIVES',
'OTHER_ATTR',
'EVENTS',
'CONTENT'
],
alphabetical: false
}
],
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false,
ignores: []
}
],
'vue/custom-event-name-casing': [
'error',
'kebab-case',
{
ignores: []
}
],
// 禁止出现console
'no-console': 'off',
// 禁止出现console
'no-var': 'warn',
// 禁用debugger
'no-debugger': 'warn',
// 禁止出现重复的 case 标签
'no-duplicate-case': 'warn',
// 禁止出现空语句块
'no-empty': 'warn',
// 禁止不必要的括号
'no-extra-parens': 'off',
// 禁止对 function 声明重新赋值
'no-func-assign': 'warn',
// 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
'no-unreachable': 'warn',
// 强制所有控制语句使用一致的括号风格
curly: 'warn',
// 要求 switch 语句中有 default 分支
'default-case': 'warn',
// 强制尽可能地使用点号
'dot-notation': 'warn',
// 要求使用 === 和 !==
eqeqeq: 'error',
// 禁止 if 语句中 return 语句之后有 else 块
'no-else-return': 'error',
// 禁止出现空函数
'no-empty-function': 'warn',
// 禁用不必要的嵌套块
'no-lone-blocks': 'warn',
// 禁止使用多个空格
'no-multi-spaces': 'warn',
// 禁止多次声明同一变量
'no-redeclare': 'warn',
// 禁止在 return 语句中使用赋值语句
'no-return-assign': 'warn',
// 禁用不必要的 return await
'no-return-await': 'warn',
// 禁止自我赋值
'no-self-assign': 'warn',
// 禁止自身比较
'no-self-compare': 'warn',
// 禁止不必要的 catch 子句
'no-useless-catch': 'warn',
// 禁止多余的 return 语句
'no-useless-return': 'warn',
// 禁止变量声明与外层作用域的变量同名
'no-shadow': 'off',
// 允许delete变量
'no-delete-var': 'off',
// 强制数组方括号中使用一致的空格
'array-bracket-spacing': 'warn',
// 强制在代码块中使用一致的大括号风格
'brace-style': 'warn',
// 强制使用骆驼拼写法命名约定
camelcase: 'warn',
// 强制使用一致的缩进
indent: 'off',
// 强制在 JSX 属性中一致地使用双引号或单引号
// 'jsx-quotes': 'warn',
// 强制可嵌套的块的最大深度4
'max-depth': 'warn',
// 强制最大行数 300
// "max-lines": ["warn", { "max": 1200 }],
// 强制函数最大代码行数 50
// 'max-lines-per-function': ['warn', { max: 70 }],
// 强制函数块最多允许的的语句数量20
'max-statements': ['warn', 100],
// 强制回调函数最大嵌套深度
'max-nested-callbacks': ['warn', 3],
// 强制函数定义中最多允许的参数数量
'max-params': ['warn', 5],
// 强制每一行中所允许的最大语句数量
'max-statements-per-line': ['warn', { max: 1 }],
// 要求方法链中每个调用都有一个换行符
'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],
// 禁止 if 作为唯一的语句出现在 else 语句中
'no-lonely-if': 'warn',
// 禁止空格和 tab 的混合缩进
'no-mixed-spaces-and-tabs': 'warn',
// 禁止出现多行空行
'no-multiple-empty-lines': 'warn',
// 禁止出现;
semi: ['warn', 'never'],
// 强制在块之前使用一致的空格
'space-before-blocks': 'warn',
// 强制在 function的左括号之前使用一致的空格
// 'space-before-function-paren': ['warn', 'never'],
// 强制在圆括号内使用一致的空格
'space-in-parens': 'warn',
// 要求操作符周围有空格
'space-infix-ops': 'warn',
// 强制在一元操作符前后使用一致的空格
'space-unary-ops': 'warn',
// 强制在注释中 // 或 /* 使用一致的空格
// "spaced-comment": "warn",
// 强制在 switch 的冒号左右有空格
'switch-colon-spacing': 'warn',
// 强制箭头函数的箭头前后使用一致的空格
'arrow-spacing': 'warn',
'prefer-const': 'warn',
'prefer-rest-params': 'warn',
'no-useless-escape': 'warn',
'no-irregular-whitespace': 'warn',
'no-prototype-builtins': 'warn',
'no-fallthrough': 'warn',
'no-extra-boolean-cast': 'warn',
'no-case-declarations': 'warn',
'no-async-promise-executor': 'warn'
}
}

1
.gitattributes

@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
.eslintcache
dist
dist-ssr
coverage
*.local
tsconfig.app.tsbuildinfo
tsconfig.node.tsbuildinfo
vite.config.ts.*
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
.husky/commit-msg

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit $1

4
.husky/pre-commit

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

1
.nvmrc

@ -0,0 +1 @@
v20.10.0

4
.prettierignore

@ -0,0 +1,4 @@
node_modules
dist
public
.vscode

14
.prettierrc.json

@ -0,0 +1,14 @@
{
"plugins": [
"prettier-plugin-tailwindcss"
],
"tabWidth": 2,
"jsxSingleQuote": true,
"jsxBracketSameLine": true,
"endOfLine": "lf",
"printWidth": 140,
"arrowParens": "avoid",
"singleQuote": true,
"semi": false,
"trailingComma": "none"
}

32
.releaserc

@ -0,0 +1,32 @@
{
"branches": [
"master", {
"name": "test", "prerelease": "beta"
},
"test", {
"name": "dev", "prerelease": "dev"
}
],
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "angular",
"releaseRules": [
{"type": "docs", "scope":"README", "release": "patch"},
{"type": "refactor", "release": "patch"},
{"type": "style", "release": "patch"}
],
"parserOpts": {
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"]
}
}],
"@semantic-release/release-notes-generator",
"@semantic-release/git",
["@saithodev/semantic-release-gitea", {
"giteaUrl": "https://git.1000my.com",
"assets": [
{"path": "compose-update.bin", "label": "gcc"},
{"path": "doc/up.prod.sql", "label": "线上环境更新SQL(up.prod.sql)"}
]
}]
]
}

3
.stylelintignore

@ -0,0 +1,3 @@
node_modules/*
dist

151
.stylelintrc.cjs

@ -0,0 +1,151 @@
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-recommended-scss',
'stylelint-config-standard-vue',
],
plugins: ['stylelint-order'],
// 不同格式的文件指定自定义语法
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts', '**/*.json', '**/*.md', '**/*.yaml'],
rules: {
'custom-property-empty-line-before': null,
// 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'no-descending-specificity': null,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: [':deep'],
},
],
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep'],
},
],
// 禁用每个选择器之前插入空行
'rule-empty-line-before': null,
// 禁止小于 1 的小数有一个前导零
// 'number-leading-zero': 'never',
// 一些特殊的scss指令
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'function',
'if',
'else',
'else-if',
'each',
'include',
'mixin',
'tailwind',
'apply',
'variants',
'responsive',
'screen',
],
},
],
'scss/at-rule-no-unknown': [
true,
{
ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'cx', 'cy', 'r'],
},
],
'at-rule-empty-line-before': [
'always',
{
except: ['blockless-after-same-name-blockless', 'first-nested'],
ignore: ['after-comment'],
ignoreAtRules: ['else', 'else-if'],
},
],
// 指定样式的排序
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'justify-content',
'align-items',
'flex-shrink',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'font-size',
'font-family',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'border',
'border-style',
'border-width',
'border-color',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'box-shadow',
'text-shadow',
'resize',
'transition',
'content',
],
},
}

9
.vscode/extensions.json

@ -0,0 +1,9 @@
{
"recommendations": [
"vscode.i18n-ally",
"dbaeumer.vscode-eslint",
"vscode.stylelint",
"vscode.Tailwind-css-IntelliSense",
"esbenp.prettier-vscode"
]
}

12
.vscode/settings.json

@ -0,0 +1,12 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"css.validate": false,
"scss.validate": false,
"files.eol": "\n",
"i18n-ally.localesPaths": [
"src/locales/lang"
]
}

444
README.md

@ -0,0 +1,444 @@
# vite_ts
### vue+vue-router+pinia+vite+typescript+prettier+eslint+stylelint+lint-staged+commitlint+tailwindcss
### 打包工具由webpack换成vite
### node版本请使用node18及以上
## 推荐使用node版本管理工具nvm
## 使用package.json内的脚本 'npm run commit' 提交代码
## 使用package.json内的脚本 'npm run commit' 提交代码
## 全局状态存储中以 map 为前缀的state状态都是用于数据检索用的 不建议用于页面展示
## css框架使用了tailwindcss
## 标准图标库 https://www.figma.com/file/cVbg9b6kIGiFdtgEisnMBo/NEW%E7%BB%84%E4%BB%B6%E5%BA%93?type=design&node-id=26%3A2189&mode=dev
## 地图sdk https://1000my.com/mapapidoc/index.html
解决文件行尾与prettier冲突 新项目根目录下新建.gitattributes文件(详细解决方案:https://juejin.cn/post/6844904062987550733)
```shell
老项目添加需要执行以下git命令
git rm --cached -r .
git reset --hard
.gitattributes
*.js eol=lf
*.jsx eol=lf
*.json eol=lf
```
# iconfont字体图标渐变色
```html
<!-- tailwindcss -->
<i class="iconfont icon-zhitimoshi bg-gradient-to-r from-pink-500 to-violet-500 bg-clip-text text-64 text-transparent"></i>
```
```css
.iconfont {
background-clip: text;
-webkit-text-fill-color: transparent;
background-image: linear-gradient(180deg, #BB8A65 0%, #E3BA9B 100%);
}
```
# 导视开发提测流程
1. 修改项目中根目录下的`.drone.yml`找到`name: 上传zip到iot测试接口platformProgramAdd文件`配置,如下所示:
```yml
commands:
# # 自动授权
# - export projectCode="projectCode=project-inluar4ppcxvciefgb_ila"
- export name="导视包ci流程测试" # 应用名称(自行填写 必填)
- export version=$(cat .tags) # 应用版本 (无需填写)
- export sourceId="8" # 应用来源id(默认为8 即测试环境专用 保持默认即可)
- export genre="localpack" # 应用类型 本地资源包 保持默认即可 [outchain, localpack]
- export resolution="1080*1920竖屏" # 分辨率(自行填写)[1080*1920竖屏, 1920*1080横屏, 3840*2160横屏, 2160*3840竖屏]
- export package="/drone/src/target/$DRONE_REPO_NAME.$(cat .tags).zip" # zip包名称 保持默认即可
- export des=$CI_COMMIT_MESSAGE # 应用描述 保持默认即可
- python3 iot_upload.py
```
2. 修改项目中根目录下的`.tpl`文件中的**git地址**配置,如下所示:
```tpl
<!-- 在.tpl文件的第6行,修改项目git地址 -->
<font color=[TPL_STATUS_COLOR] size="3">
项目git地址:https://git.1000my.com/
</font>
<!-- 以上海浦项写字楼项目为例,如下所示: -->
<font color=[TPL_STATUS_COLOR] size="3">
项目git地址:https://git.1000my.com/project_shpuxiang/shpuxiang_daoshi_vue
</font>
```
3. 钉钉**群机器人**配置([钉钉机器人接入官方文档](https://open.dingtalk.com/document/group/custom-robot-access))
* 在项目钉钉群界面点击右上角的**群设置**按钮。
* 在群设置界面点击**智能群助手**按钮。
* 在智能群助手界面点击**添加机器人**按钮。
* 在弹出的弹窗界面中点击添加机器人右边的**小齿轮**按钮。
* 在选择添加机器人列表中选择**自定义机器人**。
* 在机器人详情界面点击**添加**按钮。
* 在添加机器人界面可以修改机器人的**头像**以及**名字**,在**安全设置**选项中选择**自定义关键字**,并添加**地址**关键字,勾选**我已阅读并同意《自定义机器人服务及免责条款**》,然后点击**完成**按钮。
* 完成创建后会自动生成机器人的**Webhook**地址,例如:`https://oapi.dingtalk.com/robot/send?access_token=XXXXXX`。复制地址中的`access_token=`后面的值,修改项目中根目录下的`.drone.yml`文件中的**钉钉机器人token**配置,如下所示:
```yml
# 钉钉通知
- name: dingTalk notification
pull: if-not-exists
image: lddsb/drone-dingtalk-message
failure: ignore
settings:
token: 5f1337cd8cb70e007d2693f70a3ca89fdec543781a9a9fe2f5519e061a1820a8
```
4. 项目提测步骤:
* 通过`npm run commit`命令提交本地代码。
* `git push `提交代码到项目远端dev分支
* 在远端仓库合并`dev`分支到`test`分支
6. Vue3常用工具库函数[VueUse](https://vueuse.org/)
7. 开发 及内置组件使用说明见以下文档
8. 所有需要写在App.vue内的组件请放到PublicComponent组件
10. 地图SDK地址:https://1000my.com/mapapidoc/index.html
11. **所有跳到导航页面之前请先调用 @/utils/Class/Brand.ts 来生成一个新的shop数据 以更新store内的shop数据 (本身操作是店铺除外)看以下demo**
12. ```typescript
function nav(activity: Activity) {
return new Promise<Shop>((resolve, reject) => {
if (activity.shopCode?.length) {
const shop = mapShopListByCode.value[activity.shopCode]?.[0]
if (shop) {
resolve(shop)
} else {
reject('Unable to match store')
}
return
}
if (activity.point?.length) {
const { title, point, fileUrl, titleEn } = activity
const [buildingOrder, floorOrder, yaxis] = splitStringToArray(point)
const navBuildingInfo = buildingList.value.find(item => item.buildingOrder === buildingOrder)
const shop = new Brand({
shopName: title,
shopNameEn: titleEn,
floorOrder,
floor: navBuildingInfo?.floorList?.find(item => item.floorOrder === floorOrder)?.floor ?? '',
logoUrl: fileUrl[0],
yaxis,
buildingOrder,
building: navBuildingInfo?.building
})
resolve(shop)
}
})
}
```
# 组件使用
### AutoBackNotification
| props | type | desc | default |
| :---: | :----: | :----------: | :-----: |
| title | string | 提示文字 | '' |
| delay | number | 倒计时的数字 | 0 |
### Marquees
| props | type | desc | default |
| :-----: | :----: | :---------------------: | :-----: |
| content | string | 滚动的内容 | '' |
| delay | number | 第一次滚动时的延迟 时间 | 0.8 |
| speed | number | 滚动速度 | 40 |
### Icon
| props | type | desc | default |
| :---: | :------: | :------: | :--------------: |
| type | IconType | 图标类型 | '' |
| color | string | 填充颜色 | rgba(0, 0, 0, 1) |
Icon组件大小由父容器决定 所以需要给父容器指定宽高 type类型详见icon.d.ts
### Tabs
| props | type | desc | default |
| :-----------: | :-------: | :--------: | :-----: |
| list | TabItem[] | 数据源 | [] |
| indicateColor | string | 选中的颜色 | #fff |
TabItem类型 详见base.d.ts
| events | type | desc |
| :----: | :-----------------------------------: | :-----: |
| click | (item: TabItem, index: number)=>void | 点击tab |
组件默认ui无法满足设计稿时 可直接修改组件源码以满足设计稿 或者通过slot插槽自定义ui 详见以下demo
```html
<Tabs :list="tabs" class="p-1" indicate-color="#333">
<template #default="{ item, index, tabActiveIndex }">
<div class="relative z-0 p-11 flex-center">
<div class="h-10 w-10">
<Icon :type="item.icon" />
</div>
<div class="text-24 transition-all" :class="index === tabActiveIndex ? 'text-white' : 'text-gray-400'">
<p class="leading-none">{{ item.title }}</p>
<p class="leading-none">{{ item.titleEn }}</p>
</div>
</div>
</template>
</Tabs>
```
### ScrollView
| props | type | desc | default |
| :-------------: | :-------------: | :----------------------------------------------------------: | :-----: |
| list | array \| string | 监听的数据源 当数据源改动时会自动调用refresh重新计算滚动高度 | - |
| scrollbar | boolean | 显示滚动条 | false |
| scrollX | boolean | 是否需要横向滚动 | false |
| refreshDelay | number | 延迟初始化实例的时间 | 20 |
| scrollTop | boolean | 当list数据改变时是否自动回到顶部或者左边(scrollX为true时) | true |
| observeImage | boolean | 是否监听图片(有瀑布流布局的地方建议开启) | false |
| stopPropagation | boolean | 是否阻止事件冒泡。多用在嵌套 scroll 的场景。 | false |
| pullUp | boolean | 是否下拉刷新 | false |
### EffectFade
| props | type | desc | default |
| :--------: | :--------------------------: | :----------------: | :-----: |
| list | array | 内部渲染需要的数据 | [] |
| pagination | boolean \| PaginationOptions | 是否开启提示点 | false |
**此组件只初始化swiper轮播等相关逻辑 只做展示 dom节点自行实现 通过作用域插槽可拿到循环的每一项数据 作用域插槽字段: `{ item }`**
```javascript
<script lang="ts" setup>
import {ref} from 'vue'
import EffectFade from '@/components/EffectFade/EffectFade.vue'
const list = [
'https://img1.baidu.com/it/u=847956157,2750448390&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1695402000&t=85436b1a506b8cb8849887ab93c4ea2f',
'https://img0.baidu.com/it/u=937072262,2445742246&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1695402000&t=61d122c4ffd7b8f9e5feee57e4eff73a'
]
</script>
<template>
<div class="w-[400px] h-[150px] mx-auto">
<EffectFade pagination :list="list">
<template v-slot="{ item }">
<div class="w-[400px] h-[150px]">
<img :src="item" class="cover" alt="" />
</div>
</template>
</EffectFade>
</div>
</template>
```
### Written(手写组件的父元素需指定宽高)
| props | type | desc | default |
| :-------------: | :----: | :-----------------------------------: | :-------------------: |
| backgroundColor | string | canvas 背景色 | #f2f2f2 |
| borderRadius | string | canvas的圆角 | 10px |
| fillText | string | canvas绘制区域的提示文字 | 手写区域 |
| fillFontSize | string | canvas绘制区域的提示文字大小 | 100px |
| fillStyle | string | canvas绘制区域的提示文字颜色 | rgba(85, 73, 54, 0.1) |
| lang | string | CN \| EN 指定接口返回的是字母还是汉字 | CN |
| strokeStyle | string | 笔触的颜色 | #000 |
| events | type | desc | callback params |
| :----: | :------: | :------------------------------: | :----------------------------: |
| result | function | 组件内部响应式变量list变化时触发 | 接口请求成功之后返回的汉字列表 |
### PlateInput
| props | type | desc | default |
| :----------: | :------------: | :--------------: | :-----: |
| List | array | 车牌号或者车位号 | [] |
| btnText | string | 按钮提示文字 | '' |
| searchMethod | '车牌'\|'车位' | 找车方式 | '车牌' |
| events | type | desc |
| :----------: | :------: | :-------------------------: |
| handle-input | function | 点击输入框 |
| confirm | function | 确认找车 即点击找车按钮触发 |
**车牌输入框组件现在接受一个slot插槽 可以用来点击时显示loading提示或其他内容**
### PlateKeyboard
| props | type | desc | default |
| :-----------: | :----: | :--------------------------: | :-----: |
| searchMethods | string | 找车方式 ['车牌', '车位'] | '车牌' |
| events | type | desc |
| :-------------: | :------: | :------------------------------------------------------: |
| handle-keyboard | function | 点击找车键盘 参数为点击时的文字 如果为del则会触发del事件 |
| del | function | 删除 |
### Lottie动画组件
| props | type | desc | default |
| :------: | :--------------: | :----------------: | :-----: |
| isLocale | boolean | 是否是json本地文件 | true |
| path | Record<any, any> | 动画数据 | {} |
<!--组件会暴露内部lottie动画实例 :lottieInstance-->
# composition hooks 使用
### 活动导航
```javascript
const { nav } = useActivityNav() //nav接受参数类型
async function _nav(activity: Activity) {
await nav(activity)
//your code ...
}
```
### 通用导航的hooks
```javascript
<template>
<ScrollView ref="scroll" :list="pathShopList">
<div>
<!-- 经过店铺ui自行实现 -->
</div>
</ScrollView>
</template>
<script setup lang="ts">
import ScrollView from '@/base/ScrollView/ScrollView.vue'
//滚动组件实例传入hooks 在状态改变时能够使其重新计算 重新滚动到顶部
const scroll = ref<InstanceType<typeof ScrollView> | null>(null)
/*
directionInfo: 方向信息
pathShopList: 经过店铺
startNavi: 导航
backPathArray: 导航函数执行时的回调
*/
const { directionInfo, pathShopList, startNavi, backPathArray } = useStartNavi()
/*
passIdx: 经过店铺列表的经过状态索引
reset: 重置索引
*/
const { passIdx, reset } = usePassActive(pathShopList)
/*
replay:重播
pause:暂停
speedUp:加速
handleReplay:设置重播
togglePause: 设置暂停
handleSpeedUp: 设置加速
resetPause: 暂停初始化
scroll是ScrollView组件实例
*/
const { replay, pause, speedUp, handleReplay, togglePause, handleSpeedUp, resetPause } = useMapNavControl(reset, scroll)
/*
methodsList: 路线列表 视图层绑定
methodIdx: 路线选中索引
handleControl:路线选择
selectedWayMethod:此函数大多数情况无需调用 只需调用handleControl即可
*/
const { methodIdx, methodsList, handleControl, resetMethodIdx } = useChangeNavMethod(backPathArray, resetPause, reset, scroll)
/*
viewIcon: 文字及提示icon信息
setCameraViews: 导航视角
setText:设置文字
*/
const { viewIcon, setCameraViews, setText } = useCameraViews()
</script>
```
### 日期及天气
```javascript
const {whickWeek} = useDay()
const {currentTime} = useTime()
const {weather, icon} = useWeather()
```
### useSearchShop.ts
```javascript
import {ref} from 'vue'
import {useSearchShop} from '@/composables/useSearchShop'
const keywords = ref('')
const searchMethods = ref(0) //0:键盘搜索 1:手写搜索
//筛选之后的店铺列表
const {searchShopList} =useSearchShop(keywords,searchMethods)
```
### usePage.ts
```javascript
<ScrollView ref="scroll" pull-up @scroll-end="scrollEnd" :list="pathShopList">
<div>
your html code...
</div>
</ScrollView>
const { scrollEnd, pageList, loaded } =usePage(shopList, scroll)
```
### <!--ScrollView组件需开启pullUp 更具体使用详见具体项目 -->
## 其余hooks详见脚手架内的composables文件夹
# 项目内使用的工具函数
```javascript
import {
randomNumber, //两个数字之间的随机数
isPhoneNumber, // 手机号码验证
isUppercaseWord, //是否是大写
isZhWord, //是否是中文
isNumber, //是否是数字
isLicensePlate //验证输入车牌是否正确
uniqBy //数组内对象去重
futureDate, //未来几天 默认七天
formatDay, // 格式化年月日
isInDuringDate, //当前时间是否在指定时间段内
addPrefixByRecursive, //指定的资源加上地址前缀
splitStringToArray, //字符串点位转换成数组需以下划线 '_' 连接
trimAll //去除字符串所有空格
} from '@/utils/utils'
```

90
commitlint.config.cjs

@ -0,0 +1,90 @@
// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
/* eslint-env node */
module.exports = {
ignores: [commit => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
prompt: {
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixsSelect: '选择关联issue前缀(可选):',
customFooterPrefixs: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'feat', name: 'feat: 🚀 新增功能 | A new feature', emoji: '🚀' },
{ value: 'fix', name: 'fix: 🧩 修复缺陷 | A bug fix', emoji: '🧩' },
{ value: 'docs', name: 'docs: 📚 文档更新 | Documentation only changes', emoji: '📚' },
{
value: 'style',
name: 'style: 🎨 代码格式 | Changes that do not affect the meaning of the code',
emoji: '🎨'
},
{
value: 'refactor',
name: "refactor: '♻️'代码重构(不包括 bug 修复、功能新增) | A code change that neither fixes a bug nor adds a feature",
emoji: '♻️'
},
{
value: 'perf',
name: 'perf: ⚡️️性能提升 | A code change that improves performance',
emoji: '⚡️'
},
{
value: 'test',
name: 'test: ✅ 测试相关 | Adding missing tests or correcting existing tests',
emoji: '✅'
},
{
value: 'build',
name: 'build: 📦️ 构建相关 | Changes that affect the build system or external dependencies',
emoji: '📦️'
},
{
value: 'ci',
name: 'ci: 🎡 持续集成 | Changes to our CI configuration files and scripts',
emoji: '🎡'
},
{ value: 'revert', name: 'revert: ⏪️ 回退代码 | Revert to a commit', emoji: '⏪️' },
{
value: 'chore',
name: 'chore: 🔨 其他修改 | Other changes that do not modify src or test files',
emoji: '🔨'
}
],
useEmoji: true,
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
customIssuePrefixsAlign: 'top',
emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom',
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}

10
env.d.ts

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_SENTRY_DSN_URL: string
readonly VITE_SENTRY_ENVIRONMENT: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

13
index.html

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

134
iot_upload.py

@ -0,0 +1,134 @@
import json
import os
import requests
def file_write(file_name,text_content):
# charset()
file = open(file_name , "w")
file.write(str(text_content))
file.close()
def dingtalk(cdn_url,packageUrl):
tpl_for_dingtalk=f'''
<font color=[TPL_STATUS_COLOR] size="3">
下载地址: (<a href="{cdn_url}{packageUrl}" target="_blank">{cdn_url}{packageUrl}</a>)
</font>
![image]([TPL_STATUS_PIC])
项目名称[PLUGIN_TPL_REPO_SHORT_NAME]
更新作者: [CI_COMMIT_AUTHOR_NAME]
<font color=[TPL_STATUS_COLOR] size="3">
更新内容[TPL_COMMIT_MSG]
</font>
[ [查看构建详情)]]([TPL_BUILD_LINK])
'''
print (tpl_for_dingtalk)
file_write("./.tpl",tpl_for_dingtalk)
def obsUpload(file,uploadType):
url_obsUpload = os.getenv('url_for_iotfile') + "/api/file/v1/attachment/obsUpload"
myfiles = {'file': open(file, 'rb')}
mydata = {'uploadType': uploadType}
r = requests.post(url_obsUpload, data=mydata, files=myfiles)
print (r.text)
json_str = r.text
info = json.loads(json_str)['data']
return info
# APP管理-新增APP
def platformAppAdd():
# with open(".tags") as f: version = f.read().strip()
logoFile = obsUpload(file=str(os.getenv('logo')), uploadType='logo')
packageFile = obsUpload(file=str(os.getenv('package')),uploadType='compress')
bodyjson = {
'name': os.getenv('name'),
'version': os.getenv('version'),
'genre': os.getenv('genre'),
'logoCode': logoFile['code'],
'logoUrl': logoFile['url'],
'packageCode': packageFile['code'],
'packageUrl': packageFile['url'],
'des': os.getenv('des')
}
print (bodyjson)
url_platformAppAdd = os.getenv('url_for_iotplatform') + "/api/platform/v1/platformApp/platformAppAdd"
headers = {'content-type': 'application/json;charset=UTF-8'}
r = requests.post(url_platformAppAdd, headers=headers, data=json.dumps(bodyjson))
print (r.text)
cdn_url = os.getenv('cdn_url')
packageUrl = packageFile['url']
dingtalk(cdn_url,packageUrl)
# 应用管理-授权(获取code list)
def getPlatformProgramList():
bodyjson = {
'name': os.getenv('name'),
'version': os.getenv('version'),
'genre': os.getenv('genre'),
}
print (bodyjson)
url_platformAppAdd = os.getenv('url_for_iotplatform') + "/api/platform/v1/platformProgram/getPlatformProgramList/1/10"
headers = {'content-type': 'application/json;charset=UTF-8'}
r = requests.post(url_platformAppAdd, headers=headers, data=json.dumps(bodyjson))
print (type(json.loads(r.text)))
print (json.loads(r.text)['data']["listObject"][0]['code'])
return json.loads(r.text)['data']["listObject"][0]['code']
# 应用管理-授权
def multipleProgramToOneProject():
codeList = getPlatformProgramList()
bodyjson = {
"projectCode": os.getenv('projectCode'),
"codeList": [codeList]
}
print (bodyjson)
url_platformAppAdd = os.getenv('url_for_iotplatform') + "/api/platform/v1/platformProgram/multipleProgramToOneProject"
headers = {'content-type': 'application/json;charset=UTF-8'}
r = requests.post(url_platformAppAdd, headers=headers, data=json.dumps(bodyjson))
print (r.text)
# 应用管理-新增应用
def platformProgramAdd():
# with open(".tags") as f: version = f.read().strip()
logoFile = obsUpload(file=str(os.getenv('logo')), uploadType='logo')
packageFile = obsUpload(file=str(os.getenv('package')),uploadType='compress')
bodyjson = {
'name': os.getenv('name'),
'version': os.getenv('version'),
'genre': os.getenv('genre'),
'sourceId': os.getenv('sourceId'),
"resolution": os.getenv('resolution'),
'logoCode': logoFile['code'],
'logoUrl': logoFile['url'],
'packageCode': packageFile['code'],
'packageUrl': packageFile['url'],
'des': os.getenv('des')
}
print (bodyjson)
url_platformAppAdd = os.getenv('url_for_iotplatform') + "/api/platform/v1/platformProgram/platformProgramAdd"
headers = {'content-type': 'application/json;charset=UTF-8'}
r = requests.post(url_platformAppAdd, headers=headers, data=json.dumps(bodyjson))
print (r.text)
cdn_url = os.getenv('cdn_url')
packageUrl = packageFile['url']
dingtalk(cdn_url,packageUrl)
# 自动授权
# multipleProgramToOneProject()
platformProgramAdd()

BIN
logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

9098
package-lock.json

File diff suppressed because it is too large

87
package.json

@ -0,0 +1,87 @@
{
"name": "vite-project",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host",
"build-check": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build": "vite build",
"type-check": "vue-tsc --build --force",
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue",
"eslint:fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx,.vue",
"lint": "eslint . --ext .vue,.js,.jsx,.ts,.tsx --fix",
"stylelint": "stylelint \"./**/*.{css,scss,sass,vue,html}\"",
"stylelint:fix": "stylelint \"./**/*.{css,scss,sass,vue,html}\" --fix",
"format": "prettier --write src/",
"commit": "git add . && git-cz",
"prepare": "husky"
},
"dependencies": {
"@better-scroll/core": "^2.5.1",
"@better-scroll/observe-image": "^2.5.1",
"@better-scroll/scroll-bar": "^2.5.1",
"axios": "^1.6.7",
"axios-retry": "^4.5.0",
"lodash-es": "^4.17.21",
"pinia": "^2.1.7",
"swiper": "^11.1.1",
"vue": "^3.4.15"
},
"devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node20": "^20.1.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.10",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.17",
"commitizen": "^4.3.0",
"cz-git": "^1.8.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"mrm": "^0.1.2",
"npm-run-all2": "^6.1.1",
"pinia-logger": "^1.3.12",
"postcss": "^8.4.35",
"postcss-html": "^1.6.0",
"postcss-px-to-viewport-8-plugin": "^1.2.3",
"postcss-scss": "^4.0.9",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.11",
"sass": "^1.71.1",
"stylelint": "^16.2.1",
"stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.1.0",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.0",
"vite": "^5.0.11",
"vue-tsc": "^1.8.27"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{vue,js,jsx,ts,tsx}": "eslint --cache --fix",
"*.{vue,scss,ts,tsx,css}": "stylelint --fix",
"*.src/": "prettier --write"
}
}

23
postcss.config.cjs

@ -0,0 +1,23 @@
// eslint-disable-next-line no-undef
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 要转化的单位
viewportWidth: 1080, // UI设计稿的宽度
unitPrecision: 8, // 转换后的精度,即小数点位数
propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ['not-vw'], // 指定不转换为视窗单位的类名,
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
replace: true, // 是否转换后直接更换属性值
exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1920 // 横屏时使用的视口宽度
}
}
}

1
public/static/offline/JSON/getBackTime.json

@ -0,0 +1 @@
{"code":200,"msg":"操作成功","data":[6000000000,100000000]}

16
public/static/offline/JSON/getConfig.json

@ -0,0 +1,16 @@
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 898,
"entryCode": "QD3QZd7_rr0jkDYR2qtc7",
"title": "配置文件",
"content": {
"interfaceUrl": "https://iot.1000my.com/api",
"mobileNav": "https://qianmu-iot.1000my.com/SuZhou_JinDi_Square-szjds_react-test/index.html#/",
"handWriteUrl": "http://saas.1000my.com:8014/words"
}
}
]
}

16
public/static/offline/JSON/getCustomerQr.json

@ -0,0 +1,16 @@
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1476,
"entryCode": "ogMDNkoxrQrFIXWP06T6y",
"title": "顾客心声二维码",
"content": {
"qrUrl": [
"/iotFile/project-bg9aktmmvxxfvi6vya0dua/20240715/ZR8QBUPdCyn2qZIszWaT2.jpg"
]
}
}
]
}

29
public/static/offline/JSON/getDevCoordinateByIP.json

@ -0,0 +1,29 @@
{
"code": 200,
"msg": "success",
"data": {
"machineCode": "zYCYOZ5QfR9jvXfnnoSDj",
"machineName": "导视",
"machineTypeName": "导视",
"label": "windows",
"screenAttribute": "1920*1080横屏",
"building": "苏州金地广场",
"buildingCode": "gDQKBSW9Poutfp1gDkOeb",
"buildingOrder": 0,
"floor": "L3",
"floorCode": "Ek_MaiuKLPjakB1uB0uQV",
"floorOrder": 6,
"ip": "192.168.1.68",
"mac": "30B49EC47D5E",
"location": "51",
"angle": "0",
"projectCode": "project-ao3xebi-o6mwtgswdr-jfa",
"regionCode": "",
"lensCoordinate": "",
"orientationCoordinate": "",
"deployType": "saas",
"xfyunIp": "",
"xfyunPort": "",
"style": "0"
}
}

16
public/static/offline/JSON/getLogo.json

@ -0,0 +1,16 @@
{
"code": 200,
"msg": "操作成功",
"data": [
{
"id": 1,
"entryCode": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "内容标题",
"content": {
"logo": [
"logo.png"
]
}
}
]
}

26
public/static/offline/JSON/getWeather.json

@ -0,0 +1,26 @@
{
"code": 200,
"msg": "操作成功",
"data": {
"area": "苏州",
"o3": "41",
"city": "苏州",
"windPower": "2级",
"aqi_pm25": "70",
"pm10": "70",
"co": "1",
"quality": "良",
"no2": "42",
"temperatureHigh": "19",
"update_time": "1701207063",
"temperatureNow": "11.7",
"temperatureLow": "7",
"so2": "10",
"weather": "多云转阴",
"aqi": "94",
"humidity": "73%",
"windDirection": "东南风",
"prov": "江苏",
"pm": "127"
}
}

57
src/App.vue

@ -0,0 +1,57 @@
<template>
<Header />
<List :customer-list="list" :qr="qr" />
<!-- 倒计时返回首页提示 -->
<AutoBackNotification v-if="showCountDownDialog" :title="title" :delay="toWallpaperTime" />
</template>
<script setup lang="ts">
import { ref, onMounted, defineAsyncComponent, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useRootStore } from './stores/root'
import { getCustomerQr, getCustomerList } from './http/api'
import { HTTP_CODE } from './enums'
import { useHandleScreen } from '@/composables/useHandleScreen'
import Header from './components/Header/Header.vue'
import List from './components/List/List.vue'
const AutoBackNotification = defineAsyncComponent(() => import('@/base/AutoBackNotification/AutoBackNotification.vue'))
const store = useRootStore()
const { device } = storeToRefs(store)
const qr = ref<string>('')
const customerInfo = ref<Customer | null>(null)
const list = computed(() => {
return customerInfo.value?.proposalList
? customerInfo.value?.proposalList.map(item => ({
...item,
managerSignature: customerInfo.value?.managerSignature,
sealUrl: customerInfo.value?.sealUrl
}))
: []
})
getCustomerList(device.value.projectCode).then(res => {
if (res.code === HTTP_CODE.ERR_OK) {
customerInfo.value = res.data
}
})
getCustomerQr().then(res => {
qr.value = res.data.map(item => item.content)?.[0]?.qrUrl?.[0] ?? ''
})
const { checkHandleScreen, showCountDownDialog, title, toWallpaperTime } = useHandleScreen(handleScreen)
//
function handleScreen() {
//TODO
// router.push('/transfer')
}
onMounted(async () => {
window.addEventListener('touchend', checkHandleScreen)
})
</script>

BIN
src/assets/font/HarmonyOS_Sans_SC_Bold.ttf

Binary file not shown.

BIN
src/assets/font/HarmonyOS_Sans_SC_Regular.ttf

Binary file not shown.

533
src/assets/iconfont/iconfont.scss

@ -0,0 +1,533 @@
/* stylelint-disable selector-class-pattern */
@font-face {
font-family: iconfont; /* Project id 4149973 */
src:
url('./iconfont.woff2?t=1713419236420') format('woff2'),
url('./iconfont.woff?t=1713419236420') format('woff'),
url('./iconfont.ttf?t=1713419236420') format('truetype');
}
.iconfont {
font-size: 16px;
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: iconfont !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-shuangzhipingyi::before {
content: '\e663';
}
.icon-shuangzhisuofang::before {
content: '\e6d2';
}
.icon-danzhixuanzhuan::before {
content: '\e6d3';
}
.icon-zuijialuxian1::before {
content: '\e6d1';
}
.icon-zhitimoshi::before {
content: '\e6d0';
}
.icon-tianqi_02_wutian::before {
content: '\e6ca';
}
.icon-tianqi_02_yutian::before {
content: '\e6cb';
}
.icon-tianqi_02_yintian::before {
content: '\e6cc';
}
.icon-tianqi_02_xuetian::before {
content: '\e6cd';
}
.icon-tianqi_06_qingtian::before {
content: '\e6ce';
}
.icon-tianqi_06_duoyun::before {
content: '\e6cf';
}
.icon-jichu_gongneng_lvzhi::before {
content: '\e6a6';
}
.icon-jichu_gongneng_qidian::before {
content: '\e6a7';
}
.icon-jichu_gongneng_pingjia-cha::before {
content: '\e6a8';
}
.icon-jichu_gongneng_pingjia::before {
content: '\e6a9';
}
.icon-jichu_gongneng_shouji::before {
content: '\e6aa';
}
.icon-jichu_gongneng_shanchu::before {
content: '\e6ab';
}
.icon-jichu_gongneng_jiaofei::before {
content: '\e6ac';
}
.icon-jichu_gongneng_pingjia-zhong::before {
content: '\e6ad';
}
.icon-jichu_gongneng_tonghua::before {
content: '\e6ae';
}
.icon-jichu_gongneng_mima::before {
content: '\e6af';
}
.icon-jichu_gongneng_sousuo::before {
content: '\e6b0';
}
.icon-jichu_gongneng_shouxie::before {
content: '\e6b1';
}
.icon-jichu_gongneng_yuyin::before {
content: '\e6b2';
}
.icon-jichu_gongneng_weixiao::before {
content: '\e6b3';
}
.icon-jichu_gongneng_weigouxuan::before {
content: '\e6b4';
}
.icon-jichu_gongneng_shijian::before {
content: '\e6b5';
}
.icon-jichu_gongneng_zengjia::before {
content: '\e6b6';
}
.icon-jichu_gongneng_xihuan::before {
content: '\e6b7';
}
.icon-jichu_gongneng_zhifubao::before {
content: '\e6b8';
}
.icon-jichu_gongneng_ABC::before {
content: '\e6b9';
}
.icon-jichu_gongneng_pingjia-hao::before {
content: '\e6ba';
}
.icon-jichu_gongneng_pingjia-1::before {
content: '\e6bb';
}
.icon-tianqi_02_duoyun::before {
content: '\e6bc';
}
.icon-jichu_gongneng_LOGO::before {
content: '\e6bd';
}
.icon-tianqi_01_yintian::before {
content: '\e6be';
}
.icon-tianqi_01_wutian::before {
content: '\e6bf';
}
.icon-jichu_gongneng_yetai::before {
content: '\e6c0';
}
.icon-jichu_gongneng_wenti::before {
content: '\e6c1';
}
.icon-tianqi_01_duoyun::before {
content: '\e6c2';
}
.icon-tianqi_01_xuetian::before {
content: '\e6c3';
}
.icon-tianqi_01_yutian::before {
content: '\e6c4';
}
.icon-jichu_gongneng_shouxieziti::before {
content: '\e6c5';
}
.icon-tianqi_02_qingtian::before {
content: '\e6c6';
}
.icon-jichu_gongneng_weixinzhifu::before {
content: '\e6c7';
}
.icon-jichu_gongneng_zhongdian::before {
content: '\e6c8';
}
.icon-tianqi_01_qingtian::before {
content: '\e6c9';
}
.icon-jichu_fangxiang_zhexiang-qianzuo::before {
content: '\e679';
}
.icon-jichu_fangxiang_xiangzuochufa::before {
content: '\e67a';
}
.icon-jichu_fangxiang_renzixing-shang::before {
content: '\e67b';
}
.icon-jichu_erjicaidan_tuijian-haochide::before {
content: '\e67c';
}
.icon-jichu_fangxiang_xiangqianchufa::before {
content: '\e67d';
}
.icon-jichu_fangxiang_zhexiang-houzuo::before {
content: '\e67e';
}
.icon-jichu_fangxiang_zhixiang-xia::before {
content: '\e67f';
}
.icon-jichu_fangxiang_zhexiang-youqian::before {
content: '\e680';
}
.icon-jichu_fangxiang_zhexiang-qianyou::before {
content: '\e681';
}
.icon-jichu_fangxiang_renzixing-xia::before {
content: '\e682';
}
.icon-jichu_fangxiang_zhexiang-youhou::before {
content: '\e683';
}
.icon-jichu_fangxiang_renzixing-you::before {
content: '\e684';
}
.icon-jichu_fangxiang_tuozihao-zuo::before {
content: '\e685';
}
.icon-jichu_fangxiang_tuozihao-shang::before {
content: '\e686';
}
.icon-jichu_fangxiang_zhexiang-zuoqian::before {
content: '\e687';
}
.icon-jichu_fangxiang_tuozihao-xia::before {
content: '\e688';
}
.icon-jichu_gongneng_changjianwenti::before {
content: '\e689';
}
.icon-jichu_fangxiang_zhexiang-zuohou::before {
content: '\e68a';
}
.icon-jichu_fangxiang_zhixiang-zuoxia::before {
content: '\e68b';
}
.icon-jichu_fangxiang_zhixiang-shang::before {
content: '\e68c';
}
.icon-jichu_erjicaidan_tuijian-zhidemai::before {
content: '\e68d';
}
.icon-jichu_fangxiang_zhixiang-youshang::before {
content: '\e68e';
}
.icon-jichu_fangxiang_zhixiang-zuo::before {
content: '\e68f';
}
.icon-jichu_fangxiang_xiangyouchufa::before {
content: '\e690';
}
.icon-jichu_fangxiang_zhixiang-youxia::before {
content: '\e691';
}
.icon-jichu_gongneng_anquan::before {
content: '\e692';
}
.icon-jichu_fangxiang_zhixiang-you::before {
content: '\e693';
}
.icon-jichu_fangxiang_zhixiang-zuoshang::before {
content: '\e694';
}
.icon-jichu_gongneng_dianzan-cai::before {
content: '\e695';
}
.icon-jichu_gongneng_dianpubiaoqian-huodong::before {
content: '\e696';
}
.icon-jichu_gongneng_jianpan::before {
content: '\e697';
}
.icon-jichu_gongneng_goumai::before {
content: '\e698';
}
.icon-jichu_gongneng_dianzan-zan::before {
content: '\e699';
}
.icon-jichu_gongneng_dui::before {
content: '\e69a';
}
.icon-jichu_gongneng_dianpubiaoqian-paidui::before {
content: '\e69b';
}
.icon-jichu_gongneng_fuwutai::before {
content: '\e69c';
}
.icon-jichu_gongneng_dianhua::before {
content: '\e69d';
}
.icon-jichu_gongneng_gouxuan::before {
content: '\e69e';
}
.icon-jichu_gongneng_guanbi::before {
content: '\e69f';
}
.icon-jichu_gongneng_fanhui::before {
content: '\e6a0';
}
.icon-jichu_gongneng_gengduo::before {
content: '\e6a1';
}
.icon-jichu_gongneng_dianpubiaoqian-cuxiao::before {
content: '\e6a2';
}
.icon-jichu_gongneng_dianpuzhongdian::before {
content: '\e6a3';
}
.icon-jichu_gongneng_dianpubiaoqian-xindian::before {
content: '\e6a4';
}
.icon-jichu_gongneng_jianshao::before {
content: '\e6a5';
}
.icon-jichu_ditudaohang_fangxiang-mian::before {
content: '\e652';
}
.icon-jichu_ditudaohang_fangxiang-xie::before {
content: '\e653';
}
.icon-jichu_ditudaohang_fuwei-tianchong::before {
content: '\e654';
}
.icon-jichu_ditudaohang_zanting::before {
content: '\e655';
}
.icon-jichu_ditudaohang_jiasu::before {
content: '\e656';
}
.icon-jichu_ditudaohang_tingzhijiasu::before {
content: '\e657';
}
.icon-jichu_ditudaohang_futimoshi::before {
content: '\e658';
}
.icon-jichu_ditudaohang_fangxiang-zheng::before {
content: '\e659';
}
.icon-jichu_ditudaohang_zhongbo::before {
content: '\e65a';
}
.icon-jichu_erjicaidan_fenlei-louceng::before {
content: '\e65b';
}
.icon-jichu_ditudaohang_fuwei::before {
content: '\e65c';
}
.icon-jichu_ditudaohang_jixubofang::before {
content: '\e65d';
}
.icon-jichu_ditudaohang_2Dditu::before {
content: '\e65e';
}
.icon-jichu_ditudaohang_3Dditu::before {
content: '\e660';
}
.icon-jichu_erjicaidan_fuwu-jingdian::before {
content: '\e661';
}
.icon-jichu_erjicaidan_fenlei-yetai::before {
content: '\e662';
}
.icon-jichu_erjicaidan_fuwu-zhoubian::before {
content: '\e664';
}
.icon-jichu_erjicaidan_huiyuan-jifen::before {
content: '\e665';
}
.icon-jichu_erjicaidan_huiyuan-yewu::before {
content: '\e666';
}
.icon-jichu_erjicaidan_huiyuan-huodong::before {
content: '\e667';
}
.icon-jichu_erjicaidan_fuwu-shangchangjieshao::before {
content: '\e668';
}
.icon-jichu_erjicaidan_huiyuan-quanyi::before {
content: '\e669';
}
.icon-jichu_erjicaidan_huodong-shangchang::before {
content: '\e66a';
}
.icon-jichu_erjicaidan_fuwu-tiexinfuwu::before {
content: '\e66b';
}
.icon-jichu_fangxiang_fangxiangxing-xiao::before {
content: '\e66c';
}
.icon-jichu_erjicaidan_xunche-cheweihao::before {
content: '\e66d';
}
.icon-jichu_erjicaidan_tuijian-haowande::before {
content: '\e66e';
}
.icon-jichu_erjicaidan_tuijian-gengduo::before {
content: '\e66f';
}
.icon-jichu_fangxiang_zhexiang-houyou::before {
content: '\e670';
}
.icon-jichu_erjicaidan_huodong-pinpai::before {
content: '\e671';
}
.icon-jichu_fangxiang_tuozihao-you::before {
content: '\e672';
}
.icon-jichu_fangxiang_fangxiangxing-you::before {
content: '\e673';
}
.icon-jichu_erjicaidan_xunche-chepaihao::before {
content: '\e674';
}
.icon-jichu_fangxiang_fangxiangxing-zuo::before {
content: '\e675';
}
.icon-jichu_fangxiang_renzixing-zuo::before {
content: '\e676';
}
.icon-jichu_fangxiang_xianghouchufa::before {
content: '\e677';
}
.icon-jichu_fangxiang_fangxiangxing-shang::before {
content: '\e678';
}

BIN
src/assets/iconfont/iconfont.ttf

Binary file not shown.

BIN
src/assets/iconfont/iconfont.woff

Binary file not shown.

BIN
src/assets/iconfont/iconfont.woff2

Binary file not shown.

5
src/assets/images/a.svg

@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.4375" y="0.4375" width="27.125" height="27.125" rx="6.5625" fill="#F5F5F5"/>
<rect x="0.4375" y="0.4375" width="27.125" height="27.125" rx="6.5625" stroke="#808080" stroke-width="0.875"/>
<path d="M7.46875 21.7385L12.5928 6.26147H15.4068L20.5308 21.7385H17.9478L16.6458 17.3495H11.2698L9.96775 21.7385H7.46875ZM11.8578 15.4175H16.0577L15.4488 13.3385C14.9448 11.6585 14.4828 9.93647 13.9998 8.19347H13.9158C13.4538 9.95747 12.9708 11.6585 12.4668 13.3385L11.8578 15.4175Z" fill="#808080"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

BIN
src/assets/images/bg_v.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
src/assets/images/hand.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

3
src/assets/images/heart.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" fill="none">
<path d="M20.0014 7.5483C23.9163 4.0333 29.9663 4.14997 33.738 7.9283C37.508 11.7083 37.638 17.7283 34.1313 21.655L19.998 35.8083L5.86802 21.655C2.36135 17.7283 2.49302 11.6983 6.26135 7.9283C10.0364 4.15497 16.0747 4.0283 20.0014 7.5483ZM31.378 10.2833C28.878 7.77997 24.8447 7.6783 22.228 10.0283L20.003 12.025L17.7764 10.03C15.1514 7.67663 11.1264 7.77997 8.61968 10.2866C6.13635 12.77 6.01135 16.745 8.29968 19.3716L19.9997 31.09L31.6997 19.3733C33.9897 16.745 33.8647 12.775 31.378 10.2833Z" fill="black" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 634 B

41
src/assets/images/logo.svg

@ -0,0 +1,41 @@
<svg width="240" height="62" viewBox="0 0 240 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_238_10038)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.8672 17.0752C48.8672 7.8103 56.3219 0.549316 65.8469 0.549316C75.4177 0.549316 82.9081 7.8001 82.9081 17.0752C82.9081 26.3503 75.3718 33.6062 65.8469 33.6062C56.3219 33.6062 48.8672 26.3401 48.8672 17.0752ZM53.028 17.065C53.028 24.1934 58.8052 29.9757 65.9336 29.9757C73.0671 29.9757 78.8493 24.1934 78.8493 17.065C78.8493 9.93659 73.062 4.15431 65.9336 4.15431C58.8052 4.15431 53.028 9.93659 53.028 17.065Z" fill="#B60081"/>
<path d="M156.32 4.35523H156.233L146.106 32.8638H141.981L131.855 4.35523H131.768V32.8638H127.383V1.19385H135.23L144.347 27.2906H144.434L153.388 1.19385H160.705V32.8587H156.32V4.35523Z" fill="#B60081"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M163.789 32.8638L163.791 32.8587H168.307L171.723 24.6187H186.413L189.835 32.8587H194.613L181.809 1.19385H177.123L163.791 32.8587H163.789V32.8638ZM173.217 20.8556H185.011L179.27 5.5382L173.217 20.8556Z" fill="#B60081"/>
<path d="M197.812 1.19385V32.8587H216.929V29.0905H202.193V1.19385H197.812Z" fill="#B60081"/>
<path d="M220.904 32.8638V32.8587H240.015V29.0905H225.284V1.19385H220.904V32.8587H220.898L220.904 32.8638Z" fill="#B60081"/>
<path d="M4.65539 32.8858H0L23.9602 1.27197H47.0128V4.98915H32.2614V29.1788H47.3647V32.9011H28.1465V1.89915L4.65539 32.8858Z" fill="#B60081"/>
<path d="M106.537 1.27197V25.4515L89.1906 1.27197H86.0547V32.9011H89.9401V8.71652L107.328 32.9011H110.418V1.27197H106.537Z" fill="#B60081"/>
<path d="M52.9468 58.546C53.3752 58.2298 53.7678 57.8066 54.1247 57.2763C54.4816 56.7409 54.7621 56.2361 54.9609 55.7619C55.1649 55.2877 55.2873 54.8594 55.3332 54.4821L55.3842 54.0792H53.4261L52.0953 54.1149V52.4527L53.2987 52.4832H55.9349L57.2606 52.4527C57.2453 52.8351 57.23 53.1869 57.2096 53.4979C57.1892 53.809 57.1331 54.2322 57.0413 54.7625C56.9496 55.2928 56.8017 55.8027 56.5977 56.2922C56.3938 56.7817 56.1541 57.2508 55.8737 57.7097C55.5983 58.1635 55.3332 58.5409 55.0833 58.8468C54.8335 59.1527 54.5173 59.4791 54.14 59.836C53.7627 60.1878 53.3802 60.4938 53.0029 60.7487C52.6664 60.2337 52.3553 59.8615 52.0596 59.6321L51.6211 59.2802C52.08 59.1069 52.5236 58.8621 52.9519 58.546H52.9468Z" fill="#231815"/>
<path d="M54.4816 49.8114L54.4918 49.8118V51.5093L55.787 51.4634H58.0611V58.597C58.0611 58.8213 58.0509 58.9641 58.0407 59.0202C58.0254 59.0814 57.9999 59.1272 57.9592 59.168C57.8623 59.2649 57.3473 59.27 56.4193 59.1731L56.6742 59.734C56.7711 59.9533 56.8425 60.1725 56.8782 60.3867C56.9139 60.6009 56.9445 60.7895 56.9699 60.9527C57.3626 60.9782 57.847 60.9527 58.4181 60.8762C58.9892 60.8048 59.3512 60.6722 59.5092 60.4887C59.6673 60.3051 59.7438 60.0808 59.7438 59.8105V54.8951C60.1415 55.6905 60.5545 56.3942 60.9829 57.0061C61.4112 57.623 61.8956 58.1737 62.4361 58.6581C62.9766 59.1476 63.3896 59.4893 63.67 59.6881C63.9505 59.887 64.3533 60.1369 64.8785 60.4428C65.1946 59.8564 65.4394 59.4689 65.6077 59.2751C65.7708 59.0814 65.9238 58.908 66.0666 58.7601C65.6128 58.5868 65.2099 58.3981 64.8581 58.189C64.5063 57.98 64.0627 57.6332 63.5273 57.1437C62.9919 56.6542 62.4565 56.0525 61.916 55.3336C63.0531 54.0282 64.1646 52.6413 65.2558 51.1626C64.44 50.6833 63.9046 50.3315 63.6394 50.1173C63.3539 50.6323 63.0123 51.1779 62.6196 51.7541C62.227 52.3354 61.6865 53.0033 61.0033 53.7733L60.733 53.2889C60.6412 53.1308 60.5138 52.8504 60.3506 52.4476C60.1874 52.0447 60.0599 51.6827 59.9682 51.3564C59.8713 51.0249 59.8152 50.7394 59.7897 50.4895C59.7642 50.2397 59.7438 50.0204 59.7285 49.8369L58.0509 49.8726H55.8584L54.4918 49.8118V49.8012L54.4816 49.8114Z" fill="#231815"/>
<path d="M56.5773 46.4868C57.077 46.6653 58.7444 47.267 61.5743 48.2919C61.2888 48.9241 61.1154 49.3372 61.0492 49.5258L60.8452 50.102C59.856 49.6431 58.6628 49.1536 57.2504 48.6386L56.0011 48.1899L55.3434 47.9451C55.7972 47.4913 56.2051 47.0069 56.5722 46.4919L56.5773 46.4868Z" fill="#231815"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.3242 58.999L67.3701 57.301V49.6219L67.3242 48.0565L68.6143 48.0973H71.1077L72.2906 48.0565L72.2295 49.6372V57.301L72.2906 58.8868H75.7784V54.5985H74.2589L72.9127 54.6495V52.8394L74.2385 52.9006H75.7784V49.2395H74.0039L72.6425 49.3007V47.5415L74.0396 47.5925H79.3018L80.9182 47.5415V49.3007L79.2865 49.2395H77.5936V52.9006H79.2151L80.6071 52.8394V54.6495L79.2304 54.5985H77.5936V58.8868H79.5414L81.1629 58.8358V60.6459L79.5159 60.5848H73.7694L72.102 60.6459V59.0296H70.506V58.693H69.0783V58.999H67.3242ZM69.0783 52.5844H70.506V49.6576H69.0783V52.5844ZM69.0783 57.0817H70.506V54.2467H69.0783V57.0817Z" fill="#231815"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.0765 50.1969H84.617V50.202C84.3468 50.5844 84.0459 50.9362 83.7145 51.2524C83.3831 51.5685 82.9344 51.849 82.3633 52.0886L82.7355 52.6189C82.8936 52.8382 83.0669 53.2206 83.2505 53.7713L83.8114 53.3379C84.1173 53.1033 84.6629 52.6036 85.4329 51.8235V52.3691L85.3819 53.5265H87.2226L87.1767 52.3691V51.2371L87.5796 51.6705C87.707 51.8031 87.9212 52.1141 88.2322 52.5985L88.4566 52.313C88.5586 52.1855 88.839 51.9306 89.303 51.5583L88.9818 51.1453C88.9563 51.1198 88.6554 50.8037 88.0844 50.1969L89.252 50.2479V48.5499L88.3036 48.6009H87.1818V47.9482L87.2277 46.8774H85.387L85.438 47.9482V48.6009H84.0765L82.812 48.5499V50.2479L84.0765 50.1969ZM87.4776 50.615C87.3909 50.666 87.2889 50.7221 87.1818 50.7782V50.1969H87.962C87.7274 50.4212 87.5643 50.5589 87.4776 50.615Z" fill="#231815"/>
<path d="M85.8663 59.1049C85.1065 59.1456 84.5711 59.1609 84.2601 59.1456L84.2703 59.1558C83.9593 59.1405 83.5564 59.1049 83.0567 59.0437L83.3423 59.7473C83.4341 59.9819 83.5106 60.3592 83.5717 60.8946C85.8714 60.8079 87.4674 60.6397 88.3546 60.3898C89.2469 60.1349 90.0169 59.8442 90.6798 59.5128C91.3375 59.1813 91.9137 58.8295 92.3981 58.4624C92.8825 58.0952 93.3975 57.6261 93.9431 57.0602C94.4836 56.4942 94.8303 56.0862 94.9731 55.8415C95.1159 55.5916 95.2383 55.3571 95.3453 55.1327L94.6876 54.2098H89.0838C89.2571 53.9753 89.4254 53.7407 89.5835 53.5061C88.8492 53.2767 88.4056 53.1186 88.2526 53.037C88.0997 52.9555 87.9365 52.8586 87.7682 52.7362C87.5949 53.0778 87.3705 53.4348 87.0952 53.7917C86.8249 54.1486 86.3609 54.5667 85.7133 55.046C85.0657 55.5254 84.1989 55.8925 83.1179 56.1525L83.5004 56.6318C83.6278 56.795 83.8063 57.1723 84.0307 57.7638C84.9332 57.3865 85.5502 57.1009 85.8918 56.897C86.2283 56.693 86.7484 56.331 87.447 55.8058H92.7296C92.3012 56.3157 91.8525 56.7644 91.3732 57.1468C90.8939 57.5293 90.3687 57.8556 89.7976 58.1309C89.609 57.876 89.3744 57.5599 89.1042 57.1978C88.8934 56.9168 88.6976 56.6478 88.5122 56.393C88.4555 56.3151 88.3993 56.2379 88.3444 56.1627C87.8549 56.5044 87.3603 56.7848 86.8555 57.0092L87.3807 57.6006C87.5643 57.8046 87.8141 58.1666 88.1455 58.6918L87.5898 58.855C87.1971 58.9774 86.626 59.0641 85.8663 59.1049Z" fill="#231815"/>
<path d="M89.4509 51.8541C89.2673 51.9458 89.0685 52.0325 88.8594 52.109H88.8441C89.201 52.5577 89.4203 52.8688 89.5121 53.037C89.6019 53.2016 89.6868 53.3809 89.7763 53.57L89.7823 53.5826C90.8276 52.7821 91.4089 52.3334 91.5262 52.2365V52.573L91.465 53.6489H93.321L93.27 52.5577V51.441L93.6525 51.9509C93.7901 52.1447 94.0451 52.3997 94.4224 52.7158C94.7997 53.0268 95.2638 53.343 95.8195 53.6642C96.0541 53.1237 96.2122 52.7821 96.2989 52.6444C96.3855 52.5067 96.5181 52.3283 96.6966 52.109C96.0082 51.8286 95.4932 51.5226 95.1465 51.1861C94.8048 50.8445 94.504 50.513 94.2491 50.1918H95.172L96.1969 50.2428V48.5448L95.172 48.5958H93.2803V47.9431L93.3312 46.8927H91.4752L91.5364 47.9431V48.5958H90.6696L89.5937 48.5448V50.2428L90.7613 50.1918H91.1591C91.0979 50.2428 91.001 50.3754 90.8684 50.5895C90.7358 50.8037 90.5166 51.0433 90.2004 51.3136C89.8843 51.5838 89.6345 51.7674 89.4509 51.8541Z" fill="#231815"/>
<path d="M103.443 55.9921C103.05 56.6091 102.693 57.119 102.367 57.5167C102.046 57.9145 101.791 58.2204 101.597 58.4244C101.403 58.6283 101.097 58.9496 100.669 59.3728C100.24 59.796 99.9906 60.0357 99.9193 60.0917L99.3074 60.5863C99.1748 60.4436 99.0473 60.311 98.9249 60.1886C98.8077 60.0662 98.4303 59.7348 97.793 59.1943C98.0887 59.0362 98.3437 58.8833 98.5629 58.7405C98.7822 58.5926 99.0932 58.3275 99.5011 57.9298C99.9091 57.5371 100.302 57.0986 100.689 56.6193C101.072 56.1349 101.316 55.7678 101.423 55.5128L101.694 54.901C102.209 55.2732 102.79 55.6403 103.443 55.9921Z" fill="#231815"/>
<path d="M100.052 47.8592C100.266 47.8745 100.679 47.8694 101.286 47.8541C102.316 47.8235 103.32 47.7827 104.294 47.7267C105.268 47.6706 106.268 47.5431 107.287 47.3493C108.312 47.1556 108.914 47.0077 109.108 46.921L109.893 46.5845C110.046 46.8292 110.168 47.0077 110.25 47.125L110.857 47.9612L111.214 48.4609C110.051 48.7006 109.036 48.8841 108.169 49.0167C107.303 49.1442 106.257 49.2615 105.028 49.3634C103.8 49.4654 102.484 49.547 101.072 49.6082L100.786 52.9378H104.141V51.1991L104.09 49.6796H106.058L106.007 51.2348V52.9327H109.495L111.489 52.8817V54.8194L109.521 54.7582H106.013V59.6583C106.013 59.9694 105.982 60.1937 105.926 60.3416C105.87 60.4844 105.63 60.6169 105.212 60.7291C104.794 60.8464 104.371 60.9076 103.942 60.9178C103.642 60.928 103.366 60.9178 103.111 60.8974C103.009 60.2498 102.923 59.8266 102.841 59.6277L102.591 58.9853C103.234 59.0413 103.636 59.0566 103.8 59.0362C103.963 59.0107 104.06 58.9802 104.09 58.9343C104.121 58.8884 104.136 58.8476 104.141 58.8119C104.146 58.7711 104.151 58.6028 104.151 58.2969V54.7633H100.444L98.5221 54.8245C98.5884 54.5236 98.6547 54.2024 98.7261 53.8506C98.7975 53.4987 98.8689 53.0347 98.9402 52.4483C99.0116 51.867 99.0779 51.1991 99.1391 50.4444C99.2003 49.6949 99.236 49.2003 99.2462 48.9759L99.2819 47.7776C99.5827 47.8184 99.8377 47.8439 100.052 47.8592Z" fill="#231815"/>
<path d="M107.67 55.2987L108.006 54.8857L108.97 55.8494C109.155 56.0346 109.345 56.2258 109.54 56.4219L109.541 56.4225L109.544 56.4256C109.75 56.6331 109.962 56.846 110.178 57.0629C110.602 57.4861 110.969 57.8533 111.29 58.1694C111.606 58.4856 111.78 58.6538 111.805 58.6691L112.305 59.23L112.242 59.2912C112.11 59.4187 112.01 59.5156 111.948 59.5818L111.407 60.1427L110.816 60.724C110.576 60.3824 110.362 60.0968 110.173 59.8572C109.985 59.6175 109.5 59.0872 108.725 58.2612C107.95 57.4352 107.425 56.9202 107.15 56.706L106.594 56.2777C107.226 55.7168 107.583 55.3905 107.665 55.2885L107.67 55.2987Z" fill="#231815"/>
<path d="M114.683 51.5729V55.5196C114.112 55.6725 113.536 55.7796 112.949 55.851C113.225 56.6312 113.383 57.0901 113.423 57.2379C113.464 57.3685 113.5 57.511 113.54 57.6655L113.556 57.7274C114.03 57.5235 114.576 57.3195 115.198 57.1156C115.658 56.9685 116.017 56.8466 116.279 56.758L116.413 56.7126L116.469 56.6937L116.519 56.677C116.773 56.5853 116.957 56.5292 117.059 56.4986C117.133 56.4726 117.232 56.4467 117.352 56.4148C117.398 56.4029 117.446 56.3901 117.498 56.3762L117.472 55.8918C117.457 55.6827 117.457 55.489 117.472 55.3054C117.487 55.1218 117.508 54.9281 117.538 54.7241L117.268 54.8363C117.258 54.8414 116.957 54.9332 116.371 55.1066V51.5729H116.702L117.487 51.6035V49.9413L116.702 49.9872H116.371V48.1974L116.432 46.8156H114.622L114.683 48.1974V49.9872H114.056L113.179 49.9413V51.6035L114.056 51.5729H114.683Z" fill="#231815"/>
<path d="M116.304 59.0481C116.034 59.2724 115.743 59.4866 115.422 59.6855L115.438 59.7059C116.034 60.0118 116.401 60.2565 116.539 60.435C116.669 60.6042 116.772 60.7459 116.852 60.8558L116.865 60.8735C117.38 60.4095 117.803 59.9761 118.125 59.5733C118.446 59.1705 118.716 58.7319 118.941 58.2577C119.16 57.7835 119.313 57.2736 119.389 56.728C119.466 56.1773 119.517 55.7286 119.542 55.3666C119.568 55.0046 119.593 54.6374 119.619 54.2448H120.353V55.4992C120.343 55.7337 120.322 56.0091 120.297 56.3303C120.271 56.6516 120.205 56.8555 120.103 56.9524C119.996 57.0493 119.741 57.1054 119.333 57.1258L119.619 57.707C119.726 57.9212 119.818 58.2577 119.904 58.7166C120.18 58.681 120.486 58.5841 120.822 58.4209C121.154 58.2577 121.393 58.0742 121.531 57.86C121.669 57.651 121.755 57.4011 121.791 57.1207C121.827 56.8402 121.837 56.4731 121.832 56.0193C121.821 55.5706 121.821 54.9536 121.832 54.1683L121.867 52.7457L121.031 52.7814H119.619V51.2007L121.781 51.1905L121.867 52.0675C121.918 52.608 122.02 53.3117 122.178 54.1785C122.336 55.0454 122.469 55.6674 122.571 56.0499C122.664 56.3995 122.77 56.7449 122.877 57.0939L122.908 57.192C122.561 57.6255 122.173 58.0436 121.745 58.4464C121.317 58.8441 120.73 59.196 119.976 59.4968C120.491 59.9812 120.802 60.3024 120.899 60.4656C120.981 60.6042 121.052 60.7318 121.115 60.8452L121.148 60.9041C121.495 60.7155 121.852 60.4809 122.209 60.1954C122.566 59.9098 123.05 59.4764 123.652 58.9002L124.065 59.4611C124.182 59.6294 124.458 59.92 124.896 60.333C125.335 60.741 125.676 60.9245 125.921 60.8735C126.166 60.8225 126.375 60.6849 126.543 60.4503C126.717 60.2209 126.839 60.022 126.915 59.8639C126.981 59.737 127.056 59.5673 127.149 59.3574L127.221 59.196C127.344 58.9104 127.456 58.6555 127.558 58.4362C127.094 58.2118 126.783 58.0283 126.625 57.8804C126.462 57.7325 126.309 57.5796 126.166 57.4266C125.967 58.1456 125.783 58.4821 125.615 58.4413C125.447 58.3954 125.309 58.2883 125.197 58.115L124.937 57.7427C124.886 57.6663 124.83 57.5286 124.764 57.3399L125.09 56.6872C125.33 56.2232 125.523 55.7745 125.671 55.3462C125.819 54.9179 125.982 54.4131 126.166 53.8216C126.231 53.613 126.289 53.4276 126.339 53.2657C126.43 52.9725 126.497 52.7562 126.543 52.6182C125.977 52.4806 125.37 52.2817 124.733 52.0166L124.657 52.7049C124.641 52.8528 124.585 53.1434 124.488 53.5718C124.391 54.0001 124.31 54.3009 124.254 54.4794C124.216 54.5948 124.17 54.726 124.119 54.8701L124.118 54.8721C124.093 54.9438 124.067 55.0187 124.04 55.0964C123.892 54.617 123.764 54.0154 123.662 53.2964C123.56 52.5723 123.504 52.093 123.489 51.8585C123.474 51.6188 123.463 51.3996 123.458 51.1956L125.518 51.1854L126.829 51.2211V49.5435L125.518 49.5945H123.372L123.356 48.7583C123.346 48.4574 123.351 48.1107 123.377 47.713L123.38 47.6677L123.38 47.6665C123.404 47.2901 123.423 46.9844 123.443 46.7493L122.836 46.7901C122.581 46.8156 122.153 46.7748 121.551 46.6626L121.618 47.5753C121.643 47.8864 121.658 48.5594 121.669 49.5894H119.124L117.783 49.5231L117.895 52.608C117.916 53.1026 117.921 53.6431 117.911 54.2142C117.885 55.5094 117.798 56.3609 117.656 56.7739C117.508 57.1869 117.314 57.6153 117.074 58.0538C116.83 58.4923 116.575 58.8237 116.304 59.0481Z" fill="#231815"/>
<path d="M124.555 46.994C124.422 47.1317 124.116 47.3459 123.632 47.6467L123.637 47.6518C124.091 48.1158 124.412 48.4829 124.595 48.7583C124.779 49.0336 124.957 49.3039 125.126 49.5741C125.416 49.2478 125.845 48.8654 126.395 48.4269C125.977 47.8864 125.452 47.3204 124.815 46.734L124.555 46.994Z" fill="#231815"/>
<path d="M133.131 56.1952C132.887 55.7057 132.519 55.0174 132.122 54.3596V61.0291H130.454V54.8185C130.011 55.9658 129.506 57.006 128.955 57.7096C128.787 57.2201 128.405 56.4094 128.145 55.9199C129.047 54.9103 129.888 53.0746 130.363 51.4532H128.527V49.7705H130.454V46.757H132.122V49.7705H133.483V51.4532H132.122V51.9121C132.672 52.5392 133.927 54.2525 134.217 54.6502L133.131 56.1952Z" fill="#231815"/>
<path d="M141.116 59.3464C141.193 59.3464 141.269 59.3311 141.331 59.2852C141.376 59.2087 141.407 59.0864 141.422 58.7804C141.438 58.5051 141.453 57.832 141.468 56.9907C141.789 57.2813 142.34 57.572 142.784 57.7096C142.768 58.5204 142.707 59.4841 142.615 59.8818C142.508 60.2642 142.34 60.509 142.111 60.6926C141.866 60.8761 141.499 60.9373 141.178 60.9373H140.352C139.984 60.9373 139.556 60.8149 139.296 60.5396C139.021 60.2336 138.898 59.9124 138.898 58.7498V53.8701H136.803V54.9562C136.803 56.9754 136.298 59.6218 133.804 61.1668C133.575 60.7843 132.902 60.0501 132.581 59.7747C134.768 58.4439 134.998 56.5165 134.998 54.9256V52.1874H140.703V58.6886C140.703 59.0252 140.719 59.2087 140.749 59.2699C140.795 59.3311 140.841 59.3464 140.918 59.3464H141.116Z" fill="#231815"/>
<path d="M142.386 50.9637H134.019V49.2351H137.154C136.986 48.6232 136.65 47.7207 136.344 47.0629L138.103 46.604C138.455 47.2924 138.822 48.2102 139.021 48.8068L137.598 49.2351H142.386V50.9637Z" fill="#231815"/>
<path d="M145.033 55.3536C145.645 54.2216 145.966 52.4013 146.195 50.8716L144.635 50.4739C144.436 51.9577 144.069 53.6098 143.457 54.7112L145.033 55.3536Z" fill="#231815"/>
<path d="M150.815 54.6347C150.662 53.6251 150.142 52.0189 149.591 50.7645L148.413 51.254V47.0168H146.608V51.8506C146.608 54.497 146.333 57.5105 143.824 59.6062C144.237 59.9274 144.864 60.5852 145.14 61.0288C148.031 58.6272 148.398 55.1548 148.413 52.0036C148.826 53.1662 149.163 54.4817 149.27 55.3536L150.815 54.6347Z" fill="#231815"/>
<path d="M157.056 60.8758V46.9556H155.236V54.0075C154.93 53.0285 154.333 51.6976 153.752 50.6574L152.635 51.2081V47.3533H150.861V60.0345H152.635V51.82C153.186 52.9826 153.691 54.3287 153.874 55.216L155.236 54.4664V60.8758H157.056Z" fill="#231815"/>
<path d="M164.768 56.9966C164.813 57.5014 165.012 58.205 165.135 58.5721C162.167 60.3313 161.647 60.6678 161.341 60.9891C161.188 60.6372 160.744 60.0407 160.5 59.7806C160.821 59.5359 161.341 58.9699 161.341 58.1591V55.8187H159.337V54.1972H161.341V52.6981H160.148V51.5814C160.046 51.6936 159.944 51.7921 159.842 51.8907C159.791 51.94 159.74 51.9893 159.689 52.0403C159.551 51.6579 159.108 50.6942 158.848 50.2659C159.75 49.4245 160.576 48.1396 161.035 46.7934L162.626 47.2829C162.519 47.5736 162.397 47.8948 162.259 48.2008H164.783V49.9293H161.356C161.159 50.2621 160.942 50.565 160.729 50.8622C160.678 50.9339 160.627 51.0052 160.576 51.0766H164.553V52.6981H162.963V54.1972H164.783V55.8187H162.963V58.0062L164.768 56.9966Z" fill="#231815"/>
<path d="M173.12 54.9926C172.416 56.1858 171.498 57.2413 170.443 58.1285C170.764 58.8475 171.131 59.2758 171.483 59.2758C171.774 59.2758 171.911 58.8781 171.988 57.6543C172.324 58.0673 172.86 58.4498 173.227 58.6333C172.936 60.4843 172.493 60.9891 171.299 60.9891C170.366 60.9891 169.632 60.3007 169.051 59.1687C168.056 59.8418 166.955 60.3772 165.823 60.8361C165.594 60.3772 165.15 59.7806 164.722 59.3676C166.037 58.924 167.307 58.3274 168.424 57.5778C168.164 56.7212 167.949 55.7422 167.766 54.6714L165.196 55.1456L164.905 53.5394L167.552 53.0652C167.49 52.5757 167.445 52.0862 167.399 51.5967L165.532 51.8873L165.227 50.2659L167.292 49.9446C167.23 48.8738 167.2 47.7725 167.2 46.7017H168.944C168.928 47.696 168.944 48.7056 168.99 49.6846L172.34 49.1645L172.63 50.7554L169.097 51.3214C169.143 51.8109 169.188 52.2851 169.25 52.7593L172.737 52.1168L173.013 53.6771L169.464 54.3502C169.586 55.0997 169.724 55.8187 169.877 56.4459C170.565 55.8034 171.146 55.0997 171.59 54.3349L173.12 54.9926Z" fill="#231815"/>
<path d="M171.101 49.2869C170.672 48.8891 169.816 48.262 169.204 47.8489L170.259 46.9005C170.886 47.2676 171.743 47.8336 172.187 48.2161L171.101 49.2869Z" fill="#231815"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M183.292 51.7965V52.5002H180.921V49.6549H188.402V48.1252H184.96C184.776 47.651 184.486 47.0391 184.21 46.5649L182.451 46.9933C182.619 47.3298 182.803 47.7428 182.956 48.1252H179.346V53.0509C179.346 54.1676 179.315 55.4678 179.101 56.7528L178.81 55.4678C178.367 55.6666 177.923 55.8656 177.48 56.0338V52.1025H179.025V50.3739H177.48V46.9627H175.812V50.3739H174.283V52.1025H175.812V56.7375C175.185 56.9975 174.634 57.1964 174.16 57.3646L174.741 59.2462C174.905 59.1686 175.073 59.0886 175.246 59.0066C176.4 58.4585 177.745 57.8198 179.009 57.1811C178.795 58.2366 178.428 59.2615 177.847 60.1028C178.229 60.2711 178.918 60.7147 179.208 60.99C180.57 59.032 180.876 56.0338 180.921 53.7239H183.292V54.4123H181.304V55.6514H183.292V56.3703H181.059V61.0512H182.635V60.577H186V61.0359H187.637V56.3703H184.883V55.6514H187.499V53.8616H188.31V52.3625H187.499V50.5575H184.883V49.915H183.292V50.5575H181.35V51.7965H183.292ZM182.635 57.8082H186V59.1391H182.635V57.8082ZM185.969 52.5002H184.883V51.7965H185.969V52.5002ZM185.969 53.7239V54.4123H184.883V53.7239H185.969Z" fill="#231815"/>
<path d="M55.0266 13.1343C61.0996 13.6799 62.3182 15.2402 62.1755 15.9694C61.8032 17.8407 53.9865 19.452 43.921 19.452C33.8556 19.452 25.6462 17.8815 25.6462 15.9694C25.6462 14.0573 35.0079 12.4001 45.7516 12.4001C45.7516 12.4001 46.5368 12.4409 46.5368 12.0992C46.5164 11.6964 45.7822 11.7423 45.7822 11.7423C30.5514 11.7423 22.3828 14.0063 22.3828 17.0657C22.3828 19.9568 32.025 22.3329 43.921 22.3329C55.817 22.3329 65.4235 19.9568 65.4235 17.0657C65.4235 15.3932 62.9046 13.2261 54.9451 12.4664C54.9451 12.4664 54.3536 12.395 54.3128 12.7468C54.272 13.0935 55.0215 13.1394 55.0215 13.1394L55.0266 13.1343Z" fill="#B60081"/>
<path d="M32.2399 8.40088H28.125V16.1463H32.2399V8.40088Z" fill="#B60081"/>
</g>
<defs>
<clipPath id="clip0_238_10038">
<rect width="240" height="62" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

5
src/assets/images/nodata.svg

@ -0,0 +1,5 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M166.216 337.928H169.576V333.272L176.824 332.456L176.776 329.72C174.376 329.96 171.976 330.248 169.576 330.488V326.84H177.064V323.96H169.576V320.84H166.216V323.96H160.024C161.368 322.472 162.664 320.744 163.96 318.872H176.968V316.088H165.736C166.216 315.272 166.696 314.552 167.128 313.736L163.528 312.728C163.048 313.88 162.472 314.984 161.896 316.088H154.888V318.872H160.216C159.256 320.456 158.344 321.656 157.912 322.184C157.048 323.288 156.232 324.104 155.464 324.248C155.896 325.16 156.424 326.792 156.616 327.512C157.048 327.128 158.488 326.84 160.456 326.84H166.216V330.824C161.896 331.208 157.96 331.592 154.936 331.832L155.272 334.808L166.216 333.656V337.928ZM188.344 341.672V345.56H164.104V341.672H188.344ZM164.104 352.136V348.152H188.344V352.136H164.104ZM160.696 356.648H164.104V354.968H188.344V356.552H191.848V338.84H160.696V356.648ZM197.704 323.576H182.488V323.24V318.488C187.336 318.104 192.712 317.336 196.312 316.232L194.392 313.544C190.84 314.744 184.552 315.608 179.272 316.04V323.192C179.272 327.128 178.888 332.264 175.864 336.104C176.584 336.44 177.976 337.4 178.552 337.88C181.048 334.76 182.056 330.392 182.344 326.504H188.776V337.832H192.088V326.504H197.704V323.576ZM232.504 352.088C230.056 352.088 229.576 351.704 229.576 350.024V333.656H246.856V330.152H225.496C226.024 326.6 226.264 322.952 226.36 319.544H244.12V316.088H207.064V319.544H222.856C222.712 322.952 222.568 326.6 221.992 330.152H204.088V333.656H221.368C219.352 341.864 214.744 349.496 203.464 353.768C204.328 354.44 205.336 355.784 205.864 356.696C218.152 351.752 222.952 342.92 224.92 333.656H225.928V350.024C225.928 354.296 227.224 355.544 232.168 355.544H241.096C245.704 355.544 246.808 353.576 247.288 345.944C246.28 345.704 244.696 345.08 243.88 344.456C243.592 350.984 243.208 352.088 240.856 352.088H232.504ZM271.912 313.736C271.096 315.656 269.512 318.392 268.312 320.12L270.616 321.224C271.96 319.64 273.496 317.24 274.888 315.08L271.912 313.736ZM260.68 320.216C260.2 318.44 258.904 315.896 257.56 314.024L254.968 315.08C256.264 317.048 257.512 319.688 257.944 321.368L260.68 320.216ZM263.032 333.656H266.344V327.08C268.648 328.712 271.624 331.064 272.776 332.168L274.744 329.576C273.64 328.76 269.512 326.072 267.112 324.68H276.136V321.704H266.344V312.824H263.032V321.704H253.144V324.68H261.928C259.576 327.8 256.024 330.728 252.664 332.168C253.336 332.84 254.2 334.04 254.584 334.856C257.512 333.32 260.632 330.68 263.032 327.848V333.656ZM270.328 340.52C269.272 343.016 267.736 345.128 265.864 346.856C264.136 345.992 262.216 345.08 260.44 344.312C261.112 343.208 261.832 341.864 262.552 340.52H270.328ZM272.68 350.84C271.624 350.072 270.184 349.208 268.648 348.344C271.192 345.656 273.16 342.296 274.408 338.168L272.392 337.352L271.864 337.496H264.04C264.424 336.632 264.712 335.816 265.048 335L261.88 334.424C261.544 335.432 261.112 336.44 260.632 337.496H254.152V340.52H259.144C258.136 342.392 257.08 344.168 256.072 345.608C258.424 346.472 261.016 347.672 263.416 348.92C260.296 351.128 256.648 352.664 252.76 353.528C253.384 354.2 254.104 355.496 254.44 356.264C258.808 355.112 262.888 353.24 266.296 350.504C267.928 351.416 269.368 352.376 270.472 353.192L272.68 350.84ZM289.336 325.4C288.616 330.92 287.464 335.72 285.736 339.752C283.912 335.48 282.568 330.584 281.656 325.4H289.336ZM295.912 325.4V322.088H282.376C283.048 319.4 283.624 316.616 284.008 313.784L280.744 313.304C279.544 321.608 277.384 329.624 273.736 334.664C274.504 335.144 275.848 336.248 276.424 336.824C277.624 335 278.728 332.888 279.688 330.536C280.744 335.288 282.088 339.608 283.912 343.352C281.224 347.912 277.48 351.416 272.296 353.96C272.92 354.632 273.928 356.072 274.264 356.84C279.16 354.2 282.808 350.84 285.64 346.664C287.992 350.744 291.016 354.008 294.712 356.264C295.24 355.352 296.248 354.152 297.064 353.48C293.08 351.368 289.96 347.816 287.512 343.448C290.056 338.504 291.64 332.552 292.744 325.4H295.912ZM316.744 336.968L316.264 333.704L311.272 335.288V325.784H316.696V322.472H311.272V312.872H307.96V322.472H302.008V325.784H307.96V336.296C305.464 337.016 303.208 337.688 301.432 338.216L302.296 341.624C304.024 341.096 305.944 340.52 307.96 339.848V352.184C307.96 352.904 307.72 353.096 307.144 353.096C306.616 353.096 304.696 353.096 302.632 353.048C303.112 354.056 303.544 355.496 303.688 356.36C306.712 356.408 308.536 356.264 309.64 355.688C310.84 355.112 311.272 354.2 311.272 352.232V338.744L316.744 336.968ZM322.216 332.552C322.264 331.4 322.264 330.392 322.264 329.336V327.272H331.528V332.552H322.216ZM340.552 318.056V324.152H322.264V318.056H340.552ZM340.888 351.8H326.152V344.552H340.888V351.8ZM345.592 335.624V332.552H334.936V327.272H343.96V314.936H318.808V329.336C318.808 336.92 318.376 347.336 313.432 354.68C314.248 355.016 315.736 356.024 316.312 356.696C320.344 350.792 321.64 342.728 322.072 335.624H331.528V341.528H323.032V356.744H326.152V354.776H340.888V356.552H344.152V341.528H334.936V335.624H345.592Z" fill="#9C938F"/>
<path d="M202.275 407H204.387V397.76C204.387 395.912 204.219 394.04 204.123 392.264H204.219L206.115 395.888L212.523 407H214.803V389.408H212.691V398.552C212.691 400.376 212.859 402.344 212.979 404.12H212.859L210.987 400.496L204.579 389.408H202.275V407ZM226.9 407.312C231.316 407.312 234.412 403.784 234.412 398.144C234.412 392.504 231.316 389.072 226.9 389.072C222.484 389.072 219.388 392.504 219.388 398.144C219.388 403.784 222.484 407.312 226.9 407.312ZM226.9 405.368C223.732 405.368 221.668 402.536 221.668 398.144C221.668 393.728 223.732 391.016 226.9 391.016C230.044 391.016 232.132 393.728 232.132 398.144C232.132 402.536 230.044 405.368 226.9 405.368ZM245.175 407H249.687C254.991 407 257.895 403.712 257.895 398.144C257.895 392.552 254.991 389.408 249.591 389.408H245.175V407ZM247.407 405.176V391.208H249.399C253.551 391.208 255.591 393.68 255.591 398.144C255.591 402.584 253.551 405.176 249.399 405.176H247.407ZM264.378 399.848L265.242 397.16C265.866 395.168 266.442 393.272 266.994 391.208H267.09C267.666 393.248 268.218 395.168 268.866 397.16L269.706 399.848H264.378ZM271.938 407H274.29L268.314 389.408H265.842L259.866 407H262.122L263.826 401.624H270.258L271.938 407ZM279.791 407H282.023V391.28H287.351V389.408H274.463V391.28H279.791V407ZM292.392 399.848L293.256 397.16C293.88 395.168 294.456 393.272 295.008 391.208H295.104C295.68 393.248 296.232 395.168 296.88 397.16L297.72 399.848H292.392ZM299.952 407H302.304L296.328 389.408H293.856L287.88 407H290.136L291.84 401.624H298.272L299.952 407Z" fill="#C8C3C1"/>
<path d="M250.81 217.559C231.801 217.559 214.171 208.967 202.449 193.982C200.131 191.014 200.647 186.732 203.615 184.414C206.575 182.09 210.864 182.606 213.189 185.58C222.307 197.235 236.024 203.919 250.81 203.919C265.752 203.919 279.555 197.119 288.673 185.26C290.971 182.273 295.247 181.707 298.234 184.005C301.221 186.302 301.787 190.585 299.49 193.565C287.768 208.817 270.021 217.559 250.81 217.559V217.559ZM220.119 162.542C214.464 162.542 209.889 157.966 209.889 152.311V138.671C209.889 133.017 214.464 128.441 220.119 128.441C225.773 128.441 230.349 133.017 230.349 138.671V152.311C230.349 157.959 225.767 162.542 220.119 162.542V162.542ZM281.5 162.542C275.852 162.542 271.27 157.966 271.27 152.311V138.671C271.27 133.017 275.852 128.441 281.5 128.441C287.148 128.441 291.731 133.017 291.731 138.671V152.311C291.731 157.959 287.148 162.542 281.5 162.542V162.542Z" fill="#C8C3C1"/>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

4
src/assets/images/q.svg

@ -0,0 +1,4 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="7" fill="#B60081"/>
<path d="M13.7478 18.1154C16.3098 18.1154 17.9898 15.8054 17.9898 12.0674C17.9898 8.4554 16.3098 6.2294 13.7478 6.2294C11.1858 6.2294 9.52684 8.4554 9.52684 12.0674C9.52684 15.8054 11.1858 18.1154 13.7478 18.1154ZM18.5988 23.8904C15.6168 23.8904 13.5588 22.2524 12.5928 20.0684C9.25384 19.5224 7.00684 16.5404 7.00684 12.0674C7.00684 7.0694 9.77884 4.1084 13.7478 4.1084C17.7378 4.1084 20.4888 7.0904 20.4888 12.0674C20.4888 16.4144 18.3888 19.3334 15.2178 20.0054C15.9108 21.2864 17.3178 21.8744 18.8718 21.8744C19.5438 21.8744 20.0898 21.7694 20.5308 21.6224L20.9928 23.4704C20.4678 23.7014 19.6068 23.8904 18.5988 23.8904Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 798 B

10
src/assets/scss/base.scss

@ -0,0 +1,10 @@
@import url('@/assets/scss/font.scss');
body,
html {
line-height: 1;
user-select: none;
-webkit-tap-highlight-color: transparent;
touch-action: none;
font-family: 'HarmonyOS_Sans_SC_Regular', 'Helvetica Neue', Helvetica, 'PingFang SC', Arial, sans-serif;
}

9
src/assets/scss/font.scss

@ -0,0 +1,9 @@
@font-face {
font-family: 'HarmonyOS_Sans_SC_Regular';
src: url('@/assets/font/HarmonyOS_Sans_SC_Regular.ttf');
}
@font-face {
font-family: 'HarmonyOS_Sans_SC_Bold';
src: url('@/assets/font/HarmonyOS_Sans_SC_Bold.ttf');
}

15
src/assets/scss/index.scss

@ -0,0 +1,15 @@
@import url('./reset.scss');
@import url('./base.scss');
@import url('../iconfont/iconfont.scss');
@tailwind base;
@tailwind components;
@tailwind utilities;
#app {
background: url("../images/bg_v.png") no-repeat center center;
background-size: cover;
@apply h-screen w-screen;
}

148
src/assets/scss/reset.scss

@ -0,0 +1,148 @@
/**
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video,
input {
padding: 0;
margin: 0;
font-size: 100%;
border: 0;
font-weight: normal;
vertical-align: baseline;
touch-action: none;
box-sizing: border-box;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
blockquote,
q {
quotes: none;
}
blockquote::before,
blockquote::after,
q::before,
q::after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* custom */
i {
font-style: normal;
}
a {
text-decoration: none;
}
li {
list-style: none;
}
/* stylelint-disable-next-line no-duplicate-selectors */
body {
text-size-adjust: none;
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
}

53
src/base/AutoBackNotification/AutoBackNotification.vue

@ -0,0 +1,53 @@
<template>
<div class="content fixed left-1/2 top-1/2 z-max -ml-[220px] -mt-[220px] size-[440px] rounded-full bg-black/50 font-bold">
<p class="absolute left-1/2 top-[125px] -translate-x-1/2 whitespace-nowrap text-36 text-white">
{{ title }}
</p>
<p class="absolute left-1/2 top-[180px] flex -translate-x-1/2 items-baseline text-[144px] text-white">
{{ delay }}<i class="text-64">s</i>
</p>
<svg xmlns="http://www.w3.org/2000/svg" class="svg absolute -left-[11px] -top-[9px] size-[440px] -rotate-90">
<circle class="borders relative z-10" fill="transparent" stroke-linecap="round" />
</svg>
</div>
</template>
<script setup lang="ts">
type Props = {
title: string
delay: number
}
withDefaults(defineProps<Props>(), {
title: '',
delay: 0
})
</script>
<style lang="scss" scoped>
.content {
border: 10px solid rgb(255 255 255 / 7%);
}
.borders {
stroke: rgb(197 51 154 / 100%);
transform: translate3d(1px, 1px, 0);
stroke-dasharray: 1535px, 1535px;
stroke-dashoffset: 0;
animation: rotate 5s linear;
cx: 220px;
cy: 220px;
r: 215px;
stroke-width: 10px;
}
@keyframes rotate {
0% {
stroke-dashoffset: 1535px;
}
100% {
stroke-dashoffset: 0;
}
}
</style>

126
src/base/Loading/Loading.vue

@ -0,0 +1,126 @@
<template>
<div class="flex-center">
<span class="loading-animate relative overflow-hidden rounded-full text-inherit" :class="size" :style="style"></span>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { CSSProperties } from 'vue'
type EvenNumbers =
| 12
| 14
| 16
| 18
| 20
| 22
| 24
| 26
| 28
| 30
| 32
| 34
| 36
| 38
| 40
| 42
| 44
| 46
| 48
| 50
| 52
| 54
| 56
| 58
| 60
| 62
| 64
type Props = {
fill?: CSSProperties['color']
size?: `text-${EvenNumbers}`
}
const props = withDefaults(defineProps<Props>(), {
fill: '#515151',
size: 'text-24'
})
const style = computed(() => ({
color: props.fill
}))
</script>
<style>
.loading-animate {
width: 1em;
height: 1em;
text-indent: -9999em;
transform: translateZ(0);
animation:
mlt-shd-spin 1.7s infinite ease,
round 1.7s infinite ease;
}
@keyframes mlt-shd-spin {
0% {
box-shadow:
0 -0.83em 0 -0.4em,
0 -0.83em 0 -0.42em,
0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em,
0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow:
0 -0.83em 0 -0.4em,
0 -0.83em 0 -0.42em,
0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em,
0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow:
0 -0.83em 0 -0.4em,
-0.087em -0.825em 0 -0.42em,
-0.173em -0.812em 0 -0.44em,
-0.256em -0.789em 0 -0.46em,
-0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow:
0 -0.83em 0 -0.4em,
-0.338em -0.758em 0 -0.42em,
-0.555em -0.617em 0 -0.44em,
-0.671em -0.488em 0 -0.46em,
-0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow:
0 -0.83em 0 -0.4em,
-0.377em -0.74em 0 -0.42em,
-0.645em -0.522em 0 -0.44em,
-0.775em -0.297em 0 -0.46em,
-0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow:
0 -0.83em 0 -0.4em,
0 -0.83em 0 -0.42em,
0 -0.83em 0 -0.44em,
0 -0.83em 0 -0.46em,
0 -0.83em 0 -0.477em;
}
}
@keyframes round {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

133
src/base/ScrollView/ScrollView.vue

@ -0,0 +1,133 @@
<template>
<div ref="scroll" class="relative overflow-hidden">
<slot></slot>
</div>
</template>
<script setup lang="ts">
import { shallowRef, onMounted, onBeforeUnmount, watch } from 'vue'
import BScroll from '@better-scroll/core'
import ObserveImage from '@better-scroll/observe-image'
import type { BScrollInstance } from '@better-scroll/core'
BScroll.use(ObserveImage)
type Props = {
list: any[] | string
scrollbar?: boolean
scrollX?: boolean
observeImage?: boolean
refreshDelay?: number
scrollTop?: boolean
stopPropagation?: boolean
pullUp?: boolean
}
type Emits = {
'scroll-end': []
}
const props = withDefaults(defineProps<Props>(), {
scrollbar: false,
scrollX: false,
observeImage: false,
refreshDelay: 20,
scrollTop: true,
stopPropagation: false,
pullUp: false
})
const emits = defineEmits<Emits>()
const scrollInstance = shallowRef<BScrollInstance | null>(null)
const scroll = shallowRef<HTMLDivElement>()
const LIMIT = 50
let timer: any
function _initScroll() {
if (!scroll.value) {
return
}
scrollInstance.value = new BScroll<any>(scroll.value, {
click: true,
disableMouse: false,
disableTouch: false,
scrollX: props.scrollX,
scrollbar: props.scrollbar,
observeImage: props.observeImage,
stopPropagation: props.stopPropagation
})
if (props.pullUp) {
scrollInstance.value.on('scrollEnd', () => {
//@ts-ignore
if (scrollInstance.value?.y <= scrollInstance.value?.maxScrollY + LIMIT) {
emits('scroll-end')
}
})
}
}
function refresh() {
scrollInstance.value?.refresh()
}
function scrollTo(...args: [number, number, number, undefined?, Record<'start' | 'end', Record<string, any>>?]) {
scrollInstance.value?.scrollTo(...args)
}
type Type = number | boolean
function scrollToElement(...args: [HTMLElement | string, number, Type, Type, undefined?]) {
scrollInstance.value?.scrollToElement(...args)
}
//
function stop() {
scrollInstance.value?.stop()
}
//
function enable() {
scrollInstance.value?.enable()
}
//
function disable() {
scrollInstance.value?.disable()
}
function destroy() {
scrollInstance.value?.destroy()
clearTimeout(timer)
scrollInstance.value = null
timer = null
}
onMounted(() => {
_initScroll()
})
onBeforeUnmount(() => {
destroy()
})
defineExpose({
scrollInstance,
refresh,
scrollTo,
scrollToElement,
stop,
enable,
disable,
destroy
})
watch(
() => props.list,
() => {
clearTimeout(timer)
timer = setTimeout(() => {
refresh()
props.scrollTop && scrollTo(0, 0, 0)
clearTimeout(timer)
}, props.refreshDelay)
},
{
deep: true
}
)
</script>

41
src/components/Header/Header.vue

@ -0,0 +1,41 @@
<template>
<div class="flex items-center justify-between px-10 py-7">
<div class="flex-1">
<img :src="logo" class="h-[62px] w-60" alt="" />
</div>
<div class="flex items-center space-x-4">
<img src="../../assets/images/heart.svg" class="size-10" alt="" />
<p class="text-36 font-bold text-black/80">顾客心声</p>
</div>
<div class="flex flex-1 items-center justify-end">
<div class="mr-2">
<i class="iconfont text-28 text-black/40" :class="icon.type"></i>
</div>
<p class="text-28 font-bold text-black/70">{{ parseInt(weather.temperatureLow) }}° - {{ parseInt(weather.temperatureHigh) }}°</p>
<div class="mx-4 h-5 w-px bg-black/20"></div>
<div class="mr-3 text-28 font-bold text-black/70">{{ currentTime }}</div>
<div class="flex flex-col text-12 text-black/70">
<p class="font-700 font-bold">{{ formatDay() }}</p>
<p class="font-700 font-bold">{{ whichWeek }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { formatDay } from '@/utils/utils'
import { useWeather } from '@/composables/useWeather'
import { useTime } from '@/composables/useTime'
import { useDay } from '@/composables/useDay'
import { getLogo } from '@/http/api'
const { weather, icon } = useWeather()
const { currentTime } = useTime()
const { whichWeek } = useDay()
const logo = ref('')
getLogo().then(res => {
logo.value = res.data.map(item => item.content)[0].logo[0]
})
</script>

86
src/components/List/List.vue

@ -0,0 +1,86 @@
<template>
<div class="relative mx-10 flex items-stretch rounded-xl border border-solid border-white bg-[rgba(245,245,245,0.6)]">
<div class="swiper-container relative z-20 h-[1776px] w-[1000px] px-5">
<Swiper
v-if="customerList.length"
rewind
direction="vertical"
round-lengths
observer
center-slides
:slides-per-view="2.998"
watch-overflow
observe-parents
:space-between="16"
:autoplay="{ delay: 30000 }"
observe-slide-children
:modules="modules"
class="h-[1776px] py-5"
@swiper="swiperInit"
>
<SwiperSlide v-for="item of customerList" :key="item.proposalCode">
<ListItem :customer="item" />
</SwiperSlide>
</Swiper>
<div
class="absolute bottom-3 right-3 z-10 flex h-[252px] w-[184px] flex-col items-center justify-start gap-4 rounded-xl border border-solid border-black/5 bg-white bg-gradient-to-b from-[#ededed] to-[#fcfcfc] px-3 pt-3 shadow-[0_4px_10px_0_rgba(0,0,0,0.06)]"
>
<img :src="qr" class="size-40" alt="" />
<div class="text-center text-18 leading-relaxed text-[#333]">欢迎微信扫码留言<br />期待您的宝贵意见</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
import ListItem from '../ListItem/ListItem.vue'
import { Autoplay } from 'swiper/modules'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
const modules = [Autoplay]
type Props = {
qr: string
customerList: Customer['proposalList']
}
defineProps<Props>()
const swiper = shallowRef()
function swiperInit(_swiper: any) {
swiper.value = _swiper
}
function scroll() {
swiper.value?.slideTo(0)
}
defineExpose({
scroll
})
</script>
<style>
.tb {
writing-mode: tb;
}
/* stylelint-disable-next-line selector-class-pattern */
.fadeInRight {
animation: fade-in-right 1s infinite;
}
@keyframes fade-in-right {
from {
opacity: 0;
transform: translate3d(100%, 0, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
</style>

60
src/components/ListItem/ListItem.vue

@ -0,0 +1,60 @@
<template>
<div
class="custom relative grid grid-cols-2 flex-wrap space-x-6 rounded-xl bg-white pl-6 pr-[6px] pt-6 shadow-[0_4px_10px_0_rgba(0,0,0,0.06)]"
>
<div class="flex-1 justify-between">
<div class="mb-2 flex items-center gap-x-2.5 text-justify !text-16 font-normal leading-4.5 text-black/40">
<img src="../../assets/images/q.svg" class="size-7" alt="" />
{{ formatTime(customer.createTime) }}
</div>
<ScrollView class="scroll relative h-[506px]" scrollbar :list="customer.content">
<div class="whitespace-normal pr-3.5 text-justify font-normal leading-normal text-black/50">{{ customer.content }}</div>
</ScrollView>
</div>
<div class="flex-1">
<div class="mb-2 flex items-center gap-x-2.5 text-justify !text-16 font-normal leading-4.5 text-black/40">
<img src="../../assets/images/a.svg" class="size-7" alt="" />
{{ formatTime(customer.replyTime) }}
</div>
<ScrollView class="scroll relative h-[506px]" scrollbar observe-image :list="customer.disposeDes">
<div class="whitespace-normal pr-3.5 text-justify font-normal leading-normal text-[#333333]">
<div v-html="customer.disposeDes"></div>
<div class="flex items-center pt-4 !text-16">
总经理:{{ customer.managerSignature }}
<img class="!size-6" :src="customer.sealUrl" alt="" />
</div>
</div>
</ScrollView>
</div>
</div>
</template>
<script setup lang="ts">
import ScrollView from '@/base/ScrollView/ScrollView.vue'
type Props = {
customer: ProposalList
}
withDefaults(defineProps<Props>(), {
customer: () => ({}) as ProposalList
})
function formatTime(time: string) {
const date = new Date(time)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
return `${year}-${month}-${day} ${hour}:${minute}`
}
</script>
<style>
.custom * {
font-size: 20px !important;
}
.scroll img {
width: 100%;
}
</style>

11
src/composables/useDay.ts

@ -0,0 +1,11 @@
import { computed } from 'vue'
export const useDay = () => {
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] as const
const date = new Date()
const whichWeek = computed(() => days[date.getDay()])
return { whichWeek }
}

93
src/composables/useHandleScreen.ts

@ -0,0 +1,93 @@
import { ref, watch, onUnmounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useRootStore } from '@/stores/root'
export const useHandleScreen = (callback: () => void) => {
const MIN_TIME = 0
const MAX_TIME = 5
const CHECK_TIME = 1000
const DELAY_CHECK_TIME = 400
const title = '即将进入屏幕保护'
const store = useRootStore()
const { nativeMethods, backTime } = storeToRefs(store)
const toWallpaperTime = ref(backTime.value[1]) //回屏保的时间
const isWallpaper = ref(false) //回首页是否已经跑完
const showCountDownDialog = ref(false)
let toWallpaperInterval: any
let delayCheckRoutePathTimer: any
function sleepToWallpaper() {
isWallpaper.value = true
return new Promise<void>(resolve => {
toWallpaperInterval = setInterval(() => {
toWallpaperTime.value--
if (toWallpaperTime.value <= MIN_TIME) {
clearInterval(toWallpaperInterval)
toWallpaperTime.value = backTime.value[1]
isWallpaper.value = false
resolve()
}
}, CHECK_TIME)
})
}
function clearTimers() {
clearInterval(toWallpaperInterval)
clearTimeout(delayCheckRoutePathTimer)
}
//跳转屏保挂载到全局 原生app使用
window.leaveScreenSave = () => {
checkHandleScreen()
}
function checkHandleScreen() {
toWallpaperTime.value = backTime.value[1]
clearTimers()
delayCheckRoutePathTimer = setTimeout(async () => {
try {
//没有屏保
if (backTime.value[1] < 0) {
return
}
await sleepToWallpaper()
callback()
nativeMethods.value?.goScreenSave()
} catch (error) {
clearTimers()
}
}, DELAY_CHECK_TIME)
}
//监听时间 大于等于0且小于等于5时显示弹框
const stopHandler = watch(toWallpaperTime, wallpaperTime => {
showCountDownDialog.value = wallpaperTime >= MIN_TIME && wallpaperTime <= MAX_TIME
})
const stopCheckHandleScreenHandler = watch(
backTime,
() => {
checkHandleScreen()
},
{ immediate: true }
)
onUnmounted(() => {
clearTimers()
stopHandler()
stopCheckHandleScreenHandler()
toWallpaperInterval = null
delayCheckRoutePathTimer = null
})
return {
checkHandleScreen,
showCountDownDialog,
title,
toWallpaperTime
}
}

19
src/composables/useInitBaseData.ts

@ -0,0 +1,19 @@
import { useRootStore } from '@/stores/root'
import { getConfig, getDevice, getWeather, getBackTime } from '@/http/api'
export const useInitBaseData = async () => {
const store = useRootStore()
try {
const _config = await getConfig()
store.SET_CONFIG(_config.data.map(item => item.content)[0])
const [_deviceInfo, _weather, _backTime] = await Promise.all([getDevice(), getWeather(), getBackTime()])
store.SET_DEVICE(_deviceInfo.data)
store.SET_WEATHER(_weather.data)
store.SET_BACK_TIME([_backTime.data[0], _backTime.data[1] ? _backTime.data[1] : -1])
} catch (error) {
console.log('🚀 ~ useInitBaseData ~ error:', error)
alert('数据异常,软件启动失败')
}
}

34
src/composables/useTime.ts

@ -0,0 +1,34 @@
import { computed, onMounted, onUnmounted, shallowRef } from 'vue'
export const useTime = () => {
const date = shallowRef(new Date())
let timer: number | null = null
const formatTime = (date: Date): string => {
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
const currentTime = computed(() => formatTime(date.value))
const updateDate = () => {
date.value = new Date()
}
const startTimer = () => {
timer = setInterval(updateDate, 60000)
}
const stopTimer = () => {
if (timer !== null) {
clearInterval(timer)
timer = null
}
}
onMounted(startTimer)
onUnmounted(stopTimer)
return { currentTime }
}

37
src/composables/useWeather.ts

@ -0,0 +1,37 @@
import { ref, onMounted } from 'vue'
import { getWeather } from '@/http/api'
type IconMap = {
type:
| 'icon-tianqi_06_qingtian'
| 'icon-tianqi_06_duoyun'
| 'icon-tianqi_02_yutian'
| 'icon-tianqi_02_xuetian'
| 'icon-tianqi_02_yintian'
| 'icon-tianqi_02_wutian'
status: string
}
export const useWeather = () => {
const icon = ref({} as IconMap)
const weather = ref({} as Weather)
onMounted(() => {
getWeather().then(({ data }) => {
weather.value = data
})
})
const status: IconMap[] = [
{ type: 'icon-tianqi_06_qingtian', status: '晴' },
{ type: 'icon-tianqi_06_duoyun', status: '多云' },
{ type: 'icon-tianqi_02_yutian', status: '雨' },
{ type: 'icon-tianqi_02_xuetian', status: '雪' },
{ type: 'icon-tianqi_02_yintian', status: '阴' },
{ type: 'icon-tianqi_02_wutian', status: '雾' }
]
icon.value = status.find(item => weather.value.weather?.includes(item.status)) ?? status[0]
return { weather, icon }
}

22
src/enums/index.ts

@ -0,0 +1,22 @@
export enum DEVICE {
WINDOWS = 'windows',
ANDROID = 'android'
}
export enum HTTP_CODE {
ERR_OK = 200, //数据请求成功(可用于语音状态码)
ERR_DATA_NULL = 500, //语音无查询信息
ERR_OVER = 100, //语音播报完毕
ERR_NULL = '401', //未识别到语音
ERR_DISCERNING = 201 //语音识别中
}
export enum PREFIX {
BASE64 = 'data:image/jpg;base64,',
STATIC_URL = './static/offline'
}
export enum FIND_CAR_TYPES {
LICENSE = 0, //车牌
SPACE = 1 //车位
}

25
src/http/api.ts

@ -0,0 +1,25 @@
import { getPrefixUrl, request } from '@/http/http'
import { PREFIX } from '@/enums'
//获取配置项
export const getConfig = () => request<Base<Config>[]>({ url: `${PREFIX.STATIC_URL}/JSON/getConfig.json` })
//获取当前所处楼层
export const getDevice = () => request<Device>({ url: `${PREFIX.STATIC_URL}/JSON/getDevCoordinateByIP.json` })
//获取天气
export const getWeather = () => request<Weather>({ url: `${PREFIX.STATIC_URL}/JSON/getWeather.json` })
// 指定时间返回
export const getBackTime = () => request<[number, number]>({ url: `${PREFIX.STATIC_URL}/JSON/getBackTime.json` })
//获取logo
export const getLogo = () => request<Base<{ logo: string[] }>[]>({ url: `${PREFIX.STATIC_URL}/JSON/getLogo.json` })
//获取二维码
export const getCustomerQr = () => request<Base<{ qrUrl: string[] }>[]>({ url: `${PREFIX.STATIC_URL}/JSON/getCustomerQr.json` })
//获取心声列表
export const getCustomerList = (projectCode: string) => {
return request<Customer>({ url: `${getPrefixUrl().interfaceUrl}/data/v1/web/webProposalList/${projectCode}` })
}

120
src/http/http.ts

@ -0,0 +1,120 @@
import axios from 'axios'
import axiosRetry from 'axios-retry'
import type { AxiosResponse, AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import { addPrefixByRecursive } from '@/utils/utils'
import type { RequestConfig, RequestInterceptors, CreateRequestConfig } from './types'
import { useRootStore } from '@/stores/root'
export default class Request {
// axios 实例
instance: AxiosInstance
// 拦截器对象
interceptorsObj?: RequestInterceptors<AxiosResponse>
// * 存放取消请求控制器Map
abortControllerMap: Map<string, AbortController>
constructor(config: CreateRequestConfig) {
this.instance = axios.create(config)
axiosRetry(this.instance, { retries: 3, retryCondition: () => true, retryDelay: () => 1000 })
//取消请求控制器Map
this.abortControllerMap = new Map()
this.interceptorsObj = config.interceptors
// 拦截器执行顺序 接口请求 -> 实例请求 -> 全局请求 -> 实例响应 -> 全局响应 -> 接口响应
this.instance.interceptors.request.use(
(res: InternalAxiosRequestConfig) => {
const controller = new AbortController()
const url = res.url || ''
res.signal = controller.signal
this.abortControllerMap.set(url, controller)
return res
},
(err: any) => err
)
// 使用实例拦截器
this.instance.interceptors.request.use(this.interceptorsObj?.requestInterceptors, this.interceptorsObj?.requestInterceptorsCatch)
this.instance.interceptors.response.use(this.interceptorsObj?.responseInterceptors, this.interceptorsObj?.responseInterceptorsCatch)
this.instance.interceptors.response.use(
(res: AxiosResponse) => {
const url = res.config.url || ''
this.abortControllerMap.delete(url)
return res.data
},
(err: any) => err
)
}
request<T>(config: RequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
if (config.interceptors?.requestInterceptors) {
config = config.interceptors.requestInterceptors(config as any)
}
this.instance
.request<any, T>(config)
.then(res => {
// 如果为单个响应设置拦截器,使用单个响应的拦截器
if (config.interceptors?.responseInterceptors) {
res = config.interceptors.responseInterceptors(res)
}
resolve(res)
})
.catch((err: any) => {
reject(err)
})
})
}
//取消全部请求
cancelAllRequest() {
for (const [, controller] of this.abortControllerMap) {
controller.abort()
}
this.abortControllerMap.clear()
}
/**
* @description
* @param url URL
*/
cancelRequest(url: string | string[]) {
const urlList = Array.isArray(url) ? url : [url]
for (const _url of urlList) {
this.abortControllerMap.get(_url)?.abort()
this.abortControllerMap.delete(_url)
}
}
}
const _request = new Request({
timeout: 10000,
timeoutErrorMessage: '网络超时',
interceptors: {
requestInterceptors(config) {
if (/\.json$/i.test(config.url as string)) {
config.params = config.params || {}
config.params.t = Date.now()
}
return config
},
responseInterceptors(config) {
const isJson = /.json$/.test(config.config.url as string)
isJson && addPrefixByRecursive(config.data.data)
return config
}
}
})
export type Response<T> = {
msg: string
data: T
code: number
}
export function getPrefixUrl() {
const store = useRootStore()
return store.config
}
export const request = <T = any>(config: RequestConfig<Response<T>>) => _request.request(config)
export const cancelRequest = (url: string | string[]) => _request.cancelRequest(url)
export const cancelAllRequest = () => _request.cancelAllRequest()

19
src/http/types.ts

@ -0,0 +1,19 @@
import type { AxiosResponse, InternalAxiosRequestConfig, CreateAxiosDefaults, AxiosRequestConfig } from 'axios'
export interface RequestInterceptors<T> {
// 请求拦截
requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: (config: T) => T
responseInterceptorsCatch?: (err: any) => any
}
// 自定义传入的参数
export interface CreateRequestConfig<T = AxiosResponse> extends CreateAxiosDefaults {
interceptors?: RequestInterceptors<T>
}
export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: RequestInterceptors<T>
}
export interface CancelRequestSource {
[index: string]: () => void
}

15
src/main.ts

@ -0,0 +1,15 @@
import { createApp } from 'vue'
import App from './App.vue'
import { setupPinia } from './stores'
import { useInitBaseData } from './composables/useInitBaseData'
import '@/assets/scss/index.scss'
async function bootstrap() {
const app = createApp(App)
setupPinia(app)
await useInitBaseData()
app.mount('#app')
}
bootstrap()

7
src/stores/index.ts

@ -0,0 +1,7 @@
import { createPinia } from 'pinia'
import type { App } from 'vue'
export function setupPinia(app: App) {
const pinia = createPinia()
app.use(pinia)
}

26
src/stores/root/actions.ts

@ -0,0 +1,26 @@
import type { State } from './state'
import type { CreateActions, Root } from '../types'
export interface Actions {
SET_BACK_TIME(times: [number, number]): void
SET_WEATHER(weather: Weather): void
SET_DEVICE(device: Device): void
SET_CONFIG(config: Config): void
}
export type GenActions = CreateActions<Root, State, Actions>
export const actions: GenActions = {
SET_BACK_TIME(times) {
this.backTime = times
},
SET_WEATHER(weather) {
this.weather = weather
},
SET_CONFIG(config) {
this.config = config
},
SET_DEVICE(device) {
this.device = device
}
}

18
src/stores/root/getters.ts

@ -0,0 +1,18 @@
import { DEVICE } from '@/enums'
import type { State } from './state'
import type { CreateGetters } from '../types'
export type Getters = {
nativeMethods(): NativeMethods //容器端暴露的方法
}
export type GenGetters = CreateGetters<State, Getters>
export const getters: GenGetters = {
nativeMethods() {
if (this.device.label === DEVICE.ANDROID) {
return window.android
}
return window?.chrome?.webview?.hostObjects?.csobj
}
}

14
src/stores/root/index.ts

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
import { state } from './state'
import { getters } from './getters'
import { actions } from './actions'
import type { Actions } from './actions'
import type { Root } from '../types'
import type { State } from './state'
import type { Getters } from './getters'
export const useRootStore = defineStore<Root, State, Getters, Actions>('root', {
state,
getters,
actions
})

13
src/stores/root/state.ts

@ -0,0 +1,13 @@
export interface State {
backTime: [number, number] // 返回弹框的出现时间 第一位为返回首页 第二位返回屏保
config: Config //配置文件
device: Device //当前设备信息
weather: Weather
}
export const state = (): State => ({
weather: {} as Weather,
backTime: [60, -1],
config: {} as Config,
device: {} as Device
})

11
src/stores/types.ts

@ -0,0 +1,11 @@
import type { UnwrapRef } from 'vue'
import type { PiniaCustomProperties, StateTree, _GettersTree, _StoreWithGetters, _StoreWithState } from 'pinia'
export type Root = 'root'
export type CreateActions<Id extends string, S extends StateTree, A> = A &
ThisType<A & UnwrapRef<S> & _StoreWithState<Id, S, _GettersTree<S>, A> & _StoreWithGetters<_GettersTree<S>> & PiniaCustomProperties>
export type CreateGetters<S extends StateTree, G extends _GettersTree<S>> = G &
ThisType<UnwrapRef<S> & _StoreWithGetters<G> & PiniaCustomProperties> &
_GettersTree<S>

6
src/types/base.d.ts

@ -0,0 +1,6 @@
declare interface Base<T> {
id: number
title: string
entryCode: string
content: T
}

5
src/types/config.d.ts

@ -0,0 +1,5 @@
declare interface Config {
interfaceUrl: string
mobileNav: string
handWriteUrl: string
}

40
src/types/customer.d.ts

@ -0,0 +1,40 @@
declare interface Customer {
/**
*
*/
managerSignature: string
proposalList: ProposalList[]
/**
*
*/
sealUrl: string
}
declare interface ProposalList {
/**
*
*/
content: string
createTime: string
/**
*
*/
customerName: string
/**
*
*/
customerPhone: string
disposeDes: string
pictureListAfter: PictureListAfter[]
pictureListBefore: PictureListBefore[]
proposalCode: string
replyTime: string
/**
*
*/
signature: string
/**
*
*/
sortName: string
[x: string]: any
}

20
src/types/device.d.ts

@ -0,0 +1,20 @@
declare interface Device {
angle: string //角度
building: string //楼栋名称
buildingOrder: number //楼栋order
buildingCode: number //楼栋code
floor: string //楼层名称
floorCode: number //楼层code
floorOrder: number //楼层order
ip: string //设备ip
label: 'windows' | 'android' //设备类型
location: string //设备点位
mac: string //mac 地址
machineCode: string //设备code
machineName: string //设备名称
machineTypeName: string //设备类型
deviceCode: string
projectCode: string //项目code
screenAttribute: string //屏幕属性
deployType: string //部署方式
}

55
src/types/native.d.ts

@ -0,0 +1,55 @@
export declare global {
type VideoStream = {
age: 23
genderMale: '女' | '男'
faceID: string
faceImage: string //人脸图片base64格式 需自行拼接前缀 data:image/jpg;base64,
}
type HardwareInfo = {
width: string
height: string
ip: string
code: string
mac: string
serverIP: string
}
type VoiceContent = {
code: number | string
msg: string | null
data: {
modelType: string
actionType: string
query: string
ttsMsg?: string
word?: string
reply?: string[]
}
}
interface NativeMethods {
startFace(): boolean //通知APP开启摄像头,开始采集
stopFace(): boolean //通知APP结束识别
pushFaceBase(): VideoStream //APP推送视频流给应用
pushFaceAttribute(): VideoStream //APP获取人脸属性推送给应用
takePhoto(): string //应用通知APP拍照 返回Base64
startVoice(): boolean //应用通知APP开始语音识别
stopVoice(): boolean //应用通知APP停止语音识别
voiceContent(): VoiceContent //语音返回数据
deviceInfo(): HardwareInfo //设备信息
goScreenSave(): void // 针对导视应用与app之间屏保跳转进行通讯
hasProgram(): boolean //是否有节目列表 可结合后台管理系统屏保倒计时时间确认前端导视是否弹起屏保弹框
}
interface Window {
leaveScreenSave(): void
android: NativeMethods
chrome: {
webview: {
hostObjects: {
csobj: NativeMethods
}
}
}
}
}

14
src/types/weather.d.ts

@ -0,0 +1,14 @@
declare type Weather = {
area: string //区
temperatureHigh: string //最高气温
city: string //城市
temperatureNow: string //现在温度
windPower: string //风力 几级
temperatureLow: string //最低气温
weather: string //天气状态 多云等
windDirection: string //风向
prov: string //省份
aqi: string
humidity: string
pm: string
}

171
src/utils/utils.ts

@ -0,0 +1,171 @@
import { PREFIX } from '@/enums'
/**
* @description
* @param min
* @param max
*/
export const randomNumber = (min: number, max: number): number => Math.floor(min + Math.random() * (max - min + 1))
/**
* @description
* @param phone
*/
export const isPhoneNumber = (phone: string): boolean => /^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(phone)
/**
* @description
* @param word
*/
export const isUppercaseWord = (word: string): boolean => /^[A-Z]+$/.test(word)
/**
* @description
* @param word
*/
export const isZhWord = (word: string): boolean => {
return /^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/.test(
word
)
}
/**
* @description
* @param number
*/
export const isNumber = (num: string): boolean => {
return /^\d+$/.test(num)
}
/**
* @description
* @param license
*/
export const isLicensePlate = (license: string): boolean => {
return /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/.test(
license
)
}
/**
* @description
* @param len @default [len=7]
*/
export const futureDate = (len = 7) => {
const threeDay = ['今天', '明天', '后天']
//获取系统当前时间
const now = new Date()
const year = now.getFullYear()
const nowTime = now.getTime()
const oneDayTime = 24 * 60 * 60 * 1000
const dateList = []
for (let i = 0; i < len; i++) {
//显示星期
const showTime = nowTime + i * oneDayTime
//初始化日期时间
const myDate = new Date(showTime)
const month = myDate.getMonth() + 1
const date = myDate.getDate()
const str = '周' + '日一二三四五六'.charAt(myDate.getDay())
const _date = `${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`
dateList.push({
week: i < 3 ? threeDay[i] : str,
customDate: _date,
fullDate: `${year}-${_date}`
})
}
return dateList
}
/**
* @description
* @param format @default [format='y-m-d'] format y.m.d y/m/d y-m-d
*/
export const formatDay = (format: 'y.m.d' | 'y/m/d' | 'y-m-d' = 'y-m-d') => {
const date = new Date()
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const week = String(date.getDate()).padStart(2, '0')
if (format === 'y.m.d') {
return `${year}.${month}.${week}`
}
if (format === 'y/m/d') {
return `${year}/${month}/${week}`
}
return `${year}-${month}-${week}`
}
/**
* @description
* @param {Date} startDate -
* @param {Date} endDate -
* @returns {boolean} - truefalse
*/
export const isInDuringDate = (startDate: Date, endDate: Date): boolean => {
const currentDate = new Date()
const start = new Date(startDate)
const end = new Date(endDate)
return currentDate >= start && currentDate <= end
}
/**
* @description
* @param {Date} targetDate -
* @returns {boolean} - truefalse
*/
export const isDateExpired = (targetDate: Date): boolean => {
return new Date() > targetDate
}
/**
* @description
* @param data
* @param prefix @default [prefix='./static/offline']
* @param exp @default [exp=/\.(png|jpg|jpeg|JPG|PNG|webp|MP4|mp4)$/]
*/
export const addPrefixByRecursive = (
data: any[],
prefix = PREFIX.STATIC_URL,
exp = /\.(gif|png|jpg|jpeg|JPG|PNG|webp|MP4|mp4|MP3|mp3)$/
) => {
if (data && typeof data === 'object') {
for (const key in data) {
if (typeof data[key] === 'string' && exp.test(data[key])) {
data[key] = prefix + data[key]
}
if (data[key] && typeof data[key] === 'object') {
addPrefixByRecursive(data[key])
}
}
}
return data
}
/**
* @description 线 '_'
* @param point 线
* @example 1_2_100 buildingOrder_floorOrder_yaxis _楼层序号_导航点
*/
export const splitStringToArray = (point: string) => {
const list = point.split('_')
return list.map(item => Number(item))
}
/**
* @description
* @param text
*/
export const trimAll = (text: string) => text.replace(/\s/g, '')
/**
* @description
* @param path
*/
export const getAssetsImage = (path: string) => {
return new URL(`../assets/images/${path}`, import.meta.url).href
}

184
tailwind.config.ts

@ -0,0 +1,184 @@
import plugin from 'tailwindcss/plugin'
export default {
content: ['./src/**/*.{html,js,vue,scss,css}'],
corePlugins: {
preflight: false
},
theme: {
extend: {
spacing: {
0: '0px',
0.5: '2px',
1: '4px',
1.5: '6px',
2: '8px',
2.5: '10px',
3: '12px',
3.5: '14px',
4: '16px',
4.5: '18px',
5: '20px',
5.5: '22px',
6: '24px',
6.5: '26px',
7: '28px',
7.5: '30px',
8: '32px',
8.5: '34px',
9: '36px',
9.5: '38px',
10: '40px',
10.5: '42px',
11: '44px',
11.5: '46px',
12: '48px',
12.5: '50px',
13: '52px',
13.5: '54px',
14: '56px',
14.5: '58px',
15: '60px',
16: '64px',
18: '72px',
20: '80px',
24: '96px',
26: '100px',
27: '110px',
28: '112px',
30: '120px',
32: '128px',
36: '144px',
40: '160px',
44: '176px',
45: '180px',
46: '184px',
47: '190px',
48: '192px',
49: '200px',
52: '208px',
53: '210px',
54: '214px',
55: '220px',
56: '224px',
60: '240px',
61: '248px',
62: '254px',
64: '256px',
65: '268px',
72: '288px',
80: '320px',
96: '384px',
97: '390px',
98: '400px',
99: '420px',
100: '440px'
},
fontFamily: {
300: ['HarmonyOS_Sans_SC_Regular'],
700: ['HarmonyOS_Sans_SC_Bold']
},
fontSize: {
12: '12px',
14: '14px',
16: '16px',
18: '18px',
20: '20px',
22: '22px',
24: '24px',
26: '26px',
28: '28px',
30: '30px',
32: '32px',
34: '34px',
36: '36px',
38: '38px',
40: '40px',
42: '42px',
44: '44px',
46: '46px',
48: '48px',
50: '50px',
52: '52px',
54: '54px',
56: '56px',
58: '58px',
60: '60px',
64: '64px'
},
borderRadius: {
inherit: 'inherit',
none: '0',
sm: '2px',
DEFAULT: '4px',
md: '6px',
xl: '8px',
lg: '12px',
'2xl': '16px',
'3xl': '24px',
'4xl': '32px',
'5xl': '40px',
full: '50%'
},
lineHeight: {
3: '12px',
4: '16px',
4.5: '18px',
5: '20px',
6: '24px',
7: '28px',
8: '32px',
9: '36px',
10: '40px',
11: '44px',
12: '48px',
12.5: '50px',
13: '52px',
13.5: '54px',
14: '56px'
},
zIndex: {
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
60: '60',
70: '70',
80: '80',
90: '90',
100: '100',
200: '200',
300: '300',
400: '400',
500: '500',
600: '600',
700: '700',
800: '800',
900: '900',
max: '9999'
}
}
},
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
'.flex-center': {
display: 'flex',
'align-items': 'center',
'justify-content': 'center'
},
'.cover': {
width: '100%',
height: '100%',
'object-fit': 'cover'
},
'.contain': {
width: '100%',
height: '100%',
'object-fit': 'contain'
}
})
})
]
}

14
tsconfig.app.json

@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
tsconfig.json

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

34
vite.config.ts

@ -0,0 +1,34 @@
import { fileURLToPath, URL } from 'node:url'
import { extname } from 'node:path'
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue(), splitVendorChunkPlugin()],
assetsInclude: ['/static/qm/three.js', '/static/qm/MainMap_QM.js'],
base: './',
build: {
sourcemap: true,
cssTarget: 'chrome61',
rollupOptions: {
output: {
chunkFileNames: 'js/[name].[hash].js',
entryFileNames: 'js/[name].[hash].js',
assetFileNames: chunkInfo => {
let subDir = 'images'
if (extname(chunkInfo.name as string) === '.css') {
subDir = 'css'
} else if (extname(chunkInfo.name as string) === '.ttf') {
subDir = 'font'
}
return `assets/${subDir}/[name].[hash].[ext]`
}
}
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save