沈明 1 year ago
commit
2ec98c0b90
  1. 5
      .env.development
  2. 6
      .env.production
  3. 3
      .eslintignore
  4. 146
      .eslintrc.cjs
  5. 28
      .gitignore
  6. 4
      .husky/pre-commit
  7. 8
      .prettierignore
  8. 36
      .prettierrc.cjs
  9. 30
      README.md
  10. 85
      auto/addPage.ts
  11. 4
      auto/preinstall.js
  12. 23
      index.html
  13. 100
      package.json
  14. 29
      src/App.vue
  15. 98
      src/api/common.ts
  16. 147
      src/components/ex-list/ex-list.vue
  17. 61
      src/components/ex-tags/ex-tags.vue
  18. 13
      src/config/index.ts
  19. 128
      src/hooks/paginationLoader.ts
  20. 125
      src/hooks/useListLoadClass.ts
  21. 127
      src/hooks/usePagingLoad.ts
  22. 16
      src/main.ts
  23. 131
      src/manifest.json
  24. 111
      src/pages.json
  25. 218
      src/pages/electionList/index.vue
  26. 7
      src/pages/electionResults/index.vue
  27. 112
      src/pages/index/guide.vue
  28. 9
      src/pages/index/links.vue
  29. 314
      src/pages/mine/index.vue
  30. 7
      src/pages/myElection/index.vue
  31. 338
      src/pages/votingElection/index.vue
  32. 51
      src/permission.ts
  33. 257
      src/share.vue
  34. BIN
      src/static/bg1.png
  35. BIN
      src/static/card.png
  36. BIN
      src/static/invite.jpg
  37. BIN
      src/static/logo-bg.png
  38. BIN
      src/static/logo-tran.png
  39. BIN
      src/static/logo.png
  40. BIN
      src/static/password.png
  41. BIN
      src/static/redpacket.png
  42. BIN
      src/static/tabbar/grxx-select.png
  43. BIN
      src/static/tabbar/grxx.png
  44. BIN
      src/static/tabbar/tpxj-select.png
  45. BIN
      src/static/tabbar/tpxj.png
  46. BIN
      src/static/tabbar/wdxj-select.png
  47. BIN
      src/static/tabbar/wdxj.png
  48. BIN
      src/static/tabbar/xjjg-select.png
  49. BIN
      src/static/tabbar/xjjg.png
  50. BIN
      src/static/tabbar/xjlb-select.png
  51. BIN
      src/static/tabbar/xjlb.png
  52. BIN
      src/static/user.png
  53. 10
      src/store/index.ts
  54. 92
      src/store/user.ts
  55. 180
      src/types/auto-imports.d.ts
  56. 13
      src/types/env.d.ts
  57. 29
      src/types/global.d.ts
  58. 119
      src/uni.scss
  59. 45
      src/utils/common.ts
  60. 69
      src/utils/http.ts
  61. 6
      src/utils/rules.ts
  62. 29
      tsconfig.json
  63. 45
      vite.config.ts

5
.env.development

@ -0,0 +1,5 @@
ENV='development'
# base api
VITE_APP_BASE_URL = 'http://pos-api.lingji.vip'
VITE_APP_BASE_PRE = '/shenname'
VITE_APP_BASE_NAME = 'POS'

6
.env.production

@ -0,0 +1,6 @@
ENV='production'
# base api
VITE_APP_BASE_URL = 'http://pos-api.lingji.vip'
VITE_APP_BASE_PRE = 'http://pos-api.lingji.vip'
VITE_APP_BASE_NAME = 'POS'

3
.eslintignore

@ -0,0 +1,3 @@
dist
node_modules
uni_modules

146
.eslintrc.cjs

@ -0,0 +1,146 @@
// @see https://eslint.bootcss.com/docs/rules/
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
globals: {
NodeJS: 'readonly'
},
/* 指定如何解析语法 */
parser: 'vue-eslint-parser',
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
/* 继承已有的规则 */
extends: ['eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
plugins: ['vue', '@typescript-eslint'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.vue'],
rules: {
'no-undef': 0
}
}
],
/*
* 'off' 或 0 ==> 关闭规则
* 'warn' 或 1 ==> 打开的规则作为警告(不影响代码执行)
* 'error' 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': [2, { argsIgnorePattern: '^_' }], // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 2, // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 0, // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-namespace': 0, // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 0,
'no-prototype-builtins': 0, // 可以使用obj.hasOwnProperty()
'@typescript-eslint/no-var-requires': 0, // 不允许在import 中使用require
'@typescript-eslint/no-empty-function': 2, // 关闭空方法检查
// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
'vue/multi-word-component-names': 0, // 要求组件名称始终为 “-” 链接的单词
'vue/script-setup-uses-vars': 2, // 防止<script setup>使用的变量<template>被标记为未使用
'vue/no-mutating-props': 0, // 不允许组件 prop的改变
'vue/no-v-html': 0, // 禁止使用 v-html
'vue/no-setup-props-destructure': 0, // 禁止 props 解构传递给 setup
'vue/no-v-model-argument': 0, // 不允许添加要在 v-model 自定义组件中使用的参数
'vue/component-definition-name-casing': [2, 'PascalCase'], // 强制使用组件定义名称的特定大小写 PascalCase | kebab-case
'vue/attribute-hyphenation': [2, 'always', { ignore: [] }], // 对模板中的自定义组件强制实施属性命名样式
'vue/no-dupe-keys': [2, { groups: [] }], // 不允许重复字段名称
'vue/no-dupe-v-else-if': 2, // 不允许 / v-else-if 链中的 v-if 重复条件
'vue/no-duplicate-attributes': 2, // 禁止属性重复
'vue/no-ref-as-operand': 2, // 使用ref对象必须.value
'vue/first-attribute-linebreak': [
2,
{
singleline: 'ignore',
multiline: 'below'
}
], // 强制设置第一个属性的位置
'@typescript-eslint/no-this-alias': [
'warn',
{
allowDestructuring: false, // Disallow `const { props, state } = this`; true by default
allowedNames: ['_this'] // this的別名可以为_this
}
],
// eslint(https://eslint.bootcss.com/docs/rules/)
// 'object-curly-spacing': 0, // 在大括号内强制执行一致的间距
'no-unexpected-multiline': 2, // 禁止空余的多行
'no-await-in-loop': 2, // 该规则不允许在循环体中使用 await
'no-dupe-else-if': 2, // 禁止 if-else-if 链中的重复条件
'no-const-assign': 2, // 禁止重新分配 const 变量
'no-dupe-keys': 2, // 禁止对象字面量中的重复键
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-unused-vars': 0, // 禁止未使用的变量
'use-isnan': 2, // 检查 NaN 时需要调用 isNaN()
'valid-typeof': 2, // 强制将 typeof 表达式与有效字符串进行比较
'no-var': 2, // 要求使用 let 或 const 而不是 var
'no-extra-semi': 2, // 禁止不必要的分号
'no-multi-str': 2, // 禁止多行字符串
'no-unused-labels': 2, // 禁止未使用的标签
'array-bracket-newline': [2, 'consistent'], // 在打开数组括号之后和关闭数组括号之前强制换行
eqeqeq: [2, 'smart'], // 必须使用全等
'arrow-spacing': 2, // 在箭头函数中的箭头前后强制执行一致的间距
'function-call-argument-newline': [2, 'consistent'], // 在函数调用的参数之间强制换行
'no-undef': 2, // 禁止使用未声明的变量,除非在 /*global */ 注释中提及
complexity: [2, 15],
indent: [2, 4, { SwitchCase: 1 }],
'valid-jsdoc': 0, //jsdoc规则
'no-console': 0,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-useless-escape': 0, // 禁止不必要的转义字符
'@typescript-eslint/ban-types': 0, // 允许使用function 声明函数
'prettier/prettier': [
2,
{
//在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: 'auto',
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: 'ignore',
// 一行最多多少个字符
printWidth: 150,
// 超出打印宽度 (always | never | preserve )
proseWrap: 'preserve',
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: 'as-needed',
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 最后不需要引号
semi: false,
// 使用单引号 (true:单引号;false:双引号)
singleQuote: true,
// 缩进空格数,默认2个空格
tabWidth: 4,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'none',
// 使用制表符而不是空格缩进行
useTabs: false,
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'auto'
}
]
}
}

28
.gitignore

@ -0,0 +1,28 @@
.DS_Store
/node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.project
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
/unpackage
.hbuilderx
package-lock.josn
pnpm-lock.yaml

4
.husky/pre-commit

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run fix

8
.prettierignore

