Browse Source

feat: 🚀 first commit

master
姜鑫 3 years ago
commit
26b3c78b13
  1. 4
      .browserslistrc
  2. 122
      .drone.yml
  3. 12
      .editorconfig
  4. 5
      .eslintignore
  5. 178
      .eslintrc.js
  6. 24
      .gitignore
  7. 4
      .husky/pre-commit
  8. 4
      .prettierignore
  9. 10
      .prettierrc
  10. 5
      .stylelintignore
  11. 151
      .stylelintrc.js
  12. 19
      .tpl
  13. 548
      README.md
  14. 3
      babel.config.js
  15. 89
      commitlint.config.js
  16. 14462
      package-lock.json
  17. 89
      package.json
  18. 22
      postcss.config.js
  19. BIN
      public/favicon.ico
  20. 17
      public/index.html
  21. 19
      public/static/offline/JSON/GetDevCoordinateByIP.json
  22. 24
      public/static/offline/JSON/GetWeathers.json
  23. 9
      public/static/offline/JSON/getConfig.json
  24. 20
      src/App.vue
  25. BIN
      src/assets/font/SourceHanSansCN-Normal_0.otf
  26. BIN
      src/assets/images/hand.png
  27. BIN
      src/assets/images/heart.png
  28. BIN
      src/assets/images/logo.png
  29. 5
      src/assets/images/nodata.svg
  30. 10
      src/assets/scss/base.scss
  31. 6
      src/assets/scss/font.scss
  32. 6
      src/assets/scss/index.scss
  33. 5
      src/assets/scss/mixin.scss
  34. 147
      src/assets/scss/reset.scss
  35. 52
      src/base/AutoBackNotification/AutoBackNotification.vue
  36. 36
      src/base/Icon/Icon.vue
  37. 8
      src/base/Icon/iconList.ts
  38. 69
      src/base/Loading/Loading.vue
  39. 136
      src/base/ScrollView/ScrollView.vue
  40. 83
      src/components/ScrollList/ScrollList.vue
  41. 48
      src/components/ScrollListItem/ScrollListItem.vue
  42. 33
      src/components/WeatherAndTime/WeatherAndTime.vue
  43. 15
      src/composables/useConfig.ts
  44. 10
      src/composables/useDay.ts
  45. 22
      src/composables/useTime.ts
  46. 21
      src/composables/useWeather.ts
  47. 32
      src/enums/index.ts
  48. 16
      src/http/api/base/index.ts
  49. 104
      src/http/http.ts
  50. 19
      src/http/types.ts
  51. 14
      src/main.ts
  52. 6
      src/shims-vue.d.ts
  53. 17
      src/store/index.ts
  54. 1
      src/store/key.ts
  55. 23
      src/store/root/actions.ts
  56. 11
      src/store/root/index.ts
  57. 11
      src/store/root/state.ts
  58. 9
      src/store/types.ts
  59. 5
      src/types/config.d.ts
  60. 6
      src/types/customer.d.ts
  61. 12
      src/types/device.d.ts
  62. 3
      src/types/icon.d.ts
  63. 20
      src/types/weather.d.ts
  64. 162
      src/utils/utils.ts
  65. 132
      tailwind.config.js
  66. 43
      tsconfig.json
  67. 8
      vue.config.js

4
.browserslistrc

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

122
.drone.yml

@ -0,0 +1,122 @@
kind: pipeline
type: docker
name: deploy
clone:
pull: if-not-exists
trigger:
branch:
- test
- master
event:
- push
# - pull_request
# k8s-node-03
#node_selector:
# drone: TRUE
volumes:
- name: node
host:
path: /data/drone/node_modules_daoshi/
- name: svn
host:
path: /data/drone/svndir/
steps:
- name: 代码构建
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/tgabc-namespace/tgabc:node16-git-tools-20230118
volumes:
- name: node
path: /drone/src/node_modules
settings:
mirror: https://docker.mirrors.ustc.edu.cn
commands:
- npm config set registry https://registry.npm.taobao.org
- npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/
- npm install
- npm run build
- ls dist
- mv CHANGELOG.md dist/
# - mkdir dist
- name: 获取tag数据
image: alpine/git:v2.36.2
commands:
- git fetch --tags
- git describe --tags `git rev-list --tags --max-count=1` > /drone/src/.tags
- cat /drone/src/.tags
- name: svn上传
pull: if-not-exists
image: registry.cn-hangzhou.aliyuncs.com/tgabc-namespace/tgabc:tools-20230116
volumes:
- name: svn
path: /svn
environment:
svn_server: svn://svn.k8s.1000my.com
svn_username: yanfa
svn_password: yanfa
commands:
# zip包
- zip_name=Navigation_h5_V3.0.0_${DRONE_REPO_NAME}_$(cat /drone/src/.tags).zip
- cd /drone/src/dist
- zip -q -r $zip_name ./*
- mv $zip_name ../
# svn目录定义 开头和结尾不能有/,且不能出现中文乱码,复制地址时建议只复制02项目定制后的目录,会影响到变量取值
- svndir="2022研发/Prd007_智能导视/02项目定制/BJ006北京京西大悦城/导视前端"
# - svn_reponame=${svndir%%/*}
# - svn_path=${svndir##*/}
# - echo 仓库内部子路径取值 $svn_path
# - echo 仓库列表名称取值 $svn_reponame
- svn_reponame=$(echo $svndir | awk -F/ '{print $1}')
- svn_path=$(echo $svndir | awk -F/ '{print $NF}')
- echo 仓库列表名称取值 $svn_reponame
- echo 仓库内部子路径取值 $svn_path
# - svn --username ${DRONE_REPO_NAMESPACE} --password ${DRONE_REPO_NAMESPACE} co svn://192.168.0.2/CI
# 更新到最新
- mkdir -p /svn/${DRONE_REPO_NAMESPACE}-${DRONE_REPO_NAME}
- cd /svn/${DRONE_REPO_NAMESPACE}-${DRONE_REPO_NAME}
- svn --username $svn_username --password $svn_password co svn://192.168.0.2/$svndir || true
- cd /svn/${DRONE_REPO_NAMESPACE}-${DRONE_REPO_NAME}/$svn_path
- svn update --non-interactive --username $svn_username --password $svn_password
# svn提交,需要在宿主机的挂载目录手动配置svnauth
- ls /drone/src/$zip_name
- mv /drone/src/$zip_name /svn/${DRONE_REPO_NAMESPACE}-${DRONE_REPO_NAME}/$svn_path/
- ls /svn/${DRONE_REPO_NAMESPACE}-${DRONE_REPO_NAME}/$svn_path/$zip_name
- svn add $zip_name
- svn ci -m "AutoCI" --non-interactive --username $svn_username --password $svn_password
# 创建仓库目录
# - upload_dir="${DRONE_REPO_NAMESPACE}"
# - mkdir -p $svndir/$upload_dir
# - svn add $(ls) || echo "目录已创建"
# 钉钉通知
- name: dingTalk notification
pull: if-not-exists
image: lddsb/drone-dingtalk-message
failure: ignore
settings:
token: 'your dingTalk robot 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: ['15950312150']
success_pic: 'https://raw.githubusercontent.com/Ethan-Liuu/picture/master/success.png'
failure_pic: 'https://raw.githubusercontent.com/Ethan-Liuu/picture/master/failed.png'
tpl: '/drone/src/.tpl'
when:
status: [failure, success]

12
.editorconfig

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

5
.eslintignore

@ -0,0 +1,5 @@
node_modules
dist
public
.vscode
.tpl

178
.eslintrc.js

@ -0,0 +1,178 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2021: true
},
extends: [
'eslint:recommended',
'@vue/typescript/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-recommended',
'plugin:prettier/recommended',
'prettier'
],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaVersion: 2020,
ecmaFeatures: {
jsx: true
}
},
plugins: ['vue', '@typescript-eslint', 'prettier'],
rules: {
'no-undef': '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/no-v-html': 'off',
'vue/no-watch-after-await': 'error',
'vue/attribute-hyphenation': ['error', 'always'],
'vue/multi-word-component-names': 'off',
'@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': 'off',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/ban-types': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'prettier/prettier': 'error',
// 禁止出现console
'no-console': 'off',
// 禁用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: 'warn',
// 禁止 if 语句中 return 语句之后有 else 块
'no-else-return': 'warn',
// 禁止出现空函数
'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: 'off',
// 强制使用一致的缩进
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', 8],
// 强制每一行中所允许的最大语句数量
'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'
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly'
}
}

24
.gitignore

@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.eslintcache
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
.husky/pre-commit

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

4
.prettierignore

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

10
.prettierrc

@ -0,0 +1,10 @@
{
"tabWidth": 2,
"jsxSingleQuote": true,
"jsxBracketSameLine": true,
"printWidth": 140,
"arrowParens": "avoid",
"singleQuote": true,
"semi": false,
"trailingComma": "none"
}

5
.stylelintignore

@ -0,0 +1,5 @@
node_modules
dist
public
.vscode
.tpl

151
.stylelintrc.js

@ -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: {
'string-quotes': 'single',
// 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'no-descending-specificity': null,
'no-duplicate-selectors': null,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: [':deep']
}
],
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep']
}
],
'selector-class-pattern': null,
'selector-id-pattern': null,
'font-family-no-missing-generic-family-keyword': null,
// 禁用每个选择器之前插入空行
'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'
]
}
}

19
.tpl

@ -0,0 +1,19 @@
<font color=[TPL_STATUS_COLOR] size="3">
项目svn地址:http://svn.1000my.com/svn/2022研发/Prd007_智能导视/02项目定制/
</font>
<font color=[TPL_STATUS_COLOR] size="3">
项目git地址:https://git.1000my.com/
</font>
<font color=[TPL_STATUS_COLOR] size="3">
更新内容:[TPL_COMMIT_MSG]
</font>
![image]([TPL_STATUS_PIC])
@15950312150
更新作者:[CI_COMMIT_AUTHOR_NAME]
[ [查看构建详情)]]([TPL_BUILD_LINK])

548
README.md

@ -0,0 +1,548 @@
## 使用package.json内的脚本 'npm run commit' 提交代码
## 使用package.json内的脚本 'npm run commit' 提交代码
## 使用package.json内的脚本 'npm run commit' 提交代码
## 全局状态存储中以 map 为前缀的state状态都是用于数据检索用的 不建议用于页面展示
# 导视开发提测流程(使用npm包管理工具,其他类似)
1. 修改项目中根目录下的`.drone.yml`文件和`.tpl`文件中的**svn地址**配置,如下所示:
```yml
# 在.drone.yml文件的第75行, 修改svndir变量的值为相应项目的svn地址
# svn目录定义 开头和结尾不能有/,且不能出现中文乱码,复制地址时建议只复制02项目定制后的目录,会影响到变量取值
- svndir="2022研发/Prd007_智能导视/02项目定制"
# 以上海浦项写字楼项目为例,如下所示:
- svndir="2022研发/Prd007_智能导视/02项目定制/SH013上海浦项写字楼/导视应用"
```
```tpl
<!-- 在.tpl文件的第2行,修改项目svn地址,最好也不要出现中文乱码,复制地址时只复制02项目定制后的目录 -->
<font color=[TPL_STATUS_COLOR] size="3">
项目svn地址:http://svn.1000my.com/svn/2022研发/Prd007_智能导视/02项目定制/
</font>
<!-- 以上海浦项写字楼项目为例,如下所示: -->
<font color=[TPL_STATUS_COLOR] size="3">
项目svn地址:http://svn.1000my.com/svn/2022研发/Prd007_智能导视/02项目定制/SH013上海浦项写字楼/导视应用/
</font>
```
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
# 钉钉机器人token配置在.drone.yml文件的第112行
# 钉钉通知
- name: dingTalk notification
pull: if-not-exists
image: lddsb/drone-dingtalk-message
failure: ignore
settings:
token: 5f1337cd8cb70e007d2693f70a3ca89fdec543781a9a9fe2f5519e061a1820a8
```
4. 项目首次提测步骤:(由于项目是从脚手架派生出来,log文件里会有脚手架的相关信息,所以第一次须删除相关内容)
* 删除项目根目录下的`CHANGELOG.md`文件。
* 通过`npm run commit`命令提交本地代码。
* 通过`npm run release:first`命令生成首次提测的`CHANGELOG.md`文件,该命令也会把生成的**tag**提交到远程仓库,相当于也执行了`push`命令。
* 在仓库项目地址中把`dev`分支合并到`test`分支,合并分支时选择**变基并合并**
5. 项目后续每次提测步骤:
* 通过`npm run commit`命令提交本地代码。
* 通过`npm run release`命令生成`CHANGELOG.md`文件,该命令也会把生成的**tag**提交到远程仓库,相当于也执行了`push`命令。
* 在仓库项目地址中把`dev`分支合并到`test`分支,合并分支时选择**变基并合并**
6. 每次当测试通过,项目正式发包后:
* 通过`npm run changelog`命令生成发包后的`CHANGELOG.md`文件,该命令也会把生成的**tag**提交到远程仓库,相当于也执行了`push`命令。
###
1. web页面fork **common** 组织下的相应仓库并设置具体见文档
2. `git clone` 到本地
3. 配置 mac和linux下 `chmod 700 .husky/*`
4. 推荐通过npx创建项目 `npx @vue/cli create project_name_`
5. 建议安装vscode的vue3的专属插件volar
6. Vue3常用工具库函数[VueUse](https://vueuse.org/)
7. 开发 及内置组件使用说明见以下文档
8. 所有需要写在App.vue内的组件请放到PublicComponent组件
9. windows平台退出弹框点击区域为左上角200*200的矩形 内部会判断手指点击坐标是否在安全区域内 内嵌Android自行删除相关业务代码
10. 地图SDK地址:https://1000my.com/mapapidoc/index.html
11. **所有跳到导航页面之前请先调用 @/utils/Class/Brand.ts 来生成一个新的shop数据 以更新store内的shop数据 (本身操作是店铺除外)看以下demo**
12. ```javascript
import { useStore } from '@/store/root'
import Brand from '@/utils/Class/Brand'
import { useRouter } from 'vue-router'
export const useFacilityNav = () => {
const store = useStore()
const router = useRouter()
// eslint-disable-next-line max-params
function _handleFacility(name: string, floorOrder: number, floorName: string, logoPath: string, yaxis: number) {
const shop = new Brand({ shopName: name, floorOrder, floor: floorName, logoUrl: logoPath, yaxis })
store.SET_SHOP(shop)
}
function handleFacility(item: Facility) {
const facility = window.Map_QM.pathIcon({ type: item.abbreviation })
const floorName = store.currentBuildingFloorsList.find(floor => floor.floorOrder === facility.floor)?.floor
_handleFacility(item.customFacilityName, facility.floor, floorName as string, item.filePath, facility.node as any)
router.push('/nav')
}
return { handleFacility }
}
```
# 无需引入全局mixin.scss文件 (已提前引入)
```javascript
// vue.config.js
module.exports = defineConfig({
css: {
loaderOptions: {
scss: {
additionalData: `@import "@/assets/scss/mixin.scss";`
}
}
},
lintOnSave: true,
productionSourceMap: false
})
```
### 添加基础旋转动画(scss) 以及列表过渡动画
```scss
// @/assets/scss/mixin.scss
@mixin no-wrap {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
@mixin more-wrap($row: 2) {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $row;
-webkit-box-orient: vertical;
}
@mixin rotate($turn: 360deg, $count: infinite) {
animation-name: rotate;
animation-duration: 2s;
animation-iteration-count: $count;
animation-timing-function: linear;
@keyframes rotate {
0% {
transform: rotate(0) translateZ(0);
}
100% {
transform: rotate($turn) translateZ(0);
}
}
}
// App.vue
.zoom-enter-active {
transition: all 0.3s;
transition-timing-function: cubic-bezier(0.75, 0, 0.24, 1);
}
.zoom-leave-active {
position: absolute !important;
transition: all 0.3s;
transition-timing-function: cubic-bezier(0.75, 0, 0.24, 1);
}
.zoom-enter-from,
.zoom-leave-to {
transform: scale(0.83);
transform-origin: center center;
opacity: 0;
}
.zoom-move {
transition: transform 0.3s !important;
transition-timing-function: cubic-bezier(0.75, 0, 0.24, 1);
}
```
# 组件使用
### AutoBackNotification
| props | type | desc | default |
| :---: | :----: | :----------: | :-----: |
| title | string | 提示文字 | '' |
| delay | number | 倒计时的数字 | 0 |
### Logout
| props | type | desc | default |
| :--------: | :-----: | :--------------: | :-----: |
| modelValue | boolean | 控制组件显示隐藏 | false |
| events | type | desc |
| :----: | :------: | :----------------: |
| bingo | function | 密码输入正确时触发 |
```javascript
<!-- 退出弹框 -->
<Logout v-model="logoutRef" @bingo="bingo" />
import { ref } from 'vue'
const logoutRef = ref(false)
```
### Marquees
| props | type | desc | default |
| :-----: | :----: | :---------------------: | :-----: |
| content | string | 滚动的内容 | '' |
| delay | number | 第一次滚动时的延迟 时间 | 0.8 |
| speed | number | 滚动速度 | 40 |
### Message
使用时引入Message.js
```javascript
import Message from '@/base/Message/Message.js'
Message({type: 'success', text: '这是一条提示'})
```
| props | type | desc | default |
| :---: | :----: | :----------------------------------------------------------: | :-----: |
| type | string | warn \| error\| success(由于UI的不确定性 目前建议只使用success类型) | success |
| text | string | 提示文字 | '' |
### Icon
以下为Icon组件通用props
| props | type | desc | default |
| :---: | :----: | :------: | :-----: |
| size | number | 尺寸大小 | 35 |
| fill | string | 填充颜色 | #515151 |
### ScrollView
| props | type | desc | default |
| :-------------: | :-------------: | :----------------------------------------------------------: | :-----: |
| list | array \| string | 监听的数据源 当数据源改动时会自动调用refresh重新计算滚动高度 | - |
| scrollbar | boolean | 显示滚动条 | false |
| scrollX | boolean | 是否需要横向滚动 | false |
| refreshDelay | number | 延迟初始化实例的时间 | 20 |
| scrollTop | boolean | 当list数据改变时是否自动回到顶部或者左边(scrollX为true时) | true |
| deceleration | number | 表示 momentum 动画的减速度。 (值越小 滑动之后惯性越大) | 0.02 |
| useTransition | boolean | 是否使用 CSS3 transition 动画。如果设置为 false,则使用 requestAnimationFrame 做动画。 | false |
| observeImage | boolean | 是否监听图片(有瀑布流布局的地方建议开启) | false |
| stopPropagation | boolean | 是否阻止事件冒泡。多用在嵌套 scroll 的场景。 | false |
### EffectFade
| props | type | desc | default |
| :--------: | :-----: | :----------------: | :-----: |
| list | array | 内部渲染需要的数据 | [] |
| pagination | boolean | 是否开启提示点 | false |
**此组件只初始化swiper透明轮播等相关逻辑 只做展示 dom节点自行实现 通过作用域插槽可拿到循环的每一项数据 作用域插槽字段: `{ item }`**
```javascript
<script setup>
import {ref} from 'vue'
import EffectFade from '@/components/EffectFade/EffectFade.vue'
</script>
<template>
<EffectFade :list="[1, 2]" v-slot="{ item }">
<div>{{ item }}</div>
</EffectFade>
</template>
```
### Map
| events | type | desc |
| :-----------: | :------: | :----------------------: |
| handle-GO | function | 点击地图弹框导航按钮触发 |
| handle-Detail | function | 点击地图弹框详情按钮触发 |
### 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 | 车牌号或者车位号 | [] |
| needsEnergy | boolean | 是否需要能源输入框即最后一位的输入 如果切换到车位 可以设置为false | true |
| searchMethod | '车牌'\|'车位' | 找车方式 | '车牌' |
| events | type | desc |
| :-----------: | :------: | :----------------------------------------------------------: |
| handle-energy | function | 如needsEnergy为true 则会在初始化自动触发一次此事件 后续需要点击输入框能源车牌输入框 即最后一个输入框 参数为_isEnergy |
| confirm | function | 确认找车 即点击找车按钮触发 |
**车牌输入框组件现在接受一个slot插槽 可以用来点击时显示loading提示或其他内容**
### PlateKeyboard
| props | type | desc | default |
| :-----------: | :----: | :--------------------------: | :-----: |
| searchMethods | string | 找车方式 ['车牌', '车位'] | '车牌' |
| events | type | desc |
| :-------------: | :------: | :------------------------------------------------------: |
| handle-keyboard | function | 点击找车键盘 参数为点击时的文字 如果为del则会触发del事件 |
| del | function | 删除 |
**注:PlateInput PlateKeyboard组件默认在Parking.vue引用 寻车逻辑默认写好 需自行到两个组件内部修改UI对应设计稿**
# composition hooks 使用
### 活动导航
```javascript
//活动类型导航
//类型声明:useActivityNav: () => ({
// nav: (activity: Activity) => void;
//})
const { nav } = useActivityNav() //nav接受参数类型
```
### 导航页面
```javascript
/*
replay:重播
pause:暂停
speedUp:加速
handleReplay:设置重播
togglePause: 设置暂停
handleSpeedUp: 设置加速
resetPause: 暂停初始化
*/
const { replay, pause, speedUp, handleReplay, togglePause, handleSpeedUp, resetPause } = useMapNavControl()
/*
directionInfo: 方向信息
pathShopList: 经过店铺
*/
const { directionInfo, pathShopList, backPathArray } = useStartNavi(shop, currentFloor, resetPause)
/*
methodsList: 路线列表 视图层绑定
methodIdx: 路线选中索引
handleControl:路线选择
selectedWayMethod:此函数大多数情况无需调用 只需调用handleControl即可
*/
const { methodsList, methodIdx, handleControl, selectedWayMethods } = useChangeNavMethod(backPathArray)
/*
cameraViews: 文字及提示icon信息 {text: string, image: string}
setCameraViews: 设置2d 3d
*/
const { cameraViews, setCameraViews } = useSetCameraViews(resetPause)
```
### useQRCode.js (生成二维码)
```javascript
/*
text: 二维码地址
options: qrcode可选项 详见qrcode第三方库
*/
const url = ref('')
const { qr } = useQRCode(url, options)
```
### useFacilityNav.js (公共设施导航)
```javascript
const {handleFacility} = useFacilityNav()
```
### useDay.js (日期)
```javascript
import {useDay} from '@/composables/useDay'
import {formatDay} from '@/utils/utils'
//dateRef:Date实例
//whichWeekRef: 日期 周几
const {dateRef, whichWeekRef} = useDay()
//formatDay(Date, y.m.d | y/m/d | y-m-d):string
const ymd = formatDay(dateRef.value)
```
### useTime.js (时间)
```javascript
import {useTime} from '@/composables/useTime'
const {currentTime} = useTime()
```
### useWeather.js(天气)
```javascript
<p>{{iconRef.status}}<p> //天气状况
<p>{{iconRef.icon}}<p> //天气的iconfont
import {useWeather} from '@/composables/useWeather'
const {weatherRef, iconRef} = useWeather()
```
### useSearchShop.js
```javascript
import {ref} from 'vue'
import {useSearchShopByKeyboard} from '@/composables/useSearchShopByKeyboard'
const searchNameRef = ref('')
const searchType = 0 //0:键盘搜索 1:手写搜索
//筛选之后的店铺列表
const {searchShopListRef} =useSearchShopByKeyboard(searchNameRef,searchType)
```
### useGuideMapOperation.js(地图导览模块用于 复位 我的方向)
```javascript
// type Item = {
// name: String,
// nameEn: String,
// icon: String,
// iconActive: String //选中的图标
// }
//switchFloor:(floorOrder:number) => void 楼层切换 需传入楼层
//handleMapIcon:(item:Item, index:number) => void 绑定视图层
//list:Item[] 我的方向 复位 的列表
const { switchFloor, handleMapIcon, list } = useGuideMapOperation()
```
### useStatistics.js (数据统计)
```javascript
useStatistics(data) //data参数详见ts类型声明
```
### 切换语言
```jsx
<div>{{switchLanguage(state.item, 'name')}}</div>
import {reactive} from 'vue'
import { useSwitchLanguage } from '@/composables/useSwitchLanguage'
const state = reactive({
item: {
name: 'zh',
nameEn: 'en'
}
})
const { switchLanguage } = useSwitchLanguage()
//如果有多个地方需要用到同个内容 推荐computed包裹避免反复计算
const shopName = computed(() => switchLanguage(shop.value, 'shopName'))
```
# 地图初始化
```javascript
import {onMounted} from 'vue'
import {useInitMap,hideMapDialog} from '@/composables/useInitMap'
onMounted(() => {
useInitMap()
})
//该文件暴露除了地图初始化函数 还有一个隐藏地图弹框的函数
hideMapDialog()
```
# 项目内使用的工具函数
```javascript
import {
randomNumber, //两个数字之间的随机数
checkPhoneNumber, // 手机号码验证
isUppercaseWord, //是否是大写
isZhWord, //是否是中文
isNumber, //是否是数字
isLicensePlate //验证输入车牌是否正确
uniqBy //数组内对象去重
futureDate, //未来几天 默认七天
formatDay // 格式化年月日 搭配useDay Hook使用
} from '@/utils/utils'
```

3
babel.config.js

@ -0,0 +1,3 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
}

89
commitlint.config.js

@ -0,0 +1,89 @@
// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
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: ''
}
}

14462
package-lock.json

File diff suppressed because it is too large

89
package.json

@ -0,0 +1,89 @@
{
"name": "vue_cli_ts",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue",
"eslint:fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx,.vue",
"stylelint": "stylelint \"./**/*.{css,scss,sass,vue,html}\"",
"stylelint:fix": "stylelint \"./**/*.{css,scss,sass,vue,html}\" --fix",
"prettier": "prettier --write .",
"prepare": "husky install",
"commit": "git add . && git cz",
"changelog": "standard-version",
"release:first": "standard-version --release-as 1.0.0-B.1",
"release": "standard-version --prerelease B"
},
"dependencies": {
"@better-scroll/core": "^2.5.0",
"@better-scroll/observe-image": "^2.5.0",
"@better-scroll/scroll-bar": "^2.5.0",
"axios": "^1.3.3",
"core-js": "^3.8.3",
"pinia": "^2.0.30",
"vue": "^3.2.13"
},
"devDependencies": {
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@types/lodash-es": "^4.17.8",
"@types/qrcode": "^1.5.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-typescript": "^9.1.0",
"autoprefixer": "^10.4.14",
"commitizen": "^4.3.0",
"cz-git": "^1.4.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.7.1",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"mrm": "^4.1.13",
"pinia-logger": "^1.3.12",
"postcss": "^8.4.21",
"postcss-html": "^1.5.0",
"postcss-px-to-viewport-8-plugin": "^1.2.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.4.1",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"standard-version": "^9.5.0",
"stylelint": "^15.1.0",
"stylelint-config-recommended-scss": "^9.0.0",
"stylelint-config-standard": "^30.0.1",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-order": "^6.0.2",
"stylelint-scss": "^4.4.0",
"tailwindcss": "^3.3.3",
"typescript": "~4.5.5"
},
"config": {
"commitizen": {
"path": "cz-git"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{ts,tsx,vue,js,jsx}": "eslint --cache --fix",
"*.{vue,scss}": "stylelint --fix"
},
"standard-version": {
"scripts": {
"posttag": "git push --follow-tags origin dev"
}
}
}

22
postcss.config.js

@ -0,0 +1,22 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 要转化的单位
viewportWidth: 1920, // 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 // 横屏时使用的视口宽度
}
}
}

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
public/index.html

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

19
public/static/offline/JSON/GetDevCoordinateByIP.json

@ -0,0 +1,19 @@
{
"code": "200",
"msg": "获取成功",
"data": {
"id": 3,
"ip": "192.168.1.19",
"devNum": "ASAP",
"xaxis": "93",
"yaxis": "93",
"angle": "0",
"mallCode": "79b73dae-5d30-4f2f-ad07-d6bc648fb6dfhh",
"buildingCode": "79b73dae-5d30-4f2f-ad07-d6bc648fb66e",
"buildingName": "A",
"buildingOrder": 0,
"floorCode": "ae7bdb92-d542-4bcc-b134-ef7aa4e861f3",
"floorName": "L1",
"floorOrder": 0
}
}

24
public/static/offline/JSON/GetWeathers.json

@ -0,0 +1,24 @@
{
"code": "200",
"msg": "获取成功",
"data": {
"prov": "四川省",
"city": "成都市",
"area": "武侯区",
"status": "阴",
"temperature_High": "23",
"temperature_Low": "18",
"temperature_Now": "20",
"wind_Power": "<3级",
"wind_Direction": "北风转微风",
"humidity": "76%",
"aqi": "13",
"sun_Begin": "06:53",
"sun_End": "18:57",
"id": 2695,
"code": "a400bd1f-9bb1-4ef1-9c28-c1b69e1ef69e",
"updateTime": "2023-09-26T00:00:29.1925744",
"addTime": "2023-06-29T16:00:14.5229765",
"isDel": false
}
}

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

@ -0,0 +1,9 @@
{
"code": "200",
"data": {
"smallUrl": "http://121.199.30.36:8012/daoshi",
"bigUrl": "http://121.199.30.36:8012/mall",
"baseUrl": "/static/offline"
},
"msg": ""
}

20
src/App.vue

@ -0,0 +1,20 @@
<template>
<WeatherAndTime />
<ScrollList />
</template>
<script setup lang="ts">
import WeatherAndTime from '@/components/WeatherAndTime/WeatherAndTime.vue'
import ScrollList from '@/components/ScrollList/ScrollList.vue'
</script>
<style>
html,
body,
#app {
width: 1920px;
height: 1080px;
}
#app {
background: #000;
}
</style>

BIN
src/assets/font/SourceHanSansCN-Normal_0.otf

Binary file not shown.

BIN
src/assets/images/hand.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/assets/images/heart.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

BIN
src/assets/images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

5
src/assets/images/nodata.svg

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

After

Width:  |  Height:  |  Size: 7.3 KiB

10
src/assets/scss/base.scss

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

6
src/assets/scss/font.scss

@ -0,0 +1,6 @@
@font-face {
font-family: 'font_light';
src: url('@/assets/font/SourceHanSansCN-Normal_0.otf');
}

6
src/assets/scss/index.scss

@ -0,0 +1,6 @@
@import url('./reset.scss');
@import url('./base.scss');
@tailwind base;
@tailwind components;
@tailwind utilities;

5
src/assets/scss/mixin.scss

@ -0,0 +1,5 @@
@mixin no-wrap {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

147
src/assets/scss/reset.scss

@ -0,0 +1,147 @@
/**
* 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;
}
body {
text-size-adjust: none;
-webkit-tap-highlight-color: rgb(0 0 0 / 0%);
}

52
src/base/AutoBackNotification/AutoBackNotification.vue

@ -0,0 +1,52 @@
<template>
<div class="content bg-black/50 h-[440px] -ml-[220px] w-[440px] -mt-[220px] fixed z-[9999] top-1/2 left-1/2 rounded-full font-bolder">
<p class="absolute top-[125px] left-1/2 text-36 text-white whitespace-nowrap -translate-x-1/2">{{ title }}</p>
<p class="flex items-baseline absolute top-[180px] text-[144px] left-1/2 -translate-x-1/2 text-white">
{{ delay }}<i class="text-[64px]">s</i>
</p>
<svg xmlns="http://www.w3.org/2000/svg" class="absolute -top-[9px] -rotate-90 -left-[11px] svg w-[440px] h-[440px]">
<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 {
animation-duration: 0.2s;
border: 10px solid rgb(255 255 255 / 7%);
}
.borders {
stroke: #dcb889;
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>

36
src/base/Icon/Icon.vue

@ -0,0 +1,36 @@
<template>
<svg
:class="`qm_icon qm_icon-${props.type}`"
width="100%"
height="100%"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
v-html="type"
></svg>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { iconList } from './iconList'
interface Props {
type: IconType
color?: string
}
const props = withDefaults(defineProps<Props>(), {
type: '' as IconType,
color: 'rgba(0, 0, 0, 1)'
})
const type = computed(() => iconList[props.type])
</script>
<style lang="scss" scoped>
.qm_icon {
:deep(path) {
fill: v-bind('props.color');
}
}
</style>

8
src/base/Icon/iconList.ts

@ -0,0 +1,8 @@
export const iconList: Record<IconType, string> = {
sunny: `<path d="M11.9673 3.89071C11.4751 3.89071 11.0767 3.49227 11.0767 3.00009V2.04852C11.0767 1.55634 11.4751 1.1579 11.9673 1.1579C12.4595 1.1579 12.8579 1.55634 12.8579 2.04852V3.00009C12.8579 3.49227 12.4595 3.89071 11.9673 3.89071ZM5.59697 6.52977C5.36963 6.52977 5.13994 6.44305 4.9665 6.26962L4.18369 5.4868C3.83682 5.13993 3.83682 4.57509 4.18369 4.22821C4.53057 3.88134 5.09541 3.88134 5.44229 4.22821L6.2251 5.01102C6.57197 5.3579 6.57197 5.92274 6.2251 6.26962C6.054 6.44305 5.82432 6.52977 5.59697 6.52977ZM2.95791 12.9001H2.00635C1.51416 12.9001 1.11572 12.5016 1.11572 12.0095C1.11572 11.5173 1.51416 11.1188 2.00635 11.1188H2.95791C3.4501 11.1188 3.84854 11.5173 3.84854 12.0095C3.84854 12.5016 3.4501 12.9001 2.95791 12.9001ZM4.81416 20.0532C4.58682 20.0532 4.35947 19.9665 4.18369 19.7931C3.83682 19.4462 3.83682 18.8813 4.18369 18.5345L4.9665 17.7516C5.31338 17.4048 5.87822 17.4048 6.2251 17.7516C6.57197 18.0985 6.57197 18.6634 6.2251 19.0102L5.44229 19.7931C5.27119 19.9665 5.0415 20.0532 4.81416 20.0532ZM11.9673 22.861C11.4751 22.861 11.0767 22.4626 11.0767 21.9704V21.0188C11.0767 20.5266 11.4751 20.1282 11.9673 20.1282C12.4595 20.1282 12.8579 20.5266 12.8579 21.0188V21.9704C12.8579 22.4626 12.4595 22.861 11.9673 22.861ZM19.1204 20.0532C18.8931 20.0532 18.6634 19.9665 18.4899 19.7931L17.7071 19.0102C17.3603 18.6634 17.3603 18.0985 17.7071 17.7516C18.054 17.4048 18.6188 17.4048 18.9657 17.7516L19.7485 18.5345C20.0954 18.8813 20.0954 19.4462 19.7485 19.7931C19.5774 19.9665 19.3478 20.0532 19.1204 20.0532ZM21.9282 12.9001H20.9767C20.4845 12.9001 20.086 12.5016 20.086 12.0095C20.086 11.5173 20.4845 11.1188 20.9767 11.1188H21.9282C22.4204 11.1188 22.8188 11.5173 22.8188 12.0095C22.8188 12.5016 22.4204 12.9001 21.9282 12.9001ZM18.3376 6.52977C18.1103 6.52977 17.8829 6.44305 17.7071 6.26962C17.3603 5.92274 17.3603 5.3579 17.7071 5.01102L18.4899 4.22821C18.8368 3.88134 19.4017 3.88134 19.7485 4.22821C20.0954 4.57509 20.0954 5.13993 19.7485 5.4868L18.9657 6.26962C18.7946 6.44305 18.5649 6.52977 18.3376 6.52977ZM12.1923 19.1602C10.3407 19.1602 8.60166 18.4407 7.29385 17.1306C5.98604 15.8227 5.26416 14.0813 5.26416 12.2321C5.26416 10.3806 5.98369 8.64149 7.29385 7.33368C8.60166 6.02587 10.3431 5.30399 12.1923 5.30399C14.0438 5.30399 15.7829 6.02352 17.0907 7.33368C18.3985 8.64149 19.1204 10.3829 19.1204 12.2321C19.1204 14.0837 18.4009 15.8227 17.0907 17.1306C15.7829 18.4384 14.0438 19.1602 12.1923 19.1602ZM12.1923 7.08524C9.354 7.08524 7.04541 9.39384 7.04541 12.2321C7.04541 15.0704 9.354 17.379 12.1923 17.379C15.0306 17.379 17.3392 15.0704 17.3392 12.2321C17.3392 9.39384 15.0306 7.08524 12.1923 7.08524Z"/>`,
cloudy: `<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8416 7.62665C15.2787 7.62665 14.7235 7.6975 14.1871 7.83567C13.21 6.3797 11.592 5.51483 9.83438 5.51483C8.43516 5.51483 7.12266 6.06093 6.13125 7.04999C4.99585 8.18271 4.44449 9.80129 4.63452 11.3893C4.51227 11.4917 4.39392 11.6001 4.27983 11.7141C3.26499 12.729 2.70483 14.079 2.70483 15.5157C2.70483 16.6243 3.04233 17.6907 3.67749 18.5954C4.63007 19.95 6.26413 20.8923 7.93843 20.8923H16.008C17.8534 20.8923 19.6958 19.9527 20.883 18.5673C21.9119 17.3696 22.4767 15.8391 22.4767 14.2595C22.4767 12.4876 21.7876 10.8235 20.5337 9.56962C19.2798 8.31571 17.6134 7.62665 15.8416 7.62665ZM6.39419 10.4084C6.56678 8.66287 8.04443 7.29374 9.83438 7.29374C10.8708 7.29374 11.8368 7.75199 12.4903 8.53712C11.6112 9.05399 10.8571 9.77241 10.3009 10.6196C9.08178 10.0636 7.6661 9.98925 6.39419 10.4084ZM8.08374 11.9181C6.10093 11.9181 4.48843 13.5306 4.48843 15.5134C4.48843 17.3247 6.07802 19.111 7.93843 19.111H16.008C18.5808 19.111 20.6978 16.7755 20.6978 14.2595C20.6978 11.5829 18.5205 9.40555 15.8439 9.40555C13.8455 9.40555 12.1859 10.6365 11.3601 12.4049C11.499 12.8833 11.2438 13.3991 10.7532 13.5376C10.2844 13.6616 9.78951 13.3927 9.65397 12.9212C9.55423 12.5639 9.33053 12.2549 9.0283 12.0441C8.72277 11.9607 8.40595 11.9181 8.08374 11.9181Z"/><path d="M9.36328 4.78827C8.87109 4.78827 8.47266 4.38983 8.47266 3.89764V3.32343C8.47266 2.83124 8.87109 2.4328 9.36328 2.4328C9.85547 2.4328 10.2539 2.83124 10.2539 3.32343V3.89764C10.2539 4.38749 9.85312 4.78827 9.36328 4.78827Z"/><path d="M5.14219 6.27889C4.91484 6.27889 4.68516 6.19218 4.51172 6.01874L4.10625 5.61327C3.75937 5.26639 3.75937 4.70155 4.10625 4.35468C4.45312 4.0078 5.01797 4.0078 5.36484 4.35468L5.77031 4.76014C6.11719 5.10702 6.11719 5.67186 5.77031 6.01874C5.59687 6.19218 5.36953 6.27889 5.14219 6.27889Z"/>path d="M2.96484 10.4766H2.39062C1.89844 10.4766 1.5 10.0781 1.5 9.58593C1.5 9.09374 1.89844 8.6953 2.39062 8.6953H2.96484C3.45703 8.6953 3.85547 9.09374 3.85547 9.58593C3.85547 10.0781 3.45703 10.4766 2.96484 10.4766Z"/><path d="M13.8492 6.04452C13.6219 6.04452 13.3922 5.9578 13.2188 5.78436C12.8719 5.43749 12.8719 4.87264 13.2188 4.52577L13.6242 4.1203C13.9711 3.77343 14.5359 3.77343 14.8828 4.1203C15.2297 4.46718 15.2297 5.03202 14.8828 5.37889L14.4773 5.78436C14.3039 5.9578 14.0766 6.04452 13.8492 6.04452Z"/>`,
shade: `<path fill-rule="evenodd" clip-rule="evenodd" d="M8.09727 4.05C8.87305 3.71953 9.69805 3.55078 10.5465 3.55078C11.3973 3.55078 12.2199 3.71953 12.9863 4.05C13.734 4.36875 14.4066 4.82578 14.9809 5.40703C15.5551 5.98828 16.0074 6.66328 16.3215 7.41562C16.441 7.70391 16.5395 7.99687 16.6168 8.29688C17.3902 8.30859 18.1402 8.47031 18.8504 8.77266C19.5629 9.07734 20.2027 9.51328 20.7512 10.0664C21.2996 10.6172 21.7285 11.2617 22.0285 11.9789C22.3402 12.7219 22.4973 13.507 22.4973 14.318C22.4973 15.8203 21.9465 17.257 20.9434 18.368C19.9473 19.4719 18.5879 20.1609 17.1184 20.3086C16.9285 20.3297 16.741 20.3391 16.5605 20.3391H6.78477C6.07461 20.3391 5.3832 20.1961 4.73164 19.9172C4.10586 19.65 3.5457 19.2656 3.06289 18.7781C2.58242 18.293 2.20508 17.7258 1.94023 17.0977C1.66367 16.4438 1.52539 15.7523 1.52539 15.0422C1.52539 13.7437 1.99883 12.4945 2.86133 11.5219C3.27383 11.0578 3.75898 10.6734 4.29102 10.3828C4.27695 10.2117 4.26992 10.0406 4.26992 9.86953C4.26992 9.02109 4.43633 8.19375 4.76211 7.41562C5.07852 6.66328 5.52852 5.98828 6.10273 5.40703C6.6793 4.82578 7.34961 4.36875 8.09727 4.05ZM14.8717 8.6382C14.3399 6.73014 12.6053 5.33203 10.5465 5.33203C8.0668 5.33203 6.05586 7.36406 6.05352 9.87422C6.05352 10.4695 6.1707 11.0367 6.37695 11.557C4.6543 11.7656 3.30898 13.2539 3.30898 15.0469C3.30898 16.9805 4.87461 18.5625 6.78945 18.5625H16.5652C16.6918 18.5625 16.816 18.5555 16.9379 18.5414C19.0613 18.3305 20.7207 16.5211 20.7207 14.3203C20.7207 12.0487 18.955 10.1955 16.7359 10.0835C16.7076 10.0862 16.6789 10.0876 16.6498 10.0876C15.8951 10.0876 15.1592 10.3618 14.5826 10.8587C14.4139 11.0016 14.2076 11.072 14.0014 11.072C13.7506 11.072 13.5022 10.9665 13.3264 10.7626C13.0053 10.3899 13.0475 9.82743 13.4201 9.50634C13.8542 9.13327 14.3462 8.84091 14.8717 8.6382Z"/>`,
rain: `<path d="M16.6004 7.15321C15.885 4.44359 13.315 2.4704 10.5043 2.4704C6.88522 2.4704 3.96698 5.64962 4.22539 9.21571C2.54302 10.1199 1.44336 11.9158 1.44336 13.8352C1.44336 16.3415 3.33358 18.5771 5.81445 19.0079L6.70977 17.304C4.79023 17.297 3.22227 15.7384 3.22227 13.8352C3.22227 12.0657 4.57695 10.5985 6.30664 10.3923C5.15212 7.51908 7.36574 4.25165 10.502 4.25165C12.5727 4.25165 14.3192 5.63235 14.8543 7.51463C14.3526 7.71284 13.8826 7.99173 13.4646 8.34366C13.0896 8.66007 13.0427 9.22257 13.3591 9.59757C13.6749 9.97227 14.2376 10.0226 14.613 9.70538C15.2442 9.17554 15.963 8.99589 16.7648 8.9416C18.9809 9.06916 20.7371 10.89 20.7371 13.1204C20.7371 15.4746 18.7442 17.3063 16.441 17.3063L15.5035 19.0876C19.1027 19.0876 22.5207 17.0708 22.5207 13.1204C22.5114 9.92481 19.8255 7.19207 16.6004 7.15321Z"/><path d="M11.981 21.5648C11.8404 21.5648 11.6974 21.532 11.5638 21.4593C11.1302 21.2273 10.9662 20.6882 11.1958 20.2546L11.6529 19.3968C11.8849 18.9632 12.424 18.7992 12.8576 19.0289C13.2912 19.2609 13.4552 19.8 13.2255 20.2336L12.7685 21.0914C12.6068 21.3937 12.2998 21.5648 11.981 21.5648Z"/><path d="M13.9263 18.1593C13.7857 18.1593 13.6427 18.1265 13.5091 18.0539C13.0755 17.8218 12.9115 17.2828 13.1412 16.8492L13.5982 15.9914C13.8302 15.5578 14.3693 15.3937 14.8029 15.6234C15.2365 15.8554 15.4005 16.3945 15.1708 16.8281L14.7138 17.6859C14.5521 17.9882 14.2451 18.1593 13.9263 18.1593Z"/><path d="M7.63335 21.5648C7.49272 21.5648 7.34975 21.532 7.21616 21.4593C6.78256 21.2273 6.6185 20.6882 6.84819 20.2546L7.30522 19.3968C7.53725 18.9632 8.07631 18.7992 8.50991 19.0289C8.9435 19.2609 9.10756 19.8 8.87788 20.2336L8.42085 21.0914C8.25913 21.3937 7.9521 21.5648 7.63335 21.5648Z"/><path d="M9.57866 18.1593C9.43803 18.1593 9.29506 18.1265 9.16147 18.0539C8.72788 17.8218 8.56381 17.2828 8.7935 16.8492L9.25053 15.9914C9.48256 15.5578 10.0216 15.3937 10.4552 15.6234C10.8888 15.8554 11.0529 16.3945 10.8232 16.8281L10.3662 17.6859C10.2044 17.9882 9.89741 18.1593 9.57866 18.1593Z"/>`,
snow: `<path d="M22.3598 12.8202C22.3598 17.7901 17.6261 18.7124 13.6129 18.7124L14.5504 16.9312C17.307 16.9312 20.5691 16.3893 20.5691 12.8226C20.5691 10.6073 18.7163 8.73749 16.5072 8.73749C15.7619 8.73749 15.0353 9.00233 14.4658 9.4828C14.0904 9.80002 13.5276 9.74969 13.2119 9.37499C12.8955 8.99999 12.9424 8.43749 13.3174 8.12108C13.7547 7.75276 14.2504 7.46527 14.7798 7.26804C14.2377 5.44703 12.5386 4.11788 10.5262 4.11788C7.45229 4.11788 5.27032 7.32822 6.41055 10.146C4.71133 10.3476 3.38242 11.789 3.38242 13.5257C3.38242 16.4484 6.07098 16.9312 8.34883 16.9312L7.41133 18.7124C4.39938 18.7124 1.60352 16.6744 1.60352 13.5257C1.60352 11.6387 2.67843 9.86819 4.3293 8.97179C4.05658 5.42653 7.03555 2.33429 10.5332 2.33429C13.2946 2.33429 15.8369 4.26873 16.5473 6.93273C19.6839 6.98113 22.3598 9.64965 22.3598 12.8202Z"/><path d="M9.98511 20.613L9.54438 21.0531C9.43478 21.1627 9.25898 21.1592 9.14829 21.0531C9.03813 20.943 9.03813 20.7648 9.14829 20.6547L9.63813 20.1648H8.96802C8.81333 20.1648 8.68677 20.0383 8.68677 19.8836C8.68677 19.7289 8.81333 19.6023 8.96802 19.6023H9.53735L9.14829 19.2133C9.03813 19.1031 9.03813 18.925 9.14829 18.8148C9.25845 18.7047 9.43657 18.7047 9.54673 18.8148L9.98511 19.2532V18.5828C9.98511 18.4281 10.1117 18.3016 10.2664 18.3016C10.421 18.3016 10.5476 18.4281 10.5476 18.5828V19.2554L10.9881 18.8148C11.0983 18.7047 11.2764 18.7047 11.3866 18.8148C11.4967 18.925 11.4967 19.1031 11.3866 19.2133L10.997 19.6023H11.5696C11.7243 19.6023 11.8508 19.7289 11.8508 19.8836C11.8485 20.0406 11.7243 20.1648 11.5696 20.1648H10.8967L11.3866 20.6547C11.4967 20.7648 11.4967 20.943 11.3866 21.0531C11.2741 21.1609 11.1006 21.1609 10.9881 21.0531L10.5476 20.6126V21.1844C10.5476 21.3414 10.4234 21.468 10.2664 21.468C10.1117 21.468 9.98511 21.3414 9.98511 21.1867V20.613Z"/><path d="M11.8245 16.845L11.3838 17.2852C11.2742 17.3948 11.0984 17.3912 10.9877 17.2852C10.8775 17.175 10.8775 16.9969 10.9877 16.8867L11.4775 16.3969H10.8074C10.6527 16.3969 10.5262 16.2703 10.5262 16.1156C10.5262 15.9609 10.6527 15.8344 10.8074 15.8344H11.3768L10.9877 15.4453C10.8775 15.3352 10.8775 15.157 10.9877 15.0469C11.0979 14.9367 11.276 14.9367 11.3861 15.0469L11.8245 15.4853V14.8149C11.8245 14.6602 11.9511 14.5336 12.1058 14.5336C12.2604 14.5336 12.387 14.6602 12.387 14.8149V15.4874L12.8275 15.0469C12.9377 14.9367 13.1158 14.9367 13.226 15.0469C13.3361 15.157 13.3361 15.3352 13.226 15.4453L12.8364 15.8344H13.409C13.5637 15.8344 13.6902 15.9609 13.6902 16.1156C13.6879 16.2727 13.5637 16.3969 13.409 16.3969H12.7361L13.226 16.8867C13.3361 16.9969 13.3361 17.175 13.226 17.2852C13.1135 17.393 12.94 17.393 12.8275 17.2852L12.387 16.8446V17.4164C12.387 17.5734 12.2628 17.7 12.1058 17.7C11.9511 17.7 11.8245 17.5734 11.8245 17.4188V16.845Z"/>`,
fog: `<path d="M20.7164 9.1125C21.2648 9.65625 21.6961 10.2914 21.9984 10.9992C22.3125 11.7328 22.4719 12.5133 22.4672 13.3195C22.4672 14.8078 21.9117 16.2281 20.9039 17.325C20.168 18.1266 19.2352 18.7031 18.2062 19.0078L18.232 17.1023C19.6805 16.4484 20.6883 15 20.6883 13.3195C20.6883 11.1655 19.0337 9.39351 16.9172 9.18465C16.8857 9.18803 16.8537 9.18976 16.8212 9.18976C16.0642 9.18976 15.3283 9.45695 14.7517 9.94445C14.5876 10.0827 14.3837 10.153 14.1798 10.153C13.9267 10.153 13.6736 10.0452 13.4978 9.83663C13.1814 9.46163 13.2283 8.89913 13.6033 8.58273C13.9844 8.26127 14.41 8.0011 14.8636 7.80812C14.3505 5.91313 12.6048 4.51641 10.5305 4.51641C8.05312 4.51641 6.04453 6.50625 6.04453 8.9625C6.04453 9.54375 6.15938 10.0992 6.36563 10.6102C4.64766 10.8141 3.30234 12.2719 3.30234 14.0273C3.30234 15.4055 4.13203 16.6031 5.31797 17.1516L5.29453 19.0406C5.10469 18.9844 4.91953 18.9188 4.73672 18.8414C4.11328 18.5789 3.55312 18.2039 3.07031 17.7258C2.5875 17.2453 2.20781 16.6875 1.94063 16.0664C1.66406 15.4195 1.52344 14.7328 1.52344 14.0273C1.52344 12.7406 2.00156 11.5031 2.87109 10.5422C3.27891 10.0898 3.75703 9.71719 4.28203 9.43359C4.27031 9.27656 4.26328 9.11953 4.26328 8.96016C4.26328 8.11875 4.42969 7.30078 4.75781 6.52969C5.07422 5.78672 5.52656 5.12109 6.10312 4.54922C6.67969 3.97969 7.35 3.52969 8.09531 3.21797C8.86875 2.89453 9.68672 2.73047 10.5305 2.73047C11.3742 2.73047 12.1922 2.89453 12.9656 3.21797C13.7133 3.53203 14.3836 3.97969 14.9578 4.54922C15.5344 5.12109 15.9867 5.78672 16.3031 6.52969C16.4227 6.80625 16.5187 7.09219 16.5961 7.38047C17.3672 7.39219 18.1148 7.54922 18.8203 7.84453C19.5305 8.14219 20.168 8.56875 20.7164 9.1125Z"/><path d="M17.3111 14.6484H7.96421V16.4296H17.3111V14.6484Z"/><path d="M16.0361 17.0624H6.68921V18.8437H16.0361V17.0624Z"/><path d="M16.8845 19.4765H7.53765V21.2577H16.8845V19.4765Z"/>`
}

