├── src ├── const │ ├── index.ts │ └── cssvar.ts ├── service │ ├── index.ts │ ├── api │ │ ├── index.ts │ │ └── common.ts │ └── request │ │ ├── index.ts │ │ ├── const.ts │ │ ├── request.ts │ │ ├── config.ts │ │ └── callback.ts ├── utils │ ├── auth │ │ ├── index.ts │ │ └── user.ts │ ├── crypto │ │ ├── index.ts │ │ └── aes.ts │ ├── storage │ │ ├── index.ts │ │ └── local.ts │ ├── common │ │ ├── index.ts │ │ ├── reg.ts │ │ ├── formatter.ts │ │ ├── data.ts │ │ └── typeof.ts │ └── index.ts ├── store │ ├── modules │ │ ├── index.ts │ │ └── example.ts │ └── index.ts ├── enum │ ├── index.ts │ ├── storage.ts │ └── common.ts ├── hooks │ ├── index.ts │ ├── use-example.ts │ ├── use-cssvar.ts │ ├── use-toast.ts │ └── use-namespace.ts ├── plugins │ ├── index.ts │ ├── vant.ts │ └── unocss.ts ├── assets │ ├── images │ │ └── common │ │ │ └── logo.png │ └── js │ │ └── confetti.browser.min.js ├── style │ └── scss │ │ ├── font │ │ ├── Alibaba-PuHuiTi-Medium.ttf │ │ ├── Alibaba-PuHuiTi-Regular.ttf │ │ └── index.scss │ │ ├── mixins │ │ ├── config.scss │ │ ├── _var.scss │ │ ├── function.scss │ │ └── mixins.scss │ │ ├── index.scss │ │ ├── var.scss │ │ ├── vant.scss │ │ └── common │ │ └── var.scss ├── views │ ├── index.vue │ ├── about │ │ ├── index.vue │ │ └── home │ │ │ └── index.vue │ └── example │ │ ├── index.vue │ │ └── home │ │ └── index.vue ├── components │ └── common │ │ ├── bottom-fixing-button │ │ ├── props.ts │ │ ├── style │ │ │ └── index.scss │ │ └── index.vue │ │ ├── icon-title-desc │ │ ├── props.ts │ │ ├── style │ │ │ └── index.scss │ │ └── index.vue │ │ └── h-button │ │ ├── props.ts │ │ ├── style │ │ └── index.scss │ │ └── index.vue ├── typings │ └── env.d.ts ├── main.ts ├── App.vue └── router │ ├── index.ts │ └── auth.ts ├── scripts └── docker │ ├── rm.images.sh │ ├── rm.containers.sh │ ├── bin.sh │ ├── build.sh │ └── server.sh ├── packages └── cofetti │ ├── index.ts │ ├── hooks │ ├── index.ts │ └── use-cofetti.ts │ ├── package.json │ └── assets │ └── confetti.browser.min.js ├── pnpm-workspace.yaml ├── .eslintrc.js ├── .npmrc ├── vercel.json ├── .husky ├── commit-msg └── pre-commit ├── public └── favicon.ico ├── .editorconfig ├── Dockerfile ├── .dockerignore ├── .gitignore ├── unocss.config.ts ├── .env.test ├── .env.development ├── .env.production ├── vite.config.ts ├── tsconfig.json ├── .postcssrc.js ├── LICENSE ├── .vscode ├── extensions.json └── settings.json ├── index.html ├── package.json ├── CHANGELOG.md ├── README.zh_CN.md └── README.md /src/const/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cssvar'; 2 | -------------------------------------------------------------------------------- /src/service/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | -------------------------------------------------------------------------------- /scripts/docker/rm.images.sh: -------------------------------------------------------------------------------- 1 | docker image prune -a 2 | -------------------------------------------------------------------------------- /src/utils/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; 2 | -------------------------------------------------------------------------------- /src/utils/crypto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './aes'; 2 | -------------------------------------------------------------------------------- /packages/cofetti/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks'; 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /scripts/docker/rm.containers.sh: -------------------------------------------------------------------------------- 1 | docker container prune 2 | -------------------------------------------------------------------------------- /src/service/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common'; 2 | -------------------------------------------------------------------------------- /src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './example'; 2 | -------------------------------------------------------------------------------- /src/utils/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './local'; 2 | -------------------------------------------------------------------------------- /packages/cofetti/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-cofetti'; 2 | -------------------------------------------------------------------------------- /scripts/docker/bin.sh: -------------------------------------------------------------------------------- 1 | docker run -it hometown-h5-template /bin/bash 2 | 3 | -------------------------------------------------------------------------------- /src/enum/index.ts: -------------------------------------------------------------------------------- 1 | export * from './common'; 2 | export * from './storage'; 3 | -------------------------------------------------------------------------------- /scripts/docker/build.sh: -------------------------------------------------------------------------------- 1 | docker build -f ./Dockerfile -t hometown-h5-template . 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@hometownjs/eslint-config-vue3'] 3 | }; 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | auto-install-peers=true 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "rewrites": [{ "source": "/:path*", "destination": "/index.html" }] 4 | } 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm hometown git-commit-verify 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HoMeTownSoCool/hometown-h5-template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged && npm run typecheck 5 | -------------------------------------------------------------------------------- /src/utils/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeof'; 2 | export * from './data'; 3 | export * from './formatter'; 4 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-example'; 2 | export * from './use-namespace'; 3 | export * from './use-toast'; 4 | -------------------------------------------------------------------------------- /scripts/docker/server.sh: -------------------------------------------------------------------------------- 1 | temp=$(pwd) 2 | docker run -d -p 8080:8080 -v ${temp}/src:/hometown-h5-template/src hometown-h5-template 3 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import './vant'; 2 | import '@/assets/js/confetti.browser.min.js'; 3 | import '@/style/scss/index.scss'; 4 | -------------------------------------------------------------------------------- /src/assets/images/common/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HoMeTownSoCool/hometown-h5-template/HEAD/src/assets/images/common/logo.png -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './crypto'; 3 | export * from './storage'; 4 | export * from './common'; 5 | -------------------------------------------------------------------------------- /src/plugins/vant.ts: -------------------------------------------------------------------------------- 1 | // Toast 2 | import 'vant/es/toast/style'; 3 | // Dialog 4 | import 'vant/es/dialog/style'; 5 | // Notify 6 | 7 | // ImagePreview 8 | -------------------------------------------------------------------------------- /src/style/scss/font/Alibaba-PuHuiTi-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HoMeTownSoCool/hometown-h5-template/HEAD/src/style/scss/font/Alibaba-PuHuiTi-Medium.ttf -------------------------------------------------------------------------------- /src/style/scss/font/Alibaba-PuHuiTi-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HoMeTownSoCool/hometown-h5-template/HEAD/src/style/scss/font/Alibaba-PuHuiTi-Regular.ttf -------------------------------------------------------------------------------- /src/views/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | meta: 5 | title: HOME 6 | redirect: /about 7 | 8 | -------------------------------------------------------------------------------- /packages/cofetti/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cofetti", 3 | "version": "1.0.0", 4 | "author": { 5 | "name": "HoMeTownJS" 6 | }, 7 | "main": "index.ts" 8 | } 9 | -------------------------------------------------------------------------------- /src/views/about/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | meta: 5 | title: 关于 6 | redirect: /about/home 7 | 8 | -------------------------------------------------------------------------------- /src/views/example/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | meta: 5 | title: 栗子 6 | redirect: /example/home 7 | 8 | -------------------------------------------------------------------------------- /src/enum/storage.ts: -------------------------------------------------------------------------------- 1 | /** storage 的key */ 2 | export enum EnumStorageKey { 3 | /** 用户token */ 4 | 'token' = '__TOKEN__', 5 | /** 用户信息 */ 6 | 'user-info' = '__USER_INFO__' 7 | } 8 | -------------------------------------------------------------------------------- /src/store/modules/example.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useExampleStore = defineStore('example-store', { 4 | state: () => ({}), 5 | getters: {}, 6 | actions: {} 7 | }); 8 | -------------------------------------------------------------------------------- /src/service/api/common.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | /** 3 | * 获取something 4 | * @returns server random 5 | */ 6 | export function fetchJustExample() { 7 | return request.post('/something'); 8 | } 9 | -------------------------------------------------------------------------------- /src/style/scss/mixins/config.scss: -------------------------------------------------------------------------------- 1 | $namespace: 'hometown' !default; 2 | $common-separator: '-' !default; 3 | $element-separator: '__' !default; 4 | $modifier-separator: '--' !default; 5 | $state-prefix: 'is-' !default; 6 | -------------------------------------------------------------------------------- /src/hooks/use-example.ts: -------------------------------------------------------------------------------- 1 | /** 示例hook */ 2 | export function useExample() { 3 | /** 两数相加 */ 4 | function add(num1: number, num2: number) { 5 | return num1 + num2; 6 | } 7 | return { 8 | add 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/style/scss/index.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | @import './var.scss'; 4 | @import './vant.scss'; 5 | @import './font/index.scss'; 6 | 7 | .cfca-keyboard-v7 { 8 | padding-top: 4px; 9 | > div { 10 | display: none; 11 | } 12 | } -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | export function setupStore(app: App) { 5 | const store = createPinia(); 6 | app.use(store); 7 | } 8 | 9 | export * from './modules'; 10 | -------------------------------------------------------------------------------- /src/service/request/index.ts: -------------------------------------------------------------------------------- 1 | import { createRequest } from '@hometownjs/request'; 2 | import { axiosConfig, requestConfig } from './config'; 3 | /** 创建请求器 */ 4 | export const request = createRequest(axiosConfig, requestConfig); 5 | export default request; 6 | -------------------------------------------------------------------------------- /src/service/request/const.ts: -------------------------------------------------------------------------------- 1 | /** token失效的code */ 2 | export const TOKEN_EXPIRED_CODE = 9001; 3 | /** token失效的重定向Url */ 4 | export const TOKEN_EXPIRED_REDIRECT_URL = '../../eCard/pages/faceRecognition/index'; 5 | /** 请求成功的code */ 6 | export const SUCCESS_CODE = 200; 7 | -------------------------------------------------------------------------------- /src/service/request/request.ts: -------------------------------------------------------------------------------- 1 | import { createRequest } from '@hometownjs/request'; 2 | import { axiosConfig, requestConfig } from './config'; 3 | /** 创建请求器 */ 4 | export const request = createRequest(axiosConfig, requestConfig); 5 | export default request; 6 | // https://www.jianshu.com/p/53deecb09077 7 | -------------------------------------------------------------------------------- /src/components/common/bottom-fixing-button/props.ts: -------------------------------------------------------------------------------- 1 | export const bottomFixingButtonProps = { 2 | /** 按钮的文字 */ 3 | text: { 4 | type: String, 5 | default: (): string => '确定' 6 | }, 7 | /** 按钮是否禁用 */ 8 | disabled: { 9 | type: Boolean, 10 | default: (): boolean => false 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/const/cssvar.ts: -------------------------------------------------------------------------------- 1 | /** 命名空间 */ 2 | export const DEFAULT_NAMESPACE = 'hometown'; 3 | /** 状态前缀 */ 4 | export const STATE_PREFIX = 'is-'; 5 | /** 连接符 */ 6 | export const COMMON_SEPARATOR = '-'; 7 | /** 元素连接符 */ 8 | export const ELEMENT_SEPARATOR = '__'; 9 | /** 描述连接符 */ 10 | export const MODIFIER_SEPARATOR = '--'; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #设置构建的基础镜像 2 | FROM node:18.14.2 3 | RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm 4 | RUN mkdir -p /hometown-h5-template 5 | WORKDIR /hometown-h5-template 6 | COPY . . 7 | RUN pnpm config set registry https://registry.npmmirror.com 8 | RUN pnpm i 9 | CMD npm run dev 10 | EXPOSE 8080 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/style/scss/font/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'AlibabaSans-Medium'; 3 | src: url('@/style/scss/font/Alibaba-PuHuiTi-Medium.ttf'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | @font-face { 8 | font-family: 'AlibabaSans-Regular'; 9 | src: url('@/style/scss/font/Alibaba-PuHuiTi-Regular.ttf'); 10 | font-weight: normal; 11 | font-style: normal; 12 | } -------------------------------------------------------------------------------- /src/style/scss/mixins/_var.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | @use 'function' as *; 4 | @use '../common/var' as *; 5 | 6 | @mixin set-component-css-var($name, $variables) { 7 | @each $attribute, $value in $variables { 8 | #{getCssVarName($name, $attribute)}: #{$value}; 9 | } 10 | } 11 | 12 | 13 | @mixin set-css-var-value($name, $value) { 14 | #{joinVarName($name)}: #{$value}; 15 | } -------------------------------------------------------------------------------- /src/typings/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module '*.vue' { 5 | import type { DefineComponent } from 'vue'; 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 7 | const component: DefineComponent<{}, {}, any>; 8 | export default component; 9 | } 10 | -------------------------------------------------------------------------------- /src/enum/common.ts: -------------------------------------------------------------------------------- 1 | /** 数据类型 */ 2 | export enum EnumDataType { 3 | number = '[object Number]', 4 | string = '[object String]', 5 | boolean = '[object Boolean]', 6 | null = '[object Null]', 7 | undefined = '[object Undefined]', 8 | object = '[object Object]', 9 | array = '[object Array]', 10 | date = '[object Date]', 11 | regexp = '[object RegExp]', 12 | set = '[object Set]', 13 | map = '[object Map]' 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/common/reg.ts: -------------------------------------------------------------------------------- 1 | // 姓名 2 | export function nameReg(name: string) { 3 | const reg = /[^a-zA-Z0-9\u4E00-\u9FA5.·]/g; 4 | if (reg.test(name)) { 5 | return true; 6 | } 7 | return false; 8 | } 9 | // 身份证 10 | export function IDReg(id: string) { 11 | const reg = /(^([1-6][1-9]|50)\d{4}(18|19|20)\d{2}((0[1-9])|10|11|12)(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)/; 12 | if (reg.test(id)) { 13 | return false; 14 | } 15 | return true; 16 | } 17 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | stats.html 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | !.vscode/settings.json 22 | .idea 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | package-lock.json 30 | yarn.lock 31 | -------------------------------------------------------------------------------- /src/components/common/icon-title-desc/props.ts: -------------------------------------------------------------------------------- 1 | export const iconTitleDescProps = { 2 | /** 标题 */ 3 | title: { 4 | type: String, 5 | default: (): string => '' 6 | }, 7 | /** 副标题 */ 8 | subtitle: { 9 | type: String, 10 | default: (): string => '' 11 | }, 12 | /** 描述 */ 13 | desc: { 14 | type: String, 15 | default: (): string => '' 16 | }, 17 | /** Icon */ 18 | icon: { 19 | type: String, 20 | default: (): string => '' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | stats.html 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | !.vscode/settings.json 22 | .idea 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | package-lock.json 30 | yarn.lock 31 | 32 | components.d.ts 33 | auto-imports.d.ts 34 | -------------------------------------------------------------------------------- /src/components/common/icon-title-desc/style/index.scss: -------------------------------------------------------------------------------- 1 | @use '@/style/scss/mixins/mixins.scss' as *; 2 | @include b(icon-title-desc) { 3 | --at-apply: flex-col flex-center; 4 | @include m(icon) { 5 | --at-apply: mb-16px w-64px h-64px; 6 | } 7 | @include m(subtitle) { 8 | --at-apply: m-0 m-b-8px font-400; 9 | } 10 | @include m(title) { 11 | --at-apply: m-0 text-22px; 12 | } 13 | @include m(desc) { 14 | --at-apply: m-0 m-t-12px text-14px text-color-regular lh-20px max-w-267px; 15 | } 16 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { setupRouter } from '@/router'; 3 | import { setupStore } from '@/store'; 4 | import App from './App.vue'; 5 | import '@/plugins/index'; 6 | // eslint-disable-next-line import/no-unresolved 7 | import 'uno.css'; 8 | async function setupApp() { 9 | const app = createApp(App); 10 | 11 | // 挂载pinia状态 12 | setupStore(app); 13 | 14 | // 挂载路由 15 | await setupRouter(app); 16 | 17 | // 路由准备就绪后挂载 App 18 | app.mount('#app'); 19 | } 20 | 21 | setupApp(); 22 | -------------------------------------------------------------------------------- /src/utils/common/formatter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在字符串中填充空格 3 | * @param param 参数 4 | * @returns 5 | */ 6 | export function fillSpace({ val, oldVal, step }: Record): string { 7 | let res = val; 8 | if (val.length > (oldVal?.length || 0)) { 9 | if (step.includes(val.length)) { 10 | const length = val.length; 11 | res = `${val.substr(0, length - 1)} ${val[length - 1]}`; 12 | } 13 | } else if (step.includes(val.length)) { 14 | res = val.replace(/\s*$/g, ''); 15 | } 16 | return res; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/common/h-button/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | export type ButtonType = undefined | 'primary' | 'secondary'; 3 | export const buttonProps = { 4 | /** 按钮的类型 */ 5 | type: { 6 | type: String as PropType, 7 | default: (): undefined => undefined 8 | }, 9 | /** 是否禁用 */ 10 | disabled: { 11 | type: Boolean, 12 | default: (): boolean => false 13 | }, 14 | /** 是否是「文字」按钮 */ 15 | text: { 16 | type: Boolean, 17 | default: (): boolean => false 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/common/data.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 千分符分割 3 | * @param num 数字 4 | * @returns 1,000 5 | */ 6 | export function splitThousandSeparator(num: number | string): string { 7 | let tempnum = Number(num); 8 | let prefix = ''; 9 | if (tempnum < 0) { 10 | tempnum *= -1; 11 | prefix = '-'; 12 | } 13 | const DIGIT_PATTERN = /(^|\s)\d+(?=\.?\d*($|\s))/g; 14 | const MILI_PATTERN = /(?=(?!\b)(\d{3})+\.?\b)/g; 15 | const str: string = tempnum.toString().replace(DIGIT_PATTERN, m => m.replace(MILI_PATTERN, ',')); 16 | return prefix + str; 17 | } 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 20 | -------------------------------------------------------------------------------- /src/components/common/bottom-fixing-button/style/index.scss: -------------------------------------------------------------------------------- 1 | @use '@/style/scss/mixins/mixins.scss' as *; 2 | @use '@/style/scss/mixins/function.scss' as *; 3 | @use '@/style/scss/mixins/var' as *; 4 | @use '@/style/scss/common/var.scss' as *; 5 | :root { 6 | @include set-component-css-var('bottom-fixing-button', $bottom-fixing-button); 7 | } 8 | @include b(bottom-fixing-button) { 9 | --at-apply: w-full fixed bottom-0 left-0 bg-white flex-center; 10 | height: getCssVar('bottom-fixing-button', 'wrap-height'); 11 | border-top: 1px solid getCssVar('border-color', 'primary'); 12 | } -------------------------------------------------------------------------------- /src/style/scss/var.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | // CSS3 var 4 | @use 'common/var' as *; 5 | @use 'mixins/var' as *; 6 | 7 | :root { 8 | @include set-component-css-var('text-color', $text-color); 9 | @include set-component-css-var('color', $colors); 10 | @include set-component-css-var('border-color', $border-color); 11 | @include set-component-css-var('font-family', $font-family); 12 | @include set-component-css-var('bg-color', $bg-color); 13 | @include set-component-css-var('box-shadow', $shadow); 14 | @include set-component-css-var('box-rounded', $rounded) 15 | } -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { presetUno, presetAttributify, presetIcons, defineConfig } from 'unocss'; 2 | import transformerDirectives from '@unocss/transformer-directives'; 3 | import { createRules, createShortcuts, createTheme } from './src/plugins/unocss'; 4 | export default defineConfig({ 5 | presets: [presetUno(), presetAttributify(), presetIcons()], 6 | include: [`${__dirname}/**/*`], 7 | exclude: [`${__dirname}/node_modules/**/*`], 8 | transformers: [transformerDirectives()], 9 | rules: createRules(), 10 | shortcuts: createShortcuts(), 11 | theme: createTheme() 12 | }); 13 | -------------------------------------------------------------------------------- /src/hooks/use-cssvar.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_NAMESPACE } from '../const'; 2 | /** cssvar */ 3 | export default function useCssvar() { 4 | function joinVarName(spaces: string[]): string { 5 | let name = `--${DEFAULT_NAMESPACE}`; 6 | spaces.forEach(space => (name += `-${space}`)); 7 | return name; 8 | } 9 | function getCssvarName(spaces: string[]): string { 10 | return joinVarName(spaces); 11 | } 12 | function getCssvar(spaces: string[]): string { 13 | return `var(${joinVarName(spaces)})`; 14 | } 15 | return { 16 | getCssvarName, 17 | getCssvar 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # app name 2 | VITE_APP_NAME = 'hometown-h5-template' 3 | 4 | # same as webpack publicPath 5 | VITE_BASE_URL = '/' 6 | 7 | # usage hash or history route ? 8 | VITE_HASH_ROUTE = 'false' 9 | 10 | # usage http proxy ? 11 | VITE_HTTP_PROXY= 'false' 12 | 13 | # http proxy url will effect when VITE_HTTP_PROXY is true. 14 | VITE_HTTP_PROXY_URL = 'http://v.juhe.cn' 15 | 16 | # http proxy perfix will effect when VITE_HTTP_PROXY is true. 17 | VITE_HTTP_PROXY_PREFIX = '' 18 | 19 | # if proxy is false, the requeset will link this 20 | VITE_HTTP_API_URL = '' 21 | 22 | # usage visualizer ? 23 | VITE_VISUALIZER = 'true' 24 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # app name 2 | VITE_APP_NAME = 'hometown-h5-template' 3 | 4 | # same as webpack publicPath 5 | VITE_BASE_URL = '/' 6 | 7 | # usage hash or history route ? 8 | VITE_HASH_ROUTE = 'false' 9 | 10 | # usage http proxy ? 11 | VITE_HTTP_PROXY= 'true' 12 | 13 | # http proxy url will effect when VITE_HTTP_PROXY is true. 14 | VITE_HTTP_PROXY_URL = 'http://v.juhe.cn' 15 | 16 | # http proxy perfix will effect when VITE_HTTP_PROXY is true. 17 | VITE_HTTP_PROXY_PREFIX = '/api' 18 | 19 | # if proxy is false, the requeset will link this 20 | VITE_HTTP_API_URL = '' 21 | 22 | # usage visualizer ? 23 | VITE_VISUALIZER = 'false' 24 | -------------------------------------------------------------------------------- /src/service/request/config.ts: -------------------------------------------------------------------------------- 1 | import { onRequest, onBackendSuccess, onBackendFail } from './callback'; 2 | 3 | const viteEnv = import.meta.env; 4 | const viteHttpProxy = viteEnv.VITE_HTTP_PROXY === 'true'; 5 | const viteHttpProxyPerfix = viteEnv.VITE_HTTP_PROXY_PREFIX; 6 | const viteHttpAPIUrl = viteEnv.VITE_HTTP_API_URL; 7 | export const baseURL = viteHttpProxy ? viteHttpProxyPerfix : viteHttpAPIUrl; 8 | 9 | export const axiosConfig = { 10 | baseURL 11 | }; 12 | 13 | export const requestConfig = { 14 | codeKey: 'code', 15 | dataKey: 'data', 16 | msgKey: 'msg', 17 | onRequest, 18 | onBackendSuccess, 19 | onBackendFail 20 | }; 21 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # app name 2 | VITE_APP_NAME = 'hometown-h5-template' 3 | 4 | # same as webpack publicPath 5 | VITE_BASE_URL = '/' 6 | 7 | # usage hash or history route ? 8 | VITE_HASH_ROUTE = 'false' 9 | 10 | # usage http proxy ? 11 | VITE_HTTP_PROXY= 'false' 12 | 13 | # http proxy url will effect when VITE_HTTP_PROXY is true. 14 | VITE_HTTP_PROXY_URL = 'http://v.juhe.cn' 15 | 16 | # http proxy perfix will effect when VITE_HTTP_PROXY is true. 17 | VITE_HTTP_PROXY_PREFIX = '' 18 | 19 | # if proxy is false, the requeset will link this 20 | VITE_HTTP_API_URL = 'http://v.juhe.cn' 21 | 22 | 23 | # usage visualizer ? 24 | VITE_VISUALIZER = 'true' 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { createViteConfig } from '@hometownjs/vite-config-vue'; 2 | import { setupVitePlugins } from './build/plugins'; 3 | import { resolvePath } from './build/utils'; 4 | const vitePath = resolvePath('./', import.meta.url); 5 | export default createViteConfig({ 6 | vitePathRoot: vitePath.root, 7 | vitePathSrc: vitePath.src, 8 | vitePluginsDefaults: ['vue', 'html', 'visualizer'], 9 | unpluginResolvers: ['Vant'], 10 | vitePluginsCustom: setupVitePlugins(), 11 | serverOpen: false, 12 | buildManualChunks: { 13 | vue: ['vue'], 14 | 'vue-router': ['vue-router'], 15 | vant: ['vant'], 16 | 'crypto-js': ['crypto-js'] 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/common/h-button/style/index.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | @use '@/style/scss/mixins/mixins.scss' as *; 4 | @use '@/style/scss/common/var.scss' as *; 5 | @use '@/style/scss/mixins/function.scss' as *; 6 | @include b(van-p-button) { 7 | --at-apply: text-14px font-500 wh-full rounded-22px; 8 | @include m(secondary) { 9 | $secondary-color: getCssVar('bg-color', 'secondary') !default; 10 | background-color: $secondary-color; 11 | border-color: $secondary-color; 12 | color: getCssVar('text-color', 'secondary'); 13 | } 14 | &.van-button::before { 15 | background-color: transparent !important; 16 | border-color: transparent !important; 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/crypto/aes.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | 3 | const CryptoSecretAES = '__CryptoJS_Secret__'; 4 | 5 | /** 6 | * 加密数据 7 | * @param data - 数据 8 | */ 9 | export function encryptoAES(data: unknown) { 10 | const newData = JSON.stringify(data); 11 | return CryptoJS.AES.encrypt(newData, CryptoSecretAES).toString(); 12 | } 13 | 14 | /** 15 | * 解密数据 16 | * @param cipherText - 密文 17 | */ 18 | export function decryptoAES(cipherText: string) { 19 | const bytes = CryptoJS.AES.decrypt(cipherText, CryptoSecretAES); 20 | const originalText = bytes.toString(CryptoJS.enc.Utf8); 21 | if (originalText) { 22 | return JSON.parse(originalText); 23 | } 24 | return null; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/common/bottom-fixing-button/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 21 | 24 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'; 3 | import { setupRouterBeforeEach } from './auth'; 4 | // eslint-disable-next-line import/no-unresolved 5 | import routes from 'virtual:generated-pages'; 6 | const { VITE_HASH_ROUTE = 'false', VITE_BASE_URL } = import.meta.env; 7 | const history = 8 | VITE_HASH_ROUTE === 'true' 9 | ? createWebHashHistory(VITE_BASE_URL as string) 10 | : createWebHistory(VITE_BASE_URL as string); 11 | 12 | export const router = createRouter({ 13 | history, 14 | routes 15 | }); 16 | 17 | export async function setupRouter(app: App) { 18 | setupRouterBeforeEach(router); 19 | app.use(router); 20 | await router.isReady(); 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "ESNext", 6 | "lib": ["DOM", "ESNext"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowJs": true, 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "~/*": ["./*"] 20 | } 21 | }, 22 | "exclude": ["node_modules", "dist"], 23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "components.d.ts", "auto-imports.d.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /src/components/common/icon-title-desc/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/common/h-button/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 25 | 28 | -------------------------------------------------------------------------------- /src/utils/auth/user.ts: -------------------------------------------------------------------------------- 1 | import { EnumStorageKey } from '@/enum'; 2 | import { getLocal, removeLocal, setLocal } from '../storage/index'; 3 | 4 | /** 设置用户信息 */ 5 | export function setUserInfo(userInfo: U) { 6 | setLocal(EnumStorageKey['user-info'], userInfo); 7 | } 8 | 9 | /** 获取local中的 用户信息 */ 10 | export function getUserInfo() { 11 | return getLocal(EnumStorageKey['user-info']); 12 | } 13 | 14 | /** 设置token */ 15 | export function setToken(token: string) { 16 | setLocal(EnumStorageKey.token, token); 17 | } 18 | 19 | /** 获取local中的 token */ 20 | export function getToken() { 21 | return getLocal(EnumStorageKey.token); 22 | } 23 | /** 去除token */ 24 | export function removeToken() { 25 | removeLocal(EnumStorageKey.token); 26 | } 27 | 28 | /** 去除用户信息 */ 29 | export function removeUserInfo() { 30 | removeLocal(EnumStorageKey['user-info']); 31 | } 32 | -------------------------------------------------------------------------------- /src/views/example/home/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 19 | 20 | 30 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | plugins: { 4 | 'postcss-px-to-viewport-8-plugin': { 5 | unitToConvert: 'px', // 需要转换的单位,默认为"px" 6 | viewportWidth: 375, // 设计稿的视口宽度 7 | unitPrecision: 5, // 单位转换后保留的精度 8 | propList: ['*'], // 能转化为vw的属性列表,!font-size表示font-size后面的单位不会被转换 9 | viewportUnit: 'vw', // 希望使用的视口单位 10 | fontViewportUnit: 'vw', // 字体使用的视口单位 11 | // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。 12 | // 下面配置表示类名中含有'keep-px'都不会被转换 13 | selectorBlackList: ['keep-px]'], 14 | minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换 15 | mediaQuery: false, // 媒体查询里的单位是否需要转换单位 16 | replace: true, // 是否直接更换属性值,而不添加备用属性 17 | exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件 18 | include: [/src/], // 如果设置了include,那将只有匹配到的文件才会被转换 19 | landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape) 20 | landscapeUnit: 'vw', // 横屏时使用的单位 21 | landscapeWidth: 1338, // 横屏时使用的视口宽度 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/router/auth.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | 3 | /** 注册路由守卫 */ 4 | export function setupRouterBeforeEach(router: Router) { 5 | router.beforeEach((to, _, next) => { 6 | const { query } = to; 7 | if (query.t) { 8 | /** 存储token */ 9 | setToken(query.t as string); 10 | /** 初始化用户信息 */ 11 | initializeUserInfo(); 12 | /** 判断用户是否实名 */ 13 | if (checkAuthenticationStatus()) { 14 | next(); 15 | } else { 16 | // console.log('from', _); 17 | // console.log('to', to); 18 | next('/name-auth'); 19 | } 20 | } 21 | next(); 22 | }); 23 | } 24 | 25 | /** 初始化用户信息 */ 26 | function initializeUserInfo() { 27 | const mockUserInfo = { 28 | name: 'xxx', 29 | isAuth: true 30 | }; 31 | setUserInfo(mockUserInfo); 32 | } 33 | 34 | /** 处理是否实名认证 */ 35 | export function checkAuthenticationStatus() { 36 | const userInfo = getUserInfo() as Record; 37 | const isAuthenticated = userInfo.isAuth; 38 | return isAuthenticated; 39 | } 40 | -------------------------------------------------------------------------------- /src/style/scss/vant.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | 3 | // CSS3 var 4 | @use 'common/var' as *; 5 | @use 'mixins/var' as *; 6 | 7 | :root { 8 | --van-primary-color: #{$color-primary}; 9 | --van-button-disabled-opacity: 0.3; 10 | --van-radio-duration: 0 !important; 11 | --van-dialog-width: 280px; 12 | --van-border-color:none; 13 | // --van-step-icon-size:14px!important; 14 | --van-step-icon-size:26px!important; 15 | --van-step-circle-size:18px!important; 16 | } 17 | 18 | .van-uploader__preview-image,.van-uploader__wrapper,.van-uploader__input-wrapper { 19 | width: 100px!important; 20 | height:60px!important; 21 | } 22 | 23 | .van-step__circle { 24 | background-color:#F6ACAC!important; 25 | } 26 | 27 | 28 | // radio 29 | .van-radio__icon { 30 | &.van-radio__icon--checked { 31 | .van-badge__wrapper { 32 | background-color: $color-primary !important; 33 | } 34 | } 35 | .van-badge__wrapper { 36 | border-color: map.get($bg-color, 'secondary') !important; 37 | background-color: map.get($bg-color, 'secondary'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 HoMeTown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/hooks/use-toast.ts: -------------------------------------------------------------------------------- 1 | import { showToast, showLoadingToast, closeToast, showSuccessToast, showFailToast } from 'vant'; 2 | import { type ToastOptions } from 'vant'; 3 | const TOAST_LOADING_CONFIG = { 4 | message: '加载中...', 5 | forbidClick: true, 6 | loadingType: 'spinner' 7 | }; 8 | export function useToast() { 9 | /** loading */ 10 | function loading(config?: ToastOptions) { 11 | showLoadingToast(Object.assign(TOAST_LOADING_CONFIG, config)); 12 | } 13 | /** normal */ 14 | function toast(text: string, onClose?: () => void) { 15 | showToast({ 16 | message: text, 17 | onClose 18 | }); 19 | } 20 | /** success */ 21 | function success(text: string, onClose?: () => void) { 22 | showSuccessToast({ 23 | message: text, 24 | onClose 25 | }); 26 | } 27 | /** 关闭 */ 28 | function close() { 29 | closeToast(); 30 | } 31 | function error(text: string, onClose?: () => void) { 32 | showFailToast({ 33 | message: text, 34 | onClose 35 | }); 36 | } 37 | return { 38 | loading, 39 | success, 40 | toast, 41 | close, 42 | error 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/style/scss/mixins/function.scss: -------------------------------------------------------------------------------- 1 | @use 'config'; 2 | 3 | // css变量拼接 joinVarName(('button', 'text-color')) => '--pies-button-text-color' 4 | @function joinVarName($list) { 5 | $name: '--' + config.$namespace; 6 | @each $item in $list { 7 | @if $item != '' { 8 | $name: $name + '-' + $item; 9 | } 10 | } 11 | @return $name; 12 | } 13 | 14 | // 获取css变量名称 getCssVarName('button', 'text-color') => '--el-button-text-color' 15 | @function getCssVarName($args...) { 16 | @return joinVarName($args); 17 | } 18 | 19 | // 获取css变量 getCssVar('button', 'text-color') => var(--el-button-text-color) 20 | @function getCssVar($args...) { 21 | @return var(#{joinVarName($args)}); 22 | } 23 | 24 | // bem('block', 'element', 'modifier') => 'el-block__element--modifier' 25 | @function bem($block, $element: '', $modifier: '') { 26 | $name: config.$namespace + config.$common-separator + $block; 27 | 28 | @if $element != '' { 29 | $name: $name + config.$element-separator + $element; 30 | } 31 | 32 | @if $modifier != '' { 33 | $name: $name + config.$modifier-separator + $modifier; 34 | } 35 | 36 | // @debug $name; 37 | @return $name; 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "afzalsayed96.icones", 4 | "antfu.iconify", 5 | "antfu.unocss", 6 | "christian-kohler.path-intellisense", 7 | "dbaeumer.vscode-eslint", 8 | "DotJoshJohnson.xml", 9 | "eamodio.gitlens", 10 | "editorconfig.editorconfig", 11 | "esbenp.prettier-vscode", 12 | "formulahendry.auto-complete-tag", 13 | "formulahendry.auto-close-tag", 14 | "formulahendry.auto-rename-tag", 15 | "formulahendry.code-runner", 16 | "kisstkondoros.vscode-gutter-preview", 17 | "jock.svg", 18 | "josee9988.minifyall", 19 | "lokalise.i18n-ally", 20 | "mariusalchimavicius.json-to-ts", 21 | "mhutchie.git-graph", 22 | "mikestead.dotenv", 23 | "ms-ceintl.vscode-language-pack-zh-hans", 24 | "naumovs.color-highlight", 25 | "piyushvscode.nodejs-snippets", 26 | "pkief.material-icon-theme", 27 | "sdras.vue-vscode-snippets", 28 | "steoates.autoimport", 29 | "vue.volar", 30 | "vue.vscode-typescript-vue-plugin", 31 | "whtouche.vscode-js-console-utils", 32 | "xabikos.javascriptsnippets", 33 | "yzhang.markdown-all-in-one", 34 | "zhuangtongfa.material-theme" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <%= appName %> 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/cofetti/hooks/use-cofetti.ts: -------------------------------------------------------------------------------- 1 | import { useDebounceFn } from '@vueuse/core'; 2 | 3 | export function useCofetti() { 4 | const play = useDebounceFn(() => { 5 | const duration = 3 * 1000; 6 | const animationEnd = Date.now() + duration; 7 | const defaults = { 8 | startVelocity: 30, 9 | spread: 360, 10 | ticks: 60, 11 | zIndex: 0 12 | }; 13 | function randomInRange(min: number, max: number) { 14 | return Math.random() * (max - min) + min; 15 | } 16 | const interval: any = setInterval(() => { 17 | const timeLeft = animationEnd - Date.now(); 18 | if (timeLeft <= 0) { 19 | return clearInterval(interval); 20 | } 21 | const particleCount = 50 * (timeLeft / duration); 22 | (window as typeof window & Record).confetti({ 23 | ...defaults, 24 | particleCount, 25 | origin: { 26 | x: randomInRange(0.1, 0.3), 27 | y: Math.random() - 0.2 28 | } 29 | }); 30 | (window as typeof window & Record).confetti({ 31 | ...defaults, 32 | particleCount, 33 | origin: { 34 | x: randomInRange(0.7, 0.9), 35 | y: Math.random() - 0.2 36 | } 37 | }); 38 | return ''; 39 | }, 300); 40 | }, 300); 41 | return { 42 | play 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/style/scss/mixins/mixins.scss: -------------------------------------------------------------------------------- 1 | @use 'function' as *; 2 | @use '../common/var' as *; 3 | // forward mixins 4 | @forward 'config'; 5 | @forward 'function'; 6 | @use 'config' as *; 7 | @use 'config' as *; 8 | 9 | // BEM 10 | @mixin b($block) { 11 | $B: $namespace + '-' + $block !global; 12 | 13 | .#{$B} { 14 | @content; 15 | } 16 | } 17 | 18 | @mixin e($element) { 19 | $E: $element !global; 20 | $selector: &; 21 | $currentSelector: ''; 22 | @each $unit in $element { 23 | $currentSelector: #{$currentSelector + '.' + $B + $element-separator + $unit + ','}; 24 | } 25 | 26 | @if hitAllSpecialNestRule($selector) { 27 | @at-root { 28 | #{$selector} { 29 | #{$currentSelector} { 30 | @content; 31 | } 32 | } 33 | } 34 | } @else { 35 | @at-root { 36 | #{$currentSelector} { 37 | @content; 38 | } 39 | } 40 | } 41 | } 42 | 43 | @mixin m($modifier) { 44 | $selector: &; 45 | $currentSelector: ''; 46 | @each $unit in $modifier { 47 | $currentSelector: #{$currentSelector + $selector + $modifier-separator + $unit + ','}; 48 | } 49 | 50 | @at-root { 51 | #{$currentSelector} { 52 | @content; 53 | } 54 | } 55 | } 56 | 57 | @mixin when($state) { 58 | @at-root { 59 | &.#{$state-prefix + $state} { 60 | @content; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/utils/common/typeof.ts: -------------------------------------------------------------------------------- 1 | import { EnumDataType } from '@/enum'; 2 | 3 | export function isNumber(data: unknown) { 4 | return Object.prototype.toString.call(data) === EnumDataType.number; 5 | } 6 | export function isString(data: unknown) { 7 | return Object.prototype.toString.call(data) === EnumDataType.string; 8 | } 9 | export function isBoolean(data: unknown) { 10 | return Object.prototype.toString.call(data) === EnumDataType.boolean; 11 | } 12 | export function isNull(data: unknown) { 13 | return Object.prototype.toString.call(data) === EnumDataType.null; 14 | } 15 | export function isUndefined(data: unknown) { 16 | return Object.prototype.toString.call(data) === EnumDataType.undefined; 17 | } 18 | export function isObject(data: unknown) { 19 | return Object.prototype.toString.call(data) === EnumDataType.object; 20 | } 21 | export function isArray(data: unknown) { 22 | return Object.prototype.toString.call(data) === EnumDataType.array; 23 | } 24 | export function isDate(data: unknown) { 25 | return Object.prototype.toString.call(data) === EnumDataType.date; 26 | } 27 | export function isRegExp(data: unknown) { 28 | return Object.prototype.toString.call(data) === EnumDataType.regexp; 29 | } 30 | export function isSet(data: unknown) { 31 | return Object.prototype.toString.call(data) === EnumDataType.set; 32 | } 33 | export function isMap(data: unknown) { 34 | return Object.prototype.toString.call(data) === EnumDataType.map; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/storage/local.ts: -------------------------------------------------------------------------------- 1 | import { encryptoAES, decryptoAES } from '../crypto'; 2 | 3 | interface StorageData { 4 | value: any; 5 | expire: number | null; 6 | } 7 | 8 | /** 默认缓存期限为7天 */ 9 | const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 10 | 11 | /** 设置local storage缓存 */ 12 | export function setLocal(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) { 13 | const storageData: StorageData = { 14 | expire: expire !== null ? new Date().getTime() + expire * 1000 : null, 15 | value 16 | }; 17 | const json = encryptoAES(storageData); 18 | window.localStorage.setItem(key, json); 19 | } 20 | 21 | /** 获取local storage缓存 */ 22 | export function getLocal(key: string) { 23 | const json = window.localStorage.getItem(key); 24 | if (json) { 25 | let storageData: StorageData | null = null; 26 | try { 27 | storageData = decryptoAES(json); 28 | } catch { 29 | // 防止解析失败 30 | } 31 | if (storageData) { 32 | const { value, expire } = storageData; 33 | // 在有效期内直接返回 34 | if (expire === null || expire >= Date.now()) { 35 | return value as T; 36 | } 37 | } 38 | removeLocal(key); 39 | return null; 40 | } 41 | return null; 42 | } 43 | 44 | /** 删除local storage缓存 */ 45 | export function removeLocal(key: string) { 46 | window.localStorage.removeItem(key); 47 | } 48 | 49 | /** 清除local storage缓存 */ 50 | export function clearLocal() { 51 | window.localStorage.clear(); 52 | } 53 | -------------------------------------------------------------------------------- /src/service/request/callback.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig, AxiosResponse } from 'axios'; 2 | import { requestConfig } from './config'; 3 | import { SUCCESS_CODE, TOKEN_EXPIRED_CODE, TOKEN_EXPIRED_REDIRECT_URL } from './const'; 4 | import { showToast } from 'vant'; 5 | 6 | export function onRequest(config: AxiosRequestConfig): Promise { 7 | return new Promise(resolve => { 8 | const token = getToken() as string; 9 | config.headers = { 10 | ...config.headers, 11 | token 12 | }; 13 | resolve(config); 14 | }); 15 | } 16 | 17 | /** 处理成功 */ 18 | export function onBackendSuccess(responseData: Record & AxiosResponse): boolean { 19 | const hasCodeKey = Object.keys(responseData).includes(requestConfig.codeKey); 20 | 21 | const isSuccessByCode = responseData.code === SUCCESS_CODE; 22 | 23 | const result = isObject(responseData) && hasCodeKey && isSuccessByCode; 24 | 25 | return result; 26 | } 27 | 28 | /** 处理失败 */ 29 | export function onBackendFail(responseData: Record & AxiosResponse): Promise { 30 | return new Promise(resolve => { 31 | const { data } = responseData; 32 | showToast({ 33 | message: data.msg, 34 | onClose: () => { 35 | if (data.code === TOKEN_EXPIRED_CODE) { 36 | hanldeErrorByTokenExpired(); 37 | } 38 | } 39 | }); 40 | resolve(responseData); 41 | }); 42 | } 43 | 44 | /** 处理token失效 */ 45 | export function hanldeErrorByTokenExpired() { 46 | removeToken(); 47 | removeUserInfo(); 48 | console.log(TOKEN_EXPIRED_REDIRECT_URL, 'gogogo'); 49 | } 50 | -------------------------------------------------------------------------------- /src/hooks/use-namespace.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * BEM 组成 3 | * Block 块独立实体,独立的意义 4 | * 如:header,container,menu,checkbox,input 5 | * 6 | * Element 元素block的一部分,没有独立的意义。语意上和block有关系 7 | * 如:menuitem,listitem,checkboxcaption,headertitl 8 | * 9 | * Modifier 修饰符block或element上的标记。使用他来改变外观或行为 10 | * 如:disabled,highlighted,checked,fixed,sizebig,coloryellow 11 | */ 12 | import { DEFAULT_NAMESPACE, STATE_PREFIX, COMMON_SEPARATOR, ELEMENT_SEPARATOR, MODIFIER_SEPARATOR } from '@/const'; 13 | 14 | // eslint-disable-next-line max-params 15 | function bemCreator(namespace: string, block: string, blockSuffix: string, element: string, modifier: string) { 16 | let cls = `${namespace}-${block}`; 17 | if (blockSuffix) { 18 | cls += `${COMMON_SEPARATOR}${blockSuffix}`; 19 | } 20 | if (element) { 21 | cls += `${ELEMENT_SEPARATOR}${element}`; 22 | } 23 | if (modifier) { 24 | cls += `${MODIFIER_SEPARATOR}${modifier}`; 25 | } 26 | return cls; 27 | } 28 | 29 | /** namespace */ 30 | export const useNamespace = (block: string) => { 31 | const namespace = DEFAULT_NAMESPACE; 32 | const b = (blockSuffix = '') => bemCreator(namespace, block, blockSuffix, '', ''); 33 | const e = (element?: string) => (element ? bemCreator(namespace, block, '', element, '') : ''); 34 | const m = (modifier?: string) => (modifier ? bemCreator(namespace, block, '', '', modifier) : ''); 35 | const be = (blockSuffix?: string, element?: string) => 36 | blockSuffix && element ? bemCreator(namespace, block, blockSuffix, element, '') : ''; 37 | const em = (element?: string, modifier?: string) => 38 | element && modifier ? bemCreator(namespace, block, '', element, modifier) : ''; 39 | const bm = (blockSuffix?: string, modifier?: string) => 40 | blockSuffix && modifier ? bemCreator(namespace, block, blockSuffix, '', modifier) : ''; 41 | const bem = (blockSuffix?: string, element?: string, modifier?: string) => 42 | blockSuffix && element && modifier ? bemCreator(namespace, block, blockSuffix, element, modifier) : ''; 43 | const is = (name: string, ...args: [boolean | undefined] | []) => { 44 | const state = args.length >= 1 ? args[0] : true; 45 | return name && state ? `${STATE_PREFIX}${name}` : ''; 46 | }; 47 | return { 48 | b, 49 | e, 50 | m, 51 | be, 52 | em, 53 | bm, 54 | bem, 55 | is 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /src/style/scss/common/var.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | @use 'sass:map'; 3 | 4 | // 颜色 5 | $colors: () !default; 6 | $colors: map.merge( 7 | ( 8 | 'white': #fff, 9 | 'black': #000, 10 | 'primary': #4424e0, 11 | 'grey': #666666 12 | ), 13 | $colors 14 | ); 15 | 16 | // 主题色 17 | $color-primary: map.get($colors, 'primary') !default; 18 | $color-white: map.get($colors, 'white') !default; 19 | $color-black: map.get($colors, 'black') !default; 20 | $color-grey: map.get($colors, 'grey') !default; 21 | 22 | // 字体颜色 23 | $text-color: () !default; 24 | $text-color: map.merge( 25 | ( 26 | 'primary': rgba(0, 0, 0, 1), 27 | 'regular': rgba(0, 0, 0, 0.6), 28 | 'secondary': rgba(0, 0, 0, 0.3), 29 | 'placeholder': rgba(0, 0, 0, 0.3), 30 | 'disabled': #c0c4cc 31 | ), 32 | $text-color 33 | ); 34 | 35 | $border-color: () !default; 36 | $border-color: map.merge(( 37 | 'primary': #F4F4F4 38 | ), $border-color); 39 | 40 | // Typography 41 | $font-family: () !default; 42 | $font-family: map.merge( 43 | ( 44 | // default family 45 | '': 46 | "PingFangSC-Regular, PingFang SC", 47 | 'pf-medium': 'PingFangSC-Medium, PingFang SC', 48 | 'ali-medium': 'AlibabaSans-Medium', 49 | 'ali-regular': 'AlibabaSans-Regular' 50 | ), 51 | $font-family 52 | ); 53 | 54 | $font-size: () !default; 55 | $font-size: map.merge( 56 | ( 57 | 'extra-large': 20px, 58 | 'large': 18px, 59 | 'medium': 16px, 60 | 'base': 14px, 61 | 'small': 13px, 62 | 'extra-small': 12px 63 | ), 64 | $font-size 65 | ); 66 | 67 | $bg-color: () !default; 68 | $bg-color: map.merge( 69 | ( 70 | 'primary': #F9F9F9, 71 | 'regular': #F6F5F5, 72 | 'secondary': #F6F7F9, 73 | ), 74 | $bg-color 75 | ); 76 | 77 | $shadow: () !default; 78 | $shadow: map.merge(( 79 | 'primary': 0px 2px 6px 0px rgba(246,245,245,0.8), 80 | ), 81 | $shadow); 82 | 83 | $rounded: () !default; 84 | $rounded: map.merge( 85 | ( 86 | 'primary': 12px, 87 | ), 88 | $rounded 89 | ); 90 | 91 | // p-button 92 | $p-button: () !default; 93 | $p-button: map.deep-merge( 94 | ( 95 | 'secondary': ( 96 | 'color': #F6F7F9 97 | ), 98 | ), 99 | $p-button 100 | ); 101 | 102 | // bottom-fixing-button 103 | $bottom-fixing-button: () !default; 104 | $bottom-fixing-button: map.merge(( 105 | 'wrap-height': 60px 106 | ), $bottom-fixing-button) 107 | -------------------------------------------------------------------------------- /src/plugins/unocss.ts: -------------------------------------------------------------------------------- 1 | import useCssvar from '../hooks/use-cssvar'; 2 | import type { Rule } from 'unocss'; 3 | const uc = useCssvar(); 4 | 5 | function buildRule(keys: string[], cssvarKey: string, cssKey: string) { 6 | const rulees: Rule[] = []; 7 | keys.forEach((type: string) => 8 | rulees.push([ 9 | `${cssvarKey}${type ? `-${type}` : ''}`, 10 | { 11 | [cssKey]: `${uc.getCssvar([cssvarKey, type])} !important` 12 | } 13 | ]) 14 | ); 15 | return rulees; 16 | } 17 | 18 | export const createRules = function createRules(): Rule[] { 19 | let rules = []; 20 | /** z-index */ 21 | const zIndexs = [/^z-(\d+)$/, ([, d]: [unknown, number]) => ({ 'z-index': d })]; 22 | rules.push(zIndexs); 23 | 24 | /** text colors */ 25 | const textColors = ['primary', 'regular', 'secondary', 'placeholder', 'disabled']; 26 | rules = [...rules, ...buildRule(textColors, 'text-color', 'color')]; 27 | 28 | /** bg colors */ 29 | const bgColors = ['primary', 'secondary', 'regular']; 30 | rules = [...rules, ...buildRule(bgColors, 'bg-color', 'background-color')]; 31 | 32 | /** box shadows */ 33 | const boxShadows = ['primary']; 34 | rules = [...rules, ...buildRule(boxShadows, 'box-shadow', 'box-shadow')]; 35 | 36 | /** box rounded */ 37 | const boxRoundeds = ['primary']; 38 | rules = [...rules, ...buildRule(boxRoundeds, 'box-rounded', 'border-radius')]; 39 | 40 | /** font familys */ 41 | const fontFamilys = ['', 'pf-medium', 'ali-medium', 'ali-regular']; 42 | rules = [...rules, ...buildRule(fontFamilys, 'font-family', 'font-family')]; 43 | 44 | /** bottom-fixing-btn-padding */ 45 | rules.push([ 46 | 'p-bto-fix-btn', 47 | { 48 | 'padding-bottom': `${uc.getCssvar(['bottom-fixing-button', 'wrap-height'])} !important` 49 | } 50 | ]); 51 | return rules as Rule[]; 52 | }; 53 | 54 | export const createShortcuts = function createShortcuts() { 55 | const shortcuts = { 56 | 'wh-full': 'w-full h-full', 57 | 'flex-center': 'flex justify-center items-center', 58 | 'flex-x-center': 'flex justify-center', 59 | 'flex-y-center': 'flex items-center', 60 | 'flex-col': 'flex flex-col', 61 | 'nowrap-hidden': 'whitespace-nowrap overflow-hidden', 62 | 'ellipsis-text': 'nowrap-hidden text-ellipsis' 63 | }; 64 | return shortcuts; 65 | }; 66 | 67 | export const createTheme = function createTheme() { 68 | const theme = { 69 | colors: { 70 | primary: uc.getCssvar(['color', 'primary']) 71 | } 72 | }; 73 | return theme; 74 | }; 75 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "editor.fontLigatures": true, 6 | "editor.formatOnSave": false, 7 | "editor.guides.bracketPairs": "active", 8 | "editor.quickSuggestions": { 9 | "strings": true 10 | }, 11 | "editor.tabSize": 2, 12 | "eslint.alwaysShowStatus": true, 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "json", 21 | "jsonc", 22 | "json5", 23 | "yaml", 24 | "yml", 25 | "markdown" 26 | ], 27 | "files.associations": { 28 | "*.env.*": "dotenv" 29 | }, 30 | "files.eol": "\n", 31 | "git.enableSmartCommit": true, 32 | "gutterpreview.paths": { 33 | "@": "/src", 34 | "~@": "/src" 35 | }, 36 | "material-icon-theme.activeIconPack": "angular", 37 | "material-icon-theme.files.associations": {}, 38 | "material-icon-theme.folders.associations": { 39 | "src-tauri": "src", 40 | "enum": "typescript", 41 | "enums": "typescript", 42 | "store": "context", 43 | "stores": "context", 44 | "composable": "hook", 45 | "composables": "hook", 46 | "directive": "tools", 47 | "directives": "tools", 48 | "business": "core", 49 | "request": "api", 50 | "adapter": "middleware" 51 | }, 52 | "path-intellisense.mappings": { 53 | "@": "${workspaceFolder}/src", 54 | "~@": "${workspaceFolder}/src" 55 | }, 56 | "terminal.integrated.cursorStyle": "line", 57 | "terminal.integrated.fontSize": 14, 58 | "terminal.integrated.fontWeight": 500, 59 | "terminal.integrated.tabs.enabled": true, 60 | "workbench.iconTheme": "vs-seti", 61 | "workbench.colorTheme": "One Dark Pro", 62 | "[html]": { 63 | "editor.defaultFormatter": "esbenp.prettier-vscode" 64 | }, 65 | "[json]": { 66 | "editor.defaultFormatter": "esbenp.prettier-vscode" 67 | }, 68 | "[jsonc]": { 69 | "editor.defaultFormatter": "esbenp.prettier-vscode" 70 | }, 71 | "[javascript]": { 72 | "editor.defaultFormatter": "esbenp.prettier-vscode" 73 | }, 74 | "[javascriptreact]": { 75 | "editor.defaultFormatter": "esbenp.prettier-vscode" 76 | }, 77 | "[markdown]": { 78 | "editor.defaultFormatter": "yzhang.markdown-all-in-one" 79 | }, 80 | "[typescript]": { 81 | "editor.defaultFormatter": "esbenp.prettier-vscode" 82 | }, 83 | "[typescriptreact]": { 84 | "editor.defaultFormatter": "esbenp.prettier-vscode" 85 | }, 86 | "[vue]": { 87 | "editor.defaultFormatter": "Vue.volar" 88 | }, 89 | "nuxt.isNuxtApp": false, 90 | "liveServer.settings.port": 5501 91 | } 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hometown-h5-template", 3 | "version": "1.0.0", 4 | "description": "hometown-h5-template is a very clean, out-of-the-box, vue3-based h5 template.", 5 | "author": { 6 | "name": "HoMeTown", 7 | "email": "hometownsocool@gmail.com", 8 | "url": "https://github.com/HoMeTownSoCool" 9 | }, 10 | "license": "MIT", 11 | "homepage": "https://github.com/HoMeTownSoCool/hometown-h5-template#readme", 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/HoMeTownSoCool/hometown-h5-template.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/HoMeTownSoCool/hometown-h5-template/issues" 18 | }, 19 | "keywords": [ 20 | "vue3", 21 | "h5", 22 | "template", 23 | "vite", 24 | "cli", 25 | "vant" 26 | ], 27 | "main": "index.js", 28 | "scripts": { 29 | "dev": "vite --mode=development", 30 | "build": "pnpm run typecheck & vite build --mode=production", 31 | "build:test": "pnpm run typecheck & vite build --mode=test", 32 | "build:dev": "pnpm run typecheck & vite build --mode=dev", 33 | "lint": "eslint . --ext .vue,.js,.jsx,.ts,.tsx --fix --ignore-pattern /*.d.ts", 34 | "lint-staged": "lint-staged", 35 | "commit": "hometown git-commit", 36 | "prepare": "husky install", 37 | "typecheck": "vue-tsc --noEmit --skipLibCheck", 38 | "cleanup": "hometown cleanup", 39 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s & git add .", 40 | "docker:build": "sh ./scripts/docker/build.sh", 41 | "docker:server": "sh ./scripts/docker/server.sh", 42 | "docker:bin": "sh ./scripts/docker/bin.sh", 43 | "docker:rm-images": "sh ./scripts/docker/rm.images.sh", 44 | "docker:rm-containers": "sh ./scripts/docker/rm.containers.sh", 45 | "docker:rm-all": "pnpm docker:rm-containers & pnpm docker:rm-images" 46 | }, 47 | "dependencies": { 48 | "@hometownjs/request": "^0.0.1", 49 | "cofetti": "workspace:^1.0.0", 50 | "crypto-js": "^4.1.1", 51 | "pinia": "^2.0.13", 52 | "vant": "^4.0.10", 53 | "vconsole": "^3.14.6", 54 | "vue": "^3.2.31", 55 | "vue-router": "^4.0.14" 56 | }, 57 | "devDependencies": { 58 | "@hometownjs/command": "^0.0.4", 59 | "@hometownjs/eslint-config-vue3": "^0.0.3", 60 | "@hometownjs/vite-config-vue": "^1.0.4", 61 | "@types/crypto-js": "^4.1.1", 62 | "@unocss/preset-attributify": "^0.49.4", 63 | "@unocss/preset-icons": "^0.49.4", 64 | "@unocss/preset-uno": "^0.49.4", 65 | "@vueuse/core": "^9.13.0", 66 | "husky": "^7.0.4", 67 | "postcss-px-to-viewport-8-plugin": "^1.2.0", 68 | "sass": "^1.49.11", 69 | "typescript": "4.9.5", 70 | "unocss": "^0.49.4", 71 | "unplugin-auto-import": "^0.14.2", 72 | "vite": "^4.1.1", 73 | "vite-plugin-pages": "^0.28.0", 74 | "vite-plugin-vconsole": "^1.3.1", 75 | "vue-tsc": "^0.40.5" 76 | }, 77 | "lint-staged": { 78 | "*.{vue,js,jsx,ts,tsx}": [ 79 | "eslint --fix" 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (2023-03-02) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **deps:** fix build error ([d96e981](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/d96e981877d783d584e1ac5eb567c9242f78b4fa)) 7 | 8 | 9 | ### Features 10 | 11 | * **components:** add code to about ([e265ab9](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/e265ab935e6a9aa7cc9a582b3bed05476a0cc541)) 12 | * **components:** add example page. ([c1c10c0](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/c1c10c0ded15b323a087213f6fff5c3f08c63690)) 13 | * **components:** add some wordddddd.... ([ba5eaad](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/ba5eaadea5d43eb2579269e2b95bc214297fdeed)) 14 | * **deps:** Add @hometownjs/vite-confit-vue ([5616bb4](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/5616bb491d366da4f1dd3bbcccb6f16abbc90af3)) 15 | * **deps:** Add unocss config and optimize the style. ([7dd916e](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/7dd916eb8e4a28683b0af01af59a643d9b8fb262)) 16 | * **deps:** add vercel.json ([c5f8698](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/c5f869818a404f8fd5f24d197f9dbae3dab2072f)) 17 | * **projects:** Add const/enum/hooks/plugins/style/typings/utils/unocss... ([f8cf825](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/f8cf825fee5c77aeb00cddfb245c5c7b4089afeb)) 18 | * **projects:** add pinia store. ([3627995](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/3627995ecce9c95dd1dc057bd5188e901df6e2d4)) 19 | * **projects:** add request. ([1afbd29](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/1afbd294cf0f86557183042c10de251ee69e4b23)) 20 | * **projects:** add routes generated. ([e3acce9](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/e3acce9ed69bf67ad3067ce2368c8394fd871e11)) 21 | * **projects:** add style and auto import/utils&hooks ([4aa5ca2](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/4aa5ca25a381260619c44a5fbaeb7a6cc24720e4)) 22 | * **projects:** Add the .npmrc. ([89b61df](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/89b61df7cb90430e3fdb26f0033382020bf7e475)) 23 | * **projects:** add the license shield. ([1a748d5](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/1a748d5cc0e619debeed85c90e264b57cd7e6065)) 24 | * **projects:** add the monorepo's project of cofetti, Isolate business. ([93dacd8](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/93dacd8868af20ef45658c267ea587a650d64b74)) 25 | * **projects:** add the some content to home and add use-cofetti ([00ef92e](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/00ef92ee8ec9d3972c8982a8bd641f0a17fd6002)) 26 | * **projects:** apis arrange. ([8058faf](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/8058faf4df2091eba43fdfee2f3dd3bfe82df709)) 27 | * **projects:** update logo ([752b933](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/752b9331376cb95c40be2d2c4a6b23a6ac69bf19)) 28 | * **projects:** Vite build & server is ok. ([9da7c2e](https://github.com/HoMeTownSoCool/hometown-h5-template/commit/9da7c2ebaffe7e21be75df6508c5e949ee6409de)) 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/views/about/home/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 91 | 104 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