@ -0,0 +1,8 @@
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
/uni_modules/*

36
.prettierrc.cjs

@ -0,0 +1,36 @@
module.exports = {
//在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: 'auto',
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: 'ignore',
// 一行最多多少个字符
printWidth: 150,
// 超出打印宽度 (always | never | preserve )
proseWrap: 'preserve',
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: 'as-needed',
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 最后不需要引号
semi: false,
// 使用单引号 (true:单引号;false:双引号)
singleQuote: true,
// 缩进空格数,默认2个空格
tabWidth: 4,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'none',
// 使用制表符而不是空格缩进行
useTabs: false,
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'auto'
}

30
README.md

@ -0,0 +1,30 @@
## 项目介绍
[uniapp-vue-vite-ts](https://gitee.com/lxxlalala/uniapp-vue-vite-ts) 是基于 Vue3 + Vite4+ TypeScript + uview-plus V3 + Pinia 等最新主流技术栈构建的uniapp前端模板。
| 环境 | 名称版本 |
| -------------------- | :----------------------------------------------------------- |
| **开发工具** | VSCode |
| **运行环境** | Node 16+ |
| **VSCode插件(必装)** | 1. `Vue Language Features (Volar) ` <br/>2. `TypeScript Vue Plugin (Volar) ` <br/>3. 禁用 Vetur <br/>4.ESLint <br/>5.Prettier - Code formatter |
## 项目启动
```bash
# 安装 pnpm
npm install pnpm -g
# 安装依赖
pnpm install
# 启动运行
pnpm run dev:h5 (或者查看package.json运行对应平台命令)
```
## 项目文档
- [ESLint+Prettier+Husky 约束和统一前端代码规范](https://blog.csdn.net/qq_51091386/article/details/132099829)

85
auto/addPage.ts

@ -0,0 +1,85 @@
const fs = require('fs')
const pagesStr = fs.readFileSync('./src/pages.json', 'utf-8');
const pagesJson = JSON.parse(pagesStr);
// Pages页面
const Pages = pagesJson.pages.map((i) => {
return {
type: 'Pages',
name: i.name,
path: `/${i.path}`,
title: i.style?.navigationBarTitleText
};
});
// 二级页面
const subPages = pagesJson.subPackages.flatMap((i) => {
return i.pages.map((x) => {
return {
type: 'subPage',
name: x.name,
path: `/${i.root}/${x.path}`,
title: x.style?.navigationBarTitleText
};
});
});
// 当前已有页面
// const pages = [...Pages, ...subPages];
// 当前已创建文件
const filesList = fs.readdirSync('./src/pages');
const filesSubList = fs.readdirSync('./src/subpackage/pages');
// 获取需要新增的页面 =>取差集
let newPages = Pages.filter((i) => !filesList.includes(i.name));
const newSubPages = subPages.filter((i) => !filesSubList.includes(i.name));
newPages = [...newPages, ...newSubPages]
// 添加新路由
function addPages(pages) {
for (const page of pages) {
// 待修改根据path 路径生成
const { name, title, type } = page;
let dirPath = ''
switch (type) {
case 'Pages':
// 主包
dirPath = `./src/pages/${name}`;
break;
case 'subPage':
// 分包
dirPath = `./src/subpackage/pages/${name}`;
break;
default:
break;
}
// if (name.toLowerCase().indexOf("list") != -1) {
// console.log(22222222);
// } else {
// console.log(33333333);
// }
// return
fs.mkdirSync(dirPath);
const filePath = `${dirPath}/${name}.vue`;
const createStream = fs.createWriteStream(filePath);
const template =
`<script setup lang="ts">
import HeaderXcx from '@/components/Header/HeaderXcx.vue'
</script>
<template>
<view class="">
<HeaderXcx :leftTxt="'标题'" :textColor="'#fff'" :goBack="false"></HeaderXcx>
${title}
</view>
</template>
<style lang="scss" scoped></style>`;
createStream.write(template);
createStream.end();
console.log('\x1B[34m', `pages ${name} created successfully.`);
}
console.log('\x1B[32m%s\x1B[39m', '\n All files are created successfully.\n');
}
addPages(newPages);

4
auto/preinstall.js

@ -0,0 +1,4 @@
if (!/pnpm/.test(process.env.npm_execpath || '')) {
console.warn(`\u001b[33mThis repository must using pnpm as the package manager ` + ` for scripts to work properly.\u001b[39m\n`)
process.exit(1)
}

23
index.html

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<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></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<view id="app"><!--app-html--></view>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

100
package.json

@ -0,0 +1,100 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:prop": "uni --mode=production",
"dev:h5:ssr": "uni --ssr",
"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: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:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"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: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",
"type-check": "vue-tsc --noEmit",
"add": "node ./auto/addPage.ts",
"preinstall": "node ./auto/preinstall.js",
"lint": "eslint --ext .ts,.js,.vue ./src",
"fix": "eslint --fix --ext .ts,.js,.vue ./src",
"prepare": "husky install",
"rm": "rm -rf node_modules package-lock.json pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-components": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-h5": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-jd": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-lark": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-qq": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3081220230802001",
"@qiun/ucharts": "2.5.0-20230101",
"animate.css": "^4.1.1",
"echarts": "^5.5.0",
"pinia": "2.0.36",
"sass": "^1.63.2",
"uview-plus": "^3.4.9",
"vue": "^3.2.45",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@babel/eslint-parser": "^7.22.9",
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3081220230802001",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3081220230802001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3081220230802001",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"@vue/tsconfig": "^0.1.3",
"@vueuse/core": "^10.3.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.16.1",
"feng-uniapp-exploit": "^1.0.2",
"husky": "^8.0.0",
"pinia-plugin-unistorage": "^0.0.17",
"prettier": "^3.0.0",
"sass-loader": "^10.4.1",
"typescript": "^4.9.4",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.1",
"vite": "4.0.3",
"vue-tsc": "^1.0.24"
}
}

29
src/App.vue

@ -0,0 +1,29 @@
<template>
<view id="app">
<router-view></router-view>
</view>
</template>
<script setup lang="ts">
// import routingIntercept from '@/permission'
onLaunch(() => {
// routingIntercept()
})
onShow(() => {
console.log('App Show')
})
onHide(() => {
console.log('App Hide')
})
//
// provide('globalObj', <globalObjInt>{
// //
// goToPage
// });
// //
</script>
<style lang="scss">
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
@import 'uview-plus/index.scss';
</style>

98
src/api/common.ts

@ -0,0 +1,98 @@
import { request } from '@/utils/http'
export interface sendCodeType {
phone: number | string
}
export function sendCode(data: sendCodeType) {
return request.http({
url: '/api/t.sms/sendCode',
data
})
}
// 上传图片
export function uploadFile(file: any) {
let url = ''
// #ifdef APP-PLUS
url = import.meta.env.VITE_APP_BASE_URL
// #endif
// #ifdef H5
url = import.meta.env.VITE_APP_BASE_PRE
// #endif
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${url}/api/resources/upload`,
filePath: file,
name: 'file',
success: (res) => {
resolve(JSON.parse(res.data))
},
fail: (res) => {
reject(res)
}
})
})
}
// 根据 KEY 获取对应数据
export function getKeyData(key: string) {
return request.http({
url: '/api/t.systemData/getCategoryByKey',
data: { key: key }
})
}
// 根据 KEY 获取对应系统设置
export function getKeySetting(key: string) {
return request.http({
url: '/api/t.systemData/getSettingByKey',
data: { key: key }
})
}
// 获取无限极分类数据
export function getCategoryData() {
return request.http({
url: '/api/t.systemData/categoryTree'
})
}
// 意见反馈
export interface feedbackType {
type: useType
category_id: number
content: string
image?: string
contact_information: string
}
export function feedback(data: feedbackType) {
return request.http({
url: '/api/t.feedback/add',
data
})
}
// 购买支付接口
export function pay(data: { policy_id: number; uid: number; pay_amount: number; version: string }) {
return request.http({
url: '/api/t.notify/buyAgentPolicyNotify',
data
})
}
// 直采列表
export function voucherQuery() {
return request.http({
url: '/api/d.pickGiftBag/query'
})
}
// 直采支付
export function voucherPay(data: { id: number }) {
return request.http({
url: '/api/d.pickGiftBag/buy',
data
})
}

147
src/components/ex-list/ex-list.vue

@ -0,0 +1,147 @@
<script setup lang="ts">
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
import { ref, reactive, toRefs, nextTick, onMounted } from 'vue'
type argument = {
customListType?: 'default' | 'custom' | 'scroll'
isPerformSearch?: boolean
isPullDownRefresh?: boolean
onFormSearch: (formData: any, onSuccess: Function) => void
onRender?: Function
options?: { [n: string]: string | number | boolean }
emptyText?: string
emptyHeight?: number
}
const props = withDefaults(defineProps<argument>(), {
customListType: 'default',
isPerformSearch: true,
isPullDownRefresh: true,
onFormSearch: () => {
console.log('onFormSearch')
},
onRender: () => {
console.log('onRender')
},
options: () => {
return {}
},
emptyText: '暂无数据~',
emptyHeight: 60
})
let { options, isPerformSearch, isPullDownRefresh, onFormSearch, onRender } = toRefs(props)
const list = ref<any[]>([])
const status = ref('loading')
const initing = ref(true)
const loading = ref(true)
const query = reactive({
page: 1,
limit: 10,
total: 0
})
const loadData = (callback?: Function) => {
uni.showLoading({ title: '数据加载中' })
loading.value = true
const params = { ...query, ...options.value } as { [n: string]: string | number | boolean }
delete params.total
setTimeout(() => {
onFormSearch.value(params, ({ data }: { data: { data: any; total: number } }) => {
loading.value = false
initing.value = false
callback && callback()
query.page = params.page as number
query.limit = params.limit as number
query.total = data.total || 0
list.value = [...list.value, ...data.data]
status.value = query.page * query.limit >= query.total ? 'nomore' : 'loading'
onRender.value && onRender.value(data)
uni.hideLoading()
})
}, 100)
}
const refreshFn = (callback?: Function) => {
query.page = 1
query.total = 0
list.value = []
initing.value = true
loadData(callback)
}
const onScrolltolower = () => {
if (query.page * query.limit >= query.total) {
status.value = 'nomore'
} else {
if (loading.value) return
setTimeout(() => {
status.value = 'loading'
query.page += 1
loadData()
}, 1000)
}
}
onMounted(() => {
if (isPerformSearch.value) {
nextTick(() => {
refreshFn()
})
}
})
onPullDownRefresh(() => {
if (isPullDownRefresh.value) refreshFn(() => uni.stopPullDownRefresh())
})
onReachBottom(() => {
if (props.customListType !== 'scroll') {
onScrolltolower()
}
})
defineExpose({
list,
refreshFn
})
</script>
<template>
<view v-if="!initing">
<block v-if="list.length > 0">
<template v-if="customListType === 'custom'">
<slot v-bind:data="list" />
<u-loadmore :status="status" />
</template>
<template v-else-if="customListType === 'scroll'">
<scroll-view class="scroll-view" scroll-y @scrolltolower="onScrolltolower">
<view v-for="(item, index) in list" :key="index">
<slot v-bind:row="item" v-bind:index="index" />
</view>
<u-loadmore :status="status" />
</scroll-view>
</template>
<template v-else>
<view v-for="(item, index) in list" :key="index">
<slot v-bind:row="item" v-bind:index="index" />
</view>
<u-loadmore :status="status" />
</template>
</block>
<ex-empty v-show="list.length <= 0 && status === 'nomore'" :height="emptyHeight" :tips="emptyText" />
</view>
</template>
<style lang="scss" scoped>
.scroll-view {
width: 100%;
height: 100%;
}
</style>

61
src/components/ex-tags/ex-tags.vue

@ -0,0 +1,61 @@
<template>
<view class="tag_box flex-center-start" :style="{ '--borderColor': borderColor }">
<view class="border" :style="borderStyle" />
<text :style="{ color: labelColor }">{{ label }}</text>
<view class="flex1">
<slot></slot>
</view>
</view>
</template>
<script setup lang="ts">
interface tagsInt {
label: string
labelColor?: string
borderColor?: string
borderType?: 'round' | 'line'
}
const props = withDefaults(defineProps<tagsInt>(), {
label: '收益',
labelColor: '#616161',
borderColor: '#ff8d1a',
borderType: 'line'
})
const borderStyle = computed(() => {
if (props.borderType === 'line') {
return {
width: '8rpx',
height: '28rpx',
marginRight: '16rpx'
}
} else {
return {
width: '16rpx',
height: '16rpx',
marginRight: '13rpx'
}
}
})
</script>
<style scoped lang="scss">
.tag_box {
font-size: 28rpx;
font-weight: 400;
line-height: 40rpx;
--borderColor: #ff8d1a;
.border {
width: 8rpx;
height: 28rpx;
border-radius: 90rpx;
background-color: var(--borderColor);
}
> .flex1 {
padding-left: 20rpx;
}
}
</style>

13
src/config/index.ts

@ -0,0 +1,13 @@
export const Prefix = 'POS_'
export const getPrefixName = (name: string) => {
return Prefix + name
}
export function getAuthorization() {
return uni.getStorageSync(getPrefixName('user'))
}
export function removeAuthorization() {
return uni.removeStorageSync(getPrefixName('user'))
}

128
src/hooks/paginationLoader.ts

@ -0,0 +1,128 @@
/*
* @description:
* @fileName: useListLoadClass.ts
* @author: lxx
* @date: 2023-07-08 08:55:52
* @version: V1.0.0
*/
import { ref } from 'vue'
class PaginationLoader<T> {
// 分页数据
private data = ref<T[]>([])
// 分页基础参数
private requestParams = {
page: 1,
limit: 10
}
// 是否正在加载
private isLoading = ref(false)
// 是否还有更多数据
private hasMore = ref(true)
// 请求函数(由外部传入)
private fetchFn: (formData: Object, onSuccess: Function) => Function
constructor(fetchFn: (formData: Object, onSuccess: Function) => Function) {
this.fetchFn = fetchFn
}
// 获取当前数据
get dataList() {
return this.data.value
}
// 获取是否正在加载
get loading() {
return this.isLoading.value
}
// 获取是否还有更多数据
get more() {
return this.hasMore.value
}
// 加载下一页数据
async loadNextPage() {
// if (this.isLoading.value || !this.hasMore.value) return
// uni.showLoading({ title: '加载中...' })
// this.isLoading.value = true
// try {
// const result = await this.fetchFn(this.currentPage.value, this.pageSize)
// if (result.length > 0) {
// this.data.value = [...this.data.value, ...result]
// this.currentPage.value += 1
// } else {
// this.hasMore.value = false // 没有更多数据
// }
// } catch (error) {
// console.error('加载数据失败:', error)
// } finally {
// this.isLoading.value = false
// }
uni.showLoading({ title: '数据加载中' })
this.isLoading.value = false
const params = { ...this.requestParams } as { [n: string]: string | number | boolean }
setTimeout(() => {
this.fetchFn(params, ({ data }: { data: { data: any; total: number } }) => {
this.isLoading.value = true
this.requestParams.page = params.page as number
this.requestParams.limit = params.limit as number
this.data.value = [...this.data.value, ...data.data]
uni.hideLoading()
})
}, 100)
}
// 重置分页状态
reset() {
this.data.value = []
this.requestParams.page = 1
this.hasMore.value = true
this.isLoading.value = false
}
}
// export default PaginationLoader
const fetchData = async (formData: any, onSuccess: Function) => {
const submitData = { ...formData }
point_order_list(submitData).then((res) => {
const { data } = res as { data: { data: any; total: number } }
onSuccess({ data })
})
// // 模拟接口请求
// return new Promise<{ id: number; name: string }[]>((resolve) => {
// setTimeout(() => {
// const data = Array.from({ length: pageSize }, (_, index) => ({
// id: (page - 1) * pageSize + index,
// name: `Item ${(page - 1) * pageSize + index}`
// }))
// resolve(data)
// }, 1000)
// })
}
// 创建分页加载实例
const paginationLoader = new PaginationLoader(fetchData)
// 加载下一页数据
paginationLoader.loadNextPage(formDate.value)
// 初始化加载第一页数据
paginationLoader.loadNextPage()
return {
dataList: paginationLoader.dataList,
loading: paginationLoader.loading,
more: paginationLoader.more,
loadNextPage
}

125
src/hooks/useListLoadClass.ts

@ -0,0 +1,125 @@
/*
* @description:
* @fileName: useListLoadClass.ts
* @author: lxx
* @date: 2023-07-08 08:55:52
* @version: V1.0.0
*/
import { ref, computed } from 'vue'
import { onReachBottom } from '@dcloudio/uni-app'
class LoadDataClass {
// 请求参数
static queryParams = {
page: 1,
limit: 10
}
// 列表数据
list = ref<any[]>([])
total = ref(0)
// 前置处理方法
afterLoadData: Function | undefined
// 请求方法
Query: Function
// 加载状态参数
isLoading = ref(false)
// 无更多数据了
isNoData = computed(() => {
if (LoadDataClass.queryParams.page * LoadDataClass.queryParams.limit >= this.total.value) {
return true
} else {
return false
}
})
// 显示暂无数据
isEmpty = computed(() => {
if (this.total.value === 0) {
return true
} else {
return false
}
})
constructor(apiFunctions: Function, afterLoadData?: Function, options?: any) {
this.Query = apiFunctions
this.afterLoadData = afterLoadData
// 存在额外参数拼接
if (options) {
LoadDataClass.queryParams = { ...LoadDataClass.queryParams, ...options }
}
// 加载数据
this.LoadData()
}
// 加载数据
LoadData = async () => {
uni.showLoading({
title: '加载中...'
})
this.isLoading.value = true
const res = await this.Query(LoadDataClass.queryParams)
this.afterLoadData && this.afterLoadData(res)
this.total.value = res.data.total
this.list.value = this.list.value.concat(res.data.data)
uni.hideLoading()
uni.stopPullDownRefresh()
this.isLoading.value = false
}
// 加载更多
LoadMore = () => {
if (this.isNoData.value || this.isLoading.value) return // 无数据或者加载中不进行加载
LoadDataClass.queryParams.page += 1
this.LoadData()
}
// 重置参数
queryParamsReset = () => {
LoadDataClass.queryParams = {
page: 1,
limit: 10
}
}
/**
*
* @param isClear: 是否清空数据
*/
ReLoad = (isClear: boolean = true) => {
this.isLoading.value = false
this.list.value = []
if (isClear) {
this.queryParamsReset()
} else {
LoadDataClass.queryParams.page = 1
}
this.LoadData()
}
}
/**
*
* @param api: ListAPI
* @param afterLoadData: res数据前置处理方法
* @returns
*/
interface LoadDataInt {
api: Function
afterLoadData?: Function
options?: any
}
export function LoadData({ api, afterLoadData, options }: LoadDataInt) {
const data = new LoadDataClass(api, afterLoadData, options)
// 下拉加载
onReachBottom(() => {
console.log('onReachBottom')
data.LoadMore()
})
return {
list: data.list,
isLoading: data.isLoading,
isNoData: data.isNoData,
isEmpty: data.isEmpty,
ReLoad: data.ReLoad
}
}

127
src/hooks/usePagingLoad.ts

@ -0,0 +1,127 @@
import { ref, reactive, computed } from 'vue'
import { onReachBottom } from '@dcloudio/uni-app'
/*
* @description:
* @fileName: useListLoadClass.ts
* @author: lxx
* @date: 2023-07-08 08:55:52
* @version: V1.0.0
* loading状态
* @param {*} Query
* @param {*} LoadData
* @param {*} ReLoad (isClear?: boolean) isClear true时将请求参数queryParams
*/
export function usePagingLoad(Query: any) {
// 下拉加载
onReachBottom(() => {
console.log('onReachBottom')
loadMore()
})
const isLoading = ref(false)
let queryParams = reactive({} as any)
queryParams = {
page: 1,
limit: 10
}
const total = ref(0)
const list = ref([])
// 无更多数据了
const isNoData = computed(() => {
if (queryParams.page * queryParams.limit >= total.value) {
return true
} else {
return false
}
})
// 显示暂无数据
const isEmpty = computed(() => {
if (total.value === 0) {
return true
} else {
return false
}
})
interface optionInt {
key: string
val: any
}
const LoadData = async (afterLoadData?: any, option?: optionInt[]) => {
const obj: any = {}
console.log('option', option)
if (option && option?.length > 0) {
option?.map((item) => {
obj[item?.key] = item.val
})
}
// , ...rest: any[]
// if (rest.length > 0) {
// rest.map((item) => {
// obj[item?.key] = item?.val;
// });
// }
queryParams = reactive({ ...queryParams, ...obj })
uni.showLoading({
title: '加载中...'
})
isLoading.value = true
const res = await Query(queryParams)
total.value = res?.data?.total
// 数据加载完成后 设置 after 钩子
afterLoadData && afterLoadData(res.data)
console.log('res.data', res.data)
list.value = list.value.concat(res?.data?.data)
uni.hideLoading()
uni.stopPullDownRefresh()
isLoading.value = false
}
// const afterLoadData = (data: any) => {
// console.log(data);
// };
const ReLoad = (option?: optionInt[], isClear?: boolean) => {
isLoading.value = false
list.value = []
if (isClear) {
queryParams = reactive({
page: 1,
limit: 10
})
} else {
queryParams.page = 1
}
const obj: any = {}
if (option && option?.length > 0) {
option?.map((item) => {
obj[item?.key] = item.val
})
}
queryParams = reactive({ ...queryParams, ...obj })
LoadData()
}
const loadMore = () => {
if (isNoData.value || isLoading.value) return // 无数据或者加载中不进行加载
queryParams.page += 1
LoadData()
}
return {
list,
LoadData,
ReLoad,
isNoData,
isEmpty,
isLoading
}
}

16
src/main.ts

@ -0,0 +1,16 @@
import { createSSRApp } from 'vue'
import App from './App.vue'
import uviewPlus from 'uview-plus'
import fengUniappExploit from 'feng-uniapp-exploit'
import store from './store'
export function createApp() {
const app = createSSRApp(App)
app.use(uviewPlus)
app.use(fengUniappExploit)
app.use(store)
return { app }
}

131
src/manifest.json

@ -0,0 +1,131 @@
{
"name" : "新未来",
"appid" : "__UNI__1AE1F37",
"description" : "v3+ts+uniapp模版",
"versionName" : "1.0.68",
"versionCode" : 168,
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"compatible" : {
"ignoreVersion" : true
},
/* */
"modules" : {
"Barcode" : {},
"Camera" : {},
"VideoPlayer" : {},
"Share" : {},
"Geolocation" : {},
"Maps" : {}
},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"minSdkVersion" : 21
},
/* ios */
"ios" : {
"dSYMs" : false
},
/* SDK */
"sdkConfigs" : {
"ad" : {},
"share" : {
"weixin" : {
"appid" : "wxd448c878a54da98d",
"UniversalLinks" : ""
}
},
"geolocation" : {
"amap" : {
"name" : "amap_18648278829CcsBXVUm1",
"__platform__" : [ "ios", "android" ],
"appkey_ios" : "435934cdfcc901e4872a70391d92cccd",
"appkey_android" : "435934cdfcc901e4872a70391d92cccd"
}
},
"maps" : {
"amap" : {
"name" : "amap_18648278829CcsBXVUm1",
"appkey_ios" : "435934cdfcc901e4872a70391d92cccd",
"appkey_android" : "435934cdfcc901e4872a70391d92cccd"
}
}
},
"icons" : {
"android" : {
"hdpi" : "src/static/logo.png",
"xhdpi" : "src/static/logo.png",
"xxhdpi" : "src/static/logo.png",
"xxxhdpi" : "src/static/logo.png"
}
},
"splashscreen" : {
"iosStyle" : "common",
"androidStyle" : "default",
"android" : {
"hdpi" : "C:/Users/Lenovo/Desktop/480x762.9/res/drawable-xhdpi/480x762.9.png",
"xhdpi" : "C:/Users/Lenovo/Desktop/720x1242.9/res/drawable-xhdpi/720x1242.9.png",
"xxhdpi" : "C:/Users/Lenovo/Desktop/1080x1882.9/res/drawable-xhdpi/1080x1882.9.png"
},
"ios" : {
"storyboard" : "C:/Users/Lenovo/Desktop/CustomStoryboard.zip"
}
}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false,
"es6" : true,
"postcss" : false,
"minified" : true
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}

111
src/pages.json

@ -0,0 +1,111 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^ex-(.*)": "feng-uniapp-exploit/components/ex-$1/ex-$1.vue",
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue"
}
},
"pages": [
{
"path": "pages/votingElection/index",
"style": {
"navigationBarTitleText": "投票选举",
"enablePullDownRefresh": true
}
},
{
"path": "pages/electionList/index",
"style": {
"navigationBarTitleText": "选举列表",
"enablePullDownRefresh": false
}
},
{
"path": "pages/electionResults/index",
"style": {
"navigationBarTitleText": "选举结果",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": false
}
},
{
"path": "pages/myElection/index",
"style": {
"navigationBarTitleText": "我的选举",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": false
}
},
{
"name": "links",
"path": "pages/index/links",
"style": {
"navigationBarTitleText": "外部链接",
"enablePullDownRefresh": false
}
},
{
"name": "guide",
"path": "pages/index/guide",
"style": {
"navigationBarTitleText": "用户指南",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"name": "user",
"path": "pages/mine/index",
"style": {
"navigationBarTitleText": "个人信息",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#9CA3AF",
"selectedColor": "#2563EB",
"borderStyle": "white",
"backgroundColor": "#F9FAFB",
"list": [
{
"pagePath": "pages/votingElection/index",
"iconPath": "static/tabbar/tpxj.png",
"selectedIconPath": "static/tabbar/tpxj-select.png",
"text": "投票选举"
},
{
"pagePath": "pages/electionList/index",
"iconPath": "static/tabbar/xjlb.png",
"selectedIconPath": "static/tabbar/xjlb-select.png",
"text": "选举列表"
},
{
"pagePath": "pages/electionResults/index",
"iconPath": "static/tabbar/xjjg.png",
"selectedIconPath": "static/tabbar/xjjg-select.png",
"text": "选举结果"
},
{
"pagePath": "pages/myElection/index",
"iconPath": "static/tabbar/wdxj.png",
"selectedIconPath": "static/tabbar/wdxj-select.png",
"text": "我的选举"
},
{
"pagePath": "pages/mine/index",
"iconPath": "static/tabbar/grxx.png",
"selectedIconPath": "static/tabbar/grxx-select.png",
"text": "个人信息"
}
]
}
}

218
src/pages/electionList/index.vue

@ -0,0 +1,218 @@
<script setup lang="ts">
import EcList from '@/components/ex-list/ex-list.vue'
const doSearch = (_formData: { page: number; limit: number }, onSuccess: Function) => {
onSuccess({
data: {
data:[
{ name: '测试一' },
{ name: '测试二' }
],
total: 4
}
})
}
</script>
<template>
<view class="electionList">
<EcList ref="reListRef" custom-list-type="scroll" :on-form-search="doSearch">
<template v-slot="{ row, index }">
<view class="items" :uid="'items' + index">
<view class="flex">
<view class="flex1">
<view class="flex-center-start">
<text class="text-ellipsis title">{{ '2024年度学生会主席选举' }}</text>
<text class="status b">进行中</text>
<!-- <text class="status f">未开始</text> -->
<!-- <text class="status e">已结束</text> -->
</view>
<view class="time">{{ '投票时间:2024-03-01 至 2024-03-07' }}</view>
</view>
<view :class="{ arrow: true, active: row.showInfo }" @click="row.showInfo = !row.showInfo">
<u-icon name="arrow-down" color="#9CA3AF" />
</view>
</view>
<view class="info" v-if="row.showInfo">
<view class="flex info-items">
<view class="head"></view>
<view class="content flex1">
<view class="name flex-center-start">
<text>陈佳怡</text>
<!-- <text class="status">当选</text> -->
<text class="status un">未当选</text>
</view>
<view class="votes">得票数286</view>
</view>
<view class="progress">
<u-line-progress :percentage="60" height="8rpx" active-color="#ff0000" :show-text="false" />
<view class="progress-text">60%</view>
</view>
</view>
<view class="flex info-items">
<view class="head"></view>
<view class="content flex1">
<view class="name flex-center-start">
<text>陈佳怡</text>
<text class="status">当选</text>
<!-- <text class="status un">未当选</text> -->
</view>
<view class="votes">得票数286</view>
</view>
<view class="progress">
<u-line-progress :percentage="60" height="8rpx" active-color="#ff0000" :show-text="false" />
<view class="progress-text">60%</view>
</view>
</view>
<view class="info-bts">查看投票详情</view>
</view>
</view>
</template>
</EcList>
</view>
</template>
<style scoped lang="scss">
.electionList {
width: 100%;
box-sizing: border-box;
min-height: calc(100vh - 100rpx);
padding: 32rpx;
background-color: #f9fafb;
.items {
padding: 32rpx;
border-radius: 12px;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.001), rgba(0, 0, 0, 0.001)), #ffffff;
box-sizing: border-box;
border: 1px solid #f3f4f6;
box-shadow:
0px 1px 2px -1px rgba(0, 0, 0, 0.1),
0px 1px 3px 0px rgba(0, 0, 0, 0.1);
margin-bottom: 32rpx;
.text-ellipsis {
overflow: hidden;
white-space: noraml;
text-overflow: ellipsis;
}
.title {
color: #000;
font-size: 32rpx;
font-weight: 500;
line-height: 48rpx;
}
.status {
font-size: 24rpx;
border-radius: 20rpx;
padding: 0 16rpx;
margin-left: 16rpx;
white-space: nowrap;
&.b {
color: #fff;
background-color: #2563eb;
}
&.f {
color: #6b7280;
background-color: #e5e7eb;
}
&.e {
color: #fff;
background-color: #ef4444;
}
}
.time {
padding-top: 8rpx;
color: #6b7280;
line-height: 40rpx;
font-size: 28rpx;
}
.arrow {
transition: 0.3s all ease-in;
&.active {
transform: rotateZ(180deg);
}
}
.info {
&-items {
margin-top: 32rpx;
.head {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
overflow: hidden;
background-color: #6b7280;
margin-right: 24rpx;
}
.content {
height: min-content;
.name {
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
color: #000000;
.status {
font-size: 20rpx;
font-weight: normal;
border-radius: 999rpx;
padding: 8rpx 24rpx;
line-height: 20rpx;
color: #2563eb;
background: rgba(37, 99, 235, 0.1);
&.un {
color: #f44336;
background: rgba(244, 67, 54, 0.1);
}
}
}
.votes {
font-size: 28rpx;
line-height: 40rpx;
color: #6b7280;
}
}
.progress {
width: 192rpx;
height: min-content;
&-text {
padding-top: 8rpx;
color: #6b7280;
font-size: 24rpx;
line-height: 32rpx;
}
}
}
&-bts {
padding: 16rpx;
margin: 48rpx 0 24rpx;
font-size: 28rpx;
font-weight: normal;
line-height: 42rpx;
text-align: center;
color: #ffffff;
background-color: #2563eb;
border-radius: 8rpx;
}
}
}
}
</style>