69
src/base/Loading/Loading.vue

@ -0,0 +1,69 @@
<template>
<div class="w-28 h-28 flex-center overflow-hidden relative">
<span class="loading-animate rounded-full" :style="style"></span>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
size: {
type: Number,
default: 45
},
fill: {
type: String,
default: '#515151'
}
})
const style = computed(() => ({
color: props.fill,
fontSize: props.size + 'px'
}))
</script>
<style lang="scss" scoped>
.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>

136
src/base/ScrollView/ScrollView.vue

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

83
src/components/ScrollList/ScrollList.vue

@ -0,0 +1,83 @@
<template>
<div class="relative rounded-[10px] bg-[#E9E9E9] mx-10 h-[936px]">
<ScrollView pull-up class="relative w-[1840px]" :list="[]" scroll-x @scroll-end="scrollEnd">
<div class="inline-grid grid-rows-[repeat(2,430px)] grid-flow-col gap-4 px-6 whitespace-nowrap pt-[30px] pb-3">
<ScrollListItem v-for="item of customerList" :key="item.addTime" />
<div class="flex-center row-span-2" :class="[isFirst ? 'col-end-[56]' : '']">
<Loading v-if="!loaded" />
</div>
</div>
<img
v-if="!customerList.length && loaded"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
src="../../assets/images/nodata.svg"
alt=""
/>
<div class="absolute left-1/2 -translate-x-1/2 bottom-5 flex flex-col items-center">
<img src="../../assets/images/hand.png" class="fadeInRight mb-[6px]" alt="" />
<p class="text-14 text-[#666]">左右滑动查看更多</p>
</div>
</ScrollView>
</div>
</template>
<script setup lang="ts">
import { ref, toRefs, onMounted } from 'vue'
import { useRootStore } from '@/store/root'
import { getCustomerList } from '@/http/api/base'
import { HTTP_CODE } from '@/enums'
import ScrollView from '@/base/ScrollView/ScrollView.vue'
import ScrollListItem from '@/components/ScrollListItem/ScrollListItem.vue'
import Loading from '@/base/Loading/Loading.vue'
const store = useRootStore()
const { config, device } = toRefs(store)
const customerList = ref<Customer[]>([])
const pageIndex = ref(1)
const loaded = ref(false)
const isFirst = ref(true)
function scrollEnd() {
console.log('scrollEnd')
}
onMounted(_getCustomerList)
function _getCustomerList() {
loaded.value = false
const params = {
pageIndex: pageIndex.value,
pageSize: 10,
mallCode: device.value.mallCode
}
getCustomerList(config.value.smallUrl, params)
.then(({ code, data }) => {
if (code === HTTP_CODE.ERR_OK) {
customerList.value.push(...data.list)
}
})
.finally(() => {
// isFirst.value = false
loaded.value = true
})
}
</script>
<style>
.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>

