generated from duanshuwen/webapp-vue-frontend
Initial commit
This commit is contained in:
46
src/App.vue
Normal file
46
src/App.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive :include="KeepAliveList">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const KeepAliveList = ref([])
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const keepAlive = to?.meta?.keepAlive
|
||||
|
||||
if (!router.hasRoute(to.name)) {
|
||||
router.push('/error')
|
||||
return
|
||||
}
|
||||
|
||||
if (keepAlive) {
|
||||
if (KeepAliveList.value.indexOf(to.name) === -1) {
|
||||
KeepAliveList.value.push(to.name)
|
||||
} else {
|
||||
const index = KeepAliveList.value.findIndex((name) => name === from.name)
|
||||
index > -1 ? KeepAliveList.value.splice(index, 1) : null
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@media only screen and (min-width: 500px) {
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/assets/font/bebas.ttf
Normal file
BIN
src/assets/font/bebas.ttf
Normal file
Binary file not shown.
20
src/assets/font/icon.css
Normal file
20
src/assets/font/icon.css
Normal file
@@ -0,0 +1,20 @@
|
||||
@font-face {
|
||||
font-family: 'icon-font'; /* Project id 634611 (更新icon直接替换新的cdn地址即可,无需下载) */
|
||||
src: url('//at.alicdn.com/t/font_634611_fe4z5kh8e8n.woff2?t=1654585244447') format('woff2'),
|
||||
url('//at.alicdn.com/t/font_634611_fe4z5kh8e8n.woff?t=1654585244447') format('woff'),
|
||||
url('//at.alicdn.com/t/font_634611_fe4z5kh8e8n.ttf?t=1654585244447') format('truetype');
|
||||
}
|
||||
|
||||
.icon-font {
|
||||
font-family: 'icon-font' !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'money';
|
||||
src: url('./bebas.ttf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
96
src/assets/scss/index.scss
Normal file
96
src/assets/scss/index.scss
Normal file
@@ -0,0 +1,96 @@
|
||||
@import './normalize';
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: rgba(255, 0, 0, 0); // 移除移动端阴影
|
||||
-webkit-overflow-scrolling: touch; //滚动回弹 ios13+
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
|
||||
&,
|
||||
* {
|
||||
color-scheme: dark !important;
|
||||
transition: background-color 300ms;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
|
||||
&,
|
||||
* {
|
||||
color-scheme: light !important;
|
||||
transition: background-color 300ms;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
// 安全区域变量初始化
|
||||
:root {
|
||||
--safe-area-inset-top: 24px;
|
||||
--safe-area-inset-right: 0px;
|
||||
--safe-area-inset-bottom: 0px;
|
||||
--safe-area-inset-left: 0px;
|
||||
|
||||
@supports (top: constant(safe-area-inset-top)) and (padding: Max(10px, 20px)) {
|
||||
--safe-area-inset-top: Max(constant(safe-area-inset-top), 24px);
|
||||
--safe-area-inset-right: constant(safe-area-inset-right);
|
||||
--safe-area-inset-bottom: constant(safe-area-inset-bottom);
|
||||
--safe-area-inset-left: constant(safe-area-inset-left);
|
||||
}
|
||||
|
||||
@supports (top: env(safe-area-inset-top)) and (padding: Max(10px, 20px)) {
|
||||
--safe-area-inset-top: Max(env(safe-area-inset-top), 24px);
|
||||
--safe-area-inset-right: env(safe-area-inset-right);
|
||||
--safe-area-inset-bottom: env(safe-area-inset-bottom);
|
||||
--safe-area-inset-left: env(safe-area-inset-left);
|
||||
}
|
||||
}
|
||||
|
||||
.van-safe-area-top {
|
||||
padding-top: var(--safe-area-inset-top) !important;
|
||||
}
|
||||
|
||||
.van-image-preview__index,
|
||||
.van-image-preview__close-icon--top-right {
|
||||
top: 46px !important;
|
||||
}
|
||||
|
||||
// 表单样式
|
||||
.van-field__error-message {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.van-hairline--top-bottom:after,
|
||||
.van-hairline-unset--top-bottom:after {
|
||||
border-width: 0 !important;
|
||||
}
|
||||
|
||||
// 弹窗样式
|
||||
.van-dialog {
|
||||
width: 270px !important;
|
||||
}
|
||||
|
||||
.van-dialog__confirm,
|
||||
.van-dialog__confirm:active {
|
||||
color: #14b498 !important;
|
||||
}
|
||||
|
||||
.van-dialog__content--isolated {
|
||||
min-height: 72px !important;
|
||||
}
|
||||
|
||||
.van-dialog__message {
|
||||
color: #333333;
|
||||
font-size: 17px !important;
|
||||
font-family: PingFangSC-Semibold, PingFang SC;
|
||||
font-weight: 600;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
355
src/assets/scss/normalize.scss
vendored
Normal file
355
src/assets/scss/normalize.scss
vendored
Normal file
@@ -0,0 +1,355 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
}
|
||||
222
src/common/Track.js
Normal file
222
src/common/Track.js
Normal file
@@ -0,0 +1,222 @@
|
||||
import { bindEvent, last } from 'vtils'
|
||||
|
||||
const Track = {
|
||||
app: null,
|
||||
ready: false,
|
||||
App: {
|
||||
created() {
|
||||
Track.app = this
|
||||
},
|
||||
watch: {
|
||||
'$route.path': {
|
||||
immediate: true,
|
||||
handler: async (fromPath, toPath) => {
|
||||
await Track.ensureReady()
|
||||
Track.app.$nextTick(async () => {
|
||||
const { id, name } = await Track.injectMark()
|
||||
window.loadEventPush(
|
||||
fromPath,
|
||||
toPath || '',
|
||||
Track.app.$route.query,
|
||||
{
|
||||
mark_id: id,
|
||||
mark_name: name
|
||||
}
|
||||
)
|
||||
Track.app.$nextTick(() => {
|
||||
window.elementAddEventListener()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ensureReady: async () => {
|
||||
if (Track.ready) return
|
||||
return new Promise((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
if (
|
||||
// @ts-ignore
|
||||
typeof window.Countly !== 'undefined' &&
|
||||
// @ts-ignore
|
||||
typeof window.anyEventPush !== 'undefined' &&
|
||||
Track.app !== null
|
||||
) {
|
||||
clearInterval(timer)
|
||||
Track.ready = true
|
||||
resolve()
|
||||
}
|
||||
}, 60)
|
||||
})
|
||||
},
|
||||
prepareData(data) {
|
||||
return {
|
||||
...(data.id && data.name
|
||||
? {
|
||||
control_id: data.id,
|
||||
control_name: data.name,
|
||||
control_value: data.value || data.name
|
||||
}
|
||||
: {}),
|
||||
...(data.goods
|
||||
? data.goods.isUniGoods
|
||||
? {
|
||||
content_id: data.goods.id,
|
||||
content_name: data.goods.name,
|
||||
resource_id: data.goods.resourceId,
|
||||
resource_name: data.goods.resourceName,
|
||||
resource_position_id: data.goods.resourcePositionId,
|
||||
resource_position_name: data.goods.resourcePositionName,
|
||||
resource_position_no: data.index || 0
|
||||
}
|
||||
: {
|
||||
content_id: data.goods.commodityId,
|
||||
content_name:
|
||||
data.goods.commodityTitle ||
|
||||
// 兼容订单下的商品
|
||||
data.goods.name,
|
||||
...(data.goods.commodityResourceId
|
||||
? {
|
||||
resource_id: data.goods.commodityResourceId,
|
||||
resource_name: data.goods.commodityTitle,
|
||||
resource_position_id: '',
|
||||
resource_position_name: data.name,
|
||||
resource_position_no: data.index || 0
|
||||
}
|
||||
: {})
|
||||
}
|
||||
: {}),
|
||||
...(data.resource
|
||||
? {
|
||||
resource_id: data.resource.id,
|
||||
resource_name: data.resource.title || data.resource.name || '',
|
||||
resource_position_id: data.resource.stateId || '',
|
||||
resource_position_name: data.resource.stateName || '',
|
||||
resource_position_no: data.index || 0
|
||||
}
|
||||
: {}),
|
||||
...(data.resourcePosition
|
||||
? {
|
||||
resource_position_id: data.resourcePosition.id,
|
||||
resource_position_name: data.resourcePosition.name,
|
||||
resource_position_no: data.index || 0
|
||||
}
|
||||
: {}),
|
||||
...(data.mark
|
||||
? {
|
||||
mark_id: data.mark.id,
|
||||
mark_name: data.mark.name
|
||||
}
|
||||
: {}),
|
||||
...(data.content
|
||||
? {
|
||||
content_name: data.content
|
||||
}
|
||||
: {})
|
||||
}
|
||||
},
|
||||
pushEvent: async (data) => {
|
||||
await Track.ensureReady()
|
||||
const trackData = Track.prepareData(data)
|
||||
if (!trackData.mark_id && !trackData.mark_name) {
|
||||
const { id, name } = await Track.injectMark()
|
||||
trackData.mark_id = id
|
||||
trackData.mark_name = name
|
||||
}
|
||||
window.anyEventPush(data.type, data.path, data.name, trackData)
|
||||
},
|
||||
provideMark(cb) {
|
||||
return {
|
||||
methods: {
|
||||
provideTrackMark: cb
|
||||
}
|
||||
}
|
||||
},
|
||||
injectMark: async () => {
|
||||
const currentRoute = Track.app.$route
|
||||
const matched = currentRoute.matched
|
||||
const currentPage = last(matched)
|
||||
const currentPageInstance = currentPage?.instances.default
|
||||
const provideTrackMark = currentPageInstance?.provideTrackMark
|
||||
const { id = 'NONE', name = 'NONE' } =
|
||||
typeof provideTrackMark === 'function' ? await provideTrackMark() : {}
|
||||
return {
|
||||
id,
|
||||
name
|
||||
}
|
||||
},
|
||||
install(app, options) {
|
||||
Track.ensureReady().then(() => {
|
||||
// @ts-ignore
|
||||
countlyConfig.baseContextPath = options.appid
|
||||
})
|
||||
|
||||
async function applyTrack(el, payload) {
|
||||
await Track.ensureReady()
|
||||
let data = payload.value
|
||||
// @ts-ignore
|
||||
if (data && !data.disabled) {
|
||||
data = {
|
||||
...data,
|
||||
name: `${options.prefix || ''}${data.name}`,
|
||||
// @ts-ignore
|
||||
path: el.__track_data__?.path || window.getRoutePath()
|
||||
}
|
||||
|
||||
el.__track_data__ = data
|
||||
|
||||
// 兼容曝光事件
|
||||
if (data.expose !== false) {
|
||||
el.setAttribute('exposure-event', data.name)
|
||||
el.setAttribute(
|
||||
'extend_attr',
|
||||
JSON.stringify(Track.prepareData(data))
|
||||
)
|
||||
if (data.value !== null) {
|
||||
el.setAttribute('alt', data.value)
|
||||
}
|
||||
}
|
||||
|
||||
const shouldListen = !el.dataset.track
|
||||
if (shouldListen) {
|
||||
el.dataset.track = '1'
|
||||
el.__track_dispose__ = bindEvent(el)(
|
||||
'click',
|
||||
() => {
|
||||
console.log(`埋点:点击「${data.name}」`)
|
||||
Track.pushEvent({
|
||||
type: 'click',
|
||||
...el.__track_data__,
|
||||
value: el.innerText.trim().replace(/\s+/g, '')
|
||||
})
|
||||
},
|
||||
{
|
||||
// 必须使用捕获模式以在其他事件触发前处理(比如其他事件触发了页面跳转,就会导致问题)
|
||||
capture: true,
|
||||
// 使用被动模式告诉浏览器这不会产生阻塞行为
|
||||
passive: true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function disposeTrack(el) {
|
||||
// 需延时一下,不然当遇到点击跳转时页面卸载把事件也卸载了就触发不了了
|
||||
setTimeout(() => {
|
||||
delete el.__track_data__
|
||||
el.__track_dispose__?.()
|
||||
delete el.__track_dispose__
|
||||
}, 0)
|
||||
}
|
||||
|
||||
app.directive('track', {
|
||||
mounted: applyTrack,
|
||||
updated: applyTrack,
|
||||
beforeUpdate: applyTrack,
|
||||
unmounted: disposeTrack
|
||||
})
|
||||
console.log(1)
|
||||
}
|
||||
}
|
||||
export default Track
|
||||
131
src/common/ajax.js
Normal file
131
src/common/ajax.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import axios from 'axios'
|
||||
import {
|
||||
md5Hash,
|
||||
rsaEncrypt,
|
||||
rsaDecrypt,
|
||||
aesEncrypt,
|
||||
aesDecrypt,
|
||||
generateUUID,
|
||||
dataSign,
|
||||
generateRandomString,
|
||||
getLocalStorage
|
||||
} from '@dcb/utils'
|
||||
|
||||
const encryptionOff = Number(import.meta.env.VITE_ENCRYPTION)
|
||||
const env = import.meta.env.VITE_ENV === 'development'
|
||||
const STORE_OFF = Number(import.meta.env.VITE_STORE_OFF)
|
||||
const RSA = import.meta.env.VITE_APP_RSA
|
||||
const DATA_RSA = STORE_OFF ? import.meta.env.VITE_DATA_RSA : RSA
|
||||
const martId = import.meta.env.VITE_MART_ID
|
||||
const appKey = import.meta.env.VITE_KEY
|
||||
const appSecret = import.meta.env.VITE_SECRET
|
||||
|
||||
const request = async (method, url, data, config = {}) => {
|
||||
const p = env ? import.meta.env.VITE_P : decodeURI(getLocalStorage('p'))
|
||||
const options = Object.assign({}, config, {
|
||||
url,
|
||||
method
|
||||
})
|
||||
|
||||
console.log('入参', data)
|
||||
|
||||
const uuid = generateUUID()
|
||||
const secret = '4RBA7^@0$@KM$5D2333'
|
||||
const title = md5Hash(uuid + secret)
|
||||
const aesKey = generateRandomString(16) // 生成16位随机数
|
||||
|
||||
const init = rsaEncrypt(aesKey, RSA)
|
||||
const base = aesEncrypt(JSON.stringify(data), aesKey)
|
||||
|
||||
options.data = options.data || {}
|
||||
|
||||
/*
|
||||
* @ 验签
|
||||
* @wiki: http://wiki.gogpay.cn/pages/viewpage.action?pageId=75301563
|
||||
* */
|
||||
url = url.replace(/^https?:\/\/.+?\/([^/]+)/, '')
|
||||
|
||||
const timestamp = Date.now()
|
||||
let asHex = ''
|
||||
const header = Object.assign(
|
||||
{ clientId: '03' },
|
||||
STORE_OFF ? { martId, path: url, timestamp } : { path: url, timestamp }
|
||||
)
|
||||
|
||||
for (const [key, value] of Object.entries(header)) {
|
||||
asHex += key + value
|
||||
}
|
||||
|
||||
// console.log(asHex + appSecret)
|
||||
const sign = dataSign(header, appSecret)
|
||||
|
||||
if (method === 'post') {
|
||||
if (!config.white && encryptionOff) {
|
||||
options.data = {
|
||||
timestamp: uuid,
|
||||
title,
|
||||
init,
|
||||
base
|
||||
}
|
||||
} else {
|
||||
options.data = data
|
||||
}
|
||||
}
|
||||
|
||||
options.headers = Object.assign({}, options.headers, header, {
|
||||
sign,
|
||||
appKey,
|
||||
p
|
||||
})
|
||||
|
||||
console.log(options.headers, STORE_OFF)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.request(options)
|
||||
.then((res) => {
|
||||
let { data } = res
|
||||
|
||||
if (!data) {
|
||||
return resolve(data)
|
||||
}
|
||||
|
||||
if (method === 'post' && encryptionOff && !config.white) {
|
||||
data = JSON.parse(
|
||||
aesDecrypt(data.base, rsaDecrypt(data.init, DATA_RSA))
|
||||
)
|
||||
}
|
||||
|
||||
console.log('返回值', data)
|
||||
resolve(data)
|
||||
})
|
||||
.catch((res) => {
|
||||
reject(res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const ajax = {
|
||||
request,
|
||||
get(url, config) {
|
||||
return request('get', url, null, config)
|
||||
},
|
||||
delete(url, config) {
|
||||
return request('delete', url, null, config)
|
||||
},
|
||||
head(url, config) {
|
||||
return request('head', url, null, config)
|
||||
},
|
||||
post(url, data, config) {
|
||||
return request('post', url, data, config)
|
||||
},
|
||||
put(url, data, config) {
|
||||
return request('put', url, data, config)
|
||||
},
|
||||
patch(url, data, config) {
|
||||
return request('path', url, data, config)
|
||||
},
|
||||
setCommonHeader(key, value) {
|
||||
window.axios.defaults.headers.common[key] = value
|
||||
}
|
||||
}
|
||||
16
src/common/dcb.ajax.config.js
Normal file
16
src/common/dcb.ajax.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable no-process-env */
|
||||
import { initAjax } from '@dcb/ajax'
|
||||
const debug = import.meta.env.VITE_ENV
|
||||
const RSA = import.meta.env.VITE_APP_RSA
|
||||
const appid = import.meta.env.VITE_APP_ID
|
||||
const BASE_API = import.meta.env.VITE_BASE_API
|
||||
const encryptionOff = import.meta.env.VITE_ENCRYPTION
|
||||
|
||||
export const useAjax = function () {
|
||||
const ajax = initAjax(true)
|
||||
const options = { RSA, appid, BASE_API, encryptionOff, debug }
|
||||
|
||||
ajax.config(options)
|
||||
|
||||
return ajax
|
||||
}
|
||||
50
src/common/flexible.js
Normal file
50
src/common/flexible.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// ref: https://github.com/amfe/lib-flexible/blob/2.0/index.js
|
||||
;(function flexible(window, document) {
|
||||
const docEl = document.documentElement
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
|
||||
// adjust body font size
|
||||
const setBodyFontSize = () => {
|
||||
if (document.body) {
|
||||
document.body.style.fontSize = `${12 * dpr}px`
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', setBodyFontSize)
|
||||
}
|
||||
}
|
||||
|
||||
setBodyFontSize()
|
||||
|
||||
// set 1rem = viewWidth / 10
|
||||
const setRemUnit = () => {
|
||||
const rem =
|
||||
Math.min(
|
||||
// 宽度限制在 500 内
|
||||
500,
|
||||
docEl.clientWidth
|
||||
) / 10
|
||||
docEl.style.fontSize = `${rem}px`
|
||||
}
|
||||
|
||||
setRemUnit()
|
||||
|
||||
// reset rem unit on page resize
|
||||
window.addEventListener('resize', setRemUnit)
|
||||
window.addEventListener('pageshow', (e) => {
|
||||
if (e.persisted) {
|
||||
setRemUnit()
|
||||
}
|
||||
})
|
||||
|
||||
// detect 0.5px supports
|
||||
if (dpr >= 2) {
|
||||
const fakeBody = document.createElement('body')
|
||||
const testElement = document.createElement('div')
|
||||
testElement.style.border = '.5px solid transparent'
|
||||
fakeBody.appendChild(testElement)
|
||||
docEl.appendChild(fakeBody)
|
||||
if (testElement.offsetHeight === 1) {
|
||||
docEl.classList.add('hairlines')
|
||||
}
|
||||
docEl.removeChild(fakeBody)
|
||||
}
|
||||
})(window, document)
|
||||
3
src/common/index.js
Normal file
3
src/common/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ajax } from './ajax'
|
||||
|
||||
export { ajax }
|
||||
53
src/components/Empty.vue
Normal file
53
src/components/Empty.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<img class="empty__img" :src="img" />
|
||||
<div class="empty__desc" v-if="description" :style="{ color }">
|
||||
{{ description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import imgEmpty from '@/assets/images/img_empty_order.png'
|
||||
|
||||
const props = defineProps({
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
img: {
|
||||
type: Object,
|
||||
default: imgEmpty
|
||||
},
|
||||
|
||||
color: {
|
||||
type: String,
|
||||
default: '#999999'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.empty {
|
||||
width: 100%;
|
||||
height: 288px;
|
||||
@extend %flex-center;
|
||||
|
||||
&__img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: block;
|
||||
margin: 105px auto 0;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
color: #999999;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
src/components/PageContainer.vue
Normal file
148
src/components/PageContainer.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="page-container" :style="{ backgroundColor: bgColor }">
|
||||
<van-nav-bar :class="['nav-bar', navBgColor, isCustom ? 'custom-style' : '']" safe-area-inset-top left-arrow fixed
|
||||
v-if="showNavigator" :border="false" :left-text="navBarLeftText" @click-left="handleBack">
|
||||
<template #title>
|
||||
<slot name="title">{{ title === '首页' ? '' : title }}</slot>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<slot name="nav-bar-right"></slot>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
// nav-bar返回按钮位置的文字
|
||||
navBarLeftText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
// nav-bar标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'white'
|
||||
},
|
||||
|
||||
// nav-bar背景颜色
|
||||
navBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
// fixed情况下返回按钮背景颜色
|
||||
backBgColor: {
|
||||
type: String,
|
||||
default: 'lightgray'
|
||||
},
|
||||
|
||||
// 是否自定义样式
|
||||
isCustom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 是否显示导航栏
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
goback: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
// 是否显示隐藏导航
|
||||
const showNavigator = ref(props.show)
|
||||
|
||||
// 获取页面标题
|
||||
const { title } = proxy.$router.currentRoute.value.meta
|
||||
|
||||
const handleBack = () => {
|
||||
if (props.goback > 0) {
|
||||
proxy.$router.go(-props.goback)
|
||||
} else {
|
||||
proxy.$router.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
|
||||
.body {
|
||||
min-height: calc(100vh - 46px + var(--safe-area-inset-top));
|
||||
padding-top: calc(46px + var(--safe-area-inset-top));
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
background-color: transparent;
|
||||
z-index: 2000;
|
||||
position: fixed;
|
||||
|
||||
:deep(.van-icon-arrow-left) {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
:deep(.van-nav-bar__title) {
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 22px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.style-1,
|
||||
&.style-2 {
|
||||
:deep(.van-icon-arrow-left) {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.van-nav-bar__title) {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
&.style-1 {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&.style-2 {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.custom-style {
|
||||
background-color: rgba(22, 187, 191, 1);
|
||||
|
||||
:deep(.van-icon-arrow-left) {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
2
src/components/index.js
Normal file
2
src/components/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as PageContainer } from './PageContainer.vue'
|
||||
export { default as Empty } from './Empty.vue'
|
||||
5
src/hooks/tool.js
Normal file
5
src/hooks/tool.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export const observer = reactive({
|
||||
userInfo: undefined
|
||||
})
|
||||
13
src/main.js
Normal file
13
src/main.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue'
|
||||
import '@/assets/font/icon.css'
|
||||
import '@/common/flexible'
|
||||
import App from './App.vue'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
|
||||
const app = createApp(App)
|
||||
app.config.globalProperties.$store = store
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
22
src/router/index.js
Normal file
22
src/router/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
// 路由规则
|
||||
const routes = [
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home', // 请和文件名一样
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
meta: {
|
||||
title: '首页', // 自动设置当前页面的标题
|
||||
keepAlive: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
// vueRouter@3版本的mode改成了history,hash模式配置createWebHashHistory,history模式配置createWebHistory
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
18
src/store/index.js
Normal file
18
src/store/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
export default {
|
||||
state: reactive({
|
||||
userInfo: undefined
|
||||
}),
|
||||
|
||||
mutations: {
|
||||
setUserInfo(state, data) {
|
||||
state.userInfo = data
|
||||
}
|
||||
},
|
||||
|
||||
commit(eventName, { data, dept }) {
|
||||
data.dept.preCheck = dept.preCheck
|
||||
this.mutations[eventName](this.state, data)
|
||||
}
|
||||
}
|
||||
22
src/views/home/index.vue
Normal file
22
src/views/home/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<van-button @click="handleClick">跳转记录</van-button>
|
||||
</template>
|
||||
|
||||
<script setup name="home">
|
||||
const router = useRouter()
|
||||
|
||||
// 跳转按钮操作
|
||||
const handleClick = () => {
|
||||
console.log('跳转记录')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('onMounted')
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
console.log('onActivated')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Reference in New Issue
Block a user