1
							
								
								
									
										106
									
								
								.commitlintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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:     自定义',
 | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								.cusorrules
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,68 @@ | |||||||
|  | 每一次会话请求结束后进行会话总结, | ||||||
|  | 无论生成新文件还是修改已有文件都需要做总结, | ||||||
|  | 并将总结内容Append写入到Readme.md文件中(说明文件中的内容是累积增加的)。 | ||||||
|  | 
 | ||||||
|  | 总结内容应包括: | ||||||
|  | 
 | ||||||
|  | -会话的主要目的 | ||||||
|  | 
 | ||||||
|  | -完成的主要任务 | ||||||
|  | 
 | ||||||
|  | -关键决策和解决方案 | ||||||
|  | 
 | ||||||
|  | -使用的技术栈 | ||||||
|  | 
 | ||||||
|  | -修改了哪些文件 | ||||||
|  | 
 | ||||||
|  | -文件的修改内容 | ||||||
|  |   | ||||||
|  | // 项目技术栈和目录结构 | ||||||
|  | 项目技术栈: | ||||||
|  | - 前端框架:Vue 3.4.21 | ||||||
|  | - 构建工具:Vite 5.2.8 | ||||||
|  | - 包管理器:pnpm 9.15.4 | ||||||
|  | - 状态管理:Pinia 2.0.36 | ||||||
|  | - UI组件库:wot-design-uni 1.4.0 | ||||||
|  | - 网络请求:luch-request 3.1.1 | ||||||
|  | - 工具库: | ||||||
|  |   - dayjs 1.11.10 | ||||||
|  |   - qs 6.5.3 | ||||||
|  |   - @tanstack/vue-query 5.62.16 | ||||||
|  | - 样式处理: | ||||||
|  |   - UnoCSS 0.58.9 | ||||||
|  |   - SASS 1.77.8 | ||||||
|  |   - PostCSS 8.4.49 | ||||||
|  | - 代码规范: | ||||||
|  |   - ESLint | ||||||
|  |   - Prettier | ||||||
|  |   - StyleLint | ||||||
|  |   - TypeScript 5.7.2 | ||||||
|  |   - CommitLint | ||||||
|  | 
 | ||||||
|  | 项目目录结构: | ||||||
|  | ├── src/                    # 源代码目录 | ||||||
|  | │   ├── components/         # 公共组件 | ||||||
|  | │   ├── hooks/             # 自定义 hooks | ||||||
|  | │   ├── interceptors/      # 拦截器 | ||||||
|  | │   ├── layouts/           # 布局组件 | ||||||
|  | │   ├── pages/             # 页面 | ||||||
|  | │   ├── pages-sub/         # 分包页面 | ||||||
|  | │   ├── service/           # API 服务 | ||||||
|  | │   ├── static/            # 静态资源 | ||||||
|  | │   ├── store/             # 状态管理 | ||||||
|  | │   ├── style/             # 全局样式 | ||||||
|  | │   ├── types/             # TypeScript 类型定义 | ||||||
|  | │   ├── uni_modules/       # uni-app 模块 | ||||||
|  | │   ├── utils/             # 工具函数 | ||||||
|  | │   ├── App.vue            # 应用入口组件 | ||||||
|  | │   ├── main.ts            # 应用入口文件 | ||||||
|  | │   ├── manifest.json      # 应用配置文件 | ||||||
|  | │   ├── pages.json         # 页面路由配置 | ||||||
|  | │   └── uni.scss           # 全局样式变量 | ||||||
|  | ├── vite-plugins/          # Vite 插件 | ||||||
|  | ├── scripts/               # 脚本文件 | ||||||
|  | ├── env/                   # 环境配置 | ||||||
|  | ├── .github/               # GitHub 配置 | ||||||
|  | ├── .husky/                # Git hooks | ||||||
|  | ├── .vscode/               # VS Code 配置 | ||||||
|  | └── screenshots/           # 截图目录 | ||||||
							
								
								
									
										13
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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 # 关闭末尾空格修剪 | ||||||
							
								
								
									
										1
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | src/uni_modules/ | ||||||
							
								
								
									
										101
									
								
								.eslintrc-auto-import.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,101 @@ | |||||||
|  | { | ||||||
|  |   "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 | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										97
									
								
								.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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, | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -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 | ||||||
							
								
								
									
										6
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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 | ||||||
							
								
								
									
										12
									
								
								.prettierignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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/** | ||||||
							
								
								
									
										19
									
								
								.prettierrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								.stylelintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1 @@ | |||||||
|  | src/uni_modules/ | ||||||
							
								
								
									
										58
									
								
								.stylelintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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, | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -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" | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,62 @@ | |||||||
|  | { | ||||||
|  |   // 默认格式化工具选择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" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								.vscode/vue3.code-snippets
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -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"], | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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. | ||||||
							
								
								
									
										
											BIN
										
									
								
								art-exam-web-mobile.rar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										19
									
								
								env/.env
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | |||||||
|  | VITE_APP_TITLE = 'unibest' | ||||||
|  | VITE_APP_PORT = 9000 | ||||||
|  | 
 | ||||||
|  | VITE_UNI_APPID = 'H57F2ACE4' | ||||||
|  | VITE_WX_APPID = 'wx25fb5794b1c3026f' | ||||||
|  | 
 | ||||||
|  | # h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base | ||||||
|  | VITE_APP_PUBLIC_BASE=/ | ||||||
|  | 
 | ||||||
|  | VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload' | ||||||
|  | 
 | ||||||
|  | # 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。 | ||||||
|  | # 下面的变量如果没有设置,会默认使用 VITE_SERVER_BASEURL or VITE_UPLOAD_BASEURL  | ||||||
|  | 
 | ||||||
|  | # h5是否需要配置代理 | ||||||
|  | VITE_APP_PROXY=true | ||||||
|  | VITE_APP_PROXY_PREFIX = '/base' | ||||||
|  | VITE_SERVER_BASEURL = 'http://192.168.40.1:8888/' | ||||||
|  | 
 | ||||||
							
								
								
									
										6
									
								
								env/.env.development
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | |||||||
|  | # 变量必须以 VITE_ 为前缀才能暴露给外部读取 | ||||||
|  | NODE_ENV = 'development' | ||||||
|  | # 是否去除console 和 debugger | ||||||
|  | VITE_DELETE_CONSOLE = false | ||||||
|  | # 是否开启sourcemap | ||||||
|  | VITE_SHOW_SOURCEMAP = true | ||||||
							
								
								
									
										6
									
								
								env/.env.production
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | |||||||
|  | # 变量必须以 VITE_ 为前缀才能暴露给外部读取 | ||||||
|  | NODE_ENV = 'development' | ||||||
|  | # 是否去除console 和 debugger | ||||||
|  | VITE_DELETE_CONSOLE = true | ||||||
|  | # 是否开启sourcemap | ||||||
|  | VITE_SHOW_SOURCEMAP = false | ||||||
							
								
								
									
										4
									
								
								env/.env.test
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | |||||||
|  | # 变量必须以 VITE_ 为前缀才能暴露给外部读取 | ||||||
|  | NODE_ENV = 'development' | ||||||
|  | # 是否去除console 和 debugger | ||||||
|  | VITE_DELETE_CONSOLE = false | ||||||
							
								
								
									
										
											BIN
										
									
								
								favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										26
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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> | ||||||
							
								
								
									
										134
									
								
								manifest.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,134 @@ | |||||||
|  | // 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, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   /* 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: { | ||||||
|  |           hdpi: 'static/app/icons/72x72.png', | ||||||
|  |           xhdpi: 'static/app/icons/96x96.png', | ||||||
|  |           xxhdpi: 'static/app/icons/144x144.png', | ||||||
|  |           xxxhdpi: 'static/app/icons/192x192.png', | ||||||
|  |         }, | ||||||
|  |         ios: { | ||||||
|  |           appstore: 'static/app/icons/1024x1024.png', | ||||||
|  |           ipad: { | ||||||
|  |             app: 'static/app/icons/76x76.png', | ||||||
|  |             'app@2x': 'static/app/icons/152x152.png', | ||||||
|  |             notification: 'static/app/icons/20x20.png', | ||||||
|  |             'notification@2x': 'static/app/icons/40x40.png', | ||||||
|  |             'proapp@2x': 'static/app/icons/167x167.png', | ||||||
|  |             settings: 'static/app/icons/29x29.png', | ||||||
|  |             'settings@2x': 'static/app/icons/58x58.png', | ||||||
|  |             spotlight: 'static/app/icons/40x40.png', | ||||||
|  |             'spotlight@2x': 'static/app/icons/80x80.png', | ||||||
|  |           }, | ||||||
|  |           iphone: { | ||||||
|  |             'app@2x': 'static/app/icons/120x120.png', | ||||||
|  |             'app@3x': 'static/app/icons/180x180.png', | ||||||
|  |             'notification@2x': 'static/app/icons/40x40.png', | ||||||
|  |             'notification@3x': 'static/app/icons/60x60.png', | ||||||
|  |             'settings@2x': 'static/app/icons/58x58.png', | ||||||
|  |             'settings@3x': 'static/app/icons/87x87.png', | ||||||
|  |             'spotlight@2x': 'static/app/icons/80x80.png', | ||||||
|  |             'spotlight@3x': 'static/app/icons/120x120.png', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   /* 快应用特有相关 */ | ||||||
|  |   quickapp: {}, | ||||||
|  |   /* 小程序特有相关 */ | ||||||
|  |   'mp-weixin': { | ||||||
|  |     appid: VITE_WX_APPID, | ||||||
|  |     setting: { | ||||||
|  |       urlCheck: false, | ||||||
|  |     }, | ||||||
|  |     usingComponents: true, | ||||||
|  |     // __usePrivacyCheck__: true,
 | ||||||
|  |   }, | ||||||
|  |   'mp-alipay': { | ||||||
|  |     usingComponents: true, | ||||||
|  |     styleIsolation: 'shared', | ||||||
|  |   }, | ||||||
|  |   'mp-baidu': { | ||||||
|  |     usingComponents: true, | ||||||
|  |   }, | ||||||
|  |   'mp-toutiao': { | ||||||
|  |     usingComponents: true, | ||||||
|  |   }, | ||||||
|  |   uniStatistics: { | ||||||
|  |     enable: false, | ||||||
|  |   }, | ||||||
|  |   vueVersion: '3', | ||||||
|  | }) | ||||||
							
								
								
									
										13
									
								
								openapi-ts-request.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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[]
 | ||||||
							
								
								
									
										175
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,175 @@ | |||||||
|  | { | ||||||
|  |   "name": "wxmp", | ||||||
|  |   "type": "commonjs", | ||||||
|  |   "version": "2.6.2", | ||||||
|  |   "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-harmony": "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-alipay": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-baidu": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-jd": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-kuaishou": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-lark": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-qq": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-toutiao": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-weixin": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-mp-xhs": "3.0.0-4020920240930001", | ||||||
|  |     "@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001", | ||||||
|  |     "@tanstack/vue-query": "^5.62.16", | ||||||
|  |     "abortcontroller-polyfill": "^1.7.8", | ||||||
|  |     "dayjs": "1.11.10", | ||||||
|  |     "luch-request": "^3.1.1", | ||||||
|  |     "pinia": "2.0.36", | ||||||
|  |     "pinia-plugin-persistedstate": "3.2.1", | ||||||
|  |     "qs": "6.5.3", | ||||||
|  |     "vue": "3.4.21", | ||||||
|  |     "wot-design-uni": "^1.4.0", | ||||||
|  |     "z-paging": "^2.8.4" | ||||||
|  |   }, | ||||||
|  |   "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", | ||||||
|  |     "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" | ||||||
|  |   }, | ||||||
|  |   "packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0" | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								pages.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,43 @@ | |||||||
|  | import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages' | ||||||
|  | 
 | ||||||
