@ -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] |
|||
@ -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 |
|||
@ -0,0 +1,2 @@ |
|||
VITE_SENTRY_DSN_URL: http://936ea86f40634800857a65401ade2112@192.168.1.222:8000/3 |
|||
VITE_SENTRY_ENVIRONMENT: development |
|||
@ -0,0 +1,2 @@ |
|||
VITE_SENTRY_DSN_URL: VITE_SENTRY_DSN_URL: http://936ea86f40634800857a65401ade2112@192.168.1.222:8000/3 |
|||
VITE_SENTRY_ENVIRONMENT: production |
|||
@ -0,0 +1,4 @@ |
|||
node_modules |
|||
dist |
|||
public |
|||
.vscode |
|||
@ -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' |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
* text=auto eol=lf |
|||
@ -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? |
|||
@ -0,0 +1,4 @@ |
|||
#!/usr/bin/env sh |
|||
. "$(dirname -- "$0")/_/husky.sh" |
|||
|
|||
npx --no -- commitlint --edit $1 |
|||
@ -0,0 +1,4 @@ |
|||
#!/bin/sh |
|||
. "$(dirname "$0")/_/husky.sh" |
|||
|
|||
npx lint-staged |
|||
@ -0,0 +1 @@ |
|||
v20.10.0 |
|||
@ -0,0 +1,4 @@ |
|||
node_modules |
|||
dist |
|||
public |
|||
.vscode |
|||
@ -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" |
|||
} |
|||
@ -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)"} |
|||
] |
|||
}] |
|||
] |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
node_modules/* |
|||
|
|||
dist |
|||
@ -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', |
|||
], |
|||
}, |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
{ |
|||
"recommendations": [ |
|||
"vscode.i18n-ally", |
|||
"dbaeumer.vscode-eslint", |
|||
"vscode.stylelint", |
|||
"vscode.Tailwind-css-IntelliSense", |
|||
"esbenp.prettier-vscode" |
|||
] |
|||
} |
|||
@ -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" |
|||
] |
|||
} |
|||
@ -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' |
|||
``` |
|||
|
|||
@ -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: '' |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
|
|||
|
|||
 |
|||
|
|||
项目名称:[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() |
|||
|
After Width: | Height: | Size: 5.2 KiB |
@ -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" |
|||
} |
|||
} |
|||
@ -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 // 横屏时使用的视口宽度 |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
{"code":200,"msg":"操作成功","data":[6000000000,100000000]} |
|||
@ -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" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
{ |
|||
"code": 200, |
|||
"msg": "操作成功", |
|||
"data": [ |
|||
{ |
|||
"id": 1476, |
|||
"entryCode": "ogMDNkoxrQrFIXWP06T6y", |
|||
"title": "顾客心声二维码", |
|||
"content": { |
|||
"qrUrl": [ |
|||
"/iotFile/project-bg9aktmmvxxfvi6vya0dua/20240715/ZR8QBUPdCyn2qZIszWaT2.jpg" |
|||
] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -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" |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
{ |
|||
"code": 200, |
|||
"msg": "操作成功", |
|||
"data": [ |
|||
{ |
|||
"id": 1, |
|||
"entryCode": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
|||
"title": "内容标题", |
|||
"content": { |
|||
"logo": [ |
|||
"logo.png" |
|||
] |
|||
} |
|||
} |
|||
] |
|||
} |
|||
@ -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" |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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'; |
|||
} |
|||
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 798 B |
@ -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; |
|||
} |
|||
@ -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'); |
|||
} |
|||
@ -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; |
|||
} |
|||
|
|||
@ -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%); |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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> |
|||
@ -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 } |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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('数据异常,软件启动失败') |
|||
} |
|||
} |
|||
@ -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 } |
|||
} |
|||
@ -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 } |
|||
} |
|||
@ -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 //车位
|
|||
} |
|||
@ -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}` }) |
|||
} |
|||
@ -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() |
|||
@ -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 |
|||
} |
|||
@ -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() |
|||
@ -0,0 +1,7 @@ |
|||
import { createPinia } from 'pinia' |
|||
import type { App } from 'vue' |
|||
|
|||
export function setupPinia(app: App) { |
|||
const pinia = createPinia() |
|||
app.use(pinia) |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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 |
|||
}) |
|||
@ -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 |
|||
}) |
|||
@ -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> |
|||
@ -0,0 +1,6 @@ |
|||
declare interface Base<T> { |
|||
id: number |
|||
title: string |
|||
entryCode: string |
|||
content: T |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
declare interface Config { |
|||
interfaceUrl: string |
|||
mobileNav: string |
|||
handWriteUrl: string |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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 //部署方式
|
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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} - 如果当前日期在起始和结束日期之间,返回true,否则返回false |
|||
*/ |
|||
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} - 如果给定日期已过期,则返回true,否则返回false |
|||
*/ |
|||
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 |
|||
} |
|||
@ -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' |
|||
} |
|||
}) |
|||
}) |
|||
] |
|||
} |
|||
@ -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/*"] |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
{ |
|||
"files": [], |
|||
"references": [ |
|||
{ |
|||
"path": "./tsconfig.node.json" |
|||
}, |
|||
{ |
|||
"path": "./tsconfig.app.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"] |
|||
} |
|||
} |
|||
@ -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)) |
|||
} |
|||
} |
|||
}) |
|||