Commit 770b0c75 authored by wangtao's avatar wangtao

Initial commit

parents
# 本地测试
#VITE_BASE_URL=http://192.168.4.94:8000
# 国内测试
#VITE_BASE_URL=https://pay-rys.zhubei.cn
# 海外测试
VITE_BASE_URL=http://127.0.0.1:8000
VITE_BASE_URL=https://api.bmiss.live
VITE_BASE_URL=
\ No newline at end of file
VITE_BASE_URL=https://api.bmiss3.zhubei.cn
components.d.ts
node_modules
\ No newline at end of file
module.exports = {
env: {
browser: true,
es2021: true
},
extends: ["eslint:recommended", "plugin:vue/vue3-essential", "plugin:@typescript-eslint/recommended"],
overrides: [
{
files: ["*.d.ts", "src/types/*", "src/**/apis.ts"],
rules: {
"no-unused-vars": 0
}
}
],
parser: "vue-eslint-parser",
parserOptions: {
ecmaVersion: "latest",
parser: "@typescript-eslint/parser",
sourceType: "module"
},
plugins: ["vue", "@typescript-eslint"],
rules: {
quotes: ["error", "double"],
semi: ["error", "always"],
"vue/valid-define-emits": 0,
"@typescript-eslint/ban-ts-comment": 0,
"vue/multi-word-component-names": 0,
"vue/no-mutating-props": 0,
"prefer-template": 2,
"@typescript-eslint/no-explicit-any": 2,
"linebreak-style": 0,
"no-debugger": 1,
"no-mixed-spaces-and-tabs": 0,
"vue/no-setup-props-destructure": 0,
"no-undef": 0,
"max-params": ["error", 3],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_" // 使用正则匹配需要忽略的参数
}
]
}
};
\ No newline at end of file
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
invite-friends
# Editor directories and files
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
components.d.ts
.vscode
*-base
.eslintcache
/src/autoImport.d.ts
*.zip
production-*
standard-*
test-*
\ No newline at end of file
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Run eslint
pnpm lint-clear
# If eslint has errors or warnings, prevent the commit
if [ $? -gt 0 ]; then
echo "ESLint has errors or warnings. Please fix them before committing."
exit 1
fi
{
"semi": true,
"singleQuote": false,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid",
"tabWidth": 4,
"printWidth": 150,
"proseWrap": "preserve",
"useTabs": true
}
\ No newline at end of file
#### 接口地址: https://console-docs.apipost.cn/preview/49d3a779f1442055/1aca158c259c744f?target_id=908272f8-83a9-4e0a-95f2-6190cdbed7f5
#### 主要采用vue ^3.2.45,vue-router ^4.1.6, eslint ^8.4.10, postcss-px-to-viewport ^1.1.1 , stylus ^0.59.0, vite ^4.0.0;
#### 已引入环境变量,如需修改,请自行修改;
#### 打包时只有在生产环境下关闭console输出,路由在所有环境为hash模式,路由baseurl为package.name,打包时关闭eslint代码检查,生产环境自动关闭vconsole调试工具
#### 所有全局interface均放在`src/interface`文件夹中
#### 所有全局type均放在`src/types`文件夹中
#### 所有全局实体类均放在 `src/entities`文件夹中
#### 路由创建在页面同级下创建 `pageConfig.ts` 请参考 `views/index/pageConfig.ts`
#### 自动引入vue相关的函数, 具体函数详情见 `autoImport.d.ts`
#### !!!重要: 代码提交请执行 npm run commit 然后按照实际情况进行选择, 代码会自动格式化
#### 页面布局中如果使用components/shell 作为根组件,样式需要进行 props 传递
#### 简介:
##### utils中的 HandleApp 是与 app端 交互的方法, 里面包含了当前的设备类型 如果是ios则也会包含ios的版本信息等
##### utils中的 useCookie 是为了获取 cookie中存储的数据, 如果有userid 则会被base64 编码
##### useCrypto中 有加密和解密方法(不是请求的,请求的加解密是另外一个) ,一般会用登录成功之后 url地址上的token进行解密 或者 加解密 本地存储
##### useDebounce中 是自定义ref 可以进行数据收集时防抖操作
##### useDefer是一个高阶函数, 因为它将会返回一个新的函数, 通常会用在进行数据列表渲染的时候
##### usePxToVw使用将px转为vw的一个工具函数,十分简单,只需要注意工具中的viewportWidth 和 vite.config.ts 中的一致即可
##### useRouter 使用包含路由跳转和返回的函数, 我们通常使用它进行页面的跳转,因为这样会给页面切换添加动效, 并且 我们在写页面的时候 通常会将页面包含在 components/shell 组件中, 这样我们可以近似的模拟app的缓存页面功能
##### useUrlData 用于获取路由参数的工具函数
##### useUserIdDecode 是用于 userId解密的, 因为app注入到cookie中的userid是加密的,所以在有些情况下 如何想拿到解密后的userid,可以使用此工具函数
#### 原生充值完成之后调用 window.inApplicationIAPRechargeListener(web端提供)
#### web端通过 window.fetchFirstRechargeProduct = Function 来获取首充数据
## 只有每日礼包
// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
module.exports = {
ignores: [commit => commit === "init"],
extends: [],
rules: {},
prompt: {
messages: {
type: "选择你要提交的类型 :",
scope: "选择一个提交范围(可选):",
customScope: "请输入自定义的提交范围 :",
subject: "填写简短精炼的变更描述 :\n",
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixsSelect: "选择关联issue前缀(可选):",
customFooterPrefixs: "输入自定义issue前缀 :",
footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
confirmCommit: "是否提交或修改commit ?"
},
types: [
{ value: "feat: 特性", name: "特性: 🚀 新增功能", emoji: "🚀" },
{ value: "fix: 修复", name: "修复: 🧩 修复缺陷", emoji: "🧩" },
{ value: "docs: 文档", name: "文档: 📚 文档变更", emoji: "📚" },
{ value: "style: 格式", name: "格式: 🎨 代码格式(不影响功能,例如空格、分号等格式修正)", emoji: "🎨" },
{ value: "refactor: 重构", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)", emoji: "♻️" },
{ value: "perf: 性能", name: "性能: ⚡️ 性能优化", emoji: "⚡️" },
{ value: "test: 测试", name: "测试: ✅ 添加疏漏测试或已有测试改动", emoji: "✅" },
{ value: "chore: 构建", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)", emoji: "📦️" },
{ value: "ci: 集成", name: "集成: 🎡 修改 CI 配置、脚本", emoji: "🎡" },
{ value: "revert: 回退", name: "回退: ⏪️ 回滚 commit", emoji: "⏪️" },
{ value: "build: 打包", name: "打包: 🔨 项目打包发布", emoji: "🔨" }
],
useEmoji: true
}
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" href="/logo.ico">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
name="viewport"/>
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0">
<title>bmiss-充值</title>
</head>
<body>
<!--author: 管忠旭-->
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This diff is collapsed.
{
"name": "bmiss-charge-propaganda",
"private": true,
"version": "1.0.0",
"author": "管忠旭",
"type": "module",
"scripts": {
"dev": "vite --host ",
"build:test": "vue-tsc && vite build --mode test",
"build:standard": "vue-tsc && vite build --mode standard",
"build:production": "vue-tsc && vite build --mode production",
"preview": "vite preview",
"prepare": "husky install",
"lint": "pnpm eslint --config ./.eslintrc.cjs ./src",
"lint-clear": "pnpm lint --config ./.eslintrc.cjs --ext .vue,.ts --cache",
"prettier": "prettier --write --loglevel silent ./src",
"commit": "pnpm prettier && git status && git add -A && git-cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-git"
}
},
"dependencies": {
"@vant/use": "^1.6.0",
"animejs": "^3.2.2",
"axios": "^1.6.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"foreign-country-utils": "1.16.0",
"js-base64": "^3.7.5",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"nanoid": "^4.0.2",
"pinia": "^2.1.7",
"reflect-metadata": "^0.1.14",
"swiper": "^11.0.5",
"vant": "^4.8.2",
"vconsole": "^3.15.1",
"vue": "^3.4.10",
"vue-i18n": "^9.14.5",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@types/animejs": "^3.1.12",
"@types/crypto-js": "^4.2.1",
"@types/eslint": "^8.56.2",
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.14.202",
"@types/md5": "^2.3.5",
"@types/node": "^18.19.6",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "^4.6.2",
"commitizen": "^4.3.0",
"cz-git": "^1.8.0",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.20.0",
"husky": "^8.0.3",
"postcss-px-to-viewport": "^1.1.1",
"prettier": "^2.8.8",
"stylus": "^0.59.0",
"terser": "^5.26.0",
"typescript": "^5.3.3",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.22.12",
"vite": "^4.5.14",
"vite-plugin-eslint": "^1.8.1",
"vue-tsc": "^1.8.27"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
#app
overflow hidden
.gIn-enter-active,
.gIn-leave-active,
.gOut-enter-active,
.gOut-leave-active{
transition: all .25s linear;
position: absolute;
}
.gIn-leave-to {
transform: translateX(0);
}
.gIn-enter-from
z-index 1
transform translateX(100%)
.gIn-enter-to{
z-index 1
transform: translateX(0);
}
.gOut-leave-from
z-index 1
.gOut-leave-to {
transform: translateX(100%);
z-index 1
}
.gOut-enter-from
transform translateX(0)
.gOut-enter-to{
transform: translateX(0);
}
\ No newline at end of file
/* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */
@font-face {
font-family: 'iconfont'; /* Project id 4341786 */
src: url('../font/header.woff2') format('woff2'),
url('../font/header.woff') format('woff'),
url('../font/header.ttf') format('truetype');
}
.iHeader
font-family "iconfont"
font-style normal
\ No newline at end of file
*
font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Liberation Sans", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, SimSun, "WenQuanYi Zen Hei Sharp", sans-serif
/* CSS Document */
html, body, div, span, object, iframe, h1, h2,
h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn,
em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd,
ol, ul, li, fieldset, form, label, legend, table, caption, tbody,
tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption,
figure, footer, header, hgroup, menu, nav, section, summary, time, mark, button,
audio, video
margin 0
padding 0
border 0
outline none
vertical-align baseline
background transparent
body
color #333
touch-action none
li
list-style none
a
margin 0
padding 0
border 0
vertical-align baseline
background transparent
color #222
text-decoration none
a:hover, a:focus, a:active, a:visited
text-decoration none;
outline-style none; /*FF*/
table
border-collapse collapse
border-spacing 0
input, select
vertical-align middle
/*css为clearfix,清除浮动*/
.clearfix::before,
.clearfix::after
content ""
height 0
line-height 0
display block
visibility hidden
clear both
.clearfix:after
clear both
.removeScrollbar::-webkit-scrollbar
display none
scrollbar-width none
button
outline 0
border 0
cursor pointer
button:hover
opacity: .8
html, body
scroll-behavior smooth
html
overflow-y: scroll
:root
overflow-y auto
/* 检测横屏(最小宽高比) */
@media all and (min-aspect-ratio: 1 / 1)
:root
overflow-x hidden
:root body
position absolute
::-webkit-scrollbar
width 4px
height 4px
border-radius 4px
::-webkit-scrollbar-thumb
background-color #a8a8a8
border-radius 5px
::-webkit-scrollbar-thumb:hover
background-color: #8a8a8a
::-webkit-scrollbar-track
background-color #eee
.one
overflow: hidden
-ms-text-overflow: ellipsis
text-overflow: ellipsis
white-space: nowrap
.pt
padding-top 32px
.pb
padding-bottom 24px
//全局的样式文件,可定义全局的变量、函数、混合等
//多行隐藏默认两行
nLine(line=2){
display -webkit-box;
-webkit-box-orient vertical;
-webkit-line-clamp line;
overflow hidden;
}
borderRadius(radius = 12px){
border-radius radius
}
\ No newline at end of file
<template>
<header :class="{ pt: hasPt }" :style="headerStyle">
<slot name="back">
<i class="iHeader" @click="goBack">&#xe61e;</i>
</slot>
<slot>
<h4 :style="titleStyle" class="one">{{ defaultTitle }}</h4>
</slot>
<slot name="right"></slot>
</header>
</template>
<script lang="ts" setup>
import useRouter from "@/utils/useRouter";
import useTitle from "components/header/hooks/useTitle";
import useHasPt from "components/header/hooks/useHasPt";
import "@/assets/css/header.styl";
interface IProp {
title?: string;
onBack?: () => void;
titleStyle?: Record<string, string>;
headerStyle?: Record<string, string>;
}
const { go } = useRouter();
const { title = "", onBack, titleStyle = {}, headerStyle = {} } = defineProps<IProp>();
const { defaultTitle } = useTitle({ title });
const { hasPt } = useHasPt();
const goBack = () => {
onBack ? onBack() : go(-1);
};
</script>
<style lang="stylus" scoped>
header
position sticky
top 0
left 0
background-color #fff
display flex
align-items center
justify-content center
box-sizing border-box
max-height 70px
flex none
> h4
text-align center
line-height 46px
height 46px
max-width 80%
font-size 16px
> .iHeader
width 30px
height 46px
display flex
align-items center
justify-content flex-start
position absolute
left 16px
</style>
import useCookieStore from "@/store/useCookieStore";
import piniaObj from "@/store/pinia";
const cookieStore = useCookieStore(piniaObj);
export default () => {
const hasPt = ref(cookieStore.cookieData.platform === "app");
return {
hasPt
};
};
import router from "@/router";
/**
* 如何是在微信内则 标题为空,否则标题为pageConfig.ts中的 title 字段的值
*/
interface IParams {
title: string;
}
const isWeiXin = navigator.userAgent.toLowerCase().indexOf("micromessenger") !== -1;
export default ({ title = "" }: IParams) => {
const defaultTitle = ref("");
const initDefaultTitle = () => {
const pageTitle = router.currentRoute.value.meta.title || "";
document.title = title.length > 0 ? title : pageTitle.toString();
if (isWeiXin) {
defaultTitle.value = "";
} else if (pageTitle && typeof pageTitle === "string") {
defaultTitle.value = document.title;
}
};
initDefaultTitle();
onActivated(initDefaultTitle);
return {
defaultTitle
};
};
<template>
<div class="loading">
<div :style="{ transform: `scale(${scale}) rotateY(${deg}deg)` }" :class="{ move }">
<i :style="{ transform: ` rotateY(-${deg}deg)` }" :class="{ left: true, move }"></i>
<i :style="{ transform: ` rotateY(-${deg}deg)` }" :class="{ right: true, move }"></i>
</div>
</div>
</template>
<script lang="ts" setup>
interface IProps {
move?: boolean;
distance: number;
headHeight: number;
}
const props = defineProps<IProps>();
const scale = computed(() => props.distance / props.headHeight);
const deg = computed(() => scale.value * 360);
</script>
<style lang="stylus" scoped>
animaTime = 1s
.loading
width 100%
height 100%
perspective 40px
display flex
flex-direction column
justify-content center
> div
transform-style: preserve-3d
width 30px
height 10px
display flex
justify-content space-between
align-items center
margin 0 auto
backface-visibility visible
&.move
animation move animaTime infinite linear
> i
width 8px
height 8px
border-radius 50%
backface-visibility visible
transform-style: preserve-3d;
animation-direction reverse
&.move
animation move animaTime infinite linear
animation-direction reverse
&.left
background-color #f00
&.right
background-color: #00f
@keyframes move
0%
transform rotateY(0)
100%
transform rotateY(360deg)
</style>
<template>
<van-pull-refresh
:model-value="refreshLoading"
@refresh="emits('onRefresh')"
:head-height="headHeight"
class="shell"
:disabled="refreshDisabled || scrollTop > 0"
>
<!-- 下拉过程提示 -->
<template #pulling="props">
<slot name="iPulling" :props="props">
<Loading :head-height="headHeight" :distance="props.distance" />
</slot>
</template>
<!-- 释放提示 -->
<template #loosing>
<slot name="iLoosing">
<Loading :head-height="headHeight" :distance="headHeight" />
</slot>
</template>
<!-- 加载提示 -->
<template #loading>
<slot name="iLoading">
<Loading :head-height="headHeight" :distance="headHeight" :move="true" />
</slot>
</template>
<van-list
ref="shellRef"
:style="shellStyle"
:loading="loading"
:finished="finished"
:offset="offset"
:loading-text="loadingText"
:finishedText="finishedText"
:errorText="errorText"
:immediateCheck="immediateCheck"
:disabled="disabled"
@load="emits('onLoad')"
class="list removeScrollbar"
>
<slot></slot>
</van-list>
</van-pull-refresh>
</template>
<script lang="ts" setup>
// 参考vant官方文档 https://vant-contrib.gitee.io/vant/#/zh-CN/list
import useScrollTop from "components/shell/hooks/useScrollTop";
import Loading from "components/loading/Loading.vue";
interface IProps {
shellStyle?: Record<string, string | number>;
loading?: boolean;
finished?: boolean;
offset?: number;
loadingText?: string;
finishedText?: string;
errorText?: string;
immediateCheck?: boolean;
disabled?: boolean;
refreshLoading?: boolean;
refreshDisabled?: boolean;
headHeight?: number;
}
interface IEmits {
(eventName: "onLoad"): void;
(eventName: "onRefresh"): void;
}
const emits = defineEmits<IEmits>();
withDefaults(defineProps<IProps>(), {
shellStyle: () => ({}),
loading: false,
finished: true,
offset: 300,
loadingText: "加载中...",
finishedText: "",
errorText: "",
immediateCheck: true,
disabled: false,
headHeight: 100,
refreshDisabled: true
});
const { shellRef, scrollTop } = useScrollTop();
</script>
<style lang="stylus" scoped>
.shell
width 100%
height 100%
.list
width 100%
height 100%
overflow-y auto
display flex
flex-direction column
</style>
import { ListInstance } from "vant";
export default () => {
const shellRef = ref<ListInstance>();
const scrollTop = ref(0);
onActivated(() => {
const ele = shellRef.value;
if (ele) {
ele.$el.scrollTo({
left: 0,
top: scrollTop.value
});
}
});
const onScroll = (ele: HTMLElement) => {
scrollTop.value = ele.scrollTop;
};
const bindScroll = () => {
const ele = shellRef.value?.$el;
if (ele) {
ele.addEventListener("scroll", onScroll.bind(null, ele));
}
};
const unBindScroll = () => {
const ele = shellRef.value?.$el;
if (ele) {
ele.removeEventListener("scroll", onScroll.bind(null, ele));
}
};
onMounted(bindScroll);
onUnmounted(unBindScroll);
return {
shellRef,
scrollTop
};
};
/**
* 这是一个图片展示的指令,用于在ios14及以下展示png类型的图片,ios14以上使用webp类型的图片, 安卓一律使用webp类型的图片
*/
import { Directive, DirectiveBinding } from "vue";
import handleApp from "@/utils/HandleApp";
import { getLocale } from "@/locales/locale";
const ENV = import.meta.env;
interface OnError {
(): void;
}
export interface ShowImageDirective {
src: string;
onError?: OnError;
isBack?: boolean;
}
interface HandleImg {
ele: HTMLImageElement;
src: string;
onError: OnError;
}
interface HandleBackImg {
ele: HTMLDivElement;
src: string;
onError: OnError;
}
let baseDir = `${location.origin}${location.pathname}`;
let errorPath = "";
if (ENV.MODE === "development") {
baseDir += "public/";
}
baseDir += "images";
errorPath = `${baseDir}/error/error.png`;
const lang = getLocale();
// 正常图片处理
const handleImg = ({ ele, src, onError }: HandleImg) => {
const [dirPath, fileName] = src.split("/");
if(lang == "en"){
ele.src = `${baseDir}/${dirPath}/enpng/${fileName}.png`;
}else{
if (handleApp.agent === "ios" && handleApp.iosVersion.minorVersion < 14) {
ele.src = `${baseDir}/${dirPath}/png/${fileName}.png`;
} else {
ele.src = `${baseDir}/${dirPath}/webp/${fileName}.webp`;
}
}
ele.onerror = onError;
};
// 背景图片处理
const handleBackImg = ({ ele, src, onError }: HandleBackImg) => {
let image: HTMLImageElement | null = new Image();
image.onload = () => {
image = null;
};
image.onerror = () => {
onError();
image = null;
};
const [dirPath, fileName] = src.split("/");
if(lang == "en"){
ele.style.backgroundImage = `url(${baseDir}/${dirPath}/enpng/${fileName}.png)`;
image.src = `${baseDir}/${dirPath}/enpng/${fileName}.png`;
}else{
if (handleApp.agent === "ios" && handleApp.iosVersion.minorVersion < 14) {
// ios版本小于14,则使用png类型的图片
ele.style.backgroundImage = `url(${baseDir}/${dirPath}/png/${fileName}.png)`;
image.src = `${baseDir}/${dirPath}/png/${fileName}.png`;
} else {
ele.style.backgroundImage = `url(${baseDir}/${dirPath}/webp/${fileName}.webp)`;
image.src = `${baseDir}/${dirPath}/webp/${fileName}.webp`;
}
}
};
const render = (ele: HTMLImageElement, binding: DirectiveBinding<ShowImageDirective | string>, vNode: globalThis.VNode) => {
let src: string;
let onError: OnError = () => {
ele.src = errorPath;
};
if (vNode.props?.onError) {
onError = vNode.props.onError;
}
if (typeof binding.value === "string") {
// 默认是img标签, 进行src处理和错误函数绑定
src = binding.value;
handleImg({ ele, src, onError });
} else {
src = binding.value.src;
binding.value.onError && (onError = binding.value.onError);
if (binding.value.isBack) {
// 默认是div 标签 是背景图片处理
onError = () => {
ele.style.backgroundImage = `url(${errorPath})`;
};
if (vNode.props?.onError) {
onError = vNode.props.onError;
}
handleBackImg({ ele, src, onError });
} else {
// img标签 进行src处理和错误函数绑定
handleImg({ ele, src, onError });
}
}
};
const vShowImg: Directive = {
beforeMount(ele, binding: DirectiveBinding<ShowImageDirective | string>, vNode) {
const value = binding.value;
if (isProxy(value)) watch(() => value, render.bind(null, ele, binding, vNode), { deep: true });
render(ele, binding, vNode);
}
};
export default vShowImg;
import { plainToInstance, ClassConstructor } from "class-transformer";
import { validate, ValidationError, ValidatorOptions } from "class-validator";
const defaultValidatorOptions: ValidatorOptions = {
skipMissingProperties: false,
stopAtFirstError: true,
forbidUnknownValues: true
};
// 基础的数据校验类型, 包含了指定的数据校验、平面对象转类对象
export default abstract class BaseEntities {
[key: string]: unknown;
/**
* 验证对象是否符合数据格式
* @param config
*/
public async validator(config: ValidatorOptions = {}) {
const errors = await validate(this, { ...defaultValidatorOptions, ...config });
return BaseEntities.findError(errors);
}
/**
* 平面对象转换类对象
* @param Obj
* @param planObj
*/
public static transform<T>(Obj: ClassConstructor<T>, planObj: object): T {
if (planObj instanceof Obj) return planObj;
return plainToInstance(Obj, planObj);
}
private static findError(errors: ValidationError[], resultArr: string[] = []): string[] {
const arr = errors.map(item => item.constraints);
arr.forEach(item => {
if (item) resultArr.push(...Object.values(item));
});
errors.forEach(item => {
if (item && item.children) {
BaseEntities.findError(item.children, resultArr);
}
});
return resultArr;
}
}
import { IsInt } from "class-validator";
import BaseEntities from "entities/BaseEntities";
// 分页请求需要继承类,已自己继承了BaseEntities类
export default abstract class BasePageRequest extends BaseEntities {
@IsInt({ message: "page 必须为number类型" })
page = 1;
@IsInt({ message: "page_size 必须为number类型" })
page_size = 10;
}
import BaseEntities from "entities/BaseEntities";
import { IsInt, IsString } from "class-validator";
import useErrorMessage from "@/utils/useErrorMessage";
export default class FirstGift extends BaseEntities {
@IsString({ message: useErrorMessage("title", "string") })
title = "";
@IsString({ message: useErrorMessage("title", "string") })
src = "";
@IsString({ message: useErrorMessage("productName", "string") })
productName = "";
@IsString({ message: useErrorMessage("productLocaleSymbol", "string") })
productLocaleSymbol = "¥";
@IsInt({ message: useErrorMessage("productPrice", "int") })
productPrice = 0;
@IsString({ message: useErrorMessage("productIdentifier", "string") })
productIdentifier = "";
@IsInt({ message: useErrorMessage("ratio", "int") })
ratio = 0;
constructor(title?: string, src?: string, ratio?: number) {
super();
if (title) this.title = title;
if (src) this.src = src;
if (ratio) this.ratio = ratio;
}
}
import { ToastOptions, ToastType } from "vant/lib/toast/types";
import { allowMultipleToast, showDialog, showFailToast, showLoadingToast, showSuccessToast, showToast, ToastWrapperInstance } from "vant";
import { DialogOptions } from "vant/lib/dialog/types";
import { DialogAction } from "vant/es/dialog/types";
const defaultOption: ToastOptions = {
duration: 0,
forbidClick: true,
message: "加载中...",
overlay: true
};
allowMultipleToast();
export default class Tips {
// 加载相关
static showLoading(options: string | ToastOptions = {}): ToastWrapperInstance {
let config: ToastOptions = { ...defaultOption };
if (typeof options === "string") {
config.message = options;
} else {
config = {
...config,
...options
};
}
return showLoadingToast(config);
}
// 数据校验出错弹窗
static showDataError(message: string, options: DialogOptions = {}): Promise<DialogAction | undefined> {
return showDialog({
title: "提示",
message,
...options
});
}
// 轻提醒
static showToast(options: string | ToastOptions = {}, type: ToastType = "success"): ToastWrapperInstance {
switch (type) {
case "success":
return showSuccessToast(options);
case "fail":
return showFailToast(options);
case "html":
case "text":
case "loading":
return showToast(options);
}
}
}
type BaseType = symbol | string | number | null | unknown | boolean;
interface BaseResponse<T> {
data: T;
msg: string;
code: number;
}
interface DataObj {
[key: string]: BaseType | Array<BaseType> | Array<Record<string, BaseType>>;
}
interface Secret {
key: string;
iv: string;
}
export default {
tag: "首充禮包",
title1: "初级",
title2: "精選",
title3: "豪華",
title4: "尊享",
title5: "璀璨",
text1: "搶購",
text2: "原價",
text3: "已搶購",
appletext: "本活動與蘋果公司無關,解釋權歸平台所有",
gooletext: "本活動與谷歌公司無關,解釋權歸平臺所有",
guizhe: "首充禮包規則",
guizhe1: "1.此活動僅針對未充值過的用戶;",
guizhe2: "2.未充值的用戶只允許購買其中一檔禮包,購買後其他檔位的禮包無法購買;",
guizhe3: "3.平臺禁止一切作弊、外掛等違反平臺規則的行為,一經發現,取消獎勵,並給予相應懲罰;",
guizhe4: "4.平臺有權基於運營情況,對禮包的獎勵進行調整;",
guizhe5: "5.本次活動與Apple Inc.或Google LLC無關。",
tishi: "不符合購買要求,只有首次充值才可購買哦~",
};
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment