├── .gitignore
├── README.md
├── index.html
├── package.json
├── src
├── App.vue
├── api
│ ├── README.md
│ └── common.ts
├── components
│ ├── BookItem.vue
│ ├── BookTip.vue
│ ├── Form
│ │ ├── README.md
│ │ ├── TheForm.vue
│ │ ├── TheFormItem.vue
│ │ └── hooks.ts
│ ├── Icons.vue
│ ├── LoadMoreTip
│ │ ├── README.md
│ │ └── index.vue
│ ├── Picker
│ │ ├── Date.vue
│ │ ├── README.md
│ │ ├── index.vue
│ │ └── picker.scss
│ ├── README.md
│ ├── TheButton.vue
│ ├── TheFooter.vue
│ └── Upload
│ │ ├── Image.vue
│ │ └── README.md
├── env.d.ts
├── hooks
│ ├── README.md
│ ├── book.ts
│ ├── index.ts
│ └── loadMore.ts
├── main.ts
├── manifest.json
├── pages.json
├── pages
│ ├── README.md
│ ├── book.vue
│ ├── button.vue
│ ├── load-more-list.vue
│ └── tabBar
│ │ ├── home.vue
│ │ └── personal.vue
├── static
│ ├── README.md
│ ├── arrow-right.png
│ ├── default_head.png
│ ├── home_off.png
│ ├── home_on.png
│ ├── logo.png
│ ├── logo_wx.png
│ ├── logo_zfb.png
│ ├── none_data.png
│ ├── personal_off.png
│ └── personal_on.png
├── store
│ ├── AppOption.ts
│ ├── README.md
│ ├── User.ts
│ └── index.ts
├── styles
│ ├── README.md
│ ├── index.scss
│ └── loading.scss
├── type.d.ts
├── types
│ ├── README.md
│ ├── index.ts
│ └── user.ts
├── uni.scss
└── utils
│ ├── Config.ts
│ ├── Control.ts
│ ├── README.md
│ ├── icon.ts
│ ├── index.ts
│ └── request.ts
├── tsconfig.json
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | unpackage/
4 | dist/
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .project
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw*
24 |
25 | package-lock.json
26 | yarn.lock
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 基于 vue 实现的小说 App 阅读器
2 |
3 | > 由于之前 vue2 中 uni-app 某个版本依赖不再维护,所以当前项目更新为 vue3 版本
4 |
5 | [预览地址](https://huangjingsheng.gitee.io/hjs/reader-vue)
6 |
7 | [掘金介绍](https://juejin.cn/post/6844904127898583048)
8 |
9 | [模板引用](https://github.com/Hansen-hjs/uni-app-template)
10 |
11 | ## 初始化项目
12 | ```
13 | npm install
14 | ```
15 |
16 | ### 运行项目,APP 中连接手机调试需要借助 HbuildX
17 | ```
18 | npm run dev
19 | ```
20 |
21 | ### 打包项目
22 | ```
23 | npm run build
24 | ```
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uni-app-vue3",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev:app": "uni -p app",
6 | "dev:custom": "uni -p",
7 | "dev": "uni",
8 | "dev:h5:ssr": "uni --ssr",
9 | "dev:mp-alipay": "uni -p mp-alipay",
10 | "dev:mp-baidu": "uni -p mp-baidu",
11 | "dev:mp-kuaishou": "uni -p mp-kuaishou",
12 | "dev:mp-lark": "uni -p mp-lark",
13 | "dev:mp-qq": "uni -p mp-qq",
14 | "dev:mp-toutiao": "uni -p mp-toutiao",
15 | "dev:mp-weixin": "uni -p mp-weixin",
16 | "dev:quickapp-webview": "uni -p quickapp-webview",
17 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
18 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
19 | "build:app": "uni build -p app",
20 | "build:custom": "uni build -p",
21 | "build": "uni build",
22 | "build:h5:ssr": "uni build --ssr",
23 | "build:mp-alipay": "uni build -p mp-alipay",
24 | "build:mp-baidu": "uni build -p mp-baidu",
25 | "build:mp-kuaishou": "uni build -p mp-kuaishou",
26 | "build:mp-lark": "uni build -p mp-lark",
27 | "build:mp-qq": "uni build -p mp-qq",
28 | "build:mp-toutiao": "uni build -p mp-toutiao",
29 | "build:mp-weixin": "uni build -p mp-weixin",
30 | "build:quickapp-webview": "uni build -p quickapp-webview",
31 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
32 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union"
33 | },
34 | "dependencies": {
35 | "@dcloudio/uni-app": "3.0.0-alpha-3061020221121002",
36 | "@dcloudio/uni-app-plus": "3.0.0-alpha-3061020221121002",
37 | "@dcloudio/uni-components": "3.0.0-alpha-3061020221121002",
38 | "@dcloudio/uni-h5": "3.0.0-alpha-3061020221121002",
39 | "@dcloudio/uni-mp-alipay": "3.0.0-alpha-3061020221121002",
40 | "@dcloudio/uni-mp-baidu": "3.0.0-alpha-3061020221121002",
41 | "@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3061020221121002",
42 | "@dcloudio/uni-mp-lark": "3.0.0-alpha-3061020221121002",
43 | "@dcloudio/uni-mp-qq": "3.0.0-alpha-3061020221121002",
44 | "@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3061020221121002",
45 | "@dcloudio/uni-mp-weixin": "3.0.0-alpha-3061020221121002",
46 | "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3061020221121002",
47 | "vue": "3.2.41"
48 | },
49 | "devDependencies": {
50 | "@dcloudio/types": "3.0.16",
51 | "@dcloudio/uni-automator": "3.0.0-alpha-3061020221121002",
52 | "@dcloudio/uni-cli-shared": "3.0.0-alpha-3061020221121002",
53 | "@dcloudio/vite-plugin-uni": "3.0.0-alpha-3061020221121002",
54 | "@types/node": "17.0.40",
55 | "sass": "1.52.2",
56 | "typescript": "4.8.3",
57 | "vite": "3.1.8"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
23 |
32 |
--------------------------------------------------------------------------------
/src/api/README.md:
--------------------------------------------------------------------------------
1 | # 接口模块
2 |
3 | ## 正常 GET 和 POST 请求
4 |
5 | ```ts
6 | /**
7 | * 通过`id`获取用户信息
8 | * @param id
9 | */
10 | export function getUserInfo(id: number | string) {
11 | return request('GET', '/getUserInfoById', { id })
12 | }
13 |
14 | /**
15 | * 普通`post`json请求
16 | * @param params
17 | */
18 | export function saveBannerInfo(params: { img: string, date: string, sort: number }) {
19 | return request('POST', '/saveBannerInfo', params)
20 | }
21 | ```
22 |
23 | ## 表单请求
24 |
25 | ```ts
26 | import { jsonToFormData } from '@/utils';
27 |
28 | /**
29 | * `post`表单请求
30 | * @param params
31 | */
32 | export function saveUserInfo(params: { account: string, password: string }) {
33 | return request('POST', '/saveUserInfo', jsonToFormData(params), {
34 | headers: {
35 | 'codeMode': 'form'
36 | }
37 | })
38 | }
39 | ```
40 |
41 | ## 请求获取文件流
42 |
43 | ```ts
44 | /**
45 | * 响应结果类型为`blob`
46 | * @param params
47 | */
48 | export function getBlob(params: { name: string }) {
49 | return request('GET', '/getXlsxBlob', params, {
50 | responseType: 'blob'
51 | })
52 | }
53 | ```
--------------------------------------------------------------------------------
/src/api/common.ts:
--------------------------------------------------------------------------------
1 | import { createBookListData } from '@/hooks/book';
2 | import { randomText, ranInt } from '@/utils';
3 | import request from '@/utils/request';
4 |
5 | // ============================= 常用接口模块 =============================
6 |
7 | /**
8 | * 用户登录
9 | * @param form
10 | */
11 | export function login(form: { account: string | number, password: string | number }) {
12 | return request("POST", "/login", form);
13 | }
14 |
15 | /**
16 | * 查询用户类型
17 | * @param value 用户标识
18 | */
19 | export function searchUserType(value: "admin" | "vip" | "normal") {
20 | return request("POST", "/user/searchType", { type: value });
21 | }
22 |
23 | const images = [
24 | "https://muse-ui.org/img/img1.35d144b4.png",
25 | "https://muse-ui.org/img/img2.9bd96df4.png",
26 | "https://muse-ui.org/img/img3.6e264e66.png",
27 | "https://muse-ui.org/img/sun.a646a52d.jpg",
28 | "https://muse-ui.org/img/breakfast.f1098290.jpg"
29 | ]
30 |
31 | const testList = new Array(52).fill(0).map((_, index) => {
32 | return {
33 | id: index + 1,
34 | name: randomText(6, 30),
35 | img: images[ranInt(0, images.length - 1)]
36 | }
37 | })
38 |
39 | /**
40 | * 模拟请求数据
41 | * @param params
42 | */
43 | export function getTestList(params: PageInfo & { id?: number, keyword?: string }) {
44 | const delay = ranInt(200, 2000);
45 |
46 | const result: ApiResult = {
47 | code: -1,
48 | data: {
49 | currentPage: params.currentPage,
50 | pageSize: params.pageSize,
51 | list: [],
52 | total: testList.length
53 | },
54 | msg: ""
55 | }
56 |
57 | return new Promise>(function (resolve, reject) {
58 | setTimeout(function () {
59 | if (delay > 1500) {
60 | result.msg = "接口查询超时"
61 | resolve(result);
62 | } else {
63 | result.msg = "success";
64 | result.code = 1;
65 | const index = (params.currentPage - 1) * params.pageSize;
66 | let list = [...testList].splice(index, params.pageSize);
67 | if (params.keyword) {
68 | list = list.map(item => {
69 | return {
70 | id: item.id,
71 | name: `${params.keyword}:${item.name}`,
72 | img: item.img
73 | }
74 | });
75 | }
76 | result.data.list = list;
77 | resolve(result);
78 | }
79 | }, delay)
80 | })
81 | }
82 |
83 | /**
84 | * 获取小说列表
85 | * @param params
86 | */
87 | export function getBookList(params: PageInfo) {
88 | const delay = ranInt(200, 1000);
89 | const result: ApiResult = {
90 | code: 1,
91 | data: {
92 | currentPage: params.currentPage,
93 | pageSize: params.pageSize,
94 | list: []
95 | },
96 | msg: ""
97 | }
98 | return new Promise>(function (resolve, reject) {
99 | setTimeout(function () {
100 | if (delay > 900 && params.currentPage !== 0) {
101 | result.msg = "接口查询超时"
102 | result.code = 0;
103 | resolve(result);
104 | } else {
105 | const total = params.currentPage < 6 ? params.pageSize : ranInt(2, params.pageSize - 2);
106 | result.msg = "success"
107 | result.code = 1;
108 | result.data.list = createBookListData(params.currentPage * params.pageSize, total);
109 | resolve(result);
110 | }
111 | }, delay)
112 | })
113 | }
--------------------------------------------------------------------------------
/src/components/BookItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ bookData.index + 1 }}
7 |
8 | {{ bookData.name }}
9 | {{ bookData.info }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ bookData.name }}
19 | {{ bookData.score }} 分
20 |
21 | {{ bookData.info }}
22 | {{ bookData.label }}
23 |
24 |
25 |
26 |
27 |
28 |
29 | {{ bookData.name }}
30 | {{ bookData.score }} 分
31 |
32 |
33 |
34 |
35 | {{ bookData.index + 1 }}
36 |
37 |
38 | {{ bookData.name }}
39 | {{ bookData.label }}
40 | {{ bookData.value }}
41 |
42 |
43 |
44 |
45 |
46 |
74 |
--------------------------------------------------------------------------------
/src/components/BookTip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 点击切换到上一页
8 |
9 |
10 |
11 |
12 |
13 |
14 | 点击换出菜单
15 |
16 |
17 |
18 |
19 |
20 |
21 | 点击切换到下一页
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
--------------------------------------------------------------------------------
/src/components/Form/README.md:
--------------------------------------------------------------------------------
1 | # from 表单组件
2 |
3 | **demo:[预览地址](http://huangjingsheng.gitee.io/hjs/uni-app/#/pages/form)**
4 |
5 | 这里只实现两个核心组件``和``;因为移动端样式变化比较灵活,如果把所有表单组件都封装成`element-ui`或者`vant-ui`这类型一体库的话,导致很多无用代码和性能开销,所以这里只提供必需的功能组件,其他表单组件根据实际情况定义,保证高度灵活性。
6 |
7 | ## ``
8 |
9 | 参数说明:
10 |
11 | | props | 类型 | 是否必选 | 说明 |
12 | | --- | --- | --- | --- |
13 | | model | object | 是 | 表单绑定的值(表单数据) |
14 | | rules | object | 否 | 表单验证数据(和`element-ui`一致) |
15 | | labelWidth | string | 否 | 表单字段宽度,`px`、`rpx`、`%` |
16 | | labelPosition | string: `left`,`right`,`top` | 否 | 表单字段排版,默认`left` |
17 | | border | boolean | 否 | 是否需要显示底部边框,默认`false` |
18 | | validateScroll | boolean | 否 | 是否需要在验证时,滚动到不通过的位置,默认`true`,短表单时建议关闭,长表单开启 |
19 |
20 | 和`element-ui`差异:
21 | - `props.rules`移除了`validator`,增加了`reg`正则匹配:注意:因为微信小程序的一些特殊机制,导致传参类型会把非 `number|string|object` 的类型过滤掉,所以这里在写正则的时候,在末尾加上`.toString()`即可;
22 | - `props.rules`移除了`change`触发条件,组件内部做了智能触发机制;
23 |
24 |
25 | 事件/方法说明:
26 |
27 | | 方法 | 参数 | 参数说明 | 说明 |
28 | | --- | --- | --- | --- |
29 | | validate(callback(...)) | callback(isValid, rules) | 有两个回调参数,和`element-ui`一致 | 表单验证 |
30 | | validateField(prop, callback(...)) | `prop`是指定验证的键值,`callback`和上面一致 | 有两个回调参数,和`element-ui`一致 | 指定验证某个字段 |
31 | | resetFields() | - | - | 移除所有校验 |
32 | | resetField(prop) | `prop`是指定移除验证的键值 | - | 移除指定校验 |
33 |
34 | ## ``
35 |
36 | 参数说明:
37 |
38 | | props | 类型 | 是否必选 | 说明 |
39 | | --- | --- | --- | --- |
40 | | prop | string | 否 | 表单对象`key`字段 |
41 | | rules | array | 否 | 这里是上面`rules`对象里面的数组,类型一致;优先级高于父组件 |
42 | | label | string | 否 | 表单展示字段 |
43 | | labelWidth | string | 否 | 表单字段宽度,`px`、`rpx`、`%`;优先级高于父组件 |
44 | | labelPosition | string: `left`,`right`,`top` | 否 | 表单字段排版,默认`left`;优先级高于父组件 |
45 | | border | boolean | 否 | 是否需要显示底部边框,默认`false`;优先级高于父组件 |
46 |
47 | ## 使用示例
48 |
49 | ```html
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
155 | ```
156 |
--------------------------------------------------------------------------------
/src/components/Form/TheForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
237 |
--------------------------------------------------------------------------------
/src/components/Form/TheFormItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | *
6 | {{ label }}
7 |
8 |
9 |
10 |
11 | {{ validateText }}
12 |
13 |
14 |
15 |
16 |
17 |
23 |
211 |
--------------------------------------------------------------------------------
/src/components/Form/hooks.ts:
--------------------------------------------------------------------------------
1 | import { PropType } from "vue";
2 | import { LabelPosition } from "@/types";
3 |
4 | /**
5 | * 表单相同`props`
6 | * @param isItem 是否`item`使用
7 | */
8 | export function useFormProps(isItem?: boolean) {
9 | return {
10 | /** 表单字段宽度,这里使用字符串,因为可能是`px`或者`rpx` */
11 | labelWidth: {
12 | type: String,
13 | default: ""
14 | },
15 | /** 表单字段排版 */
16 | labelPosition: {
17 | type: String as PropType,
18 | default: isItem ? "" : "left"
19 | },
20 | /** 是否需要显示底部边框 */
21 | border: {
22 | type: [Boolean, String],
23 | default: isItem ? "-" : false, // 微信小程序抽风会把空字符串转成 boolean 所以这里随便给个字符串
24 | },
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Icons.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ icons[type] }}
3 |
4 |
18 |
45 |
46 |
--------------------------------------------------------------------------------
/src/components/LoadMoreTip/README.md:
--------------------------------------------------------------------------------
1 | # 触底加载更多组件
2 |
3 | 使用示例
4 |
5 | ```html
6 |
7 |
8 | 列表-item
9 |
10 |
11 |
12 |
53 | ```
54 |
--------------------------------------------------------------------------------
/src/components/LoadMoreTip/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 上拉加载更多
18 |
19 |
20 |
21 |
22 | {{ noneDataText }}
23 |
24 |
25 |
26 | {{ finishText }}
27 |
28 |
29 |
30 |
31 |
37 |
66 |
--------------------------------------------------------------------------------
/src/components/Picker/Date.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 取消
8 | {{ title }}
9 | 确定
10 |
11 |
12 |
13 |
14 | {{ item }}
15 |
16 |
17 | {{ item }}
18 |
19 |
20 | {{ item }}
21 |
22 |
23 |
24 |
25 |
26 |
59 |
217 |
220 |
--------------------------------------------------------------------------------
/src/components/Picker/README.md:
--------------------------------------------------------------------------------
1 | # 弹出层选择器
2 |
3 | 参数说明:
4 |
5 | | props | 类型 | 是否必选 | 说明 |
6 | | --- | --- | --- | --- |
7 | | show | boolean | 是 | 是否显示弹出选择器 |
8 | | title | string,number | 否 | 选择器标题 |
9 | | list | `Array` | 是 | 选择器数据,最多显示三层 |
10 | | column | 列数:0-3 | 否 | 指定选择器列数(优先级最高),默认是`0`,即自动根据`list`的层级动态生成 |
11 | | pickerId | string,number | 否 | `change`事件携带的`id`,一个页面有多个组件的时候用来区分用 |
12 |
13 | 事件说明:
14 |
15 | | 方法件名 | 说明 |
16 | | --- | --- |
17 | | `setIndexs(indexs: Array)` | 设置当前选择器选中的位置,传入的参数则为每一列的索引位置 |
18 |
19 | `list`注意事项说明:小程序端无法多列动态自适应,需要使用`column`指定列数;原因是组件用了`v-show`在小程序端有`bug`,改用`v-if`可以实现动态层级,不过会有些展示上的行为小瑕疵,可以自行修改测试看具体效果就知道了,这里推荐使用`v-show`+指定列数`column`在小程序端的使用。
20 |
21 | `PickerSelectItem`说明:
22 |
23 | ```ts
24 | /** 选择器`item`数据 */
25 | interface PickerSelectItem {
26 | /** 展示字段 */
27 | label: string
28 | /** 对应的值 */
29 | value: T
30 | /**
31 | * 下级数据
32 | * @description 最多三层,选择器栏目数根据当前下级动态显示
33 | */
34 | children?: Array>
35 | /** 其他携带的值 */
36 | [key: string]: any
37 | }
38 | ```
39 |
40 | **使用示例**
41 |
42 | ```html
43 |
44 |
45 | {{ selectLabel || "请选择" }}
46 |
47 |
48 |
49 |
50 |
51 |
87 | ```
88 |
89 | ## 日期选择组件
90 |
91 | | props | 类型 | 是否必选 | 说明 |
92 | | --- | --- | --- | --- |
93 | | show | boolean | 是 | 是否显示弹出选择器 |
94 | | title | string,number | 否 | 选择器标题 |
95 | | type | string: `Y-M-D`,`Y-M`,`Y` | 否,默认`Y-M-D` | 分别对应年月日 |
96 | | value | string | 否 | 初始化选中值,格式和`type`对应 |
97 | | startDate | string | 否 | 开始日期,格式和`type`对应 |
98 | | endDate | string | 否 | 结束日期,格式和`type`对应 |
99 |
100 | 细心看过`index.vue`的可以发现,其实可以二次封装一下日期数据就可以复用上面的组件,这里我单独抽出来的理由是:因为上面的组件需要依赖固定的上下级数据,如果日期选择两个年份差较大的话,会导致庞大的日期数据;另外日期的范围逻辑也跟其他的选项数据不太一样,所以这里单独抽出来性能会好很多。
101 |
102 | **使用示例**
103 |
104 | ```html
105 |
106 |
107 | {{ selectLabel || "请选择" }}
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
138 | ```
139 |
--------------------------------------------------------------------------------
/src/components/Picker/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 取消
8 | {{ title }}
9 | 确定
10 |
11 |
12 |
13 |
14 | {{ item.label }}
15 |
16 |
17 | {{ item.label }}
18 |
19 |
20 | {{ item.label }}
21 |
22 |
23 |
24 |
25 |
26 |
35 |
152 |
155 |
--------------------------------------------------------------------------------
/src/components/Picker/picker.scss:
--------------------------------------------------------------------------------
1 | @import "../../uni.scss";
2 |
3 | $duration: 0.3s all;
4 |
5 | .the-picker {
6 | width: 100%;
7 | height: 100%;
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | background-color: rgba(0, 0, 0, 0.45);
12 | z-index: 999;
13 | overflow: hidden;
14 | flex-direction: column;
15 | -webkit-flex-direction: column;
16 | transition: $duration;
17 | opacity: 0;
18 | visibility: hidden;
19 |
20 | .picker-option {
21 | width: 100%;
22 | padding: 12rpx 0;
23 |
24 | .picker-title {
25 | font-size: 32rpx;
26 | color: #555;
27 | font-weight: 500;
28 | text-align: center;
29 | line-height: 1;
30 | }
31 |
32 | .btn {
33 | font-size: 30rpx;
34 | width: 160rpx;
35 | line-height: 80rpx;
36 | color: #999;
37 | text-align: center;
38 | }
39 |
40 | .confirm {
41 | color: $blue;
42 | }
43 | }
44 |
45 | .picker-content {
46 | background-color: #fff;
47 | transition: $duration;
48 | transform: translate3d(0, 100%, 0);
49 |
50 | .picker-view {
51 | width: 100%;
52 | height: 236px;
53 |
54 | .picker-item {
55 | text-align: center;
56 | line-height: 36px;
57 | font-size: 16px;
58 | }
59 | }
60 | }
61 | }
62 |
63 | .the-picker-show {
64 | opacity: 1;
65 | visibility: visible;
66 |
67 | .picker-content {
68 | transform: translate3d(0, 0%, 0);
69 | }
70 | }
--------------------------------------------------------------------------------
/src/components/README.md:
--------------------------------------------------------------------------------
1 | # 通用组件目录
2 |
3 | 局部组件不要写在这里
4 |
--------------------------------------------------------------------------------
/src/components/TheButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
47 |
--------------------------------------------------------------------------------
/src/components/TheFooter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
33 |
--------------------------------------------------------------------------------
/src/components/Upload/Image.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
117 |
--------------------------------------------------------------------------------
/src/components/Upload/README.md:
--------------------------------------------------------------------------------
1 | # 上传组件
2 |
3 | ## 上传图片组件
4 |
5 | 使用示例
6 |
7 | ```html
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 | ```
28 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module "*.vue" {
4 | import { DefineComponent } from "vue"
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/src/hooks/README.md:
--------------------------------------------------------------------------------
1 | # 通用`hooks`目录
2 |
--------------------------------------------------------------------------------
/src/hooks/book.ts:
--------------------------------------------------------------------------------
1 | import { randomText, ranInt } from "@/utils";
2 |
3 | /** 书本id */
4 | let bookId = 0;
5 |
6 | /** 图片列表 */
7 | const images = [
8 | "https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eefa59a729d34f4fa470d9c53be410c8~tplv-k3u1fbpfcp-watermark.image",
9 | "https://img.yzcdn.cn/vant/ipad.jpeg"
10 | ];
11 |
12 | /**
13 | * 小说数据类型
14 | */
15 | export interface BookInfo {
16 | id: number
17 | index: number
18 | /** 封面图 */
19 | thumb: string
20 | /** 小说名 */
21 | name: string
22 | /** 描述 */
23 | info: string
24 | /** 评分 */
25 | score: string
26 | /** 标签 */
27 | label: string
28 | /** 小说热度 */
29 | value: string
30 | }
31 |
32 | /**
33 | * 创建单个小说数据
34 | */
35 | export function createBookInfo() {
36 | return {
37 | id: 0,
38 | index: 0,
39 | thumb: "",
40 | name: "",
41 | info: "",
42 | score: "",
43 | label: "",
44 | value: "",
45 | } as BookInfo
46 | }
47 |
48 | /**
49 | * 创建小说列表数据
50 | * @param index 索引开始
51 | * @param total 列表总数
52 | */
53 | export function createBookListData(index: number = 0, total: number = 8) {
54 | const list: Array = [];
55 | const titleStr = "小说名小说名小说名";
56 | const decStr = "小说描述小说描述小说描述小说描述";
57 | for (let i = index; i < index + total; i++) {
58 | bookId++;
59 | list.push({
60 | id: bookId,
61 | index: i,
62 | thumb: images[Math.floor(Math.random() * images.length)],
63 | name: `小说${bookId} ${titleStr.slice(Math.floor(Math.random() * titleStr.length))}`,
64 | info: `小说描述${decStr.slice(Math.floor(Math.random() * decStr.length))}`,
65 | score: `${(Math.floor(Math.random() * 100) / 10).toFixed(1)}`,
66 | label: "都市·穿越·完结·29万人在读",
67 | value: "785万热度",
68 | });
69 | }
70 | return list;
71 | }
72 |
73 | /** 段落分隔符 */
74 | export const paragraphSeparate = "
";
75 |
76 | /**
77 | * 创建阅读器内容信息(一个章节)
78 | * @param index
79 | */
80 | export function createBookContent(index: number) {
81 | function outputText() {
82 | const total = ranInt(1, 5);
83 | let text = "";
84 | for (let i = 0; i < total; i++) {
85 | text += randomText(2, 10);
86 | if (i == total - 1) {
87 | text += "。" + paragraphSeparate;
88 | } else {
89 | text += ",";
90 | }
91 | }
92 | return text;
93 | }
94 | function outputParagraph() {
95 | let paragraph = "";
96 | const total = ranInt(50, 100);
97 | for (let i = 0; i < total; i++) {
98 | paragraph += outputText();
99 | }
100 | return paragraph;
101 | }
102 |
103 | return {
104 | title: `第${index + 1}章节`,
105 | content: outputParagraph()
106 | };
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | import { PickerSelectItem } from "@/types";
2 |
3 | /** 城市数据 */
4 | export function useCityData(): Array {
5 | return [
6 | {
7 | label: "广东省",
8 | value: 0,
9 | children: [
10 | {
11 | label: "广州市",
12 | value: 0,
13 | children: [
14 | {
15 | label: "天河区",
16 | value: 0
17 | },
18 | {
19 | label: "番禺区",
20 | value: 1
21 | },
22 | {
23 | label: "白云区",
24 | value: 2
25 | }
26 | ]
27 | },
28 | {
29 | label: "深圳市",
30 | value: 1,
31 | children: [
32 | {
33 | label: "保安区",
34 | value: 0
35 | },
36 | {
37 | label: "龙华区",
38 | value: 1
39 | },
40 | {
41 | label: "福田区",
42 | value: 2
43 | }
44 | ]
45 | },
46 | {
47 | label: "其他",
48 | value: 2
49 | }
50 | ]
51 | },
52 | {
53 | label: "湖南省",
54 | value: 1,
55 | children: [
56 | {
57 | label: "长沙市",
58 | value: 0,
59 | children: [
60 | {
61 | label: "芙蓉区",
62 | value: 0
63 | },
64 | {
65 | label: "天心区",
66 | value: 1
67 | },
68 | {
69 | label: "岳麓区",
70 | value: 2
71 | }
72 | ]
73 | },
74 | {
75 | label: "衡阳市",
76 | value: 1,
77 | children: [
78 | {
79 | label: "衡阳县",
80 | value: 0
81 | },
82 | {
83 | label: "衡南县",
84 | value: 1
85 | },
86 | {
87 | label: "衡山县",
88 | value: 2
89 | }
90 | ]
91 | }
92 | ]
93 | },
94 | {
95 | label: "黑龙江省",
96 | value: 1,
97 | children: [
98 | {
99 | label: "哈尔滨市",
100 | value: 0,
101 | children: [
102 | {
103 | label: "道外区",
104 | value: 0
105 | },
106 | {
107 | label: "松北区",
108 | value: 1
109 | },
110 | {
111 | label: "呼兰区",
112 | value: 2
113 | }
114 | ]
115 | },
116 | {
117 | label: "沈阳市",
118 | value: 1,
119 | }
120 | ]
121 | },
122 | {
123 | label: '浙江省',
124 | value: 12,
125 | }
126 | ]
127 | }
--------------------------------------------------------------------------------
/src/hooks/loadMore.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { onReachBottom } from "@dcloudio/uni-app";
3 | import { showToast } from "@/utils/control";
4 | import { modifyData } from "@/utils";
5 |
6 | export interface LoadMoreInfo extends PageInfo {
7 | /** 加载状态 */
8 | state: "wait" | "loading" | "nomore"
9 | /** 加载的列表数据 */
10 | list: Array
11 | /**
12 | * 请求成功次数
13 | * - 成功时才会累加,`requestCount === 1` 时为首次获取数据
14 | */
15 | requestCount: number
16 | /**
17 | * 列表数据`key`值,因为每个后台返回的数组字段不一样,所以这里加一个动态设置的字段
18 | * @example
19 | * ```js
20 | * // 假设后台返回的数据是这样的:
21 | * const res = {
22 | * currentPage: 1,
23 | * records: [...],
24 | * pageSize: 10
25 | * ...more
26 | * }
27 | * // 那么 listDataKey = "records";
28 | * // 默认 listDataKey = "list";
29 | * ```
30 | */
31 | listDataKey: string
32 | }
33 |
34 | /** 加载更多数据对象 */
35 | export function useLoadMoreData(): LoadMoreInfo {
36 | return {
37 | state: "wait",
38 | list: [],
39 | currentPage: 1,
40 | pageSize: 10,
41 | requestCount: 0,
42 | listDataKey: "list"
43 | }
44 | }
45 |
46 | /**
47 | * 加载更多-功能函数
48 | */
49 | export default function useLoadMore() {
50 |
51 | /** 加载更多数据 */
52 | const loadMoreData = reactive(useLoadMoreData());
53 |
54 | /** 请求函数 */
55 | let requestListFn: () => Promise>;
56 |
57 | /**
58 | * 设置请求函数
59 | * @param fn
60 | */
61 | function setRequestListFn(fn: () => Promise>) {
62 | requestListFn = fn;
63 | }
64 |
65 | /**
66 | * 开始请求获取列表数据
67 | * @param callback 加载结束回调
68 | */
69 | function getData(callback?: () => void) {
70 | if (!requestListFn) return console.log("%c 请调用“setRequestListFn”方法设置请求函数 >>", "color: #4fc08d");
71 | const { state, list } = loadMoreData;
72 | if (state === "nomore" || state === "loading") return;
73 | loadMoreData.state = "loading";
74 | requestListFn().then(result => {
75 | loadMoreData.state = "wait";
76 | if (result.code === 1) {
77 | const listData = result.data[loadMoreData.listDataKey as "list"];
78 | loadMoreData.requestCount++;
79 | loadMoreData.list = list.concat(listData);
80 | // 判断是否没有数据了
81 | // result.data.totalCount >= loadMoreData.list.length
82 | if (listData.length < loadMoreData.pageSize) {
83 | loadMoreData.state = "nomore";
84 | } else {
85 | loadMoreData.currentPage++;
86 | }
87 | } else {
88 | showToast(result.msg || "加载列表出错");
89 | }
90 | callback && callback();
91 | }).catch(error => {
92 | console.log("%c getListData error >>", "color: #f04e7d", error);
93 | loadMoreData.state = "wait";
94 | callback && callback();
95 | })
96 | }
97 |
98 | /**
99 | * 刷新数据
100 | * @param callback
101 | */
102 | function refreshData(callback?: () => void) {
103 | loadMoreData.currentPage = 1;
104 | loadMoreData.list = [];
105 | loadMoreData.state = "wait";
106 | getData(callback);
107 | }
108 |
109 | /** 重置列表数据 */
110 | function resetListData() {
111 | modifyData(loadMoreData, useLoadMoreData());
112 | }
113 |
114 | onReachBottom(function () {
115 | getData();
116 | })
117 |
118 | return {
119 | loadMoreData,
120 | refreshData,
121 | resetListData,
122 | setRequestListFn
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createSSRApp } from "vue";
2 | import App from "./App.vue";
3 |
4 | export function createApp() {
5 | const app = createSSRApp(App);
6 | return {
7 | app,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-uni-app-template",
3 | "appid": "",
4 | "description": "",
5 | "versionName": "1.0.0",
6 | "versionCode": "100",
7 | "transformPx": false,
8 | "app-plus": {
9 | "usingComponents": true,
10 | "nvueStyleCompiler": "uni-app",
11 | "compilerVersion": 3,
12 | "splashscreen": {
13 | "alwaysShowBeforeRender": true,
14 | "waiting": true,
15 | "autoclose": true,
16 | "delay": 0
17 | },
18 | "modules": {},
19 | "distribute": {
20 | "android": {
21 | "permissions": [
22 | "",
23 | "",
24 | "",
25 | "",
26 | "",
27 | "",
28 | "",
29 | "",
30 | "",
31 | "",
32 | "",
33 | "",
34 | "",
35 | "",
36 | ""
37 | ]
38 | },
39 | "ios": {},
40 | "sdkConfigs": {}
41 | }
42 | },
43 | "quickapp": {},
44 | "mp-weixin": {
45 | "appid": "",
46 | "setting": {
47 | "urlCheck": false
48 | },
49 | "usingComponents": true
50 | },
51 | "mp-alipay": {
52 | "usingComponents": true
53 | },
54 | "mp-baidu": {
55 | "usingComponents": true
56 | },
57 | "mp-toutiao": {
58 | "usingComponents": true
59 | },
60 | "uniStatistics": {
61 | "enable": false
62 | },
63 | "vueVersion": "3"
64 | }
--------------------------------------------------------------------------------
/src/pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | {
4 | "path": "pages/tabBar/home",
5 | "style": {
6 | "navigationBarTitleText": "首页",
7 | "enablePullDownRefresh": true
8 | }
9 | },
10 | {
11 | "path": "pages/tabBar/personal",
12 | "style": {
13 | "navigationBarTitleText": "个人页"
14 | }
15 | },
16 | {
17 | "path": "pages/load-more-list",
18 | "style": {
19 | "navigationBarTitleText": "加载更多列表",
20 | "navigationBarBackgroundColor": "#222222",
21 | "navigationBarTextStyle": "white",
22 | "enablePullDownRefresh": true
23 | }
24 | },
25 | {
26 | "path": "pages/book",
27 | "style": {
28 | "navigationBarTitleText": "小说阅读器",
29 | "navigationStyle": "custom"
30 | }
31 | }
32 | ],
33 | "globalStyle": {
34 | "navigationBarTextStyle": "black",
35 | "navigationBarTitleText": "uni-app",
36 | "navigationBarBackgroundColor": "#F8F8F8",
37 | "backgroundColor": "#F8F8F8"
38 | },
39 | "tabBar": {
40 | "color": "#7A7E83",
41 | "selectedColor": "#000000",
42 | "borderStyle": "white",
43 | "backgroundColor": "#f4f4f4",
44 | "list": [
45 | {
46 | "pagePath": "pages/tabBar/home",
47 | "iconPath": "static/home_off.png",
48 | "selectedIconPath": "static/home_on.png",
49 | "text": "首页"
50 | },
51 | {
52 | "pagePath": "pages/tabBar/personal",
53 | "iconPath": "static/personal_off.png",
54 | "selectedIconPath": "static/personal_on.png",
55 | "text": "我的"
56 | }
57 | ]
58 | }
59 | }
--------------------------------------------------------------------------------
/src/pages/README.md:
--------------------------------------------------------------------------------
1 | # 页面目录
2 |
--------------------------------------------------------------------------------
/src/pages/book.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
31 |
32 |
33 |
34 |
35 |
42 | {{ `小说ID: ${bookId}` }}
43 |
44 | 300币
45 | $
46 |
47 |
48 |
49 |
50 | {{ pageTextList[index].title }}
58 |
59 |
60 | {{ p }}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
91 |
92 |
93 |
94 |
95 |
96 |
100 |
111 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
692 |
--------------------------------------------------------------------------------
/src/pages/button.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 加载 2 秒
4 |
5 | 圆角按钮 加载 3 秒
13 |
14 | 自定义字体颜色 加载 4 秒
15 |
16 |
17 |
30 |
--------------------------------------------------------------------------------
/src/pages/load-more-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ item.name }}
5 |
6 | ID: {{ item.id }}
7 |
8 |
9 |
10 |
11 |
12 |
53 |
--------------------------------------------------------------------------------
/src/pages/tabBar/home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
37 |
--------------------------------------------------------------------------------
/src/pages/tabBar/personal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | userInfo: {{ JSON.stringify(userInfo, null, 4) }}
4 |
5 | 环境变量:{{ config.env }}
6 |
7 |
8 |
18 |
--------------------------------------------------------------------------------
/src/static/README.md:
--------------------------------------------------------------------------------
1 | # 静态文件存放目录
2 |
--------------------------------------------------------------------------------
/src/static/arrow-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/arrow-right.png
--------------------------------------------------------------------------------
/src/static/default_head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/default_head.png
--------------------------------------------------------------------------------
/src/static/home_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/home_off.png
--------------------------------------------------------------------------------
/src/static/home_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/home_on.png
--------------------------------------------------------------------------------
/src/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/logo.png
--------------------------------------------------------------------------------
/src/static/logo_wx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/logo_wx.png
--------------------------------------------------------------------------------
/src/static/logo_zfb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/logo_zfb.png
--------------------------------------------------------------------------------
/src/static/none_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/none_data.png
--------------------------------------------------------------------------------
/src/static/personal_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/personal_off.png
--------------------------------------------------------------------------------
/src/static/personal_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Travis-hjs/Reader-Vue/60a6e969061a993150531a04575eefc43acccb8c/src/static/personal_on.png
--------------------------------------------------------------------------------
/src/store/AppOption.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 |
3 | export default class ModuleAppOption {
4 |
5 | /** `APP`操作信息 */
6 | readonly appOption = reactive({
7 | /** `小程序`导航栏高度 */
8 | navBarHeight: 0,
9 | /** `小程序`胶囊距右方间距(方保持左、右间距一致) */
10 | menuRight: 0,
11 | /** `小程序`胶囊距底部间距(保持底部间距一致) */
12 | menuBottom: 0,
13 | /** `小程序`胶囊高度(自定义内容可与胶囊高度保证一致) */
14 | menuHeight: 0,
15 | /** `小程序`胶囊宽度 */
16 | menuWidth: 0,
17 | /** 状态栏高度 */
18 | statusBarHeight: 0,
19 | /** 原生底部`tabbar`高度 */
20 | tabBarHeight: 0,
21 | /** 可使用窗口高度 */
22 | windowHeight: 0,
23 | /** 可使用窗口宽度 */
24 | windowWidth: 0,
25 | /** 屏幕宽度 */
26 | screenWidth: 0,
27 | /** 屏幕高度 */
28 | screenHeight: 0,
29 | /** 是否为`iPhoneX`系列(做底部`UI`判断) */
30 | isIPhoneX: false
31 | })
32 |
33 | /**
34 | * 初始化`APP`操作信息
35 | * @description 最好放在`App.onLaunch`执行,因为这时才是页页面初始化完成,各个尺寸值会比较准确
36 | * @learn 条件编译 https://uniapp.dcloud.io/platform
37 | */
38 | initAppOption() {
39 | const systemInfo = uni.getSystemInfoSync();
40 |
41 | this.appOption.statusBarHeight = systemInfo.statusBarHeight!;
42 | this.appOption.tabBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - systemInfo.statusBarHeight!;
43 | this.appOption.windowHeight = systemInfo.windowHeight;
44 | this.appOption.windowWidth = systemInfo.windowWidth;
45 | this.appOption.screenWidth = systemInfo.screenWidth
46 | this.appOption.screenHeight = systemInfo.screenHeight
47 |
48 | const isIos = systemInfo.system.toLocaleLowerCase().includes("ios");
49 | const vaule = (systemInfo.screenWidth / systemInfo.screenHeight) < 0.5;
50 | this.appOption.isIPhoneX = (isIos && vaule);
51 |
52 | // #ifdef H5
53 | this.appOption.tabBarHeight = 50;
54 | this.appOption.isIPhoneX = false; // 网页端不需要判断底部UI判断
55 | // #endif
56 |
57 | // #ifdef MP
58 | const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
59 | // 导航栏高度 = 状态栏到胶囊的间距(胶囊距上距离-状态栏高度) * 2 + 胶囊高度 + 状态栏高度
60 | this.appOption.navBarHeight = (menuButtonInfo.top - systemInfo.statusBarHeight!) * 2 + menuButtonInfo.height + systemInfo.statusBarHeight!;
61 | this.appOption.menuRight = systemInfo.screenWidth - menuButtonInfo.right;
62 | this.appOption.menuBottom = menuButtonInfo.top - systemInfo.statusBarHeight!;
63 | this.appOption.menuHeight = menuButtonInfo.height;
64 | this.appOption.menuWidth = menuButtonInfo.width;
65 | // #endif
66 | }
67 | }
--------------------------------------------------------------------------------
/src/store/README.md:
--------------------------------------------------------------------------------
1 | # 状态模块
2 |
--------------------------------------------------------------------------------
/src/store/User.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { modifyData } from "@/utils";
3 | import { UserInfo } from "@/types/user";
4 |
5 | const cacheName = "user-info";
6 |
7 | function useUserInfo(): UserInfo {
8 | return {
9 | id: "",
10 | token: "",
11 | phone: ""
12 | }
13 | }
14 |
15 | /**
16 | * 用户状态管理模块
17 | */
18 | export default class ModuleUser {
19 | constructor() {
20 | this.init();
21 | }
22 |
23 | /** 初始化用户数据(从本地获取) */
24 | private init() {
25 | const data = uni.getStorageSync(cacheName);
26 | if (data) {
27 | this.update(JSON.parse(data));
28 | }
29 | }
30 |
31 | /** 用户信息 */
32 | readonly info = reactive>(useUserInfo());
33 |
34 | /**
35 | * 更新用户信息字段
36 | * @param value 修改的值
37 | */
38 | update(value: Partial) {
39 | modifyData(this.info, value);
40 | uni.setStorageSync(cacheName, JSON.stringify(this.info));
41 | }
42 |
43 | /** 重置用户信息 */
44 | reset() {
45 | modifyData(this.info, useUserInfo());
46 | uni.setStorageSync(cacheName, JSON.stringify(this.info));
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { modifyData } from "@/utils";
2 | import { reactive } from "vue";
3 | import ModuleAppOption from "./AppOption";
4 | import ModuleUser from "./User";
5 |
6 | // import { getIamgeByName } from "@/utils";
7 | export class ModuleStore extends ModuleAppOption {
8 | constructor() {
9 | super();
10 | this.initBookOption();
11 | }
12 |
13 | /** 图片对象集 */
14 | get imageInfo() {
15 | // 需要用作背景图的可以用`import`引入
16 | return {
17 | iconWx: "/static/logo_wx.png",
18 | iconZfb: "/static/logo_zfb.png",
19 | logo: "/static/logo.png",
20 | defaultHead: "/static/default_head.png",
21 | noneData: "/static/none_data.png",
22 | iconArrowRight: "/static/arrow-right.png"
23 | // iconWx: getIamgeByName("logo_wx.png"),
24 | // iconZfb: getIamgeByName("logo_zfb.png"),
25 | // logo: getIamgeByName("logo.png"),
26 | // defaultHead: getIamgeByName("default_head.png"),
27 | // noneData: getIamgeByName("none_data.png"),
28 | // iconArrowRight: getIamgeByName("arrow-right.png")
29 | }
30 | }
31 |
32 | /** 用户状态 */
33 | readonly user = new ModuleUser();
34 |
35 | /** 小说操作信息 */
36 | readonly bookOption = reactive({
37 | /** 是否首次打开 */
38 | first: true,
39 | /** 主题 */
40 | theme: 1,
41 | /** 阅读器字体信息 */
42 | sizeInfo: {
43 | /** 标题字体大小 */
44 | title: 24,
45 | /** 段落字体大小 */
46 | p: 18,
47 | /** 标题行高 */
48 | tLineHeight: 24 * 1.5,
49 | /** 段落行高 */
50 | pLineHeight: 18 * 1.5,
51 | /** 下边距 */
52 | margin: 18
53 | }
54 | })
55 |
56 | /** 保存小说操作信息 */
57 | saveBookOption() {
58 | uni.setStorageSync("book-app-option", JSON.stringify(this.bookOption));
59 | }
60 |
61 | /** 获取小说操作信息 */
62 | private initBookOption() {
63 | const data = uni.getStorageSync("book-app-option");
64 | if (data) {
65 | modifyData(this.bookOption, JSON.parse(data))
66 | }
67 | }
68 |
69 | /** 清除小说操作信息 */
70 | removeBookOption() {
71 | uni.removeStorageSync("book-app-option");
72 | }
73 |
74 | }
75 |
76 | /**
77 | * 状态管理模块
78 | * - `OOP`单例设计模式
79 | * - 参考 [你不需要`Vuex`](https://juejin.cn/post/6844903904023429128)
80 | */
81 | const store = new ModuleStore();
82 |
83 | export default store;
--------------------------------------------------------------------------------
/src/styles/README.md:
--------------------------------------------------------------------------------
1 | # 通用样式文件目录
2 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import "../uni.scss";
2 |
3 | @mixin reset { margin: 0; padding: 0; box-sizing: border-box; }
4 |
5 | page { background-color: #fff; width: 100%; min-height: 100vh; @include reset(); }
6 | view, button, input, textarea { font-family: "Arial", "Helvetica Neue", "Helvetica", "sans-serif"; @include reset(); }
7 | image, input, textarea, picker { display: block; width: 100%; @include reset();}
8 | input::after, input::before, button::after, button::before { border: none; @include reset(); }
9 |
10 | // h5默认是100vh,即使页面没有内容也会滚动,这里取消这个设定
11 | // 注意:scss 条件编译必须以 css 注释为标准才能条件编译
12 | /* #ifdef H5 */
13 | uni-page-body { min-height: auto !important; }
14 | /* #endif */
15 |
16 | /* flex布局 */
17 | .flex { display: flex; flex-wrap: nowrap; }
18 | .fwrap { display: flex; flex-wrap: wrap; }
19 | .f1 { flex: 1; }
20 | .f2 { flex: 2; }
21 | .f3 { flex: 3; }
22 | /* 垂直居中 */
23 | .fvertical { display: flex; align-items: center; }
24 | /* 水平居中 */
25 | .fcenter { display: flex; justify-content: center; }
26 | /* 水平+垂直居中 */
27 | .fvc { display: flex; align-items: center; justify-content: center; }
28 | /* 右对齐 */
29 | .fright { display: flex; justify-content: flex-end; }
30 | /* 两端对齐 */
31 | .fbetween { display: flex; justify-content: space-between; }
32 | /* 靠底部对齐 */
33 | .fbottom { display: flex; align-items: flex-end; }
34 |
35 | /* 溢出...显示 当前节点生效 */
36 | .ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
37 |
38 | /* 溢出...显示 子节点生效 */
39 | .ellipsis_1 { @include ellipsis(1); }
40 | .ellipsis_2 { @include ellipsis(2); }
41 | .ellipsis_3 { @include ellipsis(3); }
42 |
43 | /* 卡片 */
44 | .card {
45 | border-radius: 2px;
46 | box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12);
47 | background-color: #fff;
48 | overflow: hidden;
49 | }
50 |
51 | .button { @include button($dark, #eee); }
52 | .button-dark { @include button(#e3b480, $dark); }
53 | .button-pink { @include button($white, $pink); }
54 | .button-red { @include button($white, $red); }
55 |
56 | .line { width: 100%; padding-top: 40rpx; margin-bottom: 40rpx; border-bottom: solid 1px #eee; }
57 |
58 | /* 上下左右边距 */
59 | @for $index from 1 through 5 {
60 | .mgl_#{$index}0 {
61 | margin-left: 10rpx * $index;
62 | }
63 | .mgr_#{$index}0 {
64 | margin-right: 10rpx * $index;
65 | }
66 | .mgt_#{$index}0 {
67 | margin-top: 10rpx * $index;
68 | }
69 | .mgb_#{$index}0 {
70 | margin-bottom: 10rpx * $index;
71 | }
72 | .pdl_#{$index}0 {
73 | padding-left: 10rpx * $index;
74 | }
75 | .pdr_#{$index}0 {
76 | padding-right: 10rpx * $index;
77 | }
78 | .pdt_#{$index}0 {
79 | padding-top: 10rpx * $index;
80 | }
81 | .pdb_#{$index}0 {
82 | padding-bottom: 10rpx * $index;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/styles/loading.scss:
--------------------------------------------------------------------------------
1 | .preloader-inner {
2 | position: relative;
3 | display: block;
4 | width: 100%;
5 | height: 100%;
6 | animation: md-preloader-inner-rotate 5.25s cubic-bezier(.35, 0, .25, 1) infinite;
7 |
8 | .preloader-inner-gap {
9 | position: absolute;
10 | width: 2px;
11 | left: 50%;
12 | margin-left: -1px;
13 | top: 0;
14 | bottom: 0;
15 | box-sizing: border-box;
16 | border-top: 3px solid #010101;
17 | }
18 |
19 | .preloader-inner-left,
20 | .preloader-inner-right {
21 | position: absolute;
22 | top: 0;
23 | height: 100%;
24 | width: 50%;
25 | overflow: hidden
26 | }
27 |
28 | .preloader-inner-half-circle {
29 | position: absolute;
30 | top: 0;
31 | height: 100%;
32 | width: 200%;
33 | box-sizing: border-box;
34 | border: 3px solid #010101;
35 | border-bottom-color: transparent !important;
36 | border-radius: 50%;
37 | animation-iteration-count: infinite;
38 | animation-duration: 1.3125s;
39 | animation-timing-function: cubic-bezier(.35, 0, .25, 1)
40 | }
41 |
42 | .preloader-inner-left {
43 | left: 0;
44 |
45 | .preloader-inner-half-circle {
46 | left: 0;
47 | border-right-color: transparent !important;
48 | animation-name: md-preloader-left-rotate;
49 | }
50 | }
51 |
52 | .preloader-inner-right {
53 | right: 0;
54 |
55 | .preloader-inner-half-circle {
56 | right: 0;
57 | border-left-color: transparent !important;
58 | animation-name: md-preloader-right-rotate;
59 | }
60 | }
61 | }
62 |
63 | @keyframes md-preloader-left-rotate {
64 |
65 | 0%,
66 | 100% {
67 | transform: rotate(130deg)
68 | }
69 |
70 | 50% {
71 | transform: rotate(-5deg)
72 | }
73 | }
74 |
75 | @keyframes md-preloader-right-rotate {
76 |
77 | 0%,
78 | 100% {
79 | transform: rotate(-130deg)
80 | }
81 |
82 | 50% {
83 | transform: rotate(5deg)
84 | }
85 | }
86 |
87 | @keyframes md-preloader-inner-rotate {
88 | 12.5% {
89 | transform: rotate(135deg)
90 | }
91 |
92 | 25% {
93 | transform: rotate(270deg)
94 | }
95 |
96 | 37.5% {
97 | transform: rotate(405deg)
98 | }
99 |
100 | 50% {
101 | transform: rotate(540deg)
102 | }
103 |
104 | 62.5% {
105 | transform: rotate(675deg)
106 | }
107 |
108 | 75% {
109 | transform: rotate(810deg)
110 | }
111 |
112 | 87.5% {
113 | transform: rotate(945deg)
114 | }
115 |
116 | 100% {
117 | transform: rotate(1080deg)
118 | }
119 | }
120 |
121 | @keyframes md-preloader-outer {
122 | 0% {
123 | transform: rotate(0)
124 | }
125 |
126 | 100% {
127 | transform: rotate(360deg)
128 | }
129 | }
130 |
131 | .preloader {
132 | display: block;
133 | width: 40px;
134 | height: 40px;
135 | animation: md-preloader-outer 3.3s linear infinite;
136 | }
--------------------------------------------------------------------------------
/src/type.d.ts:
--------------------------------------------------------------------------------
1 | // 全局声明文件,局部声明文件不要写在这里
2 |
3 | /** 深层递归所有属性为可选 */
4 | type DeepPartial = {
5 | [P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
6 | }
7 |
8 | /** 深层递归所有属性为只读 */
9 | type DeepReadonly = {
10 | readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
11 | }
12 |
13 | /** 深层递归所有属性为必选选(貌似不生效) */
14 | type DeepRequired = {
15 | [P in keyof T]-?: T[P] extends object ? Required : T[P];
16 | }
17 |
18 | /** 运算符号 */
19 | type NumberSymbols = "+" | "-" | "*" | "/";
20 |
21 | /**
22 | * `JavaScript`类型
23 | * - 这里只枚举一些常见类型,后续根据使用场景自行添加即可
24 | */
25 | type JavaScriptTypes = "string" | "number" | "array" | "object" | "boolean" | "function" | "null" | "undefined" | "regexp" | "promise" | "formdata";
26 |
27 | /** 基础对象 */
28 | interface BaseObj {
29 | [key: string]: T
30 | }
31 |
32 | /** 接口请求基础响应数据 */
33 | interface ApiResult {
34 | /** 接口状态`code === 1`为成功 */
35 | code: number
36 | /** 接口响应数据 */
37 | data: T
38 | /** 接口响应信息 */
39 | msg: string
40 | }
41 |
42 | /** 接口请求列表响应数据 */
43 | interface ApiResultList extends PageInfo {
44 | /** 列表数据 */
45 | list: Array
46 | }
47 |
48 | /** 请求配置 */
49 | interface RequestOptions {
50 | /**
51 | * 请求头配置(header 中不能设置 Referer。),会覆盖默认设置
52 | * - 需要表单形式就`headers: { "codeMode": "form" }`;
53 | * - 其他头部设置自行定义
54 | */
55 | headers: BaseObj & {
56 | codeMode?: "json" | "form"
57 | },
58 | /** 请求数据类型 */
59 | dataType: "json" | "text" | ""
60 | /** 接口数据响应类型 */
61 | responseType: "json" | "arraybuffer" | "blob" | "stream" | "document" | "text"
62 | /** 是否在`res.code !== 1`的时候显示提示,默认`false`,传入`true`则用`res.msg`作为提示,也可以传入字符串作为提示内容 */
63 | showTip: string | boolean
64 | }
65 |
66 | /** 页码信息 */
67 | interface PageInfo {
68 | /** 一页多少条 */
69 | pageSize: number
70 | /** 当前页,从`1`开始 */
71 | currentPage: number
72 | /** 总数 */
73 | total?: number
74 | }
75 |
76 | /** `uni-app`change事件参数 */
77 | interface UniAppChangeEvent {
78 | detail: {
79 | /** `@change`事件触发的值 */
80 | value: T
81 | }
82 | }
83 |
84 | interface Window {
85 | /**
86 | * 当前版本,方便在控制台查看调试用
87 | * @description 引用的是`package.json`中的`version`
88 | */
89 | version: string
90 | }
91 |
92 | /** .nvue文件专用模块 */
93 | declare const weex: any;
94 |
--------------------------------------------------------------------------------
/src/types/README.md:
--------------------------------------------------------------------------------
1 | # 类型文件目录,统一小写命名
2 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // import { ComponentInternalInstance } from "vue";
2 |
3 | // /**
4 | // * `vue3`类型扩充
5 | // * - 弥补官方的类型不足问题
6 | // */
7 | // export declare namespace Vue3 {
8 | // /**
9 | // * `vue`实例上下文
10 | // * - `vue3`类型文件中貌似没有暴露可以使用的类型,所以自定义补充一个接口代替,和`vue2`中一样的类型一致
11 | // */
12 | // interface Ctx {
13 | // /** 当前组件节点 */
14 | // readonly $el: HTMLElement
15 | // /** 父节点 */
16 | // readonly $parent?: Ctx
17 | // /** 当前组件实例 */
18 | // readonly $root: Ctx
19 | // /** 选项配置 */
20 | // readonly $options: {
21 | // name: string
22 | // }
23 | // }
24 | // /**
25 | // * `hooks: getCurrentInstance()`钩子函数返回的类型扩充
26 | // */
27 | // interface Instance extends ComponentInternalInstance {
28 | // /** 实例上下文对象 */
29 | // ctx: Ctx
30 | // }
31 | // }
32 |
33 | /** 上传图片返回结果 */
34 | export interface UploadChange {
35 | /** 和当前上传组件绑定的`id` */
36 | id: string | number
37 | /** 图片路径 */
38 | src: string
39 | }
40 |
41 | /** 表单规则类型 */
42 | export interface TheFormRulesItem {
43 | /** 是否必填项 */
44 | required?: boolean
45 | /** 提示字段 */
46 | message?: string
47 | /** 指定类型 */
48 | type?: "number" | "array"
49 | /**
50 | * 自定义的校验规则(正则)
51 | * - 考虑到微信一些特殊的抽风机制,在微信小程序中,除`number|string|object|undefined|null`这几个基础类型外,其他类型是会被过滤掉,所以这里在写正则的时候,在末尾加上`.toString()`即可
52 | */
53 | reg?: string // | RegExp
54 | }
55 |
56 | /** 表单规则类型 */
57 | export type TheFormRules = { [key: string]: Array };
58 |
59 | /** `label`布局位置 */
60 | export type LabelPosition = "left" | "right" | "top";
61 |
62 | /** 表单验证回调类型 */
63 | export interface TheFormValidateCallback {
64 | (
65 | /** 是否验证通过 */
66 | isValid: boolean,
67 | /** 验证不通过的规则列表 */
68 | rules: { [key: string]: Array }
69 | ): void
70 | }
71 |
72 | /** 选择器`item`数据 */
73 | export interface PickerSelectItem {
74 | /** 展示字段 */
75 | label: string
76 | /** 对应的值 */
77 | value: T
78 | /**
79 | * 下级数据
80 | * @description 最多三层,选择器栏目数根据当前下级动态显示
81 | */
82 | children?: Array>
83 | /** 其他携带的值 */
84 | [key: string]: any
85 | }
86 |
--------------------------------------------------------------------------------
/src/types/user.ts:
--------------------------------------------------------------------------------
1 | /** 用户信息类型 */
2 | export interface UserInfo {
3 | /** 登录凭据 */
4 | token: string
5 | /** 用户手机号 */
6 | phone: number | "",
7 | /** 用户`id` */
8 | id: number | ""
9 | }
10 |
--------------------------------------------------------------------------------
/src/uni.scss:
--------------------------------------------------------------------------------
1 | // 每个页面需要引入的文件,注意:vue.config.js 中配置的 css.loaderOptions 是无法在当前项目生效的,
2 | // 可能是uni-app项目设定和标准vue-cli项目设定不一样导致的,需要在当前文件全局引入即可
3 | // 全局样式写在`App.vue`里面,一些预处理工具样式可以写这里,因为每个页面都会注入一遍
4 | // 全局样式不写在这里的原因是因为每个页面都注入一遍的话会导致`html-style`有多个相同的代码标签
5 |
6 | $white: #fff;
7 | $black: #010101;
8 | $dark: #222;
9 | $red: #fa2d2d;
10 | $pink: #f04e7d;
11 | $blue: #2C72F3;
12 |
13 | // 溢出...显示
14 | @mixin ellipsis($number) {
15 | display: -webkit-box;
16 | -webkit-box-orient: vertical;
17 | -webkit-line-clamp: $number;
18 | overflow: hidden;
19 | }
20 |
21 | @mixin button($color, $bg) {
22 | font-size: 30rpx;
23 | min-width: 160rpx;
24 | padding: 0 20rpx;
25 | border-radius: 2px;
26 | height: 80rpx;
27 | background-color: $bg;
28 | border-color: $bg;
29 | color: $color;
30 | line-height: 1;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 |
35 | &::after,
36 | &::before {
37 | border: none;
38 | background-color: transparent;
39 | }
40 |
41 | &:hover {
42 | background-color: $bg;
43 | border-color: $bg;
44 | color: $color;
45 | }
46 |
47 | &:active {
48 | opacity: 0.85;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/Config.ts:
--------------------------------------------------------------------------------
1 | function moduleConfig() {
2 |
3 | const env = process.env.NODE_ENV === "development" ? "dev" : "prod";
4 |
5 | const url = {
6 | dev: `/api`,
7 | prod: "https://huangjingsheng.com/api"
8 | }
9 |
10 | return {
11 | /** 请求超时毫秒 */
12 | get requestOvertime() {
13 | return 8000;
14 | },
15 | /** `api`请求域名 */
16 | get apiUrl() {
17 | return url[env];
18 | },
19 | /** 当前环境模式 */
20 | get env() {
21 | return env;
22 | },
23 | /** 上传图片地址 */
24 | get uploadUrl() {
25 | return "http://xxx.com/upload"
26 | }
27 | }
28 | }
29 |
30 | /** 配置模块 */
31 | const config = moduleConfig();
32 |
33 | export default config;
34 |
--------------------------------------------------------------------------------
/src/utils/Control.ts:
--------------------------------------------------------------------------------
1 | import { nextTick } from "vue";
2 |
3 | // ========================= 控件模块 =========================
4 |
5 | /**
6 | * 打开其他应用
7 | * @param name 应用名
8 | * @param callback 错误回调
9 | */
10 | export function openApp(name: "qq" | "wx" | "zfb" | "sina" | "taobao", callback?: (result: any) => void) {
11 | // #ifdef APP-PLUS
12 | // learn: https://ask.dcloud.net.cn/article/35621
13 | const data = {
14 | qq: "mqq://",
15 | wx: "weixin://",
16 | zfb: "alipay://",
17 | sina: "sinaweibo://",
18 | taobao: "taobao://",
19 | }
20 | plus.runtime.openURL(data[name], callback);
21 | // #endif
22 | }
23 |
24 | /**
25 | * 显示加载提示
26 | * @param text 提示文字
27 | */
28 | export function showLoading(text: string = "加载中..") {
29 | uni.showLoading({
30 | title: text,
31 | mask: true
32 | });
33 | }
34 |
35 | /**
36 | * 显示提示条
37 | * @param tip 提示文字
38 | * @param duration 持续时间
39 | */
40 | export function showToast(tip: string, duration = 2000) {
41 | uni.showToast({
42 | title: tip,
43 | // position: "bottom",
44 | icon: "none",
45 | duration: duration,
46 | // image: src
47 | });
48 | }
49 |
50 | /**
51 | * 显示提示框
52 | * @param content 提示的内容
53 | * @param success 确认回调
54 | * @param title 提示标题
55 | */
56 | export function showAlert(content: string, success?: (res: UniApp.ShowModalRes) => void, title: string = "操作提示") {
57 | uni.showModal({
58 | title: title,
59 | content: content,
60 | showCancel: false,
61 | success: success
62 | })
63 | }
64 |
65 | interface ShowConfirmOptions {
66 | /** 内容 */
67 | content: string
68 | /** 标题 */
69 | title?: string
70 | /** 确认回调 */
71 | confirm?(): void
72 | /** 取消回调 */
73 | cancel?(): void
74 | /** 确认按钮文字 */
75 | confirmText?: string
76 | /** 取消按钮文字,超过4个中文会报错 */
77 | cancelText: string
78 | }
79 |
80 | /**
81 | * 确认弹窗
82 | * @param options 传参对象
83 | */
84 | export function showConfirm(options: ShowConfirmOptions) {
85 | uni.showModal({
86 | title: options.title || "操作提示",
87 | content: options.content,
88 | showCancel: true,
89 | confirmText: options.confirmText || "确认",
90 | cancelText: options.cancelText || "取消",
91 | success(res) {
92 | if (res.confirm) {
93 | options.confirm && options.confirm();
94 | } else if (res.cancel) {
95 | options.cancel && options.cancel();
96 | }
97 | },
98 | });
99 | }
100 |
101 | /**
102 | * 复制文本
103 | * @param value 复制的内容
104 | * @param success 成功回调
105 | */
106 | export function copyText(value: string, success?: () => void) {
107 | value = value.replace(/(^\s*)|(\s*$)/g, "");
108 | if (!value) {
109 | return showToast("复制的内容不能为空!");
110 | }
111 |
112 | // #ifndef H5
113 | uni.setClipboardData({
114 | data: value,
115 | success() {
116 | showToast("复制成功");
117 | success && success();
118 | }
119 | });
120 | // #endif
121 |
122 | // #ifdef H5
123 | const id = "the-clipboard";
124 | let clipboard = document.getElementById(id) as HTMLTextAreaElement;
125 | if (!clipboard) {
126 | clipboard = document.createElement("textarea");
127 | clipboard.id = id;
128 | clipboard.readOnly = true;
129 | clipboard.style.cssText = "font-size: 15px; position: fixed; top: -1000%; left: -1000%;";
130 | document.body.appendChild(clipboard);
131 | }
132 | clipboard.value = value;
133 | clipboard.select();
134 | clipboard.setSelectionRange(0, clipboard.value.length);
135 | const state = document.execCommand("copy");
136 | if (state) {
137 | showToast("复制成功");
138 | success && success();
139 | } else {
140 | showToast("复制失败");
141 | }
142 | // #endif
143 | }
144 |
145 | interface ScrollviewCenterOptions {
146 | /**
147 | * 当前实例
148 | * ```js
149 | * import { getCurrentInstance } from "vue";
150 | * getCurrentInstance();
151 | * ```
152 | */
153 | ctx: T,
154 | /** 要滚动的目标节点`id` */
155 | id: string
156 | /** ``的宽度,默认是屏幕宽度 */
157 | wrapWidth?: number
158 | /** 点击事件 */
159 | event?: Event
160 | /** 是否主动设置偏移到中心位置,设置值时,`event`不需要传入 */
161 | scrollValue?: number
162 | /** 回调 */
163 | callback?: (left: number, info: UniApp.NodeInfo) => void
164 | }
165 |
166 | /**
167 | * 监听``组件指定元素滚动到视图中心的偏移值
168 | * @param option 配置参数
169 | */
170 | export function onScrollviewCenter(option: ScrollviewCenterOptions) {
171 | const width = option.wrapWidth || uni.getSystemInfoSync().windowWidth;
172 | nextTick(function () {
173 | const node = uni.createSelectorQuery().in(option.ctx).select(`#${option.id}`);
174 | const left = option.event ? (option.event.currentTarget as any).offsetLeft : 0;
175 | node.boundingClientRect(function (nodeInfo) {
176 | let result = 0;
177 | if (nodeInfo && !Array.isArray(nodeInfo)) {
178 | if (typeof option.scrollValue === "number") {
179 | result = option.scrollValue + nodeInfo.left! + nodeInfo.width! / 2 - width / 2;
180 | } else {
181 | result = left + nodeInfo.width! / 2 - width / 2;
182 | }
183 | }
184 | typeof option.callback === "function" && option.callback(result, nodeInfo as any);
185 | }).exec();
186 | });
187 | }
188 |
--------------------------------------------------------------------------------
/src/utils/README.md:
--------------------------------------------------------------------------------
1 | # 工具类目录
2 |
--------------------------------------------------------------------------------
/src/utils/icon.ts:
--------------------------------------------------------------------------------
1 | const iconData = {
2 | /** icon 数量 */
3 | icons: {
4 | "pulldown": "\ue588",
5 | "refreshempty": "\ue461",
6 | "back": "\ue471",
7 | "forward": "\ue470",
8 | "more": "\ue507",
9 | "more-filled": "\ue537",
10 | "scan": "\ue612",
11 | "qq": "\ue264",
12 | "weibo": "\ue260",
13 | "weixin": "\ue261",
14 | "pengyouquan": "\ue262",
15 | "loop": "\ue565",
16 | "refresh": "\ue407",
17 | "refresh-filled": "\ue437",
18 | "arrowthindown": "\ue585",
19 | "arrowthinleft": "\ue586",
20 | "arrowthinright": "\ue587",
21 | "arrowthinup": "\ue584",
22 | "undo-filled": "\ue7d6",
23 | "undo": "\ue406",
24 | "redo": "\ue405",
25 | "redo-filled": "\ue7d9",
26 | "bars": "\ue563",
27 | "chatboxes": "\ue203",
28 | "camera": "\ue301",
29 | "chatboxes-filled": "\ue233",
30 | "camera-filled": "\ue7ef",
31 | "cart-filled": "\ue7f4",
32 | "cart": "\ue7f5",
33 | "checkbox-filled": "\ue442",
34 | "checkbox": "\ue7fa",
35 | "arrowleft": "\ue582",
36 | "arrowdown": "\ue581",
37 | "arrowright": "\ue583",
38 | "smallcircle-filled": "\ue801",
39 | "arrowup": "\ue580",
40 | "circle": "\ue411",
41 | "eye-filled": "\ue568",
42 | "eye-slash-filled": "\ue822",
43 | "eye-slash": "\ue823",
44 | "eye": "\ue824",
45 | "flag-filled": "\ue825",
46 | "flag": "\ue508",
47 | "gear-filled": "\ue532",
48 | "reload": "\ue462",
49 | "gear": "\ue502",
50 | "hand-thumbsdown-filled": "\ue83b",
51 | "hand-thumbsdown": "\ue83c",
52 | "hand-thumbsup-filled": "\ue83d",
53 | "heart-filled": "\ue83e",
54 | "hand-thumbsup": "\ue83f",
55 | "heart": "\ue840",
56 | "home": "\ue500",
57 | "info": "\ue504",
58 | "home-filled": "\ue530",
59 | "info-filled": "\ue534",
60 | "circle-filled": "\ue441",
61 | "chat-filled": "\ue847",
62 | "chat": "\ue263",
63 | "mail-open-filled": "\ue84d",
64 | "email-filled": "\ue231",
65 | "mail-open": "\ue84e",
66 | "email": "\ue201",
67 | "checkmarkempty": "\ue472",
68 | "list": "\ue562",
69 | "locked-filled": "\ue856",
70 | "locked": "\ue506",
71 | "map-filled": "\ue85c",
72 | "map-pin": "\ue85e",
73 | "map-pin-ellipse": "\ue864",
74 | "map": "\ue364",
75 | "minus-filled": "\ue440",
76 | "mic-filled": "\ue332",
77 | "minus": "\ue410",
78 | "micoff": "\ue360",
79 | "mic": "\ue302",
80 | "clear": "\ue434",
81 | "smallcircle": "\ue868",
82 | "close": "\ue404",
83 | "closeempty": "\ue460",
84 | "paperclip": "\ue567",
85 | "paperplane": "\ue503",
86 | "paperplane-filled": "\ue86e",
87 | "person-filled": "\ue131",
88 | "contact-filled": "\ue130",
89 | "person": "\ue101",
90 | "contact": "\ue100",
91 | "images-filled": "\ue87a",
92 | "phone": "\ue200",
93 | "images": "\ue87b",
94 | "image": "\ue363",
95 | "image-filled": "\ue877",
96 | "location-filled": "\ue333",
97 | "location": "\ue303",
98 | "plus-filled": "\ue439",
99 | "plus": "\ue409",
100 | "plusempty": "\ue468",
101 | "help-filled": "\ue535",
102 | "help": "\ue505",
103 | "navigate-filled": "\ue884",
104 | "navigate": "\ue501",
105 | "mic-slash-filled": "\ue892",
106 | "search": "\ue466",
107 | "settings": "\ue560",
108 | "sound": "\ue590",
109 | "sound-filled": "\ue8a1",
110 | "spinner-cycle": "\ue465",
111 | "download-filled": "\ue8a4",
112 | "personadd-filled": "\ue132",
113 | "videocam-filled": "\ue8af",
114 | "personadd": "\ue102",
115 | "upload": "\ue402",
116 | "upload-filled": "\ue8b1",
117 | "starhalf": "\ue463",
118 | "star-filled": "\ue438",
119 | "star": "\ue408",
120 | "trash": "\ue401",
121 | "phone-filled": "\ue230",
122 | "compose": "\ue400",
123 | "videocam": "\ue300",
124 | "trash-filled": "\ue8dc",
125 | "download": "\ue403",
126 | "chatbubble-filled": "\ue232",
127 | "chatbubble": "\ue202",
128 | "cloud-download": "\ue8e4",
129 | "cloud-upload-filled": "\ue8e5",
130 | "cloud-upload": "\ue8e6",
131 | "cloud-download-filled": "\ue8e9",
132 | "headphones":"\ue8bf",
133 | "shop":"\ue609"
134 | },
135 | /** 字体文件 */
136 | font: 'data:font/truetype;charset=utf-8;base64,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AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMHdW5pMDAwMAdjb250YWN0BnBlcnNvbglwZXJzb25hZGQNY29udGFjdGZpbGxlZAxwZXJzb25maWxsZWQPcGVyc29uYWRkZmlsbGVkBXBob25lBWVtYWlsCmNoYXRidWJibGUJY2hhdGJveGVzC3Bob25lZmlsbGVkC2VtYWlsZmlsbGVkEGNoYXRidWJibGVmaWxsZWQPY2hhdGJveGVzZmlsbGVkBXdlaWJvBndlaXhpbgtwZW5neW91cXVhbgRjaGF0AnFxCHZpZGVvY2FtBmNhbWVyYQNtaWMIbG9jYXRpb24JbWljZmlsbGVkDmxvY2F0aW9uZmlsbGVkBm1pY29mZgVpbWFnZQNtYXAHY29tcG9zZQV0cmFzaAZ1cGxvYWQIZG93bmxvYWQFY2xvc2UEcmVkbwR1bmRvB3JlZnJlc2gEc3Rhcgt3aGl0ZWNpcmNsZQVjbGVhcg1yZWZyZXNoZmlsbGVkCnN0YXJmaWxsZWQKcGx1c2ZpbGxlZAttaW51c2ZpbGxlZAxjaXJjbGVmaWxsZWQOY2hlY2tib3hmaWxsZWQKY2xvc2VlbXB0eQxyZWZyZXNoZW1wdHkGcmVsb2FkCHN0YXJoYWxmDHNwaW5uZXJjeWNsZQZzZWFyY2gJcGx1c2VtcHR5B2ZvcndhcmQEYmFjaw5jaGVja21hcmtlbXB0eQRob21lCG5hdmlnYXRlBGdlYXIKcGFwZXJwbGFuZQRpbmZvBGhlbHAGbG9ja2VkBG1vcmUEZmxhZwpob21lZmlsbGVkCmdlYXJmaWxsZWQKaW5mb2ZpbGxlZApoZWxwZmlsbGVkCm1vcmVmaWxsZWQIc2V0dGluZ3MEbGlzdARiYXJzBGxvb3AJcGFwZXJjbGlwCWV5ZWZpbGxlZAx1cHdhcmRzYXJyb3cOZG93bndhcmRzYXJyb3cObGVmdHdhcmRzYXJyb3cPcmlnaHR3YXJkc2Fycm93C2Fycm93dGhpbnVwDWFycm93dGhpbmRvd24NYXJyb3d0aGlubGVmdA5hcnJvd3RoaW5yaWdodAhwdWxsZG93bgVzb3VuZARzaG9wBHNjYW4KdW5kb2ZpbGxlZApyZWRvZmlsbGVkDGNhbWVyYWZpbGxlZApjYXJ0ZmlsbGVkBGNhcnQIY2hlY2tib3gRc21hbGxjaXJjbGVmaWxsZWQOZXllc2xhc2hmaWxsZWQIZXllc2xhc2gDZXllCmZsYWdmaWxsZWQVaGFuZHRodW1ic2Rvd25fZmlsbGVkDmhhbmR0aHVtYnNkb3duEmhhbmR0aHVtYnN1cGZpbGxlZAtoZWFydGZpbGxlZAxoYW5kdGh1bWJzdXAOYmxhY2toZWFydHN1aXQKY2hhdGZpbGxlZA5tYWlsb3BlbmZpbGxlZAhtYWlsb3Blbgxsb2NrZWRmaWxsZWQJbWFwZmlsbGVkBm1hcHBpbg1tYXBwaW5lbGxpcHNlC3NtYWxsY2lyY2xlEHBhcGVycGxhbmVmaWxsZWQLaW1hZ2VmaWxsZWQMaW1hZ2VzZmlsbGVkBmltYWdlcw5uYXZpZ2F0ZWZpbGxlZA5taWNzbGFzaGZpbGxlZAtzb3VuZGZpbGxlZA5kb3dubG9hZGZpbGxlZA52aWRlb2NhbWZpbGxlZAx1cGxvYWRmaWxsZWQKaGVhZHBob25lcwt0cmFzaGZpbGxlZA1jbG91ZGRvd25sb2FkEWNsb3VkdXBsb2FkZmlsbGVkC2Nsb3VkdXBsb2FkE2Nsb3VkZG93bmxvYWRmaWxsZWQHdW5pMDAwOQAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAQCFAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANoxE3MAAAAA2jSpUA=='
137 | }
138 |
139 | export default iconData;
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 检测类型
3 | * @param target 检测的目标
4 | */
5 | export function checkType(target: any) {
6 | const value: string = Object.prototype.toString.call(target);
7 | const result = (value.match(/\[object (\S*)\]/) as RegExpMatchArray)[1];
8 | return result.toLocaleLowerCase() as JavaScriptTypes;
9 | }
10 |
11 | /**
12 | * 修改属性值-只修改之前存在的值
13 | * @param target 修改的目标
14 | * @param value 修改的内容
15 | */
16 | export function modifyData(target: T, value: T) {
17 | for (const key in value) {
18 | if (Object.prototype.hasOwnProperty.call(target, key)) {
19 | // target[key] = value[key];
20 | // 需要的话,深层逐个赋值
21 | if (checkType(target[key]) === "object") {
22 | modifyData(target[key], value[key]);
23 | } else {
24 | target[key] = value[key];
25 | }
26 | }
27 | }
28 | }
29 |
30 | /**
31 | * 设置属性值-之前不存在的值也根据传入的`value`值去设置
32 | * @param target 设置的目标
33 | * @param value 设置的内容
34 | */
35 | export function setData(target: T, value: T) {
36 | for (const key in value) {
37 | target[key] = value[key];
38 | }
39 | }
40 |
41 | /**
42 | * 格式化日期
43 | * @param value 指定日期
44 | * @param format 格式化的规则
45 | * @example
46 | * ```js
47 | * formatDate();
48 | * formatDate(1603264465956);
49 | * formatDate(1603264465956, "h:m:s");
50 | * formatDate(1603264465956, "Y年M月D日");
51 | * ```
52 | */
53 | export function formatDate(value: string | number | Date = Date.now(), format = "Y-M-D h:m:s") {
54 | if (["null", null, "undefined", undefined, ""].includes(value as any)) return "";
55 | // ios 和 mac 系统中,带横杆的字符串日期是格式不了的,这里做一下判断处理
56 | if (typeof value === "string" && new Date(value).toString() === "Invalid Date") {
57 | value = value.replace(/-/g, "/");
58 | }
59 | const formatNumber = (n: number) => `0${n}`.slice(-2);
60 | const date = new Date(value);
61 | const formatList = ["Y", "M", "D", "h", "m", "s"];
62 | const resultList = [];
63 | resultList.push(date.getFullYear().toString());
64 | resultList.push(formatNumber(date.getMonth() + 1));
65 | resultList.push(formatNumber(date.getDate()));
66 | resultList.push(formatNumber(date.getHours()));
67 | resultList.push(formatNumber(date.getMinutes()));
68 | resultList.push(formatNumber(date.getSeconds()));
69 | for (let i = 0; i < resultList.length; i++) {
70 | format = format.replace(formatList[i], resultList[i]);
71 | }
72 | return format;
73 | }
74 |
75 | /**
76 | * 数字运算(主要用于小数点精度问题)
77 | * [see](https://juejin.im/post/6844904066418491406#heading-12)
78 | * @param a 前面的值
79 | * @param type 计算方式
80 | * @param b 后面的值
81 | * @example
82 | * ```js
83 | * // 可链式调用
84 | * const res = computeNumber(1.3, "-", 1.2).next("+", 1.5).next("*", 2.3).next("/", 0.2).result;
85 | * console.log(res);
86 | * ```
87 | */
88 | export function computeNumber(a: number, type: NumberSymbols, b: number) {
89 | /**
90 | * 获取数字小数点的长度
91 | * @param n 数字
92 | */
93 | function getDecimalLength(n: number) {
94 | const decimal = n.toString().split(".")[1];
95 | return decimal ? decimal.length : 0;
96 | }
97 | /**
98 | * 修正小数点
99 | * @description 防止出现 `33.33333*100000 = 3333332.9999999995` && `33.33*10 = 333.29999999999995` 这类情况做的处理
100 | * @param n 数字
101 | */
102 | const amend = (n: number, precision = 15) => parseFloat(Number(n).toPrecision(precision));
103 | const power = Math.pow(10, Math.max(getDecimalLength(a), getDecimalLength(b)));
104 | let result = 0;
105 |
106 | a = amend(a * power);
107 | b = amend(b * power);
108 |
109 | switch (type) {
110 | case "+":
111 | result = (a + b) / power;
112 | break;
113 | case "-":
114 | result = (a - b) / power;
115 | break;
116 | case "*":
117 | result = (a * b) / (power * power);
118 | break;
119 | case "/":
120 | result = a / b;
121 | break;
122 | }
123 |
124 | result = amend(result);
125 |
126 | return {
127 | /** 计算结果 */
128 | result,
129 | /**
130 | * 继续计算
131 | * @param nextType 继续计算方式
132 | * @param nextValue 继续计算的值
133 | */
134 | next(nextType: NumberSymbols, nextValue: number) {
135 | return computeNumber(result, nextType, nextValue);
136 | },
137 | /**
138 | * 小数点进位
139 | * @param n 小数点后的位数
140 | */
141 | toHex(n: number) {
142 | const strings = result.toString().split(".");
143 | if (n > 0 && strings[1] && strings[1].length > n) {
144 | const decimal = strings[1].slice(0, n);
145 | const value = Number(`${strings[0]}.${decimal}`);
146 | const difference = 1 / Math.pow(10, decimal.length);
147 | result = computeNumber(value, "+", difference).result;
148 | }
149 | return result;
150 | }
151 | };
152 | }
153 |
154 | /**
155 | * 获取`url?`后面参数(JSON对象)
156 | * @param name 获取指定参数名
157 | * @param target 目标字段,默认`location.search`
158 | * @example
159 | * ```js
160 | * // 当前网址为 www.https://hjs.com?id=99&age=123&key=sdasfdfr
161 | * const targetAge = getQueryParam("age", "id=12&age=14&name=hjs");
162 | * const params = getQueryParam();
163 | * const age = getQueryParam("age");
164 | * // 非IE浏览器下简便方法
165 | * new URLSearchParams(location.search).get("age");
166 | * ```
167 | */
168 | export function getQueryParam(name?: string, target?: string) {
169 | const code = target || location.href.split("?")[1] || "";
170 | const list = code.split("&");
171 | const params: any = {};
172 | for (let i = 0; i < list.length; i++) {
173 | const item = list[i];
174 | const items = item.split("=");
175 | if (items.length > 1) {
176 | params[items[0]] = item.replace(`${items[0]}=`, "");
177 | }
178 | }
179 | if (name) {
180 | return params[name] || "";
181 | } else {
182 | return params;
183 | }
184 | }
185 |
186 | /**
187 | * 获取一些深层`key`的对象值
188 | * @param target 目标对象
189 | * @param key `key`字段
190 | * @example
191 | * ```js
192 | * const info = {
193 | * list: [
194 | * { value: "hjs" }
195 | * ]
196 | * }
197 | * getDeepLevelValue(info, "list.0.value"); // => "hjs"
198 | * ```
199 | */
200 | export function getDeepLevelValue(target: any, key: string) {
201 | const keys = key.split(".");
202 | let result;
203 | for (let i = 0; i < keys.length; i++) {
204 | const prop = keys[i];
205 | result = target[prop];
206 | const type = checkType(result);
207 | if (type !== "object" && type !== "array") {
208 | break;
209 | } else {
210 | target = target[prop];
211 | }
212 | }
213 | return result;
214 | }
215 |
216 | /**
217 | * ES5 兼容 ES6 `Array.findIndex`
218 | * @param array
219 | * @param compare 对比函数
220 | */
221 | export function findIndex(array: Array, compare: (value: T, index: number) => boolean) {
222 | var result = -1;
223 | for (var i = 0; i < array.length; i++) {
224 | if (compare(array[i], i)) {
225 | result = i;
226 | break;
227 | }
228 | }
229 | return result;
230 | }
231 |
232 | /**
233 | * 范围随机整数
234 | * @param min 最小数
235 | * @param max 最大数
236 | */
237 | export function ranInt(min: number, max: number) {
238 | return Math.round(Math.random() * (max - min) + min);
239 | }
240 |
241 | /**
242 | * 随机生成中文
243 | * @param min 最小数
244 | * @param max 最大数
245 | */
246 | export function randomText(min: number, max: number) {
247 | const len = Math.floor(Math.random() * max) + min;
248 | const base = 20000;
249 | const range = 1000;
250 | let str = "";
251 | let i = 0;
252 | while (i < len) {
253 | i++;
254 | const lower = Math.floor(Math.random() * range);
255 | str += String.fromCharCode(base + lower);
256 | }
257 | return str;
258 | }
259 |
260 | // /**
261 | // * 获取`/static/`目录下的图片路径
262 | // * @param name 图片或文件路径名,需要带后缀
263 | // * @returns
264 | // */
265 | // export function getIamgeByName(name: string) {
266 | // console.log(name, import.meta.url);
267 | // return new URL(`../static/${name}`, import.meta.url).href;
268 | // }
269 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import config from "./config";
2 | import store from "@/store";
3 |
4 | function getResultInfo(result: { statusCode: number, data: any }) {
5 | const info: ApiResult = { code: -1, msg: "", data: null }
6 | switch (result.statusCode) {
7 | case 999:
8 | info.msg = "网络出错了";
9 | break;
10 | case 200:
11 | info.code = 1;
12 | info.msg = "ok";
13 | info.data = result.data;
14 | break;
15 | case 400:
16 | info.msg = "接口传参不正确";
17 | break;
18 | case 403:
19 | info.msg = "登录已过期";
20 | store.user.reset();
21 | break;
22 | case 404:
23 | info.msg = "接口不存在";
24 | break;
25 | default:
26 | break;
27 | }
28 | if (result.statusCode >= 500) {
29 | info.msg = "服务器闹脾气了";
30 | }
31 | return info;
32 | }
33 |
34 | /**
35 | * 基础请求
36 | * @param method 请求方法
37 | * @param path 请求路径
38 | * @param data 请求参数
39 | * @param options 其他配置参数
40 | */
41 | export default function request(method: "GET" | "POST" | "DELETE" | "PUT", path: string, data?: any, options: Partial = {}) {
42 | const headers = options.headers || {};
43 | return new Promise>(function (resolve, reject) {
44 | uni.request({
45 | method: method,
46 | header: {
47 | "Authorization": store.user.info.token,
48 | ...headers
49 | },
50 | url: config.apiUrl + path,
51 | data: data,
52 | timeout: config.requestOvertime,
53 | success(res) {
54 | // console.log("request.success", res);
55 | const info = getResultInfo(res);
56 | if (info.code !== 1 && options.showTip) {
57 | uni.showToast({
58 | title: typeof options.showTip === 'boolean' ? (info.msg || "操作失败") : options.showTip as string,
59 | position: "bottom",
60 | icon: "none",
61 | duration: 2400
62 | });
63 | }
64 | resolve(info);
65 | },
66 | fail(err) {
67 | const info = getResultInfo({ statusCode: 999, data: null });
68 | info.msg = err.errMsg;
69 | resolve(info);
70 | }
71 | })
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "lib": ["esnext", "dom"],
13 | "baseUrl": "./",
14 | "paths": {
15 | "@/*": ["src/*"]
16 | },
17 | "types": [
18 | "@dcloudio/types",
19 | "@types/node"
20 | ]
21 | },
22 | "include": [
23 | "src/**/*.ts",
24 | "src/**/*.d.ts",
25 | "src/**/*.tsx",
26 | "src/**/*.vue"
27 | ]
28 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { defineConfig } from "vite";
3 | import uni from "@dcloudio/vite-plugin-uni";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [uni()],
8 | base: "./",
9 | resolve: {
10 | alias: {
11 | "@": path.resolve(__dirname, "src")
12 | }
13 | },
14 | server: {
15 | port: 2023,
16 | host: "0.0.0.0"
17 | }
18 | });
19 |
--------------------------------------------------------------------------------