7
src/pages/electionResults/index.vue

@ -0,0 +1,7 @@
<template>
<view>选举结果</view>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped></style>

112
src/pages/index/guide.vue

@ -0,0 +1,112 @@
<script setup lang="ts">
import useUserStore from '@/store/user'
const userStore = useUserStore()
const phone = ref('')
const kefuRef = ref()
const list = ref<
{
id?: number
name?: string
guide: { guide_id?: number; title?: string }[]
}[]
>([])
const kefuConfirm = () => {
uni.makePhoneCall({
phoneNumber: phone.value,
success: (response) => {
console.log('success', response)
},
fail: (err) => {
console.log('fail', err)
}
})
}
const navto = (url: string, params = {}) => uni.$util.goToPage({ url, params })
onShow(() => {
// getKeySetting('service_telephone').then(({ data: { value } }: any) => (phone.value = value.trim()))
// listApi().then(({ data }: any) => (list.value = data || []))
})
</script>
<template>
<ex-header title="用户指南" />
<view class="guide-view">
<view class="kefu-btn flex" @click="kefuRef.show = true">
<image src="http://cdn-pos.lingji.vip/static/icon/customer.png" mode="widthFix" style="width: 40rpx" />
<text style="margin-left: 14rpx">联系客服</text>
</view>
<view class="pages-view">
<u-collapse accordion :border="true">
<block v-for="(v, k) of list" :key="k">
<u-collapse-item :title="v.name">
<view
:key="ck"
v-for="(cv, ck) of v.guide"
:class="{ 'pages-view-content': true, 'text-ellipsis': true, border: ck < v.guide.length - 1 }"
@click.stop="navto('pages/setting/agreement', { type: 'guide', id: cv.guide_id })"
>
{{ cv.title }}
</view>
</u-collapse-item>
</block>
</u-collapse>
</view>
<ex-dialog ref="kefuRef" title="联系客服" show-cancel-button confirm-text="拨号" confirm-color="#43CF7C" @confirm="kefuConfirm">
<view class="flex column">
<image style="width: 84rpx" :src="`http://cdn-pos.lingji.vip/static/icon/phone-${userStore.useType}.png`" mode="widthFix" />
<view style="margin-top: 36rpx; font-size: 60rpx; font-weight: 700; color: #383838; text-align: center">
{{ phone }}
</view>
</view>
</ex-dialog>
</view>
</template>
<style scoped lang="scss">
.guide-view {
padding: 26rpx 28rpx;
.kefu-btn {
color: #fff;
padding: 32rpx;
margin-bottom: 26rpx;
background: linear-gradient(90deg, #4778ff 0%, #4778ffb8 100%);
}
.pages-view {
background-color: #fff;
&-content {
padding: 16rpx;
color: #9a9bad;
text-align: left;
font-size: 28rpx;
font-weight: 400;
letter-spacing: 0;
line-height: 48rpx;
&.border {
border-bottom: 1rpx solid #dbdbdb;
}
}
:deep(.u-collapse) {
.u-cell__body {
padding: 24rpx 28rpx;
}
.u-collapse-item__content__text {
padding: 0 28rpx;
border-bottom: 4rpx solid #f5f5f5 !important;
&.content-class {
border: none !important;
}
}
}
}
}
</style>

9
src/pages/index/links.vue

@ -0,0 +1,9 @@
<template>
<web-view :src="url" />
</template>
<script setup lang="ts">
const url = ref('')
onLoad((opt) => (url.value = opt?.url))
</script>

314
src/pages/mine/index.vue

@ -0,0 +1,314 @@
<script setup lang="ts">
import useUserStore from '@/store/user'
import { getHeaderImage } from '@/utils/common'
const userStore = useUserStore()
const OBJ = {
other: [
{
label: '地址管理',
icon: 'position',
url: 'pages/address/index'
},
{
label: '用户指南',
icon: 'guide',
url: 'pages/index/guide'
},
{
label: '意见反馈',
icon: 'suggest',
url: 'pages/feedback/index'
},
{
label: '设置',
icon: 'setting',
url: 'pages/setting/index'
}
]
}
const { statusBarHeight } = uni.getSystemInfoSync()
const userphone = computed(() => uni.$util.hidePhone(userStore.userInfo.phone || '17612341234'))
const data = reactive({
cumulative: '',
estimate: 0,
formalities: 0
})
const navto = (url: string, params = {}) => uni.$util.goToPage({ url, params })
</script>
<template>
<view class="user-container">
<view class="header-box" :style="{ paddingTop: `${(statusBarHeight as number) * 2 + 20}rpx` }">
<view class="title">我的</view>
<view class="flex user-box" @click="navto('pages/mine/info')">
<view class="headimg">
<image class="img" :src="getHeaderImage(userStore.userInfo.head_image)" mode="widthFix" />
</view>
<view class="flex-start-evenly column">
<view class="name">{{ userStore?.userInfo?.nickname || '' }}</view>
<view class="phone">{{ userphone }}</view>
</view>
<view class="flex1 flex-center-end">
<u-icon name="arrow-right" size="14" color="#fff" />
</view>
</view>
<view class="options flex-center-evenly">
<view class="flex" @click="navto('pages/mine/wallet')">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/kb.png" mode="heightFix" />
<text>钱包</text>
</view>
<view class="options-line"></view>
<view class="flex" @click="navto('pages/mine/revenue')">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/sr.png" mode="heightFix" />
<text>收入明细</text>
</view>
</view>
</view>
<view class="report">
<view class="report-title">我的省钱报告</view>
<view class="flex" style="margin-bottom: 32rpx">
<view style="margin-right: 100rpx">
<view class="report-tip">累计节省手续费</view>
<u-count-to ref="uCountTo" :end-val="data.formalities" color="#fff" font-size="36rpx" />
</view>
<view style="margin-right: auto">
<view class="report-tip">预计最高还能省</view>
<u-count-to ref="uCountTo" :end-val="data.estimate" color="#fff" font-size="36rpx" />
</view>
</view>
<view class="report-tip" style="color: #fff">
<text>累计综合收款费率</text>
<text class="report-price">{{ data.cumulative }}%</text>
</view>
</view>
<view class="option-list">
<view class="option-list-head flex-center-between">
<view class="option-list-head-title">我的订单</view>
<view class="option-list-head-tip flex" @click="navto('pages/order/list')">
<text>查看全部</text>
<u-icon name="arrow-right" size="16" color="#DEDEDE" />
</view>
</view>
<view class="option-list-body">
<view class="flex column" @click="navto('pages/order/list', { status: 'unpaid' })">
<view class="img-box bg flex">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/besent.png" mode="widthFix" />
</view>
<view class="text">待付款</view>
</view>
<view class="flex column" @click="navto('pages/order/list', { status: 'paid' })">
<view class="img-box bg flex">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/unsend.png" mode="widthFix" />
</view>
<view class="text">待发货</view>
</view>
<view class="flex column" @click="navto('pages/order/list', { status: 'shipped' })">
<view class="img-box bg flex">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/received.png" mode="widthFix" />
</view>
<view class="text">待收货</view>
</view>
<view class="flex column" @click="navto('pages/order/list', { status: 'finished' })">
<view class="img-box bg flex">
<image src="http://cdn-pos.lingji.vip/static/icon/mine/completed.png" mode="widthFix" />
</view>
<view class="text">已完成</view>
</view>
</view>
</view>
<view class="option-list wb">
<view class="option-list-head flex-center-between">
<view class="option-list-head-title">其它</view>
</view>
<view class="option-list-body">
<block v-for="(v, k) of OBJ.other" :key="k">
<view class="flex column" @click="navto(v.url)">
<view class="img-box flex">
<image :src="`http://cdn-pos.lingji.vip/static/icon/mine/${v.icon}.png`" mode="widthFix" />
</view>
<view class="text">{{ v.label }}</view>
</view>
</block>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.user-container {
background-color: #fff;
.header-box {
width: 100%;
background-image: url(http://cdn-pos.lingji.vip/static/images/mine/bg.png);
background-repeat: no-repeat;
background-size: cover;
padding-bottom: 24rpx;
.title {
font-size: 32rpx;
font-weight: 500;
color: #fff;
text-align: center;
}
.user-box {
width: 100%;
padding: 0 32rpx;
margin-top: 48rpx;
margin-bottom: 46rpx;
align-items: stretch;
.headimg {
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background-clip: content-box;
background-color: #000000a6;
border: 10rpx solid rgba(255, 255, 255, 0.3);
overflow: hidden;
position: relative;
margin-right: 22rpx;
> .img {
width: 120%;
position: absolute;
top: -10%;
left: -10%;
}
}
.name {
font-size: 32rpx;
font-weight: 400;
color: #fff;
}
.phone {
font-size: 14px;
font-weight: 400;
color: #fff;
}
}
> .options {
image {
min-width: 32rpx;
height: 36rpx;
margin-right: 24rpx;
}
text {
color: #fff;
font-size: 28rpx;
font-weight: 400;
letter-spacing: 0rpx;
}
.options-line {
width: 0rpx;
height: 48rpx;
border-right: 2rpx solid #ffffff2e;
}
}
}
.report {
width: calc(100% - 56rpx);
margin: 28rpx 28rpx 8rpx;
border-radius: 8rpx;
padding: 26rpx 26rpx 28rpx;
background-image: url(http://cdn-pos.lingji.vip/static/images/mine/bg1.png);
background-repeat: no-repeat;
background-size: cover;
&-title {
color: #fff;
text-align: left;
font-size: 36rpx;
font-weight: bold;
font-style: oblique;
line-height: 46rpx;
letter-spacing: 2rpx;
margin-bottom: 24rpx;
}
&-tip {
font-size: 20rpx;
font-weight: 400;
letter-spacing: 0rpx;
line-height: 29rpx;
color: #ffe9d4;
text-align: left;
}
}
.option-list {
padding: 22rpx 28rpx 40rpx;
border-bottom: 16rpx solid #f5f5f5;
&.wb {
border-color: #fff;
}
&-head {
margin-bottom: 30rpx;
&-title {
color: #333;
font-size: 32rpx;
font-weight: 400;
line-height: 46rpx;
letter-spacing: 0rpx;
}
&-tip {
color: #a6a6a6;
font-size: 24rpx;
font-weight: 400;
letter-spacing: 0rpx;
line-height: 35rpx;
}
}
&-body {
display: grid;
grid-template-columns: repeat(4, 1fr);
row-gap: 30rpx;
.img-box {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
&.bg {
background: #f5f8ff;
}
image {
width: 58rpx;
}
}
.text {
color: #333;
font-size: 26rpx;
font-weight: 400;
text-align: center;
line-height: 38rpx;
letter-spacing: 0rpx;
margin-top: 4rpx;
}
}
}
}
</style>

7
src/pages/myElection/index.vue

@ -0,0 +1,7 @@
<template>
<view>我的选举</view>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped></style>

338
src/pages/votingElection/index.vue

@ -0,0 +1,338 @@
<script setup lang="ts">
const doSearch = (_formData: { page: number; limit: number }, onSuccess: Function) => {
// const submitData = { ...formData }
// listApi(submitData, userStore.useType).then((res) => {
// const { data } = res as { data: { data: any; total: number } }
// onSuccess({ data })
// })
onSuccess({
data: {
data: [
{
img: '/static/img/grxx.png',
name: '陈志远',
class: '现任财务总监'
},
{
img: '/static/img/grxx.png',
name: '陈志远',
class: '现任财务总监'
},
{
img: '/static/img/grxx.png',
name: '陈志远',
class: '现任财务总监'
},
{
img: '/static/img/grxx.png',
name: '陈志远',
class: '现任财务总监'
},
{
img: '/static/img/grxx.png',
name: '陈志远',
class: '现任财务总监'
}
],
total: 0
}
})
}
const selectIndex = ref(0)
const buttlist = ref([
{
butname: '同意'
},
{
butname: '反对'
},
{
butname: '弃权'
}
])
const selectBut = (index: number) => {
selectIndex.value = index
}
</script>
<template>
<!-- 你的页面内容 -->
<view class="box">
<ex-list ref="reListRef" custom-list-type="scroll" :on-form-search="doSearch" empty-text="">
<template v-slot="{ data }">
<view class="headpart">
<view class="title">2024年度董事会成员选举</view>
<view class="time">投票开始时间2024年3月15日 18:00</view>
<view class="time">投票截止时间2024年3月15日 18:00</view>
</view>
<view class="tppart">
<view class="tpone" v-for="(row, index) of data" :key="'tpone' + index">
<view class="topp">
<img style="width: 96rpx; height: 96rpx; border-radius: 50%" :src="row.img" alt="" />
<view class="rightpart">
<view class="name">
{{ row.name }}
</view>
<view class="class">
{{ row.class }}
</view>
</view>
</view>
<view class="bottomp">
<view
class="minbut"
v-for="(item, index) in buttlist"
:key="'minbut' + index"
@click="selectBut(index)"
:class="{ active: selectIndex === index }"
>
{{ item.butname }}
</view>
</view>
</view>
</view>
</template>
</ex-list>
<view class="bottbutton">
<view class="qbty">全部同意</view>
<view class="tjtp">提交投票</view>
</view>
</view>
</template>
<style scoped lang="scss">
.box {
width: 100%;
height: 100vh;
background-color: #f9fafb;
.headpart {
width: 86%;
height: 194rpx;
border-radius: 24rpx;
background-color: #eff6ff;
margin: 32rpx auto;
padding: 20rpx 3%;
.title {
color: #2563eb;
font-size: 28rpx;
margin-top: 16rpx;
}
.time {
margin-top: 16rpx;
color: #4b5563;
font-size: 28rpx;
}
}
.tppart {
width: 100%;
max-height: 69vh;
overflow-y: auto;
display: grid;
justify-items: center;
.tpone {
width: 91%;
height: 264rpx;
padding: 20rpx 4%;
box-sizing: border-box;
border-radius: 24rpx;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.001), rgba(0, 0, 0, 0.001)), #ffffff;
box-sizing: border-box;
border: 2rpx solid #f3f4f6;
box-shadow:
0rpx 2rpx 4rpx -2rpx rgba(0, 0, 0, 0.1),
0rpx 2rpx 6rpx 0rpx rgba(0, 0, 0, 0.1);
margin-top: 32rpx;
display: grid;
.topp {
display: flex;
.rightpart {
margin-left: 32rpx;
display: grid;
justify-items: left;
align-content: baseline;
.name {
font-family: Roboto;
font-size: 28rpx;
font-weight: 500;
line-height: 42rpx;
letter-spacing: normal;
color: #000000;
margin-top: 7rpx;
}
.class {
font-family: Roboto;
font-size: 28rpx;
font-weight: normal;
line-height: 40rpx;
letter-spacing: normal;
color: #6b7280;
margin-top: 7rpx;
}
}
}
.bottomp {
display: flex;
justify-content: space-between;
gap: 0rpx 20rpx;
.minbut {
flex: 1;
width: 190rpx;
height: 76rpx;
/* 自动布局 */
display: flex;
justify-content: center;
padding: 16rpx 32rpx;
flex-wrap: wrap;
align-content: flex-start;
border-radius: 8rpx;
background: #ffffff;
box-sizing: border-box;
border: 2rpx solid #d1d5db;
font-family: Roboto;
font-size: 28rpx;
font-weight: normal;
line-height: 40rpx;
text-align: center;
letter-spacing: normal;
color: #4b5563;
}
.minbut.active {
border: 2rpx solid #2563eb;
color: #2563eb;
}
}
}
.tpone:first-child {
margin-top: 0;
}
}
.bottbutton {
width: 100%;
height: 10vh;
display: flex;
padding: 24rpx 32rpx;
gap: 0rpx 24rpx;
flex-wrap: wrap;
align-content: flex-start;
background: #ffffff;
box-sizing: border-box;
border-width: 2rpx 0rpx 0rpx 0rpx;
border-style: solid;
border-color: #f3f4f6;
position: fixed;
bottom: 0;
.qbty {
width: 331rpx;
height: 90rpx;
/* 自动布局 */
display: flex;
box-sizing: border-box;
justify-content: center;
padding: 24rpx 0rpx;
gap: 0rpx 20rpx;
flex-wrap: wrap;
border-radius: 8rpx;
background: #eff6ff;
font-family: Roboto;
font-size: 28rpx;
font-weight: 500;
line-height: 42rpx;
text-align: center;
letter-spacing: normal;
color: #2563eb;
}
.tjtp {
width: 331rpx;
height: 90rpx;
/* 自动布局 */
display: flex;
box-sizing: border-box;
justify-content: center;
padding: 24rpx 0rpx;
gap: 0rpx 20rpx;
flex-wrap: wrap;
border-radius: 8rpx;
background: #2563eb;
font-family: Roboto;
font-size: 28rpx;
font-weight: 500;
line-height: 42rpx;
text-align: center;
letter-spacing: normal;
color: #ffffff;
}
}
.address-items {
padding: 20rpx 15rpx 32rpx 28rpx;
background-color: #fff;
border-bottom: 2rpx solid #f5f5f5;
.left {
flex: 1;
overflow: hidden;
.name {
font-size: 28rpx;
font-weight: 700;
color: #101010;
line-height: 40rpx;
margin-right: 16rpx;
}
.isdefault {
font-size: 20rpx;
font-weight: 400;
color: #fff;
padding: 4rpx 12rpx;
border-radius: 8rpx;
background: linear-gradient(90deg, #4778ff 0%, #4778ffb8 100%);
}
.info {
width: 100%;
color: #666;
font-size: 24rpx;
font-weight: 400;
margin-top: 12rpx;
}
}
.right {
width: 140rpx;
padding-left: 32rpx;
font-size: 24rpx;
font-weight: 400;
color: #4979ff;
text-align: right;
}
}
.buts {
width: 100%;
height: 10vh;
background-color: F3F4F6;
position: fixed;
bottom: 0;
border-top: 2rpx solid #ebebec;
}
}
</style>

51
src/permission.ts

@ -0,0 +1,51 @@
// import { getAuthorization } from '@/config'
// 白名单
// const whiteList = ['/agentpages/index/index', '/agentpages/mine/index']
export default async function () {
const list = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']
// 用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器
list.forEach((item) => {
uni.addInterceptor(item, {
invoke(e) {
console.log('e', e)
// 获取要跳转的页面路径(url去掉"?"和"?"后的参数)
// const url = e.url.split('?')[0]
// const type = url.split('/')[1] || ''
// let data
// if (getAuthorization()) {
// data = JSON.parse(getAuthorization())
// } else {
// data = { userInfo: { is_real: 0 } }
// }
// // 判断当前窗口是白名单,如果是则不重定向路由
// if (type === 'agentpages' && !whiteList.includes(url) && !data.userInfo.is_real) {
// uni.showModal({
// title: '提示',
// content: '请先实名认证',
// showCancel: true,
// success({ confirm }) {
// if (confirm) {
// uni.navigateTo({
// url: '/pages/mine/authentication'
// })
// }
// }
// })
// return false
// }
return e
},
fail(err) {
// 失败回调拦截
console.log(err)
}
})
})
}

257
src/share.vue

@ -0,0 +1,257 @@
<script setup lang="ts">
import { shareApi } from '@/api/agent/promotion'
import { getHeaderImage } from '@/utils/common'
const { screenWidth } = uni.getSystemInfoSync()
const qrcodeRef = ref()
const userInfo = ref<{ head_img: string; id: number; phone: string; nickname: string }>({
head_img: '',
id: 0,
phone: '',
nickname: ''
})
const showSaveImgWin = ref(false)
const canvasToTempFilePath = ref('')
const oncomplete = ({ success }: { success: boolean }) => {
if (!success) return
qrcodeRef.value.toTempFilePath({
success: ({ tempFilePath }: { tempFilePath: string }) => {
createCanvas(tempFilePath)
}
})
}
const saveShareImg = () => {
uni.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath.value,
success: () => {
showSaveImgWin.value = false
uni.showToast({
title: '保存成功,快去分享到朋友圈吧~',
icon: 'none',
duration: 1000
})
},
fail: () => {
uni.showToast({
title: '保存失败,请检查是否授权保存图片权限~',
icon: 'none',
duration: 1000
})
}
})
}
const createCanvas = (tempFilePath: string) => {
uni.getImageInfo({
src: getHeaderImage(userInfo.value.head_img),
success: ({ path }) => {
const ctx = uni.createCanvasContext('shareCanvas', this)
const canvasWidth = screenWidth - 30
const canvasHeight = (screenWidth - 30) * 1.62
ctx.translate(0, 0)
ctx.beginPath()
ctx.drawImage('../../static/invite.jpg', 0, 0, canvasWidth, canvasHeight)
ctx.restore()
ctx.beginPath()
ctx.translate(0, canvasHeight - 78)
ctx.beginPath()
ctx.setFillStyle('rgba(0, 0, 0, 0.24)')
ctx.fillRect(0, 0, canvasWidth, 78)
ctx.save()
ctx.save()
ctx.beginPath()
ctx.arc(40, 40, 22, 0, 2 * Math.PI, false)
ctx.setFillStyle('rgba(0, 0, 0, 0.29)')
ctx.clip()
ctx.drawImage(path, 19, 19, 42, 42)
ctx.restore()
ctx.font = 'normal normal 13px sans-serif'
ctx.setFillStyle('#ffffff')
ctx.fillText('用户昵称', 71, 37)
ctx.font = 'normal normal 12px sans-serif'
ctx.setFillStyle('#ffffff')
ctx.fillText(`${userInfo.value.nickname}`, 71, 54)
ctx.save()
ctx.beginPath()
const x = canvasWidth - 78,
y = 7,
width = 64,
height = 64,
radius = 4
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5)
ctx.lineTo(x + width - radius, y)
ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2)
ctx.lineTo(x + width, y + height - radius)
ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5)
ctx.lineTo(x + radius, y + height)
ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI)
ctx.lineTo(x, y + radius)
ctx.setFillStyle('#ffffff')
ctx.fill()
ctx.closePath()
ctx.beginPath()
ctx.drawImage(tempFilePath, x + 3, y + 3, 58, 58)
ctx.restore()
ctx.draw(false, () => {
uni.canvasToTempFilePath({
width: canvasWidth,
height: canvasHeight,
destWidth: canvasWidth * 2,
destHeight: canvasHeight * 2,
canvasId: 'shareCanvas',
quality: 1,
success: ({ tempFilePath }) => {
canvasToTempFilePath.value = tempFilePath
},
complete: () => {
uni.hideLoading()
setTimeout(() => uni.hideToast(), 1000)
}
})
})
}
})
}
// const shareToFriends = () => {
// plus.share.sendWithSystem(
// { content: '', href: 'https://www.dcloud.io/' },
// function () {
// console.log('')
// },
// function (e) {
// console.log('' + JSON.stringify(e))
// }
// )
// }
onLoad(() => {
uni.showLoading({ title: '加载中...' })
shareApi().then((res) => {
const { data } = res as { data: { head_img: string; id: number; phone: string; nickname: string } }
userInfo.value = data
qrcodeRef.value.make()
})
})
</script>
<template>
<view class="invite">
<ex-header title="邀请好友" />
<uv-qrcode
ref="qrcodeRef"
class="qrcode"
size="174rpx"
:value="`http://pos-admin.lingji.vip/#/sign?p=${userInfo.phone}&t=a`"
@complete="oncomplete"
/>
<canvas canvas-id="shareCanvas" :style="{ width: `${screenWidth - 30}px`, height: `${(screenWidth - 30) * 1.62}px` }" hidpi />
<image @longpress="showSaveImgWin = true" style="width: 100%; height: 100%" :src="canvasToTempFilePath" />
<view class="option-box flex-center-between">
<view class="btn" @click="showSaveImgWin = true">保存图片</view>
<!-- <view class="option-box-item">
<view class="img-box">
<image src="http://cdn-pos.lingji.vip/static/share/circle_of_friends.png" mode="widthFix" />
</view>
<view class="label">朋友圈</view>
</view>
<view class="option-box-item" @click="shareToFriends()">
<view class="img-box">
<image src="http://cdn-pos.lingji.vip/static/share/wechart.png" mode="widthFix" />
</view>
<view class="label">微信</view>
</view>
<view class="option-box-item" @click="downloadFn()">
<view class="img-box">
<image src="http://cdn-pos.lingji.vip/static/share/download.png" mode="widthFix" />
</view>
<view class="label">保存</view>
</view> -->
</view>
<u-modal
:show="showSaveImgWin"
show-cancel-button
close-on-click-overlay
@cancel="showSaveImgWin = false"
@close="showSaveImgWin = false"
@confirm="saveShareImg"
>
确定要保存图片吗
</u-modal>
</view>
</template>
<style lang="scss" scoped>
.invite {
width: 100vw;
min-height: 100vh;
padding: 30rpx;
.qrcode {
position: absolute;
top: -100%;
}
.option-box {
padding: 46rpx 37rpx 102rpx;
&-item {
.img-box {
@extend .flex;
width: 104rpx;
height: 104rpx;
overflow: hidden;
border-radius: 50%;
margin-bottom: 10rpx;
background-color: #f2f6ff;
image {
width: 66rpx;
}
}
.label {
width: 100%;
color: #000;
font-size: 28rpx;
font-weight: 400;
letter-spacing: 0;
line-height: 40rpx;
text-align: center;
}
}
.btn {
width: 100%;
color: #ffffff;
font-size: 32rpx;
font-weight: 500;
letter-spacing: 0;
line-height: 40rpx;
text-align: center;
padding: 20rpx 0;
border-radius: 8rpx;
background: linear-gradient(90deg, #4778ff 0%, #4778ffb8 100%);
}
}
}
</style>

BIN
src/static/bg1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
src/static/card.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
src/static/invite.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
src/static/logo-bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 KiB

BIN
src/static/logo-tran.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
src/static/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
src/static/password.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

BIN
src/static/redpacket.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/static/tabbar/grxx-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

BIN
src/static/tabbar/grxx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

BIN
src/static/tabbar/tpxj-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

BIN
src/static/tabbar/tpxj.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

BIN
src/static/tabbar/wdxj-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

BIN
src/static/tabbar/wdxj.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

BIN
src/static/tabbar/xjjg-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

BIN
src/static/tabbar/xjjg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
src/static/tabbar/xjlb-select.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

BIN
src/static/tabbar/xjlb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

BIN
src/static/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

10
src/store/index.ts

@ -0,0 +1,10 @@
import { createPinia } from 'pinia'
import { createUnistorage } from 'pinia-plugin-unistorage'
const store = createPinia()
// 使用该插件
// 关键代码 👇
store.use(createUnistorage())
//导出
export default store

92
src/store/user.ts

@ -0,0 +1,92 @@
// 定义组合式API仓库
import { defineStore } from 'pinia'
import { getPrefixName } from '@/config'
interface userInfoStoreInt {
[n: string]: any
}
export default defineStore(
getPrefixName('user'),
() => {
const token = ref('')
const isShowPrize = ref(false)
const useType = ref<useType>('client')
const userInfo = ref<userInfoStoreInt>({})
const accountInfo = reactive({
can_switch: false,
client: {
account: '',
password: ''
},
agent: {
account: '',
password: ''
}
})
const bankCard = ref<userInfoStoreInt>({})
const checkLogin = computed(() => token.value !== '')
function logOut() {
token.value = ''
if (useType.value === 'client') {
accountInfo.client = { account: '', password: '' }
} else {
accountInfo.agent = { account: '', password: '' }
}
useType.value = 'client'
userInfo.value = {}
bankCard.value = {}
uni.clearStorageSync()
uni.showToast({
icon: 'none',
title: '退出成功',
mask: true,
success() {
setTimeout(() => uni.reLaunch({ url: 'pages/login/login' }), 1000)
}
})
}
return {
token,
accountInfo,
isShowPrize,
useType,
userInfo,
bankCard,
checkLogin,
logOut
}
},
{
unistorage: {
serializer: {
// 序列化,默认为 JSON.stringify
serialize(v) {
return JSON.stringify(v)
},
// 反序列化,默认为 JSON.parse
deserialize(v) {
return JSON.parse(v)
}
}
} // 开启后对 state 的数据读写都将持久化
// unistorage: {
// key: 'userInfo', // 缓存的键,默认为该 store 的 id,这里是 main,
// paths: ['userInfo.token'], // 需要缓存的路径,这里设置 foo 和 nested 下的 data 会被缓存
// // 初始化恢复前触发
// beforeRestore(ctx: any) {
// console.log(ctx)
// },
// // 初始化恢复后触发
// afterRestore(ctx: any) {
// console.log('ctx', ctx)
// },
// },
}
)

180
src/types/auto-imports.d.ts

@ -0,0 +1,180 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onError: typeof import('@dcloudio/uni-app')['onError']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onHide: typeof import('@dcloudio/uni-app')['onHide']
const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
const onMounted: typeof import('vue')['onMounted']
const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
const onReady: typeof import('@dcloudio/uni-app')['onReady']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onResize: typeof import('@dcloudio/uni-app')['onResize']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
const onShow: typeof import('@dcloudio/uni-app')['onShow']
const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useModel: typeof import('vue')['useModel']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useId: UnwrapRef<typeof import('vue')['useId']>
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}