|  | export default defineUniPages({ | ||||||
|  |   globalStyle: { | ||||||
|  |     navigationStyle: 'default', | ||||||
|  |     navigationBarTitleText: 'unibest', | ||||||
|  |     navigationBarBackgroundColor: '#f8f8f8', | ||||||
|  |     navigationBarTextStyle: 'black', | ||||||
|  |     backgroundColor: '#FFFFFF', | ||||||
|  |   }, | ||||||
|  |   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: '#222222', | ||||||
|  |     selectedColor: '#000000', | ||||||
|  |     backgroundColor: '#ffffff', | ||||||
|  |     borderStyle: 'black', | ||||||
|  |     height: '50px', | ||||||
|  |     fontSize: '10px', | ||||||
|  |     iconWidth: '20px', | ||||||
|  |     spacing: '3px', | ||||||
|  |     list: [ | ||||||
|  |       { | ||||||
|  |         iconPath: 'static/tabbar/edit.svg', | ||||||
|  |         selectedIconPath: 'static/tabbar/editHL.svg', | ||||||
|  |         pagePath: 'pages/index/index', | ||||||
|  |         text: '报名', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         iconPath: 'static/tabbar/document.svg', | ||||||
|  |         selectedIconPath: 'static/tabbar/documentHL.svg', | ||||||
|  |         pagePath: 'pages/fate/fate', | ||||||
|  |         text: '考试', | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |   }, | ||||||
|  | }) | ||||||
							
								
								
									
										13210
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										25
									
								
								project.config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,25 @@ | |||||||
|  | { | ||||||
|  |   "setting": { | ||||||
|  |     "es6": true, | ||||||
|  |     "postcss": true, | ||||||
|  |     "minified": true, | ||||||
|  |     "uglifyFileName": false, | ||||||
|  |     "enhance": true, | ||||||
|  |     "packNpmRelationList": [], | ||||||
|  |     "babelSetting": { | ||||||
|  |       "ignore": [], | ||||||
|  |       "disablePlugins": [], | ||||||
|  |       "outputPath": "" | ||||||
|  |     }, | ||||||
|  |     "useCompilerPlugins": false, | ||||||
|  |     "minifyWXML": true | ||||||
|  |   }, | ||||||
|  |   "compileType": "miniprogram", | ||||||
|  |   "simulatorPluginLibVersion": {}, | ||||||
|  |   "packOptions": { | ||||||
|  |     "ignore": [], | ||||||
|  |     "include": [] | ||||||
|  |   }, | ||||||
|  |   "appid": "wx4ec68802a3f48c39", | ||||||
|  |   "editorSetting": {} | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								project.private.config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | |||||||
|  | { | ||||||
|  |   "libVersion": "3.8.7", | ||||||
|  |   "projectname": "xiangqingmp", | ||||||
|  |   "setting": { | ||||||
|  |     "urlCheck": true, | ||||||
|  |     "coverView": true, | ||||||
|  |     "lazyloadPlaceholderEnable": false, | ||||||
|  |     "skylineRenderEnable": false, | ||||||
|  |     "preloadBackgroundData": false, | ||||||
|  |     "autoAudits": false, | ||||||
|  |     "showShadowRootInWxmlPanel": true, | ||||||
|  |     "compileHotReLoad": true | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								screenshots/pay-1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 116 KiB | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pay-2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 134 KiB | 
							
								
								
									
										36
									
								
								scripts/postupgrade.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -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}`) | ||||||
|  | }) | ||||||
							
								
								
									
										67
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,67 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' | ||||||
|  | import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only' | ||||||
|  | import { useUserStore } from './store' | ||||||
|  | const userStore = useUserStore() | ||||||
|  | onLaunch(() => { | ||||||
|  |   console.log('App Launch') | ||||||
|  |   console.log(userStore.userInfo) | ||||||
|  |   console.log(uni.getStorageSync('x-token')) | ||||||
|  |   if (!uni.getStorageSync('x-token')) { | ||||||
|  |     uni.navigateTo({ | ||||||
|  |       url: '/pages/login/index', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | onShow(() => { | ||||||
|  |   console.log('App Show') | ||||||
|  | }) | ||||||
|  | onHide(() => { | ||||||
|  |   console.log('App Hide') | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss"> | ||||||
|  | /* 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
									
								
								src/components/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										31
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -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
									
								
								src/hooks/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										44
									
								
								src/hooks/useRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,44 @@ | |||||||
|  | import { UnwrapRef } from 'vue' | ||||||
|  | 
 | ||||||
|  | type IUseRequestOptions<T> = { | ||||||
|  |   /** 是否立即执行 */ | ||||||
|  |   immediate?: boolean | ||||||
|  |   /** 初始化数据 */ | ||||||
|  |   initialData?: T | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * useRequest是一个定制化的请求钩子,用于处理异步请求和响应。 | ||||||
|  |  * @param func 一个执行异步请求的函数,返回一个包含响应数据的Promise。 | ||||||
|  |  * @param options 包含请求选项的对象 {immediate, initialData}。 | ||||||
|  |  * @param options.immediate 是否立即执行请求,默认为false。 | ||||||
|  |  * @param options.initialData 初始化数据,默认为undefined。 | ||||||
|  |  * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。 | ||||||
|  |  */ | ||||||
|  | export default function useRequest<T>( | ||||||
|  |   func: () => Promise<IResData<T>>, | ||||||
|  |   options: IUseRequestOptions<T> = { immediate: false }, | ||||||
|  | ) { | ||||||
|  |   const loading = ref(false) | ||||||
|  |   const error = ref(false) | ||||||
|  |   const data = ref<T>(options.initialData) | ||||||
|  |   const run = async () => { | ||||||
|  |     loading.value = true | ||||||
|  |     return func() | ||||||
|  |       .then((res) => { | ||||||
|  |         data.value = res.data as UnwrapRef<T> | ||||||
|  |         error.value = false | ||||||
|  |         return data.value | ||||||
|  |       }) | ||||||
|  |       .catch((err) => { | ||||||
|  |         error.value = err | ||||||
|  |         throw err | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         loading.value = false | ||||||
|  |       }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   options.immediate && run() | ||||||
|  |   return { loading, error, data, run } | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								src/hooks/useUpload.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | |||||||
|  | // TODO: 别忘加更改环境变量的 VITE_UPLOAD_BASEURL 地址。
 | ||||||
|  | import { getEnvBaseUploadUrl } from '@/utils' | ||||||
|  | 
 | ||||||
|  | const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}` | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * useUpload 是一个定制化的请求钩子,用于处理上传图片。 | ||||||
|  |  * @param formData 额外传递给后台的数据,如{name: '菲鸽'}。 | ||||||
|  |  * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。 | ||||||
|  |  */ | ||||||
|  | export default function useUpload<T = string>(formData: Record<string, any> = {}) { | ||||||
|  |   const loading = ref(false) | ||||||
|  |   const error = ref(false) | ||||||
|  |   const data = ref<T>() | ||||||
|  |   const run = () => { | ||||||
|  |     // #ifdef MP-WEIXIN
 | ||||||
|  |     // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
 | ||||||
|  |     // 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议
 | ||||||
|  |     uni.chooseMedia({ | ||||||
|  |       count: 1, | ||||||
|  |       mediaType: ['image'], | ||||||
|  |       success: (res) => { | ||||||
|  |         loading.value = true | ||||||
|  |         const tempFilePath = res.tempFiles[0].tempFilePath | ||||||
|  |         uploadFile<T>({ tempFilePath, formData, data, error, loading }) | ||||||
|  |       }, | ||||||
|  |       fail: (err) => { | ||||||
|  |         console.error('uni.chooseMedia err->', err) | ||||||
|  |         error.value = true | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |     // #endif
 | ||||||
|  |     // #ifndef MP-WEIXIN
 | ||||||
|  |     uni.chooseImage({ | ||||||
|  |       count: 1, | ||||||
|  |       success: (res) => { | ||||||
|  |         loading.value = true | ||||||
|  |         const tempFilePath = res.tempFilePaths[0] | ||||||
|  |         uploadFile<T>({ tempFilePath, formData, data, error, loading }) | ||||||
|  |       }, | ||||||
|  |       fail: (err) => { | ||||||
|  |         console.error('uni.chooseImage err->', err) | ||||||
|  |         error.value = true | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |     // #endif
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return { loading, error, data, run } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function uploadFile<T>({ tempFilePath, formData, data, error, loading }) { | ||||||
|  |   uni.uploadFile({ | ||||||
|  |     url: VITE_UPLOAD_BASEURL, | ||||||
|  |     filePath: tempFilePath, | ||||||
|  |     name: 'file', | ||||||
|  |     formData, | ||||||
|  |     success: (uploadFileRes) => { | ||||||
|  |       data.value = uploadFileRes.data as T | ||||||
|  |     }, | ||||||
|  |     fail: (err) => { | ||||||
|  |       console.error('uni.uploadFile err->', err) | ||||||
|  |       error.value = true | ||||||
|  |     }, | ||||||
|  |     complete: () => { | ||||||
|  |       loading.value = false | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								src/interceptors/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | |||||||
|  | export { routeInterceptor } from './route' | ||||||
|  | export { prototypeInterceptor } from './prototype' | ||||||
							
								
								
									
										13
									
								
								src/interceptors/prototype.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | |||||||
|  | export const prototypeInterceptor = { | ||||||
|  |   install() { | ||||||
|  |     // 解决低版本手机不识别 array.at() 导致运行报错的问题
 | ||||||
|  |     if (typeof Array.prototype.at !== 'function') { | ||||||
|  |       // eslint-disable-next-line no-extend-native
 | ||||||
|  |       Array.prototype.at = function (index: number) { | ||||||
|  |         if (index < 0) return this[this.length + index] | ||||||
|  |         if (index >= this.length) return undefined | ||||||
|  |         return this[index] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								src/interceptors/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,54 @@ | |||||||
|  | /** | ||||||
|  |  * by 菲鸽 on 2024-03-06 | ||||||
|  |  * 路由拦截,通常也是登录拦截 | ||||||
|  |  * 可以设置路由白名单,或者黑名单,看业务需要选哪一个 | ||||||
|  |  * 我这里应为大部分都可以随便进入,所以使用黑名单 | ||||||
|  |  */ | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils' | ||||||
|  | 
 | ||||||
|  | // TODO Check
 | ||||||
|  | const loginRoute = '/pages/login/index' | ||||||
|  | 
 | ||||||
|  | const isLogined = () => { | ||||||
|  |   const userStore = useUserStore() | ||||||
|  |   return userStore.isLogined | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const isDev = import.meta.env.DEV | ||||||
|  | 
 | ||||||
|  | // 黑名单登录拦截器 - (适用于大部分页面不需要登录,少部分页面需要登录)
 | ||||||
|  | const navigateToInterceptor = { | ||||||
|  |   // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
 | ||||||
|  |   invoke({ url }: { url: string }) { | ||||||
|  |     // console.log(url) // /pages/route-interceptor/index?name=feige&age=30
 | ||||||
|  |     const path = url.split('?')[0] | ||||||
|  |     let needLoginPages: string[] = [] | ||||||
|  |     // 为了防止开发时出现BUG,这里每次都获取一下。生产环境可以移到函数外,性能更好
 | ||||||
|  |     if (isDev) { | ||||||
|  |       needLoginPages = getNeedLoginPages() | ||||||
|  |     } else { | ||||||
|  |       needLoginPages = _needLoginPages | ||||||
|  |     } | ||||||
|  |     const isNeedLogin = needLoginPages.includes(path) | ||||||
|  |     if (!isNeedLogin) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |     const hasLogin = isLogined() | ||||||
|  |     if (hasLogin) { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |     const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}` | ||||||
|  |     uni.navigateTo({ url: redirectRoute }) | ||||||
|  |     return false | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const routeInterceptor = { | ||||||
|  |   install() { | ||||||
|  |     uni.addInterceptor('navigateTo', navigateToInterceptor) | ||||||
|  |     uni.addInterceptor('reLaunch', navigateToInterceptor) | ||||||
|  |     uni.addInterceptor('redirectTo', navigateToInterceptor) | ||||||
|  |     uni.addInterceptor('switchTab', navigateToInterceptor) | ||||||
|  |   }, | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								src/layouts/default.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,17 @@ | |||||||
|  | <template> | ||||||
|  |   <wd-config-provider :themeVars="themeVars"> | ||||||
|  |     <slot /> | ||||||
|  |     <wd-toast /> | ||||||
|  |     <wd-message-box /> | ||||||
|  |   </wd-config-provider> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ConfigProviderThemeVars } from 'wot-design-uni' | ||||||
|  | 
 | ||||||
|  | const themeVars: ConfigProviderThemeVars = { | ||||||
|  |   // colorTheme: 'red', | ||||||
|  |   // buttonPrimaryBgColor: '#07c160', | ||||||
|  |   // buttonPrimaryColor: '#07c160', | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										17
									
								
								src/layouts/demo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,17 @@ | |||||||
|  | <template> | ||||||
|  |   <wd-config-provider :themeVars="themeVars"> | ||||||
|  |     <slot /> | ||||||
|  |     <wd-toast /> | ||||||
|  |     <wd-message-box /> | ||||||
|  |   </wd-config-provider> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ConfigProviderThemeVars } from 'wot-design-uni' | ||||||
|  | 
 | ||||||
|  | const themeVars: ConfigProviderThemeVars = { | ||||||
|  |   // colorTheme: 'red', | ||||||
|  |   // buttonPrimaryBgColor: '#07c160', | ||||||
|  |   // buttonPrimaryColor: '#07c160', | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										20
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | import '@/style/index.scss' | ||||||
|  | import { VueQueryPlugin } from '@tanstack/vue-query' | ||||||
|  | import 'virtual:uno.css' | ||||||
|  | import { createSSRApp } from 'vue' | ||||||
|  | 
 | ||||||
|  | import App from './App.vue' | ||||||
|  | import { prototypeInterceptor, routeInterceptor } from './interceptors' | ||||||
|  | import store from './store' | ||||||
|  | 
 | ||||||
|  | export function createApp() { | ||||||
|  |   const app = createSSRApp(App) | ||||||
|  |   app.use(store) | ||||||
|  |   app.use(routeInterceptor) | ||||||
|  |   app.use(prototypeInterceptor) | ||||||
|  |   app.use(VueQueryPlugin) | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     app, | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										111
									
								
								src/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,111 @@ | |||||||
|  | { | ||||||
|  |   "name": "unibest", | ||||||
|  |   "appid": "H57F2ACE4", | ||||||
|  |   "description": "", | ||||||
|  |   "versionName": "1.0.0", | ||||||
|  |   "versionCode": "100", | ||||||
|  |   "transformPx": false, | ||||||
|  |   "app-plus": { | ||||||
|  |     "usingComponents": true, | ||||||
|  |     "nvueStyleCompiler": "uni-app", | ||||||
|  |     "compilerVersion": 3, | ||||||
|  |     "splashscreen": { | ||||||
|  |       "alwaysShowBeforeRender": true, | ||||||
|  |       "waiting": true, | ||||||
|  |       "autoclose": true, | ||||||
|  |       "delay": 0 | ||||||
|  |     }, | ||||||
|  |     "modules": {}, | ||||||
|  |     "distribute": { | ||||||
|  |       "android": { | ||||||
|  |         "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\"/>" | ||||||
|  |         ], | ||||||
|  |         "minSdkVersion": 30, | ||||||
|  |         "targetSdkVersion": 30, | ||||||
|  |         "abiFilters": [ | ||||||
|  |           "armeabi-v7a", | ||||||
|  |           "arm64-v8a" | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "ios": {}, | ||||||
|  |       "sdkConfigs": {}, | ||||||
|  |       "icons": { | ||||||
|  |         "android": { | ||||||
|  |           "hdpi": "static/app/icons/72x72.png", | ||||||
|  |           "xhdpi": "static/app/icons/96x96.png", | ||||||
|  |           "xxhdpi": "static/app/icons/144x144.png", | ||||||
|  |           "xxxhdpi": "static/app/icons/192x192.png" | ||||||
|  |         }, | ||||||
|  |         "ios": { | ||||||
|  |           "appstore": "static/app/icons/1024x1024.png", | ||||||
|  |           "ipad": { | ||||||
|  |             "app": "static/app/icons/76x76.png", | ||||||
|  |             "app@2x": "static/app/icons/152x152.png", | ||||||
|  |             "notification": "static/app/icons/20x20.png", | ||||||
|  |             "notification@2x": "static/app/icons/40x40.png", | ||||||
|  |             "proapp@2x": "static/app/icons/167x167.png", | ||||||
|  |             "settings": "static/app/icons/29x29.png", | ||||||
|  |             "settings@2x": "static/app/icons/58x58.png", | ||||||
|  |             "spotlight": "static/app/icons/40x40.png", | ||||||
|  |             "spotlight@2x": "static/app/icons/80x80.png" | ||||||
|  |           }, | ||||||
|  |           "iphone": { | ||||||
|  |             "app@2x": "static/app/icons/120x120.png", | ||||||
|  |             "app@3x": "static/app/icons/180x180.png", | ||||||
|  |             "notification@2x": "static/app/icons/40x40.png", | ||||||
|  |             "notification@3x": "static/app/icons/60x60.png", | ||||||
|  |             "settings@2x": "static/app/icons/58x58.png", | ||||||
|  |             "settings@3x": "static/app/icons/87x87.png", | ||||||
|  |             "spotlight@2x": "static/app/icons/80x80.png", | ||||||
|  |             "spotlight@3x": "static/app/icons/120x120.png" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "compatible": { | ||||||
|  |       "ignoreVersion": true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "quickapp": {}, | ||||||
|  |   "mp-weixin": { | ||||||
|  |     "appid": "wx25fb5794b1c3026f", | ||||||
|  |     "setting": { | ||||||
|  |       "urlCheck": false | ||||||
|  |     }, | ||||||
|  |     "usingComponents": true | ||||||
|  |   }, | ||||||
|  |   "mp-alipay": { | ||||||
|  |     "usingComponents": true, | ||||||
|  |     "styleIsolation": "shared" | ||||||
|  |   }, | ||||||
|  |   "mp-baidu": { | ||||||
|  |     "usingComponents": true | ||||||
|  |   }, | ||||||
|  |   "mp-toutiao": { | ||||||
|  |     "usingComponents": true | ||||||
|  |   }, | ||||||
|  |   "uniStatistics": { | ||||||
|  |     "enable": false | ||||||
|  |   }, | ||||||
|  |   "vueVersion": "3", | ||||||
|  |   "h5": { | ||||||
|  |     "router": { | ||||||
|  |       "base": "/" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/pages-sub/demo/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   style: { navigationBarTitleText: '分包页面 标题' }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="text-center"> | ||||||
|  |     <view class="m-8">http://localhost:9000/#/pages-sub/demo/index</view> | ||||||
|  |     <view class="text-green-500">分包页面demo</view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | // code here | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | // | ||||||
|  | </style> | ||||||
							
								
								
									
										209
									
								
								src/pages.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,209 @@ | |||||||
|  | { | ||||||
|  |   "globalStyle": { | ||||||
|  |     "navigationStyle": "default", | ||||||
|  |     "navigationBarTitleText": "unibest", | ||||||
|  |     "navigationBarBackgroundColor": "#f8f8f8", | ||||||
|  |     "navigationBarTextStyle": "black", | ||||||
|  |     "backgroundColor": "#FFFFFF" | ||||||
|  |   }, | ||||||
|  |   "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": "#222222", | ||||||
|  |     "selectedColor": "#000000", | ||||||
|  |     "backgroundColor": "#ffffff", | ||||||
|  |     "borderStyle": "black", | ||||||
|  |     "height": "50px", | ||||||
|  |     "fontSize": "10px", | ||||||
|  |     "iconWidth": "20px", | ||||||
|  |     "spacing": "3px", | ||||||
|  |     "list": [ | ||||||
|  |       { | ||||||
|  |         "iconPath": "static/tabbar/edit.svg", | ||||||
|  |         "selectedIconPath": "static/tabbar/editHL.svg", | ||||||
|  |         "pagePath": "pages/index/index", | ||||||
|  |         "text": "报名" | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "iconPath": "static/tabbar/document.svg", | ||||||
|  |         "selectedIconPath": "static/tabbar/documentHL.svg", | ||||||
|  |         "pagePath": "pages/fate/fate", | ||||||
|  |         "text": "考试" | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "pages": [ | ||||||
|  |     { | ||||||
|  |       "path": "pages/index/index", | ||||||
|  |       "type": "home", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/activity/detail", | ||||||
|  |       "type": "page" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/activity/index", | ||||||
|  |       "type": "page" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/agreement/privacy", | ||||||
|  |       "type": "page", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "隐私政策" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/agreement/user", | ||||||
|  |       "type": "page", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "用户协议" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/detail/index", | ||||||
|  |       "type": "page", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "用户详情" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/fate/fate", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "红娘" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/login/index", | ||||||
|  |       "type": "page", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/activities", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "我的活动" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/complaint", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "发起投诉" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/feedback", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "意见反馈" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/invite", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "邀请好友" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/inviter", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "我的邀请人" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/matches", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "我的配对" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/my", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "我的" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/profile", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "我的资料" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/recharge", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "充值记录" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/refund", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "退款记录" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/spend-detail", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "消费记录" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/spend", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "消费记录" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/subordinate", | ||||||
|  |       "type": "page" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/my/user-info", | ||||||
|  |       "type": "page", | ||||||
|  |       "layout": "default", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "用户信息" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "path": "pages/recommend/index", | ||||||
|  |       "type": "page", | ||||||
|  |       "style": { | ||||||
|  |         "navigationBarTitleText": "推荐列表" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "subPackages": [] | ||||||
|  | } | ||||||
							
								
								
									
										173
									
								
								src/pages/activity/detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,173 @@ | |||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 顶部导航 --> | ||||||
|  |     <view class="sticky top-0 z-10 bg-white shadow-sm"> | ||||||
|  |       <view class="flex items-center justify-between px-4 py-3"> | ||||||
|  |         <view class="flex items-center space-x-2"> | ||||||
|  |           <text class="iconfont icon-arrow-left text-xl" @tap="goBack"></text> | ||||||
|  |           <text class="text-lg font-bold">活动详情</text> | ||||||
|  |         </view> | ||||||
|  |         <view class="flex items-center space-x-4"> | ||||||
|  |           <text class="iconfont icon-share text-xl"></text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 活动详情内容 --> | ||||||
|  |     <scroll-view class="h-screen" scroll-y> | ||||||
|  |       <!-- 活动封面 --> | ||||||
|  |       <view class="relative"> | ||||||
|  |         <image | ||||||
|  |           :src="activityDetail.eventAvatar" | ||||||
|  |           mode="aspectFill" | ||||||
|  |           class="w-full h-80 object-cover" | ||||||
|  |         /> | ||||||
|  |         <view class="absolute top-3 right-3 px-3 py-1 bg-white/90 rounded-full backdrop-blur-sm"> | ||||||
|  |           <text class="text-sm text-primary font-medium">¥{{ activityDetail.eventPrice }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view | ||||||
|  |           :class="[ | ||||||
|  |             'absolute top-3 left-3 px-3 py-1 rounded-full text-sm font-medium', | ||||||
|  |             isOngoing ? 'bg-green-500/90 text-white' : 'bg-gray-500/90 text-white', | ||||||
|  |           ]" | ||||||
|  |         > | ||||||
|  |           {{ isOngoing ? '报名中' : '已结束' }} | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 活动信息 --> | ||||||
|  |       <view class="p-4 bg-white"> | ||||||
|  |         <text class="block text-xl font-bold mb-2">{{ activityDetail.eventTitle }}</text> | ||||||
|  |         <view class="flex items-center text-sm text-gray-500 mb-2"> | ||||||
|  |           <text class="iconfont icon-time mr-1"></text> | ||||||
|  |           <text>截止时间: {{ activityDetail.endTime }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view class="flex items-center text-sm text-gray-500 mb-4"> | ||||||
|  |           <text class="iconfont icon-location mr-1"></text> | ||||||
|  |           <text>{{ activityDetail.eventPlace }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view class="flex items-center justify-between"> | ||||||
|  |           <view class="flex items-center"> | ||||||
|  |             <text class="text-sm text-gray-500"> | ||||||
|  |               已有 {{ activityDetail.eventNumber }}/{{ activityDetail.eventLimitNumber }} 人报名 | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |           <button | ||||||
|  |             class="px-8 py-2.5 text-white text-sm rounded-full flex items-center justify-center shadow-md transition-all duration-300" | ||||||
|  |             :class="{ | ||||||
|  |               'bg-gradient-to-r from-pink-500 to-purple-500 hover:from-pink-600 hover:to-purple-600': | ||||||
|  |                 isOngoing, | ||||||
|  |               'bg-gray-400 cursor-not-allowed': !isOngoing, | ||||||
|  |             }" | ||||||
|  |             :disabled="!isOngoing" | ||||||
|  |             @tap="handleSignUp" | ||||||
|  |           > | ||||||
|  |             <text class="iconfont icon-add mr-1" v-if="isOngoing"></text> | ||||||
|  |             {{ isOngoing ? '立即报名' : '已结束' }} | ||||||
|  |           </button> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 活动详情 --> | ||||||
|  |       <view class="p-4 mt-4 bg-white"> | ||||||
|  |         <text class="block text-lg font-bold mb-4">活动详情</text> | ||||||
|  |         <rich-text :nodes="activityDetail.eventContent"></rich-text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 活动信息补充 --> | ||||||
|  |       <view class="p-4 mt-4 bg-white"> | ||||||
|  |         <text class="block text-lg font-bold mb-4">活动信息</text> | ||||||
|  |         <view class="space-y-2"> | ||||||
|  |           <view class="flex justify-between"> | ||||||
|  |             <text class="text-gray-500">发起时间</text> | ||||||
|  |             <text>{{ activityDetail.createTime }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between"> | ||||||
|  |             <text class="text-gray-500">有效天数</text> | ||||||
|  |             <text>{{ activityDetail.effectiveTime }}天</text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </scroll-view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed, onMounted } from 'vue' | ||||||
|  | import { onLoad } from '@dcloudio/uni-app' | ||||||
|  | import type { EventItem } from '@/service/event/type' | ||||||
|  | import { joinEventAPI } from '@/service/event' | ||||||
|  | 
 | ||||||
|  | onLoad(() => { | ||||||
|  |   // 页面加载时执行的逻辑 | ||||||
|  |   const instance: any = getCurrentInstance().proxy | ||||||
|  |   const eventChannel = instance.getOpenerEventChannel() | ||||||
|  |   eventChannel.on('item', function (data) { | ||||||
|  |     const { item } = data | ||||||
|  |     activityDetail.value = item | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 活动详情数据 | ||||||
|  | const activityDetail = ref<EventItem>({ | ||||||
|  |   id: '', | ||||||
|  |   eventTitle: '', | ||||||
|  |   eventContent: '', | ||||||
|  |   eventAvatar: '', | ||||||
|  |   eventPrice: '', | ||||||
|  |   eventPlace: '', | ||||||
|  |   createTime: '', | ||||||
|  |   endTime: '', | ||||||
|  |   effectiveTime: '', | ||||||
|  |   eventNumber: '', | ||||||
|  |   eventLimitNumber: '', | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 计算活动是否进行中 | ||||||
|  | const isOngoing = computed(() => { | ||||||
|  |   if (!activityDetail.value.endTime) return false | ||||||
|  |   return new Date(activityDetail.value.endTime) > new Date() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 加载状态 | ||||||
|  | const isLoading = ref(false) | ||||||
|  | const hasMore = ref(true) | ||||||
|  | 
 | ||||||
|  | // 报名活动 | ||||||
|  | const handleSignUp = async () => { | ||||||
|  |   try { | ||||||
|  |     uni.showModal({ | ||||||
|  |       title: '报名确认', | ||||||
|  |       content: '确定要报名参加此活动吗?', | ||||||
|  |       success: async (res) => { | ||||||
|  |         if (res.confirm) { | ||||||
|  |           const result = await joinEventAPI(activityDetail.value.id) | ||||||
|  |           if (result.code === 500) { | ||||||
|  |             uni.showModal({ | ||||||
|  |               title: '报名失败', | ||||||
|  |               content: result.message || '报名失败,请稍后重试', | ||||||
|  |               showCancel: false, | ||||||
|  |             }) | ||||||
|  |             return | ||||||
|  |           } | ||||||
|  |           uni.showToast({ | ||||||
|  |             title: '报名成功', | ||||||
|  |             icon: 'success', | ||||||
|  |           }) | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showModal({ | ||||||
|  |       title: '报名失败', | ||||||
|  |       content: '系统异常,请稍后重试', | ||||||
|  |       showCancel: false, | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 返回上一页 | ||||||
|  | const goBack = () => { | ||||||
|  |   uni.navigateBack() | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										236
									
								
								src/pages/activity/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,236 @@ | |||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 顶部导航 --> | ||||||
|  |     <view class="sticky top-0 z-10 bg-white shadow-sm"> | ||||||
|  |       <view class="flex items-center justify-between px-4 py-3"> | ||||||
|  |         <view class="flex items-center space-x-2"> | ||||||
|  |           <text class="iconfont icon-arrow-left text-xl" @tap="goBack"></text> | ||||||
|  |           <text class="text-2xl font-bold">相亲活动</text> | ||||||
|  |         </view> | ||||||
|  |         <view class="flex items-center space-x-4"> | ||||||
|  |           <text class="iconfont icon-search text-xl"></text> | ||||||
|  |           <text class="iconfont icon-filter text-xl"></text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |       <!-- 筛选器 --> | ||||||
|  |       <view class="flex px-4 pb-3 space-x-4"> | ||||||
|  |         <view | ||||||
|  |           :class="[ | ||||||
|  |             'px-4 py-2 rounded-full text-sm transition-all duration-300', | ||||||
|  |             currentFilter === '1' ? 'bg-primary/90 shadow-lg shadow-primary/20' : 'bg-gray-100', | ||||||
|  |           ]" | ||||||
|  |           @tap="changeFilter('1')" | ||||||
|  |         > | ||||||
|  |           <text :class="currentFilter === '1' ? 'text-gray-600' : 'text-gray-600'">全部</text> | ||||||
|  |         </view> | ||||||
|  |         <view | ||||||
|  |           :class="[ | ||||||
|  |             'px-4 py-2 rounded-full text-sm transition-all duration-300', | ||||||
|  |             currentFilter === '2' ? 'bg-primary/90 shadow-lg shadow-primary/20' : 'bg-gray-100', | ||||||
|  |           ]" | ||||||
|  |           @tap="changeFilter('2')" | ||||||
|  |         > | ||||||
|  |           <text :class="currentFilter === '2' ? 'text-gray-600' : 'text-gray-600'">报名中</text> | ||||||
|  |         </view> | ||||||
|  |         <view | ||||||
|  |           :class="[ | ||||||
|  |             'px-4 py-2 rounded-full text-sm transition-all duration-300', | ||||||
|  |             currentFilter === '3' ? 'bg-primary/90 shadow-lg shadow-primary/20' : 'bg-gray-100', | ||||||
|  |           ]" | ||||||
|  |           @tap="changeFilter('3')" | ||||||
|  |         > | ||||||
|  |           <text :class="currentFilter === '3' ? 'text-gray-600' : 'text-gray-600'">已结束</text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 活动列表 --> | ||||||
|  |     <view class="px-4 pt-2 pb-8 overflow-hidden"> | ||||||
|  |       <scroll-view | ||||||
|  |         class="space-y-4" | ||||||
|  |         scroll-y | ||||||
|  |         @scrolltolower="loadMore" | ||||||
|  |         refresher-enabled | ||||||
|  |         :refresher-triggered="loading" | ||||||
|  |         @refresherrefresh="onRefresh" | ||||||
|  |       > | ||||||
|  |         <!-- 加载状态 --> | ||||||
|  |         <view v-if="loading" class="flex justify-center items-center py-8"> | ||||||
|  |           <text class="text-gray-500">加载中...</text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 错误状态 --> | ||||||
|  |         <view v-else-if="error" class="flex justify-center items-center py-8"> | ||||||
|  |           <text class="text-red-500">{{ error }}</text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 空状态 --> | ||||||
|  |         <view v-else-if="!activities.length" class="flex justify-center items-center py-8"> | ||||||
|  |           <text class="text-gray-500">暂无活动</text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view v-else class="space-y-4"> | ||||||
|  |           <view | ||||||
|  |             class="bg-white rounded-2xl shadow-lg overflow-hidden transform transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]" | ||||||
|  |             v-for="item in activities" | ||||||
|  |             :key="item.id" | ||||||
|  |             @tap="viewActivity(item)" | ||||||
|  |           > | ||||||
|  |             <view class="relative"> | ||||||
|  |               <image :src="item.eventAvatar" mode="aspectFill" class="w-full h-40 object-cover" /> | ||||||
|  |               <view | ||||||
|  |                 class="absolute top-3 right-3 px-3 py-1 bg-white/90 rounded-full backdrop-blur-sm" | ||||||
|  |               > | ||||||
|  |                 <text class="text-sm text-primary font-medium">¥{{ item.eventPrice }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view | ||||||
|  |                 :class="[ | ||||||
|  |                   'absolute top-3 left-3 px-3 py-1 rounded-full text-sm font-medium', | ||||||
|  |                   isOngoing(item) ? 'bg-green-500/90 text-white' : 'bg-gray-500/90 text-white', | ||||||
|  |                 ]" | ||||||
|  |               > | ||||||
|  |                 {{ isOngoing(item) ? '报名中' : '已结束' }} | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="p-4"> | ||||||
|  |               <text class="block text-lg font-bold mb-2">{{ item.eventTitle }}</text> | ||||||
|  |               <view class="flex items-center text-sm text-gray-500"> | ||||||
|  |                 <text class="iconfont icon-time mr-1"></text> | ||||||
|  |                 <text>截止时间: {{ formatDate(item.endTime) }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view class="mt-3 flex items-center justify-between"> | ||||||
|  |                 <view class="flex items-center"> | ||||||
|  |                   <view class="flex -space-x-2"> | ||||||
|  |                     <image | ||||||
|  |                       v-for="(avatar, index) in getRandomAvatars(3)" | ||||||
|  |                       :key="index" | ||||||
|  |                       :src="avatar" | ||||||
|  |                       class="w-8 h-8 rounded-full border-2 border-white" | ||||||
|  |                     /> | ||||||
|  |                   </view> | ||||||
|  |                   <text class="ml-2 text-sm text-gray-500">已有 {{ item.eventNumber }} 人报名</text> | ||||||
|  |                 </view> | ||||||
|  |                 <view | ||||||
|  |                   class="px-4 py-2 bg-primary text-white text-sm rounded-full" | ||||||
|  |                   @tap.stop="handleJoin(item)" | ||||||
|  |                 > | ||||||
|  |                   立即报名 | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 加载完成提示 --> | ||||||
|  |         <view | ||||||
|  |           v-if="!loading && activities.length > 0 && activities.length >= total" | ||||||
|  |           class="py-4 text-center text-gray-500" | ||||||
|  |         > | ||||||
|  |           <text>没有更多数据了</text> | ||||||
|  |         </view> | ||||||
|  |       </scroll-view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { joinEventAPI } from '@/service/event' | ||||||
|  | import type { EventItem } from '@/service/event/type' | ||||||
|  | import { formatDate } from '@/utils/date' | ||||||
|  | 
 | ||||||
|  | // 状态 | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | const activities = ref<EventItem[]>([]) | ||||||
|  | const currentFilter = ref<'1' | '2' | '3'>('1') // 1-全部 2-报名中 3-已结束 | ||||||
|  | const total = ref(0) | ||||||
|  | 
 | ||||||
|  | // 获取活动列表 | ||||||
|  | const fetchActivities = async (isRefresh = false) => { | ||||||
|  |   if (loading.value) return | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |   } catch (err) { | ||||||
|  |     error.value = '网络错误,请稍后重试' | ||||||
|  |     console.error('获取活动列表失败:', err) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 判断活动是否进行中 | ||||||
|  | const isOngoing = (item: EventItem) => { | ||||||
|  |   const now = new Date().getTime() | ||||||
|  |   const endTime = new Date(item.endTime).getTime() | ||||||
|  |   return endTime > now | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 加载更多 | ||||||
|  | const loadMore = () => { | ||||||
|  |   if (activities.value.length >= total.value) return | ||||||
|  |   fetchActivities() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 下拉刷新 | ||||||
|  | const onRefresh = () => { | ||||||
|  |   fetchActivities(true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 切换筛选 | ||||||
|  | const changeFilter = (filter: '1' | '2' | '3') => { | ||||||
|  |   currentFilter.value = filter | ||||||
|  |   fetchActivities(true) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 查看活动详情 | ||||||
|  | const viewActivity = (item: EventItem) => { | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: `/pages/activity/detail?id=${item.id}`, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 处理报名 | ||||||
|  | const handleJoin = async (item: EventItem) => { | ||||||
|  |   try { | ||||||
|  |     const res = await joinEventAPI(item.id) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '报名成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |       // 刷新列表 | ||||||
|  |       fetchActivities(true) | ||||||
|  |     } else { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: res.message || '报名失败', | ||||||
|  |         icon: 'error', | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (err) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '网络错误,请稍后重试', | ||||||
|  |       icon: 'error', | ||||||
|  |     }) | ||||||
|  |     console.error('报名失败:', err) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const goBack = () => { | ||||||
|  |   uni.navigateBack() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取随机头像(实际项目中应该使用真实数据) | ||||||
|  | const getRandomAvatars = (count: number) => { | ||||||
|  |   return Array(count).fill( | ||||||
|  |     'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg', | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 初始化 | ||||||
|  | onMounted(() => { | ||||||
|  |   fetchActivities() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										103
									
								
								src/pages/agreement/privacy.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,103 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '隐私政策', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-white p-4"> | ||||||
|  |     <view class="prose prose-sm max-w-none"> | ||||||
|  |       <view class="text-center mb-8"> | ||||||
|  |         <text class="text-xl font-bold">隐私政策</text> | ||||||
|  |         <text class="block text-sm text-gray-500 mt-2">最后更新日期:2024年3月</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <view class="space-y-6"> | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">1. 引言</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             我们非常重视您的隐私保护。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息。请您在使用我们的服务前,仔细阅读并了解本隐私政策的全部内容。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">2. 信息收集</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             2.1 我们收集的信息包括: - 基本信息:昵称、头像、性别、年龄等 - | ||||||
|  |             位置信息:您的位置信息(需要您授权) - 设备信息:设备型号、操作系统版本、设备设置等 - | ||||||
|  |             日志信息:使用时间、使用时长、操作记录等 2.2 我们通过以下方式收集信息: - | ||||||
|  |             您主动提供的信息 - 在您使用服务时产生的信息 - 经您授权从第三方获取的信息 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">3. 信息使用</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             3.1 我们使用收集的信息用于: - 提供、维护和改进我们的服务 - 开发新的服务和功能 - | ||||||
|  |             了解用户如何使用我们的服务 - 防止欺诈和滥用 - 向您推送可能感兴趣的内容 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">4. 信息共享</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             4.1 我们不会向第三方出售您的个人信息。 4.2 在以下情况下,我们可能会共享您的信息: - | ||||||
|  |             获得您的明确同意 - 法律法规要求 - 维护我们的合法权益 - 与我们的关联公司共享 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">5. 信息安全</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             5.1 我们采取各种安全措施保护您的个人信息: - 数据加密存储 - 访问控制机制 - 安全审计机制 | ||||||
|  |             - 应急响应机制 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">6. 信息存储</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             6.1 我们会将您的信息存储在中国境内的服务器上。 6.2 | ||||||
|  |             我们会根据法律法规的要求,在必要的时间内保存您的信息。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">7. 您的权利</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             您对您的个人信息享有以下权利: - 访问和查看您的个人信息 - 更正或更新您的个人信息 - | ||||||
|  |             删除您的个人信息 - 撤回您的授权同意 - 注销您的账号 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">8. 未成年人保护</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             8.1 我们建议未成年人在监护人的指导下使用我们的服务。 8.2 | ||||||
|  |             我们会采取特殊措施保护未成年人的个人信息。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">9. 政策更新</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             我们可能会不时更新本隐私政策。当我们更新隐私政策时,我们会在小程序内发布更新后的版本,并更新"最后更新日期"。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">10. 联系我们</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             如果您对本隐私政策有任何疑问、意见或建议,请通过小程序内的客服功能与我们联系。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | // 无需额外逻辑 | ||||||
|  | </script> | ||||||
							
								
								
									
										100
									
								
								src/pages/agreement/user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,100 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '用户协议', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-white p-4"> | ||||||
|  |     <view class="prose prose-sm max-w-none"> | ||||||
|  |       <view class="text-center mb-8"> | ||||||
|  |         <text class="text-xl font-bold">用户协议</text> | ||||||
|  |         <text class="block text-sm text-gray-500 mt-2">最后更新日期:2024年3月</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <view class="space-y-6"> | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">1. 协议的范围</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             欢迎您使用我们的相亲小程序。本协议是您与我们之间关于使用本小程序服务所订立的协议。请您仔细阅读本协议的全部内容。如果您不同意本协议的任何内容,您应立即停止使用本小程序。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">2. 服务内容</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             本小程序是一个相亲交友平台,为用户提供信息展示、交流互动等服务。我们会不断改进服务质量,但不对服务的及时性、安全性、准确性做出保证。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">3. 用户注册与账号</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             3.1 | ||||||
|  |             您承诺以真实身份注册成为本小程序的用户,并保证所提供的个人资料真实、准确、完整、合法有效。 | ||||||
|  |             3.2 您应当妥善保管账号和密码,对账号下的所有行为负责。 3.3 | ||||||
|  |             如发现任何未经授权使用您账号的情况,应立即通知我们。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">4. 用户行为规范</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             4.1 您在使用本小程序时,必须遵守中华人民共和国相关法律法规。 4.2 | ||||||
|  |             您不得利用本小程序从事违法违规活动,包括但不限于: - 发布虚假信息 - 侵犯他人知识产权 - | ||||||
|  |             传播色情、暴力等不良信息 - 从事诈骗等违法活动 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">5. 隐私保护</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             我们重视对您个人信息的保护,具体隐私政策请参见《隐私政策》。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">6. 知识产权</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             本小程序的所有内容,包括但不限于文字、图片、音频、视频、软件、程序、版面设计等,均受著作权法和国际著作权条约以及其他知识产权法律法规的保护。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">7. 免责声明</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             7.1 对于因不可抗力或我们无法控制的原因造成的服务中断或其它缺陷,我们不承担任何责任。 7.2 | ||||||
|  |             您理解并同意,我们不对您因使用本小程序而遭受的任何直接或间接损失负责。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">8. 协议修改</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             我们有权在必要时修改本协议条款。您可以在本小程序中查阅最新版本的协议条款。本协议条款变更后,如果您继续使用本小程序,即视为您已接受修改后的协议。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">9. 法律适用</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             本协议的订立、执行和解释及争议的解决均应适用中华人民共和国法律。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <view> | ||||||
|  |           <text class="font-bold block mb-2">10. 联系我们</text> | ||||||
|  |           <text class="text-gray-600 block"> | ||||||
|  |             如果您对本协议有任何疑问,请通过小程序内的客服功能与我们联系。 | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | // 无需额外逻辑 | ||||||
|  | </script> | ||||||
							
								
								
									
										384
									
								
								src/pages/detail/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,384 @@ | |||||||
|  | <!-- 用户详情页面 --> | ||||||
|  | <route lang="json5"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '用户详情', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 用户基本信息卡片 --> | ||||||
|  |     <view class="bg-white rounded-b-3xl shadow-sm"> | ||||||
|  |       <view class="p-6"> | ||||||
|  |         <view class="flex items-center"> | ||||||
|  |           <image | ||||||
|  |             :src="userInfo.avatar" | ||||||
|  |             mode="aspectFill" | ||||||
|  |             class="w-24 h-24 rounded-xl object-cover" | ||||||
|  |           /> | ||||||
|  |           <view class="ml-4 flex-1"> | ||||||
|  |             <view class="flex items-center justify-between"> | ||||||
|  |               <view class="flex items-center"> | ||||||
|  |                 <text class="text-xl font-semibold text-gray-900">{{ userInfo.nickName }}</text> | ||||||
|  |                 <text class="ml-2 text-sm text-gray-500"> | ||||||
|  |                   {{ calculateAge(userInfo.birthday) }}岁 | ||||||
|  |                 </text> | ||||||
|  |               </view> | ||||||
|  |               <view | ||||||
|  |                 v-if="!isUnlocked" | ||||||
|  |                 class="px-4 py-1.5 bg-primary text-sm rounded-full flex items-center" | ||||||
|  |                 :class="{ 'opacity-50': isUnlocking }" | ||||||
|  |                 @tap="handleUnlock" | ||||||
|  |               > | ||||||
|  |                 <text v-if="isUnlocking">解锁中...</text> | ||||||
|  |                 <text v-else>解锁</text> | ||||||
|  |               </view> | ||||||
|  |               <view | ||||||
|  |                 v-else | ||||||
|  |                 class="px-4 py-1.5 bg-red-500 text-sm text-white rounded-full flex items-center" | ||||||
|  |                 @tap="handleComplaint" | ||||||
|  |               > | ||||||
|  |                 <text>投诉</text> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="mt-2 flex items-center space-x-2"> | ||||||
|  |               <text class="text-sm text-gray-500">{{ userInfo.height }}cm</text> | ||||||
|  |               <view class="w-px h-3 bg-gray-200"></view> | ||||||
|  |               <text class="text-sm text-gray-500">{{ getEducationText(userInfo.education) }}</text> | ||||||
|  |               <view class="w-px h-3 bg-gray-200"></view> | ||||||
|  |               <text class="text-sm text-gray-500">{{ userInfo.workArea }}</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 详细信息列表 --> | ||||||
|  |     <view class="mt-4 bg-white rounded-3xl p-6"> | ||||||
|  |       <view class="space-y-6"> | ||||||
|  |         <!-- 基本信息 --> | ||||||
|  |         <view class="space-y-4"> | ||||||
|  |           <text class="text-lg font-semibold text-gray-900">基本信息</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">职业</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900">{{ userInfo.profession }}</text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">月收入</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900"> | ||||||
|  |                   {{ getMonthlyIncomeText(userInfo.monthlyIncome) }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">婚姻状况</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900"> | ||||||
|  |                   {{ getMaritalStatusText(userInfo.maritalStatus) }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">住房情况</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900"> | ||||||
|  |                   {{ getHousingStatusText(userInfo.housingStatus) }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 联系方式 --> | ||||||
|  |         <view class="space-y-4"> | ||||||
|  |           <text class="text-lg font-semibold text-gray-900">联系方式</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">微信号</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900" @tap="copyText(userInfo.wechat)"> | ||||||
|  |                   {{ userInfo.wechat }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">QQ</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900" @tap="copyText(userInfo.qq)"> | ||||||
|  |                   {{ userInfo.qq }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">邮箱</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900" @tap="copyText(userInfo.email)"> | ||||||
|  |                   {{ userInfo.email }} | ||||||
|  |                 </text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 其他信息 --> | ||||||
|  |         <view class="space-y-4"> | ||||||
|  |           <text class="text-lg font-semibold text-gray-900">其他信息</text> | ||||||
|  |           <view class="space-y-4"> | ||||||
|  |             <view class="space-y-1"> | ||||||
|  |               <text class="text-sm text-gray-500">个人介绍</text> | ||||||
|  |               <view class="relative"> | ||||||
|  |                 <text class="text-sm text-gray-900">{{ userInfo.selfIntroduction }}</text> | ||||||
|  |                 <view | ||||||
|  |                   v-if="!isUnlocked" | ||||||
|  |                   class="absolute inset-0 bg-white/80 backdrop-blur-sm flex items-center justify-center" | ||||||
|  |                 > | ||||||
|  |                   <text class="text-sm text-gray-500">点击解锁查看</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import type { LoginResponse } from '@/service/login/type' | ||||||
|  | import { checkIsLoveAPI, unlockUserAPI } from '@/service/love' | ||||||
|  | import { onLoad } from '@dcloudio/uni-app' | ||||||
|  | 
 | ||||||
|  | // 用户信息 | ||||||
|  | const userInfo = ref<LoginResponse>({} as LoginResponse) | ||||||
|  | const isUnlocked = ref(false) | ||||||
|  | const userId = ref('') | ||||||
|  | const isLoading = ref(false) | ||||||
|  | const isUnlocking = ref(false) | ||||||
|  | 
 | ||||||
|  | // 计算年龄 | ||||||
|  | const calculateAge = (birthday: string | undefined) => { | ||||||
|  |   if (!birthday) return '未知' | ||||||
|  |   const birthDate = new Date(birthday) | ||||||
|  |   const today = new Date() | ||||||
|  |   let age = today.getFullYear() - birthDate.getFullYear() | ||||||
|  |   const monthDiff = today.getMonth() - birthDate.getMonth() | ||||||
|  |   if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { | ||||||
|  |     age-- | ||||||
|  |   } | ||||||
|  |   return age | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取婚姻状况文本 | ||||||
|  | const getMaritalStatusText = (status: string | undefined) => { | ||||||
|  |   const statusMap = { | ||||||
|  |     '1': '未婚', | ||||||
|  |     '2': '离异', | ||||||
|  |     '3': '丧偶', | ||||||
|  |   } | ||||||
|  |   return status ? statusMap[status as keyof typeof statusMap] || '未知' : '未知' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取学历文本 | ||||||
|  | const getEducationText = (education: string | undefined) => { | ||||||
|  |   const educationMap = { | ||||||
|  |     '1': '初中', | ||||||
|  |     '2': '高中', | ||||||
|  |     '3': '大专', | ||||||
|  |     '4': '本科', | ||||||
|  |     '5': '硕士', | ||||||
|  |     '6': '博士', | ||||||
|  |   } | ||||||
|  |   return education ? educationMap[education as keyof typeof educationMap] || '未知' : '未知' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取月收入文本 | ||||||
|  | const getMonthlyIncomeText = (income: string | undefined) => { | ||||||
|  |   const incomeMap = { | ||||||
|  |     '1': '3000以下', | ||||||
|  |     '2': '3000~5000', | ||||||
|  |     '3': '5000~8000', | ||||||
|  |     '4': '8000~10000', | ||||||
|  |     '5': '10000~20000', | ||||||
|  |     '6': '20000以上', | ||||||
|  |   } | ||||||
|  |   return income ? incomeMap[income as keyof typeof incomeMap] || '未知' : '未知' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取住房情况文本 | ||||||
|  | const getHousingStatusText = (status: string | undefined) => { | ||||||
|  |   const statusMap = { | ||||||
|  |     '1': '已购房(有贷款)', | ||||||
|  |     '2': '已购房(无贷款)', | ||||||
|  |     '3': '有能力购房', | ||||||
|  |     '4': '无房', | ||||||
|  |     '5': '无房希望对方解决', | ||||||
|  |     '6': '无房希望双方解决', | ||||||
|  |     '7': '与父母同住', | ||||||
|  |     '8': '独自租房', | ||||||
|  |     '9': '与人合租', | ||||||
|  |     '10': '住单位房', | ||||||
|  |   } | ||||||
|  |   return status ? statusMap[status as keyof typeof statusMap] || '未知' : '未知' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取用户详细信息 | ||||||
|  | const getUserDetail = async () => { | ||||||
|  |   if (isLoading.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     isLoading.value = true | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取用户信息失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取用户信息失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     isLoading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 检查是否已解锁 | ||||||
|  | const checkIsUnlocked = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await checkIsLoveAPI(userId.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       isUnlocked.value = res.data | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('检查解锁状态失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '检查解锁状态失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 处理解锁 | ||||||
|  | const handleUnlock = async () => { | ||||||
|  |   if (isUnlocking.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     isUnlocking.value = true | ||||||
|  |     const res = await unlockUserAPI(userId.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       isUnlocked.value = true | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '解锁成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |       // 重新获取用户信息以显示联系方式 | ||||||
|  |       await getUserDetail() | ||||||
|  |     } else { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: (res.data as any) || '解锁失败', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('解锁失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '解锁失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     isUnlocking.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 复制文本到剪贴板 | ||||||
|  | const copyText = (text: string | undefined) => { | ||||||
|  |   if (!text) return | ||||||
|  |   if (!isUnlocked.value) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '请先解锁查看', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uni.setClipboardData({ | ||||||
|  |     data: text, | ||||||
|  |     success: () => { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '复制成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     fail: () => { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '复制失败', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 处理投诉 | ||||||
|  | const handleComplaint = () => { | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: `/pages/my/complaint?id=${userId.value}`, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onLoad((options) => { | ||||||
|  |   if (options.id) { | ||||||
|  |     userId.value = options.id | ||||||
|  |     getUserDetail() | ||||||
|  |     checkIsUnlocked() | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss"> | ||||||
|  | @import '@/style/iconfont.css'; | ||||||
|  | </style> | ||||||
							
								
								
									
										128
									
								
								src/pages/fate/components/MatchmakerRecommend.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,128 @@ | |||||||
|  | <template> | ||||||
|  |   <view class="mb-8"> | ||||||
|  |     <view class="flex justify-between items-center mb-4"> | ||||||
|  |       <view class="flex items-center"> | ||||||
|  |         <text class="text-2xl font-bold text-primary">红娘推荐</text> | ||||||
|  |         <view class="ml-2 px-2 py-1 bg-primary/10 rounded-full"> | ||||||
|  |           <text class="text-xs text-primary font-medium">专业认证</text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |       <view class="flex items-center"> | ||||||
|  |         <text class="i-carbon-task-complete text-base text-primary"></text> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |     <view class="relative"> | ||||||
|  |       <view | ||||||
|  |         class="p-5 bg-white rounded-xl shadow-sm hover:shadow-md transition-shadow duration-300" | ||||||
|  |       > | ||||||
|  |         <view class="flex items-start"> | ||||||
|  |           <view class="relative"> | ||||||
|  |             <image | ||||||
|  |               :src="matchmakerInfo.avatar" | ||||||
|  |               mode="aspectFill" | ||||||
|  |               class="w-24 h-24 rounded-full border-2 border-primary/20 shadow-md" | ||||||
|  |             /> | ||||||
|  |             <view | ||||||
|  |               class="absolute -bottom-1 -right-1 w-7 h-7 bg-gradient-to-r from-primary to-primary/80 rounded-full flex items-center justify-center shadow-md" | ||||||
|  |             > | ||||||
|  |               <text class="i-carbon-task-complete text-xs text-white"></text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |           <view class="ml-5 flex-1"> | ||||||
|  |             <view class="flex items-center mb-3"> | ||||||
|  |               <text class="text-xl font-bold text-gray-800 mr-3">{{ matchmakerInfo.name }}</text> | ||||||
|  |               <view class="px-3 py-1 bg-gradient-to-r from-primary/10 to-primary/5 rounded-full"> | ||||||
|  |                 <text class="text-sm text-primary font-medium">金牌红娘</text> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="space-y-2.5"> | ||||||
|  |               <view class="flex items-center"> | ||||||
|  |                 <text class="i-carbon-logo-wechat text-lg text-gray-500 mr-2"></text> | ||||||
|  |                 <text class="text-sm text-gray-600">微信: {{ matchmakerInfo.wechat }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view class="flex items-center"> | ||||||
|  |                 <text class="i-carbon-phone text-lg text-gray-500 mr-2"></text> | ||||||
|  |                 <text class="text-sm text-gray-600">电话: {{ matchmakerInfo.phone }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view class="flex items-center text-sm"> | ||||||
|  |                 <text class="i-carbon-star-filled text-lg text-yellow-400 mr-2"></text> | ||||||
|  |                 <text class="text-gray-600"> | ||||||
|  |                   成功率: | ||||||
|  |                   <text class="font-medium text-primary">{{ matchmakerInfo.successRate }}%</text> | ||||||
|  |                 </text> | ||||||
|  |                 <text class="mx-3 text-gray-300">|</text> | ||||||
|  |                 <text class="text-gray-600"> | ||||||
|  |                   服务人数: | ||||||
|  |                   <text class="font-medium text-primary">{{ matchmakerInfo.serviceCount }}+</text> | ||||||
|  |                 </text> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |         <view class="mt-5 pt-4 border-t border-gray-100"> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <view class="flex items-center"> | ||||||
|  |               <text class="i-carbon-chat text-lg text-gray-500 mr-2"></text> | ||||||
|  |               <text class="text-sm text-gray-600">在线咨询</text> | ||||||
|  |             </view> | ||||||
|  |             <button | ||||||
|  |               class="flex items-center px-5 py-2.5 bg-gradient-to-r from-[#FF6B6B] to-[#FF8E8E] text-white rounded-full text-sm font-medium shadow-md hover:shadow-lg hover:from-[#FF8E8E] hover:to-[#FF6B6B] transition-all duration-300 active:scale-95 active:shadow-inner" | ||||||
|  |               @click="contactMatchmaker" | ||||||
|  |             > | ||||||
|  |               <text class="font-semibold">立即联系</text> | ||||||
|  |               <text class="i-carbon-arrow-right text-xs ml-1"></text> | ||||||
|  |             </button> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref } from 'vue' | ||||||
|  | 
 | ||||||
|  | interface MatchmakerInfo { | ||||||
|  |   name: string | ||||||
|  |   avatar: string | ||||||
|  |   wechat: string | ||||||
|  |   phone: string | ||||||
|  |   successRate: number | ||||||
|  |   serviceCount: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 红娘信息 | ||||||
|  | const matchmakerInfo = ref<MatchmakerInfo>({ | ||||||
|  |   name: '王红娘', | ||||||
|  |   avatar: | ||||||
|  |     'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg', | ||||||
|  |   wechat: 'matchmaker123', | ||||||
|  |   phone: '13800138000', | ||||||
|  |   successRate: 98, | ||||||
|  |   serviceCount: 1000, | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 联系红娘方法 | ||||||
|  | const contactMatchmaker = () => { | ||||||
|  |   uni.showActionSheet({ | ||||||
|  |     itemList: ['复制微信号', '拨打电话'], | ||||||
|  |     success: (res) => { | ||||||
|  |       if (res.tapIndex === 0) { | ||||||
|  |         uni.setClipboardData({ | ||||||
|  |           data: matchmakerInfo.value.wechat, | ||||||
|  |           success: () => { | ||||||
|  |             uni.showToast({ | ||||||
|  |               title: '微信号已复制', | ||||||
|  |               icon: 'success', | ||||||
|  |             }) | ||||||
|  |           }, | ||||||
|  |         }) | ||||||
|  |       } else if (res.tapIndex === 1) { | ||||||
|  |         uni.makePhoneCall({ | ||||||
|  |           phoneNumber: matchmakerInfo.value.phone, | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										23
									
								
								src/pages/fate/fate.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,23 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '红娘', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="px-4 py-3"> | ||||||
|  |     <!-- 红娘推荐组件 --> | ||||||
|  |     <MatchmakerRecommend /> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import MatchmakerRecommend from './components/MatchmakerRecommend.vue' | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | // 自定义样式 | ||||||
|  | </style> | ||||||
							
								
								
									
										284
									
								
								src/pages/index/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,284 @@ | |||||||
|  | <!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 --> | ||||||
|  | <route lang="json5" type="home"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | <template> | ||||||
|  |   <view class="px-4 py-3" @click="closeUserMenu"> | ||||||
|  |     <!-- 顶部栏 --> | ||||||
|  |     <view class="flex items-center justify-between mb-6"> | ||||||
|  |       <text class="text-lg font-bold">可报名考试</text> | ||||||
|  |       <view class="flex items-center relative" @click.stop> | ||||||
|  |         <i class="i-carbon-user-filled text-xl mr-2 text-gray-600"></i> | ||||||
|  |         <text | ||||||
|  |           class="text-base font-bold text-gray-700 mr-4 cursor-pointer transition-colors duration-200 hover:text-gray-900" | ||||||
|  |           @click="toggleUserMenu" | ||||||
|  |         > | ||||||
|  |           {{ userName }} | ||||||
|  |         </text> | ||||||
|  |         <!-- 用户菜单 --> | ||||||
|  |         <transition name="menu-fade" mode="out-in"> | ||||||
|  |           <view | ||||||
|  |             v-if="showUserMenu" | ||||||
|  |             class="absolute top-full right-0 mt-2 bg-white rounded-lg shadow-lg border border-gray-200 z-50 min-w-32 transform origin-top-right" | ||||||
|  |           > | ||||||
|  |             <view | ||||||
|  |               class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer border-b border-gray-100 transition-colors duration-150" | ||||||
|  |               @click="handleModifyPassword" | ||||||
|  |             > | ||||||
|  |               修改密码 | ||||||
|  |             </view> | ||||||
|  |             <view | ||||||
|  |               class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer border-b border-gray-100 transition-colors duration-150" | ||||||
|  |               @click="handleModifyProfile" | ||||||
|  |             > | ||||||
|  |               修改个人信息 | ||||||
|  |             </view> | ||||||
|  |             <view | ||||||
|  |               class="px-4 py-2 text-sm text-red-600 hover:bg-red-50 cursor-pointer transition-colors duration-150" | ||||||
|  |               @click="handleLogout" | ||||||
|  |             > | ||||||
|  |               退出登录 | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </transition> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 考试列表 --> | ||||||
|  |     <view class="space-y-6"> | ||||||
|  |       <view | ||||||
|  |         v-for="exam in examList" | ||||||
|  |         :key="exam.id" | ||||||
|  |         class="bg-white rounded-lg shadow-xl p-4 border-2 border-gray-400 relative transition-all duration-300 hover:shadow-2xl hover:-translate-y-1 hover:border-gray-500 cursor-pointer" | ||||||
|  |         style=" | ||||||
|  |           box-shadow: | ||||||
|  |             0 8px 16px -4px rgba(0, 0, 0, 0.15), | ||||||
|  |             0 4px 8px -2px rgba(0, 0, 0, 0.1), | ||||||
|  |             inset 0 1px 0 rgba(255, 255, 255, 0.1); | ||||||
|  |         " | ||||||
|  |         @mouseenter="handleCardHover(exam.id)" | ||||||
|  |         @mouseleave="handleCardLeave(exam.id)" | ||||||
|  |       > | ||||||
|  |         <!-- 第一行:考试名称 --> | ||||||
|  |         <view class="mb-2"> | ||||||
|  |           <text class="text-xl font-bold text-gray-800">{{ exam.examName }}</text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 第二行:考试时间 --> | ||||||
|  |         <view class="mb-2"> | ||||||
|  |           <text class="text-base text-gray-600"> | ||||||
|  |             {{ formatExamTime(exam.examDate, exam.startTime, exam.endTime) }} | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 第三行:考试地点 --> | ||||||
|  |         <view class="mb-2"> | ||||||
|  |           <text class="text-base text-gray-600"> | ||||||
|  |             {{ | ||||||
|  |               formatExamLocation(exam.provinceName, exam.cityName, exam.address, exam.locationName) | ||||||
|  |             }} | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 第四、五行:空白间距 --> | ||||||
|  |         <view class="h-4"></view> | ||||||
|  |         <view class="h-4"></view> | ||||||
|  | 
 | ||||||
|  |         <!-- 第六行:报名截止信息 --> | ||||||
|  |         <view class="mb-4"> | ||||||
|  |           <text class="text-sm text-gray-500">报名截止:{{ exam.registrationDeadline }}</text> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 报名按钮:右下角 --> | ||||||
|  |         <view class="absolute bottom-4 right-4"> | ||||||
|  |           <button | ||||||
|  |             class="bg-black text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-800 transition-colors duration-200" | ||||||
|  |             @click="handleRegisterExam(exam)" | ||||||
|  |           > | ||||||
|  |             报名 | ||||||
|  |           </button> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 加载状态 --> | ||||||
|  |     <view v-if="loading" class="text-center py-8"> | ||||||
|  |       <text class="text-gray-500">加载中...</text> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 空状态 --> | ||||||
|  |     <view v-if="!loading && examList.length === 0" class="text-center py-8"> | ||||||
|  |       <text class="text-gray-500">暂无可报名的考试</text> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref } from 'vue' | ||||||
|  | import { getCanBookExamListAPI } from '@/service/exam' | ||||||
|  | 
 | ||||||
|  | defineOptions({ | ||||||
|  |   name: 'Home', | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const userName = ref('') | ||||||
|  | const showUserMenu = ref(false) | ||||||
|  | const examList = ref([]) | ||||||
|  | const loading = ref(false) | ||||||
|  | 
 | ||||||
|  | // 获取考试列表 | ||||||
|  | const getExamList = async () => { | ||||||
|  |   loading.value = true | ||||||
|  |   try { | ||||||
|  |     const res = await getCanBookExamListAPI() | ||||||
|  |     examList.value = res.data.list || [] | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取考试列表失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取考试列表失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化考试时间 | ||||||
|  | const formatExamTime = (examDate: string, startTime: string, endTime: string) => { | ||||||
|  |   if (!examDate || !startTime || !endTime) return '' | ||||||
|  |   return `${examDate} ${startTime}-${endTime}` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化考试地点 | ||||||
|  | const formatExamLocation = ( | ||||||
|  |   provinceName: string, | ||||||
|  |   cityName: string, | ||||||
|  |   address: string, | ||||||
|  |   locationName: string, | ||||||
|  | ) => { | ||||||
|  |   const parts = [provinceName, cityName, address, locationName].filter(Boolean) | ||||||
|  |   return parts.join(' ') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 报名考试 | ||||||
|  | const handleRegisterExam = (exam: any) => { | ||||||
|  |   uni.showModal({ | ||||||
|  |     title: '确认报名', | ||||||
|  |     content: `确定要报名参加"${exam.examName}"吗?`, | ||||||
|  |     success: (res) => { | ||||||
|  |       if (res.confirm) { | ||||||
|  |         // TODO: 调用报名接口 | ||||||
|  |         uni.showToast({ | ||||||
|  |           title: '报名成功', | ||||||
|  |           icon: 'success', | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 卡片hover效果 | ||||||
|  | const handleCardHover = (examId: string) => { | ||||||
|  |   // 可以在这里添加额外的hover逻辑,比如改变按钮样式等 | ||||||
|  |   console.log('卡片hover:', examId) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleCardLeave = (examId: string) => { | ||||||
|  |   // 可以在这里添加离开时的逻辑 | ||||||
|  |   console.log('卡片离开:', examId) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 切换用户菜单 | ||||||
|  | const toggleUserMenu = () => { | ||||||
|  |   showUserMenu.value = !showUserMenu.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 关闭用户菜单 | ||||||
|  | const closeUserMenu = () => { | ||||||
|  |   showUserMenu.value = false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 修改密码 | ||||||
|  | const handleModifyPassword = () => { | ||||||
|  |   showUserMenu.value = false | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: '/pages/user/modify-password', | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 修改个人信息 | ||||||
|  | const handleModifyProfile = () => { | ||||||
|  |   showUserMenu.value = false | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: '/pages/user/profile', | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 退出登录 | ||||||
|  | const handleLogout = () => { | ||||||
|  |   showUserMenu.value = false | ||||||
|  |   uni.showModal({ | ||||||
|  |     title: '提示', | ||||||
|  |     content: '确定要退出登录吗?', | ||||||
|  |     success: (res) => { | ||||||
|  |       if (res.confirm) { | ||||||
|  |         // 清除本地存储 | ||||||
|  |         uni.removeStorageSync('token') | ||||||
|  |         uni.removeStorageSync('loginData') | ||||||
|  |         // 跳转到登录页 | ||||||
|  |         uni.reLaunch({ | ||||||
|  |           url: '/pages/login/index', | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onLoad(() => { | ||||||
|  |   // 获取本地存储的用户昵称 | ||||||
|  |   try { | ||||||
|  |     const loginData = uni.getStorageSync('loginData') | ||||||
|  |     userName.value = loginData.user.nickName | ||||||
|  |   } catch (e) { | ||||||
|  |     userName.value = '' | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // 获取考试列表 | ||||||
|  |   getExamList() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 下拉刷新 | ||||||
|  | onPullDownRefresh(() => { | ||||||
|  |   getExamList().then(() => { | ||||||
|  |     uni.stopPullDownRefresh() | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | /* 菜单淡入淡出动画 */ | ||||||
|  | .menu-fade-enter-active, | ||||||
|  | .menu-fade-leave-active { | ||||||
|  |   transition: all 0.2s ease-in-out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .menu-fade-enter-from { | ||||||
|  |   opacity: 0; | ||||||
|  |   transform: scale(0.95) translateY(-10px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .menu-fade-leave-to { | ||||||
|  |   opacity: 0; | ||||||
|  |   transform: scale(0.95) translateY(-10px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .menu-fade-enter-to, | ||||||
|  | .menu-fade-leave-from { | ||||||
|  |   opacity: 1; | ||||||
|  |   transform: scale(1) translateY(0); | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										86
									
								
								src/pages/login/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,86 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen flex items-center justify-center bg-gray-200 px-4"> | ||||||
|  |     <view | ||||||
|  |       class="w-full max-w-md mx-auto bg-white rounded-2xl shadow-lg p-8 border border-gray-100 box-border" | ||||||
|  |     > | ||||||
|  |       <view class="mb-6 text-center"> | ||||||
|  |         <text class="text-2xl font-bold text-gray-800">艺术评测考试平台</text> | ||||||
|  |       </view> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <input | ||||||
|  |           v-model="account" | ||||||
|  |           type="text" | ||||||
|  |           placeholder="用户名/手机号" | ||||||
|  |           class="w-full h-12 px-4 border-2 border-gray-300 rounded-lg focus:border-pink-400 outline-none bg-gray-50 text-base transition-all duration-200 box-border" | ||||||
|  |         /> | ||||||
|  |       </view> | ||||||
|  |       <view class="mb-6"> | ||||||
|  |         <input | ||||||
|  |           v-model="password" | ||||||
|  |           type="password" | ||||||
|  |           placeholder="密码" | ||||||
|  |           class="w-full h-12 px-4 border-2 border-gray-300 rounded-lg focus:border-pink-400 outline-none bg-gray-50 text-base transition-all duration-200 box-border" | ||||||
|  |         /> | ||||||
|  |       </view> | ||||||
|  |       <view class="flex mb-2 mt-4 gap-3"> | ||||||
|  |         <button | ||||||
|  |           class="flex-1 h-12 bg-black text-white rounded-l-lg text-base font-semibold shadow hover:shadow-md transition-all duration-200 flex items-center justify-center border border-gray-300" | ||||||
|  |           @click="handleLogin" | ||||||
|  |         > | ||||||
|  |           <span class="w-full text-center">登录</span> | ||||||
|  |         </button> | ||||||
|  |         <button | ||||||
|  |           class="flex-1 h-12 bg-white text-black rounded-r-lg text-base font-semibold hover:bg-gray-50 transition-all duration-200 flex items-center justify-center border border-gray-300" | ||||||
|  |           @click="handleRegister" | ||||||
|  |         > | ||||||
|  |           <span class="w-full text-center">注册</span> | ||||||
|  |         </button> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref } from 'vue' | ||||||
|  | import { loginAPI } from '@/service/login' | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | 
 | ||||||
|  | const useStore = useUserStore() | ||||||
|  | const account = ref('') | ||||||
|  | const password = ref('') | ||||||
|  | 
 | ||||||
|  | const handleLogin = async () => { | ||||||
|  |   if (!account.value || !password.value) { | ||||||
|  |     uni.showToast({ title: '请输入账号和密码', icon: 'none' }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     const res = await loginAPI({ username: account.value, password: password.value }) | ||||||
|  |     uni.setStorageSync('loginData', res.data) | ||||||
|  |     uni.setStorageSync('x-token', res.data.token) | ||||||
|  |     useStore.setUserInfo() | ||||||
|  |     uni.reLaunch({ url: '/pages/index/index' }) | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ title: '登录失败', icon: 'none' }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleRegister = () => { | ||||||
|  |   // 跳转到注册页(假设有注册页面) | ||||||
|  |   uni.navigateTo({ url: '/pages/register/index' }) | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | body { | ||||||
|  |   background: #fafaff; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										170
									
								
								src/pages/my/activities.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,170 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '我的活动', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 顶部筛选 --> | ||||||
|  |     <view class="sticky top-0 z-10 bg-white shadow-sm"> | ||||||
|  |       <view class="flex items-center justify-around p-4 border-b border-gray-100"> | ||||||
|  |         <view | ||||||
|  |           v-for="(item, index) in filterOptions" | ||||||
|  |           :key="index" | ||||||
|  |           class="flex-1 text-center" | ||||||
|  |           @tap="changeFilter(item.value)" | ||||||
|  |         > | ||||||
|  |           <text | ||||||
|  |             :class="[ | ||||||
|  |               'text-sm py-2 px-4 rounded-full transition-colors', | ||||||
|  |               currentFilter === item.value | ||||||
|  |                 ? 'bg-sky-500 text-white' | ||||||
|  |                 : 'text-gray-600 hover:bg-gray-100', | ||||||
|  |             ]" | ||||||
|  |           > | ||||||
|  |             {{ item.label }} | ||||||
|  |           </text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 活动列表 --> | ||||||
|  |     <view class="p-4"> | ||||||
|  |       <!-- 加载状态 --> | ||||||
|  |       <view v-if="loading" class="flex justify-center items-center py-8"> | ||||||
|  |         <text class="text-gray-500">加载中...</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 错误状态 --> | ||||||
|  |       <view v-else-if="error" class="flex justify-center items-center py-8"> | ||||||
|  |         <text class="text-red-500">{{ error }}</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 空状态 --> | ||||||
|  |       <view v-else-if="!activities.length" class="flex justify-center items-center py-8"> | ||||||
|  |         <text class="text-gray-500">暂无活动</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 活动列表 --> | ||||||
|  |       <view v-else class="space-y-4"> | ||||||
|  |         <view | ||||||
|  |           class="bg-white rounded-2xl shadow-lg overflow-hidden transform transition-all duration-300 hover:scale-[1.02] active:scale-[0.98]" | ||||||
|  |           v-for="item in activities" | ||||||
|  |           :key="item.id" | ||||||
|  |           @tap="viewActivity(item)" | ||||||
|  |         > | ||||||
|  |           <view class="relative"> | ||||||
|  |             <image :src="item.eventAvatar" mode="aspectFill" class="w-full h-40 object-cover" /> | ||||||
|  |             <view | ||||||
|  |               class="absolute top-3 right-3 px-3 py-1 bg-white/90 rounded-full backdrop-blur-sm" | ||||||
|  |             > | ||||||
|  |               <text class="text-sm text-primary font-medium">¥{{ item.eventPrice }}</text> | ||||||
|  |             </view> | ||||||
|  |             <view | ||||||
|  |               :class="[ | ||||||
|  |                 'absolute top-3 left-3 px-3 py-1 rounded-full text-sm font-medium', | ||||||
|  |                 item.status === '1' ? 'bg-green-500/90 text-white' : 'bg-gray-500/90 text-white', | ||||||
|  |               ]" | ||||||
|  |             > | ||||||
|  |               {{ item.status === '1' ? '进行中' : '已结束' }} | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |           <view class="p-4"> | ||||||
|  |             <text class="block text-lg font-bold mb-2">{{ item.eventTitle }}</text> | ||||||
|  |             <view class="flex items-center text-sm text-gray-500"> | ||||||
|  |               <text class="iconfont icon-time mr-1"></text> | ||||||
|  |               <text>截止时间: {{ formatDate(item.eventEndTime) }}</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="mt-3 flex items-center justify-between"> | ||||||
|  |               <view class="flex items-center"> | ||||||
|  |                 <view class="flex -space-x-2"> | ||||||
|  |                   <image | ||||||
|  |                     v-for="(avatar, index) in getRandomAvatars(3)" | ||||||
|  |                     :key="index" | ||||||
|  |                     :src="avatar" | ||||||
|  |                     class="w-8 h-8 rounded-full border-2 border-white" | ||||||
|  |                   /> | ||||||
|  |                 </view> | ||||||
|  |                 <text class="ml-2 text-sm text-gray-500">已有 {{ item.eventNumber }} 人报名</text> | ||||||
|  |               </view> | ||||||
|  |               <view | ||||||
|  |                 class="px-4 py-2 bg-primary text-white text-sm rounded-full" | ||||||
|  |                 @tap.stop="handleJoin(item)" | ||||||
|  |               > | ||||||
|  |                 查看详情 | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { getMyEventListAPI } from '@/service/event' | ||||||
|  | import type { MyEventItem } from '@/service/event/type' | ||||||
|  | import { formatDate } from '@/utils/date' | ||||||
|  | 
 | ||||||
|  | // 筛选选项 | ||||||
|  | const filterOptions = [ | ||||||
|  |   { label: '全部', value: '1' }, | ||||||
|  |   { label: '进行中', value: '2' }, | ||||||
|  |   { label: '已结束', value: '3' }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | // 状态 | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | const activities = ref<MyEventItem[]>([]) | ||||||
|  | const currentFilter = ref('1') | ||||||
|  | 
 | ||||||
|  | // 获取活动列表 | ||||||
|  | const fetchActivities = async () => { | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  |   try { | ||||||
|  |     const res = await getMyEventListAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       activities.value = res.data | ||||||
|  |     } else { | ||||||
|  |       error.value = res.message || '获取活动列表失败' | ||||||
|  |     } | ||||||
|  |   } catch (err) { | ||||||
|  |     error.value = '网络错误,请稍后重试' | ||||||
|  |     console.error('获取活动列表失败:', err) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 切换筛选 | ||||||
|  | const changeFilter = (filter: string) => { | ||||||
|  |   currentFilter.value = filter | ||||||
|  |   fetchActivities() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 查看活动详情 | ||||||
|  | const viewActivity = (item: MyEventItem) => { | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: `/pages/activity/detail?id=${item.id}`, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取随机头像(实际项目中应该使用真实数据) | ||||||
|  | const getRandomAvatars = (count: number) => { | ||||||
|  |   return Array(count).fill( | ||||||
|  |     'https://bpic.588ku.com/element_origin_min_pic/23/07/11/d32dabe266d10da8b21bd640a2e9b611.jpg', | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 初始化 | ||||||
|  | onMounted(() => { | ||||||
|  |   fetchActivities() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										212
									
								
								src/pages/my/complaint.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,212 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '发起投诉', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 p-4"> | ||||||
|  |     <view class="bg-white rounded-2xl shadow-sm p-4"> | ||||||
|  |       <!-- 投诉分类 --> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <text class="text-sm text-gray-600 mb-2 block">投诉分类</text> | ||||||
|  |         <view class="grid grid-cols-2 gap-2"> | ||||||
|  |           <view | ||||||
|  |             v-for="item in complaintClasses" | ||||||
|  |             :key="item.value" | ||||||
|  |             class="h-12 px-4 bg-gray-50 rounded-lg flex items-center justify-center" | ||||||
|  |             :class="{ | ||||||
|  |               'bg-sky-50 border border-sky-200': selectedClass === item.value, | ||||||
|  |               'border border-gray-100': selectedClass !== item.value, | ||||||
|  |             }" | ||||||
|  |             @click="selectedClass = item.value" | ||||||
|  |           > | ||||||
|  |             <text | ||||||
|  |               :class="{ | ||||||
|  |                 'text-sky-600': selectedClass === item.value, | ||||||
|  |                 'text-gray-600': selectedClass !== item.value, | ||||||
|  |               }" | ||||||
|  |             > | ||||||
|  |               {{ item.label }} | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 投诉内容 --> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <text class="text-sm text-gray-600 mb-2 block">投诉详情</text> | ||||||
|  |         <textarea | ||||||
|  |           v-model="complaintContent" | ||||||
|  |           class="w-full h-40 px-4 py-3 bg-gray-50 rounded-lg text-gray-800 placeholder-gray-400" | ||||||
|  |           placeholder="请详细描述您遇到的问题..." | ||||||
|  |           maxlength="500" | ||||||
|  |         /> | ||||||
|  |         <text class="text-xs text-gray-400 text-right block mt-1"> | ||||||
|  |           {{ complaintContent.length }}/500 | ||||||
|  |         </text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 图片上传 --> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <text class="text-sm text-gray-600 mb-2 block">上传证据(选填)</text> | ||||||
|  |         <view class="grid grid-cols-3 gap-2"> | ||||||
|  |           <view v-for="(image, index) in imageList" :key="index" class="relative aspect-square"> | ||||||
|  |             <image :src="image" class="w-full h-full rounded-lg object-cover" mode="aspectFill" /> | ||||||
|  |             <view | ||||||
|  |               class="absolute top-1 right-1 w-6 h-6 bg-black/50 rounded-full flex items-center justify-center" | ||||||
|  |               @click="removeImage(index)" | ||||||
|  |             > | ||||||
|  |               <i class="i-carbon-close text-white text-sm"></i> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |           <view | ||||||
|  |             v-if="imageList.length < 9" | ||||||
|  |             class="aspect-square bg-gray-50 rounded-lg flex items-center justify-center border border-dashed border-gray-200" | ||||||
|  |             @click="chooseImage" | ||||||
|  |           > | ||||||
|  |             <i class="i-carbon-image text-gray-400 text-2xl"></i> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |         <text class="text-xs text-gray-400 mt-1 block">最多上传9张图片,每张不超过5MB</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 提交按钮 --> | ||||||
|  |       <button | ||||||
|  |         class="w-full h-12 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-lg text-base flex items-center justify-center shadow-sm hover:shadow-md transition-shadow" | ||||||
|  |         :disabled="!isValid" | ||||||
|  |         :class="{ 'opacity-50': !isValid }" | ||||||
|  |         @click="submitComplaint" | ||||||
|  |       > | ||||||
|  |         提交投诉 | ||||||
|  |       </button> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed } from 'vue' | ||||||
|  | import { addComplaintAPI } from '@/service/feedback' | ||||||
|  | import { uploadImageAPI } from '@/service/file' | ||||||
|  | import { onLoad } from '@dcloudio/uni-app' | ||||||
|  | 
 | ||||||
|  | const id = ref('') | ||||||
|  | onLoad((options) => { | ||||||
|  |   id.value = options.id | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 投诉分类选项 | ||||||
|  | const complaintClasses = [ | ||||||
|  |   { label: '虚假宣传', value: '虚假宣传' }, | ||||||
|  |   { label: '微商', value: '微商' }, | ||||||
|  |   { label: '诈骗', value: '诈骗' }, | ||||||
|  |   { label: '个人信息不符', value: '个人信息不符' }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | const selectedClass = ref('') | ||||||
|  | const complaintContent = ref('') | ||||||
|  | const imageList = ref<string[]>([]) | ||||||
|  | 
 | ||||||
|  | // 验证表单是否有效 | ||||||
|  | const isValid = computed(() => { | ||||||
|  |   return selectedClass.value && complaintContent.value.trim().length > 0 | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 选择图片 | ||||||
|  | const chooseImage = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await uni.chooseImage({ | ||||||
|  |       count: 9 - imageList.value.length, | ||||||
|  |       sizeType: ['compressed'], | ||||||
|  |       sourceType: ['album', 'camera'], | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     // 检查图片大小 | ||||||
|  |     const tempFiles = res.tempFiles as UniApp.ChooseImageSuccessCallbackResultFile[] | ||||||
|  |     const tempFilePaths = res.tempFilePaths as string[] | ||||||
|  | 
 | ||||||
|  |     const validImages = tempFilePaths.filter((path) => { | ||||||
|  |       const file = tempFiles.find((f) => f.path === path) | ||||||
|  |       return file && file.size <= 5 * 1024 * 1024 // 5MB | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     if (validImages.length !== tempFilePaths.length) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '部分图片超过5MB限制', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     imageList.value.push(...validImages) | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('选择图片失败:', error) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 移除图片 | ||||||
|  | const removeImage = (index: number) => { | ||||||
|  |   imageList.value.splice(index, 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 上传图片 | ||||||
|  | const uploadImages = async () => { | ||||||
|  |   const uploadedUrls: string[] = [] | ||||||
|  | 
 | ||||||
|  |   for (const image of imageList.value) { | ||||||
|  |     try { | ||||||
|  |       const res = await uploadImageAPI(image, 'complaint') | ||||||
|  |       if (res.url) { | ||||||
|  |         uploadedUrls.push(res.url) | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('上传图片失败:', error) | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '图片上传失败', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return uploadedUrls | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 提交投诉 | ||||||
|  | const submitComplaint = async () => { | ||||||
|  |   if (!isValid.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     uni.showLoading({ title: '提交中...' }) | ||||||
|  | 
 | ||||||
|  |     // 上传图片 | ||||||
|  |     const imageUrls = await uploadImages() | ||||||
|  | 
 | ||||||
|  |     // 提交投诉 | ||||||
|  |     await addComplaintAPI({ | ||||||
|  |       userId: id.value, // TODO: 从路由参数或全局状态获取被投诉人ID | ||||||
|  |       complaintClass: selectedClass.value as any, | ||||||
|  |       complaintContent: complaintContent.value.trim(), | ||||||
|  |       complaintImageList: imageUrls, | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     uni.hideLoading() | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '提交成功', | ||||||
|  |       icon: 'success', | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     // 延迟返回上一页 | ||||||
|  |     setTimeout(() => { | ||||||
|  |       uni.navigateBack() | ||||||
|  |     }, 1500) | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.hideLoading() | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '提交失败,请重试', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										92
									
								
								src/pages/my/feedback.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,92 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '意见反馈', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 p-4"> | ||||||
|  |     <view class="bg-white rounded-2xl shadow-sm p-4"> | ||||||
|  |       <!-- 标题输入 --> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <text class="text-sm text-gray-600 mb-2 block">反馈标题</text> | ||||||
|  |         <input | ||||||
|  |           v-model="feedbackTitle" | ||||||
|  |           class="w-full h-12 px-4 bg-gray-50 rounded-lg text-gray-800 placeholder-gray-400" | ||||||
|  |           placeholder="请输入反馈标题" | ||||||
|  |           maxlength="50" | ||||||
|  |         /> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 内容输入 --> | ||||||
|  |       <view class="mb-4"> | ||||||
|  |         <text class="text-sm text-gray-600 mb-2 block">反馈内容</text> | ||||||
|  |         <textarea | ||||||
|  |           v-model="feedbackContent" | ||||||
|  |           class="w-full h-40 px-4 py-3 bg-gray-50 rounded-lg text-gray-800 placeholder-gray-400" | ||||||
|  |           placeholder="请详细描述您遇到的问题或建议..." | ||||||
|  |           maxlength="500" | ||||||
|  |         /> | ||||||
|  |         <text class="text-xs text-gray-400 text-right block mt-1"> | ||||||
|  |           {{ feedbackContent.length }}/500 | ||||||
|  |         </text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 提交按钮 --> | ||||||
|  |       <button | ||||||
|  |         class="w-full h-12 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-lg text-base flex items-center justify-center shadow-sm hover:shadow-md transition-shadow" | ||||||
|  |         :disabled="!isValid" | ||||||
|  |         :class="{ 'opacity-50': !isValid }" | ||||||
|  |         @click="submitFeedback" | ||||||
|  |       > | ||||||
|  |         提交反馈 | ||||||
|  |       </button> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed } from 'vue' | ||||||
|  | import { addFeedbackAPI } from '@/service/feedback' | ||||||
|  | 
 | ||||||
|  | const feedbackTitle = ref('') | ||||||
|  | const feedbackContent = ref('') | ||||||
|  | 
 | ||||||
|  | // 验证表单是否有效 | ||||||
|  | const isValid = computed(() => { | ||||||
|  |   return feedbackTitle.value.trim().length > 0 && feedbackContent.value.trim().length > 0 | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 提交反馈 | ||||||
|  | const submitFeedback = async () => { | ||||||
|  |   if (!isValid.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     uni.showLoading({ title: '提交中...' }) | ||||||
|  |     await addFeedbackAPI({ | ||||||
|  |       feedbackTitle: feedbackTitle.value.trim(), | ||||||
|  |       feedbackContent: feedbackContent.value.trim(), | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     uni.hideLoading() | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '提交成功', | ||||||
|  |       icon: 'success', | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     // 延迟返回上一页 | ||||||
|  |     setTimeout(() => { | ||||||
|  |       uni.navigateBack() | ||||||
|  |     }, 1500) | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.hideLoading() | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '提交失败,请重试', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										274
									
								
								src/pages/my/invite.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,274 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '邀请好友', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 邀请码卡片 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-2 block">我的邀请码</text> | ||||||
|  |           <view class="flex items-center justify-center space-x-2 mb-4"> | ||||||
|  |             <text class="text-3xl font-bold text-sky-500">{{ inviteCode }}</text> | ||||||
|  |             <button | ||||||
|  |               class="px-3 py-1 bg-sky-50 text-sky-500 rounded-full text-sm" | ||||||
|  |               @click="copyInviteCode" | ||||||
|  |             > | ||||||
|  |               复制 | ||||||
|  |             </button> | ||||||
|  |           </view> | ||||||
|  |           <text class="text-sm text-gray-500">邀请好友注册时填写此邀请码,双方均可获得奖励</text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 邀请记录 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm overflow-hidden"> | ||||||
|  |         <view class="p-4 border-b border-gray-100"> | ||||||
|  |           <text class="text-base font-bold text-gray-800">邀请记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-if="inviteList.length === 0" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">暂无邀请记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else class="divide-y divide-gray-100"> | ||||||
|  |           <view v-for="(item, index) in inviteList" :key="index" class="p-4"> | ||||||
|  |             <!-- 一级用户 --> | ||||||
|  |             <view class="flex items-center justify-between"> | ||||||
|  |               <view class="flex-1"> | ||||||
|  |                 <text class="text-gray-800 block">{{ item.nickname }}</text> | ||||||
|  |                 <text class="text-sm text-gray-500">{{ item.createTime }}</text> | ||||||
|  |               </view> | ||||||
|  |               <view class="flex items-center space-x-2"> | ||||||
|  |                 <button | ||||||
|  |                   class="px-2 py-1 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-lg text-xs flex items-center shadow-sm hover:shadow transition-all duration-300" | ||||||
|  |                   @click.stop=" | ||||||
|  |                     navigateTo('/pages/my/spend-detail', { | ||||||
|  |                       nickname: item.nickname, | ||||||
|  |                       id: item.id, | ||||||
|  |                       points: item.points, | ||||||
|  |                     }) | ||||||
|  |                   " | ||||||
|  |                 > | ||||||
|  |                   <i class="i-carbon-money text-xs mr-1"></i> | ||||||
|  |                   消费明细 | ||||||
|  |                 </button> | ||||||
|  |                 <view class="w-8 h-8 flex items-center justify-center" @click="toggleExpand(index)"> | ||||||
|  |                   <i | ||||||
|  |                     :class="[ | ||||||
|  |                       expandedItems[index] ? 'i-carbon-chevron-down' : 'i-carbon-chevron-right', | ||||||
|  |                       'text-gray-400', | ||||||
|  |                     ]" | ||||||
|  |                   ></i> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  | 
 | ||||||
|  |             <!-- 子用户列表 --> | ||||||
|  |             <view | ||||||
|  |               v-if="item.child && item.child.length > 0 && expandedItems[index]" | ||||||
|  |               class="mt-3 ml-4 pl-4 border-l-2 border-gray-100" | ||||||
|  |             > | ||||||
|  |               <view v-for="(child, childIndex) in item.child" :key="childIndex" class="py-3"> | ||||||
|  |                 <!-- 子用户信息 --> | ||||||
|  |                 <view class="flex items-center justify-between"> | ||||||
|  |                   <view class="flex-1"> | ||||||
|  |                     <text class="text-gray-800 block">{{ child.nickname }}</text> | ||||||
|  |                     <text class="text-sm text-gray-500">{{ child.createTime }}</text> | ||||||
|  |                   </view> | ||||||
|  |                   <view class="flex items-center space-x-2"> | ||||||
|  |                     <button | ||||||
|  |                       class="px-2 py-1 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-lg text-xs flex items-center shadow-sm hover:shadow transition-all duration-300" | ||||||
|  |                       @click.stop=" | ||||||
|  |                         navigateTo('/pages/my/spend-detail', { | ||||||
|  |                           nickname: child.nickname, | ||||||
|  |                           id: child.id, | ||||||
|  |                           points: child.points, | ||||||
|  |                         }) | ||||||
|  |                       " | ||||||
|  |                     > | ||||||
|  |                       <i class="i-carbon-money text-xs mr-1"></i> | ||||||
|  |                       消费明细 | ||||||
|  |                     </button> | ||||||
|  |                     <view | ||||||
|  |                       class="w-8 h-8 flex items-center justify-center" | ||||||
|  |                       @click="toggleChildExpand(index, childIndex)" | ||||||
|  |                     > | ||||||
|  |                       <i | ||||||
|  |                         :class="[ | ||||||
|  |                           expandedChildItems[`${index}-${childIndex}`] | ||||||
|  |                             ? 'i-carbon-chevron-down' | ||||||
|  |                             : 'i-carbon-chevron-right', | ||||||
|  |                           'text-gray-400', | ||||||
|  |                         ]" | ||||||
|  |                       ></i> | ||||||
|  |                     </view> | ||||||
|  |                   </view> | ||||||
|  |                 </view> | ||||||
|  | 
 | ||||||
|  |                 <!-- 孙用户列表 --> | ||||||
|  |                 <view | ||||||
|  |                   v-if=" | ||||||
|  |                     child.child && | ||||||
|  |                     child.child.length > 0 && | ||||||
|  |                     expandedChildItems[`${index}-${childIndex}`] | ||||||
|  |                   " | ||||||
|  |                   class="mt-3 ml-4 pl-4 border-l-2 border-gray-100" | ||||||
|  |                 > | ||||||
|  |                   <view | ||||||
|  |                     v-for="(grandChild, grandChildIndex) in child.child" | ||||||
|  |                     :key="grandChildIndex" | ||||||
|  |                     class="py-3 flex items-center justify-between" | ||||||
|  |                   > | ||||||
|  |                     <view class="flex-1"> | ||||||
|  |                       <text class="text-gray-800 block">{{ grandChild.nickname }}</text> | ||||||
|  |                       <text class="text-sm text-gray-500">{{ grandChild.createTime }}</text> | ||||||
|  |                     </view> | ||||||
|  |                     <view class="flex items-center space-x-2"> | ||||||
|  |                       <button | ||||||
|  |                         class="px-2 py-1 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-lg text-xs flex items-center shadow-sm hover:shadow transition-all duration-300" | ||||||
|  |                         @click.stop=" | ||||||
|  |                           navigateTo('/pages/my/spend-detail', { | ||||||
|  |                             nickname: grandChild.nickname, | ||||||
|  |                             id: grandChild.id, | ||||||
|  |                             points: grandChild.points, | ||||||
|  |                           }) | ||||||
|  |                         " | ||||||
|  |                       > | ||||||
|  |                         <i class="i-carbon-money text-xs mr-1"></i> | ||||||
|  |                         消费明细 | ||||||
|  |                       </button> | ||||||
|  |                       <view class="w-8 h-8 flex items-center justify-center"> | ||||||
|  |                         <i class="i-carbon-chevron-right text-gray-300"></i> | ||||||
|  |                       </view> | ||||||
|  |                     </view> | ||||||
|  |                   </view> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 邀请说明 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-4"> | ||||||
|  |         <text class="text-base font-bold text-gray-800 mb-2 block">邀请说明</text> | ||||||
|  |         <view class="space-y-2 text-sm text-gray-600"> | ||||||
|  |           <text class="block">1. 邀请好友填写您的邀请码</text> | ||||||
|  |           <text class="block">2. 好友注册成功后,双方均可获得奖励</text> | ||||||
|  |           <text class="block">3. 邀请记录将在好友注册成功后显示</text> | ||||||
|  |           <text class="block">4. 如有疑问,请联系客服</text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { getInviteCodeAPI, getMyInviteListAPI } from '@/service/login' | ||||||
|  | 
 | ||||||
|  | interface InviteUserItem { | ||||||
|  |   nickname: string | ||||||
|  |   id: string | number | ||||||
|  |   createTime?: string | ||||||
|  |   child?: InviteUserItem[] | ||||||
|  |   points?: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const inviteCode = ref('') | ||||||
|  | const inviteList = ref<InviteUserItem[]>([]) | ||||||
|  | const expandedItems = ref<{ [key: number]: boolean }>({}) | ||||||
|  | const expandedChildItems = ref<{ [key: string]: boolean }>({}) | ||||||
|  | 
 | ||||||
|  | // 切换一级用户展开/收起状态 | ||||||
|  | const toggleExpand = (index: number) => { | ||||||
|  |   expandedItems.value[index] = !expandedItems.value[index] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 切换子用户展开/收起状态 | ||||||
|  | const toggleChildExpand = (parentIndex: number, childIndex: number) => { | ||||||
|  |   const key = `${parentIndex}-${childIndex}` | ||||||
|  |   expandedChildItems.value[key] = !expandedChildItems.value[key] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取邀请码 | ||||||
|  | const getInviteCode = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await getInviteCodeAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       inviteCode.value = res.data | ||||||
|  |       console.log('邀请码:', res.data) // 添加日志查看数据 | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取邀请码失败:', error) // 添加错误日志 | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取邀请码失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取邀请记录 | ||||||
|  | const getInviteList = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await getMyInviteListAPI() | ||||||
|  |     if (res.code === 200 && Array.isArray(res.data)) { | ||||||
|  |       inviteList.value = res.data | ||||||
|  |       console.log('邀请记录数据:', res.data) // 添加日志查看数据 | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取邀请记录失败:', error) // 添加错误日志 | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取邀请记录失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 复制邀请码 | ||||||
|  | const copyInviteCode = () => { | ||||||
|  |   if (!inviteCode.value) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '邀请码获取失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uni.setClipboardData({ | ||||||
|  |     data: inviteCode.value, | ||||||
|  |     success: () => { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '复制成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const navigateTo = (url: string, params?: Record<string, any>) => { | ||||||
|  |   if (params) { | ||||||
|  |     console.log('传递的参数:', params) // 添加日志 | ||||||
|  |     const query = Object.entries(params) | ||||||
|  |       .map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`) | ||||||
|  |       .join('&') | ||||||
|  |     url = `${url}?${query}` | ||||||
|  |     console.log('最终URL:', url) // 添加日志 | ||||||
|  |   } | ||||||
|  |   uni.navigateTo({ url }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getInviteCode() | ||||||
|  |   getInviteList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										122
									
								
								src/pages/my/inviter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,122 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '我的邀请人', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-4 block">邀请人信息</text> | ||||||
|  | 
 | ||||||
|  |           <!-- 有邀请人时显示 --> | ||||||
|  |           <template v-if="userInfo.inviterCode"> | ||||||
|  |             <view class="bg-gradient-to-r from-pink-50 to-rose-50 rounded-xl p-4 mb-4"> | ||||||
|  |               <text class="text-sm text-gray-500 mb-2 block">我的邀请人代码</text> | ||||||
|  |               <text class="text-2xl font-bold text-pink-500 tracking-wider"> | ||||||
|  |                 {{ userInfo.inviterCode }} | ||||||
|  |               </text> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-sm text-gray-500">感谢您使用我们的服务</text> | ||||||
|  |           </template> | ||||||
|  | 
 | ||||||
|  |           <!-- 无邀请人时显示 --> | ||||||
|  |           <template v-else> | ||||||
|  |             <view class="bg-gray-50 rounded-xl p-6 mb-4"> | ||||||
|  |               <text class="text-gray-500 mb-4 block">您还没有绑定邀请人</text> | ||||||
|  |               <button | ||||||
|  |                 class="w-full bg-gradient-to-r from-pink-400 to-rose-400 text-white py-3 rounded-xl shadow-sm hover:shadow transition-all duration-300" | ||||||
|  |                 @click="showBindInviter" | ||||||
|  |               > | ||||||
|  |                 绑定邀请人 | ||||||
|  |               </button> | ||||||
|  |             </view> | ||||||
|  |           </template> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | import { bindInviterAPI } from '@/service/login' | ||||||
|  | 
 | ||||||
|  | const userStore = useUserStore() | ||||||
|  | const userInfo = ref<any>({}) | ||||||
|  | const inviterCode = ref('') | ||||||
|  | 
 | ||||||
|  | // 获取用户信息 | ||||||
|  | const getUserInfo = async () => { | ||||||
|  |   try { | ||||||
|  |     await userStore.setUserInfo() | ||||||
|  |     userInfo.value = userStore.userInfo | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取用户信息失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 显示绑定邀请人弹窗 | ||||||
|  | const showBindInviter = () => { | ||||||
|  |   uni.showModal({ | ||||||
|  |     title: '绑定邀请人', | ||||||
|  |     editable: true, | ||||||
|  |     placeholderText: '请输入邀请人代码', | ||||||
|  |     success: async (res) => { | ||||||
|  |       if (res.confirm && res.content) { | ||||||
|  |         inviterCode.value = res.content | ||||||
|  |         await bindInviter() | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 绑定邀请人 | ||||||
|  | const bindInviter = async () => { | ||||||
|  |   if (!inviterCode.value) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '请输入邀请人代码', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const res = await bindInviterAPI(inviterCode.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '绑定成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |       getUserInfo() | ||||||
|  |     } else { | ||||||
|  |       throw new Error(res.message || '绑定失败') | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.message || '绑定失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getUserInfo() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | /* 确保弹窗样式正确加载 */ | ||||||
|  | :deep(.uni-popup) { | ||||||
|  |   z-index: 999; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										211
									
								
								src/pages/my/matches.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,211 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '我的配对', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 列表内容 --> | ||||||
|  |     <scroll-view | ||||||
|  |       scroll-y | ||||||
|  |       class="h-screen" | ||||||
|  |       refresher-enabled | ||||||
|  |       :refresher-triggered="isRefreshing" | ||||||
|  |       @refresherrefresh="onRefresh" | ||||||
|  |       @scrolltolower="loadMore" | ||||||
|  |     > | ||||||
|  |       <!-- 配对列表 --> | ||||||
|  |       <view class="p-4 space-y-4"> | ||||||
|  |         <view | ||||||
|  |           v-for="item in matchList" | ||||||
|  |           :key="item.id" | ||||||
|  |           class="bg-white rounded-2xl shadow-sm overflow-hidden" | ||||||
|  |         > | ||||||
|  |           <view class="p-4"> | ||||||
|  |             <view class="flex items-start"> | ||||||
|  |               <!-- 头像 --> | ||||||
|  |               <image | ||||||
|  |                 class="w-20 h-20 rounded-xl" | ||||||
|  |                 :src="item.avatar || defaultAvatar" | ||||||
|  |                 mode="aspectFill" | ||||||
|  |               /> | ||||||
|  |               <!-- 基本信息 --> | ||||||
|  |               <view class="flex-1 ml-4"> | ||||||
|  |                 <view class="flex items-center mb-1"> | ||||||
|  |                   <text class="text-lg font-bold text-gray-800">{{ item.nickname }}</text> | ||||||
|  |                   <text class="ml-2 text-sm text-gray-500">{{ item.age }}岁</text> | ||||||
|  |                 </view> | ||||||
|  |                 <view class="flex items-center space-x-2 mb-2"> | ||||||
|  |                   <text class="text-sm text-gray-600">{{ item.height }}cm</text> | ||||||
|  |                   <text class="text-sm text-gray-600">|</text> | ||||||
|  |                   <text class="text-sm text-gray-600"> | ||||||
|  |                     {{ getEducationText(item.educationLevel) }} | ||||||
|  |                   </text> | ||||||
|  |                 </view> | ||||||
|  |                 <view class="text-sm text-gray-600"> | ||||||
|  |                   {{ item.workArea }} · {{ item.profession }} | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <!-- 操作按钮 --> | ||||||
|  |             <view class="mt-4 flex space-x-3"> | ||||||
|  |               <button | ||||||
|  |                 class="flex-1 bg-gradient-to-r from-sky-400 to-indigo-400 text-white text-sm py-2 rounded-lg shadow-sm hover:shadow-md transition-shadow" | ||||||
|  |                 @tap="handleViewDetail(item)" | ||||||
|  |               > | ||||||
|  |                 查看详情 | ||||||
|  |               </button> | ||||||
|  |               <button | ||||||
|  |                 class="flex-1 bg-white border border-sky-200 text-sky-600 text-sm py-2 rounded-lg hover:bg-sky-50 transition-colors" | ||||||
|  |                 @tap="handleViewDetail(item)" | ||||||
|  |               > | ||||||
|  |                 联系TA | ||||||
|  |               </button> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 加载状态 --> | ||||||
|  |       <view v-if="isLoading" class="flex justify-center py-6"> | ||||||
|  |         <view class="flex items-center space-x-2"> | ||||||
|  |           <view class="w-2 h-2 bg-sky-500 rounded-full animate-bounce"></view> | ||||||
|  |           <view | ||||||
|  |             class="w-2 h-2 bg-sky-500 rounded-full animate-bounce" | ||||||
|  |             style="animation-delay: 0.2s" | ||||||
|  |           ></view> | ||||||
|  |           <view | ||||||
|  |             class="w-2 h-2 bg-sky-500 rounded-full animate-bounce" | ||||||
|  |             style="animation-delay: 0.4s" | ||||||
|  |           ></view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |       <view v-else-if="!hasMore" class="flex justify-center py-6"> | ||||||
|  |         <text class="text-sm text-gray-400">没有更多了</text> | ||||||
|  |       </view> | ||||||
|  |     </scroll-view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { getMyLoveListAPI } from '@/service/love' | ||||||
|  | import type { LoveUserItem } from '@/service/love/type' | ||||||
|  | 
 | ||||||
|  | const defaultAvatar = '/static/images/default-avatar.png' | ||||||
|  | 
 | ||||||
|  | // 列表数据 | ||||||
|  | const matchList = ref<LoveUserItem[]>([]) | ||||||
|  | const isLoading = ref(false) | ||||||
|  | const isRefreshing = ref(false) | ||||||
|  | const hasMore = ref(true) | ||||||
|  | 
 | ||||||
|  | // 获取学历文本 | ||||||
|  | const getEducationText = (level: string) => { | ||||||
|  |   const educationMap: Record<string, string> = { | ||||||
|  |     '1': '初中', | ||||||
|  |     '2': '高中', | ||||||
|  |     '3': '大专', | ||||||
|  |     '4': '本科', | ||||||
|  |     '5': '硕士', | ||||||
|  |     '6': '博士', | ||||||
|  |   } | ||||||
|  |   return educationMap[level] || '未知' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取配对列表 | ||||||
|  | const getMatchList = async () => { | ||||||
|  |   if (isLoading.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     isLoading.value = true | ||||||
|  |     const res = await getMyLoveListAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       matchList.value = res.data | ||||||
|  |       hasMore.value = false // 由于API没有分页,所以直接设置为没有更多 | ||||||
|  |     } else { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: res.message || '获取配对列表失败', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取配对列表失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取配对列表失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     isLoading.value = false | ||||||
|  |     isRefreshing.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 下拉刷新 | ||||||
|  | const onRefresh = async () => { | ||||||
|  |   isRefreshing.value = true | ||||||
|  |   await getMatchList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 加载更多 | ||||||
|  | const loadMore = () => { | ||||||
|  |   if (!hasMore.value || isLoading.value) return | ||||||
|  |   getMatchList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 查看详情 | ||||||
|  | const handleViewDetail = (item: LoveUserItem) => { | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: `/pages/detail/index?id=${item.id}`, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 联系TA | ||||||
|  | const handleContact = (item: LoveUserItem) => { | ||||||
|  |   if (!item.isUnlocked) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '请先解锁查看联系方式', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (!item.wechatId) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '对方暂未设置联系方式', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uni.showModal({ | ||||||
|  |     title: '联系方式', | ||||||
|  |     content: `微信号:${item.wechatId}`, | ||||||
|  |     showCancel: false, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getMatchList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @keyframes bounce { | ||||||
|  |   0%, | ||||||
|  |   100% { | ||||||
|  |     transform: translateY(0); | ||||||
|  |   } | ||||||
|  |   50% { | ||||||
|  |     transform: translateY(-6px); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .animate-bounce { | ||||||
|  |   animation: bounce 1s infinite; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										364
									
								
								src/pages/my/my.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,364 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '我的', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 顶部背景 --> | ||||||
|  |     <view | ||||||
|  |       class="h-56 bg-gradient-to-br from-pink-50 via-rose-50 to-purple-50 relative overflow-hidden" | ||||||
|  |     > | ||||||
|  |       <view class="absolute top-0 left-0 right-0 bottom-0"> | ||||||
|  |         <view | ||||||
|  |           class="absolute top-0 right-0 w-72 h-72 bg-gradient-to-br from-pink-200/40 to-transparent rounded-full blur-3xl" | ||||||
|  |         ></view> | ||||||
|  |         <view | ||||||
|  |           class="absolute bottom-0 left-0 w-72 h-72 bg-gradient-to-tr from-rose-200/40 to-transparent rounded-full blur-3xl" | ||||||
|  |         ></view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 个人信息卡片 --> | ||||||
|  |     <view class="mx-4 -mt-24 relative"> | ||||||
|  |       <view class="bg-white/95 backdrop-blur-md rounded-3xl shadow-xl p-6 border border-pink-100"> | ||||||
|  |         <view class="flex items-center"> | ||||||
|  |           <view class="relative"> | ||||||
|  |             <image | ||||||
|  |               class="w-24 h-24 rounded-2xl border-4 border-white shadow-xl" | ||||||
|  |               :src="userInfo.avatar || defaultAvatar" | ||||||
|  |               mode="aspectFill" | ||||||
|  |               @click="handleUploadAvatar" | ||||||
|  |             /> | ||||||
|  |             <view | ||||||
|  |               v-if="userInfo.vipLevel" | ||||||
|  |               class="absolute -bottom-2 -right-2 bg-gradient-to-r from-pink-400 to-rose-400 text-white px-3 py-1 rounded-full text-xs flex items-center shadow-lg" | ||||||
|  |             > | ||||||
|  |               <i class="i-carbon-crown text-sm mr-1"></i> | ||||||
|  |               <text>VIP{{ userInfo.points }}</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex-1 ml-6"> | ||||||
|  |             <view class="flex flex-wrap items-center gap-2 mb-3"> | ||||||
|  |               <text class="text-xl font-bold text-gray-800 truncate max-w-[140px]"> | ||||||
|  |                 {{ userInfo.nickName }} | ||||||
|  |               </text> | ||||||
|  |               <view | ||||||
|  |                 class="px-3 py-1 bg-pink-50 text-pink-600 rounded-full text-xs border border-pink-100 whitespace-nowrap" | ||||||
|  |               > | ||||||
|  |                 ID: {{ userInfo.id }} | ||||||
|  |               </view> | ||||||
|  |               <view | ||||||
|  |                 class="px-3 py-1 bg-rose-50 text-rose-600 rounded-full text-xs border border-rose-100 whitespace-nowrap" | ||||||
|  |               > | ||||||
|  |                 <i class="i-carbon-money text-xs mr-1"></i> | ||||||
|  |                 余额: {{ userInfo.points || 0 }} | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |             <view class="flex space-x-3"> | ||||||
|  |               <button | ||||||
|  |                 class="flex-1 bg-gradient-to-r from-pink-400 to-rose-400 text-white text-sm py-2 rounded-xl shadow-md hover:shadow-lg transition-all duration-300 flex items-center justify-center" | ||||||
|  |                 @click="navigateTo('/pages/my/profile')" | ||||||
|  |               > | ||||||
|  |                 <i class="i-carbon-user-profile text-base mr-2"></i> | ||||||
|  |                 <text class="truncate">我的资料</text> | ||||||
|  |               </button> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 数据统计 --> | ||||||
|  |     <!-- <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white/80 backdrop-blur-sm rounded-2xl shadow-lg p-4 border border-gray-100"> | ||||||
|  |         <view class="grid grid-cols-3 gap-4"> | ||||||
|  |           <view class="text-center"> | ||||||
|  |             <view | ||||||
|  |               class="w-12 h-12 bg-sky-50 rounded-full flex items-center justify-center mx-auto mb-2" | ||||||
|  |             > | ||||||
|  |               <i class="i-carbon-calendar text-2xl text-sky-500"></i> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-2xl font-bold text-sky-500 block"> | ||||||
|  |               {{ userInfo.activityCount || 0 }} | ||||||
|  |             </text> | ||||||
|  |             <text class="text-sm text-gray-600">活动</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="text-center"> | ||||||
|  |             <view | ||||||
|  |               class="w-12 h-12 bg-indigo-50 rounded-full flex items-center justify-center mx-auto mb-2" | ||||||
|  |             > | ||||||
|  |               <i class="i-carbon-favorite text-2xl text-indigo-500"></i> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-2xl font-bold text-indigo-500 block"> | ||||||
|  |               {{ userInfo.matchCount || 0 }} | ||||||
|  |             </text> | ||||||
|  |             <text class="text-sm text-gray-600">配对</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="text-center"> | ||||||
|  |             <view | ||||||
|  |               class="w-12 h-12 bg-purple-50 rounded-full flex items-center justify-center mx-auto mb-2" | ||||||
|  |             > | ||||||
|  |               <i class="i-carbon-group text-2xl text-purple-500"></i> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-2xl font-bold text-purple-500 block"> | ||||||
|  |               {{ userInfo.groupCount || 0 }} | ||||||
|  |             </text> | ||||||
|  |             <text class="text-sm text-gray-600">群组</text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> --> | ||||||
|  | 
 | ||||||
|  |     <!-- 功能菜单 --> | ||||||
|  |     <view class="mx-4 mt-6"> | ||||||
|  |       <view | ||||||
|  |         class="bg-white/95 backdrop-blur-md rounded-3xl shadow-xl overflow-hidden border border-pink-100" | ||||||
|  |       > | ||||||
|  |         <view | ||||||
|  |           class="flex items-center justify-between px-6 py-4 border-b border-pink-50 active:bg-pink-50/50 transition-colors" | ||||||
|  |           v-for="(item, index) in menuItems" | ||||||
|  |           :key="index" | ||||||
|  |           @click="navigateTo(item.path)" | ||||||
|  |         > | ||||||
|  |           <view class="flex items-center"> | ||||||
|  |             <view | ||||||
|  |               class="w-12 h-12 rounded-2xl bg-gradient-to-br from-pink-50 to-rose-50 flex items-center justify-center mr-4 shadow-sm" | ||||||
|  |             > | ||||||
|  |               <i :class="item.icon" class="text-xl text-pink-500"></i> | ||||||
|  |             </view> | ||||||
|  |             <view> | ||||||
|  |               <text class="text-base font-medium text-gray-800 block">{{ item.title }}</text> | ||||||
|  |               <text class="text-xs text-gray-500">{{ item.desc }}</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |           <i class="i-carbon-chevron-right text-pink-300"></i> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 退出登录按钮 --> | ||||||
|  |     <view class="mx-4 my-6 mb-20"> | ||||||
|  |       <button | ||||||
|  |         class="w-full h-14 bg-white text-rose-500 rounded-xl text-base font-medium flex items-center justify-center shadow-md hover:shadow-lg transition-all duration-300 border border-rose-100" | ||||||
|  |         @click="handleLogout" | ||||||
|  |       > | ||||||
|  |         退出登录 | ||||||
|  |       </button> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 在线客服 --> | ||||||
|  |     <!-- <view class="fixed bottom-6 left-0 right-0 px-4"> | ||||||
|  |       <button | ||||||
|  |         class="w-full h-12 bg-gradient-to-r from-sky-400 to-indigo-400 text-white rounded-full text-base flex items-center justify-center shadow-lg hover:shadow-xl transition-shadow" | ||||||
|  |         open-type="contact" | ||||||
|  |       > | ||||||
|  |         <i class="i-carbon-chat text-lg mr-2"></i> | ||||||
|  |         在线客服 | ||||||
|  |       </button> | ||||||
|  |     </view> --> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { logoutAPI, updateUserInfoAPI } from '@/service/login' | ||||||
|  | import { uploadImageAPI } from '@/service/file' | ||||||
|  | import type { UserInfo as ApiUserInfo } from '@/service/login/type' | ||||||
|  | import { onShow } from '@dcloudio/uni-app' | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | 
 | ||||||
|  | const userStore = useUserStore() | ||||||
|  | 
 | ||||||
|  | const defaultAvatar = '/static/images/default-avatar.png' | ||||||
|  | 
 | ||||||
|  | interface UserInfo { | ||||||
|  |   avatar: string | ||||||
|  |   name: string | ||||||
|  |   vipLevel: number | ||||||
|  |   userId: string | ||||||
|  |   profileCompletion: number | ||||||
|  |   activityCount: number | ||||||
|  |   matchCount: number | ||||||
|  |   groupCount: number | ||||||
|  |   points: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const userInfo = ref<UserInfo>({ | ||||||
|  |   avatar: '', | ||||||
|  |   name: '未设置', | ||||||
|  |   vipLevel: 0, | ||||||
|  |   userId: '---', | ||||||
|  |   profileCompletion: 0, | ||||||
|  |   activityCount: 0, | ||||||
|  |   matchCount: 0, | ||||||
|  |   groupCount: 0, | ||||||
|  |   points: 0, | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const menuItems = [ | ||||||
|  |   { | ||||||
|  |     title: '我的活动', | ||||||
|  |     desc: '查看已参与的活动', | ||||||
|  |     path: '/pages/my/activities', | ||||||
|  |     icon: 'i-carbon-calendar', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '我的配对', | ||||||
|  |     desc: '查看配对信息', | ||||||
|  |     path: '/pages/my/matches', | ||||||
|  |     icon: 'i-carbon-favorite', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '邀请好友', | ||||||
|  |     desc: '邀请好友加入', | ||||||
|  |     path: '/pages/my/invite', | ||||||
|  |     icon: 'i-carbon-user-multiple', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '消费记录', | ||||||
|  |     desc: '查看消费明细', | ||||||
|  |     path: '/pages/my/spend', | ||||||
|  |     icon: 'i-carbon-money', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '充值记录', | ||||||
|  |     desc: '查看充值', | ||||||
|  |     path: '/pages/my/recharge', | ||||||
|  |     icon: 'i-carbon-wallet', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '退款记录', | ||||||
|  |     desc: '查看退款', | ||||||
|  |     path: '/pages/my/refund', | ||||||
|  |     icon: 'i-carbon-money', | ||||||
|  |   }, | ||||||
|  |   // { | ||||||
|  |   //   title: '投诉', | ||||||
|  |   //   desc: '反馈问题或违规行为', | ||||||
|  |   //   path: '/pages/my/complaint', | ||||||
|  |   //   icon: 'i-carbon-warning', | ||||||
|  |   // }, | ||||||
|  |   { | ||||||
|  |     title: '意见反馈', | ||||||
|  |     desc: '帮助我们提供更好的服务', | ||||||
|  |     path: '/pages/my/feedback', | ||||||
|  |     icon: 'i-carbon-chat-bot', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '我的邀请人', | ||||||
|  |     desc: '查看或绑定邀请人', | ||||||
|  |     path: '/pages/my/inviter', | ||||||
|  |     icon: 'i-carbon-user-follow', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     title: '我的下级', | ||||||
|  |     desc: '查看我的下级', | ||||||
|  |     path: '/pages/my/subordinate', | ||||||
|  |     icon: 'i-carbon-user-multiple', | ||||||
|  |   }, | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | const navigateTo = (url: string) => { | ||||||
|  |   uni.navigateTo({ url }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 退出登录 | ||||||
|  | const handleLogout = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await logoutAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '退出成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |       // 清除本地存储的用户信息 | ||||||
|  |       uni.removeStorageSync('x-token') | ||||||
|  |       uni.removeStorageSync('userInfo') | ||||||
|  |       // 跳转到登录页 | ||||||
|  |       setTimeout(() => { | ||||||
|  |         uni.reLaunch({ | ||||||
|  |           url: '/pages/login/index', | ||||||
|  |         }) | ||||||
|  |       }, 1500) | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '退出失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const handleUploadAvatar = () => { | ||||||
|  |   uni.chooseImage({ | ||||||
|  |     count: 1, | ||||||
|  |     sizeType: ['compressed'], | ||||||
|  |     sourceType: ['album', 'camera'], | ||||||
|  |     success: async (res) => { | ||||||
|  |       const tempFilePath = res.tempFilePaths[0] | ||||||
|  |       try { | ||||||
|  |         // 显示上传中提示 | ||||||
|  |         uni.showLoading({ | ||||||
|  |           title: '上传中...', | ||||||
|  |           mask: true, | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         // 直接上传临时文件路径 | ||||||
|  |         const uploadRes = await uploadImageAPI(tempFilePath, 'avatar') | ||||||
|  | 
 | ||||||
|  |         if (uploadRes.url) { | ||||||
|  |           // 更新用户头像 | ||||||
|  |           const updateRes = await userStore.updateUserInfo({ | ||||||
|  |             avatar: uploadRes.url, | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           if (updateRes.code === 200) { | ||||||
|  |             // 更新本地用户信息 | ||||||
|  |             userInfo.value.avatar = uploadRes.url | ||||||
|  |             uni.showToast({ | ||||||
|  |               title: '头像更新成功', | ||||||
|  |               icon: 'success', | ||||||
|  |             }) | ||||||
|  |           } else { | ||||||
|  |             throw new Error(updateRes.message) | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           throw new Error('上传失败') | ||||||
|  |         } | ||||||
|  |       } catch (error) { | ||||||
|  |         uni.showToast({ | ||||||
|  |           title: error.message || '上传失败', | ||||||
|  |           icon: 'none', | ||||||
|  |         }) | ||||||
|  |       } finally { | ||||||
|  |         uni.hideLoading() | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | onShow(() => { | ||||||
|  |   // 页面显示时获取用户信息 | ||||||
|  |   getUserInfo() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const getUserInfo = async () => { | ||||||
|  |   try { | ||||||
|  |     await userStore.setUserInfo() | ||||||
|  |     userInfo.value = userStore.userInfo as any | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取用户信息失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | // 移除之前的 iconfont 样式,因为现在使用 Carbon 图标 | ||||||
|  | </style> | ||||||
							
								
								
									
										944
									
								
								src/pages/my/profile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,944 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '我的资料', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 头像区域 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="avatar-section"> | ||||||
|  |         <image class="avatar" :src="userInfo.avatar || defaultAvatar" mode="aspectFill" /> | ||||||
|  |         <view class="info"> | ||||||
|  |           <view class="name">{{ userInfo.nickName || '未设置昵称' }}</view> | ||||||
|  |           <view class="id">ID: {{ userInfo.id }}</view> | ||||||
|  |           <view class="location">{{ userInfo.workArea || '未设置工作地区' }}</view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 资料编辑表单 --> | ||||||
|  |     <view class="mx-4"> | ||||||
|  |       <!-- 基本信息 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">基本信息</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">昵称</text> | ||||||
|  |           <input v-model="userInfo.nickName" class="value" placeholder="请输入昵称" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">生日</text> | ||||||
|  |           <picker mode="date" :value="userInfo.birthday" @change="onBirthdayChange" class="value"> | ||||||
|  |             <text>{{ userInfo.birthday || '请选择生日' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">身高(cm)</text> | ||||||
|  |           <input v-model="userInfo.height" type="number" class="value" placeholder="请输入身高" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">体重(kg)</text> | ||||||
|  |           <input v-model="userInfo.weight" type="number" class="value" placeholder="请输入体重" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">婚姻状况</text> | ||||||
|  |           <picker | ||||||
|  |             :range="maritalStatusOptions" | ||||||
|  |             :value="maritalStatusIndex" | ||||||
|  |             @change="onMaritalStatusChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ maritalStatusOptions[maritalStatusIndex] || '请选择婚姻状况' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">血型</text> | ||||||
|  |           <input v-model="userInfo.bloodType" class="value" placeholder="请输入血型" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">民族</text> | ||||||
|  |           <input v-model="userInfo.nation" class="value" placeholder="请输入民族" /> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 工作信息 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">工作信息</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作省份</text> | ||||||
|  |           <picker | ||||||
|  |             :range="provinces" | ||||||
|  |             :value="provinceIndex" | ||||||
|  |             @change="onProvinceChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ userInfo.workProvinceName || '请选择省份' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作城市</text> | ||||||
|  |           <picker | ||||||
|  |             :range="getCurrentCities()" | ||||||
|  |             :value="cityIndex" | ||||||
|  |             @change="onCityChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ userInfo.workCityName || '请选择城市' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作区县</text> | ||||||
|  |           <picker | ||||||
|  |             :range="getCurrentDistricts()" | ||||||
|  |             :value="districtIndex" | ||||||
|  |             @change="onDistrictChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ userInfo.workCountryName || '请选择区县' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作地址</text> | ||||||
|  |           <input v-model="userInfo.workArea" class="value" placeholder="请输入工作地址" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作单位</text> | ||||||
|  |           <input v-model="userInfo.workUnit" class="value" placeholder="请输入工作单位" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">单位类型</text> | ||||||
|  |           <picker | ||||||
|  |             :range="companyTypeOptions" | ||||||
|  |             :value="companyTypeIndex" | ||||||
|  |             @change="onCompanyTypeChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ companyTypeOptions[companyTypeIndex] || '请选择单位类型' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作行业</text> | ||||||
|  |           <input v-model="userInfo.workIndustry" class="value" placeholder="请输入工作行业" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">职业</text> | ||||||
|  |           <input v-model="userInfo.profession" class="value" placeholder="请输入职业" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">月收入</text> | ||||||
|  |           <picker | ||||||
|  |             :range="incomeOptions" | ||||||
|  |             :value="incomeIndex" | ||||||
|  |             @change="onIncomeChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ incomeOptions[incomeIndex] || '请选择月收入' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">学历</text> | ||||||
|  |           <picker | ||||||
|  |             :range="educationOptions" | ||||||
|  |             :value="educationIndex" | ||||||
|  |             @change="onEducationChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ educationOptions[educationIndex] || '请选择学历' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">毕业院校</text> | ||||||
|  |           <input v-model="userInfo.graduationSchool" class="value" placeholder="请输入毕业院校" /> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 住房资产 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">住房资产</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">住房状况</text> | ||||||
|  |           <picker | ||||||
|  |             :range="housingStatusOptions" | ||||||
|  |             :value="housingStatusIndex" | ||||||
|  |             @change="onHousingStatusChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ housingStatusOptions[housingStatusIndex] || '请选择住房状况' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">购车情况</text> | ||||||
|  |           <picker | ||||||
|  |             :range="carOwnershipOptions" | ||||||
|  |             :value="carOwnershipIndex" | ||||||
|  |             @change="onCarOwnershipChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ carOwnershipOptions[carOwnershipIndex] || '请选择购车情况' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 家庭背景 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">家庭背景</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">父母状况</text> | ||||||
|  |           <picker | ||||||
|  |             :range="parentsStatusOptions" | ||||||
|  |             :value="parentsStatusIndex" | ||||||
|  |             @change="onParentsStatusChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ parentsStatusOptions[parentsStatusIndex] || '请选择父母状况' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">兄弟姐妹</text> | ||||||
|  |           <picker | ||||||
|  |             :range="siblingsOptions" | ||||||
|  |             :value="siblingsIndex" | ||||||
|  |             @change="onSiblingsChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ siblingsOptions[siblingsIndex] || '请选择兄弟姐妹' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">与父母同住</text> | ||||||
|  |           <picker | ||||||
|  |             :range="liveWithParentsOptions" | ||||||
|  |             :value="liveWithParentsIndex" | ||||||
|  |             @change="onLiveWithParentsChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text> | ||||||
|  |               {{ liveWithParentsOptions[liveWithParentsIndex] || '请选择是否与父母同住' }} | ||||||
|  |             </text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">婚娶形式</text> | ||||||
|  |           <picker | ||||||
|  |             :range="marriageFormOptions" | ||||||
|  |             :value="marriageFormIndex" | ||||||
|  |             @change="onMarriageFormChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ marriageFormOptions[marriageFormIndex] || '请选择婚娶形式' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">子女情况</text> | ||||||
|  |           <picker | ||||||
|  |             :range="childrenStatusOptions" | ||||||
|  |             :value="childrenStatusIndex" | ||||||
|  |             @change="onChildrenStatusChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ childrenStatusOptions[childrenStatusIndex] || '请选择子女情况' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 生活习惯 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">生活习惯</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">作息习惯</text> | ||||||
|  |           <picker | ||||||
|  |             :range="sleepHabitsOptions" | ||||||
|  |             :value="sleepHabitsIndex" | ||||||
|  |             @change="onSleepHabitsChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ sleepHabitsOptions[sleepHabitsIndex] || '请选择作息习惯' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">锻炼习惯</text> | ||||||
|  |           <picker | ||||||
|  |             :range="exerciseHabitsOptions" | ||||||
|  |             :value="exerciseHabitsIndex" | ||||||
|  |             @change="onExerciseHabitsChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ exerciseHabitsOptions[exerciseHabitsIndex] || '请选择锻炼习惯' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">是否吸烟</text> | ||||||
|  |           <switch | ||||||
|  |             :checked="userInfo.smoke === 1" | ||||||
|  |             @change="(e) => (userInfo.smoke = e.detail.value ? 1 : 0)" | ||||||
|  |           /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">是否饮酒</text> | ||||||
|  |           <switch | ||||||
|  |             :checked="userInfo.drink === 1" | ||||||
|  |             @change="(e) => (userInfo.drink = e.detail.value ? 1 : 0)" | ||||||
|  |           /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">兴趣爱好</text> | ||||||
|  |           <input v-model="userInfo.hobbies" class="value" placeholder="请输入兴趣爱好" /> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 择偶要求 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">择偶要求</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">期望结婚时间</text> | ||||||
|  |           <picker | ||||||
|  |             :range="expectedMarriageTimeOptions" | ||||||
|  |             :value="expectedMarriageTimeIndex" | ||||||
|  |             @change="onExpectedMarriageTimeChange" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text> | ||||||
|  |               {{ expectedMarriageTimeOptions[expectedMarriageTimeIndex] || '请选择期望结婚时间' }} | ||||||
|  |             </text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">婚况要求</text> | ||||||
|  |           <checkbox-group @change="onPartnerMaritalStatusChange"> | ||||||
|  |             <label v-for="(item, index) in partnerMaritalStatusOptions" :key="index"> | ||||||
|  |               <checkbox | ||||||
|  |                 :value="item.value" | ||||||
|  |                 :checked="selectedPartnerMaritalStatus.includes(item.value)" | ||||||
|  |               /> | ||||||
|  |               <text>{{ item.label }}</text> | ||||||
|  |             </label> | ||||||
|  |           </checkbox-group> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">年龄范围</text> | ||||||
|  |           <input v-model="userInfo.ageRange" class="value" placeholder="请输入年龄范围" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">身高范围</text> | ||||||
|  |           <input v-model="userInfo.heightRange" class="value" placeholder="请输入身高范围" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">最低学历</text> | ||||||
|  |           <picker | ||||||
|  |             :range="educationOptions" | ||||||
|  |             :value="Number(userInfo.minEducation) - 1" | ||||||
|  |             @change="(e) => (userInfo.minEducation = String(Number(e.detail.value) + 1))" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text> | ||||||
|  |               {{ educationOptions[Number(userInfo.minEducation) - 1] || '请选择最低学历' }} | ||||||
|  |             </text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">最低收入</text> | ||||||
|  |           <picker | ||||||
|  |             :range="incomeOptions" | ||||||
|  |             :value="Number(userInfo.minIncome) - 1" | ||||||
|  |             @change="(e) => (userInfo.minIncome = String(Number(e.detail.value) + 1))" | ||||||
|  |             class="value" | ||||||
|  |           > | ||||||
|  |             <text>{{ incomeOptions[Number(userInfo.minIncome) - 1] || '请选择最低收入' }}</text> | ||||||
|  |           </picker> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">工作地区要求</text> | ||||||
|  |           <input | ||||||
|  |             v-model="userInfo.partnerWorkArea" | ||||||
|  |             class="value" | ||||||
|  |             placeholder="请输入工作地区要求" | ||||||
|  |           /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">其他要求</text> | ||||||
|  |           <input v-model="userInfo.otherRequirements" class="value" placeholder="请输入其他要求" /> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 联系方式 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">联系方式</text> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">手机号</text> | ||||||
|  |           <input v-model="userInfo.phone" type="number" class="value" placeholder="请输入手机号" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">微信号</text> | ||||||
|  |           <input v-model="userInfo.wechat" class="value" placeholder="请输入微信号" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">QQ</text> | ||||||
|  |           <input v-model="userInfo.qq" type="number" class="value" placeholder="请输入QQ号" /> | ||||||
|  |         </view> | ||||||
|  |         <view class="form-item"> | ||||||
|  |           <text class="label">邮箱</text> | ||||||
|  |           <input v-model="userInfo.email" class="value" placeholder="请输入邮箱" /> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 自我介绍 --> | ||||||
|  |       <view class="card"> | ||||||
|  |         <text class="card-title">自我介绍</text> | ||||||
|  |         <textarea v-model="userInfo.selfIntroduction" placeholder="请输入自我介绍" /> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 保存按钮 --> | ||||||
|  |     <button class="save-button" @click="saveUserInfo">保存修改</button> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import type { UserInfo } from '@/service/login/type' | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | import { provinces, cities, districts } from '@/utils/area-data' | ||||||
|  | 
 | ||||||
|  | const userStore = useUserStore() | ||||||
|  | 
 | ||||||
|  | const defaultAvatar = '/static/images/default-avatar.png' | ||||||
|  | const userInfo = ref<UserInfo>({} as UserInfo) | ||||||
|  | 
 | ||||||
|  | // 婚姻状况选项 | ||||||
|  | const maritalStatusOptions = ['未婚', '离异', '丧偶'] | ||||||
|  | const maritalStatusIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 月收入选项 | ||||||
|  | const incomeOptions = [ | ||||||
|  |   '3000以下', | ||||||
|  |   '3000~5000', | ||||||
|  |   '5000~8000', | ||||||
|  |   '8000~10000', | ||||||
|  |   '10000~20000', | ||||||
|  |   '20000以上', | ||||||
|  | ] | ||||||
|  | const incomeIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 学历选项 | ||||||
|  | const educationOptions = ['初中', '高中', '大专', '本科', '硕士', '博士'] | ||||||
|  | const educationIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 住房状况选项 | ||||||
|  | const housingStatusOptions = [ | ||||||
|  |   '已购房(有贷款)', | ||||||
|  |   '已购房(无贷款)', | ||||||
|  |   '有能力购房', | ||||||
|  |   '无房', | ||||||
|  |   '无房希望对方解决', | ||||||
|  |   '无房希望双方解决', | ||||||
|  |   '与父母同住', | ||||||
|  |   '独自租房', | ||||||
|  |   '与人合租', | ||||||
|  |   '住单位房', | ||||||
|  | ] | ||||||
|  | const housingStatusIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 购车情况选项 | ||||||
|  | const carOwnershipOptions = [ | ||||||
|  |   '无车', | ||||||
|  |   '已购车(经济型)', | ||||||
|  |   '已购车(中档型)', | ||||||
|  |   '已购车(豪华型)', | ||||||
|  |   '单位用车', | ||||||
|  |   '需要时购置', | ||||||
|  | ] | ||||||
|  | const carOwnershipIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 期望结婚时间选项 | ||||||
|  | const expectedMarriageTimeOptions = ['随时', '半年内', '一年内', '两年内', '三年内'] | ||||||
|  | const expectedMarriageTimeIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 父母状况选项 | ||||||
|  | const parentsStatusOptions = ['父母均建在', '只有母亲建在', '只有父亲建在', '父母均以离世'] | ||||||
|  | const parentsStatusIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 兄弟姐妹选项 | ||||||
|  | const siblingsOptions = ['独生子女', '2个', '3个', '4个', '5个'] | ||||||
|  | const siblingsIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 与父母同住选项 | ||||||
|  | const liveWithParentsOptions = ['愿意', '不愿意', '视具体情况而定', '尊重伴侣意见'] | ||||||
|  | const liveWithParentsIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 婚娶形式选项 | ||||||
|  | const marriageFormOptions = ['嫁娶', '两顾', '上门'] | ||||||
|  | const marriageFormIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 子女情况选项 | ||||||
|  | const childrenStatusOptions = ['未育', '子女归自己', '子女归对方'] | ||||||
|  | const childrenStatusIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 修改择偶婚况选项为数字代码 | ||||||
|  | const partnerMaritalStatusOptions = [ | ||||||
|  |   { label: '未婚', value: '1' }, | ||||||
|  |   { label: '离异', value: '2' }, | ||||||
|  |   { label: '丧偶', value: '3' }, | ||||||
|  | ] | ||||||
|  | const selectedPartnerMaritalStatus = ref<string[]>([]) | ||||||
|  | 
 | ||||||
|  | // 作息习惯选项 | ||||||
|  | const sleepHabitsOptions = [ | ||||||
|  |   '早睡早起很规律', | ||||||
|  |   '经常夜猫子', | ||||||
|  |   '总是早起鸟', | ||||||
|  |   '偶尔懒散一下', | ||||||
|  |   '没有规律', | ||||||
|  | ] | ||||||
|  | const sleepHabitsIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 锻炼习惯选项 | ||||||
|  | const exerciseHabitsOptions = [ | ||||||
|  |   '每天锻炼', | ||||||
|  |   '每周至少一次', | ||||||
|  |   '每月几次', | ||||||
|  |   '没时间锻炼', | ||||||
|  |   '集中时间锻炼', | ||||||
|  |   '不喜欢锻炼', | ||||||
|  | ] | ||||||
|  | const exerciseHabitsIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 单位类型选项 | ||||||
|  | const companyTypeOptions = [ | ||||||
|  |   '政府机关', | ||||||
|  |   '事业单位', | ||||||
|  |   '外资企业', | ||||||
|  |   '合资企业', | ||||||
|  |   '国营企业', | ||||||
|  |   '私营企业', | ||||||
|  |   '自有公司', | ||||||
|  |   '其他', | ||||||
|  | ] | ||||||
|  | const companyTypeIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 省市区选择器 | ||||||
|  | const provinceIndex = ref(0) | ||||||
|  | const cityIndex = ref(0) | ||||||
|  | const districtIndex = ref(0) | ||||||
|  | 
 | ||||||
|  | // 当前选中的省市区数据 | ||||||
|  | const currentProvince = ref('') | ||||||
|  | const currentCity = ref('') | ||||||
|  | const currentDistrict = ref('') | ||||||
|  | 
 | ||||||
|  | // 获取当前省份的城市列表 | ||||||
|  | const getCurrentCities = () => { | ||||||
|  |   if (!currentProvince.value) return [] | ||||||
|  |   return cities[currentProvince.value] || [] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取当前城市的区县列表 | ||||||
|  | const getCurrentDistricts = () => { | ||||||
|  |   if (!currentProvince.value || !currentCity.value) return [] | ||||||
|  |   return districts[`${currentProvince.value}-${currentCity.value}`] || [] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 省份选择变化 | ||||||
|  | const onProvinceChange = (e: any) => { | ||||||
|  |   const index = e.detail.value | ||||||
|  |   provinceIndex.value = index | ||||||
|  |   currentProvince.value = provinces[index] | ||||||
|  |   cityIndex.value = 0 | ||||||
|  |   districtIndex.value = 0 | ||||||
|  |   currentCity.value = getCurrentCities()[0] || '' | ||||||
|  |   currentDistrict.value = getCurrentDistricts()[0] || '' | ||||||
|  | 
 | ||||||
|  |   // 更新用户信息 | ||||||
|  |   userInfo.value.workProvinceName = currentProvince.value | ||||||
|  |   userInfo.value.workCityName = currentCity.value | ||||||
|  |   userInfo.value.workCountryName = currentDistrict.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 城市选择变化 | ||||||
|  | const onCityChange = (e: any) => { | ||||||
|  |   const index = e.detail.value | ||||||
|  |   cityIndex.value = index | ||||||
|  |   currentCity.value = getCurrentCities()[index] | ||||||
|  |   districtIndex.value = 0 | ||||||
|  |   currentDistrict.value = getCurrentDistricts()[0] || '' | ||||||
|  | 
 | ||||||
|  |   // 更新用户信息 | ||||||
|  |   userInfo.value.workCityName = currentCity.value | ||||||
|  |   userInfo.value.workCountryName = currentDistrict.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 区县选择变化 | ||||||
|  | const onDistrictChange = (e: any) => { | ||||||
|  |   const index = e.detail.value | ||||||
|  |   districtIndex.value = index | ||||||
|  |   currentDistrict.value = getCurrentDistricts()[index] | ||||||
|  | 
 | ||||||
|  |   // 更新用户信息 | ||||||
|  |   userInfo.value.workCountryName = currentDistrict.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 在获取用户信息时设置省市区索引 | ||||||
|  | const setAreaIndexes = () => { | ||||||
|  |   // 设置省份索引 | ||||||
|  |   const provinceIdx = provinces.findIndex((p) => p === userInfo.value.workProvinceName) | ||||||
|  |   if (provinceIdx !== -1) { | ||||||
|  |     provinceIndex.value = provinceIdx | ||||||
|  |     currentProvince.value = provinces[provinceIdx] | ||||||
|  | 
 | ||||||
|  |     // 设置城市索引 | ||||||
|  |     const cityList = getCurrentCities() | ||||||
|  |     const cityIdx = cityList.findIndex((c) => c === userInfo.value.workCityName) | ||||||
|  |     if (cityIdx !== -1) { | ||||||
|  |       cityIndex.value = cityIdx | ||||||
|  |       currentCity.value = cityList[cityIdx] | ||||||
|  | 
 | ||||||
|  |       // 设置区县索引 | ||||||
|  |       const districtList = getCurrentDistricts() | ||||||
|  |       const districtIdx = districtList.findIndex((d) => d === userInfo.value.workCountryName) | ||||||
|  |       if (districtIdx !== -1) { | ||||||
|  |         districtIndex.value = districtIdx | ||||||
|  |         currentDistrict.value = districtList[districtIdx] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取用户信息 | ||||||
|  | const getUserInfo = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await userStore.setUserInfo() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       userInfo.value = res.data | ||||||
|  |       // 确保nickName字段存在 | ||||||
|  |       if (!userInfo.value.nickName && userInfo.value.nickname) { | ||||||
|  |         userInfo.value.nickName = userInfo.value.nickname | ||||||
|  |       } | ||||||
|  |       // 设置各个选项的索引 | ||||||
|  |       maritalStatusIndex.value = Number(userInfo.value.maritalStatus) - 1 | ||||||
|  |       incomeIndex.value = Number(userInfo.value.monthlyIncome) - 1 | ||||||
|  |       educationIndex.value = Number(userInfo.value.education) - 1 | ||||||
|  |       housingStatusIndex.value = Number(userInfo.value.housingStatus) - 1 | ||||||
|  |       carOwnershipIndex.value = Number(userInfo.value.carOwnership) - 1 | ||||||
|  |       expectedMarriageTimeIndex.value = Number(userInfo.value.expectedMarriageTime) - 1 | ||||||
|  |       parentsStatusIndex.value = Number(userInfo.value.parentsStatus) - 1 | ||||||
|  |       siblingsIndex.value = Number(userInfo.value.siblings) - 1 | ||||||
|  |       liveWithParentsIndex.value = Number(userInfo.value.liveWithParents) - 1 | ||||||
|  |       marriageFormIndex.value = Number(userInfo.value.marriageForm) - 1 | ||||||
|  |       childrenStatusIndex.value = Number(userInfo.value.childrenStatus) - 1 | ||||||
|  |       sleepHabitsIndex.value = Number(userInfo.value.sleepHabits) - 1 | ||||||
|  |       exerciseHabitsIndex.value = Number(userInfo.value.exerciseHabits) - 1 | ||||||
|  |       companyTypeIndex.value = Number(userInfo.value.companyType) - 1 | ||||||
|  | 
 | ||||||
|  |       // 设置省市区索引 | ||||||
|  |       setAreaIndexes() | ||||||
|  | 
 | ||||||
|  |       // 确保minEducation和minIncome是字符串类型 | ||||||
|  |       if (typeof userInfo.value.minEducation !== 'string') { | ||||||
|  |         userInfo.value.minEducation = String(userInfo.value.minEducation || '1') | ||||||
|  |       } | ||||||
|  |       if (typeof userInfo.value.minIncome !== 'string') { | ||||||
|  |         userInfo.value.minIncome = String(userInfo.value.minIncome || '1') | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // 确保smoke和drink是数字类型 | ||||||
|  |       if (typeof userInfo.value.smoke !== 'number') { | ||||||
|  |         userInfo.value.smoke = Number(userInfo.value.smoke || 0) | ||||||
|  |       } | ||||||
|  |       if (typeof userInfo.value.drink !== 'number') { | ||||||
|  |         userInfo.value.drink = Number(userInfo.value.drink || 0) | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // 设置多选值 | ||||||
|  |       if (typeof userInfo.value.partnerMaritalStatus === 'string') { | ||||||
|  |         selectedPartnerMaritalStatus.value = userInfo.value.partnerMaritalStatus | ||||||
|  |           ? userInfo.value.partnerMaritalStatus.split(',') | ||||||
|  |           : [] | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取用户信息失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 保存用户信息 | ||||||
|  | const saveUserInfo = async () => { | ||||||
|  |   try { | ||||||
|  |     // 处理多选值 | ||||||
|  |     userInfo.value.partnerMaritalStatus = selectedPartnerMaritalStatus.value.join(',') | ||||||
|  | 
 | ||||||
|  |     const res = await userStore.updateUserInfo(userInfo.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '保存成功', | ||||||
|  |         icon: 'success', | ||||||
|  |       }) | ||||||
|  |       // 返回上一页 | ||||||
|  |       setTimeout(() => { | ||||||
|  |         uni.navigateBack() | ||||||
|  |       }, 1500) | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('保存用户信息失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '保存失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 各种选择器的change事件处理函数 | ||||||
|  | const onMaritalStatusChange = (e: any) => { | ||||||
|  |   maritalStatusIndex.value = e.detail.value | ||||||
|  |   userInfo.value.maritalStatus = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onHousingStatusChange = (e: any) => { | ||||||
|  |   housingStatusIndex.value = e.detail.value | ||||||
|  |   userInfo.value.housingStatus = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onCarOwnershipChange = (e: any) => { | ||||||
|  |   carOwnershipIndex.value = e.detail.value | ||||||
|  |   userInfo.value.carOwnership = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onExpectedMarriageTimeChange = (e: any) => { | ||||||
|  |   expectedMarriageTimeIndex.value = e.detail.value | ||||||
|  |   userInfo.value.expectedMarriageTime = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onParentsStatusChange = (e: any) => { | ||||||
|  |   parentsStatusIndex.value = e.detail.value | ||||||
|  |   userInfo.value.parentsStatus = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onSiblingsChange = (e: any) => { | ||||||
|  |   siblingsIndex.value = e.detail.value | ||||||
|  |   userInfo.value.siblings = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onLiveWithParentsChange = (e: any) => { | ||||||
|  |   liveWithParentsIndex.value = e.detail.value | ||||||
|  |   userInfo.value.liveWithParents = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onMarriageFormChange = (e: any) => { | ||||||
|  |   marriageFormIndex.value = e.detail.value | ||||||
|  |   userInfo.value.marriageForm = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onChildrenStatusChange = (e: any) => { | ||||||
|  |   childrenStatusIndex.value = e.detail.value | ||||||
|  |   userInfo.value.childrenStatus = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onSleepHabitsChange = (e: any) => { | ||||||
|  |   sleepHabitsIndex.value = e.detail.value | ||||||
|  |   userInfo.value.sleepHabits = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onExerciseHabitsChange = (e: any) => { | ||||||
|  |   exerciseHabitsIndex.value = e.detail.value | ||||||
|  |   userInfo.value.exerciseHabits = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const onCompanyTypeChange = (e: any) => { | ||||||
|  |   companyTypeIndex.value = e.detail.value | ||||||
|  |   userInfo.value.companyType = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 多选处理函数 | ||||||
|  | const onPartnerMaritalStatusChange = (e: any) => { | ||||||
|  |   selectedPartnerMaritalStatus.value = e.detail.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 生日选择器变化 | ||||||
|  | const onBirthdayChange = (e: any) => { | ||||||
|  |   userInfo.value.birthday = e.detail.value | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 收入选择器变化 | ||||||
|  | const onIncomeChange = (e: any) => { | ||||||
|  |   incomeIndex.value = e.detail.value | ||||||
|  |   userInfo.value.monthlyIncome = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 学历选择器变化 | ||||||
|  | const onEducationChange = (e: any) => { | ||||||
|  |   educationIndex.value = e.detail.value | ||||||
|  |   userInfo.value.education = String(Number(e.detail.value) + 1) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getUserInfo() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | // 输入框样式 | ||||||
|  | input { | ||||||
|  |   min-width: 200rpx; | ||||||
|  |   text-align: right; | ||||||
|  |   padding: 4rpx 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 选择器样式 | ||||||
|  | picker { | ||||||
|  |   min-width: 200rpx; | ||||||
|  |   text-align: right; | ||||||
|  |   padding: 4rpx 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 多选框组样式 | ||||||
|  | checkbox-group { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: 16rpx; | ||||||
|  |   padding: 16rpx 0; | ||||||
|  | 
 | ||||||
|  |   label { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     background-color: #f5f5f5; | ||||||
|  |     padding: 8rpx 16rpx; | ||||||
|  |     border-radius: 8rpx; | ||||||
|  | 
 | ||||||
|  |     checkbox { | ||||||
|  |       transform: scale(0.8); | ||||||
|  |       margin-right: 4rpx; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     text { | ||||||
|  |       font-size: 24rpx; | ||||||
|  |       color: #666; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 开关样式 | ||||||
|  | switch { | ||||||
|  |   transform: scale(0.8); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 表单项样式 | ||||||
|  | .form-item { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   padding: 24rpx 0; | ||||||
|  |   border-bottom: 1px solid #f5f5f5; | ||||||
|  | 
 | ||||||
|  |   &:last-child { | ||||||
|  |     border-bottom: none; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .label { | ||||||
|  |     color: #666; | ||||||
|  |     font-size: 28rpx; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .value { | ||||||
|  |     color: #333; | ||||||
|  |     font-size: 28rpx; | ||||||
|  |     text-align: right; | ||||||
|  |     flex: 1; | ||||||
|  |     margin-left: 24rpx; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 文本域样式 | ||||||
|  | textarea { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 200rpx; | ||||||
|  |   background-color: #f5f5f5; | ||||||
|  |   border-radius: 12rpx; | ||||||
|  |   padding: 24rpx; | ||||||
|  |   font-size: 28rpx; | ||||||
|  |   color: #333; | ||||||
|  |   margin-top: 16rpx; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 保存按钮样式 | ||||||
|  | .save-button { | ||||||
|  |   position: fixed; | ||||||
|  |   bottom: 48rpx; | ||||||
|  |   left: 48rpx; | ||||||
|  |   right: 48rpx; | ||||||
|  |   height: 88rpx; | ||||||
|  |   background: linear-gradient(to right, #3b82f6, #6366f1); | ||||||
|  |   color: white; | ||||||
|  |   border-radius: 44rpx; | ||||||
|  |   font-size: 32rpx; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3); | ||||||
|  | 
 | ||||||
|  |   &:active { | ||||||
|  |     transform: scale(0.98); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 卡片样式 | ||||||
|  | .card { | ||||||
|  |   background-color: white; | ||||||
|  |   border-radius: 24rpx; | ||||||
|  |   padding: 32rpx; | ||||||
|  |   margin-bottom: 24rpx; | ||||||
|  |   box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||||||
|  | 
 | ||||||
|  |   .card-title { | ||||||
|  |     font-size: 32rpx; | ||||||
|  |     font-weight: bold; | ||||||
|  |     color: #333; | ||||||
|  |     margin-bottom: 32rpx; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 头像区域样式 | ||||||
|  | .avatar-section { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 32rpx; | ||||||
|  |   background-color: white; | ||||||
|  |   border-radius: 24rpx; | ||||||
|  |   margin-bottom: 24rpx; | ||||||
|  |   box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); | ||||||
|  | 
 | ||||||
|  |   .avatar { | ||||||
|  |     width: 120rpx; | ||||||
|  |     height: 120rpx; | ||||||
|  |     border-radius: 60rpx; | ||||||
|  |     border: 4rpx solid #f5f5f5; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .info { | ||||||
|  |     margin-left: 24rpx; | ||||||
|  |     flex: 1; | ||||||
|  | 
 | ||||||
|  |     .name { | ||||||
|  |       font-size: 32rpx; | ||||||
|  |       font-weight: bold; | ||||||
|  |       color: #333; | ||||||
|  |       margin-bottom: 8rpx; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .id { | ||||||
|  |       font-size: 24rpx; | ||||||
|  |       color: #999; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .location { | ||||||
|  |       font-size: 26rpx; | ||||||
|  |       color: #666; | ||||||
|  |       margin-top: 8rpx; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										157
									
								
								src/pages/my/recharge.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,157 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '充值记录', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 消费统计 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-2 block">充值统计</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-sky-500 block">¥{{ totalSpend }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">总充值数</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-indigo-500 block">{{ spendCount }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">充值次数</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 消费记录列表 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm overflow-hidden"> | ||||||
|  |         <view class="p-4 border-b border-gray-100 flex justify-between items-center"> | ||||||
|  |           <text class="text-base font-bold text-gray-800">充值明细</text> | ||||||
|  |           <text class="text-sm text-sky-500" @tap="refreshData">刷新</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-if="loading" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">加载中...</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="error" class="p-8 text-center"> | ||||||
|  |           <text class="text-red-500">{{ error }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="spendList.length === 0" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">暂无充值记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else class="divide-y divide-gray-100"> | ||||||
|  |           <view | ||||||
|  |             v-for="(item, index) in spendList" | ||||||
|  |             :key="index" | ||||||
|  |             class="p-4 flex items-center justify-between" | ||||||
|  |           > | ||||||
|  |             <view> | ||||||
|  |               <view class="flex items-center mb-1"> | ||||||
|  |                 <view | ||||||
|  |                   class="bg-pink-50 text-pink-600 px-2 py-0.5 rounded-full text-xs border border-pink-100 mr-2" | ||||||
|  |                 > | ||||||
|  |                   订单号 | ||||||
|  |                 </view> | ||||||
|  |                 <text class="text-gray-800 font-mono">{{ item.orderNo }}</text> | ||||||
|  |               </view> | ||||||
|  |               <text class="text-sm text-gray-500">{{ formatDate(item.createTime) }}</text> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-lg font-bold text-green-500"> | ||||||
|  |               +¥{{ formatPrice(item.payPoints) }} | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed, onMounted } from 'vue' | ||||||
|  | import { getUserPayInfoAPI } from '@/service/login' | ||||||
|  | import type { PayInfoList } from '@/service/login/type' | ||||||
|  | 
 | ||||||
|  | const spendList = ref<PayInfoList[]>([]) | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | 
 | ||||||
|  | // 计算总消费 | ||||||
|  | const totalSpend = computed(() => { | ||||||
|  |   return spendList.value.reduce((sum, item) => sum + Number(item.payPoints), 0).toFixed(2) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 计算消费次数 | ||||||
|  | const spendCount = computed(() => spendList.value.length) | ||||||
|  | 
 | ||||||
|  | // 获取消费类型文本 | ||||||
|  | const getSpendTypeText = (type: number) => { | ||||||
|  |   const typeMap: Record<number, string> = { | ||||||
|  |     1: '解锁用户', | ||||||
|  |     2: '充值', | ||||||
|  |     3: '其他', | ||||||
|  |   } | ||||||
|  |   return typeMap[type] || '未知类型' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化日期 | ||||||
|  | const formatDate = (dateStr: string) => { | ||||||
|  |   try { | ||||||
|  |     const date = new Date(dateStr) | ||||||
|  |     return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}` | ||||||
|  |   } catch (e) { | ||||||
|  |     return dateStr | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化价格 | ||||||
|  | const formatPrice = (price: string) => { | ||||||
|  |   try { | ||||||
|  |     return Number(price).toFixed(2) | ||||||
|  |   } catch (e) { | ||||||
|  |     return '0.00' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取消费记录 | ||||||
|  | const getSpendList = async () => { | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const res = await getUserPayInfoAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       spendList.value = res.data || [] | ||||||
|  |     } else { | ||||||
|  |       error.value = res.message || '获取充值记录失败' | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: error.value, | ||||||
|  |         icon: 'none', | ||||||
|  |         duration: 2000, | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (e) { | ||||||
|  |     error.value = '网络请求失败,请稍后重试' | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 刷新数据 | ||||||
|  | const refreshData = () => { | ||||||
|  |   getSpendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getSpendList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										240
									
								
								src/pages/my/refund.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,240 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '退款记录', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 消费统计 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-2 block">退款统计</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-sky-500 block">¥{{ totalSpend }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">总退款数</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-indigo-500 block">{{ spendCount }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">退款次数</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 消费记录列表 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm overflow-hidden"> | ||||||
|  |         <view class="p-4 border-b border-gray-100 flex justify-between items-center"> | ||||||
|  |           <text class="text-base font-bold text-gray-800">退款明细</text> | ||||||
|  |           <text class="text-sm text-sky-500" @tap="refreshData">刷新</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-if="loading" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">加载中...</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="error" class="p-8 text-center"> | ||||||
|  |           <text class="text-red-500">{{ error }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="spendList.length === 0" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">暂无退款记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else class="divide-y divide-gray-100"> | ||||||
|  |           <view v-for="(item, index) in spendList" :key="index" class="p-4"> | ||||||
|  |             <!-- 顶部:退款金额和状态 --> | ||||||
|  |             <view class="flex items-center justify-between mb-3"> | ||||||
|  |               <text class="text-lg font-bold text-rose-500"> | ||||||
|  |                 -¥{{ formatPrice(item.refundPoints) }} | ||||||
|  |               </text> | ||||||
|  |               <view | ||||||
|  |                 :class="[ | ||||||
|  |                   'px-3 py-1 rounded-full text-xs border', | ||||||
|  |                   item.refundStatus === '0' | ||||||
|  |                     ? 'bg-yellow-50 text-yellow-600 border-yellow-100' | ||||||
|  |                     : item.refundStatus === '1' | ||||||
|  |                       ? 'bg-green-50 text-green-600 border-green-100' | ||||||
|  |                       : 'bg-red-50 text-red-600 border-red-100', | ||||||
|  |                 ]" | ||||||
|  |               > | ||||||
|  |                 {{ getRefundStatusText(item.refundStatus) }} | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  | 
 | ||||||
|  |             <!-- 中间:退款原因 --> | ||||||
|  |             <view class="mb-3"> | ||||||
|  |               <view class="flex items-center mb-1"> | ||||||
|  |                 <view | ||||||
|  |                   class="bg-sky-50 text-sky-600 px-2 py-0.5 rounded-full text-xs border border-sky-100 mr-2" | ||||||
|  |                 > | ||||||
|  |                   退款原因 | ||||||
|  |                 </view> | ||||||
|  |                 <text class="text-gray-600 text-sm flex-1">{{ item.refundReason }}</text> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  | 
 | ||||||
|  |             <!-- 底部:时间和操作按钮 --> | ||||||
|  |             <view class="flex items-center justify-between"> | ||||||
|  |               <view class="flex flex-col space-y-1"> | ||||||
|  |                 <text class="text-xs text-gray-500"> | ||||||
|  |                   申请时间:{{ formatDate(item.createTime) }} | ||||||
|  |                 </text> | ||||||
|  |                 <text v-if="item.auditTime" class="text-xs text-gray-500"> | ||||||
|  |                   审核时间:{{ formatDate(item.auditTime) }} | ||||||
|  |                 </text> | ||||||
|  |               </view> | ||||||
|  |               <button | ||||||
|  |                 v-if="item.refundStatus === '0'" | ||||||
|  |                 class="flex items-center justify-center bg-white border border-green-200 text-green-500 px-4 py-1.5 rounded-lg text-xs hover:bg-green-50 active:bg-green-100 transition-colors mr-0" | ||||||
|  |                 @tap="handleCancelRefund(item.id)" | ||||||
|  |               > | ||||||
|  |                 <i class="i-carbon-close text-sm mr-1"></i> | ||||||
|  |                 撤销 | ||||||
|  |               </button> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed, onMounted } from 'vue' | ||||||
|  | import { getUserRefundInfoAPI, cancelRefundAPI } from '@/service/login' | ||||||
|  | import type { RefundInfoList } from '@/service/login/type' | ||||||
|  | 
 | ||||||
|  | const spendList = ref<RefundInfoList[]>([]) | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | 
 | ||||||
|  | // 计算总消费 | ||||||
|  | const totalSpend = computed(() => { | ||||||
|  |   return spendList.value.reduce((sum, item) => sum + Number(item.refundPoints), 0).toFixed(2) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 计算消费次数 | ||||||
|  | const spendCount = computed(() => spendList.value.length) | ||||||
|  | 
 | ||||||
|  | // 获取消费类型文本 | ||||||
|  | const getSpendTypeText = (type: number) => { | ||||||
|  |   const typeMap: Record<number, string> = { | ||||||
|  |     1: '解锁用户', | ||||||
|  |     2: '充值', | ||||||
|  |     3: '其他', | ||||||
|  |   } | ||||||
|  |   return typeMap[type] || '未知类型' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化日期 | ||||||
|  | const formatDate = (dateStr: string) => { | ||||||
|  |   try { | ||||||
|  |     const date = new Date(dateStr) | ||||||
|  |     return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}` | ||||||
|  |   } catch (e) { | ||||||
|  |     return dateStr | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化价格 | ||||||
|  | const formatPrice = (price: string) => { | ||||||
|  |   try { | ||||||
|  |     return Number(price).toFixed(2) | ||||||
|  |   } catch (e) { | ||||||
|  |     return '0.00' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取消费记录 | ||||||
|  | const getSpendList = async () => { | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const res = await getUserRefundInfoAPI() | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       spendList.value = res.data || [] | ||||||
|  |     } else { | ||||||
|  |       error.value = res.message || '获取充值记录失败' | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: error.value, | ||||||
|  |         icon: 'none', | ||||||
|  |         duration: 2000, | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (e) { | ||||||
|  |     error.value = '网络请求失败,请稍后重试' | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 刷新数据 | ||||||
|  | const refreshData = () => { | ||||||
|  |   getSpendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取退款状态文本 | ||||||
|  | const getRefundStatusText = (status: string) => { | ||||||
|  |   const statusMap: Record<string, string> = { | ||||||
|  |     '0': '审核中', | ||||||
|  |     '1': '已通过', | ||||||
|  |     '2': '已拒绝', | ||||||
|  |   } | ||||||
|  |   return statusMap[status] || '未知状态' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 处理撤销退款 | ||||||
|  | const handleCancelRefund = async (id: string) => { | ||||||
|  |   try { | ||||||
|  |     uni.showModal({ | ||||||
|  |       title: '提示', | ||||||
|  |       content: '确定要撤销该退款申请吗?', | ||||||
|  |       success: async (res) => { | ||||||
|  |         if (res.confirm) { | ||||||
|  |           uni.showLoading({ | ||||||
|  |             title: '处理中...', | ||||||
|  |             mask: true, | ||||||
|  |           }) | ||||||
|  | 
 | ||||||
|  |           const result = await cancelRefundAPI(id) | ||||||
|  | 
 | ||||||
|  |           if (result.code === 200) { | ||||||
|  |             uni.showToast({ | ||||||
|  |               title: '撤销成功', | ||||||
|  |               icon: 'success', | ||||||
|  |             }) | ||||||
|  |             // 刷新列表 | ||||||
|  |             getSpendList() | ||||||
|  |           } else { | ||||||
|  |             uni.showToast({ | ||||||
|  |               title: result.message || '撤销失败', | ||||||
|  |               icon: 'none', | ||||||
|  |             }) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '操作失败,请稍后重试', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     uni.hideLoading() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getSpendList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										181
									
								
								src/pages/my/spend-detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,181 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '消费记录', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 消费统计 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-2 block">消费统计</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-sky-500 block">¥{{ totalSpend }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">总消费</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-indigo-500 block">{{ spendCount }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">消费次数</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 消费记录列表 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm overflow-hidden"> | ||||||
|  |         <view class="p-4 border-b border-gray-100 flex justify-between items-center"> | ||||||
|  |           <text class="text-base font-bold text-gray-800">消费明细</text> | ||||||
|  |           <text class="text-sm text-sky-500" @tap="refreshData">刷新</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-if="loading" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">加载中...</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="error" class="p-8 text-center"> | ||||||
|  |           <text class="text-red-500">{{ error }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="spendList.length === 0" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">暂无消费记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else class="divide-y divide-gray-100"> | ||||||
|  |           <view | ||||||
|  |             v-for="(item, index) in spendList" | ||||||
|  |             :key="index" | ||||||
|  |             class="p-4 flex items-center justify-between" | ||||||
|  |           > | ||||||
|  |             <view> | ||||||
|  |               <text | ||||||
|  |                 :class="[ | ||||||
|  |                   'block text-sm', | ||||||
|  |                   item.spendType === '1' | ||||||
|  |                     ? 'text-yellow-300 font-semibold border-l-2 border-purple-500 pl-2' | ||||||
|  |                     : item.spendType === '2' | ||||||
|  |                       ? 'text-emerald-300 font-semibold border-l-2 border-emerald-500 pl-2' | ||||||
|  |                       : 'text-gray-600 border-l-2 border-gray-300 pl-2', | ||||||
|  |                 ]" | ||||||
|  |               > | ||||||
|  |                 {{ getSpendTypeText(item.spendType) }} | ||||||
|  |               </text> | ||||||
|  |               <text class="text-sm text-gray-500">{{ formatDate(item.createTime) }}</text> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-lg font-bold text-red-500">-¥{{ formatPrice(item.spendPrice) }}</text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed, onMounted } from 'vue' | ||||||
|  | import { getUserSpendHistoryAPI } from '@/service/login' | ||||||
|  | import type { SpendHistoryItem } from '@/service/login/type' | ||||||
|  | 
 | ||||||
|  | const spendList = ref<SpendHistoryItem[]>([]) | ||||||
|  | const userId = ref('') | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | 
 | ||||||
|  | // 获取消费记录 | ||||||
|  | const getSpendList = async () => { | ||||||
|  |   if (!userId.value) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const res = await getUserSpendHistoryAPI(userId.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       spendList.value = res.data || [] | ||||||
|  |     } else { | ||||||
|  |       error.value = res.message || '获取消费记录失败' | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: error.value, | ||||||
|  |         icon: 'none', | ||||||
|  |         duration: 2000, | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (e) { | ||||||
|  |     error.value = '网络请求失败,请稍后重试' | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 刷新数据 | ||||||
|  | const refreshData = () => { | ||||||
|  |   getSpendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 计算总消费 | ||||||
|  | const totalSpend = computed(() => { | ||||||
|  |   return spendList.value.reduce((sum, item) => sum + Number(item.spendPrice), 0).toFixed(2) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 计算消费次数 | ||||||
|  | const spendCount = computed(() => spendList.value.length) | ||||||
|  | 
 | ||||||
|  | // 获取消费类型文本 | ||||||
|  | const getSpendTypeText = (type: number) => { | ||||||
|  |   const typeMap: Record<number, string> = { | ||||||
|  |     1: '解锁用户', | ||||||
|  |     2: '参加活动', | ||||||
|  |     3: '其他', | ||||||
|  |   } | ||||||
|  |   return typeMap[type] || '未知类型' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化日期 | ||||||
|  | const formatDate = (dateStr: string) => { | ||||||
|  |   try { | ||||||
|  |     const date = new Date(dateStr) | ||||||
|  |     return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}` | ||||||
|  |   } catch (e) { | ||||||
|  |     return dateStr | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化价格 | ||||||
|  | const formatPrice = (price: string) => { | ||||||
|  |   try { | ||||||
|  |     return Number(price).toFixed(2) | ||||||
|  |   } catch (e) { | ||||||
|  |     return '0.00' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 页面加载时获取参数 | ||||||
|  | onLoad((options: Record<string, string>) => { | ||||||
|  |   console.log('onLoad参数:', options) | ||||||
|  |   if (options.id) { | ||||||
|  |     userId.value = options.id | ||||||
|  |     console.log('设置的用户ID:', userId.value) | ||||||
|  |     getSpendList() | ||||||
|  |   } else { | ||||||
|  |     error.value = '未获取到用户信息' | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getSpendList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										185
									
								
								src/pages/my/spend.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,185 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '消费记录', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 消费统计 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-6"> | ||||||
|  |         <view class="text-center"> | ||||||
|  |           <text class="text-lg font-bold text-gray-800 mb-2 block">消费统计</text> | ||||||
|  |           <view class="grid grid-cols-2 gap-4"> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-sky-500 block">¥{{ totalSpend }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">总消费</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="text-center"> | ||||||
|  |               <text class="text-2xl font-bold text-indigo-500 block">{{ spendCount }}</text> | ||||||
|  |               <text class="text-sm text-gray-500">消费次数</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 消费记录列表 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm overflow-hidden"> | ||||||
|  |         <view class="p-4 border-b border-gray-100 flex justify-between items-center"> | ||||||
|  |           <text class="text-base font-bold text-gray-800">消费明细</text> | ||||||
|  |           <text class="text-sm text-sky-500" @tap="refreshData">刷新</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-if="loading" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">加载中...</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="error" class="p-8 text-center"> | ||||||
|  |           <text class="text-red-500">{{ error }}</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else-if="spendList.length === 0" class="p-8 text-center"> | ||||||
|  |           <text class="text-gray-500">暂无消费记录</text> | ||||||
|  |         </view> | ||||||
|  |         <view v-else class="divide-y divide-gray-100"> | ||||||
|  |           <view | ||||||
|  |             v-for="(item, index) in spendList" | ||||||
|  |             :key="index" | ||||||
|  |             class="p-4 flex items-center justify-between" | ||||||
|  |           > | ||||||
|  |             <view> | ||||||
|  |               <text | ||||||
|  |                 :class="[ | ||||||
|  |                   'block text-sm', | ||||||
|  |                   item.spendType === '1' | ||||||
|  |                     ? 'text-yellow-300 font-semibold border-l-2 border-purple-500 pl-2' | ||||||
|  |                     : item.spendType === '2' | ||||||
|  |                       ? 'text-emerald-300 font-semibold border-l-2 border-emerald-500 pl-2' | ||||||
|  |                       : 'text-gray-600 border-l-2 border-gray-300 pl-2', | ||||||
|  |                 ]" | ||||||
|  |               > | ||||||
|  |                 {{ getSpendTypeText(item.spendType) }} | ||||||
|  |               </text> | ||||||
|  |               <text class="text-sm text-gray-500">{{ formatDate(item.createTime) }}</text> | ||||||
|  |             </view> | ||||||
|  |             <text class="text-lg font-bold text-red-500">-¥{{ formatPrice(item.spendPrice) }}</text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, computed, onMounted } from 'vue' | ||||||
|  | import { getUserSpendHistoryAPI } from '@/service/login' | ||||||
|  | import type { SpendHistoryItem } from '@/service/login/type' | ||||||
|  | 
 | ||||||
|  | const spendList = ref<SpendHistoryItem[]>([]) | ||||||
|  | const userId = ref('') | ||||||
|  | const loading = ref(false) | ||||||
|  | const error = ref('') | ||||||
|  | 
 | ||||||
|  | // 获取用户ID | ||||||
|  | const getUserInfo = () => { | ||||||
|  |   try { | ||||||
|  |     const userInfo = uni.getStorageSync('loginData') | ||||||
|  |     if (!userInfo || !userInfo.id) { | ||||||
|  |       error.value = '未获取到用户信息,请重新登录' | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  |     userId.value = userInfo.id | ||||||
|  |     return true | ||||||
|  |   } catch (e) { | ||||||
|  |     error.value = '获取用户信息失败' | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 计算总消费 | ||||||
|  | const totalSpend = computed(() => { | ||||||
|  |   return spendList.value.reduce((sum, item) => sum + Number(item.spendPrice), 0).toFixed(2) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 计算消费次数 | ||||||
|  | const spendCount = computed(() => spendList.value.length) | ||||||
|  | 
 | ||||||
|  | // 获取消费类型文本 | ||||||
|  | const getSpendTypeText = (type: number) => { | ||||||
|  |   const typeMap: Record<number, string> = { | ||||||
|  |     1: '解锁用户', | ||||||
|  |     2: '参加活动', | ||||||
|  |     3: '其他', | ||||||
|  |   } | ||||||
|  |   return typeMap[type] || '未知类型' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化日期 | ||||||
|  | const formatDate = (dateStr: string) => { | ||||||
|  |   try { | ||||||
|  |     const date = new Date(dateStr) | ||||||
|  |     return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}` | ||||||
|  |   } catch (e) { | ||||||
|  |     return dateStr | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 格式化价格 | ||||||
|  | const formatPrice = (price: string) => { | ||||||
|  |   try { | ||||||
|  |     return Number(price).toFixed(2) | ||||||
|  |   } catch (e) { | ||||||
|  |     return '0.00' | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取消费记录 | ||||||
|  | const getSpendList = async () => { | ||||||
|  |   if (!getUserInfo()) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   loading.value = true | ||||||
|  |   error.value = '' | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     const res = await getUserSpendHistoryAPI(userId.value) | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       spendList.value = res.data || [] | ||||||
|  |     } else { | ||||||
|  |       error.value = res.message || '获取消费记录失败' | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: error.value, | ||||||
|  |         icon: 'none', | ||||||
|  |         duration: 2000, | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } catch (e) { | ||||||
|  |     error.value = '网络请求失败,请稍后重试' | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: error.value, | ||||||
|  |       icon: 'none', | ||||||
|  |       duration: 2000, | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 刷新数据 | ||||||
|  | const refreshData = () => { | ||||||
|  |   getSpendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getSpendList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										240
									
								
								src/pages/my/subordinate.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,240 @@ | |||||||
|  | <template> | ||||||
|  |   <view class="subordinate-container"> | ||||||
|  |     <view class="header"> | ||||||
|  |       <text class="title">我的下级</text> | ||||||
|  |     </view> | ||||||
|  |     <view v-if="loading" class="loading-state"> | ||||||
|  |       <text class="loading-text">加载中...</text> | ||||||
|  |     </view> | ||||||
|  |     <view v-else-if="subordinates.length === 0" class="empty-state"> | ||||||
|  |       <text class="empty-text">暂无下级用户</text> | ||||||
|  |     </view> | ||||||
|  |     <view v-else class="subordinate-grid"> | ||||||
|  |       <view class="subordinate-card" v-for="item in subordinates" :key="item.id"> | ||||||
|  |         <view class="card-content"> | ||||||
|  |           <view class="user-avatar"> | ||||||
|  |             <text class="avatar-text"> | ||||||
|  |               {{ item?.nickname?.charAt?.(0) ?? '' }} | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |           <view class="user-info"> | ||||||
|  |             <view class="user-header"> | ||||||
|  |               <text class="user-name">{{ item.nickname }}</text> | ||||||
|  |               <text class="user-id">ID: {{ item.id }}</text> | ||||||
|  |             </view> | ||||||
|  |             <view v-if="item.child && item.child.length > 0" class="child-list"> | ||||||
|  |               <text class="child-count">直接下级: {{ item.child.length }}人</text> | ||||||
|  |               <view class="child-container"> | ||||||
|  |                 <view v-for="child in item.child" :key="child.id" class="child-item"> | ||||||
|  |                   <view class="child-content"> | ||||||
|  |                     <view class="child-avatar"> | ||||||
|  |                       <text class="child-avatar-text"> | ||||||
|  |                         {{ child?.nickname?.charAt?.(0) ?? '' }} | ||||||
|  |                       </text> | ||||||
|  |                     </view> | ||||||
|  |                     <view class="child-info"> | ||||||
|  |                       <text class="child-name">{{ child.nickname }}</text> | ||||||
|  |                       <text class="child-id">ID: {{ child.id }}</text> | ||||||
|  |                     </view> | ||||||
|  |                   </view> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import { getMyInviteListAPI } from '@/service/login' | ||||||
|  | import type { InviteUserItem } from '@/service/login/type' | ||||||
|  | 
 | ||||||
|  | defineOptions({ | ||||||
|  |   name: 'SubordinateList', | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // 加载状态 | ||||||
|  | const loading = ref(true) | ||||||
|  | // 下级用户数据 | ||||||
|  | const subordinates = ref<InviteUserItem[]>([]) | ||||||
|  | 
 | ||||||
|  | // 获取下级用户列表 | ||||||
|  | const fetchSubordinates = async () => { | ||||||
|  |   try { | ||||||
|  |     loading.value = true | ||||||
|  |     const res = await getMyInviteListAPI() | ||||||
|  |     if (res.code === 200 && res.data) { | ||||||
|  |       console.log(666, res) | ||||||
|  | 
 | ||||||
|  |       subordinates.value = res.data | ||||||
|  |       loading.value = false | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取下级用户列表失败:', error) | ||||||
|  |   } finally { | ||||||
|  |     loading.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   fetchSubordinates() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .subordinate-container { | ||||||
|  |   min-height: 100vh; | ||||||
|  |   padding: 24rpx; | ||||||
|  |   background-color: #f5f7fa; | ||||||
|  | 
 | ||||||
|  |   .header { | ||||||
|  |     margin-bottom: 32rpx; | ||||||
|  | 
 | ||||||
|  |     .title { | ||||||
|  |       font-size: 36rpx; | ||||||
|  |       font-weight: 600; | ||||||
|  |       color: #333; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .loading-state, | ||||||
|  |   .empty-state { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: center; | ||||||
|  |     padding: 48rpx 0; | ||||||
|  | 
 | ||||||
|  |     .loading-text, | ||||||
|  |     .empty-text { | ||||||
|  |       font-size: 28rpx; | ||||||
|  |       color: #909399; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .subordinate-grid { | ||||||
|  |     display: grid; | ||||||
|  |     gap: 24rpx; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .subordinate-card { | ||||||
|  |     background: #ffffff; | ||||||
|  |     border-radius: 16rpx; | ||||||
|  |     box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); | ||||||
|  |     transition: all 0.3s ease; | ||||||
|  | 
 | ||||||
|  |     &:active { | ||||||
|  |       transform: scale(0.98); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .card-content { | ||||||
|  |       display: flex; | ||||||
|  |       align-items: flex-start; | ||||||
|  |       padding: 24rpx; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .user-avatar { | ||||||
|  |       display: flex; | ||||||
|  |       flex-shrink: 0; | ||||||
|  |       align-items: center; | ||||||
|  |       justify-content: center; | ||||||
|  |       width: 88rpx; | ||||||
|  |       height: 88rpx; | ||||||
|  |       margin-right: 24rpx; | ||||||
|  |       background: linear-gradient(135deg, #4a90e2, #357abd); | ||||||
|  |       border-radius: 50%; | ||||||
|  | 
 | ||||||
|  |       .avatar-text { | ||||||
|  |         font-size: 36rpx; | ||||||
|  |         font-weight: 600; | ||||||
|  |         color: #ffffff; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .user-info { | ||||||
|  |       flex: 1; | ||||||
|  | 
 | ||||||
|  |       .user-header { | ||||||
|  |         display: flex; | ||||||
|  |         align-items: center; | ||||||
|  |         justify-content: space-between; | ||||||
|  |         margin-bottom: 16rpx; | ||||||
|  | 
 | ||||||
|  |         .user-name { | ||||||
|  |           font-size: 32rpx; | ||||||
|  |           font-weight: 600; | ||||||
|  |           color: #333; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .user-id { | ||||||
|  |           font-size: 24rpx; | ||||||
|  |           color: #909399; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .child-list { | ||||||
|  |       padding-top: 16rpx; | ||||||
|  |       margin-top: 16rpx; | ||||||
|  |       border-top: 2rpx solid #f0f2f5; | ||||||
|  | 
 | ||||||
|  |       .child-count { | ||||||
|  |         display: block; | ||||||
|  |         margin-bottom: 16rpx; | ||||||
|  |         font-size: 26rpx; | ||||||
|  |         color: #606266; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       .child-container { | ||||||
|  |         padding-left: 24rpx; | ||||||
|  |         border-left: 4rpx solid #e8eaf6; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       .child-item { | ||||||
|  |         margin-bottom: 16rpx; | ||||||
|  | 
 | ||||||
|  |         &:last-child { | ||||||
|  |           margin-bottom: 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .child-content { | ||||||
|  |           display: flex; | ||||||
|  |           align-items: center; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .child-avatar { | ||||||
|  |           display: flex; | ||||||
|  |           align-items: center; | ||||||
|  |           justify-content: center; | ||||||
|  |           width: 64rpx; | ||||||
|  |           height: 64rpx; | ||||||
|  |           margin-right: 16rpx; | ||||||
|  |           background: linear-gradient(135deg, #e8eaf6, #c5cae9); | ||||||
|  |           border-radius: 50%; | ||||||
|  | 
 | ||||||
|  |           .child-avatar-text { | ||||||
|  |             font-size: 28rpx; | ||||||
|  |             font-weight: 600; | ||||||
|  |             color: #4a90e2; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .child-info { | ||||||
|  |           .child-name { | ||||||
|  |             margin-right: 12rpx; | ||||||
|  |             font-size: 28rpx; | ||||||
|  |             color: #333; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           .child-id { | ||||||
|  |             font-size: 24rpx; | ||||||
|  |             color: #909399; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										169
									
								
								src/pages/my/user-info.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,169 @@ | |||||||
|  | <route lang="json5" type="page"> | ||||||
|  | { | ||||||
|  |   layout: 'default', | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '用户信息', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50 pb-20"> | ||||||
|  |     <!-- 用户信息卡片 --> | ||||||
|  |     <view class="mx-4 mt-4"> | ||||||
|  |       <view class="bg-white rounded-2xl shadow-sm p-4"> | ||||||
|  |         <view class="flex items-center mb-4"> | ||||||
|  |           <image | ||||||
|  |             class="w-20 h-20 rounded-full border-2 border-gray-100" | ||||||
|  |             :src="userInfo.avatar || defaultAvatar" | ||||||
|  |             mode="aspectFill" | ||||||
|  |           /> | ||||||
|  |           <view class="ml-4 flex-1"> | ||||||
|  |             <view class="flex items-center mb-2"> | ||||||
|  |               <text class="text-lg font-bold text-gray-800"> | ||||||
|  |                 {{ userInfo.nickname || '未设置昵称' }} | ||||||
|  |               </text> | ||||||
|  |               <text class="ml-2 text-sm text-gray-500">ID: {{ userInfo.id }}</text> | ||||||
|  |             </view> | ||||||
|  |             <view class="text-sm text-gray-600"> | ||||||
|  |               <text>{{ userInfo.workArea || '未设置工作地区' }}</text> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  | 
 | ||||||
|  |         <!-- 基本信息列表 --> | ||||||
|  |         <view class="mt-4 space-y-4"> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">手机号</text> | ||||||
|  |             <text class="text-gray-800">{{ userInfo.phone || '未设置' }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">生日</text> | ||||||
|  |             <text class="text-gray-800">{{ userInfo.birthday || '未设置' }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">身高</text> | ||||||
|  |             <text class="text-gray-800"> | ||||||
|  |               {{ userInfo.height ? `${userInfo.height}cm` : '未设置' }} | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">体重</text> | ||||||
|  |             <text class="text-gray-800"> | ||||||
|  |               {{ userInfo.weight ? `${userInfo.weight}kg` : '未设置' }} | ||||||
|  |             </text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">学历</text> | ||||||
|  |             <text class="text-gray-800">{{ getEducationText(userInfo.education) }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">月收入</text> | ||||||
|  |             <text class="text-gray-800">{{ getIncomeText(userInfo.monthlyIncome) }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">职业</text> | ||||||
|  |             <text class="text-gray-800">{{ userInfo.profession || '未设置' }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">住房情况</text> | ||||||
|  |             <text class="text-gray-800">{{ getHousingStatusText(userInfo.housingStatus) }}</text> | ||||||
|  |           </view> | ||||||
|  |           <view class="flex justify-between items-center"> | ||||||
|  |             <text class="text-gray-600">购车情况</text> | ||||||
|  |             <text class="text-gray-800">{{ getCarOwnershipText(userInfo.carOwnership) }}</text> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import type { UserInfo } from '@/service/login/type' | ||||||
|  | import { useUserStore } from '@/store' | ||||||
|  | const userStore = useUserStore() | ||||||
|  | 
 | ||||||
|  | const defaultAvatar = '/static/images/default-avatar.png' | ||||||
|  | const userInfo = ref<UserInfo>({} as UserInfo) | ||||||
|  | 
 | ||||||
|  | // 获取用户信息 | ||||||
|  | const getUserInfo = async () => { | ||||||
|  |   try { | ||||||
|  |     const res = await userStore.setUserInfo() | ||||||
|  |     // 从本地存储获取用户ID | ||||||
|  |     const userInfoStr = userStore.userInfo | ||||||
|  |     if (!userInfoStr) { | ||||||
|  |       uni.showToast({ | ||||||
|  |         title: '请先登录', | ||||||
|  |         icon: 'none', | ||||||
|  |       }) | ||||||
|  |       setTimeout(() => { | ||||||
|  |         uni.navigateTo({ | ||||||
|  |           url: '/pages/login/index', | ||||||
|  |         }) | ||||||
|  |       }, 1500) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (res.code === 200) { | ||||||
|  |       userInfo.value = res.data | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取用户信息失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取学历文本 | ||||||
|  | const getEducationText = (education: number) => { | ||||||
|  |   const educationMap = { | ||||||
|  |     1: '初中', | ||||||
|  |     2: '高中', | ||||||
|  |     3: '大专', | ||||||
|  |     4: '本科', | ||||||
|  |     5: '硕士', | ||||||
|  |     6: '博士', | ||||||
|  |   } | ||||||
|  |   return educationMap[education] || '未设置' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取收入文本 | ||||||
|  | const getIncomeText = (income: number) => { | ||||||
|  |   const incomeMap = { | ||||||
|  |     1: '3000以下', | ||||||
|  |     2: '3000~5000', | ||||||
|  |     3: '5000~8000', | ||||||
|  |     4: '8000~10000', | ||||||
|  |     5: '10000~20000', | ||||||
|  |     6: '20000以上', | ||||||
|  |   } | ||||||
|  |   return incomeMap[income] || '未设置' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取住房情况文本 | ||||||
|  | const getHousingStatusText = (status: number) => { | ||||||
|  |   const statusMap = { | ||||||
|  |     1: '租房', | ||||||
|  |     2: '有房', | ||||||
|  |     3: '与父母同住', | ||||||
|  |   } | ||||||
|  |   return statusMap[status] || '未设置' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 获取购车情况文本 | ||||||
|  | const getCarOwnershipText = (status: number) => { | ||||||
|  |   const statusMap = { | ||||||
|  |     1: '无车', | ||||||
|  |     2: '有车', | ||||||
|  |   } | ||||||
|  |   return statusMap[status] || '未设置' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   getUserInfo() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										176
									
								
								src/pages/recommend/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,176 @@ | |||||||
|  | <!-- 推荐列表页面 --> | ||||||
|  | <route lang="json5"> | ||||||
|  | { | ||||||
|  |   style: { | ||||||
|  |     navigationBarTitleText: '推荐列表', | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </route> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <view class="min-h-screen bg-gray-50"> | ||||||
|  |     <!-- 顶部导航栏 --> | ||||||
|  |     <view class="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b border-gray-100"> | ||||||
|  |       <view class="flex items-center justify-between px-4 py-3"> | ||||||
|  |         <view class="flex items-center"> | ||||||
|  |           <text class="text-xl font-bold text-gray-900">推荐列表</text> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |     </view> | ||||||
|  | 
 | ||||||
|  |     <!-- 推荐列表 --> | ||||||
|  |     <scroll-view | ||||||
|  |       scroll-y | ||||||
|  |       class="h-[calc(100vh-120rpx)]" | ||||||
|  |       refresher-enabled | ||||||
|  |       :refresher-triggered="isRefreshing" | ||||||
|  |       @refresherrefresh="onRefresh" | ||||||
|  |       @scrolltolower="loadMore" | ||||||
|  |     > | ||||||
|  |       <view v-if="recommendList.length > 0" class="p-4 space-y-4"> | ||||||
|  |         <view | ||||||
|  |           v-for="(item, index) in recommendList" | ||||||
|  |           :key="index" | ||||||
|  |           class="bg-white rounded-2xl shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md active:scale-[0.98]" | ||||||
|  |           @tap="viewDetail(item)" | ||||||
|  |         > | ||||||
|  |           <view class="flex p-4"> | ||||||
|  |             <view class="relative"> | ||||||
|  |               <image | ||||||
|  |                 :src="item.avatarUrl || item.avatar" | ||||||
|  |                 mode="aspectFill" | ||||||
|  |                 class="w-24 h-24 rounded-xl object-cover" | ||||||
|  |               /> | ||||||
|  |             </view> | ||||||
|  |             <view class="flex-1 ml-4"> | ||||||
|  |               <view class="flex items-center justify-between mb-2"> | ||||||
|  |                 <view class="flex items-center"> | ||||||
|  |                   <text class="text-lg font-semibold text-gray-900 mr-2">{{ item.nickname }}</text> | ||||||
|  |                   <text class="text-sm text-gray-500">{{ item.age }}岁</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |               <view class="flex items-center space-x-2 mb-3"> | ||||||
|  |                 <view class="flex items-center"> | ||||||
|  |                   <text class="text-xs text-gray-500">身高 {{ item.height }}cm</text> | ||||||
|  |                 </view> | ||||||
|  |                 <view class="w-px h-3 bg-gray-200"></view> | ||||||
|  |                 <view class="flex items-center"> | ||||||
|  |                   <text class="text-xs text-gray-500">{{ item.education }}</text> | ||||||
|  |                 </view> | ||||||
|  |                 <view class="w-px h-3 bg-gray-200"></view> | ||||||
|  |                 <view class="flex items-center"> | ||||||
|  |                   <text class="text-xs text-gray-500">{{ item.workArea }}</text> | ||||||
|  |                 </view> | ||||||
|  |               </view> | ||||||
|  |               <view class="flex items-center"> | ||||||
|  |                 <text class="text-sm text-gray-600">{{ item.profession }}</text> | ||||||
|  |               </view> | ||||||
|  |             </view> | ||||||
|  |           </view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 空状态 --> | ||||||
|  |       <view v-else-if="!isLoading" class="flex flex-col items-center justify-center py-12"> | ||||||
|  |         <image src="/static/empty.png" class="w-32 h-32 mb-4" /> | ||||||
|  |         <text class="text-gray-400">暂无推荐数据</text> | ||||||
|  |       </view> | ||||||
|  | 
 | ||||||
|  |       <!-- 加载状态 --> | ||||||
|  |       <view v-if="isLoading" class="flex justify-center py-6"> | ||||||
|  |         <view class="flex items-center space-x-2"> | ||||||
|  |           <view class="w-2 h-2 bg-primary rounded-full animate-bounce"></view> | ||||||
|  |           <view | ||||||
|  |             class="w-2 h-2 bg-primary rounded-full animate-bounce" | ||||||
|  |             style="animation-delay: 0.2s" | ||||||
|  |           ></view> | ||||||
|  |           <view | ||||||
|  |             class="w-2 h-2 bg-primary rounded-full animate-bounce" | ||||||
|  |             style="animation-delay: 0.4s" | ||||||
|  |           ></view> | ||||||
|  |         </view> | ||||||
|  |       </view> | ||||||
|  |       <view v-else-if="!hasMore && recommendList.length > 0" class="flex justify-center py-6"> | ||||||
|  |         <text class="text-sm text-gray-400">没有更多了</text> | ||||||
|  |       </view> | ||||||
|  |     </scroll-view> | ||||||
|  |   </view> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import { ref, onMounted } from 'vue' | ||||||
|  | import type { LoveUserItem, LoveListParams } from '@/service/love/type' | ||||||
|  | import { onLoad } from '@dcloudio/uni-app' | ||||||
|  | 
 | ||||||
|  | // 列表数据 | ||||||
|  | const recommendList = ref<LoveUserItem[]>([]) | ||||||
|  | const page = ref(1) | ||||||
|  | const pageSize = ref(10) | ||||||
|  | const hasMore = ref(true) | ||||||
|  | const isLoading = ref(false) | ||||||
|  | const isRefreshing = ref(false) | ||||||
|  | 
 | ||||||
|  | // 获取推荐列表 | ||||||
|  | const getRecommendList = async () => { | ||||||
|  |   if (isLoading.value) return | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     isLoading.value = true | ||||||
|  |     const params: LoveListParams = { | ||||||
|  |       pageIndex: page.value, | ||||||
|  |     } | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('获取推荐列表失败:', error) | ||||||
|  |     uni.showToast({ | ||||||
|  |       title: '获取推荐列表失败', | ||||||
|  |       icon: 'none', | ||||||
|  |     }) | ||||||
|  |   } finally { | ||||||
|  |     isLoading.value = false | ||||||
|  |     isRefreshing.value = false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 下拉刷新 | ||||||
|  | const onRefresh = async () => { | ||||||
|  |   isRefreshing.value = true | ||||||
|  |   page.value = 1 | ||||||
|  |   await getRecommendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 加载更多 | ||||||
|  | const loadMore = () => { | ||||||
|  |   if (!hasMore.value || isLoading.value) return | ||||||
|  |   page.value++ | ||||||
|  |   getRecommendList() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 查看详情 | ||||||
|  | const viewDetail = (item: LoveUserItem) => { | ||||||
|  |   uni.navigateTo({ | ||||||
|  |     url: `/pages/detail/index?id=${item.id}`, | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | onLoad(() => { | ||||||
|  |   getRecommendList() | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss"> | ||||||
|  | @import '@/style/iconfont.css'; | ||||||
|  | 
 | ||||||
|  | @keyframes bounce { | ||||||
|  |   0%, | ||||||
|  |   100% { | ||||||
|  |     transform: translateY(0); | ||||||
|  |   } | ||||||
|  |   50% { | ||||||
|  |     transform: translateY(-6px); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .animate-bounce { | ||||||
|  |   animation: bounce 1s infinite; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										19
									
								
								src/service/event/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { | ||||||
|  |   EventItem, | ||||||
|  |   EventListParams, | ||||||
|  |   MyEventItem, | ||||||
|  |   JoinEventResponse, | ||||||
|  |   ApiResponse, | ||||||
|  | } from './type' | ||||||
|  | 
 | ||||||
|  | /** 获取活动列表 */ | ||||||
|  | export const getEventListAPI = (params: EventListParams) => | ||||||
|  |   request.post<ApiResponse<EventItem[]>>('/event/list', params) | ||||||
|  | 
 | ||||||
|  | /** 参加活动 */ | ||||||
|  | export const joinEventAPI = (id: string) => | ||||||
|  |   request.get<ApiResponse<JoinEventResponse>>(`/event/join/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 获取我参加的活动列表 */ | ||||||
|  | export const getMyEventListAPI = () => request.get<ApiResponse<MyEventItem[]>>('/event/myEvent') | ||||||
							
								
								
									
										63
									
								
								src/service/event/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,63 @@ | |||||||
|  | /** 活动列表项 */ | ||||||
|  | export interface EventItem { | ||||||
|  |   /** 活动ID */ | ||||||
|  |   id: string | ||||||
|  |   /** 活动标题 */ | ||||||
|  |   eventTitle: string | ||||||
|  |   /** 活动内容 markdown格式 */ | ||||||
|  |   eventContent: string | ||||||
|  |   /** 活动封面图 */ | ||||||
|  |   eventAvatar: string | ||||||
|  |   /** 活动价格 */ | ||||||
|  |   eventPrice: string | ||||||
|  |   /** 活动地点 */ | ||||||
|  |   eventPlace: string | ||||||
|  |   /** 发起时间 */ | ||||||
|  |   createTime: string | ||||||
|  |   /** 到期时间 */ | ||||||
|  |   endTime: string | ||||||
|  |   /** 有效天数 */ | ||||||
|  |   effectiveTime: string | ||||||
|  |   /** 已参与人数 */ | ||||||
|  |   eventNumber: string | ||||||
|  |   /** 限制人数 */ | ||||||
|  |   eventLimitNumber: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 我参加的活动列表项 */ | ||||||
|  | export interface MyEventItem extends EventItem { | ||||||
|  |   /** 我的活动表id,非活动id */ | ||||||
|  |   id: string | ||||||
|  |   /** 活动发起时间 */ | ||||||
|  |   enevtCreateTime: string | ||||||
|  |   /** 到期时间 */ | ||||||
|  |   eventEndTime: string | ||||||
|  |   /** 活动状态 1-启用 0-禁用 */ | ||||||
|  |   status: '1' | '0' | ||||||
|  |   /** 参加状态 1-已参加 0或者为空就是未参加 */ | ||||||
|  |   joinStatus?: '1' | '0' | ||||||
|  |   /** 参加活动时间 */ | ||||||
|  |   createTime: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 活动列表查询参数 */ | ||||||
|  | export interface EventListParams { | ||||||
|  |   /** 活动状态:1-全部 2-报名中 3-已结束 */ | ||||||
|  |   status: '1' | '2' | '3' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 参加活动响应 */ | ||||||
|  | export interface JoinEventResponse { | ||||||
|  |   /** 订单编号 */ | ||||||
|  |   trackingNo: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 通用响应格式 */ | ||||||
|  | export interface ApiResponse<T> { | ||||||
|  |   /** 响应码 */ | ||||||
|  |   code: number | ||||||
|  |   /** 响应消息 */ | ||||||
|  |   message: string | ||||||
|  |   /** 响应数据 */ | ||||||
|  |   data: T | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/service/exam/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { BaseResponse } from '../login/type' | ||||||
|  | 
 | ||||||
|  | /** 考试信息类型 */ | ||||||
|  | export interface ExamItem { | ||||||
|  |   id: string | ||||||
|  |   examName: string | ||||||
|  |   examDate: string | ||||||
|  |   startTime: string | ||||||
|  |   endTime: string | ||||||
|  |   provinceName: string | ||||||
|  |   cityName: string | ||||||
|  |   address: string | ||||||
|  |   locationName: string | ||||||
|  |   estimatedAttendees: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获取可报名考试列表 */ | ||||||
|  | export const getCanBookExamListAPI = () => | ||||||
|  |   request.get<BaseResponse<ExamItem[]>>('/exam/getCanBookExamList') | ||||||
							
								
								
									
										10
									
								
								src/service/feedback/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { FeedbackParams, ComplaintParams, ApiResponse } from './type' | ||||||
|  | 
 | ||||||
|  | /** 提交反馈 */ | ||||||
|  | export const addFeedbackAPI = (data: FeedbackParams) => | ||||||
|  |   request.post<ApiResponse>('/feedback/add', data) | ||||||
|  | 
 | ||||||
|  | /** 提交投诉 */ | ||||||
|  | export const addComplaintAPI = (data: ComplaintParams) => | ||||||
|  |   request.post<ApiResponse>('/complaint/add', data) | ||||||
							
								
								
									
										29
									
								
								src/service/feedback/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | |||||||
|  | /** 反馈参数 */ | ||||||
|  | export interface FeedbackParams { | ||||||
|  |   /** 反馈意见标题 */ | ||||||
|  |   feedbackTitle: string | ||||||
|  |   /** 反馈意见内容 */ | ||||||
|  |   feedbackContent: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 投诉参数 */ | ||||||
|  | export interface ComplaintParams { | ||||||
|  |   /** 被投诉人id */ | ||||||
|  |   userId: string | ||||||
|  |   /** 投诉分类 */ | ||||||
|  |   complaintClass: '虚假宣传' | '微商' | '诈骗' | '个人信息不符' | ||||||
|  |   /** 投诉详情 */ | ||||||
|  |   complaintContent: string | ||||||
|  |   /** 附件图片url列表 */ | ||||||
|  |   complaintImageList?: string[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** API响应基础类型 */ | ||||||
|  | export interface ApiResponse<T = any> { | ||||||
|  |   /** 状态码 */ | ||||||
|  |   code: '200' | '500' | ||||||
|  |   /** 消息 */ | ||||||
|  |   message: string | ||||||
|  |   /** 数据 */ | ||||||
|  |   data?: T | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								src/service/file/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,30 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import { getEnvBaseUrl } from '@/utils' | ||||||
|  | 
 | ||||||
|  | /** 上传图片 */ | ||||||
|  | export const uploadImageAPI = (filePath: string, imageClassId: string) => { | ||||||
|  |   return new Promise<{ url: string }>((resolve, reject) => { | ||||||
|  |     uni.uploadFile({ | ||||||
|  |       url: getEnvBaseUrl() + 'attachment/uploadImageForUser', | ||||||
|  |       filePath, | ||||||
|  |       name: 'img', | ||||||
|  |       formData: { | ||||||
|  |         image_class_id: imageClassId, | ||||||
|  |       }, | ||||||
|  |       header: { | ||||||
|  |         'x-token': uni.getStorageSync('x-token') || '', | ||||||
|  |       }, | ||||||
|  |       success: (res) => { | ||||||
|  |         const data = JSON.parse(res.data) | ||||||
|  |         if (data.code === 200) { | ||||||
|  |           resolve(data.data) | ||||||
|  |         } else { | ||||||
|  |           reject(new Error(data.message)) | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       fail: (err) => { | ||||||
|  |         reject(err) | ||||||
|  |       }, | ||||||
|  |     }) | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								src/service/index/foo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | export interface IFooItem { | ||||||
|  |   id: string | ||||||
|  |   name: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** GET 请求 */ | ||||||
|  | export const getFooAPI = (name: string) => request.get<IFooItem>('/foo', { name }) | ||||||
|  | 
 | ||||||
|  | /** POST 请求 */ | ||||||
|  | export const postFooAPI = (name: string) => request.post<IFooItem>('/foo', { name }, { name }) | ||||||
							
								
								
									
										64
									
								
								src/service/login/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,64 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { | ||||||
|  |   BaseResponse, | ||||||
|  |   GetOpenIdResponse, | ||||||
|  |   LoginRequest, | ||||||
|  |   LoginResponse, | ||||||
|  |   UserInfo, | ||||||
|  |   UpdateUserInfoRequest, | ||||||
|  |   InviteUserItem, | ||||||
|  |   SpendHistoryItem, | ||||||
|  |   PayInfoList, | ||||||
|  |   GetInviteCodeResponse, | ||||||
|  |   RefundInfoList, | ||||||
|  | } from './type' | ||||||
|  | 
 | ||||||
|  | /** 获取用户openId */ | ||||||
|  | export const getOpenIdAPI = (code: string) => | ||||||
|  |   request.post<BaseResponse<GetOpenIdResponse>>('/sys/getOpenId', { code }) | ||||||
|  | 
 | ||||||
|  | /** 用户登录 */ | ||||||
|  | export const loginAPI = (data: LoginRequest) => | ||||||
|  |   request.post<BaseResponse<LoginResponse>>('/base/mobileLogin', data) | ||||||
|  | 
 | ||||||
|  | /** 获取用户信息 */ | ||||||
|  | export const getUserInfoAPI = (id: string) => | ||||||
|  |   request.get<BaseResponse<UserInfo>>(`/user/info/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 更新用户信息 */ | ||||||
|  | export const updateUserInfoAPI = (data: UpdateUserInfoRequest) => | ||||||
|  |   request.post<BaseResponse>('/user/update', data) | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * banner图片 | ||||||
|  |  */ | ||||||
|  | export const getBannerImage = () => request.get<BaseResponse>('/attachment/getBannerImage') | ||||||
|  | 
 | ||||||
|  | /** 获取我邀请的用户列表 */ | ||||||
|  | export const getMyInviteListAPI = () => | ||||||
|  |   request.get<BaseResponse<InviteUserItem[]>>('/user/myInvite') | ||||||
|  | 
 | ||||||
|  | /** 获取用户消费明细 */ | ||||||
|  | export const getUserSpendHistoryAPI = (id: string) => | ||||||
|  |   request.get<BaseResponse<SpendHistoryItem[]>>(`/user/spendHistory/${id}`) | ||||||
|  | 
 | ||||||
|  | export const getUserPayInfoAPI = () => request.get<BaseResponse<PayInfoList[]>>(`/pay/payInfo`) | ||||||
|  | 
 | ||||||
|  | /** 获取用户退款记录 */ | ||||||
|  | export const getUserRefundInfoAPI = () => | ||||||
|  |   request.get<BaseResponse<RefundInfoList[]>>('/pay/refundInfo') | ||||||
|  | 
 | ||||||
|  | /** 撤销退款申请 */ | ||||||
|  | export const cancelRefundAPI = (id: string) => request.get<BaseResponse>(`/pay/cancelRefund/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 退出登录 */ | ||||||
|  | export const logoutAPI = () => request.get<BaseResponse>('/sys/logout') | ||||||
|  | 
 | ||||||
|  | /** 获取邀请码 */ | ||||||
|  | export const getInviteCodeAPI = () => | ||||||
|  |   request.get<BaseResponse<GetInviteCodeResponse>>('/sys/getInviteCode') | ||||||
|  | 
 | ||||||
|  | /** 绑定邀请人 */ | ||||||
|  | export const bindInviterAPI = (code: string) => { | ||||||
|  |   return request.get(`/sys/bindInviteUser/${code}`) | ||||||
|  | } | ||||||
							
								
								
									
										176
									
								
								src/service/login/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,176 @@ | |||||||
|  | /** 基础响应类型 */ | ||||||
|  | export interface BaseResponse<T = any> { | ||||||
|  |   code: number | ||||||
|  |   message: string | ||||||
|  |   data: T | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获取openId响应类型 */ | ||||||
|  | export type GetOpenIdResponse = string | ||||||
|  | 
 | ||||||
|  | /** 登录请求参数类型 */ | ||||||
|  | export interface LoginRequest { | ||||||
|  |   openId: string | ||||||
|  |   avatarUrl: string | ||||||
|  |   userName: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 登录响应类型 */ | ||||||
|  | export interface LoginResponse { | ||||||
|  |   /** 用户ID */ | ||||||
|  |   id?: number | ||||||
|  |   /** 用户token */ | ||||||
|  |   'x-token'?: string | ||||||
|  |   /** 账户余额,展示在个人信息页面 */ | ||||||
|  |   points?: string | ||||||
|  |   /** 退款状态 */ | ||||||
|  |   refundStatus?: string | ||||||
|  |   /** 手机号 */ | ||||||
|  |   phone?: string | ||||||
|  |   /** 头像URL */ | ||||||
|  |   avatar?: string | ||||||
|  |   /** 昵称 */ | ||||||
|  |   nickName?: string | ||||||
|  |   /** 生日,日期Date类型 */ | ||||||
|  |   birthday?: string | ||||||
|  |   /** 工作地址 */ | ||||||
|  |   workArea?: string | ||||||
|  |   /** 工作省份名称 */ | ||||||
|  |   workProvinceName?: string | ||||||
|  |   /** 工作城市名称 */ | ||||||
|  |   workCityName?: string | ||||||
|  |   /** 工作区县名称 */ | ||||||
|  |   workCountryName?: string | ||||||
|  |   /** 户籍地址 */ | ||||||
|  |   householdArea?: string | ||||||
|  |   /** 婚姻状况 (1-未婚, 2-离异, 3-丧偶) */ | ||||||
|  |   maritalStatus?: string | ||||||
|  |   /** 身高,单位:米 */ | ||||||
|  |   height?: number | ||||||
|  |   /** 体重,单位:千克 */ | ||||||
|  |   weight?: number | ||||||
|  |   /** 学历 (1-初中, 2-高中, 3-大专, 4-本科, 5-硕士, 6-博士) */ | ||||||
|  |   education?: string | ||||||
|  |   /** 月收入 1-3000以下 2-3000~5000 3-5000~8000 4-8000~10000 5-10000~20000 6-20000以上 */ | ||||||
|  |   monthlyIncome?: string | ||||||
|  |   /** 职业 */ | ||||||
|  |   profession?: string | ||||||
|  |   /** 住房情况(1-已购房(有贷款) 2-已购房(无贷款) 3-有能力购房 4-无房 5-无房希望对方解决 6-无房希望双方解决 7-与父母同住 8-独自租房 9-与人合租 10-住单位房) */ | ||||||
|  |   housingStatus?: string | ||||||
|  |   /** 购车情况(1-无车 2-已购车(经济型) 3-已购车(中档型) 4-已购车(豪华型) 5-单位用车 6-需要时购置) */ | ||||||
|  |   carOwnership?: string | ||||||
|  |   /** 期望结婚时间(1-随时 2-半年内 3-一年内 4-两年内 5-三年内) */ | ||||||
|  |   expectedMarriageTime?: string | ||||||
|  |   /** 自我介绍 */ | ||||||
|  |   selfIntroduction?: string | ||||||
|  |   /** 微信 */ | ||||||
|  |   wechat?: string | ||||||
|  |   /** 微信二维码url */ | ||||||
|  |   wechatQrcode?: string | ||||||
|  |   /** QQ号 */ | ||||||
|  |   qq?: string | ||||||
|  |   /** 邮箱 */ | ||||||
|  |   email?: string | ||||||
|  |   /** 父母状况(1-父母均建在 2-只有母亲建在 3-只有父亲建在 4-父母均以离世) */ | ||||||
|  |   parentsStatus?: string | ||||||
|  |   /** 兄弟姐妹 (1-独生子女 2-2 3-3 4-4 5-5) */ | ||||||
|  |   siblings?: string | ||||||
|  |   /** 与Ta父母住(1-愿意 2-不愿意 3-视具体情况而定 4-尊重伴侣意见) */ | ||||||
|  |   liveWithParents?: string | ||||||
|  |   /** 是否吸烟(1-是 0-否) */ | ||||||
|  |   smoke?: number | ||||||
|  |   /** 婚娶形式(1-嫁娶 2-两顾 3-上门) */ | ||||||
|  |   marriageForm?: string | ||||||
|  |   /** 是否饮酒(1-是 0-否) */ | ||||||
|  |   drink?: number | ||||||
|  |   /** 子女情况(1-未育 2-子女归自己 3-子女归对方) */ | ||||||
|  |   childrenStatus?: string | ||||||
|  |   /** 养宠物情况(多选)(猫,狗,鸟,鱼,兔,鼠,乌龟,蛇,爬行动物,另类动物,不喜欢养,可能会养,过敏) */ | ||||||
|  |   pets?: string | ||||||
|  |   /** 兴趣爱好 */ | ||||||
|  |   hobbies?: string | ||||||
|  |   /** 血型(A B AB O 其他) */ | ||||||
|  |   bloodType?: string | ||||||
|  |   /** 作息习惯(早睡早起很规律,经常夜猫子,总是早起鸟,偶尔懒散一下,没有规律) */ | ||||||
|  |   sleepHabits?: string | ||||||
|  |   /** 民族 */ | ||||||
|  |   nation?: string | ||||||
|  |   /** 毕业院校 */ | ||||||
|  |   graduationSchool?: string | ||||||
|  |   /** 锻炼习惯(每天锻炼,每周至少一次,每月几次,没时间锻炼,集中时间锻炼,不喜欢锻炼) */ | ||||||
|  |   exerciseHabits?: string | ||||||
|  |   /** 工作行业 */ | ||||||
|  |   workIndustry?: string | ||||||
|  |   /** 单位类型(政府机关,事业单位,外资企业,合资企业,国营企业,私营企业,自有公司,其他) */ | ||||||
|  |   companyType?: string | ||||||
|  |   /** 工作单位 */ | ||||||
|  |   workUnit?: string | ||||||
|  |   /** 我的标签(多选)(孝顺,酷,责任心,经济适用,憨直,感性,事业,睿智,猥琐,幽默,旅行,宅男,体贴,有魄力,仗义,稳重) */ | ||||||
|  |   tags?: string | ||||||
|  |   /** 房产位置(多选)(市区 城区有房 老家有房) */ | ||||||
|  |   propertyLocation?: string | ||||||
|  |   /** 期望婚况(多选)(1-未婚 2-离异 3-丧偶) */ | ||||||
|  |   partnerMaritalStatus?: string | ||||||
|  |   /** 期望年龄范围 20-23 */ | ||||||
|  |   ageRange?: string | ||||||
|  |   /** 期望身高范围 160-180 */ | ||||||
|  |   heightRange?: string | ||||||
|  |   /** 期望最低学历(1-初中 2-高中 3-大专 4-本科 5-硕士 6-博士) */ | ||||||
|  |   minEducation?: string | ||||||
|  |   /** 期望月收入 1-3000以下 2-3000~5000 3-5000~8000 4-8000~10000 5-10000~20000 6-20000以上 */ | ||||||
|  |   minIncome?: string | ||||||
|  |   /** 期望工作地区,选省份代码 */ | ||||||
|  |   partnerWorkArea?: string | ||||||
|  |   /** 期望其他要求 */ | ||||||
|  |   otherRequirements?: string | ||||||
|  |   /** 创建时间 */ | ||||||
|  |   createTime?: string | ||||||
|  |   /** 更新时间 */ | ||||||
|  |   updateTime?: string | ||||||
|  |   /** 邀请人id */ | ||||||
|  |   inviterId?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 用户信息类型 */ | ||||||
|  | export type UserInfo = LoginResponse | ||||||
|  | 
 | ||||||
|  | /** 更新用户信息请求参数类型 */ | ||||||
|  | export type UpdateUserInfoRequest = Partial<UserInfo> | ||||||
|  | 
 | ||||||
|  | /** 邀请用户列表项类型 */ | ||||||
|  | export interface InviteUserItem { | ||||||
|  |   id: string | ||||||
|  |   nickname: string | ||||||
|  |   child: { | ||||||
|  |     id: string | ||||||
|  |     nickname: string | ||||||
|  |   }[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 用户消费明细类型 */ | ||||||
|  | export interface SpendHistoryItem { | ||||||
|  |   id: string | ||||||
|  |   spendPrice: string | ||||||
|  |   spendType: number | ||||||
|  |   createTime: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface PayInfoList { | ||||||
|  |   id: string | ||||||
|  |   payPoints: string | ||||||
|  |   createTime: string | ||||||
|  |   orderNo: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 获取邀请码响应类型 */ | ||||||
|  | export type GetInviteCodeResponse = string | ||||||
|  | 
 | ||||||
|  | /** 退款记录类型 */ | ||||||
|  | export interface RefundInfoList { | ||||||
|  |   id: string | ||||||
|  |   refundPoints: string | ||||||
|  |   refundReason: string | ||||||
|  |   refundStatus: string | ||||||
|  |   createTime: string | ||||||
|  |   auditTime: string | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/service/love/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { LoveUserItem, LoveListParams, ApiResponse, UnlockUserResult } from './type' | ||||||
|  | 
 | ||||||
|  | /** 获取我解锁的相亲对象列表 */ | ||||||
|  | export const getMyLoveListAPI = () => request.get<ApiResponse<LoveUserItem[]>>('/love/myList') | ||||||
|  | 
 | ||||||
|  | /** 获取推荐相亲对象列表 */ | ||||||
|  | export const getLoveListAPI = (params: LoveListParams) => | ||||||
|  |   request.post<ApiResponse<LoveUserItem[]>>('/love/list', params) | ||||||
|  | 
 | ||||||
|  | /** 检查用户是否已解锁 */ | ||||||
|  | export const checkIsLoveAPI = (id: string) => | ||||||
|  |   request.get<ApiResponse<boolean>>(`/user/isLove/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 解锁用户 */ | ||||||
|  | export const unlockUserAPI = (id: string) => | ||||||
|  |   request.get<ApiResponse<UnlockUserResult[]>>(`/user/love/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 获取用户详细信息 */ | ||||||
|  | export const getUserDetailAPI = (id: string) => | ||||||
|  |   request.get<ApiResponse<LoveUserItem>>(`/user/detail/${id}`) | ||||||
							
								
								
									
										91
									
								
								src/service/love/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,91 @@ | |||||||
|  | /** 相亲对象列表项 */ | ||||||
|  | export interface LoveUserItem { | ||||||
|  |   /** 用户ID */ | ||||||
|  |   id: string | number | ||||||
|  |   /** 用户昵称 */ | ||||||
|  |   nickname: string | ||||||
|  |   /** 用户头像 */ | ||||||
|  |   avatar: string | ||||||
|  |   /** 年龄 */ | ||||||
|  |   age: number | ||||||
|  |   /** 身高(cm) */ | ||||||
|  |   height: number | ||||||
|  |   /** 学历 */ | ||||||
|  |   education: string | ||||||
|  |   /** 职业 */ | ||||||
|  |   occupation: string | ||||||
|  |   /** 收入 */ | ||||||
|  |   income: string | ||||||
|  |   /** 所在地 */ | ||||||
|  |   location: string | ||||||
|  |   /** 是否已解锁 */ | ||||||
|  |   isUnlocked: boolean | ||||||
|  |   /** 微信ID */ | ||||||
|  |   wechatId?: string | ||||||
|  |   /** 头像URL */ | ||||||
|  |   avatarUrl: string | ||||||
|  |   /** 工作地区 */ | ||||||
|  |   workArea: string | ||||||
|  |   /** 婚姻状况 (1-未婚, 2-离异, 3-丧偶) */ | ||||||
|  |   maritalStatus: '1' | '2' | '3' | ||||||
|  |   /** 体重,单位:千克 */ | ||||||
|  |   weight: string | ||||||
|  |   /** 学历 (1-初中, 2-高中, 3-大专, 4-本科, 5-硕士, 6-博士) */ | ||||||
|  |   educationLevel: '1' | '2' | '3' | '4' | '5' | '6' | ||||||
|  |   /** 月收入 1-3000以下 2-3000~5000 3-5000~8000 4-8000~10000 5-10000~20000 6-20000以上 */ | ||||||
|  |   monthlyIncome: '1' | '2' | '3' | '4' | '5' | '6' | ||||||
|  |   /** 职业 */ | ||||||
|  |   profession: string | ||||||
|  |   /** 兴趣爱好 */ | ||||||
|  |   hobbies: string | ||||||
|  |   /** 我的标签(多选) */ | ||||||
|  |   tags: string | ||||||
|  |   /** 房产位置(多选) */ | ||||||
|  |   propertyLocation: string | ||||||
|  |   /** 住房情况 */ | ||||||
|  |   housingStatus: string | ||||||
|  |   /** QQ号 */ | ||||||
|  |   qq: string | ||||||
|  |   /** 邮箱 */ | ||||||
|  |   email: string | ||||||
|  |   /** 个人介绍 */ | ||||||
|  |   selfIntroduction: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 相亲对象列表查询参数 */ | ||||||
|  | export interface LoveListParams { | ||||||
|  |   /** 页码 */ | ||||||
|  |   pageIndex: number | string | ||||||
|  |   /** 年龄范围 示例:20-30 */ | ||||||
|  |   ageRange?: string | ||||||
|  |   /** 体重范围 示例:20-30 */ | ||||||
|  |   weightRange?: string | ||||||
|  |   /** 身高范围 示例:20-30 */ | ||||||
|  |   heightRange?: string | ||||||
|  |   /** 工作地区 市6位代码 示例:211000 */ | ||||||
|  |   workCityCode?: string | ||||||
|  |   /** 户籍地区 市6位代码 示例:211000 */ | ||||||
|  |   householdCityCode?: string | ||||||
|  |   /** 民族 直接传中文即可 */ | ||||||
|  |   nation?: string | ||||||
|  |   /** 婚姻状况 (1-未婚, 2-离异, 3-丧偶) */ | ||||||
|  |   maritalStatus?: '1' | '2' | '3' | ||||||
|  |   /** 住房情况 1-10 */ | ||||||
|  |   housingStatus?: string | ||||||
|  |   /** 是否吸烟(1-是 0-否) */ | ||||||
|  |   smoke?: '1' | '0' | ||||||
|  |   /** 子女情况(1-未育 2-子女归自己 3-子女归对方) */ | ||||||
|  |   childrenStatus?: '1' | '2' | '3' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 解锁用户返回项 */ | ||||||
|  | export interface UnlockUserResult { | ||||||
|  |   trackingNo: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 通用API响应体 */ | ||||||
|  | export interface ApiResponse<T = any> { | ||||||
|  |   code: number | ||||||
|  |   message: string | ||||||
|  |   data: T | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								src/service/pay/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,21 @@ | |||||||
|  | import { request } from '@/utils/request' | ||||||
|  | import type { PayParams, RefundParams, PayInfo, RefundInfo } from './type' | ||||||
|  | 
 | ||||||
|  | /** 发起支付 */ | ||||||
|  | export const payAPI = (data: PayParams) => request.post<{ outTradeNo: string }>('/pay/pay', data) | ||||||
|  | 
 | ||||||
|  | /** 查询支付结果 */ | ||||||
|  | export const queryPayResultAPI = (outTradeNo: string) => | ||||||
|  |   request.get<boolean>(`/pay/queryPayResult/${outTradeNo}`) | ||||||
|  | 
 | ||||||
|  | /** 申请退款 */ | ||||||
|  | export const refundAPI = (data: RefundParams) => request.post('/pay/refund', data) | ||||||
|  | 
 | ||||||
|  | /** 获取退款详情 */ | ||||||
|  | export const getRefundInfoAPI = () => request.get<RefundInfo[]>('/pay/refundInfo') | ||||||
|  | 
 | ||||||
|  | /** 撤销退款 */ | ||||||
|  | export const cancelRefundAPI = (id: string) => request.get(`/pay/cancelRefund/${id}`) | ||||||
|  | 
 | ||||||
|  | /** 获取充值记录 */ | ||||||
|  | export const getPayInfoAPI = () => request.get<PayInfo[]>('/pay/payInfo') | ||||||
							
								
								
									
										43
									
								
								src/service/pay/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,43 @@ | |||||||
|  | /** 支付参数 */ | ||||||
|  | export interface PayParams { | ||||||
|  |   /** 支付金额 */ | ||||||
|  |   amount: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 退款参数 */ | ||||||
|  | export interface RefundParams { | ||||||
|  |   /** 退款金额 */ | ||||||
|  |   amount: number | ||||||
|  |   /** 退款原因 */ | ||||||
|  |   reason?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 支付记录 */ | ||||||
|  | export interface PayInfo { | ||||||
|  |   /** 支付记录ID */ | ||||||
|  |   id: string | ||||||
|  |   /** 支付金额 */ | ||||||
|  |   amount: number | ||||||
|  |   /** 支付状态:pending-待支付,success-支付成功,failed-支付失败 */ | ||||||
|  |   status: 'pending' | 'success' | 'failed' | ||||||
|  |   /** 创建时间 */ | ||||||
|  |   createTime: string | ||||||
|  |   /** 商户订单号 */ | ||||||
|  |   outTradeNo: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 退款记录 */ | ||||||
|  | export interface RefundInfo { | ||||||
|  |   /** 退款记录ID */ | ||||||
|  |   id: string | ||||||
|  |   /** 退款金额 */ | ||||||
|  |   amount: number | ||||||
|  |   /** 退款状态:pending-待审核,approved-已通过,rejected-已拒绝,cancelled-已取消 */ | ||||||
|  |   status: 'pending' | 'approved' | 'rejected' | 'cancelled' | ||||||
|  |   /** 退款原因 */ | ||||||
|  |   reason?: string | ||||||
|  |   /** 创建时间 */ | ||||||
|  |   createTime: string | ||||||
|  |   /** 更新时间 */ | ||||||
|  |   updateTime: string | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/1024x1024.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 58 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/120x120.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/144x144.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/152x152.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/167x167.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/180x180.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/20x20.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 574 B | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/29x29.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 780 B | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/40x40.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 985 B | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/58x58.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/60x60.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/72x72.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/76x76.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/static/app/icons/80x80.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.0 KiB | 
 Quincy_J
						Quincy_J