hookehuyr

feat: 初始化项目基础结构和功能

添加项目基础文件结构,包括页面、组件、API、状态管理等
集成Taro4、Vue3、Pinia、TailwindCSS等技术栈
实现授权登录、页面导航、海报生成等核心功能
配置构建工具和开发环境
Showing 63 changed files with 2844 additions and 0 deletions
1 +# http://editorconfig.org
2 +root = true
3 +
4 +[*]
5 +indent_style = space
6 +indent_size = 2
7 +charset = utf-8
8 +trim_trailing_whitespace = true
9 +insert_final_newline = true
10 +
11 +[*.md]
12 +trim_trailing_whitespace = false
1 +// ESLint 检查 .vue 文件需要单独配置编辑器:
2 +// https://eslint.vuejs.org/user-guide/#editor-integrations
3 +{
4 + "extends": ["taro/vue3"]
5 +}
6 +
1 +dist/
2 +deploy_versions/
3 +.temp/
4 +.rn_temp/
5 +node_modules/
6 +.DS_Store
7 +.swc
8 +.history
9 +.trae
1 +## 项目介绍
2 +
3 +基于Taro4的微信小程序模版,集成了常用的功能,如登录、注册、列表、详情、购物车等。
4 +
5 +## 技术栈
6 +
7 +- Taro4
8 +- Vue3
9 +- TypeScript
10 +- Pinia
11 +- Less
12 +
13 +## 项目结构
14 +
15 +- src
16 + - api:请求接口
17 + - assets:静态资源
18 + - components:全局组件
19 + - config:项目配置
20 + - pages:页面
21 + - stores:状态管理
22 + - utils:工具函数
23 + - app.config.js:项目配置
24 + - app.js:应用入口
25 + - app.less:全局样式
26 +- taro.config.js:Taro配置
27 +- tsconfig.json:TypeScript配置
28 +- package.json:依赖配置
29 +
30 +## 项目运行
31 +
32 +1. 安装依赖
33 +
34 +```bash
35 +npm install
36 +```
37 +
38 +2. 运行项目
39 +
40 +```bash
41 +npm run dev:weapp
42 +```
43 +
44 +3. 打包项目
45 +
46 +```bash
47 +npm run build:weapp
48 +```
......
1 +// babel-preset-taro 更多选项和默认值:
2 +// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
3 +module.exports = {
4 + presets: [
5 + ['taro', {
6 + framework: 'vue3',
7 + ts: false,
8 + compiler: 'webpack5',
9 + }]
10 + ]
11 +}
1 +/* eslint-disable */
2 +/* prettier-ignore */
3 +// @ts-nocheck
4 +// Generated by unplugin-vue-components
5 +// Read more: https://github.com/vuejs/core/pull/3399
6 +export {}
7 +
8 +declare module 'vue' {
9 + export interface GlobalComponents {
10 + NavBar: typeof import('./src/components/navBar.vue')['default']
11 + NutButton: typeof import('@nutui/nutui-taro')['Button']
12 + NutToast: typeof import('@nutui/nutui-taro')['Toast']
13 + Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
14 + PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
15 + RouterLink: typeof import('vue-router')['RouterLink']
16 + RouterView: typeof import('vue-router')['RouterView']
17 + }
18 +}
1 +/*
2 + * @Date: 2025-06-28 10:33:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-28 10:45:27
5 + * @FilePath: /myApp/config/dev.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + env: {
10 + NODE_ENV: '"development"'
11 + },
12 + logger: {
13 + quiet: false,
14 + stats: true
15 + },
16 + mini: {},
17 + h5: {}
18 +}
1 +import { defineConfig } from '@tarojs/cli'
2 +
3 +import devConfig from './dev'
4 +import prodConfig from './prod'
5 +import NutUIResolver from '@nutui/auto-import-resolver'
6 +import Components from 'unplugin-vue-components/webpack'
7 +
8 +const path = require('path')
9 +const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack')
10 +
11 +// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
12 +export default defineConfig(async (merge) => {
13 + const baseConfig = {
14 + projectName: 'myApp',
15 + date: '2025-6-28',
16 + designWidth (input) {
17 + // 配置 NutUI 375 尺寸
18 + if (input?.file?.replace(/\\+/g, '/').indexOf('@nutui') > -1) {
19 + return 375
20 + }
21 + // 全局使用 Taro 默认的 750 尺寸
22 + return 750
23 + },
24 + deviceRatio: {
25 + 640: 2.34 / 2,
26 + 750: 1,
27 + 375: 2,
28 + 828: 1.81 / 2
29 + },
30 + alias: { // 配置目录别名
31 + "@/utils": path.resolve(__dirname, "../src/utils"),
32 + "@/components": path.resolve(__dirname, "../src/components"),
33 + "@/images": path.resolve(__dirname, "../src/assets/images"),
34 + "@/assets": path.resolve(__dirname, "../src/assets"),
35 + "@/composables": path.resolve(__dirname, "../src/composables"),
36 + "@/api": path.resolve(__dirname, "../src/api"),
37 + "@/stores": path.resolve(__dirname, "../src/stores"),
38 + "@/hooks": path.resolve(__dirname, "../src/hooks"),
39 + },
40 + sourceRoot: 'src',
41 + outputRoot: 'dist',
42 + plugins: ['@tarojs/plugin-html', 'taro-plugin-pinia',],
43 + defineConstants: {
44 + },
45 + copy: {
46 + patterns: [
47 + ],
48 + options: {
49 + }
50 + },
51 + framework: 'vue3',
52 + compiler: {
53 + type: 'webpack5',
54 + prebundle: {
55 + enable: false
56 + }
57 + },
58 + cache: {
59 + enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
60 + },
61 + sass:{
62 + data: `@import "@nutui/nutui-taro/dist/styles/variables.scss";`
63 + },
64 + mini: {
65 + miniCssExtractPluginOption: {
66 + ignoreOrder: true
67 + },
68 + postcss: {
69 + pxtransform: {
70 + enable: true,
71 + config: {
72 +
73 + }
74 + },
75 + // url: {
76 + // enable: true,
77 + // config: {
78 + // limit: 1024 // 设定转换尺寸上限
79 + // }
80 + // },
81 + cssModules: {
82 + enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
83 + config: {
84 + namingPattern: 'module', // 转换模式,取值为 global/module
85 + generateScopedName: '[name]__[local]___[hash:base64:5]'
86 + }
87 + }
88 + },
89 + webpackChain(chain) {
90 +
91 + chain.plugin('unplugin-vue-components').use(Components({
92 + resolvers: [NutUIResolver({taro: true})]
93 + }))
94 +
95 + chain.merge({
96 + plugin: {
97 + install: {
98 + plugin: UnifiedWebpackPluginV5,
99 + args: [{
100 + appType: 'taro',
101 + // 下面个配置,会开启 rem -> rpx 的转化
102 + rem2rpx: true,
103 + injectAdditionalCssVarScope: true
104 + }]
105 + }
106 + }
107 + })
108 + }
109 + },
110 + h5: {
111 + publicPath: '/',
112 + staticDirectory: 'static',
113 + // esnextModules: ['nutui-taro', 'icons-vue-taro'],
114 + output: {
115 + filename: 'js/[name].[hash:8].js',
116 + chunkFilename: 'js/[name].[chunkhash:8].js'
117 + },
118 + miniCssExtractPluginOption: {
119 + ignoreOrder: true,
120 + filename: 'css/[name].[hash].css',
121 + chunkFilename: 'css/[name].[chunkhash].css'
122 + },
123 + postcss: {
124 + autoprefixer: {
125 + enable: true,
126 + config: {}
127 + },
128 + cssModules: {
129 + enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
130 + config: {
131 + namingPattern: 'module', // 转换模式,取值为 global/module
132 + generateScopedName: '[name]__[local]___[hash:base64:5]'
133 + }
134 + }
135 + },
136 + webpackChain(chain) {
137 +
138 + chain.plugin('unplugin-vue-components').use(Components({
139 + resolvers: [NutUIResolver({taro: true})]
140 + }))
141 + }
142 + },
143 + rn: {
144 + appName: 'taroDemo',
145 + postcss: {
146 + cssModules: {
147 + enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
148 + }
149 + }
150 + }
151 + }
152 + if (process.env.NODE_ENV === 'development') {
153 + // 本地开发构建配置(不混淆压缩)
154 + return merge({}, baseConfig, devConfig)
155 + }
156 + // 生产构建配置(默认开启压缩混淆等)
157 + return merge({}, baseConfig, prodConfig)
158 +})
1 +/*
2 + * @Date: 2025-06-28 10:33:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-28 11:00:57
5 + * @FilePath: /myApp/config/prod.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + env: {
10 + NODE_ENV: '"production"'
11 + },
12 + mini: {},
13 + h5: {
14 + /**
15 + * WebpackChain 插件配置
16 + * @docs https://github.com/neutrinojs/webpack-chain
17 + */
18 + // webpackChain (chain) {
19 + // /**
20 + // * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
21 + // * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
22 + // */
23 + // chain.plugin('analyzer')
24 + // .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
25 + // /**
26 + // * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
27 + // * @docs https://github.com/chrisvfritz/prerender-spa-plugin
28 + // */
29 + // const path = require('path')
30 + // const Prerender = require('prerender-spa-plugin')
31 + // const staticDir = path.join(__dirname, '..', 'dist')
32 + // chain
33 + // .plugin('prerender')
34 + // .use(new Prerender({
35 + // staticDir,
36 + // routes: [ '/pages/index/index' ],
37 + // postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
38 + // }))
39 + // }
40 + }
41 +}
1 +{
2 + "name": "myApp",
3 + "version": "1.0.0",
4 + "private": true,
5 + "description": "myApp",
6 + "templateInfo": {
7 + "name": "vue3-NutUI",
8 + "typescript": false,
9 + "css": "Less",
10 + "framework": "Vue3"
11 + },
12 + "scripts": {
13 + "build:weapp": "taro build --type weapp",
14 + "build:swan": "taro build --type swan",
15 + "build:alipay": "taro build --type alipay",
16 + "build:tt": "taro build --type tt",
17 + "build:h5": "taro build --type h5",
18 + "build:rn": "taro build --type rn",
19 + "build:qq": "taro build --type qq",
20 + "build:quickapp": "taro build --type quickapp",
21 + "dev:weapp": "npm run build:weapp -- --watch",
22 + "dev:swan": "npm run build:swan -- --watch",
23 + "dev:alipay": "npm run build:alipay -- --watch",
24 + "dev:tt": "npm run build:tt -- --watch",
25 + "dev:h5": "npm run build:h5 -- --watch",
26 + "dev:rn": "npm run build:rn -- --watch",
27 + "dev:qq": "npm run build:qq -- --watch",
28 + "dev:quickapp": "npm run build:quickapp -- --watch",
29 + "postinstall": "weapp-tw patch"
30 + },
31 + "browserslist": [
32 + "last 3 versions",
33 + "Android >= 4.1",
34 + "ios >= 8"
35 + ],
36 + "author": "",
37 + "license": "MIT",
38 + "dependencies": {
39 + "@babel/runtime": "^7.7.7",
40 + "@nutui/icons-vue-taro": "^0.0.9",
41 + "@nutui/nutui-taro": "^4.3.13",
42 + "@tarojs/components": "4.1.2",
43 + "@tarojs/helper": "4.1.2",
44 + "@tarojs/plugin-framework-vue3": "4.1.2",
45 + "@tarojs/plugin-html": "4.1.2",
46 + "@tarojs/plugin-platform-alipay": "4.1.2",
47 + "@tarojs/plugin-platform-h5": "4.1.2",
48 + "@tarojs/plugin-platform-jd": "4.1.2",
49 + "@tarojs/plugin-platform-qq": "4.1.2",
50 + "@tarojs/plugin-platform-swan": "4.1.2",
51 + "@tarojs/plugin-platform-tt": "4.1.2",
52 + "@tarojs/plugin-platform-weapp": "4.1.2",
53 + "@tarojs/runtime": "4.1.2",
54 + "@tarojs/shared": "4.1.2",
55 + "@tarojs/taro": "4.1.2",
56 + "axios-miniprogram": "^2.7.2",
57 + "pinia": "^3.0.3",
58 + "taro-plugin-pinia": "^1.0.0",
59 + "vue": "^3.3.0"
60 + },
61 + "devDependencies": {
62 + "@babel/core": "^7.8.0",
63 + "@nutui/auto-import-resolver": "^1.0.0",
64 + "@tarojs/cli": "4.1.2",
65 + "@tarojs/taro-loader": "4.1.2",
66 + "@tarojs/webpack5-runner": "4.1.2",
67 + "@types/webpack-env": "^1.13.6",
68 + "@vue/babel-plugin-jsx": "^1.0.6",
69 + "@vue/compiler-sfc": "^3.0.0",
70 + "autoprefixer": "^10.4.21",
71 + "babel-preset-taro": "4.1.2",
72 + "css-loader": "3.4.2",
73 + "eslint": "^8.12.0",
74 + "eslint-config-taro": "4.1.2",
75 + "postcss": "^8.5.6",
76 + "style-loader": "1.3.0",
77 + "tailwindcss": "^3.4.0",
78 + "unplugin-vue-components": "^0.26.0",
79 + "vue-loader": "^17.0.0",
80 + "weapp-tailwindcss": "^4.1.10",
81 + "webpack": "5.78.0"
82 + }
83 +}
1 +/*
2 + * @Date: 2025-06-30 13:27:35
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-30 13:27:42
5 + * @FilePath: /myApp/postcss.config.js
6 + * @Description: 文件描述
7 + */
8 +// postcss 插件以 object 方式注册的话,是按照由上到下的顺序执行的
9 +module.exports = {
10 + plugins: {
11 + tailwindcss: {},
12 + autoprefixer: {},
13 + },
14 +}
1 +{
2 + "miniprogramRoot": "./dist",
3 + "projectname": "myApp",
4 + "description": "myApp",
5 + "appid": "touristappid",
6 + "setting": {
7 + "urlCheck": true,
8 + "es6": false,
9 + "enhance": false,
10 + "compileHotReLoad": false,
11 + "postcss": false,
12 + "minified": false
13 + },
14 + "compileType": "miniprogram"
15 +}
1 +{
2 + "miniprogramRoot": "./",
3 + "projectname": "myApp",
4 + "description": "myApp",
5 + "appid": "touristappid",
6 + "setting": {
7 + "urlCheck": true,
8 + "es6": false,
9 + "postcss": false,
10 + "minified": false
11 + },
12 + "compileType": "miniprogram"
13 +}
1 +/*
2 + * @Date: 2022-06-17 14:54:29
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-06-18 22:18:46
5 + * @FilePath: /tswj/src/api/common.js
6 + * @Description: 通用接口
7 + */
8 +import { fn, fetch, uploadFn } from '@/api/fn';
9 +
10 +const Api = {
11 + SMS: '/srv/?a=sms',
12 + TOKEN: '/srv/?a=upload',
13 + SAVE_FILE: '/srv/?a=upload&t=save_file',
14 +}
15 +
16 +/**
17 + * @description: 发送验证码
18 + * @param {*} phone 手机号码
19 + * @returns
20 + */
21 +export const smsAPI = (params) => fn(fetch.post(Api.SMS, params));
22 +
23 +/**
24 + * @description: 获取七牛token
25 + * @param {*} filename 文件名
26 + * @param {*} file 图片base64
27 + * @returns
28 + */
29 +export const qiniuTokenAPI = (params) => fn(fetch.stringifyPost(Api.TOKEN, params));
30 +
31 +/**
32 + * @description: 上传七牛
33 + * @param {*}
34 + * @returns
35 + */
36 +export const qiniuUploadAPI = (url, data, config) => uploadFn(fetch.basePost(url, data, config));
37 +
38 +/**
39 + * @description: 保存图片
40 + * @param {*} format
41 + * @param {*} hash
42 + * @param {*} height
43 + * @param {*} width
44 + * @param {*} filekey
45 + * @returns
46 + */
47 +export const saveFileAPI = (params) => fn(fetch.stringifyPost(Api.SAVE_FILE, params));
1 +/*
2 + * @Date: 2022-05-18 22:56:08
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-05-25 22:35:00
5 + * @FilePath: /meihuaApp/src/api/fn.js
6 + * @Description: 文件描述
7 + */
8 +import axios from '@/utils/request';
9 +import Taro from '@tarojs/taro'
10 +// import qs from 'qs'
11 +
12 +/**
13 + * 网络请求功能函数
14 + * @param {*} api 请求axios接口
15 + * @returns 请求成功后,获取数据
16 + */
17 +export const fn = (api) => {
18 + return api
19 + .then(res => {
20 + if (res.data.code) {
21 + return res.data || true;
22 + } else {
23 + // tslint:disable-next-line: no-console
24 + console.warn(res);
25 + Taro.showToast({
26 + title: res.data.msg,
27 + icon: 'none',
28 + duration: 2000
29 + });
30 + return false;
31 + }
32 + })
33 + .catch(err => {
34 + // tslint:disable-next-line: no-console
35 + console.error(err);
36 + return false;
37 + })
38 + .finally(() => { // 最终执行
39 + })
40 +}
41 +
42 +/**
43 + * 七牛返回格式
44 + * @param {*} api
45 + * @returns
46 + */
47 +export const uploadFn = (api) => {
48 + return api
49 + .then(res => {
50 + if (res.statusText === 'OK') {
51 + return res.data || true;
52 + } else {
53 + // tslint:disable-next-line: no-console
54 + console.warn(res);
55 + Taro.showToast({
56 + title: res.data.msg,
57 + icon: 'none',
58 + duration: 2000
59 + });
60 + return false;
61 + }
62 + })
63 + .catch(err => {
64 + // tslint:disable-next-line: no-console
65 + console.error(err);
66 + return false;
67 + })
68 +}
69 +
70 +/**
71 + * 统一 GET/POST 不同传参形式
72 + */
73 +export const fetch = {
74 + get: function (api, params) {
75 + return axios.get(api, params)
76 + },
77 + post: function (api, params) {
78 + return axios.post(api, params)
79 + },
80 + // stringifyPost: function (api, params) {
81 + // return axios.post(api, qs.stringify(params))
82 + // },
83 + basePost: function (url, data, config) {
84 + return axios.post(url, data, config)
85 + }
86 +}
1 +/*
2 + * @Date: 2023-12-22 10:29:37
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-06-06 09:31:34
5 + * @FilePath: /meihuaApp/src/api/index.js
6 + * @Description: 文件描述
7 + */
8 +import { fn, fetch } from './fn';
9 +
10 +const Api = {
11 + BIND_PHONE: '/srv/?a=room_order&t=bind_phone',
12 + SEND_SMS_CODE: '/srv/?a=room_order&t=send_sms_code',
13 + SHOW_SESSION: '/srv/?a=room_order&t=show_session',
14 + SAVE_CUSTOMER_INFO: '/srv/?a=room_order&t=save_customer_info',
15 + SYS_PARAM: '/srv/?a=room_order&t=sys_param',
16 + GET_LIST: '/srv/?a=room_data&t=get_list',
17 + GET_ROOM: '/srv/?a=room_data&t=get_room',
18 + ADD_ORDER: '/srv/?a=room_data&t=add_order',
19 + MY_ORDER: '/srv/?a=room_data&t=my_order',
20 + ORDER_CANCEL: '/srv/?a=room_data&t=order_cancel',
21 + PAY: '/srv/?a=pay',
22 + PAY_CHECK: '/srv/?a=pay_check',
23 + ORDER_SUCCESS: '/srv/?a=room_data&t=order_success',
24 + TMP_SYS_PARAM: '/srv/?a=get_item',
25 +}
26 +
27 +/**
28 + * @description: 绑定手机号(手机号登录)
29 + * @param phone 手机号
30 + * @param sms_code 验证码
31 + * @returns
32 + */
33 +export const bindPhoneAPI = (params) => fn(fetch.post(Api.BIND_PHONE, params));
34 +
35 +/**
36 + * @description: 发送验证码
37 + * @param phone 手机号
38 + * @returns
39 + */
40 +export const sendSmsCodeAPI = (params) => fn(fetch.post(Api.SEND_SMS_CODE, params));
41 +
42 +/**
43 + * @description: 获取我的信息
44 + * @returns
45 + */
46 +export const showMyInfoAPI = (params) => fn(fetch.get(Api.SHOW_SESSION, params));
47 +
48 +/**
49 + * @description: 保存我的信息
50 + * @param params
51 + * @returns
52 + */
53 +export const saveCustomerInfoAPI = (params) => fn(fetch.post(Api.SAVE_CUSTOMER_INFO, params));
54 +
55 +/**
56 + * @description: 获取系统参数
57 + * @returns
58 + */
59 +export const sysParamAPI = (params) => fn(fetch.get(Api.SYS_PARAM, params));
60 +
61 +/**
62 + * @description: 获取房间列表
63 + * @param start_date 入住时间
64 + * @param end_date 离店时间
65 + * @param offset 偏移量
66 + * @param limit 条数
67 + * @returns
68 + */
69 +export const getListAPI = (params) => fn(fetch.get(Api.GET_LIST, params));
70 +
71 +/**
72 + * @description: 获取房间详情
73 + * @param start_date 入住时间
74 + * @param end_date 离店时间
75 + * @param room_type floor/room
76 + * @returns
77 + */
78 +export const getRoomAPI = (params) => fn(fetch.get(Api.GET_ROOM, params));
79 +
80 +/**
81 + * @description: 预定房间
82 + * @param id ID
83 + * @param num 预定房间数量
84 + * @param plan_in 入住时间
85 + * @param plan_out 离店时间
86 + * @param contact_name 联系人
87 + * @param contact_phone 联系电话
88 + * @param order_remark 备注
89 + * @param room_type floor/room
90 + * @returns
91 + */
92 +export const addOrderAPI = (params) => fn(fetch.post(Api.ADD_ORDER, params));
93 +
94 +/**
95 + * @description: 支付
96 + * @param order_id 订单ID
97 + * @returns
98 + */
99 +export const payAPI = (params) => fn(fetch.post(Api.PAY, params));
100 +
101 +/**
102 + * @description: 检查是否支付成功
103 + * @param order_id 订单ID
104 + * @returns
105 + */
106 +export const payCheckAPI = (params) => fn(fetch.post(Api.PAY_CHECK, params));
107 +
108 +/**
109 + * @description: 获取我的订单列表
110 + * @param pay_type
111 + * @param page
112 + * @param limit
113 + * @returns
114 + */
115 +export const myOrderAPI = (params) => fn(fetch.get(Api.MY_ORDER, params));
116 +
117 +/**
118 + * @description: 取消订单
119 + * @param id
120 + * @returns
121 + */
122 +export const orderCancelAPI = (params) => fn(fetch.post(Api.ORDER_CANCEL, params));
123 +
124 +/**
125 + * @description: 订单成功
126 + * @param id
127 + * @returns
128 + */
129 +export const orderSuccessAPI = (params) => fn(fetch.post(Api.ORDER_SUCCESS, params));
130 +
131 +/**
132 + * @description:
133 + * @param id
134 + * @returns
135 + */
136 +export const tmpSysParamAPI = (params) => fn(fetch.get(Api.TMP_SYS_PARAM, params));
1 +/*
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2022-06-09 13:32:44
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2022-06-14 14:47:01
6 + * @FilePath: /tswj/src/api/wx/config.js
7 + * @Description:
8 + */
9 +import { fn, fetch } from '@/api/fn';
10 +
11 +const Api = {
12 + WX_JSAPI: '/srv/?a=wx_share',
13 +}
14 +
15 +/**
16 + * @description 获取微信CONFIG配置文件
17 + * @param {*} url
18 + * @returns {*} cfg
19 + */
20 +export const wxJsAPI = (params) => fn(fetch.get(Api.WX_JSAPI, params));
1 +/*
2 + * @Date: 2022-06-13 14:18:57
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-06-13 14:27:21
5 + * @FilePath: /tswj/src/api/wx/jsApiList.js
6 + * @Description: 文件描述
7 + */
8 +export const apiList = [
9 + "updateAppMessageShareData",
10 + "updateTimelineShareData",
11 + "onMenuShareTimeline",
12 + "onMenuShareAppMessage",
13 + "onMenuShareQQ",
14 + "onMenuShareWeibo",
15 + "onMenuShareQZone",
16 + "startRecord",
17 + "stopRecord",
18 + "onVoiceRecordEnd",
19 + "playVoice",
20 + "pauseVoice",
21 + "stopVoice",
22 + "onVoicePlayEnd",
23 + "uploadVoice",
24 + "downloadVoice",
25 + "chooseImage",
26 + "previewImage",
27 + "uploadImage",
28 + "downloadImage",
29 + "translateVoice",
30 + "getNetworkType",
31 + "openLocation",
32 + "getLocation",
33 + "hideOptionMenu",
34 + "showOptionMenu",
35 + "hideMenuItems",
36 + "showMenuItems",
37 + "hideAllNonBaseMenuItem",
38 + "showAllNonBaseMenuItem",
39 + "closeWindow",
40 + "scanQRCode",
41 + "chooseWXPay",
42 + "openProductSpecificView",
43 + "addCard",
44 + "chooseCard",
45 + "openCard"
46 +]
1 +/*
2 + * @Author: hookehuyr hookehuyr@gmail.com
3 + * @Date: 2022-06-09 13:32:44
4 + * @LastEditors: hookehuyr hookehuyr@gmail.com
5 + * @LastEditTime: 2022-06-09 13:42:06
6 + * @FilePath: /tswj/src/api/wx/config.js
7 + * @Description:
8 + */
9 +import { fn, fetch } from '@/api/fn';
10 +
11 +const Api = {
12 + WX_PAY: 'c/bill_paymentForBill.do',
13 +}
14 +
15 +/**
16 + * @description 微信支付接口
17 + * @param {*}
18 + * @returns {*}
19 + */
20 +export const wxPayAPI = (params) => fn(fetch.get(Api.WX_PAY, params));
1 +/*
2 + * @Date: 2025-06-28 10:33:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-28 11:05:47
5 + * @FilePath: /myApp/src/app.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + pages: [
10 + 'pages/index/index',
11 + 'pages/auth/index',
12 + ],
13 + subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
14 + {
15 + root: 'pages/demo',
16 + pages: ['index'],
17 + },
18 + ],
19 + window: {
20 + backgroundTextStyle: 'light',
21 + navigationBarBackgroundColor: '#fff',
22 + navigationBarTitleText: 'WeChat',
23 + navigationBarTextStyle: 'black'
24 + }
25 +}
1 +/*
2 + * @Date: 2025-06-28 10:33:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-28 11:04:17
5 + * @FilePath: /myApp/src/app.js
6 + * @Description: 文件描述
7 + */
8 +import { createApp } from 'vue'
9 +import { createPinia } from 'pinia'
10 +import './app.less'
11 +import { routerStore } from '@/stores/router'
12 +import Taro from '@tarojs/taro'
13 +
14 +const App = createApp({
15 + // 对应 onLaunch
16 + onLaunch(options) {
17 + // 未授权状态跳转授权页面,首页不需要权限
18 + const path = options.path;
19 + const query = options.query;
20 + // 缓存没有权限的地址
21 + const router = routerStore();
22 + router.add(path);
23 + // if (path !== 'pages/index/index' && !wx.getStorageSync("sessionid")) {
24 + if (!wx.getStorageSync("sessionid")) {
25 + console.warn("没有权限");
26 + // if (path === 'pages/detail/index') {
27 + // Taro.navigateTo({
28 + // url: `./pages/auth/index?url=${path}&id=${query.id}&start_date=${query.start_date}&end_date=${query.end_date}`,
29 + // })
30 + // } else {
31 + // Taro.navigateTo({
32 + // url: './pages/auth/index?url=' + path,
33 + // })
34 + // }
35 + }
36 + },
37 + onShow(options) {
38 + },
39 + // 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
40 +});
41 +
42 +App.use(createPinia())
43 +
44 +export default App
1 +@tailwind base;
2 +@tailwind components;
3 +@tailwind utilities;
1 +@namespace: 'meihua';
2 +
3 +/* ============ 颜色 ============ */
4 +
5 +// 主色调
6 +@base-color: #199A74;
7 +
8 +// 文字颜色
9 +@base-font-color: #333333;
10 +@sub-font-color: #999999;
11 +
12 +// 定义一个映射
13 +#colors() {
14 + base-color: @base-color;
15 + base-font-color: @base-font-color;
16 +}
17 +
18 +// 混合
19 +.width100 {
20 + width: 100%;
21 +}
1 +<template>
2 + <canvas
3 + type="2d"
4 + :id="canvasId"
5 + :style="`height: ${height}rpx; width:${width}rpx;
6 + position: absolute;
7 + ${debug ? '' : 'transform:translate3d(-9999rpx, 0, 0)'}`"
8 + />
9 +</template>
10 +<script lang="ts">
11 +import Taro from "@tarojs/taro"
12 +import { defineComponent, onMounted, PropType, ref } from "vue"
13 +import { Image, DrawConfig } from "./types"
14 +import { drawImage, drawText, drawBlock, drawLine } from "./utils/draw"
15 +import {
16 + toPx,
17 + toRpx,
18 + getRandomId,
19 + getImageInfo,
20 + getLinearColor,
21 +} from "./utils/tools"
22 +
23 +export default defineComponent({
24 + name: "PosterBuilder",
25 + props: {
26 + showLoading: {
27 + type: Boolean,
28 + default: false,
29 + },
30 + config: {
31 + type: Object as PropType<DrawConfig>,
32 + default: () => ({}),
33 + },
34 + },
35 + emits: ["success", "fail"],
36 + setup(props, context) {
37 + const count = ref(1)
38 + const {
39 + width,
40 + height,
41 + backgroundColor,
42 + texts = [],
43 + blocks = [],
44 + lines = [],
45 + debug = false,
46 + } = props.config || {}
47 +
48 + const canvasId = getRandomId()
49 +
50 + /**
51 + * step1: 初始化图片资源
52 + * @param {Array} images = imgTask
53 + * @return {Promise} downloadImagePromise
54 + */
55 + const initImages = (images: Image[]) => {
56 + const imagesTemp = images.filter((item) => item.url)
57 + const drawList = imagesTemp.map((item, index) =>
58 + getImageInfo(item, index)
59 + )
60 + return Promise.all(drawList)
61 + }
62 +
63 + /**
64 + * step2: 初始化 canvas && 获取其 dom 节点和实例
65 + * @return {Promise} resolve 里返回其 dom 和实例
66 + */
67 + const initCanvas = () =>
68 + new Promise<any>((resolve) => {
69 + setTimeout(() => {
70 + const pageInstance = Taro.getCurrentInstance()?.page || {} // 拿到当前页面实例
71 + const query = Taro.createSelectorQuery().in(pageInstance) // 确定在当前页面内匹配子元素
72 + query
73 + .select(`#${canvasId}`)
74 + .fields({ node: true, size: true, context: true }, (res) => {
75 + const canvas = res.node
76 + const ctx = canvas.getContext("2d")
77 + resolve({ ctx, canvas })
78 + })
79 + .exec()
80 + }, 300)
81 + })
82 +
83 + /**
84 + * @description 保存绘制的图片
85 + * @param { object } config
86 + */
87 + const getTempFile = (canvas) => {
88 + Taro.canvasToTempFilePath(
89 + {
90 + canvas,
91 + success: (result) => {
92 + Taro.hideLoading()
93 + context.emit("success", result)
94 + },
95 + fail: (error) => {
96 + const { errMsg } = error
97 + if (errMsg === "canvasToTempFilePath:fail:create bitmap failed") {
98 + count.value += 1
99 + if (count.value <= 3) {
100 + getTempFile(canvas)
101 + } else {
102 + Taro.hideLoading()
103 + Taro.showToast({
104 + icon: "none",
105 + title: errMsg || "绘制海报失败",
106 + })
107 + context.emit("fail", errMsg)
108 + }
109 + }
110 + },
111 + },
112 + context
113 + )
114 + }
115 +
116 + /**
117 + * step2: 开始绘制任务
118 + * @param { Array } drawTasks 待绘制任务
119 + */
120 + const startDrawing = async (drawTasks) => {
121 + // TODO: check
122 + // const configHeight = getHeight(config)
123 + const { ctx, canvas } = await initCanvas()
124 +
125 + canvas.width = width
126 + canvas.height = height
127 +
128 + // 设置画布底色
129 + if (backgroundColor) {
130 + ctx.save() // 保存绘图上下文
131 + const grd = getLinearColor(ctx, backgroundColor, 0, 0, width, height)
132 + ctx.fillStyle = grd // 设置填充颜色
133 + ctx.fillRect(0, 0, width, height) // 填充一个矩形
134 + ctx.restore() // 恢复之前保存的绘图上下文
135 + }
136 + // 将要画的方块、文字、线条放进队列数组
137 + const queue = drawTasks
138 + .concat(
139 + texts.map((item) => {
140 + item.type = "text"
141 + item.zIndex = item.zIndex || 0
142 + return item
143 + })
144 + )
145 + .concat(
146 + blocks.map((item) => {
147 + item.type = "block"
148 + item.zIndex = item.zIndex || 0
149 + return item
150 + })
151 + )
152 + .concat(
153 + lines.map((item) => {
154 + item.type = "line"
155 + item.zIndex = item.zIndex || 0
156 + return item
157 + })
158 + )
159 +
160 + queue.sort((a, b) => a.zIndex - b.zIndex) // 按照层叠顺序由低至高排序, 先画低的,再画高的
161 + for (let i = 0; i < queue.length; i++) {
162 + const drawOptions = {
163 + canvas,
164 + ctx,
165 + toPx,
166 + toRpx,
167 + }
168 + if (queue[i].type === "image") {
169 + await drawImage(queue[i], drawOptions)
170 + } else if (queue[i].type === "text") {
171 + drawText(queue[i], drawOptions)
172 + } else if (queue[i].type === "block") {
173 + drawBlock(queue[i], drawOptions)
174 + } else if (queue[i].type === "line") {
175 + drawLine(queue[i], drawOptions)
176 + }
177 + }
178 +
179 + setTimeout(() => {
180 + getTempFile(canvas) // 需要做延时才能能正常加载图片
181 + }, 300)
182 + }
183 +
184 + // start: 初始化 canvas 实例 && 下载图片资源
185 + const init = () => {
186 + if (props.showLoading)
187 + Taro.showLoading({ mask: true, title: "生成中..." })
188 + if (props.config?.images?.length) {
189 + initImages(props.config.images)
190 + .then((result) => {
191 + // 1. 下载图片资源
192 + startDrawing(result)
193 + })
194 + .catch((err) => {
195 + Taro.hideLoading()
196 + Taro.showToast({
197 + icon: "none",
198 + title: err.errMsg || "下载图片失败",
199 + })
200 + context.emit("fail", err)
201 + })
202 + } else {
203 + startDrawing([])
204 + }
205 + }
206 +
207 + onMounted(() => {
208 + init()
209 + })
210 +
211 + return {
212 + canvasId,
213 + debug,
214 + width,
215 + height,
216 + }
217 + },
218 +})
219 +</script>
1 +export type DrawType = 'text' | 'image' | 'block' | 'line';
2 +
3 +export interface Block {
4 + type?: DrawType;
5 + x: number;
6 + y: number;
7 + width?: number;
8 + height: number;
9 + paddingLeft?: number;
10 + paddingRight?: number;
11 + borderWidth?: number;
12 + borderColor?: string;
13 + backgroundColor?: string;
14 + borderRadius?: number;
15 + borderRadiusGroup?: number[];
16 + text?: Text;
17 + opacity?: number;
18 + zIndex?: number;
19 +}
20 +
21 +export interface Text {
22 + type?: DrawType;
23 + x?: number;
24 + y?: number;
25 + text: string | Text[];
26 + fontSize?: number;
27 + color?: string;
28 + opacity?: 1 | 0;
29 + lineHeight?: number;
30 + lineNum?: number;
31 + width?: number;
32 + marginTop?: number;
33 + marginLeft?: number;
34 + marginRight?: number;
35 + textDecoration?: 'line-through' | 'none';
36 + baseLine?: 'top' | 'middle' | 'bottom';
37 + textAlign?: 'left' | 'center' | 'right';
38 + fontFamily?: string;
39 + fontWeight?: string;
40 + fontStyle?: string;
41 + zIndex?: number;
42 +}
43 +
44 +export interface Image {
45 + type?: DrawType;
46 + x: number;
47 + y: number;
48 + url: string;
49 + width: number;
50 + height: number;
51 + borderRadius?: number;
52 + borderRadiusGroup?: number[];
53 + borderWidth?: number;
54 + borderColor?: string;
55 + zIndex?: number;
56 +}
57 +
58 +export interface Line {
59 + type?: DrawType;
60 + startX: number;
61 + startY: number;
62 + endX: number;
63 + endY: number;
64 + width: number;
65 + color?: string;
66 + zIndex?: number;
67 +}
68 +
69 +export type DrawConfig = {
70 + width: number;
71 + height: number;
72 + backgroundColor?: string;
73 + debug?: boolean;
74 + blocks?: Block[];
75 + texts?: Text[];
76 + images?: Image[];
77 + lines?: Line[];
78 +};
This diff is collapsed. Click to expand it.
1 +/* eslint-disable prefer-destructuring */
2 +import Taro, { CanvasContext, CanvasGradient } from '@tarojs/taro';
3 +
4 +declare const wx: any;
5 +
6 +/**
7 + * @description 生成随机字符串
8 + * @param { number } length - 字符串长度
9 + * @returns { string }
10 + */
11 +export function randomString(length) {
12 + let str = Math.random().toString(36).substr(2);
13 + if (str.length >= length) {
14 + return str.substr(0, length);
15 + }
16 + str += randomString(length - str.length);
17 + return str;
18 +}
19 +
20 +/**
21 + * 随机创造一个id
22 + * @param { number } length - 字符串长度
23 + * @returns { string }
24 + */
25 +export function getRandomId(prefix = 'canvas', length = 10) {
26 + return prefix + randomString(length);
27 +}
28 +
29 +/**
30 + * @description 获取最大高度
31 + * @param {} config
32 + * @returns { number }
33 + */
34 +// export function getHeight (config) {
35 +// const getTextHeight = text => {
36 +// const fontHeight = text.lineHeight || text.fontSize
37 +// let height = 0
38 +// if (text.baseLine === 'top') {
39 +// height = fontHeight
40 +// } else if (text.baseLine === 'middle') {
41 +// height = fontHeight / 2
42 +// } else {
43 +// height = 0
44 +// }
45 +// return height
46 +// }
47 +// const heightArr: number[] = [];
48 +// (config.blocks || []).forEach(item => {
49 +// heightArr.push(item.y + item.height)
50 +// });
51 +// (config.texts || []).forEach(item => {
52 +// let height
53 +// if (Object.prototype.toString.call(item.text) === '[object Array]') {
54 +// item.text.forEach(i => {
55 +// height = getTextHeight({ ...i, baseLine: item.baseLine })
56 +// heightArr.push(item.y + height)
57 +// })
58 +// } else {
59 +// height = getTextHeight(item)
60 +// heightArr.push(item.y + height)
61 +// }
62 +// });
63 +// (config.images || []).forEach(item => {
64 +// heightArr.push(item.y + item.height)
65 +// });
66 +// (config.lines || []).forEach(item => {
67 +// heightArr.push(item.startY)
68 +// heightArr.push(item.endY)
69 +// })
70 +// const sortRes = heightArr.sort((a, b) => b - a)
71 +// let canvasHeight = 0
72 +// if (sortRes.length > 0) {
73 +// canvasHeight = sortRes[0]
74 +// }
75 +// if (config.height < canvasHeight || !config.height) {
76 +// return canvasHeight
77 +// }
78 +// return config.height
79 +// }
80 +
81 +/**
82 + * 将http转为https
83 + * @param {String}} rawUrl 图片资源url
84 + * @returns { string }
85 + */
86 +export function mapHttpToHttps(rawUrl) {
87 + if (rawUrl.indexOf(':') < 0 || rawUrl.startsWith('http://tmp')) {
88 + return rawUrl;
89 + }
90 + const urlComponent = rawUrl.split(':');
91 + if (urlComponent.length === 2) {
92 + if (urlComponent[0] === 'http') {
93 + urlComponent[0] = 'https';
94 + return `${urlComponent[0]}:${urlComponent[1]}`;
95 + }
96 + }
97 + return rawUrl;
98 +}
99 +
100 +/**
101 + * 获取 rpx => px 的转换系数
102 + * @returns { number } factor 单位转换系数 1rpx = factor * px
103 + */
104 +export const getFactor = () => {
105 + const sysInfo = Taro.getSystemInfoSync();
106 + const { screenWidth } = sysInfo;
107 + return screenWidth / 750;
108 +};
109 +
110 +/**
111 + * rpx => px 单位转换
112 + * @param { number } rpx - 需要转换的数值
113 + * @param { number } factor - 转化因子
114 + * @returns { number }
115 + */
116 +export const toPx = (rpx, factor = getFactor()) =>
117 + parseInt(String(rpx * factor), 10);
118 +
119 +/**
120 + * px => rpx 单位转换
121 + * @param { number } px - 需要转换的数值
122 + * @param { number } factor - 转化因子
123 + * @returns { number }
124 + */
125 +export const toRpx = (px, factor = getFactor()) =>
126 + parseInt(String(px / factor), 10);
127 +
128 +/**
129 + * 下载图片资源
130 + * @param { string } url
131 + * @returns { Promise }
132 + */
133 +export function downImage(url) {
134 + return new Promise<string>((resolve, reject) => {
135 + // eslint-disable-next-line no-undef
136 + if (/^http/.test(url) && !new RegExp(wx.env.USER_DATA_PATH).test(url)) {
137 + // wx.env.USER_DATA_PATH 文件系统中的用户目录路径
138 + Taro.downloadFile({
139 + url: mapHttpToHttps(url),
140 + success: (res) => {
141 + if (res.statusCode === 200) {
142 + resolve(res.tempFilePath);
143 + } else {
144 + console.log('下载失败', res);
145 + reject(res);
146 + }
147 + },
148 + fail(err) {
149 + console.log('下载失败了', err);
150 + reject(err);
151 + }
152 + });
153 + } else {
154 + resolve(url); // 支持本地地址
155 + }
156 + });
157 +}
158 +
159 +/**
160 + * 下载图片并获取图片信息
161 + * @param {} item 图片参数信息
162 + * @param {} index 图片下标
163 + * @returns { Promise } result 整理后的图片信息
164 + */
165 +export const getImageInfo = (item, index) =>
166 + new Promise((resolve, reject) => {
167 + const { x, y, width, height, url, zIndex } = item;
168 + downImage(url).then((imgPath) =>
169 + Taro.getImageInfo({ src: imgPath })
170 + .then((imgInfo) => {
171 + // 获取图片信息
172 + // 根据画布的宽高计算出图片绘制的大小,这里会保证图片绘制不变形, 即宽高比不变,截取再拉伸
173 + let sx; // 截图的起点 x 坐标
174 + let sy; // 截图的起点 y 坐标
175 + const borderRadius = item.borderRadius || 0;
176 + const imgWidth = toRpx(imgInfo.width); // 图片真实宽度 单位 px
177 + const imgHeight = toRpx(imgInfo.height); // 图片真实高度 单位 px
178 + // 根据宽高比截取图片
179 + if (imgWidth / imgHeight <= width / height) {
180 + sx = 0;
181 + sy = (imgHeight - (imgWidth / width) * height) / 2;
182 + } else {
183 + sy = 0;
184 + sx = (imgWidth - (imgHeight / height) * width) / 2;
185 + }
186 + // 给 canvas 画图准备参数,详见 ./draw.ts-drawImage
187 + const result = {
188 + type: 'image',
189 + borderRadius,
190 + borderWidth: item.borderWidth,
191 + borderColor: item.borderColor,
192 + borderRadiusGroup: item.borderRadiusGroup,
193 + zIndex: typeof zIndex !== 'undefined' ? zIndex : index,
194 + imgPath: url,
195 + sx,
196 + sy,
197 + sw: imgWidth - sx * 2,
198 + sh: imgHeight - sy * 2,
199 + x,
200 + y,
201 + w: width,
202 + h: height
203 + };
204 + resolve(result);
205 + })
206 + .catch((err) => {
207 + console.log('读取图片信息失败', err);
208 + reject(err);
209 + })
210 + );
211 + });
212 +
213 +/**
214 + * 获取线性渐变色
215 + * @param {CanvasContext} ctx canvas 实例对象
216 + * @param {String} color 线性渐变色,如 'linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #fff 100%)'
217 + * @param {Number} startX 起点 x 坐标
218 + * @param {Number} startY 起点 y 坐标
219 + * @param {Number} w 宽度
220 + * @param {Number} h 高度
221 + * @returns {}
222 + */
223 +// TODO: 待优化, 支持所有角度,多个颜色的线性渐变
224 +export function getLinearColor(
225 + ctx: CanvasContext,
226 + color,
227 + startX,
228 + startY,
229 + w,
230 + h
231 +) {
232 + if (
233 + typeof startX !== 'number' ||
234 + typeof startY !== 'number' ||
235 + typeof w !== 'number' ||
236 + typeof h !== 'number'
237 + ) {
238 + console.warn('坐标或者宽高只支持数字');
239 + return color;
240 + }
241 + let grd: CanvasGradient | string = color;
242 + if (color.includes('linear-gradient')) {
243 + // fillStyle 不支持线性渐变色
244 + const colorList = color.match(/\((\d+)deg,\s(.+)\s\d+%,\s(.+)\s\d+%/);
245 + const radian = colorList[1]; // 渐变弧度(角度)
246 + const color1 = colorList[2];
247 + const color2 = colorList[3];
248 +
249 + const L = Math.sqrt(w * w + h * h);
250 + const x = Math.ceil(Math.sin(180 - radian) * L);
251 + const y = Math.ceil(Math.cos(180 - radian) * L);
252 +
253 + // 根据弧度和宽高确定渐变色的两个点的坐标
254 + if (Number(radian) === 180 || Number(radian) === 0) {
255 + if (Number(radian) === 180) {
256 + grd = ctx.createLinearGradient(startX, startY, startX, startY + h);
257 + }
258 + if (Number(radian) === 0) {
259 + grd = ctx.createLinearGradient(startX, startY + h, startX, startY);
260 + }
261 + } else if (radian > 0 && radian < 180) {
262 + grd = ctx.createLinearGradient(startX, startY, x + startX, y + startY);
263 + } else {
264 + throw new Error('只支持0 <= 颜色弧度 <= 180');
265 + }
266 + (grd as CanvasGradient).addColorStop(0, color1);
267 + (grd as CanvasGradient).addColorStop(1, color2);
268 + }
269 + return grd;
270 +}
271 +
272 +/**
273 + * 根据文字对齐方式设置坐标
274 + * @param {*} imgPath
275 + * @param {*} index
276 + * @returns { Promise }
277 + */
278 +export function getTextX(textAlign, x, width) {
279 + let newX = x;
280 + if (textAlign === 'center') {
281 + newX = width / 2 + x;
282 + } else if (textAlign === 'right') {
283 + newX = width + x;
284 + }
285 + return newX;
286 +}
1 +<!--
2 + * @Date: 2022-09-21 11:59:20
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-09-13 10:52:42
5 + * @FilePath: /meihuaApp/src/components/navBar.vue
6 + * @Description: 底部导航栏
7 +-->
8 +<template>
9 + <view id="navbar-page" class="navbar-page">
10 + <view @tap="goTo('index')" class="home">
11 + <view style="height: 1.5rem;">
12 + <IconFont :name="icon_home" size="1.5rem" color="" />
13 + </view>
14 + <view><text :style="homeStyle">首页</text></view>
15 + </view>
16 + <view @tap="goTo('book')" class="book">
17 + <view style="height: 1.5rem;">
18 + <IconFont :name="icon_book" size="1.5rem" color="" />
19 + </view>
20 + <view><text :style="bookStyle">订房</text></view>
21 + </view>
22 + <view @tap="goTo('serverInfo')" class="server">
23 + <view style="height: 1.5rem;">
24 + <IconFont :name="icon_server" size="1.5rem" color="" />
25 + </view>
26 + <view><text :style="serverStyle">服务</text></view>
27 + </view>
28 + <view @tap="goTo('my')" class="my">
29 + <view style="height: 1.5rem;">
30 + <IconFont :name="icon_my" size="1.5rem" color="" />
31 + </view>
32 + <view><text :style="myStyle">我的</text></view>
33 + </view>
34 + </view>
35 +</template>
36 +
37 +<script setup>
38 +import Taro from '@tarojs/taro'
39 +import { ref, defineProps, computed, onMounted } from 'vue'
40 +import icon_home1 from '@/images/icon/icon_home1@2x.png'
41 +import icon_home2 from '@/images/icon/icon_home2@2x.png'
42 +import icon_my1 from '@/images/icon/icon_my1@2x.png'
43 +import icon_my2 from '@/images/icon/icon_my2@2x.png'
44 +import icon_book1 from '@/images/icon/icon_book1@2x.png'
45 +import icon_book2 from '@/images/icon/icon_book2@2x.png'
46 +import icon_server1 from '@/images/icon/icon_server1.png'
47 +import icon_server2 from '@/images/icon/icon_server2.png'
48 +// import { hostListAPI } from '@/api/Host/index'
49 +import { IconFont } from '@nutui/icons-vue-taro';
50 +
51 +const goTo = (page) => {
52 + if (props.activated === page) {
53 + return;
54 + }
55 + wx.redirectTo({
56 + url: `../${page}/index`
57 + });
58 +}
59 +
60 +// const createActivity = async () => {
61 +// // 获取主办方列表信息
62 +// const { code, data } = await hostListAPI();
63 +// if (code) {
64 +// if (!data.my_hosts.length) { // 主办方为空
65 +// Taro.showModal({
66 +// title: '温馨提示',
67 +// content: '请先创建主办方后新建活动',
68 +// success: function (res) {
69 +// if (res.confirm) {
70 +// Taro.navigateTo({
71 +// url: '../createProject/index'
72 +// });
73 +// }
74 +// }
75 +// });
76 +// } else {
77 +// Taro.navigateTo({
78 +// url: '../createActivity/index'
79 +// })
80 +// }
81 +// }
82 +// }
83 +
84 +const currentPage = ref('');
85 +
86 +onMounted(() => {
87 + let pages = getCurrentPages();
88 + let current_page = pages[pages.length - 1];
89 + let url = current_page.route;
90 + if (url == 'pages/index/index') {
91 + currentPage.value = 'index'
92 + } else {
93 + currentPage.value = 'my'
94 + }
95 +})
96 +
97 +const props = defineProps({
98 + activated: String,
99 +})
100 +
101 +const homeStyle = ref({})
102 +const myStyle = ref({})
103 +const bookStyle = ref({})
104 +const serverStyle = ref({})
105 +
106 +const icon_home = computed(() => {
107 + if (props.activated === 'index') {
108 + return icon_home1
109 + } else {
110 + return icon_home2
111 + }
112 +})
113 +const icon_my = computed(() => {
114 + if (props.activated === 'my') {
115 + return icon_my1
116 + } else {
117 + return icon_my2
118 + }
119 +})
120 +const icon_book = computed(() => {
121 + if (props.activated === 'book') {
122 + return icon_book1
123 + } else {
124 + return icon_book2
125 + }
126 +})
127 +const icon_server= computed(() => {
128 + if (props.activated === 'serverInfo') {
129 + return icon_server1
130 + } else {
131 + return icon_server2
132 + }
133 +})
134 +
135 +if (props.activated === 'index') {
136 + homeStyle.value = {
137 + color: '#6A4925',
138 + fontSize: '0.9rem'
139 + }
140 + myStyle.value = {
141 + color: '#999999',
142 + fontSize: '0.9rem'
143 + }
144 + bookStyle.value = {
145 + color: '#999999',
146 + fontSize: '0.9rem'
147 + }
148 + serverStyle.value = {
149 + color: '#999999',
150 + fontSize: '0.9rem'
151 + }
152 +} else if (props.activated === 'my') {
153 + homeStyle.value = {
154 + color: '#999999',
155 + fontSize: '0.9rem'
156 + }
157 + myStyle.value = {
158 + color: '#6A4925',
159 + fontSize: '0.9rem'
160 + }
161 + bookStyle.value = {
162 + color: '#999999',
163 + fontSize: '0.9rem'
164 + }
165 + serverStyle.value = {
166 + color: '#999999',
167 + fontSize: '0.9rem'
168 + }
169 +} else if (props.activated === 'book') {
170 + homeStyle.value = {
171 + color: '#999999',
172 + fontSize: '0.9rem'
173 + }
174 + myStyle.value = {
175 + color: '#999999',
176 + fontSize: '0.9rem'
177 + }
178 + bookStyle.value = {
179 + color: '#6A4925',
180 + fontSize: '0.9rem'
181 + }
182 + serverStyle.value = {
183 + color: '#999999',
184 + fontSize: '0.9rem'
185 + }
186 +} else if (props.activated === 'serverInfo') {
187 + homeStyle.value = {
188 + color: '#999999',
189 + fontSize: '0.9rem'
190 + }
191 + myStyle.value = {
192 + color: '#999999',
193 + fontSize: '0.9rem'
194 + }
195 + bookStyle.value = {
196 + color: '#999999',
197 + fontSize: '0.9rem'
198 + }
199 + serverStyle.value = {
200 + color: '#6A4925',
201 + fontSize: '0.9rem'
202 + }
203 +}
204 +
205 +</script>
206 +
207 +<style lang="less">
208 +.navbar-page {
209 + position: fixed;
210 + bottom: 0;
211 + background-color: #FFFFFF;
212 + padding-top: 0.5rem;
213 + height: 5rem;
214 + width: 100%;
215 +
216 + .home {
217 + position: absolute;
218 + left: 10%;
219 + transform: translateX(-15%);
220 + text-align: center;
221 + }
222 +
223 + .book {
224 + position: absolute;
225 + left: 35%;
226 + transform: translateX(-50%);
227 + text-align: center;
228 + }
229 +
230 + .server {
231 + position: absolute;
232 + left: 60%;
233 + transform: translateX(-50%);
234 + text-align: center;
235 + }
236 +
237 + .my {
238 + position: absolute;
239 + left: 85%;
240 + transform: translateX(-85%);
241 + text-align: center;
242 + }
243 +}
244 +</style>
1 +var getDaysInOneMonth = function (year, month) {
2 + let _month = parseInt(month, 10);
3 + let d = new Date(year, _month, 0);
4 + return d.getDate();
5 +}
6 +var dateDate = function (date) {
7 + let year = date && date.getFullYear();
8 + let month = date && date.getMonth() + 1;
9 + let day = date && date.getDate();
10 + let hours = date && date.getHours();
11 + let minutes = date && date.getMinutes();
12 + return {
13 + year, month, day, hours, minutes
14 + }
15 +}
16 +var dateTimePicker = function (startyear, endyear) {
17 + // 获取date time 年份,月份,天数,小时,分钟推后30分
18 + const years = [];
19 + const months = [];
20 + const hours = [];
21 + const minutes = [];
22 + for (let i = startyear; i <= endyear; i++) {
23 + years.push({
24 + name: i + '年',
25 + id: i
26 + });
27 + }
28 + //获取月份
29 + for (let i = 1; i <= 12; i++) {
30 + if (i < 10) {
31 + i = "0" + i;
32 + }
33 + months.push({
34 + name: i + '月',
35 + id: i
36 + });
37 + }
38 + //获取小时
39 + for (let i = 0; i < 24; i++) {
40 + if (i < 10) {
41 + i = "0" + i;
42 + }
43 + hours.push({
44 + name: i + '时',
45 + id: i
46 + });
47 + }
48 + //获取分钟
49 + for (let i = 0; i < 60; i++) {
50 + if (i < 10) {
51 + i = "0" + i;
52 + }
53 + minutes.push({
54 + name: i + '分',
55 + id: i
56 + });
57 + }
58 + return function (_year, _month) {
59 + const days = [];
60 + _year = parseInt(_year);
61 + _month = parseInt(_month);
62 + //获取日期
63 + for (let i = 1; i <= getDaysInOneMonth(_year, _month); i++) {
64 + if (i < 10) {
65 + i = "0" + i;
66 + }
67 + days.push({
68 + name: i + '日',
69 + id: i
70 + });
71 + }
72 + return [years, months, days, hours, minutes];
73 + }
74 +}
75 +export {
76 + dateTimePicker,
77 + getDaysInOneMonth,
78 + dateDate
79 +}
1 +<template>
2 + <picker mode="multiSelector" :range-key="'name'" :value="timeIndex" :range="activityArray" :disabled="disabled"
3 + @change="bindMultiPickerChange" @columnChange="bindMultiPickerColumnChange">
4 + <slot />
5 + </picker>
6 +</template>
7 +<script>
8 +import { dateTimePicker, dateDate } from "./dateTimePicker.js";
9 +export default {
10 + props: {
11 + startTime: {
12 + type: [Object, Date],
13 + default: new Date(),
14 + },
15 + endTime: {
16 + type: [Object, Date],
17 + default: new Date(),
18 + },
19 + defaultTime: {
20 + type: [Object, Date],
21 + default: new Date(),
22 + },
23 + disabled: {
24 + type: Boolean,
25 + default: false,
26 + },
27 + },
28 + data() {
29 + return {
30 + timeIndex: [0, 0, 0, 0, 0],
31 + activityArray: [],
32 + year: 0,
33 + month: 1,
34 + day: 1,
35 + hour: 0,
36 + minute: 0,
37 + datePicker: "",
38 + defaultIndex: [0, 0, 0, 0, 0],
39 + startIndex: [0, 0, 0, 0, 0],
40 + endIndex: [0, 0, 0, 0, 0],
41 + };
42 + },
43 + computed: {
44 + timeDate() {
45 + const { startTime, endTime } = this;
46 + return { startTime, endTime };
47 + },
48 + },
49 + watch: {
50 + timeDate() {
51 + this.initData();
52 + },
53 + defaultTime () {
54 + this.initData();
55 + }
56 + },
57 + created() {
58 + this.initData();
59 + },
60 + methods: {
61 + initData() {
62 + let startTime = this.startTime;
63 + let endTime = this.endTime;
64 + this.datePicker = dateTimePicker(
65 + startTime.getFullYear(),
66 + endTime.getFullYear()
67 + );
68 + this.setDateData(this.defaultTime);
69 + this.getKeyIndex(this.startTime, "startIndex");
70 + // 截止时间索引
71 + this.getKeyIndex(this.endTime, "endIndex");
72 + // 默认索引
73 + this.getKeyIndex(this.defaultTime, "defaultIndex");
74 + this.timeIndex = this.defaultIndex;
75 + // 初始时间
76 + this.initTime();
77 + },
78 + getKeyIndex(time, key) {
79 + let Arr = dateDate(time);
80 + let _index = this.getIndex(Arr);
81 + this[key] = _index;
82 + },
83 + getIndex(arr) {
84 + let timeIndex = [];
85 + let indexKey = ["year", "month", "day", "hours", "minutes"];
86 + this.activityArray.forEach((element, index) => {
87 + let _index = element.findIndex(
88 + (item) => parseInt(item.id) === parseInt(arr[indexKey[index]])
89 + );
90 + timeIndex[index] = _index >= 0 ? _index : 0;
91 + });
92 + return timeIndex;
93 + },
94 + initTime() {
95 + let _index = this.timeIndex;
96 + this.year = this.activityArray[0][_index[0]].id;
97 + this.month = this.activityArray[1].length && this.activityArray[1][_index[1]].id;
98 + this.day = this.activityArray[2].length && this.activityArray[2][_index[2]].id;
99 + this.hour = this.activityArray[3].length && this.activityArray[3][_index[3]].id;
100 + this.minute = this.activityArray[4].length && this.activityArray[4][_index[4]].id;
101 + },
102 + setDateData(_date) {
103 + let _data = dateDate(_date);
104 + this.activityArray = this.datePicker(_data.year, _data.month);
105 + },
106 + bindMultiPickerChange(e) {
107 + console.log("picker发送选择改变,携带值为", e.detail.value);
108 + let activityArray = JSON.parse(JSON.stringify(this.activityArray)),
109 + { value } = e.detail,
110 + _result = [];
111 + for (let i = 0; i < value.length; i++) {
112 + _result[i] = activityArray[i][value[i]].id;
113 + }
114 + this.$emit("result", _result);
115 + },
116 + bindMultiPickerColumnChange(e) {
117 + console.log("修改的列为", e.detail.column, ",值为", e.detail.value);
118 + let _data = JSON.parse(JSON.stringify(this.activityArray)),
119 + timeIndex = JSON.parse(JSON.stringify(this.timeIndex)),
120 + { startIndex, endIndex } = this,
121 + { column, value } = e.detail,
122 + _value = _data[column][value].id,
123 + _start = dateDate(this.startTime),
124 + _end = dateDate(this.endTime);
125 + switch (e.detail.column) {
126 + case 0:
127 + if (_value <= _start.year) {
128 + timeIndex = startIndex;
129 + this.year = _start.year;
130 + this.setDateData(this.startTime);
131 + } else if (_value >= _end.year) {
132 + this.year = _end.year;
133 + timeIndex = [endIndex[0], 0, 0, 0, 0];
134 + this.setDateData(this.endTime);
135 + } else {
136 + this.year = _value;
137 + timeIndex = [value, 0, 0, 0, 0];
138 + this.activityArray = this.datePicker(_value, 1);
139 + }
140 + timeIndex = this.timeIndex = JSON.parse(JSON.stringify(timeIndex));
141 + this.timeIndex = timeIndex;
142 + break;
143 + case 1:
144 + if (this.year == _start.year && value <= startIndex[1]) {
145 + timeIndex = startIndex;
146 + this.month = _start.month;
147 + this.setDateData(this.startTime);
148 + } else if (this.year == _end.year && value >= endIndex[1]) {
149 + timeIndex = endIndex;
150 + this.month = _end.month;
151 + this.setDateData(this.endTime);
152 + } else {
153 + this.month = _value;
154 + _data[2] = this.datePicker(this.year, this.month)[2];
155 + timeIndex = [timeIndex[0], value, 0, 0, 0];
156 + this.activityArray = _data;
157 + }
158 + this.timeIndex = JSON.parse(JSON.stringify(timeIndex));
159 + break;
160 + case 2:
161 + if (
162 + this.year == _start.year &&
163 + this.month == _start.month &&
164 + value <= startIndex[2]
165 + ) {
166 + this.day = _start.day;
167 + timeIndex = startIndex;
168 + } else if (
169 + this.year == _end.year &&
170 + this.month == _end.month &&
171 + value >= endIndex[2]
172 + ) {
173 + this.day = _end.day;
174 + timeIndex = endIndex;
175 + } else {
176 + this.day = _value;
177 + timeIndex = [timeIndex[0], timeIndex[1], value, 0, 0];
178 + }
179 + this.timeIndex = JSON.parse(JSON.stringify(timeIndex));
180 + break;
181 + case 3:
182 + if (
183 + this.year == _start.year &&
184 + this.month == _start.month &&
185 + this.day == _start.day &&
186 + value <= startIndex[3]
187 + ) {
188 + this.hour = _start.hours;
189 + timeIndex = startIndex;
190 + } else if (
191 + this.year == _end.year &&
192 + this.month == _end.month &&
193 + this.day == _end.day &&
194 + value >= endIndex[3]
195 + ) {
196 + this.hour = _end.hours;
197 + timeIndex = endIndex;
198 + } else {
199 + this.hour = _value;
200 + timeIndex[3] = value;
201 + timeIndex[4] = 0;
202 + }
203 + this.timeIndex = JSON.parse(JSON.stringify(timeIndex));
204 + break;
205 + case 4:
206 + timeIndex[4] = value;
207 + if (
208 + this.year == _start.year &&
209 + this.month == _start.month &&
210 + this.day == _start.day &&
211 + this.hour == _start.hours &&
212 + value <= startIndex[4]
213 + ) {
214 + timeIndex = startIndex;
215 + } else if (
216 + this.year == _end.year &&
217 + this.month == _end.month &&
218 + this.day == _end.day &&
219 + this.hour == _end.hours &&
220 + value >= endIndex[4]
221 + ) {
222 + timeIndex = endIndex;
223 + }
224 + this.timeIndex = JSON.parse(JSON.stringify(timeIndex));
225 + break;
226 + }
227 + },
228 + },
229 +};
230 +</script>
1 +<!DOCTYPE html>
2 +<html>
3 +<head>
4 + <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
5 + <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
6 + <meta name="apple-mobile-web-app-capable" content="yes">
7 + <meta name="apple-touch-fullscreen" content="yes">
8 + <meta name="format-detection" content="telephone=no,address=no">
9 + <meta name="apple-mobile-web-app-status-bar-style" content="white">
10 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
11 + <title>myApp</title>
12 + <script><%= htmlWebpackPlugin.options.script %></script>
13 +</head>
14 +<body>
15 + <div id="app"></div>
16 +</body>
17 +</html>
1 +export default {
2 + navigationBarTitleText: '授权页',
3 + usingComponents: {
4 + },
5 +}
1 +.red {
2 + color: red;
3 +}
1 +<!--
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-05-26 10:17:04
5 + * @FilePath: /meihuaApp/src/pages/auth/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div>
10 + <!-- <button wx:if="{{canIUse}}" open-type="getUserInfo" @getuserinfo="bindGetUserInfo">授权登录</button>
11 + <view @tap="auth">授权登陆</view> -->
12 + </div>
13 +</template>
14 +
15 +<script setup>
16 +import Taro from '@tarojs/taro'
17 +import { ref } from "vue";
18 +import request from '@/utils/request';
19 +
20 +</script>
21 +
22 +<script>
23 +import "./index.less";
24 +import { getCurrentPageParam } from "@/utils/weapp";
25 +
26 +export default {
27 + name: "authPage",
28 + mounted () {
29 + // 授权登陆
30 + Taro.login({
31 + success: function (res) {
32 + if (res.code) {
33 + //发起网络请求
34 + Taro.showLoading({
35 + title: '授权中',
36 + })
37 + request.post('/srv/?a=openid', {
38 + code: res.code
39 + // openid: '0002'
40 + // openid: 'o5NFZ5cFQtLRy3aVHaZMLkjHFusI'
41 + // openid: 'o5NFZ5TpgG4FwYursGCLjcUJH2ak'
42 + // openid: 'o5NFZ5cqroPYwawCp8FEOxewtgnw'
43 + })
44 + .then(res => {
45 + if (res.data.code) {
46 + var cookie = res.cookies[0];
47 + if (cookie != null) {
48 + wx.setStorageSync("sessionid", res.cookies[0]);//服务器返回的 Set-Cookie,保存到本地
49 + //TAG 小程序绑定cookie
50 + // 修改请求头
51 + request.defaults.headers.cookie = res.cookies[0];
52 + // if (res.data.data.avatar) {
53 + // Taro.reLaunch({
54 + // url: '../../' + getCurrentPageParam().url
55 + // })
56 + // } else { // 头像没有设置跳转完善信息页面
57 + // Taro.redirectTo({
58 + // url: '../apxUserInfo/index'
59 + // })
60 + // }
61 + // TAG:处理分享跳转问题
62 + const params = getCurrentPageParam();
63 + if (getCurrentPageParam().url === 'pages/detail/index') { // 详情页的分享跳转处理
64 + Taro.reLaunch({
65 + url: `../../${params.url}?id=${params.id}&start_date=${params.start_date}&end_date=${params.end_date}`
66 + })
67 + } else { // 其他页面分享跳首页
68 + Taro.reLaunch({
69 + url: `/pages/index/index?first_in=${wx.getStorageSync("first_in")}`
70 + })
71 + }
72 + Taro.hideLoading();
73 + }
74 + } else {
75 + console.warn(res.data.msg);
76 + Taro.hideLoading();
77 + }
78 + })
79 + .catch(err => {
80 + console.error(err);
81 + Taro.hideLoading();
82 + });
83 + } else {
84 + console.log('登录失败!' + res.errMsg)
85 + }
86 + }
87 + })
88 + },
89 + data () {
90 + return {
91 + canIUse: wx.canIUse('button.open-type.getUserInfo')
92 + }
93 + },
94 + onLoad: function() {
95 + // 查看是否授权
96 + // wx.getSetting({
97 + // success (res){
98 + // if (res.authSetting['scope.userInfo']) {
99 + // // 已经授权,可以直接调用 getUserInfo 获取头像昵称
100 + // wx.getUserInfo({
101 + // success: function(res) {
102 + // console.warn(res.userInfo)
103 + // }
104 + // })
105 + // }
106 + // }
107 + // })
108 + },
109 + methods: {
110 + bindGetUserInfo (e) {
111 + console.warn(e.detail.userInfo)
112 + },
113 + // auth () {
114 + // Taro.getSetting({
115 + // success: function (res) {
116 + // if (!res.authSetting['scope.userInfo']) {
117 + // console.warn(0);
118 + // Taro.authorize({
119 + // scope: 'scope.userInfo',
120 + // success: function () {
121 + // Taro.getUserInfo({
122 + // success: function(res) {
123 + // var userInfo = res.userInfo
124 + // console.warn(userInfo);
125 + // }
126 + // })
127 + // },
128 + // fail: function (error) {
129 + // console.error(error)
130 + // }
131 + // })
132 + // }
133 + // }
134 + // })
135 + // }
136 + auth () {
137 + // wx.getSetting({
138 + // success (res){
139 + // if (res.authSetting['scope.userInfo']) {
140 + // // 已经授权,可以直接调用 getUserInfo 获取头像昵称
141 + // wx.getUserInfo({
142 + // success: function(res) {
143 + // console.warn(res.userInfo)
144 + // }
145 + // })
146 + // }
147 + // }
148 + // })
149 + wx.getSetting({
150 + success(res) {
151 + if (!res.authSetting['scope.userInfo']) {
152 + wx.authorize({
153 + scope: 'scope.userInfo',
154 + success () {
155 + // 已经授权,可以直接调用 getUserInfo 获取头像昵称
156 + wx.getUserInfo({
157 + success: function(res) {
158 + console.warn(res.userInfo)
159 + }
160 + })
161 + }
162 + })
163 + }
164 + }
165 + })
166 + }
167 + }
168 +};
169 +</script>
1 +export default {
2 + navigationBarTitleText: 'demo',
3 + usingComponents: {
4 + },
5 +}
1 +.red {
2 + color: red;
3 +}
1 +<!--
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-01 10:56:38
5 + * @FilePath: /myApp/src/pages/demo/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="red">{{ str }}</div>
10 +</template>
11 +
12 +<script setup>
13 +import '@tarojs/taro/html.css'
14 +import { ref } from "vue";
15 +import "./index.less";
16 +
17 +// 定义响应式数据
18 +const str = ref('Demo页面')
19 +</script>
20 +
21 +<script>
22 +export default {
23 + name: "demoPage",
24 +};
25 +</script>
1 +export default {
2 + navigationBarTitleText: '首页'
3 +}
1 +/**
2 + * index页面样式
3 + */
4 +.index {
5 + padding: 20px;
6 +
7 + .nut-button {
8 + margin-bottom: 20px;
9 + }
10 +}
...\ No newline at end of file ...\ No newline at end of file
1 +<!--
2 + * @Date: 2025-06-28 10:33:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-01 11:13:13
5 + * @FilePath: /myApp/src/pages/index/index.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <view class="index">
10 + <nut-button type="primary" @click="onClick">按钮</nut-button>
11 + <nut-toast v-model:visible="show" msg="你成功了" />
12 + <View className="text-[#acc855] text-[100px]">Hello world!</View>
13 + </view>
14 +</template>
15 +
16 +<script setup>
17 +import Taro from '@tarojs/taro'
18 +import '@tarojs/taro/html.css'
19 +import { ref, onMounted } from 'vue'
20 +import { useDidShow, useReady } from '@tarojs/taro'
21 +import "./index.less";
22 +
23 +const show = ref(false)
24 +const onClick = () => {
25 + show.value = true
26 +}
27 +
28 +// 生命周期钩子
29 +useDidShow(() => {
30 + console.warn('index onShow')
31 +})
32 +
33 +useReady(async () => {
34 + console.warn('index onReady')
35 + // 版本更新检查
36 + if (!Taro.canIUse("getUpdateManager")) {
37 + Taro.showModal({
38 + title: "提示",
39 + content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试",
40 + showCancel: false,
41 + });
42 + return;
43 + }
44 +
45 + // https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html
46 + const updateManager = Taro.getUpdateManager();
47 +
48 + updateManager.onCheckForUpdate((res) => {
49 + // 请求完新版本信息的回调
50 + if (res.hasUpdate) {
51 + updateManager.onUpdateReady(function () {
52 + Taro.showModal({
53 + title: "更新提示",
54 + content: "新版本已经准备好,是否重启应用?",
55 + success: function (res) {
56 + if (res.confirm) {
57 + // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
58 + updateManager.applyUpdate();
59 + }
60 + },
61 + });
62 + });
63 +
64 + updateManager.onUpdateFailed(function () {
65 + // 新版本下载失败
66 + Taro.showModal({
67 + title: "更新提示",
68 + content: "新版本已上线,请删除当前小程序,重新搜索打开",
69 + });
70 + });
71 + }
72 + });
73 +})
74 +
75 +onMounted(() => {
76 + console.warn('index mounted')
77 +})
78 +
79 +// 分享功能
80 +wx.showShareMenu({
81 + withShareTicket: true,
82 + menus: ['shareAppMessage', 'shareTimeline']
83 +})
84 +</script>
85 +
86 +<script>
87 +import { getCurrentPageParam } from "@/utils/weapp";
88 +
89 +export default {
90 + name: "indexPage",
91 + onHide () {
92 + console.warn('index onHide')
93 + },
94 + onShareAppMessage() {
95 + let params = getCurrentPageParam();
96 + // 设置菜单中的转发按钮触发转发事件时的转发内容
97 + var shareObj = {
98 + title: "xxx", // 默认是小程序的名称(可以写slogan等)
99 + path: `pages/detail/index?id=${params.id}&start_date=${params.start_date}&end_date=${params.end_date}&room_type=${params.room_type}`, // 默认是当前页面,必须是以'/'开头的完整路径
100 + imageUrl: '', //自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径,支持PNG及JPG,不传入 imageUrl 则使用默认截图。显示图片长宽比是 5:4
101 + success: function (res) {
102 + // 转发成功之后的回调
103 + if (res.errMsg == 'shareAppMessage:ok') {
104 + //
105 + }
106 + },
107 + fail: function () {
108 + // 转发失败之后的回调
109 + if (res.errMsg == 'shareAppMessage:fail cancel') {
110 + // 用户取消转发
111 + } else if (res.errMsg == 'shareAppMessage:fail') {
112 + // 转发失败,其中 detail message 为详细失败信息
113 + }
114 + },
115 + complete: function () {
116 + // 转发结束之后的回调(转发成不成功都会执行)
117 + }
118 + }
119 + // 来自页面内的按钮的转发
120 + // if (options.from == 'button') {
121 + // var eData = options.target.dataset;
122 + // // 此处可以修改 shareObj 中的内容
123 + // shareObj.path = '/pages/goods/goods?goodId=' + eData.id;
124 + // }
125 + // 返回shareObj
126 + return shareObj;
127 + }
128 +};
129 +</script>
1 +// https://pinia.esm.dev/introduction.html
2 +import { defineStore } from 'pinia'
3 +
4 +export const useCounterStore = defineStore('counter', {
5 + state: () => {
6 + return { count: 0 }
7 + },
8 + // could also be defined as
9 + // state: () => ({ count: 0 })
10 + actions: {
11 + increment() {
12 + this.count++
13 + },
14 + },
15 +})
16 +
17 +// You can even use a function (similar to a component setup()) to define a Store for more advanced use cases:
18 +// export const useCounterStore = defineStore('counter', () => {
19 +// const count = ref(0)
20 +//
21 +// function increment() {
22 +// count.value++
23 +// }
24 +//
25 +// return {count, increment}
26 +// })
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * @Date: 2022-10-28 14:34:22
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-10-28 15:12:55
5 + * @FilePath: /swx/src/stores/host.js
6 + * @Description: 缓存主办方ID
7 + */
8 +import { defineStore } from 'pinia'
9 +
10 +export const hostStore = defineStore('host', {
11 + state: () => {
12 + return {
13 + id: '',
14 + join_id: ''
15 + }
16 + },
17 + actions: {
18 + add (id) {
19 + this.id = id
20 + },
21 + addJoin (id) {
22 + this.join_id = id
23 + },
24 + },
25 +})
1 +/*
2 + * @Date: 2022-10-28 14:34:22
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-11-01 13:27:09
5 + * @FilePath: /swx/src/stores/router.js
6 + * @Description: 缓存路由信息
7 + */
8 +import { defineStore } from 'pinia'
9 +
10 +export const routerStore = defineStore('router', {
11 + state: () => {
12 + return {
13 + url: '',
14 + }
15 + },
16 + actions: {
17 + add (path) {
18 + this.url = path
19 + },
20 + remove () {
21 + this.url = ''
22 + },
23 + },
24 +})
1 +/*
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2024-01-15 17:07:14
5 + * @FilePath: /meihuaApp/src/utils/config.js
6 + * @Description: 文件描述
7 + */
8 +// TAG:服务器环境配置
9 +// const BASE_URL = "https://oa-dev.onwall.cn"; // 测试服务器
10 +const BASE_URL = "https://oa.onwall.cn"; // 正式服务器
11 +
12 +export default BASE_URL
1 +/*
2 + * @Date: 2022-10-13 22:36:08
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2023-12-22 18:23:08
5 + * @FilePath: /meihuaApp/src/utils/mixin.js
6 + * @Description: 文件描述
7 + */
8 +import { getSessionId, setSessionId, clearSessionId } from './request';
9 +
10 +/**
11 + * 全局mixin,提供sessionid管理功能
12 + * 注意:sessionid现在由request.js自动管理,无需手动设置
13 + */
14 +export default {
15 + // 初始化设置
16 + init: {
17 + created () {
18 + // sessionid现在由request.js的拦截器自动管理
19 + // 这里可以添加其他初始化逻辑
20 + }
21 + }
22 +};
23 +
24 +// 导出sessionid管理工具函数,供其他组件使用
25 +export { getSessionId, setSessionId, clearSessionId }
This diff is collapsed. Click to expand it.
1 +/*
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-01 11:17:49
5 + * @FilePath: /myApp/src/utils/request.js
6 + * @Description: 简单axios封装,后续按实际处理
7 + */
8 +// import axios from 'axios'
9 +import axios from 'axios-miniprogram';
10 +import Taro from '@tarojs/taro'
11 +// import { strExist } from './tools'
12 +// import qs from 'Qs'
13 +import { routerStore } from '@/stores/router'
14 +
15 +// import { ProgressStart, ProgressEnd } from '@/components/axios-progress/progress';
16 +// import store from '@/store'
17 +// import { getToken } from '@/utils/auth'
18 +import BASE_URL from './config';
19 +
20 +/**
21 + * 获取sessionid的工具函数
22 + * @returns {string|null} sessionid或null
23 + */
24 +const getSessionId = () => {
25 + try {
26 + return wx.getStorageSync("sessionid") || null;
27 + } catch (error) {
28 + console.error('获取sessionid失败:', error);
29 + return null;
30 + }
31 +};
32 +
33 +/**
34 + * 设置sessionid的工具函数
35 + * @param {string} sessionid - 要设置的sessionid
36 + */
37 +const setSessionId = (sessionid) => {
38 + try {
39 + wx.setStorageSync("sessionid", sessionid);
40 + } catch (error) {
41 + console.error('设置sessionid失败:', error);
42 + }
43 +};
44 +
45 +/**
46 + * 清除sessionid的工具函数
47 + */
48 +const clearSessionId = () => {
49 + try {
50 + wx.removeStorageSync("sessionid");
51 + } catch (error) {
52 + console.error('清除sessionid失败:', error);
53 + }
54 +};
55 +// create an axios instance
56 +const service = axios.create({
57 + baseURL: BASE_URL, // url = base url + request url
58 + // withCredentials: true, // send cookies when cross-domain requests
59 + timeout: 5000, // request timeout
60 +})
61 +
62 +service.defaults.params = {
63 + f: 'room',
64 + client_id: '772428',
65 +};
66 +
67 +// request interceptor
68 +service.interceptors.request.use(
69 + config => {
70 + // console.warn(config)
71 + // console.warn(store)
72 +
73 + /**
74 + * 动态获取sessionid并设置到请求头
75 + * 确保每个请求都带上最新的sessionid
76 + */
77 + const sessionid = getSessionId();
78 + if (sessionid) {
79 + config.headers.cookie = sessionid;
80 + }
81 +
82 + /**
83 + * POST PHP需要修改数据格式
84 + * 序列化POST请求时需要屏蔽上传相关接口,上传相关接口序列化后报错
85 + */
86 + // config.data = config.method === 'post' && !strExist(['a=upload', 'upload.qiniup.com'], config.url) ? qs.stringify(config.data) : config.data;
87 + return config
88 + },
89 + error => {
90 + // do something with request error
91 + console.error(error, 'err') // for debug
92 + return Promise.reject(error)
93 + }
94 +)
95 +
96 +// response interceptor
97 +service.interceptors.response.use(
98 + /**
99 + * If you want to get http information such as headers or status
100 + * Please return response => response
101 + */
102 +
103 + /**
104 + * Determine the request status by custom code
105 + * Here is just an example
106 + * You can also judge the status by HTTP Status Code
107 + */
108 + response => {
109 + /**
110 + * 检查响应头中是否有新的sessionid
111 + * 如果有,则更新本地存储
112 + */
113 + const setCookieHeader = response.headers['set-cookie'];
114 + if (setCookieHeader) {
115 + // 解析set-cookie头,提取sessionid
116 + const sessionidMatch = setCookieHeader.match(/sessionid=([^;]+)/);
117 + if (sessionidMatch && sessionidMatch[1]) {
118 + setSessionId(sessionidMatch[1]);
119 + }
120 + }
121 +
122 + // wx.hideLoading();
123 + // const res = response.data
124 + // // Toast.clear();
125 + // // if the custom code is not 20000, it is judged as an error.
126 + // if (res.code !== 100000) {
127 + // // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
128 + // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
129 + // // to re-login
130 + // // Toast.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
131 + // // confirmButtonText: 'Re-Login',
132 + // // cancelButtonText: 'Cancel',
133 + // // type: 'warning'
134 + // // }).then(() => {
135 + // // // store.dispatch('user/resetToken').then(() => {
136 + // // // location.reload()
137 + // // // })
138 + // // })
139 + // } else {
140 + // // Toast.fail({
141 + // // message: res.message,
142 + // // duration: 1.5 * 1000
143 + // // })
144 + // // Tips.error(res.message, false)
145 + // }
146 + // return Promise.reject(new Error(res.message || 'Error'))
147 + // } else {
148 + // return res
149 + // }
150 +
151 + /**
152 + * 处理401未授权状态
153 + * 清除本地sessionid并跳转到登录页
154 + */
155 + if (response.data.code === 401) {
156 + // 清除无效的sessionid
157 + clearSessionId();
158 + /**
159 + * 未授权跳转登录页
160 + * 授权完成后 返回当前页面
161 + */
162 + setTimeout(() => {
163 + Taro.navigateTo({
164 + url: '../../pages/auth/index?url=' + routerStore().url
165 + });
166 + }, 1000);
167 + }
168 + return response
169 + },
170 + error => {
171 + // Toast.clear();
172 + console.error('err' + error) // for debug
173 + // Toast.fail({
174 + // message: error.message,
175 + // duration: 1.5 * 1000
176 + // })
177 + return Promise.reject(error)
178 + }
179 +)
180 +
181 +// 导出sessionid管理工具函数
182 +export { getSessionId, setSessionId, clearSessionId };
183 +
184 +export default service
1 +/**
2 + * 系统参数
3 + */
4 +
5 +const DEFAULT_HOST_TYPE = ['首次参与', '老用户']; // 主办方默认用户类型
6 +const DEFAULT_HOST_STATUS = ['跟踪', '引导']; // 主办方默认用户状态
7 +
8 +export { DEFAULT_HOST_TYPE, DEFAULT_HOST_STATUS }
1 +/*
2 + * @Date: 2022-04-18 15:59:42
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2022-12-07 22:09:30
5 + * @FilePath: /swx/src/utils/tools.js
6 + * @Description: 文件描述
7 + */
8 +import Taro from '@tarojs/taro'
9 +import moment from '@/utils/moment.min.js'
10 +
11 +// 格式化时间
12 +const formatDate = (date) => {
13 + return moment(date).format('YYYY-MM-DD HH:mm')
14 +};
15 +
16 +/**
17 + * @description 判断浏览器属于平台
18 + * @returns
19 + */
20 +const wxInfo = () => {
21 + let u = navigator.userAgent;
22 + let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端或者uc浏览器
23 + let isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
24 + let uAgent = navigator.userAgent.toLowerCase();
25 + let isTable = (uAgent.match(/MicroMessenger/i) == 'micromessenger') ? true : false;
26 + let isMobile = window.navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i); // 是否手机端
27 + let isWx = /micromessenger/i.test(navigator.userAgent); // 是否微信
28 + let isWxPc = isWx && !isMobile; // PC端微信
29 + return {
30 + isAndroid,
31 + isiOS,
32 + isTable,
33 + isWxPc
34 + };
35 +};
36 +
37 +/**
38 + * @description 判断多行省略文本
39 + * @param {*} id 目标dom标签
40 + * @returns
41 + */
42 +const hasEllipsis = (id) => {
43 + let oDiv = document.getElementById(id);
44 + let flag = false;
45 + if (oDiv.scrollHeight > oDiv.clientHeight) {
46 + flag = true
47 + }
48 + return flag
49 +}
50 +
51 +/**
52 + * @description 解析URL参数
53 + * @param {*} url
54 + * @returns
55 + */
56 +const parseQueryString = url => {
57 + var json = {};
58 + var arr = url.indexOf('?') >= 0 ? url.substr(url.indexOf('?') + 1).split('&') : [];
59 + arr.forEach(item => {
60 + var tmp = item.split('=');
61 + json[tmp[0]] = tmp[1];
62 + });
63 + return json;
64 +}
65 +
66 +/**
67 + * 字符串包含字符数组中字符的状态
68 + * @param {*} array 字符数组
69 + * @param {*} str 字符串
70 + * @returns 包含状态
71 + */
72 +const strExist = (array, str) => {
73 + const exist = array.filter(arr => {
74 + if (str.indexOf(arr) >= 0) return str;
75 + })
76 + return exist.length > 0
77 +}
78 +
79 +const randomId = (n) => {
80 + const charts = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
81 + var res = '';
82 + for (var i = 0; i < n; i++) {
83 + undefined
84 + var id = Math.ceil(Math.random() * 35);
85 + res += charts[id];
86 + }
87 + return res;
88 +}
89 +
90 +/**
91 + * 获取页面query参数
92 + */
93 +const pageQuery = () => {
94 + const instance = Taro.getCurrentInstance();
95 + let $query = '';
96 + $query = JSON.stringify(instance.router.params);
97 + return JSON.parse($query)
98 +}
99 +
100 +export { formatDate, wxInfo, hasEllipsis, parseQueryString, strExist, randomId, pageQuery };
1 +/*获取当前页url*/
2 +const getCurrentPageUrl = () => {
3 + let pages = getCurrentPages() //获取加载的页面
4 + let currentPage = pages[pages.length - 1] //获取当前页面的对象
5 + let url = currentPage.route //当前页面url
6 + return url
7 +}
8 +/*获取当前页参数*/
9 +const getCurrentPageParam = () => {
10 + let pages = getCurrentPages() //获取加载的页面
11 + let currentPage = pages[pages.length - 1] //获取当前页面的对象
12 + let options = currentPage.options //如果要获取url中所带的参数可以查看options
13 + return options
14 +}
15 +
16 +export {
17 + getCurrentPageUrl,
18 + getCurrentPageParam
19 +}
1 +/*
2 + * @Date: 2025-06-30 13:27:50
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-06-30 13:27:56
5 + * @FilePath: /myApp/tailwind.config.js
6 + * @Description: 文件描述
7 + */
8 +/** @type {import('tailwindcss').Config} */
9 +module.exports = {
10 + // 这里给出了一份 taro 通用示例,具体要根据你自己项目的目录结构进行配置
11 + // 比如你使用 vue3 项目,你就需要把 vue 这个格式也包括进来
12 + // 不在 content glob 表达式中包括的文件,在里面编写 tailwindcss class,是不会生成对应的 css 工具类的
13 + content: ['./public/index.html', './src/**/*.{html,js,ts,jsx,tsx,vue}'],
14 + theme: {
15 + extend: {},
16 + },
17 + plugins: [],
18 + corePlugins: {
19 + // 小程序不需要 preflight,因为这主要是给 h5 的,如果你要同时开发多端,你应该使用 process.env.TARO_ENV 环境变量来控制它
20 + preflight: false,
21 + },
22 +}
This diff could not be displayed because it is too large.