48
src/components/ScrollListItem/ScrollListItem.vue

@ -0,0 +1,48 @@
<template>
<div class="flex flex-wrap pt-6 pl-6 pr-[6px] space-x-8 relative w-[720px] rounded-xl bg-white shadow-[3px_3px_0_0_rgba(0,0,0,0.10)]">
<div class="flex-1 justify-between">
<div class="text-justify text-[#808080] text-12 font-normal leading-[18px] mb-2">{{ customer.addTime }}</div>
<ScrollView class="relative h-[356px]" scrollbar :list="[]">
<div class="text-justify text-[#C70082] text-16 font-normal leading-normal whitespace-normal pr-[14px]">
Q:{{ customer.suggestionContent }}
</div>
</ScrollView>
</div>
<div class="flex-1">
<div class="text-justify text-[#808080] text-12 font-normal leading-[18px] mb-2">{{ customer.updateTime }}</div>
<ScrollView class="relative h-[356px]" scrollbar observe-image :list="[]">
<div class="text-justify text-[##333333] text-16 font-normal leading-normal whitespace-normal pr-[14px]">
A:
<div v-html="customer.replyContent"></div>
</div>
</ScrollView>
</div>
</div>
</template>
<script setup lang="ts">
import ScrollView from '@/base/ScrollView/ScrollView.vue'
type Props = {
customer: Customer
}
withDefault(defineProps<Props>(), {
customer: {} as Customer
})
</script>
<style>
.bscroll-vertical-scrollbar {
width: 4px !important;
height: 120px !important;
background-color: rgb(0 0 0 / 3%) !important;
opacity: 1 !important;
}
.bscroll-indicator {
width: 4px !important;
height: 60px !important;
background-color: rgb(0 0 0 / 6%) !important;
border: none !important;
}
</style>