13
src/types/env.d.ts

@ -0,0 +1,13 @@
/// <reference types="vite/client" />
/// <reference types="@dcloudio/types/index" />
/// <reference types="uview-plus/types/index" />
/// <reference types="feng-uniapp-exploit/types/index" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'uview-plus'
declare module 'feng-uniapp-exploit'

29
src/types/global.d.ts

@ -0,0 +1,29 @@
/* eslint-disable prettier/prettier */
declare global {
type useType = 'client' | 'agent'
/**
*
* @author shenname <shenname@163.com>
* @license MIT
* @param { string } fileName
* @param { string } filePath
* @example
* fileName: images.png
* filePath: images
*/
interface imgUrlInt {
fileName: string
filePath?: string
}
interface pageType {
page: number
limit: number
total?: number
}
}
export { }

119
src/uni.scss

@ -0,0 +1,119 @@
@import 'uview-plus/theme.scss';
// @use 'uview-plus/theme.scss';
#app {
box-sizing: border-box;
background-color: #f5f5f5
}
/* 颜色变量 */
/*============================= 文字尺寸 =============================*/
$font-size-20: 20rpx;
$font-size-22: 22rpx;
$font-size-24: 24rpx;
$font-size-26: 26rpx;
$font-size-28: 28rpx;
$font-size-30: 30rpx;
$font-size-32: 32rpx;
$font-size-34: 34rpx;
$font-size-36: 36rpx;
$font-size-40: 40rpx;
image {
width: 100%;
height: 100%;
box-sizing: border-box;
}
view {
box-sizing: border-box;
}
/*============================= 弹性盒子 =============================*/
%flex-base {
display: flex;
flex-wrap: nowrap;
}
$flex-way: (
start,
flex-start),
(center, center),
(end, flex-end),
(between, space-between),
(around, space-around),
(evenly, space-evenly
);
@mixin flex-algin($align) {
@each $way, $justify in $flex-way {
&-#{$way} {
@if $way !=$align {
@if $way !=$align {
@extend %flex-base;
align-items: $align;
justify-content: $justify;
}
}
}
}
}
.flex {
@extend %flex-base;
align-items: center;
justify-content: center;
@each $way, $justify in (start, flex-start), (center, center), (end, flex-end) {
&-#{$way} {
@include flex-algin($justify);
}
}
}
[class*="flex-"],
[class^="flex"] {
&.nowrap {
flex-wrap: nowrap;
}
&.stretch {
align-items: stretch;
}
@each $direction, $name in (row, direction), (column, direction), (wrap, wrap) {
&.#{$direction} {
flex-#{$name}: $direction;
&-reverse {
flex-#{$name}: '#{$direction}-reverse';
}
}
}
}
@for $i from 1 through 4 {
.flex#{$i} {
flex: $i;
}
}
/*============================= 文字溢出 =============================*/
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@for $i from 2 through 4 {
&-#{$i} {
overflow: hidden;
white-space: normal;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
line-clamp: 1;
-webkit-line-clamp: $i;
}
}
}

