feat: init
This commit is contained in:
commit
a8ee21b558
|
|
@ -0,0 +1,106 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
|
||||
const scopes = fs
|
||||
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name.replace(/s$/, ''))
|
||||
|
||||
// precomputed scope
|
||||
const scopeComplete = execSync('git status --porcelain || true')
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.find((r) => ~r.indexOf('M src'))
|
||||
?.replace(/(\/)/g, '%%')
|
||||
?.match(/src%%((\w|-)*)/)?.[1]
|
||||
?.replace(/s$/, '')
|
||||
|
||||
module.exports = {
|
||||
ignores: [(commit) => commit.includes('init')],
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'body-leading-blank': [2, 'always'],
|
||||
'footer-leading-blank': [1, 'always'],
|
||||
'header-max-length': [2, 'always', 108],
|
||||
'subject-empty': [2, 'never'],
|
||||
'type-empty': [2, 'never'],
|
||||
'subject-case': [0],
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat',
|
||||
'fix',
|
||||
'perf',
|
||||
'style',
|
||||
'docs',
|
||||
'test',
|
||||
'refactor',
|
||||
'build',
|
||||
'ci',
|
||||
'chore',
|
||||
'revert',
|
||||
'wip',
|
||||
'workflow',
|
||||
'types',
|
||||
'release',
|
||||
],
|
||||
],
|
||||
},
|
||||
prompt: {
|
||||
/** @use `pnpm commit :f` */
|
||||
alias: {
|
||||
f: 'docs: fix typos',
|
||||
r: 'docs: update README',
|
||||
s: 'style: update code format',
|
||||
b: 'build: bump dependencies',
|
||||
c: 'chore: update config',
|
||||
},
|
||||
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
|
||||
defaultScope: scopeComplete,
|
||||
scopes: [...scopes, 'mock'],
|
||||
allowEmptyIssuePrefixs: false,
|
||||
allowCustomIssuePrefixs: false,
|
||||
|
||||
// English
|
||||
typesAppend: [
|
||||
{ value: 'wip', name: 'wip: work in process' },
|
||||
{ value: 'workflow', name: 'workflow: workflow improvements' },
|
||||
{ value: 'types', name: 'types: type definition file changes' },
|
||||
],
|
||||
|
||||
// 中英文对照版
|
||||
// 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: 新增功能' },
|
||||
// { value: 'fix', name: 'fix: 修复缺陷' },
|
||||
// { value: 'docs', name: 'docs: 文档变更' },
|
||||
// { value: 'style', name: 'style: 代码格式' },
|
||||
// { value: 'refactor', name: 'refactor: 代码重构' },
|
||||
// { value: 'perf', name: 'perf: 性能优化' },
|
||||
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
|
||||
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
|
||||
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
|
||||
// { value: 'revert', name: 'revert: 回滚 commit' },
|
||||
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
|
||||
// { value: 'wip', name: 'wip: 正在开发中' },
|
||||
// { value: 'workflow', name: 'workflow: 工作流程改进' },
|
||||
// { value: 'types', name: 'types: 类型定义文件修改' },
|
||||
// ],
|
||||
// emptyScopesAlias: 'empty: 不填写',
|
||||
// customScopesAlias: 'custom: 自定义',
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*] # 表示所有文件适用
|
||||
charset = utf-8 # 设置文件字符集为 utf-8
|
||||
indent_style = space # 缩进风格(tab | space)
|
||||
indent_size = 2 # 缩进大小
|
||||
end_of_line = lf # 控制换行类型(lf | cr | crlf)
|
||||
trim_trailing_whitespace = true # 去除行首的任意空白字符
|
||||
insert_final_newline = true # 始终在文件末尾插入一个新行
|
||||
|
||||
[*.md] # 表示仅 md 文件适用以下规则
|
||||
max_line_length = off # 关闭最大行长度限制
|
||||
trim_trailing_whitespace = false # 关闭末尾空格修剪
|
||||
|
|
@ -0,0 +1 @@
|
|||
src/uni_modules/
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"effectScope": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onAddToFavorites": true,
|
||||
"onBackPress": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onError": true,
|
||||
"onErrorCaptured": true,
|
||||
"onHide": true,
|
||||
"onLaunch": true,
|
||||
"onLoad": true,
|
||||
"onMounted": true,
|
||||
"onNavigationBarButtonTap": true,
|
||||
"onNavigationBarSearchInputChanged": true,
|
||||
"onNavigationBarSearchInputClicked": true,
|
||||
"onNavigationBarSearchInputConfirmed": true,
|
||||
"onNavigationBarSearchInputFocusChanged": true,
|
||||
"onPageNotFound": true,
|
||||
"onPageScroll": true,
|
||||
"onPullDownRefresh": true,
|
||||
"onReachBottom": true,
|
||||
"onReady": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onResize": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onShareAppMessage": true,
|
||||
"onShareTimeline": true,
|
||||
"onShow": true,
|
||||
"onTabItemTap": true,
|
||||
"onThemeChange": true,
|
||||
"onUnhandledRejection": true,
|
||||
"onUnload": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useRequest": true,
|
||||
"useSlots": true,
|
||||
"useUpload": true,
|
||||
"useUpload2": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true,
|
||||
"DirectiveBinding": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"onWatcherCleanup": true,
|
||||
"useId": true,
|
||||
"useModel": true,
|
||||
"useTemplateRef": true,
|
||||
"tabbarList": true,
|
||||
"iphoneBottom": true,
|
||||
"useNavbarWeixin": true,
|
||||
"cities": true,
|
||||
"getCities": true,
|
||||
"useCityInfo": true,
|
||||
"useRules": true,
|
||||
"rules": true,
|
||||
"optionalSubjectList": true,
|
||||
"requireSubjectList": true,
|
||||
"optionalSubject": true,
|
||||
"requireSubject": true,
|
||||
"unSortTypeList": true,
|
||||
"useUnSortType": true,
|
||||
"useUniversityType": true,
|
||||
"useRegionInfo": true,
|
||||
"useUniversityLevel": true,
|
||||
"useNatureList": true,
|
||||
"useUniversityRank": true,
|
||||
"useCityNewTop": true,
|
||||
"useCityNewDetail": true,
|
||||
"newDetail": true,
|
||||
"newsDetail": true,
|
||||
"useNewsList": true,
|
||||
"newsList": true,
|
||||
"useLogin": true,
|
||||
"useWxInfo": true,
|
||||
"useUniversityInfo": true,
|
||||
"universityBaseInfo": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vue/vue3-essential',
|
||||
// eslint-plugin-import 插件, @see https://www.npmjs.com/package/eslint-plugin-import
|
||||
'plugin:import/recommended',
|
||||
// eslint-config-airbnb-base 插件 已经改用 eslint-config-standard 插件
|
||||
'standard',
|
||||
// 1. 接入 prettier 的规则
|
||||
'prettier',
|
||||
'plugin:prettier/recommended',
|
||||
'./.eslintrc-auto-import.json',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: ['.eslintrc.{js,cjs}'],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'vue',
|
||||
// 2. 加入 prettier 的 eslint 插件
|
||||
'prettier',
|
||||
// eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
|
||||
'import',
|
||||
],
|
||||
rules: {
|
||||
// 3. 注意要加上这一句,开启 prettier 自动修复的功能
|
||||
'prettier/prettier': 'error',
|
||||
// turn on errors for missing imports
|
||||
'import/no-unresolved': 'off',
|
||||
// 对后缀的检测,否则 import 一个ts文件也会报错,需要手动添加'.ts', 增加了下面的配置后就不用了
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{ js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
|
||||
],
|
||||
// 只允许1个默认导出,关闭,否则不能随意export xxx
|
||||
'import/prefer-default-export': ['off'],
|
||||
'no-console': ['off'],
|
||||
// 'no-unused-vars': ['off'],
|
||||
// '@typescript-eslint/no-unused-vars': ['off'],
|
||||
// 解决vite.config.ts报错问题
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-shadow': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
// 避免 `eslint` 对于 `typescript` 函数重载的误报
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
},
|
||||
// eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {},
|
||||
},
|
||||
},
|
||||
globals: {
|
||||
$t: true,
|
||||
uni: true,
|
||||
UniApp: true,
|
||||
wx: true,
|
||||
WechatMiniprogram: true,
|
||||
getCurrentPages: true,
|
||||
UniHelper: true,
|
||||
Page: true,
|
||||
App: true,
|
||||
NodeJS: true,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
name: Auto Merge Base to Other Branches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- base
|
||||
workflow_dispatch: # 手动触发
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_TOKEN_AUTO_MERGE }}
|
||||
|
||||
- name: Merge base into main
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git checkout main
|
||||
git merge base --no-ff -m "Auto merge base into main"
|
||||
git push origin main
|
||||
|
||||
- name: Merge base into i18n
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git checkout i18n
|
||||
git merge base --no-ff -m "Auto merge base into i18n"
|
||||
git push origin i18n
|
||||
|
||||
- name: Merge base into tabbar
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git checkout tabbar
|
||||
git merge base --no-ff -m "Auto merge base into tabbar"
|
||||
git push origin tabbar
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.hbuilderx
|
||||
|
||||
.stylelintcache
|
||||
.eslintcache
|
||||
|
||||
docs/.vitepress/dist
|
||||
docs/.vitepress/cache
|
||||
|
||||
# lock 文件还是不要了,我主要的版本写死就好了
|
||||
# pnpm-lock.yaml
|
||||
# package-lock.json
|
||||
|
||||
# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
|
||||
# `git rm -r --cached .` 然后提交 commit 即可。
|
||||
|
||||
# git rm -r --cached file1 file2 ## 针对某些文件
|
||||
# git rm -r --cached dir1 dir2 ## 针对某些文件夹
|
||||
# git rm -r --cached . ## 针对所有文件
|
||||
|
||||
# 更新 uni-app 官方版本
|
||||
# npx @dcloudio/uvm@latest
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run the commit-msg hook
|
||||
npx --no-install commitlint --edit
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run the pre-commit hook
|
||||
npx --no-install -- lint-staged
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# registry = https://registry.npmjs.org
|
||||
registry = https://registry.npmmirror.com
|
||||
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
shamefully-hoist=true
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
|
||||
auto-import.d.ts
|
||||
|
||||
# vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
|
||||
uni-pages.d.ts
|
||||
|
||||
# 插件生成的文件
|
||||
src/pages.json
|
||||
src/manifest.json
|
||||
|
||||
# 忽略自动生成文件
|
||||
src/service/app/**
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// @see https://prettier.io/docs/en/options
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: false,
|
||||
trailingComma: 'all',
|
||||
endOfLine: 'auto',
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
overrides: [
|
||||
{
|
||||
files: '*.json',
|
||||
options: {
|
||||
trailingComma: 'none',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
src/uni_modules/
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// .stylelintrc.cjs
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
// stylelint-config-standard 替换成了更宽松的 stylelint-config-recommended
|
||||
'stylelint-config-recommended',
|
||||
// stylelint-config-standard-scss 替换成了更宽松的 stylelint-config-recommended-scss
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-recommended-vue/scss',
|
||||
'stylelint-config-html/vue',
|
||||
'stylelint-config-recess-order',
|
||||
],
|
||||
plugins: ['stylelint-prettier'],
|
||||
overrides: [
|
||||
// 扫描 .vue/html 文件中的<style>标签内的样式
|
||||
{
|
||||
files: ['**/*.{vue,html}'],
|
||||
customSyntax: 'postcss-html',
|
||||
},
|
||||
{
|
||||
files: ['**/*.{css,scss}'],
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
'prettier/prettier': true,
|
||||
// 允许 global 、export 、v-deep等伪类
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
|
||||
},
|
||||
],
|
||||
'unit-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreUnits: ['rpx'],
|
||||
},
|
||||
],
|
||||
// 处理小程序page标签不认识的问题
|
||||
'selector-type-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreTypes: ['page'],
|
||||
},
|
||||
],
|
||||
'comment-empty-line-before': 'never', // never|always|always-multi-line|never-multi-line
|
||||
'custom-property-empty-line-before': 'never',
|
||||
'no-empty-source': null,
|
||||
'comment-no-empty': null,
|
||||
'no-duplicate-selectors': null,
|
||||
'scss/comment-no-empty': null,
|
||||
'selector-class-pattern': null,
|
||||
'font-family-no-missing-generic-family-keyword': null,
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"stylelint.vscode-stylelint",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.unocss",
|
||||
"antfu.iconify",
|
||||
"evils.uniapp-vscode",
|
||||
"uni-helper.uni-helper-vscode",
|
||||
"uni-helper.uni-app-schemas-vscode",
|
||||
"uni-helper.uni-highlight-vscode",
|
||||
"uni-helper.uni-ui-snippets-vscode",
|
||||
"uni-helper.uni-app-snippets-vscode",
|
||||
"mrmlnc.vscode-json5",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
// 默认格式化工具选择prettier
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
// 保存的时候自动格式化
|
||||
"editor.formatOnSave": true,
|
||||
//开启自动修复
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.stylelint": "explicit"
|
||||
},
|
||||
// 配置stylelint检查的文件类型范围
|
||||
"stylelint.validate": ["css", "scss", "vue", "html"], // 与package.json的scripts对应
|
||||
"stylelint.enable": true,
|
||||
"css.validate": false,
|
||||
"less.validate": false,
|
||||
"scss.validate": false,
|
||||
"[shellscript]": {
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
"[dotenv]": {
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
// 配置语言的文件关联
|
||||
"files.associations": {
|
||||
"pages.json": "jsonc", // pages.json 可以写注释
|
||||
"manifest.json": "jsonc" // manifest.json 可以写注释
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Aplipay",
|
||||
"climblee",
|
||||
"commitlint",
|
||||
"dcloudio",
|
||||
"iconfont",
|
||||
"qrcode",
|
||||
"refresherrefresh",
|
||||
"scrolltolower",
|
||||
"tabbar",
|
||||
"Toutiao",
|
||||
"unibest",
|
||||
"uvui",
|
||||
"Wechat",
|
||||
"WechatMiniprogram",
|
||||
"Weixin"
|
||||
],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
|
||||
".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*,.eslintrc-auto-import.json,.editorconfig,.commitlint.cjs"
|
||||
},
|
||||
"vetur.validation.template": false,
|
||||
"vetur.validation.script": false,
|
||||
"vetur.validation.style": false,
|
||||
"vetur.experimental.templateInterpolationService": true
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
// Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Print unibest Vue3 SFC": {
|
||||
"scope": "vue",
|
||||
"prefix": "v3",
|
||||
"body": [
|
||||
"<route lang=\"json5\" type=\"page\">",
|
||||
"{",
|
||||
" layout: 'default',",
|
||||
" style: {",
|
||||
" navigationBarTitleText: '$1',",
|
||||
" },",
|
||||
"}",
|
||||
"</route>\n",
|
||||
"<template>",
|
||||
" <view class=\"\">$2</view>",
|
||||
"</template>\n",
|
||||
"<script lang=\"ts\" setup>",
|
||||
"//$3",
|
||||
"</script>\n",
|
||||
"<style lang=\"scss\" scoped>",
|
||||
"//$4",
|
||||
"</style>\n",
|
||||
],
|
||||
},
|
||||
"Print unibest style": {
|
||||
"scope": "vue",
|
||||
"prefix": "st",
|
||||
"body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
|
||||
},
|
||||
"Print unibest script": {
|
||||
"scope": "vue",
|
||||
"prefix": "sc",
|
||||
"body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
|
||||
},
|
||||
"Print unibest template": {
|
||||
"scope": "vue",
|
||||
"prefix": "te",
|
||||
"body": ["<template>", " <view class=\"\">$1</view>", "</template>\n"],
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 菲鸽
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# 六维生涯
|
||||
|
||||
六维生涯小程序
|
||||
|
||||
## ⚙️ 环境
|
||||
|
||||
- node>=18
|
||||
- pnpm>=7.30
|
||||
- Vue Official>=2.1.10
|
||||
- TypeScript>=5.0
|
||||
|
||||
## 📂 快速开始
|
||||
|
||||
执行 `pnpm create unibest` 创建项目
|
||||
|
||||
执行 `pnpm i` 安装依赖
|
||||
|
||||
执行 `pnpm dev` 运行 `H5`
|
||||
|
||||
## 📦 运行(支持热更新)
|
||||
|
||||
- web平台: `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。
|
||||
- weixin平台:`pnpm dev:mp-weixin` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。
|
||||
- APP平台:`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。
|
||||
|
||||
## 🔗 发布
|
||||
|
||||
- web平台: `pnpm build:h5`,打包后的文件在 `dist/build/h5`,可以放到web服务器,如nginx运行。如果最终不是放在根目录,可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。
|
||||
- weixin平台:`pnpm build:mp-weixin`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
|
||||
- APP平台:`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
VITE_APP_TITLE = '六纬生涯'
|
||||
VITE_APP_PORT = 9000
|
||||
|
||||
VITE_UNI_APPID = 'H57F2ACE4'
|
||||
VITE_WX_APPID = 'wxc2399d3aa57174db'
|
||||
|
||||
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
|
||||
VITE_APP_PUBLIC_BASE=
|
||||
|
||||
VITE_SERVER_BASEURL = 'https://tuiwuv1.ycymedu.com'
|
||||
VITE_STATIC_SERVER_BASEURL = "https://api.static.ycymedu.com"
|
||||
VITE_UPLOAD_BASEURL = 'https://api.v3.ycymedu.com/upload'
|
||||
|
||||
# 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
|
||||
# 下面的变量如果没有设置,会默认使用 VITE_SERVER_BASEURL or VITE_UPLOAD_BASEURL
|
||||
VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'https://api.v3.ycymedu.com'
|
||||
VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'https://api.v3.ycymedu.com'
|
||||
VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'https://api.v3.ycymedu.com'
|
||||
|
||||
VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = 'https://api.v3.ycymedu.com'
|
||||
VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = 'https://api.v3.ycymedu.com'
|
||||
VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = 'https://api.v3.ycymedu.com'
|
||||
|
||||
# h5是否需要配置代理
|
||||
VITE_APP_PROXY=false
|
||||
VITE_APP_PROXY_PREFIX = '/api'
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
Binary file not shown.
|
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html build-time="%BUILD_TIME%">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||
<script>
|
||||
var coverSupport =
|
||||
'CSS' in window &&
|
||||
typeof CSS.supports === 'function' &&
|
||||
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') +
|
||||
'" />',
|
||||
)
|
||||
</script>
|
||||
<title>unibest</title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// manifest.config.ts
|
||||
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
|
||||
import path from 'node:path'
|
||||
import { loadEnv } from 'vite'
|
||||
|
||||
// 获取环境变量的范例
|
||||
const env = loadEnv(process.env.NODE_ENV!, path.resolve(process.cwd(), 'env'))
|
||||
const {
|
||||
VITE_APP_TITLE,
|
||||
VITE_UNI_APPID,
|
||||
VITE_WX_APPID,
|
||||
VITE_APP_PUBLIC_BASE,
|
||||
VITE_FALLBACK_LOCALE,
|
||||
} = env
|
||||
|
||||
export default defineManifestConfig({
|
||||
name: VITE_APP_TITLE,
|
||||
appid: VITE_UNI_APPID,
|
||||
description: '',
|
||||
versionName: '1.0.0',
|
||||
versionCode: '100',
|
||||
transformPx: false,
|
||||
locale: VITE_FALLBACK_LOCALE, // 'zh-Hans'
|
||||
h5: {
|
||||
router: {
|
||||
base: VITE_APP_PUBLIC_BASE,
|
||||
mode: 'history',
|
||||
},
|
||||
},
|
||||
/* 5+App特有相关 */
|
||||
'app-plus': {
|
||||
usingComponents: true,
|
||||
nvueStyleCompiler: 'uni-app',
|
||||
compilerVersion: 3,
|
||||
compatible: {
|
||||
ignoreVersion: true,
|
||||
},
|
||||
splashscreen: {
|
||||
alwaysShowBeforeRender: true,
|
||||
waiting: true,
|
||||
autoclose: true,
|
||||
delay: 0,
|
||||
},
|
||||
/* 模块配置 */
|
||||
modules: {},
|
||||
/* 应用发布信息 */
|
||||
distribute: {
|
||||
/* android打包配置 */
|
||||
android: {
|
||||
minSdkVersion: 30,
|
||||
targetSdkVersion: 30,
|
||||
abiFilters: ['armeabi-v7a', 'arm64-v8a'],
|
||||
permissions: [
|
||||
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
|
||||
'<uses-permission android:name="android.permission.VIBRATE"/>',
|
||||
'<uses-permission android:name="android.permission.READ_LOGS"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
|
||||
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CAMERA"/>',
|
||||
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
|
||||
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
|
||||
'<uses-feature android:name="android.hardware.camera"/>',
|
||||
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
|
||||
],
|
||||
},
|
||||
/* ios打包配置 */
|
||||
ios: {},
|
||||
/* SDK配置 */
|
||||
sdkConfigs: {},
|
||||
/* 图标配置 */
|
||||
icons: {
|
||||
android: {},
|
||||
ios: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* 小程序特有相关 */
|
||||
'mp-weixin': {
|
||||
appid: VITE_WX_APPID,
|
||||
setting: {
|
||||
urlCheck: false,
|
||||
},
|
||||
usingComponents: true,
|
||||
optimization: {
|
||||
subPackages: true,
|
||||
},
|
||||
// requiredPrivateInfos: ['getLocation'],
|
||||
// requiredBackgroundModes: ['audio'],
|
||||
lazyCodeLoading: 'requiredComponents',
|
||||
// __usePrivacyCheck__: true,
|
||||
},
|
||||
uniStatistics: {
|
||||
enable: false,
|
||||
},
|
||||
vueVersion: '3',
|
||||
})
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { GenerateServiceProps } from 'openapi-ts-request'
|
||||
|
||||
export default [
|
||||
{
|
||||
schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
|
||||
serversPath: './src/service/app',
|
||||
requestLibPath: `import request from '@/utils/request';\n import { CustomRequestOptions } from '@/interceptors/request';`,
|
||||
requestOptionsType: 'CustomRequestOptions',
|
||||
isGenReactQuery: true,
|
||||
reactQueryMode: 'vue',
|
||||
isGenJavaScript: false,
|
||||
},
|
||||
] as GenerateServiceProps[]
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
{
|
||||
"name": "volunteer-4",
|
||||
"type": "commonjs",
|
||||
"version": "2.5.5",
|
||||
"description": "unibest - 最好的 uniapp 开发模板",
|
||||
"author": {
|
||||
"name": "feige996",
|
||||
"zhName": "菲鸽",
|
||||
"email": "1020103647@qq.com",
|
||||
"github": "https://github.com/feige996",
|
||||
"gitee": "https://gitee.com/feige996"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/feige996/unibest",
|
||||
"repository-gitee": "https://gitee.com/feige996/unibest",
|
||||
"repository-deprecated": "https://github.com/codercup/unibest",
|
||||
"bugs": {
|
||||
"url": "https://github.com/feige996/unibest/issues"
|
||||
},
|
||||
"homepage": "https://feige996.github.io/unibest/",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=7.30"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"uvm": "npx @dcloudio/uvm@latest",
|
||||
"uvm-rm": "node ./scripts/postupgrade.js",
|
||||
"postuvm": "echo upgrade uni-app success!",
|
||||
"dev:app": "uni -p app",
|
||||
"dev:app-android": "uni -p app-android",
|
||||
"dev:app-ios": "uni -p app-ios",
|
||||
"dev:custom": "uni -p",
|
||||
"dev": "uni",
|
||||
"dev:h5": "uni",
|
||||
"dev:h5:ssr": "uni --ssr",
|
||||
"dev:mp": "uni -p mp-weixin",
|
||||
"dev:mp-alipay": "uni -p mp-alipay",
|
||||
"dev:mp-baidu": "uni -p mp-baidu",
|
||||
"dev:mp-jd": "uni -p mp-jd",
|
||||
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||
"dev:mp-lark": "uni -p mp-lark",
|
||||
"dev:mp-qq": "uni -p mp-qq",
|
||||
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"dev:mp-xhs": "uni -p mp-xhs",
|
||||
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||
"build:app": "uni build -p app",
|
||||
"build:app-android": "uni build -p app-android",
|
||||
"build:app-ios": "uni build -p app-ios",
|
||||
"build:custom": "uni build -p",
|
||||
"build:h5": "uni build",
|
||||
"build": "uni build",
|
||||
"build:h5:ssr": "uni build --ssr",
|
||||
"build:mp-alipay": "uni build -p mp-alipay",
|
||||
"build:mp": "uni build -p mp-weixin",
|
||||
"build:mp-baidu": "uni build -p mp-baidu",
|
||||
"build:mp-jd": "uni build -p mp-jd",
|
||||
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||
"build:mp-lark": "uni build -p mp-lark",
|
||||
"build:mp-qq": "uni build -p mp-qq",
|
||||
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"build:mp-xhs": "uni build -p mp-xhs",
|
||||
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||
"prepare": "git init && husky install ",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"cz": "czg",
|
||||
"openapi-ts-request": "openapi-ts"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{html,vue,ts,cjs,json,md}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"**/*.{vue,js,ts,jsx,tsx}": [
|
||||
"eslint --cache --fix"
|
||||
],
|
||||
"**/*.{vue,css,scss,html}": [
|
||||
"stylelint --fix"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"bin-wrapper": "npm:bin-wrapper-china"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-components": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4020920240930001",
|
||||
"@tanstack/vue-query": "^5.62.16",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"dayjs": "1.11.10",
|
||||
"pinia": "2.0.36",
|
||||
"pinia-plugin-persistedstate": "3.2.1",
|
||||
"pinyin-pro": "^3.26.0",
|
||||
"qs": "6.5.3",
|
||||
"vue": "3.4.21",
|
||||
"wot-design-uni": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.3",
|
||||
"@dcloudio/types": "^3.4.14",
|
||||
"@dcloudio/uni-automator": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4020920240930001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@iconify-json/carbon": "^1.2.4",
|
||||
"@rollup/rollup-darwin-x64": "^4.28.0",
|
||||
"@types/node": "^20.17.9",
|
||||
"@types/wechat-miniprogram": "^3.4.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@uni-helper/uni-types": "1.0.0-alpha.3",
|
||||
"@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
|
||||
"@uni-helper/vite-plugin-uni-manifest": "^0.2.7",
|
||||
"@uni-helper/vite-plugin-uni-pages": "0.2.20",
|
||||
"@uni-helper/vite-plugin-uni-platform": "^0.0.4",
|
||||
"@unocss/preset-legacy-compat": "^0.59.4",
|
||||
"@vue/runtime-core": "^3.5.13",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"commitlint": "^18.6.1",
|
||||
"czg": "^1.9.4",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.7.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.2.10",
|
||||
"openapi-ts-request": "^1.1.2",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-html": "^1.7.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "1.77.8",
|
||||
"stylelint": "^16.11.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recess-order": "^4.6.0",
|
||||
"stylelint-config-recommended": "^14.0.1",
|
||||
"stylelint-config-recommended-scss": "^14.1.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-prettier": "^5.0.2",
|
||||
"svg-sprite-loader": "^6.0.11",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "^5.7.2",
|
||||
"unocss": "^0.58.9",
|
||||
"unocss-applet": "^0.7.8",
|
||||
"unplugin-auto-import": "^0.17.8",
|
||||
"vite": "5.2.8",
|
||||
"vite-plugin-restart": "^0.4.2",
|
||||
"vue-tsc": "^1.8.27"
|
||||
},
|
||||
"minimize": {
|
||||
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --minimize"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||
|
||||
export default defineUniPages({
|
||||
globalStyle: {
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarTitleText: '六纬生涯',
|
||||
navigationBarBackgroundColor: '#fff',
|
||||
backgroundColor: '#F8F8F8',
|
||||
},
|
||||
easycom: {
|
||||
autoscan: true,
|
||||
custom: {
|
||||
'^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue',
|
||||
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
|
||||
'z-paging/components/z-paging$1/z-paging$1.vue',
|
||||
},
|
||||
},
|
||||
tabBar: {
|
||||
color: '#7A7E83',
|
||||
selectedColor: '#3370ff',
|
||||
borderStyle: 'white',
|
||||
backgroundColor: '#ffffff',
|
||||
height: '60px',
|
||||
fontSize: '10px',
|
||||
iconWidth: '16px',
|
||||
spacing: '3px',
|
||||
list: [
|
||||
{
|
||||
iconPath: '/static/tabBar/news.png',
|
||||
selectedIconPath: '/static/tabBar/news-active.png',
|
||||
pagePath: 'pages/evaluation/index/index',
|
||||
text: '测评',
|
||||
},
|
||||
{
|
||||
iconPath: '/static/tabBar/center.png',
|
||||
selectedIconPath: '/static/tabBar/center-active.png',
|
||||
pagePath: 'pages/ucenter/index/index',
|
||||
text: '我的',
|
||||
},
|
||||
],
|
||||
},
|
||||
pages: [],
|
||||
preloadRule: {
|
||||
'pages/evaluation/index/index': {
|
||||
network: 'all',
|
||||
packages: ['pages-evaluation-sub'],
|
||||
},
|
||||
},
|
||||
condition: {
|
||||
current: 0,
|
||||
list: [
|
||||
{
|
||||
name: '',
|
||||
path: '',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"appid": "wxd9369129a1e69342",
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.7.8",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"compileWorklet": false,
|
||||
"uglifyFileName": false,
|
||||
"uploadWithSourceMap": true,
|
||||
"packNpmManually": false,
|
||||
"minifyWXSS": true,
|
||||
"minifyWXML": true,
|
||||
"localPlugins": false,
|
||||
"disableUseStrict": false,
|
||||
"useCompilerPlugins": false,
|
||||
"condition": false,
|
||||
"swc": false,
|
||||
"disableSWC": true
|
||||
},
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
},
|
||||
"simulatorPluginLibVersion": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||
"projectname": "six-dimensional",
|
||||
"setting": {
|
||||
"compileHotReLoad": true,
|
||||
"urlCheck": true,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"useStaticServer": false,
|
||||
"useLanDebug": false,
|
||||
"showES6CompileOption": false,
|
||||
"checkInvalidKey": true,
|
||||
"ignoreDevUnusedFiles": true,
|
||||
"bigPackageSizeSupport": false
|
||||
},
|
||||
"libVersion": "3.7.8",
|
||||
"condition": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// # 执行 `pnpm upgrade` 后会升级 `uniapp` 相关依赖
|
||||
// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
|
||||
// # 只需要执行下面的命令即可
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { exec } = require('child_process')
|
||||
|
||||
// 定义要执行的命令
|
||||
const dependencies = [
|
||||
'@dcloudio/uni-app-harmony',
|
||||
// TODO: 如果需要某个平台的小程序,请手动删除或注释掉
|
||||
'@dcloudio/uni-mp-alipay',
|
||||
'@dcloudio/uni-mp-baidu',
|
||||
'@dcloudio/uni-mp-jd',
|
||||
'@dcloudio/uni-mp-kuaishou',
|
||||
'@dcloudio/uni-mp-lark',
|
||||
'@dcloudio/uni-mp-qq',
|
||||
'@dcloudio/uni-mp-toutiao',
|
||||
'@dcloudio/uni-mp-xhs',
|
||||
'@dcloudio/uni-quickapp-webview',
|
||||
// i18n模板要注释掉下面的
|
||||
'vue-i18n',
|
||||
]
|
||||
|
||||
// 使用exec执行命令
|
||||
exec(`pnpm un ${dependencies.join(' ')}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// 如果有错误,打印错误信息
|
||||
console.error(`执行出错: ${error}`)
|
||||
return
|
||||
}
|
||||
// 打印正常输出
|
||||
console.log(`stdout: ${stdout}`)
|
||||
// 如果有错误输出,也打印出来
|
||||
console.error(`stderr: ${stderr}`)
|
||||
})
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch')
|
||||
})
|
||||
onShow(() => {
|
||||
console.log('App Show')
|
||||
})
|
||||
onHide(() => {
|
||||
console.log('App Hide')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
/* stylelint-disable selector-type-no-unknown */
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
swiper,
|
||||
scroll-view {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// 单行省略,优先使用 unocss: text-ellipsis
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 两行省略
|
||||
.ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
// 三行省略
|
||||
.ellipsis-3 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<route lang="json5" type="page">
|
||||
{
|
||||
style: {
|
||||
navigationBarTitleText: '六纬AI小助手',
|
||||
},
|
||||
needLogin: true,
|
||||
}
|
||||
</route>
|
||||
<template>
|
||||
<web-view :src="url" @message="handleChildMessage" :update-title="false" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '@/store'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
//chat.ycymedu.com
|
||||
//chatv2.ycymedu.com
|
||||
const url = ref(
|
||||
`https://chat.ycymedu.com?userId=${userStore.userInfo.estimatedAchievement.wxId}&subjectGroup=${userStore.userInfo.estimatedAchievement.subjectGroup}&expectedScore=${userStore.userInfo.estimatedAchievement.expectedScore}&provinceName=${userStore.userInfo.estimatedAchievement.provinceName}&locationCode=${userStore.userInfo.estimatedAchievement.provinceCode}&token=${userStore.userInfo.token}×tamp=${new Date().getTime()}`,
|
||||
)
|
||||
|
||||
const handleChildMessage = (event) => {
|
||||
console.log('子应用传递的消息', event)
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.id) {
|
||||
url.value += `&reportId=${options.id}`
|
||||
}
|
||||
if (options.type) {
|
||||
url.value += `&reportType=${options.type}`
|
||||
}
|
||||
if (options.fileId) {
|
||||
url.value += `&fileId=${options.fileId}`
|
||||
}
|
||||
// if (options.locationCode) {
|
||||
// url.value += `&locationCode=${options.locationCode}`
|
||||
// }
|
||||
// const recorderManager = uni.getRecorderManager()
|
||||
// recorderManager.onError((res) => {
|
||||
// console.log('录音错误', res)
|
||||
// })
|
||||
// recorderManager.onStop((res) => {
|
||||
// console.log('录音停止', res)
|
||||
// })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<!-- TabBar占位块 - 与TabBar高度一致 -->
|
||||
<view
|
||||
v-if="showPlaceholder"
|
||||
class="tabbar-placeholder"
|
||||
:style="{ height: `${tabbarTotalHeight}px` }"
|
||||
></view>
|
||||
|
||||
<!-- TabBar组件 -->
|
||||
<view class="custom-tabbar pb-safe">
|
||||
<view class="tabbar-content">
|
||||
<view
|
||||
class="tabbar-item"
|
||||
v-for="item in tabbarList"
|
||||
:key="item.id"
|
||||
:class="[item.centerItem ? 'center-item' : '']"
|
||||
@click="changeItem(item)"
|
||||
>
|
||||
<view class="item-top">
|
||||
<image
|
||||
class="w-full h-full object-contain item-icon"
|
||||
:src="currentPage == item.id ? item.selectIcon : item.icon"
|
||||
></image>
|
||||
</view>
|
||||
<view class="item-bottom" :class="[currentPage == item.id ? 'item-active' : '']">
|
||||
<text>{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TabesItem } from '@/service/app/types'
|
||||
import { tabbarList } from '@/hooks/useTabbarList'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 是否显示占位块
|
||||
showPlaceholder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 安全区域底部高度
|
||||
const safeAreaBottom = ref(0)
|
||||
|
||||
// 计算TabBar总高度 (TabBar高度 + 安全区域高度)
|
||||
const tabbarTotalHeight = computed(() => {
|
||||
// 100rpx转为px,不同设备可能有差异
|
||||
const tabbarHeight = uni.upx2px(100)
|
||||
return tabbarHeight + safeAreaBottom.value
|
||||
})
|
||||
|
||||
const changeItem = (item: TabesItem) => {
|
||||
if (item.navigatorItem) {
|
||||
uni.navigateTo({
|
||||
url: item.path,
|
||||
})
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: item.path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
uni.hideTabBar()
|
||||
|
||||
// 获取系统信息以计算安全区域
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
if (res.safeAreaInsets) {
|
||||
safeAreaBottom.value = res.safeAreaInsets.bottom || 0
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// 暴露高度信息给父组件
|
||||
defineExpose({
|
||||
tabbarTotalHeight,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-tabbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.tabbar-content {
|
||||
display: flex;
|
||||
height: 100rpx;
|
||||
padding: 0 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabbar-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.item-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.item-bottom {
|
||||
font-size: 20rpx;
|
||||
margin-top: 6rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-active {
|
||||
color: #3370ff;
|
||||
}
|
||||
|
||||
.center-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.center-item .item-icon {
|
||||
width: 98rpx;
|
||||
height: 98rpx;
|
||||
}
|
||||
|
||||
.center-item .item-bottom {
|
||||
position: absolute;
|
||||
bottom: 5rpx;
|
||||
}
|
||||
|
||||
/* 占位块样式 */
|
||||
.tabbar-placeholder {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 安全区域适配 */
|
||||
.pb-safe {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<custom-tab-bar :current-page="currentPage" :safe-area-inset-bottom="true" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import CustomTabBar from './CustomTabBar.vue'
|
||||
|
||||
defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<view
|
||||
class="fab-button"
|
||||
@touchstart.stop="startDrag"
|
||||
@touchmove.stop="onDrag"
|
||||
@touchend.stop="endDrag"
|
||||
:style="{ right: position.x + 'px', bottom: position.y + 'px' }"
|
||||
>
|
||||
<image
|
||||
class="w-full h-full rounded-full"
|
||||
src="https://api.static.ycymedu.com/src/images/home/customerService.svg"
|
||||
></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
initialX: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
initialY: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const systemInfo = uni.getWindowInfo()
|
||||
|
||||
const position = ref({ x: props.initialX, y: props.initialY })
|
||||
const startPosition = ref({ x: 0, y: 0 })
|
||||
const startTime = ref(0)
|
||||
const longPressTimer = ref<number | null>(null)
|
||||
const canDrag = ref(false)
|
||||
const moveDistance = ref(0)
|
||||
|
||||
const startDrag = (event: TouchEvent) => {
|
||||
startPosition.value = { x: event.touches[0].clientX, y: event.touches[0].clientY }
|
||||
startTime.value = Date.now()
|
||||
canDrag.value = false
|
||||
moveDistance.value = 0
|
||||
|
||||
// 设置长按定时器,300ms后允许拖动
|
||||
longPressTimer.value = setTimeout(() => {
|
||||
if (!canDrag.value) {
|
||||
canDrag.value = true
|
||||
}
|
||||
}, 300) as unknown as number
|
||||
}
|
||||
|
||||
const onDrag = (event: TouchEvent) => {
|
||||
const deltaX = event.touches[0].clientX - startPosition.value.x
|
||||
const deltaY = event.touches[0].clientY - startPosition.value.y
|
||||
|
||||
// 计算移动距离
|
||||
moveDistance.value = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||
|
||||
// 如果移动距离超过阈值,立即允许拖动
|
||||
if (moveDistance.value > 10 && !canDrag.value) {
|
||||
canDrag.value = true
|
||||
if (longPressTimer.value) {
|
||||
clearTimeout(longPressTimer.value)
|
||||
longPressTimer.value = null
|
||||
}
|
||||
}
|
||||
|
||||
if (!canDrag.value) return
|
||||
|
||||
position.value = { x: position.value.x - deltaX, y: position.value.y - deltaY }
|
||||
startPosition.value = { x: event.touches[0].clientX, y: event.touches[0].clientY }
|
||||
}
|
||||
|
||||
const endDrag = () => {
|
||||
// 清除长按定时器
|
||||
if (longPressTimer.value) {
|
||||
clearTimeout(longPressTimer.value)
|
||||
longPressTimer.value = null
|
||||
}
|
||||
|
||||
// 如果移动距离小于阈值且触摸时间小于300ms,则触发点击事件
|
||||
if (!canDrag.value && moveDistance.value < 10 && Date.now() - startTime.value < 300) {
|
||||
handleClick()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是拖动结束,处理边界
|
||||
if (canDrag.value) {
|
||||
const windowWidth = systemInfo.windowWidth
|
||||
const windowHeight = systemInfo.windowHeight
|
||||
const buttonWidth = 128 // 按钮宽度
|
||||
const buttonHeight = 128 // 按钮高度
|
||||
|
||||
if (position.value.x < 0) position.value.x = 0
|
||||
if (position.value.y < 0) position.value.y = 0
|
||||
if (position.value.x + buttonWidth > windowWidth) position.value.x = windowWidth - buttonWidth
|
||||
if (position.value.y + buttonHeight > windowHeight)
|
||||
position.value.y = windowHeight - buttonHeight
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/customerService/index/index',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fab-button {
|
||||
position: fixed;
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<view
|
||||
:class="`mx-5 rounded-lg bg-white px-[32rpx] ${userStore.userInfo.openid ? 'py-[56rpx]' : 'py-[26rpx]'}`"
|
||||
>
|
||||
<view
|
||||
class="flex items-center justify-between mx-[34rpx] py-[26rpx]"
|
||||
style="border-bottom: 2rpx solid #ededed"
|
||||
@click="handleChange"
|
||||
v-if="userStore.userInfo.openid"
|
||||
>
|
||||
<text class="text-[44rpx] text-[#333]">
|
||||
{{
|
||||
userStore.userInfo.estimatedAchievement.expectedScore
|
||||
? userStore.userInfo.estimatedAchievement.expectedScore
|
||||
: '输入模考/高考成绩'
|
||||
}}
|
||||
</text>
|
||||
<image
|
||||
class="w-[42rpx] h-[39rpx]"
|
||||
src="https://api.static.ycymedu.com/src/images/home/pen.svg"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex items-center justify-center" v-else>
|
||||
<image
|
||||
class="w-[74%] h-[50rpx]"
|
||||
mode="widthFix"
|
||||
src="https://api.static.ycymedu.com/pagefirstloginbg.png"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt-[56rpx] flex items-center justify-between" v-if="userStore.userInfo.openid">
|
||||
<button
|
||||
class="w-[240rpx]! h-[88rpx]! border-[#1580FF]! text-[#1580FF]! text-[30rpx]! font-normal! mr-[32rpx] flex! items-center! justify-center! rounded-[8rpx]!"
|
||||
plain
|
||||
@click="navigatorTo"
|
||||
>
|
||||
一键填报
|
||||
</button>
|
||||
<button
|
||||
class="w-[350rpx]! h-[88rpx]! text-[#fff]! text-[30rpx]! bg-[#1580FF]! font-normal flex! items-center! justify-center! rounded-[8rpx]!"
|
||||
@click="navigatorToAi"
|
||||
>
|
||||
智能填报
|
||||
</button>
|
||||
</view>
|
||||
<view class="flex items-center justify-between mt-[26rpx]" v-else>
|
||||
<button
|
||||
class="h-[78rpx]! w-full! text-[#fff]! text-[30rpx]! bg-[#1580FF]! font-normal flex! items-center! justify-center! rounded-[8rpx]!"
|
||||
@click="navigatorToLogin"
|
||||
>
|
||||
登录/注册
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const handleChange = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/home/inputScore/index',
|
||||
})
|
||||
}
|
||||
|
||||
const navigatorTo = () => {
|
||||
if (userStore.userInfo.estimatedAchievement.expectedScore === '') {
|
||||
handleChange()
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/home/autoFill/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const navigatorToAi = () => {
|
||||
if (userStore.userInfo.estimatedAchievement.expectedScore === '') {
|
||||
handleChange()
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pages-evaluation-sub/aiAutoFill/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const navigatorToLogin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/login-sub/index',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<view class="mx-[36rpx] mt-[48rpx]">
|
||||
<view
|
||||
class="flex items-center justify-between"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
>
|
||||
<text class="text-[32rpx] text-[#333333] font-semibold">高考资讯</text>
|
||||
<image
|
||||
class="w-[40rpx] h-[40rpx]"
|
||||
src="https://api.static.ycymedu.com/src/images/home/right.svg"
|
||||
@click="toNewsPage"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="truncate flex flex-col py-[32rpx]"
|
||||
style="border-bottom: 2rpx solid #eee"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
v-for="item in newsList"
|
||||
:key="item.id"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<text class="truncate text-[28rpx] text-[#333333] font-normal mb-[16rpx] max-w-full">
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<text class="text-[24rpx] color-[#999999] font-normal">{{ item.createTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useCityNewTop, newsList } from '@/hooks/useCityInfoHook'
|
||||
import { News } from '@/types/app-type'
|
||||
|
||||
onShow(() => {
|
||||
useCityNewTop()
|
||||
})
|
||||
|
||||
const handleClick = (item: News) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages-sub/home/news/index?newsId=${item.id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const toNewsPage = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/home/news/newsList',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<view class="mt-[44rpx]" hover-class="none" :hover-stop-propagation="false">
|
||||
<view
|
||||
class="flex items-center justify-between mb-[30rpx] mx-[36rpx]"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
>
|
||||
<text class="text-[32rpx] text-[#333333] font-semibold">热门排行榜</text>
|
||||
<image
|
||||
class="w-[40rpx] h-[40rpx]"
|
||||
src="https://api.static.ycymedu.com/src/images/home/right.svg"
|
||||
@click="toSchool('0')"
|
||||
></image>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="flex items-center overflow-x-auto hot-rank-outer gap-[16rpx] h-[462rpx] px-[32rpx]"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
>
|
||||
<!-- 骨架屏 -->
|
||||
<view
|
||||
v-if="isLoading"
|
||||
v-for="(_skeleton, index) in skeletonItems"
|
||||
:key="'skeleton-' + index"
|
||||
:class="`hot-rank-item flex-none skeleton-item`"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
>
|
||||
<view class="skeleton-text mx-[32rpx] mt-[32rpx] h-[40rpx] w-[120rpx] rounded"></view>
|
||||
|
||||
<view class="flex items-center justify-left mt-[30rpx] mx-[32rpx]" v-for="i in 3" :key="i">
|
||||
<view class="skeleton-text w-[20rpx] h-[28rpx] mr-[10rpx] rounded"></view>
|
||||
<view class="skeleton-image w-[80rpx] h-[80rpx] rounded-full flex-none mr-[16rpx]"></view>
|
||||
<view class="flex flex-col w-full">
|
||||
<view class="skeleton-text h-[28rpx] w-[120rpx] rounded"></view>
|
||||
<view class="skeleton-text h-[22rpx] w-[100rpx] mt-[10rpx] rounded"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 真实数据 -->
|
||||
<view
|
||||
v-else
|
||||
:class="`hot-rank-item flex-none`"
|
||||
hover-class="none"
|
||||
:hover-stop-propagation="false"
|
||||
v-for="typeWrap in universityTypeRankList"
|
||||
:key="typeWrap.type"
|
||||
@click="toSchool(typeWrap.type)"
|
||||
v-show="typeWrap.rows.length > 0"
|
||||
>
|
||||
<text class="font-semibold text-[#303030] text-[32rpx] inline-block mx-[32rpx] mt-[32rpx]">
|
||||
{{ typeWrap.name }}
|
||||
</text>
|
||||
|
||||
<view
|
||||
class="flex items-center justify-left mt-[30rpx] mx-[32rpx]"
|
||||
v-for="(item, index) in typeWrap.rows"
|
||||
:key="index"
|
||||
>
|
||||
<text class="font-[28rpx] text-[#999999] font-normal mr-[10rpx]">
|
||||
{{ item.rank }}
|
||||
</text>
|
||||
<image
|
||||
class="w-[80rpx] h-[80rpx] rounded-full flex-none mr-[16rpx]"
|
||||
:src="item.logo"
|
||||
></image>
|
||||
<view class="truncate flex flex-col" hover-class="none">
|
||||
<text class="font-normal text-[#333333] text-[28rpx] truncate">
|
||||
{{ item.universityName }}
|
||||
</text>
|
||||
<text class="text-[22rpx] text-[#999999] font-normal mt-[10rpx]">
|
||||
{{ item.cityName }}.{{ item.uType }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUnSortType } from '@/hooks/useUnSortType'
|
||||
import { getUniversityRank } from '@/service/index/api'
|
||||
|
||||
const toSchool = (id: string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages-sub/home/schoolRank/index?type=${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const { unSortTypeList } = useUnSortType()
|
||||
let universityTypeRankList = ref([])
|
||||
const isLoading = ref(true)
|
||||
|
||||
// 创建默认的骨架屏数据
|
||||
const skeletonItems = ref([
|
||||
{ type: 'skeleton-1', name: '综合排名', rows: [] },
|
||||
{ type: 'skeleton-2', name: '理工排名', rows: [] },
|
||||
{ type: 'skeleton-3', name: '文科排名', rows: [] },
|
||||
{ type: 'skeleton-4', name: '医科排名', rows: [] },
|
||||
])
|
||||
|
||||
watch(
|
||||
() => unSortTypeList.value,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
Promise.all(
|
||||
newVal.map((item) =>
|
||||
getUniversityRank({
|
||||
Year: 2023,
|
||||
Type: item.type,
|
||||
PageIndex: 1,
|
||||
PageSize: 3,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.then((res) => {
|
||||
universityTypeRankList.value = []
|
||||
res.forEach((item, index) => {
|
||||
universityTypeRankList.value.push({
|
||||
...newVal[index],
|
||||
rows: (item.result as { rows: any[] }).rows,
|
||||
loaded: true,
|
||||
})
|
||||
})
|
||||
isLoading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hot-rank-item {
|
||||
width: 356rpx;
|
||||
height: 452rpx;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hot-rank-item::before {
|
||||
content: '';
|
||||
width: 360rpx;
|
||||
height: 456rpx;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
top: -2rpx;
|
||||
left: -2rpx;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.hot-rank-outer .hot-rank-item:nth-child(1) {
|
||||
background: linear-gradient(180deg, #caddff 0%, #eaf1ff 23%, #fff 100%);
|
||||
}
|
||||
.hot-rank-outer .hot-rank-item:nth-child(1)::before {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(201.8750050663948, 221.00000202655792, 255, 1),
|
||||
rgba(233.7500050663948, 241.39999777078629, 255, 1)
|
||||
);
|
||||
}
|
||||
|
||||
.hot-rank-outer .hot-rank-item:nth-child(2) {
|
||||
background: linear-gradient(180deg, #cef5e1 0%, #ddf7ea 23%, #fff 100%);
|
||||
}
|
||||
.hot-rank-outer .hot-rank-item:nth-child(2)::before {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(205.5883178114891, 245.07227271795273, 225.3302800655365, 1),
|
||||
rgba(221.00000202655792, 247.00000047683716, 234.00000125169754, 1)
|
||||
);
|
||||
}
|
||||
|
||||
.hot-rank-outer .hot-rank-item:nth-child(3)::before {
|
||||
background: linear-gradient(180deg, rgba(245, 237, 255, 1), rgba(245, 237, 255, 1));
|
||||
}
|
||||
.hot-rank-outer .hot-rank-item:nth-child(3) {
|
||||
background: linear-gradient(180deg, #f7e7ff 0%, rgba(245, 237, 255, 0) 23%, #fff 100%);
|
||||
}
|
||||
|
||||
.hot-rank-outer .hot-rank-item:nth-child(4)::before {
|
||||
background: linear-gradient(180deg, rgba(255, 228, 196, 1), rgba(255, 228, 196, 1));
|
||||
}
|
||||
.hot-rank-outer .hot-rank-item:nth-child(4) {
|
||||
background: linear-gradient(180deg, #ffe4c4 0%, rgba(255, 228, 196, 0) 23%, #fff 100%);
|
||||
}
|
||||
|
||||
.hot-rank-outer .hot-rank-item:nth-child(5)::before {
|
||||
background: linear-gradient(180deg, rgba(213, 255, 196, 0), rgba(213, 255, 196, 0));
|
||||
}
|
||||
.hot-rank-outer .hot-rank-item:nth-child(5) {
|
||||
background: linear-gradient(180deg, #e5ffc4 0%, rgba(213, 255, 196, 0) 23%, #fff 100%);
|
||||
}
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.skeleton-text,
|
||||
.skeleton-image {
|
||||
background-color: #e0e0e0;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.skeleton-item {
|
||||
background: linear-gradient(180deg, #eaeaea 0%, #f5f5f5 23%, #fff 100%);
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<view class="grid grid-cols-4 grid-rows-2 items-center gap-2 mt-[48rpx] px-[36rpx]">
|
||||
<view
|
||||
v-for="item in subMenus"
|
||||
:key="item.id"
|
||||
class="flex items-center justify-center flex-col"
|
||||
@click="goPath(item.path, item.isTab)"
|
||||
>
|
||||
<view class="relative w-[88rpx] h-[88rpx]">
|
||||
<image
|
||||
class="skeleton w-[88rpx] h-[88rpx] rounded-full absolute"
|
||||
:class="{ hidden: item.loaded }"
|
||||
></image>
|
||||
<image
|
||||
:src="item.icon"
|
||||
class="w-[88rpx] h-[88rpx] absolute"
|
||||
:class="{ 'opacity-0': !item.loaded }"
|
||||
mode="widthFix"
|
||||
@load="handleLoad(item)"
|
||||
></image>
|
||||
</view>
|
||||
<view class="text-[24rpx] text-[#303030] mt-[8rpx]">{{ item.name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const subMenus = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '找大学',
|
||||
path: '/pages-sub/home/college/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/college.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '查专业',
|
||||
path: '/pages-sub/home/major/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/major.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
// 看职业
|
||||
{
|
||||
id: 3,
|
||||
name: '看职业',
|
||||
path: '/pages-sub/home/career/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/career.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
// 批次线
|
||||
{
|
||||
id: 4,
|
||||
name: '批次线',
|
||||
path: '/pages-sub/home/line/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/line.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
// 查位次
|
||||
{
|
||||
id: 5,
|
||||
name: '查位次',
|
||||
path: '/pages-evaluation-sub/rank/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/rank.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
// 查扩缩招
|
||||
{
|
||||
id: 6,
|
||||
name: '查扩缩招',
|
||||
path: '/pages-sub/home/expand/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/expand.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
// 专业测评
|
||||
{
|
||||
id: 7,
|
||||
name: '专业测评',
|
||||
path: '/pages/evaluation/index/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/evaluation.svg',
|
||||
isTab: true,
|
||||
loaded: false,
|
||||
},
|
||||
// 大学甄别
|
||||
{
|
||||
id: 8,
|
||||
name: '大学甄别',
|
||||
path: '/pages-sub/home/distinguish/index',
|
||||
icon: 'https://api.static.ycymedu.com/src/images/home/distinguish.svg',
|
||||
isTab: false,
|
||||
loaded: false,
|
||||
},
|
||||
])
|
||||
|
||||
const goPath = (path: string, isTab: boolean) => {
|
||||
if (isTab) {
|
||||
uni.switchTab({
|
||||
url: path,
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleLoad = (item: any) => {
|
||||
item.loaded = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.skeleton {
|
||||
background-color: #e0e0e0; /* 骨架屏的背景色 */
|
||||
animation: pulse 1.5s infinite; /* 添加动画效果 */
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
<template>
|
||||
<view class="navbar">
|
||||
<!-- 状态栏占位 -->
|
||||
<view
|
||||
v-if="safeAreaInsetTop"
|
||||
class="status-bar"
|
||||
:style="{ height: statusBarHeight + 'px', backgroundColor: bgColor }"
|
||||
></view>
|
||||
|
||||
<!-- 导航栏主体 -->
|
||||
<view
|
||||
class="navbar-content"
|
||||
:class="[contentClass, fixed ? 'navbar-fixed' : '', bordered ? 'navbar-border' : '']"
|
||||
:style="{
|
||||
backgroundColor: bgColor,
|
||||
height: navHeight + 'px',
|
||||
top: fixed ? (safeAreaInsetTop ? statusBarHeight : 0) + 'px' : '0',
|
||||
}"
|
||||
>
|
||||
<!-- 左侧区域 -->
|
||||
<view class="navbar-left" @click="handleClickLeft">
|
||||
<view v-if="leftArrow" class="back-icon">
|
||||
<view class="i-carbon-chevron-left text-[40rpx] text-[#333] font-semibold" />
|
||||
</view>
|
||||
<slot name="left"></slot>
|
||||
</view>
|
||||
|
||||
<!-- 中间标题区域 -->
|
||||
<view class="navbar-title">
|
||||
<slot name="title">
|
||||
<text class="title-text">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<view class="navbar-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位元素 -->
|
||||
<view
|
||||
v-if="placeholder && fixed"
|
||||
:style="{
|
||||
height: `${navHeight}px`,
|
||||
backgroundColor: bgColor,
|
||||
}"
|
||||
></view>
|
||||
<slot name="background"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
leftArrow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
safeAreaInsetTop: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff',
|
||||
},
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: 'justify-between',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['clickLeft'])
|
||||
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getWindowInfo()
|
||||
const deviceInfo = uni.getDeviceInfo()
|
||||
|
||||
const statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 动态计算导航栏高度
|
||||
const navHeight = computed(() => {
|
||||
// 获取屏幕信息
|
||||
const { screenWidth } = systemInfo
|
||||
const { platform } = deviceInfo
|
||||
|
||||
// 将px转换为rpx的比例
|
||||
const ratio = 750 / screenWidth
|
||||
|
||||
// 根据平台设置不同的导航栏高度
|
||||
if (platform === 'ios') {
|
||||
return 88 / ratio // iOS 一般是 44pt,转换为px
|
||||
} else if (platform === 'android') {
|
||||
return 96 / ratio // Android 一般是 48dp,转换为px
|
||||
} else {
|
||||
return 88 / ratio // 默认值
|
||||
}
|
||||
})
|
||||
|
||||
const handleClickLeft = () => {
|
||||
emit('clickLeft')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
width: 100%;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* justify-content: space-between; */
|
||||
padding: 0 16rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.navbar-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.navbar-border {
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-width: 100rpx;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
/* flex: 1; */
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 52rpx;
|
||||
justify-content: flex-end;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAKYAAsAAAAABlAAAAJMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACCcApcdgE2AiQDCAsGAAQgBYRnBzYbmQXIHpIkBQQKkYCABBEPz2/t/XN3twEbowBkQTxQEQ1RKaKSxEOi0agkJKF5Qvj/f037IFKwlZ2dWU2tJu0EhPwHkBwgOVAclKcvAQpI/v/fz/08XECy+YBymmPQiwIcSmhAY4uSFcgJ+IaxC1zCYwLtRjWSnZ2rGgQWBowLxCPrVBBYllQqNTQ0VISaBXEHtTRNUwW4jb4f/xYEC0kqMzDx6CGrQuKXxKc6Zf7POYQgQHs5kIwjYwEoxK3G/DpRwbi0dlNwKKjAL4lf6vw/R2zVWvTPIwuiCnp2wCRUZ3yJX5pJFVDfByyAFR2AblMAX/OR3t7+zOJi8GyyfzC1uQXLZvtnk/0zyfTy+PvH0/Xp5OzR98/H797/+/fDu3d/3739+/fd+/+nmxvLc5vrS+sry2vz84tLs9Mzc4vzs9NTM/Ozc1OzM3MzU/Mz0wvTU4vTk0tTE8uTEyuT4yv/G0E3XUxv7wwNbu/s9G8fbO9v7+3sb+3ubW4dbO4dbO3vbu4dbO3JzqPFtRE4gEGAX0NBkL+hpCZALkEp5FKUQqE0NHlXJIGrDNAOcEQBCHU+kXT5QNblC7kEv1EK9Y9SB/8o7YYu2m0YXrJLouNIjQJhH+QbVkVZrUQ+YuqzUJdzxPMHhdIj0+hg4o0D8ogj5r5bSoQUxjADz+A8hBDQFEYwh3mommXTul7Vm5ZtqAqJHIdoKCDYDyQ3mCqUG1YKn5+C0s0yiJ/qKVAQedKAhg6Y3mEHJBQaWKnvLVMiiEIxGAY8Aw6HIAhAJmEIzIIOUjLTTAB1taL1QvNq+fYN7QDjcc2okeioaOmy5LFXt3QAAAAA')
|
||||
format('woff2');
|
||||
}
|
||||
|
||||
.back-text {
|
||||
font-family: 'iconfont' !important;
|
||||
font-size: 48rpx;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<view class="tab-container">
|
||||
<!-- tab标题栏 -->
|
||||
<scroll-view
|
||||
scroll-x
|
||||
class="tab-scroll-view"
|
||||
:scroll-left="scrollLeft"
|
||||
scroll-with-animation
|
||||
show-scrollbar="false"
|
||||
:id="tabScrollId"
|
||||
>
|
||||
<view class="tab-items-container">
|
||||
<view
|
||||
v-for="(item, index) in tabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: currentIndex === index }"
|
||||
@click="handleTabClick(index)"
|
||||
:id="`tab-item-${index}`"
|
||||
>
|
||||
<text class="tab-text">{{ item.title }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 独立的滑块元素 -->
|
||||
<view
|
||||
class="tab-line"
|
||||
:style="{
|
||||
transform: `translateX(${lineLeft}px)`,
|
||||
width: `${lineWidth}rpx`,
|
||||
backgroundColor: props.themeColor,
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, onMounted } from 'vue'
|
||||
|
||||
// 唯一ID,防止多个Tab组件冲突
|
||||
const tabScrollId = `tab-scroll-${Date.now()}`
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
// tabs数据,格式:[{title: '标签1'}, {title: '标签2'}]
|
||||
tabs: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 默认选中的索引
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// 主题色
|
||||
themeColor: {
|
||||
type: String,
|
||||
default: '#3C9CFD',
|
||||
},
|
||||
// 滑块宽度
|
||||
lineWidth: {
|
||||
type: [Number, String],
|
||||
default: 48,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
// 当前激活的索引
|
||||
const currentIndex = ref(props.modelValue)
|
||||
|
||||
// 滚动位置
|
||||
const scrollLeft = ref(0)
|
||||
|
||||
// 滑块位置
|
||||
const lineLeft = ref(0)
|
||||
|
||||
// 监听props变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (currentIndex.value !== newVal) {
|
||||
currentIndex.value = newVal
|
||||
updateTabPosition()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听currentIndex变化
|
||||
watch(
|
||||
() => currentIndex.value,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) {
|
||||
updateTabPosition()
|
||||
// 向父组件同步更新
|
||||
emit('update:modelValue', newVal)
|
||||
emit('change', {
|
||||
index: newVal,
|
||||
item: props.tabs[newVal] || {},
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 监听tabs变化,重新计算滑块位置
|
||||
watch(
|
||||
() => props.tabs,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
updateTabPosition()
|
||||
})
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// 组件挂载后初始化
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
updateTabPosition()
|
||||
})
|
||||
})
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// 更新标签位置和滑块位置 - 优化版
|
||||
const updateTabPosition = () => {
|
||||
nextTick(() => {
|
||||
// 创建查询对象
|
||||
const query = uni.createSelectorQuery().in(instance)
|
||||
|
||||
// 获取滚动视图和当前选中标签的信息
|
||||
query.select(`#${tabScrollId}`).boundingClientRect()
|
||||
query.select(`#tab-item-${currentIndex.value}`).boundingClientRect()
|
||||
|
||||
query.exec((res) => {
|
||||
if (res && res[0] && res[1]) {
|
||||
const scrollView = res[0]
|
||||
const currentTab = res[1]
|
||||
|
||||
// 1. 计算滑块位置 - 直接使用当前标签的中心位置
|
||||
const tabCenter = currentTab.left + currentTab.width / 2 - scrollView.left
|
||||
|
||||
// 2. 计算滑块左侧应该在的位置(居中)
|
||||
const lineWidthPx = uni.upx2px(Number(props.lineWidth))
|
||||
lineLeft.value = tabCenter - lineWidthPx / 2
|
||||
|
||||
// 3. 计算滚动位置,使选中的标签居中显示在滚动视图中
|
||||
const offsetLeft = currentTab.left - scrollView.left
|
||||
scrollLeft.value = offsetLeft - scrollView.width / 2 + currentTab.width / 2
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 点击标签切换
|
||||
const handleTabClick = (index) => {
|
||||
if (currentIndex.value !== index) {
|
||||
currentIndex.value = index
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tab-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-scroll-view {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-items-container {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 32rpx;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: v-bind('props.themeColor');
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
/* 平滑过渡效果 */
|
||||
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-svg-loader" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
interface ImportMetaEnv {
|
||||
/** 网站标题,应用名称 */
|
||||
readonly VITE_APP_TITLE: string
|
||||
/** 服务端口号 */
|
||||
readonly VITE_SERVER_PORT: string
|
||||
/** 后台接口地址 */
|
||||
readonly VITE_SERVER_BASEURL: string
|
||||
/** H5是否需要代理 */
|
||||
readonly VITE_APP_PROXY: 'true' | 'false'
|
||||
/** H5是否需要代理,需要的话有个前缀 */
|
||||
readonly VITE_APP_PROXY_PREFIX: string // 一般是/api
|
||||
/** 上传图片地址 */
|
||||
readonly VITE_UPLOAD_BASEURL: string
|
||||
/** 是否清除console */
|
||||
readonly VITE_DELETE_CONSOLE: string
|
||||
// 更多环境变量...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import { getNewsTop, getProvinceInitialization, getNewsDetailInfo } from '@/service/index/api'
|
||||
import { useCityStore } from '@/store/city'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { City, News, NewsDetail } from '@/types/app-type'
|
||||
import { pinyin } from 'pinyin-pro'
|
||||
|
||||
interface Province {
|
||||
provincename: string
|
||||
}
|
||||
|
||||
const cityStore = useCityStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
export const cities = []
|
||||
export const useCityInfo = () => {
|
||||
getProvinceInitialization().then((res) => {
|
||||
if (res.result) {
|
||||
const list = res.result as Province[]
|
||||
const li = groupByFirstLetter(list)
|
||||
cityStore.setCities(li)
|
||||
const defaultCity = list.filter((item) => item.provincename === '山东省')[0] as City
|
||||
if (userStore.userInfo.city.code === '0') {
|
||||
userStore.setUserCity(defaultCity)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 按照首字母分组
|
||||
const groupByFirstLetter = (lis: Province[]): { letter: string; provinces: Province[] }[] => {
|
||||
const grouped: { [key: string]: Province[] } = {}
|
||||
|
||||
for (let i = 0; i < lis.length; i++) {
|
||||
const firstLetter = pinyin(lis[i].provincename, {
|
||||
pattern: 'first',
|
||||
toneType: 'none',
|
||||
})[0].toUpperCase()
|
||||
|
||||
if (!grouped[firstLetter]) {
|
||||
grouped[firstLetter] = []
|
||||
}
|
||||
|
||||
grouped[firstLetter].push(lis[i])
|
||||
}
|
||||
|
||||
return Object.keys(grouped)
|
||||
.sort()
|
||||
.map((key) => ({ letter: key, provinces: grouped[key] }))
|
||||
}
|
||||
|
||||
export const newsList = ref([])
|
||||
export const useCityNewTop = () => {
|
||||
const fetchNewTopFun = (provinceCode) => {
|
||||
getNewsTop({ Top: 4, CategoryId: 1, provinceCode }).then((res) => {
|
||||
if (res.code === 200) {
|
||||
newsList.value = res.result as { title: string }[]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (userStore.userInfo.city.code === '0') {
|
||||
userStore.$subscribe((mutation, state) => {
|
||||
if ((mutation.events as { key: string }).key === 'city') {
|
||||
fetchNewTopFun(state.userInfo.city.code)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
fetchNewTopFun(userStore.userInfo.city.code)
|
||||
}
|
||||
}
|
||||
|
||||
export const newsDetail = ref<NewsDetail>({
|
||||
coverImg: '',
|
||||
createTime: '',
|
||||
id: 0,
|
||||
summary: '',
|
||||
title: '',
|
||||
author: '',
|
||||
click: 0,
|
||||
detail: '',
|
||||
})
|
||||
export const useCityNewDetail = (id: number) => {
|
||||
getNewsDetailInfo({ id }).then((res) => {
|
||||
if (res.code === 200) {
|
||||
newsDetail.value = res.result as NewsDetail
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
let cachedIphoneStyle = ''
|
||||
|
||||
;(function () {
|
||||
wx.getSystemInfo({
|
||||
Loading…
Reference in New Issue