33
src/components/WeatherAndTime/WeatherAndTime.vue

@ -0,0 +1,33 @@
<template>
<div class="flex justify-between items-center py-7 px-10">
<img src="../../assets/images/logo.png" class="w-16 h-16" alt="" />
<div class="flex items-center space-x-4">
<img src="../../assets/images/heart.png" class="w-10 h-10" alt="" />
<p class="text-36 text-white font-bold">顾客心声</p>
</div>
<div class="flex items-center">
<div class="w-9 h-9 mr-2">
<Icon :type="icon.type" color="#fff" />
</div>
<p class="text-white text-40 font-bold">{{ weather.temperature_Now }}°</p>
<div class="w-px h-6 mx-5 bg-[#808080]"></div>
<div class="text-40 font-bold text-white mr-3">{{ currentTime }}</div>
<div class="flex flex-col text-white text-16">
<p>{{ formatDay('y/m/d') }}</p>
<p>{{ whichWeek }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { formatDay } from '@/utils/utils'
import { useWeather } from '@/composables/useWeather'
import { useTime } from '@/composables/useTime'
import { useDay } from '@/composables/useDay'
import Icon from '@/base/Icon/Icon.vue'
const { weather, icon } = useWeather()
const { currentTime } = useTime()
const { whichWeek } = useDay()
</script>

15
src/composables/useConfig.ts

@ -0,0 +1,15 @@
import { getWeather, getConfig, getDevice } from '@/http/api/base'
import { useRootStore } from '@/store/root'
export const useConfig = async () => {
const store = useRootStore()
try {
const [_weather, _config, _device] = await Promise.all([getWeather(), getConfig(), getDevice()])
store.SET_WEATHER(_weather.data)
store.SET_CONFIG(_config.data)
store.SET_DEVICE(_device.data)
Promise.resolve()
} catch (error) {
console.log('error: ', error)
}
}

10
src/composables/useDay.ts

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

22
src/composables/useTime.ts

@ -0,0 +1,22 @@
import { computed, onMounted, onBeforeUnmount, shallowRef } from 'vue'
export const useTime = () => {
const date = shallowRef(new Date())
const timer = shallowRef()
const currentTime = computed(() => {
return `${date.value.getHours().toString().padStart(2, '0')}:${date.value.getMinutes().toString().padStart(2, '0')}`
})
const getDate = () => {
timer.value = setInterval(() => {
date.value = new Date()
}, 60000)
}
onMounted(getDate)
onBeforeUnmount(() => clearInterval(timer.value))
return {
currentTime
}
}

21
src/composables/useWeather.ts

@ -0,0 +1,21 @@
import { ref, toRefs } from 'vue'
import { useRootStore } from '@/store/root'
type IconMap = { type: ExtractIcons<'sunny' | 'cloudy' | 'rain' | 'snow' | 'shade'>; status: string }
export const useWeather = () => {
const store = useRootStore()
const { weather } = toRefs(store)
const icon = ref({} as IconMap)
const status: IconMap[] = [
{ type: 'sunny', status: '晴' },
{ type: 'cloudy', status: '云' },
{ type: 'rain', status: '雨' },
{ type: 'snow', status: '雪' },
{ type: 'shade', status: '阴' }
]
icon.value = status.find(item => weather.value.status?.includes(item.status)) ?? status[0]
return { weather, icon }
}

32
src/enums/index.ts

@ -0,0 +1,32 @@
export enum ACTIVITY_TYPE {
// 1商场活动;2品牌活动;3会员活动
MALL = 1,
BRAND = 2,
MEMBER = 3
}
export enum DEVICE {
WINDOWS = 'windows',
ANDROID = 'android'
}
//卡片推荐类型
export enum FEATURED {
//1好吃的,2好玩的,3值得买
FOOD = 1,
PLAY = 2,
BUY = 3
}
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'
}