45
src/utils/common.ts

@ -0,0 +1,45 @@
/**
*
* @author shenname <shenname@163.com>
* @license MIT
* @param { string } url
* @returns { string }
*/
export const getHeaderImage = (url?: string) => {
if (url) {
return import.meta.env.VITE_APP_BASE_URL + url
} else {
return 'http://cdn-pos.lingji.vip/static/system/headimg.png'
}
}
/**
*
* @author shenname <shenname@163.com>
* @license MIT
* @param { string } url
* @returns { string }
*/
export const getImageUrl = (url?: string) => {
return import.meta.env.VITE_APP_BASE_URL + url
}
/**
* URL
* @author shenname <shenname@163.com>
* @license MIT
* @param { string } fileName |
* @param { string } [filePath=images] filePath /static URL时不传
* @example
* imgUrl('a.png')
* imgUrl('a.png', '/public/')
* imgUrl('/upload/images/img.png')
* @returns {string}
*/
export const fileUrl = (fileName: string, filePath: string = 'images'): string => {
if (/[ `!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/.test(fileName)) {
return import.meta.env.VITE_APP_BASE_URL + fileName
} else {
return `http://cdn-pos.lingji.vip/static/${filePath}/${fileName}`
}
}

69
src/utils/http.ts

@ -0,0 +1,69 @@
import useUserStore from '../store/user'
interface ResponseOptions {
url: string
headers?: { [key: string]: string }
method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'
data?: { [key: string]: any }
isSinglePost?: boolean
}
const userStore = useUserStore()
export const request = {
isLock: false,
http({ url = '', headers = {}, data = {}, method = 'POST', isSinglePost = false }: ResponseOptions) {
const _this = this
return new Promise(function (resolve, reject) {
if (isSinglePost && _this.isLock) {
reject({ message: '加载中' })
}
_this.isLock = true
// #ifdef APP-PLUS
url = import.meta.env.VITE_APP_BASE_URL + url
// #endif
// #ifdef H5
url = import.meta.env.VITE_APP_BASE_PRE + url
// #endif
const header = Object.assign({ 'content-type': 'application/json', Authorization: '' }, headers)
if (userStore.token) {
header['Authorization'] = 'Bearer' + userStore.token
}
uni.request({
url,
header,
method,
data,
success(res) {
const data = res.data as { code: number; data: object; msg: string }
switch (data.code) {
case 1005:
uni.showToast({ title: '登录状态已失效,请重新登录!', icon: 'none' })
setTimeout(() => uni.navigateTo({ url: 'pages/login/login' }), 1000)
break
case 200:
resolve(res.data)
break
default:
uni.showToast({ title: data.msg, icon: 'none', mask: true })
reject(res.data)
break
}
},
fail(err) {
uni.showToast({ title: '网络错误', icon: 'none' })
reject(err)
},
complete() {
_this.controller = null
_this.isLock = false
}
})
})
}
}

6
src/utils/rules.ts

@ -0,0 +1,6 @@
// 验证密码
export function checkPassword(password: string) {
if (password.trim() === '') return false
return /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,16}$/.test(password)
}