✨ hometown-h5-template 💥

5 |

轻松,简单,开箱即用 📦

6 |

English | 中文

7 | 8 | ## 简介 9 | 10 | [hometown-h5-template](https://github.com/HoMeTownSoCool/hometown-h5-template) 是一个基于前端前沿技术栈、干净、开箱即用的前端H5解决方案。 提供完善的前端H5开发环境,快速启动项目,为专注业务实现与开发,提高开发效率而生,也可用于学习参考。 11 | 12 | ## 预览 13 | [在线预览](https://hh5t.hometownjs.top/about/home) 14 | 15 | ## 技术栈 16 | 17 | - **Vue3** `v3.2.31` 18 | - **TypeScript** `v4.9.5` 19 | - **Vite** `v4.1.1` 20 | - **VantUI** `v4.0.10` 21 | - **Pinia** `v2.0.13` 22 | - **UnoCSS** `v0.49.4` 23 | - **sass** `v1.49.11` 24 | - **HoMeTownJS** `*.**.**` 25 | 26 | ## 特性 27 | 28 | - **最新技术栈:** Vue3、TypeScirpt、Vite4、UnoCSS、Pinia 等前沿技术开发。 29 | - **高效包管理工具:** 使用pnpm作为首选包管理工具。 30 | - **TypeScript:** 应用程序级 JavaScript。 31 | - **灵活的 CSS:** 单一主题配置入口,强大的 CssVar。 32 | - **开箱即用:** 配置丰富,易上手,开箱即用。 33 | - **monorepo架构** 内置monorepo架构,你可以不用,我不能没有。 34 | - **现代移动端自适应方案:** 使用viewport 35 | 36 | ## 开发环境推荐 37 | 38 | - **node** `v14.21.2` 39 | - **npm** `v6.14.17` 40 | - **pnpm** `v7.26.3` 41 | - **vscode** `v1.75.1` 42 | 43 | ## 安装和使用 44 | 45 | ### 克隆代码 46 | 47 | ```bash 48 | git clone https://github.com/HoMeTownSoCool/hometown-h5-template.git 49 | ``` 50 | 51 | ### 安装依赖 52 | 53 | ```bash 54 | pnpm install 55 | ``` 56 | 57 | ### 运行 58 | 59 | ```bash 60 | pnpm dev 61 | ``` 62 | 63 | ### 打包 64 | 65 | ```bash 66 | pnpm build 67 | ``` 68 | 69 | ## 目录 70 | 71 | ```text 72 | ├── LICENSE 73 | ├── README.md 74 | ├── README.zh_CN.md 75 | ├── auto-imports.d.ts # 自动导入文件 76 | ├── build # 打包相关 77 | ├── components.d.ts # 组件自动注册文件 78 | ├── dist # 打包的产物 79 | ├── index.html 80 | ├── node_modules 81 | ├── package.json 82 | ├── pnpm-lock.yaml 83 | ├── public # 静态文件 84 | ├── src 85 | ├── App.vue 86 | ├── assets # 资源 87 | ├── components # 组件 88 | ├── const # 常量 89 | ├── enum # 枚举 90 | ├── hooks # 钩子 91 | ├── main.ts # 入口 92 | ├── plugins # 插件 93 | ├── router # 路由 94 | ├── service # 服务 95 | ├── store # 状态 96 | ├── style # 样式 97 | ├── typings # 类型 98 | ├── utils # 工具 99 | └── views # 页面 100 | ├── stats.html 101 | ├── tsconfig.json # ts配置文件 102 | ├── unocss.config.ts # unocss配置文件 103 | └── vite.config.ts # vite配置文件 104 | ``` 105 | 106 | ## 代码提交 107 | 108 | 项目已经内置 angular 提交规范,直接执行 commit 命令即可。 109 | 110 | 代码提交 111 | 112 | ```bash 113 | git add . 114 | 115 | pnpm commit 116 | 117 | git push origin xx 118 | ``` 119 | 120 | ## 浏览器支持 121 | 122 | 推荐使用`Chrome 90+` 浏览器 123 | 124 | | [IE](http://godban.github.io/browsers-support-badges/) | [ Edge](http://godban.github.io/browsers-support-badges/) | [Firefox](http://godban.github.io/browsers-support-badges/) | [Chrome](http://godban.github.io/browsers-support-badges/) | [Safari](http://godban.github.io/browsers-support-badges/) | 125 | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 126 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | 127 | 128 | ## 许可证 129 | [MIT](./LICENSE) 130 | ## 作者 131 | 132 | [HoMeTown](https://juejin.cn/user/4116184668057390) 🙊 133 | 134 | ## Thanks 135 | [Soybean](https://github.com/honghuangdc) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