16
src/http/api/base/index.ts

@ -0,0 +1,16 @@
import { request } from '@/http/http'
import { PREFIX } from '@/enums'
//获取配置项
export const getConfig = () => request<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/GetWeathers.json` })
//获取心声列表
export const getCustomerList = (url: string, params: { pageIndex: number; pageSize: number; mallCode: string }) => {
return request<{ allPage: number; allCount: number; list: Customer[] }>({ url: `${url}/Api/Suggestion/Page`, params })
}

104
src/http/http.ts

@ -0,0 +1,104 @@
import axios, { type AxiosResponse, AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import { addPrefixByRecursive } from '@/utils/utils'
import type { RequestConfig, RequestInterceptors, CreateRequestConfig } from './types'
export default class Request {
// axios 实例
instance: AxiosInstance
// 拦截器对象
interceptorsObj?: RequestInterceptors<AxiosResponse>
// * 存放取消请求控制器Map
abortControllerMap: Map<string, AbortController>
constructor(config: CreateRequestConfig) {
this.instance = axios.create(config)
//取消请求控制器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()
}
/**
*
* @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: {
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 const request = <T = any>(config: RequestConfig<Response<T>>) => _request.request(config)
export const cancelRequest = (url: string | string[]) => _request.cancelRequest(url)
export const cancelAllRequest = () => _request.cancelAllRequest()

19
src/http/types.ts

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

14
src/main.ts

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

6
src/shims-vue.d.ts

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

17
src/store/index.ts

@ -0,0 +1,17 @@
import { createPinia } from 'pinia'
import { PiniaLogger } from 'pinia-logger'
import type { App } from 'vue'
export function setupPinia(app: App) {
const pinia = createPinia()
app.use(pinia)
pinia.use(
PiniaLogger({
disabled: process.env.NODE_ENV === 'production',
expanded: false,
showDuration: true,
showStoreName: true,
logErrors: true
})
)
}

1
src/store/key.ts

@ -0,0 +1 @@
export type Root = 'root'

23
src/store/root/actions.ts

@ -0,0 +1,23 @@
import type { State } from './state'
import type { Root } from '../key'
import type { CreateActions } from '../types'
export interface Actions {
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_WEATHER(weather) {
this.weather = weather
},
SET_CONFIG(config) {
this.config = config
},
SET_DEVICE(device) {
this.device = device
}
}

11
src/store/root/index.ts

@ -0,0 +1,11 @@
import { defineStore } from 'pinia'
import { state } from './state'
import { actions } from './actions'
import type { Actions } from './actions'
import type { Root } from '../key'
import type { State } from './state'
export const useRootStore = defineStore<Root, State, Record<any, any>, Actions>('root', {
state,
actions
})

11
src/store/root/state.ts

@ -0,0 +1,11 @@
export interface State {
weather: Weather //天气
config: Config //配置文件
device: Device //当前设备信息
}
export const state = (): State => ({
weather: {} as Weather,
config: {} as Config,
device: {} as Device
})

9
src/store/types.ts

@ -0,0 +1,9 @@
import type { UnwrapRef } from 'vue'
import type { PiniaCustomProperties, StateTree, _GettersTree, _StoreWithGetters, _StoreWithState } from 'pinia'
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>

5
src/types/config.d.ts

@ -0,0 +1,5 @@
declare interface Config {
smallUrl: string
bigUrl: string
baseUrl: string
}

6
src/types/customer.d.ts

@ -0,0 +1,6 @@
interface Customer {
suggestionContent: string
replyContent: string
addTime: string
updateTime: string
}

12
src/types/device.d.ts

@ -0,0 +1,12 @@
interface Device {
id: number
ip: string
devNum: string
xaxis: string
yaxis: string
angle: string
floorCode: string
floorName: string
order: number
mallCode: string
}

3
src/types/icon.d.ts

@ -0,0 +1,3 @@
declare type IconType = 'sunny' | 'cloudy' | 'shade' | 'rain' | 'snow' | 'fog'
declare type ExtractIcons<U extends IconType> = Extract<IconType, U>

20
src/types/weather.d.ts

@ -0,0 +1,20 @@
interface Weather {
prov: string
city: string
area: string
status: string
temperature_High: string
temperature_Low: string
temperature_Now: string
wind_Power: string
wind_Direction: string
humidity: string
aqi: string
sun_Begin: string
sun_End: string
id: number
code: string
updateTime: string
addTime: string
isDel: boolean
}

162
src/utils/utils.ts

@ -0,0 +1,162 @@
import { PREFIX } from '@/enums'
/**
* @description
* @param max
* @param min
*/
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 str
*/
export const isUppercaseWord = (str: string): boolean => /^[A-Z]+$/.test(str)
/**
* @description
* @param str
*/
export const isZhWord = (str: 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(
str
)
}
/**
* @description
* @param str
*/
export const isNumber = (str: string): boolean => {
return /^\d+$/.test(str)
}
/**
* @description
* @param str
*/
export const isLicensePlate = (str: string): boolean => {
return /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/.test(
str
)
}
/**
* @description
* @param array
* @param key key来去重
*/
export const uniqBy = <T extends object, K extends keyof T>(array: T[], key: K): T[] => {
const obj: any = {}
const arraySet = array.reduce<T[]>((item, next) => {
obj[next[key]] ? '' : (obj[next[key]] = true && item.push(next))
return item
}, [])
return arraySet
}
/**
* @description
* @param.default [len=7] len
*/
export const futureDate = (len = 7) => {
const threeDay = ['今天', '明天', '后天']
//获取系统当前时间
const now = new Date()
const nowTime = now.getTime()
const oneDayTime = 24 * 60 * 60 * 1000
const timeArr = []
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')}`
timeArr.push({
week: i < 3 ? threeDay[i] : str,
customDate: _date
})
}
return timeArr
}
/**
* @description
* @param.default [format='y-m-d'] format y.m.d y/m/d y-m-d
* @returns
*/
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 startDate
* @param endDate
*/
export const isInDuringDate = (startDate: string, endDate: string) => {
const currentDate = new Date().toLocaleString()
const start = new Date(startDate).toLocaleString()
const end = new Date(endDate).toLocaleString()
return currentDate >= start && currentDate <= end
}
/**
* @description
* @param data
* @param.default [prefix=PREFIX.STATIC_URL]
* @param.default [exp=/\.(png|jpg|jpeg|JPG|PNG|webp|MP4|mp4)$/]
*/
export const addPrefixByRecursive = (data: any[], prefix = PREFIX.STATIC_URL, exp = /\.(png|jpg|jpeg|JPG|PNG|webp|MP4|mp4)$/) => {
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, '')