29
tsconfig.json

@ -0,0 +1,29 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
"lib": [
"esnext",
"dom"
],
"types": [
"vite/client",
"@dcloudio/types",
],
"typeRoots": [
"src/**/*.d.ts"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/types/*.d.ts"
]
}

45
vite.config.ts

@ -0,0 +1,45 @@
import { defineConfig, loadEnv } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import AutoImport from 'unplugin-auto-import/vite'
import path from 'path'
const pathSrc = path.resolve(__dirname, 'src')
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const { VITE_APP_BASE_URL, VITE_APP_BASE_PRE } = loadEnv(mode, process.cwd())
return {
transpileDependencies: ['uview-plus'],
resolve: {
// 别名
alias: {
'@': path.join(__dirname, './src')
}
},
plugins: [
uni(),
AutoImport({
// imports: ['vue', 'uni-app', { 'feng-uniapp-exploit': ['default'] }, { 'src/utils': ['default'] }],
imports: ['vue', 'uni-app'],
vueTemplate: true,
dts: path.resolve(pathSrc, 'types', 'auto-imports.d.ts')
})
],
// build: {
// rollupOptions: {
// external: ['feng-exploit-plus']
// }
// },
server: {
proxy: {
[VITE_APP_BASE_PRE]: {
target: VITE_APP_BASE_URL,
ws: true,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp('^' + VITE_APP_BASE_PRE), '')
}
}
}
}
})
Loading…
Cancel
Save