✨ hometown-h5-template 💥

5 |

A Vue 3 h5 template.

6 |

Be easy. Be simple. Out of the box📦

7 |

English | 中文

8 | 9 |

10 | 11 |

12 | 13 | ## Introduction 14 | 15 | [hometown-h5-template](https://github.com/HoMeTownSoCool/hometown-h5-template) It is an out-of-the-box h5 front-end solution, which provides a rich and concise development environment for front-end development friends, focuses on business, improves development efficiency, and can also be used for learning reference. 16 | 17 | ## Preview 18 | 19 | [Online preview](https://hh5t.hometownjs.top/about/home) 20 | 21 | ## Technology stack 22 | 23 | - **Vue3** `v3.2.31` 24 | - **TypeScript** `v4.9.5` 25 | - **Vite** `v4.1.1` 26 | - **VantUI** `v4.0.10` 27 | - **Pinia** `v2.0.13` 28 | - **UnoCSS** `v0.49.4` 29 | - **sass** `v1.49.11` 30 | - **HoMeTownJS** `*.**.**` 31 | 32 | ## Characteristic 33 | 34 | - **Latest technology stack:** Vue3/Vite4 and other cutting-edge technology development&efficient npm package management tool pnpm 35 | - **TypeScript:** Application-level JavaScript 36 | - **Flexible CSS:** Single theme configuration entry, powerful CssVar 37 | - **Request:** A complete network request scheme based on axios 38 | 39 | ## Recommended development environment 40 | 41 | - **node** `v14.21.2` 42 | - **npm** `v6.14.17` 43 | - **pnpm** `v7.26.3` 44 | - **vscode** `v1.75.1` 45 | 46 | ## Installation and use 47 | 48 | ### Clone Code 49 | 50 | ```bash 51 | git clone https://github.com/HoMeTownSoCool/hometown-h5-template.git 52 | ``` 53 | 54 | ### Installation dependency 55 | 56 | ```bash 57 | pnpm install 58 | ``` 59 | 60 | ### Run 61 | 62 | ```bash 63 | pnpm dev 64 | ``` 65 | 66 | ### Bulild 67 | 68 | ```bash 69 | pnpm build 70 | ``` 71 | 72 | ## Dir 73 | 74 | ```text 75 | ├── LICENSE 76 | ├── README.md 77 | ├── README.zh_CN.md 78 | ├── auto-imports.d.ts 79 | ├── build # Packaging related 80 | ├── components.d.ts 81 | ├── dist 82 | ├── index.html 83 | ├── node_modules 84 | ├── package.json 85 | ├── pnpm-lock.yaml 86 | ├── public 87 | ├── src 88 | ├── App.vue 89 | ├── assets 90 | ├── components 91 | ├── const 92 | ├── enum 93 | ├── hooks 94 | ├── main.ts 95 | ├── plugins 96 | ├── router 97 | ├── service 98 | ├── store 99 | ├── style 100 | ├── typings 101 | ├── utils 102 | └── views 103 | ├── stats.html 104 | ├── tsconfig.json 105 | ├── unocss.config.ts 106 | └── vite.config.ts 107 | ``` 108 | 109 | ## Code commit 110 | 111 | The project has built in the regular submission specification. Just execute the commit command directly. 112 | 113 | code commit: 114 | 115 | ```bash 116 | git add . 117 | 118 | pnpm commit 119 | 120 | git push origin xx 121 | ``` 122 | 123 | ## Browser support 124 | 125 | `Chrome 90+` browser is recommended 126 | 127 | | [IE](http://godban.github.io/browsers-support-badges/) | [ Edge](http://godban.github.io/browsers-support-badges/) | [Firefox](http://godban.github.io/browsers-support-badges/) | [Chrome](http://godban.github.io/browsers-support-badges/) | [Safari](http://godban.github.io/browsers-support-badges/) | 128 | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 129 | | not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | 130 | 131 | ## License 132 | 133 | [MIT](./LICENSE) 134 | 135 | ## CHANGELOG 136 | 137 | [CHANGELOG]('./CHANGELOG') 138 | 139 | ## Author 140 | 141 | [HoMeTown](https://juejin.cn/user/4116184668057390) 🙊 142 | 143 | ## Thanks 144 | 145 | [Soybean](https://github.com/honghuangdc) 146 | -------------------------------------------------------------------------------- /src/assets/js/confetti.browser.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using Terser v5.10.0. 3 | * Original file: /npm/canvas-confetti@1.5.1/dist/confetti.browser.js 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | !(function (t, e) { 8 | !(function t(e, n, a, i) { 9 | var o = !!( 10 | e.Worker && 11 | e.Blob && 12 | e.Promise && 13 | e.OffscreenCanvas && 14 | e.OffscreenCanvasRenderingContext2D && 15 | e.HTMLCanvasElement && 16 | e.HTMLCanvasElement.prototype.transferControlToOffscreen && 17 | e.URL && 18 | e.URL.createObjectURL 19 | ); 20 | function r() {} 21 | function l(t) { 22 | var a = n.exports.Promise, 23 | i = void 0 !== a ? a : e.Promise; 24 | return 'function' == typeof i ? new i(t) : (t(r, r), null); 25 | } 26 | var c, 27 | s, 28 | u, 29 | d, 30 | f, 31 | h, 32 | m, 33 | g, 34 | b, 35 | v = 36 | ((u = Math.floor(1e3 / 60)), 37 | (d = {}), 38 | (f = 0), 39 | 'function' == typeof requestAnimationFrame && 40 | 'function' == typeof cancelAnimationFrame 41 | ? ((c = function (t) { 42 | var e = Math.random(); 43 | return ( 44 | (d[e] = requestAnimationFrame(function n(a) { 45 | f === a || f + u - 1 < a 46 | ? ((f = a), delete d[e], t()) 47 | : (d[e] = requestAnimationFrame(n)); 48 | })), 49 | e 50 | ); 51 | }), 52 | (s = function (t) { 53 | d[t] && cancelAnimationFrame(d[t]); 54 | })) 55 | : ((c = function (t) { 56 | return setTimeout(t, u); 57 | }), 58 | (s = function (t) { 59 | return clearTimeout(t); 60 | })), 61 | { frame: c, cancel: s }), 62 | p = 63 | ((g = {}), 64 | function () { 65 | if (h) return h; 66 | if (!a && o) { 67 | var e = [ 68 | 'var CONFETTI, SIZE = {}, module = {};', 69 | '(' + t.toString() + ')(this, module, true, SIZE);', 70 | 'onmessage = function(msg) {', 71 | ' if (msg.data.options) {', 72 | ' CONFETTI(msg.data.options).then(function () {', 73 | ' if (msg.data.callback) {', 74 | ' postMessage({ callback: msg.data.callback });', 75 | ' }', 76 | ' });', 77 | ' } else if (msg.data.reset) {', 78 | ' CONFETTI.reset();', 79 | ' } else if (msg.data.resize) {', 80 | ' SIZE.width = msg.data.resize.width;', 81 | ' SIZE.height = msg.data.resize.height;', 82 | ' } else if (msg.data.canvas) {', 83 | ' SIZE.width = msg.data.canvas.width;', 84 | ' SIZE.height = msg.data.canvas.height;', 85 | ' CONFETTI = module.exports.create(msg.data.canvas);', 86 | ' }', 87 | '}', 88 | ].join('\n'); 89 | try { 90 | h = new Worker(URL.createObjectURL(new Blob([e]))); 91 | } catch (t) { 92 | return ( 93 | void 0 !== typeof console && 94 | 'function' == typeof console.warn && 95 | console.warn('🎊 Could not load worker', t), 96 | null 97 | ); 98 | } 99 | !(function (t) { 100 | function e(e, n) { 101 | t.postMessage({ 102 | options: e || {}, 103 | callback: n, 104 | }); 105 | } 106 | (t.init = function (e) { 107 | var n = e.transferControlToOffscreen(); 108 | t.postMessage({ canvas: n }, [n]); 109 | }), 110 | (t.fire = function (n, a, i) { 111 | if (m) return e(n, null), m; 112 | var o = Math.random().toString(36).slice(2); 113 | return (m = l(function (a) { 114 | function r(e) { 115 | e.data.callback === o && 116 | (delete g[o], 117 | t.removeEventListener( 118 | 'message', 119 | r 120 | ), 121 | (m = null), 122 | i(), 123 | a()); 124 | } 125 | t.addEventListener('message', r), 126 | e(n, o), 127 | (g[o] = r.bind(null, { 128 | data: { callback: o }, 129 | })); 130 | })); 131 | }), 132 | (t.reset = function () { 133 | for (var e in (t.postMessage({ reset: !0 }), 134 | g)) 135 | g[e](), delete g[e]; 136 | }); 137 | })(h); 138 | } 139 | return h; 140 | }), 141 | y = { 142 | particleCount: 50, 143 | angle: 90, 144 | spread: 45, 145 | startVelocity: 45, 146 | decay: 0.9, 147 | gravity: 1, 148 | drift: 0, 149 | ticks: 200, 150 | x: 0.5, 151 | y: 0.5, 152 | shapes: ['square', 'circle'], 153 | zIndex: 100, 154 | colors: [ 155 | '#26ccff', 156 | '#a25afd', 157 | '#ff5e7e', 158 | '#88ff5a', 159 | '#fcff42', 160 | '#ffa62d', 161 | '#ff36ff', 162 | ], 163 | disableForReducedMotion: !1, 164 | scalar: 1, 165 | }; 166 | function M(t, e, n) { 167 | return (function (t, e) { 168 | return e ? e(t) : t; 169 | })(t && null != t[e] ? t[e] : y[e], n); 170 | } 171 | function w(t) { 172 | return t < 0 ? 0 : Math.floor(t); 173 | } 174 | function x(t) { 175 | return parseInt(t, 16); 176 | } 177 | function C(t) { 178 | return t.map(k); 179 | } 180 | function k(t) { 181 | var e = String(t).replace(/[^0-9a-f]/gi, ''); 182 | return ( 183 | e.length < 6 && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]), 184 | { 185 | r: x(e.substring(0, 2)), 186 | g: x(e.substring(2, 4)), 187 | b: x(e.substring(4, 6)), 188 | } 189 | ); 190 | } 191 | function I(t) { 192 | (t.width = document.documentElement.clientWidth), 193 | (t.height = document.documentElement.clientHeight); 194 | } 195 | function S(t) { 196 | var e = t.getBoundingClientRect(); 197 | (t.width = e.width), (t.height = e.height); 198 | } 199 | function T(t, e, n, o, r) { 200 | var c, 201 | s, 202 | u = e.slice(), 203 | d = t.getContext('2d'), 204 | f = l(function (e) { 205 | function l() { 206 | (c = s = null), 207 | d.clearRect(0, 0, o.width, o.height), 208 | r(), 209 | e(); 210 | } 211 | (c = v.frame(function e() { 212 | !a || 213 | (o.width === i.width && o.height === i.height) || 214 | ((o.width = t.width = i.width), 215 | (o.height = t.height = i.height)), 216 | o.width || 217 | o.height || 218 | (n(t), 219 | (o.width = t.width), 220 | (o.height = t.height)), 221 | d.clearRect(0, 0, o.width, o.height), 222 | (u = u.filter(function (t) { 223 | return (function (t, e) { 224 | (e.x += 225 | Math.cos(e.angle2D) * e.velocity + 226 | e.drift), 227 | (e.y += 228 | Math.sin(e.angle2D) * e.velocity + 229 | e.gravity), 230 | (e.wobble += e.wobbleSpeed), 231 | (e.velocity *= e.decay), 232 | (e.tiltAngle += 0.1), 233 | (e.tiltSin = Math.sin(e.tiltAngle)), 234 | (e.tiltCos = Math.cos(e.tiltAngle)), 235 | (e.random = Math.random() + 2), 236 | (e.wobbleX = 237 | e.x + 238 | 10 * e.scalar * Math.cos(e.wobble)), 239 | (e.wobbleY = 240 | e.y + 241 | 10 * e.scalar * Math.sin(e.wobble)); 242 | var n = e.tick++ / e.totalTicks, 243 | a = e.x + e.random * e.tiltCos, 244 | i = e.y + e.random * e.tiltSin, 245 | o = e.wobbleX + e.random * e.tiltCos, 246 | r = e.wobbleY + e.random * e.tiltSin; 247 | return ( 248 | (t.fillStyle = 249 | 'rgba(' + 250 | e.color.r + 251 | ', ' + 252 | e.color.g + 253 | ', ' + 254 | e.color.b + 255 | ', ' + 256 | (1 - n) + 257 | ')'), 258 | t.beginPath(), 259 | 'circle' === e.shape 260 | ? t.ellipse 261 | ? t.ellipse( 262 | e.x, 263 | e.y, 264 | Math.abs(o - a) * 265 | e.ovalScalar, 266 | Math.abs(r - i) * 267 | e.ovalScalar, 268 | (Math.PI / 10) * e.wobble, 269 | 0, 270 | 2 * Math.PI 271 | ) 272 | : (function ( 273 | t, 274 | e, 275 | n, 276 | a, 277 | i, 278 | o, 279 | r, 280 | l, 281 | c 282 | ) { 283 | t.save(), 284 | t.translate(e, n), 285 | t.rotate(o), 286 | t.scale(a, i), 287 | t.arc( 288 | 0, 289 | 0, 290 | 1, 291 | r, 292 | l, 293 | c 294 | ), 295 | t.restore(); 296 | })( 297 | t, 298 | e.x, 299 | e.y, 300 | Math.abs(o - a) * 301 | e.ovalScalar, 302 | Math.abs(r - i) * 303 | e.ovalScalar, 304 | (Math.PI / 10) * e.wobble, 305 | 0, 306 | 2 * Math.PI 307 | ) 308 | : (t.moveTo( 309 | Math.floor(e.x), 310 | Math.floor(e.y) 311 | ), 312 | t.lineTo( 313 | Math.floor(e.wobbleX), 314 | Math.floor(i) 315 | ), 316 | t.lineTo( 317 | Math.floor(o), 318 | Math.floor(r) 319 | ), 320 | t.lineTo( 321 | Math.floor(a), 322 | Math.floor(e.wobbleY) 323 | )), 324 | t.closePath(), 325 | t.fill(), 326 | e.tick < e.totalTicks 327 | ); 328 | })(d, t); 329 | })), 330 | u.length ? (c = v.frame(e)) : l(); 331 | })), 332 | (s = l); 333 | }); 334 | return { 335 | addFettis: function (t) { 336 | return (u = u.concat(t)), f; 337 | }, 338 | canvas: t, 339 | promise: f, 340 | reset: function () { 341 | c && v.cancel(c), s && s(); 342 | }, 343 | }; 344 | } 345 | function E(t, n) { 346 | var a, 347 | i = !t, 348 | r = !!M(n || {}, 'resize'), 349 | c = M(n, 'disableForReducedMotion', Boolean), 350 | s = o && !!M(n || {}, 'useWorker') ? p() : null, 351 | u = i ? I : S, 352 | d = !(!t || !s) && !!t.__confetti_initialized, 353 | f = 354 | 'function' == typeof matchMedia && 355 | matchMedia('(prefers-reduced-motion)').matches; 356 | function h(e, n, i) { 357 | for ( 358 | var o, 359 | r, 360 | l, 361 | c, 362 | s, 363 | d = M(e, 'particleCount', w), 364 | f = M(e, 'angle', Number), 365 | h = M(e, 'spread', Number), 366 | m = M(e, 'startVelocity', Number), 367 | g = M(e, 'decay', Number), 368 | b = M(e, 'gravity', Number), 369 | v = M(e, 'drift', Number), 370 | p = M(e, 'colors', C), 371 | y = M(e, 'ticks', Number), 372 | x = M(e, 'shapes'), 373 | k = M(e, 'scalar'), 374 | I = (function (t) { 375 | var e = M(t, 'origin', Object); 376 | return ( 377 | (e.x = M(e, 'x', Number)), 378 | (e.y = M(e, 'y', Number)), 379 | e 380 | ); 381 | })(e), 382 | S = d, 383 | E = [], 384 | F = t.width * I.x, 385 | N = t.height * I.y; 386 | S--; 387 | 388 | ) 389 | E.push( 390 | ((o = { 391 | x: F, 392 | y: N, 393 | angle: f, 394 | spread: h, 395 | startVelocity: m, 396 | color: p[S % p.length], 397 | shape: x[ 398 | ((c = 0), 399 | (s = x.length), 400 | Math.floor(Math.random() * (s - c)) + c) 401 | ], 402 | ticks: y, 403 | decay: g, 404 | gravity: b, 405 | drift: v, 406 | scalar: k, 407 | }), 408 | (r = void 0), 409 | (l = void 0), 410 | (r = o.angle * (Math.PI / 180)), 411 | (l = o.spread * (Math.PI / 180)), 412 | { 413 | x: o.x, 414 | y: o.y, 415 | wobble: 10 * Math.random(), 416 | wobbleSpeed: Math.min( 417 | 0.11, 418 | 0.1 * Math.random() + 0.05 419 | ), 420 | velocity: 421 | 0.5 * o.startVelocity + 422 | Math.random() * o.startVelocity, 423 | angle2D: -r + (0.5 * l - Math.random() * l), 424 | tiltAngle: (0.5 * Math.random() + 0.25) * Math.PI, 425 | color: o.color, 426 | shape: o.shape, 427 | tick: 0, 428 | totalTicks: o.ticks, 429 | decay: o.decay, 430 | drift: o.drift, 431 | random: Math.random() + 2, 432 | tiltSin: 0, 433 | tiltCos: 0, 434 | wobbleX: 0, 435 | wobbleY: 0, 436 | gravity: 3 * o.gravity, 437 | ovalScalar: 0.6, 438 | scalar: o.scalar, 439 | }) 440 | ); 441 | return a ? a.addFettis(E) : (a = T(t, E, u, n, i)).promise; 442 | } 443 | function m(n) { 444 | var o = c || M(n, 'disableForReducedMotion', Boolean), 445 | m = M(n, 'zIndex', Number); 446 | if (o && f) 447 | return l(function (t) { 448 | t(); 449 | }); 450 | i && a 451 | ? (t = a.canvas) 452 | : i && 453 | !t && 454 | ((t = (function (t) { 455 | var e = document.createElement('canvas'); 456 | return ( 457 | (e.style.position = 'fixed'), 458 | (e.style.top = '0px'), 459 | (e.style.left = '0px'), 460 | (e.style.pointerEvents = 'none'), 461 | (e.style.zIndex = t), 462 | e 463 | ); 464 | })(m)), 465 | document.body.appendChild(t)), 466 | r && !d && u(t); 467 | var g = { width: t.width, height: t.height }; 468 | function b() { 469 | if (s) { 470 | var e = { 471 | getBoundingClientRect: function () { 472 | if (!i) return t.getBoundingClientRect(); 473 | }, 474 | }; 475 | return ( 476 | u(e), 477 | void s.postMessage({ 478 | resize: { width: e.width, height: e.height }, 479 | }) 480 | ); 481 | } 482 | g.width = g.height = null; 483 | } 484 | function v() { 485 | (a = null), 486 | r && e.removeEventListener('resize', b), 487 | i && 488 | t && 489 | (document.body.removeChild(t), 490 | (t = null), 491 | (d = !1)); 492 | } 493 | return ( 494 | s && !d && s.init(t), 495 | (d = !0), 496 | s && (t.__confetti_initialized = !0), 497 | r && e.addEventListener('resize', b, !1), 498 | s ? s.fire(n, g, v) : h(n, g, v) 499 | ); 500 | } 501 | return ( 502 | (m.reset = function () { 503 | s && s.reset(), a && a.reset(); 504 | }), 505 | m 506 | ); 507 | } 508 | function F() { 509 | return b || (b = E(null, { useWorker: !0, resize: !0 })), b; 510 | } 511 | (n.exports = function () { 512 | return F().apply(this, arguments); 513 | }), 514 | (n.exports.reset = function () { 515 | F().reset(); 516 | }), 517 | (n.exports.create = E); 518 | })( 519 | (function () { 520 | return void 0 !== t 521 | ? t 522 | : 'undefined' != typeof self 523 | ? self 524 | : this || {}; 525 | })(), 526 | e, 527 | !1 528 | ), 529 | (t.confetti = e.exports); 530 | })(window, {}); 531 | //# sourceMappingURL=/sm/ab60d7fb9bf5b5ded42c77782b65b071de85f56c21da42948364d6b2b1961762.map 532 | -------------------------------------------------------------------------------- /packages/cofetti/assets/confetti.browser.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using Terser v5.10.0. 3 | * Original file: /npm/canvas-confetti@1.5.1/dist/confetti.browser.js 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | !(function (t, e) { 8 | !(function t(e, n, a, i) { 9 | var o = !!( 10 | e.Worker && 11 | e.Blob && 12 | e.Promise && 13 | e.OffscreenCanvas && 14 | e.OffscreenCanvasRenderingContext2D && 15 | e.HTMLCanvasElement && 16 | e.HTMLCanvasElement.prototype.transferControlToOffscreen && 17 | e.URL && 18 | e.URL.createObjectURL 19 | ); 20 | function r() {} 21 | function l(t) { 22 | var a = n.exports.Promise, 23 | i = void 0 !== a ? a : e.Promise; 24 | return 'function' == typeof i ? new i(t) : (t(r, r), null); 25 | } 26 | var c, 27 | s, 28 | u, 29 | d, 30 | f, 31 | h, 32 | m, 33 | g, 34 | b, 35 | v = 36 | ((u = Math.floor(1e3 / 60)), 37 | (d = {}), 38 | (f = 0), 39 | 'function' == typeof requestAnimationFrame && 40 | 'function' == typeof cancelAnimationFrame 41 | ? ((c = function (t) { 42 | var e = Math.random(); 43 | return ( 44 | (d[e] = requestAnimationFrame(function n(a) { 45 | f === a || f + u - 1 < a 46 | ? ((f = a), delete d[e], t()) 47 | : (d[e] = requestAnimationFrame(n)); 48 | })), 49 | e 50 | ); 51 | }), 52 | (s = function (t) { 53 | d[t] && cancelAnimationFrame(d[t]); 54 | })) 55 | : ((c = function (t) { 56 | return setTimeout(t, u); 57 | }), 58 | (s = function (t) { 59 | return clearTimeout(t); 60 | })), 61 | { frame: c, cancel: s }), 62 | p = 63 | ((g = {}), 64 | function () { 65 | if (h) return h; 66 | if (!a && o) { 67 | var e = [ 68 | 'var CONFETTI, SIZE = {}, module = {};', 69 | '(' + t.toString() + ')(this, module, true, SIZE);', 70 | 'onmessage = function(msg) {', 71 | ' if (msg.data.options) {', 72 | ' CONFETTI(msg.data.options).then(function () {', 73 | ' if (msg.data.callback) {', 74 | ' postMessage({ callback: msg.data.callback });', 75 | ' }', 76 | ' });', 77 | ' } else if (msg.data.reset) {', 78 | ' CONFETTI.reset();', 79 | ' } else if (msg.data.resize) {', 80 | ' SIZE.width = msg.data.resize.width;', 81 | ' SIZE.height = msg.data.resize.height;', 82 | ' } else if (msg.data.canvas) {', 83 | ' SIZE.width = msg.data.canvas.width;', 84 | ' SIZE.height = msg.data.canvas.height;', 85 | ' CONFETTI = module.exports.create(msg.data.canvas);', 86 | ' }', 87 | '}', 88 | ].join('\n'); 89 | try { 90 | h = new Worker(URL.createObjectURL(new Blob([e]))); 91 | } catch (t) { 92 | return ( 93 | void 0 !== typeof console && 94 | 'function' == typeof console.warn && 95 | console.warn('🎊 Could not load worker', t), 96 | null 97 | ); 98 | } 99 | !(function (t) { 100 | function e(e, n) { 101 | t.postMessage({ 102 | options: e || {}, 103 | callback: n, 104 | }); 105 | } 106 | (t.init = function (e) { 107 | var n = e.transferControlToOffscreen(); 108 | t.postMessage({ canvas: n }, [n]); 109 | }), 110 | (t.fire = function (n, a, i) { 111 | if (m) return e(n, null), m; 112 | var o = Math.random().toString(36).slice(2); 113 | return (m = l(function (a) { 114 | function r(e) { 115 | e.data.callback === o && 116 | (delete g[o], 117 | t.removeEventListener( 118 | 'message', 119 | r 120 | ), 121 | (m = null), 122 | i(), 123 | a()); 124 | } 125 | t.addEventListener('message', r), 126 | e(n, o), 127 | (g[o] = r.bind(null, { 128 | data: { callback: o }, 129 | })); 130 | })); 131 | }), 132 | (t.reset = function () { 133 | for (var e in (t.postMessage({ reset: !0 }), 134 | g)) 135 | g[e](), delete g[e]; 136 | }); 137 | })(h); 138 | } 139 | return h; 140 | }), 141 | y = { 142 | particleCount: 50, 143 | angle: 90, 144 | spread: 45, 145 | startVelocity: 45, 146 | decay: 0.9, 147 | gravity: 1, 148 | drift: 0, 149 | ticks: 200, 150 | x: 0.5, 151 | y: 0.5, 152 | shapes: ['square', 'circle'], 153 | zIndex: 100, 154 | colors: [ 155 | '#26ccff', 156 | '#a25afd', 157 | '#ff5e7e', 158 | '#88ff5a', 159 | '#fcff42', 160 | '#ffa62d', 161 | '#ff36ff', 162 | ], 163 | disableForReducedMotion: !1, 164 | scalar: 1, 165 | }; 166 | function M(t, e, n) { 167 | return (function (t, e) { 168 | return e ? e(t) : t; 169 | })(t && null != t[e] ? t[e] : y[e], n); 170 | } 171 | function w(t) { 172 | return t < 0 ? 0 : Math.floor(t); 173 | } 174 | function x(t) { 175 | return parseInt(t, 16); 176 | } 177 | function C(t) { 178 | return t.map(k); 179 | } 180 | function k(t) { 181 | var e = String(t).replace(/[^0-9a-f]/gi, ''); 182 | return ( 183 | e.length < 6 && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]), 184 | { 185 | r: x(e.substring(0, 2)), 186 | g: x(e.substring(2, 4)), 187 | b: x(e.substring(4, 6)), 188 | } 189 | ); 190 | } 191 | function I(t) { 192 | (t.width = document.documentElement.clientWidth), 193 | (t.height = document.documentElement.clientHeight); 194 | } 195 | function S(t) { 196 | var e = t.getBoundingClientRect(); 197 | (t.width = e.width), (t.height = e.height); 198 | } 199 | function T(t, e, n, o, r) { 200 | var c, 201 | s, 202 | u = e.slice(), 203 | d = t.getContext('2d'), 204 | f = l(function (e) { 205 | function l() { 206 | (c = s = null), 207 | d.clearRect(0, 0, o.width, o.height), 208 | r(), 209 | e(); 210 | } 211 | (c = v.frame(function e() { 212 | !a || 213 | (o.width === i.width && o.height === i.height) || 214 | ((o.width = t.width = i.width), 215 | (o.height = t.height = i.height)), 216 | o.width || 217 | o.height || 218 | (n(t), 219 | (o.width = t.width), 220 | (o.height = t.height)), 221 | d.clearRect(0, 0, o.width, o.height), 222 | (u = u.filter(function (t) { 223 | return (function (t, e) { 224 | (e.x += 225 | Math.cos(e.angle2D) * e.velocity + 226 | e.drift), 227 | (e.y += 228 | Math.sin(e.angle2D) * e.velocity + 229 | e.gravity), 230 | (e.wobble += e.wobbleSpeed), 231 | (e.velocity *= e.decay), 232 | (e.tiltAngle += 0.1), 233 | (e.tiltSin = Math.sin(e.tiltAngle)), 234 | (e.tiltCos = Math.cos(e.tiltAngle)), 235 | (e.random = Math.random() + 2), 236 | (e.wobbleX = 237 | e.x + 238 | 10 * e.scalar * Math.cos(e.wobble)), 239 | (e.wobbleY = 240 | e.y + 241 | 10 * e.scalar * Math.sin(e.wobble)); 242 | var n = e.tick++ / e.totalTicks, 243 | a = e.x + e.random * e.tiltCos, 244 | i = e.y + e.random * e.tiltSin, 245 | o = e.wobbleX + e.random * e.tiltCos, 246 | r = e.wobbleY + e.random * e.tiltSin; 247 | return ( 248 | (t.fillStyle = 249 | 'rgba(' + 250 | e.color.r + 251 | ', ' + 252 | e.color.g + 253 | ', ' + 254 | e.color.b + 255 | ', ' + 256 | (1 - n) + 257 | ')'), 258 | t.beginPath(), 259 | 'circle' === e.shape 260 | ? t.ellipse 261 | ? t.ellipse( 262 | e.x, 263 | e.y, 264 | Math.abs(o - a) * 265 | e.ovalScalar, 266 | Math.abs(r - i) * 267 | e.ovalScalar, 268 | (Math.PI / 10) * e.wobble, 269 | 0, 270 | 2 * Math.PI 271 | ) 272 | : (function ( 273 | t, 274 | e, 275 | n, 276 | a, 277 | i, 278 | o, 279 | r, 280 | l, 281 | c 282 | ) { 283 | t.save(), 284 | t.translate(e, n), 285 | t.rotate(o), 286 | t.scale(a, i), 287 | t.arc( 288 | 0, 289 | 0, 290 | 1, 291 | r, 292 | l, 293 | c 294 | ), 295 | t.restore(); 296 | })( 297 | t, 298 | e.x, 299 | e.y, 300 | Math.abs(o - a) * 301 | e.ovalScalar, 302 | Math.abs(r - i) * 303 | e.ovalScalar, 304 | (Math.PI / 10) * e.wobble, 305 | 0, 306 | 2 * Math.PI 307 | ) 308 | : (t.moveTo( 309 | Math.floor(e.x), 310 | Math.floor(e.y) 311 | ), 312 | t.lineTo( 313 | Math.floor(e.wobbleX), 314 | Math.floor(i) 315 | ), 316 | t.lineTo( 317 | Math.floor(o), 318 | Math.floor(r) 319 | ), 320 | t.lineTo( 321 | Math.floor(a), 322 | Math.floor(e.wobbleY) 323 | )), 324 | t.closePath(), 325 | t.fill(), 326 | e.tick < e.totalTicks 327 | ); 328 | })(d, t); 329 | })), 330 | u.length ? (c = v.frame(e)) : l(); 331 | })), 332 | (s = l); 333 | }); 334 | return { 335 | addFettis: function (t) { 336 | return (u = u.concat(t)), f; 337 | }, 338 | canvas: t, 339 | promise: f, 340 | reset: function () { 341 | c && v.cancel(c), s && s(); 342 | }, 343 | }; 344 | } 345 | function E(t, n) { 346 | var a, 347 | i = !t, 348 | r = !!M(n || {}, 'resize'), 349 | c = M(n, 'disableForReducedMotion', Boolean), 350 | s = o && !!M(n || {}, 'useWorker') ? p() : null, 351 | u = i ? I : S, 352 | d = !(!t || !s) && !!t.__confetti_initialized, 353 | f = 354 | 'function' == typeof matchMedia && 355 | matchMedia('(prefers-reduced-motion)').matches; 356 | function h(e, n, i) { 357 | for ( 358 | var o, 359 | r, 360 | l, 361 | c, 362 | s, 363 | d = M(e, 'particleCount', w), 364 | f = M(e, 'angle', Number), 365 | h = M(e, 'spread', Number), 366 | m = M(e, 'startVelocity', Number), 367 | g = M(e, 'decay', Number), 368 | b = M(e, 'gravity', Number), 369 | v = M(e, 'drift', Number), 370 | p = M(e, 'colors', C), 371 | y = M(e, 'ticks', Number), 372 | x = M(e, 'shapes'), 373 | k = M(e, 'scalar'), 374 | I = (function (t) { 375 | var e = M(t, 'origin', Object); 376 | return ( 377 | (e.x = M(e, 'x', Number)), 378 | (e.y = M(e, 'y', Number)), 379 | e 380 | ); 381 | })(e), 382 | S = d, 383 | E = [], 384 | F = t.width * I.x, 385 | N = t.height * I.y; 386 | S--; 387 | 388 | ) 389 | E.push( 390 | ((o = { 391 | x: F, 392 | y: N, 393 | angle: f, 394 | spread: h, 395 | startVelocity: m, 396 | color: p[S % p.length], 397 | shape: x[ 398 | ((c = 0), 399 | (s = x.length), 400 | Math.floor(Math.random() * (s - c)) + c) 401 | ], 402 | ticks: y, 403 | decay: g, 404 | gravity: b, 405 | drift: v, 406 | scalar: k, 407 | }), 408 | (r = void 0), 409 | (l = void 0), 410 | (r = o.angle * (Math.PI / 180)), 411 | (l = o.spread * (Math.PI / 180)), 412 | { 413 | x: o.x, 414 | y: o.y, 415 | wobble: 10 * Math.random(), 416 | wobbleSpeed: Math.min( 417 | 0.11, 418 | 0.1 * Math.random() + 0.05 419 | ), 420 | velocity: 421 | 0.5 * o.startVelocity + 422 | Math.random() * o.startVelocity, 423 | angle2D: -r + (0.5 * l - Math.random() * l), 424 | tiltAngle: (0.5 * Math.random() + 0.25) * Math.PI, 425 | color: o.color, 426 | shape: o.shape, 427 | tick: 0, 428 | totalTicks: o.ticks, 429 | decay: o.decay, 430 | drift: o.drift, 431 | random: Math.random() + 2, 432 | tiltSin: 0, 433 | tiltCos: 0, 434 | wobbleX: 0, 435 | wobbleY: 0, 436 | gravity: 3 * o.gravity, 437 | ovalScalar: 0.6, 438 | scalar: o.scalar, 439 | }) 440 | ); 441 | return a ? a.addFettis(E) : (a = T(t, E, u, n, i)).promise; 442 | } 443 | function m(n) { 444 | var o = c || M(n, 'disableForReducedMotion', Boolean), 445 | m = M(n, 'zIndex', Number); 446 | if (o && f) 447 | return l(function (t) { 448 | t(); 449 | }); 450 | i && a 451 | ? (t = a.canvas) 452 | : i && 453 | !t && 454 | ((t = (function (t) { 455 | var e = document.createElement('canvas'); 456 | return ( 457 | (e.style.position = 'fixed'), 458 | (e.style.top = '0px'), 459 | (e.style.left = '0px'), 460 | (e.style.pointerEvents = 'none'), 461 | (e.style.zIndex = t), 462 | e 463 | ); 464 | })(m)), 465 | document.body.appendChild(t)), 466 | r && !d && u(t); 467 | var g = { width: t.width, height: t.height }; 468 | function b() { 469 | if (s) { 470 | var e = { 471 | getBoundingClientRect: function () { 472 | if (!i) return t.getBoundingClientRect(); 473 | }, 474 | }; 475 | return ( 476 | u(e), 477 | void s.postMessage({ 478 | resize: { width: e.width, height: e.height }, 479 | }) 480 | ); 481 | } 482 | g.width = g.height = null; 483 | } 484 | function v() { 485 | (a = null), 486 | r && e.removeEventListener('resize', b), 487 | i && 488 | t && 489 | (document.body.removeChild(t), 490 | (t = null), 491 | (d = !1)); 492 | } 493 | return ( 494 | s && !d && s.init(t), 495 | (d = !0), 496 | s && (t.__confetti_initialized = !0), 497 | r && e.addEventListener('resize', b, !1), 498 | s ? s.fire(n, g, v) : h(n, g, v) 499 | ); 500 | } 501 | return ( 502 | (m.reset = function () { 503 | s && s.reset(), a && a.reset(); 504 | }), 505 | m 506 | ); 507 | } 508 | function F() { 509 | return b || (b = E(null, { useWorker: !0, resize: !0 })), b; 510 | } 511 | (n.exports = function () { 512 | return F().apply(this, arguments); 513 | }), 514 | (n.exports.reset = function () { 515 | F().reset(); 516 | }), 517 | (n.exports.create = E); 518 | })( 519 | (function () { 520 | return void 0 !== t 521 | ? t 522 | : 'undefined' != typeof self 523 | ? self 524 | : this || {}; 525 | })(), 526 | e, 527 | !1 528 | ), 529 | (t.confetti = e.exports); 530 | })(window, {}); 531 | //# sourceMappingURL=/sm/ab60d7fb9bf5b5ded42c77782b65b071de85f56c21da42948364d6b2b1961762.map 532 | --------------------------------------------------------------------------------