132
tailwind.config.js

@ -0,0 +1,132 @@
const plugin = require('tailwindcss/plugin')
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js,vue,scss,css}'],
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',
5: '20px',
6: '24px',
7: '28px',
8: '32px',
9: '36px',
10: '40px',
11: '44px',
12: '48px',
14: '56px',
16: '64px',
20: '80px',
24: '96px',
28: '112px',
32: '128px',
36: '144px',
40: '160px',
44: '176px',
48: '192px',
52: '208px',
56: '224px',
60: '240px',
64: '256px',
72: '288px',
80: '320px',
96: '384px'
},
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'
},
borderRadius: {
none: '0',
sm: '2px',
DEFAULT: '4px',
md: '6px',
xl: '8px',
lg: '12px',
'2xl': '16px',
'3xl': '24px',
full: '50%'
},
lineHeight: {
3: '12px',
4: '16px',
5: '20px',
6: '24px',
7: '28px',
8: '32px',
9: '36px',
10: '40px'
},
zIndex: {
100: '100',
200: '200',
300: '300',
400: '400',
500: '500',
600: '600',
700: '700',
800: '800',
900: '900',
max: '9999'
}
}
},
plugins: [
plugin(function ({ addComponents }) {
addComponents({
'.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'
},
'.scale-down': {
width: '100%',
height: '100%',
'object-fit': 'scale-down'
}
})
})
]
}

43
tsconfig.json

@ -0,0 +1,43 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"suppressImplicitAnyIndexErrors": true,
"esModuleInterop": true,
"allowJs": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": "./",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

8
vue.config.js

@ -0,0 +1,8 @@
const { defineConfig } = require('@vue/cli-service')
const prod = process.env.NODE_ENV === 'production'
module.exports = defineConfig({
transpileDependencies: true,
publicPath: prod ? './' : '/'
})
Loading…
Cancel
Save