feat: 初始化

This commit is contained in:
George
2025-07-07 16:05:18 +08:00
commit c169958240
986 changed files with 132574 additions and 0 deletions

View File

@ -0,0 +1,40 @@
<template>
<image :style="{ width: size + 'px', height: size + 'px', flexShrink: 0 }" src="/static/copy.svg" mode="aspectFit" @click="handleCopy"></image>
</template>
<script>
export default {
name: 'CopyIcon',
props: {
value: {
type: String,
required: false,
default: ''
},
size: {
type: Number,
required: false,
default: 14
}
},
methods: {
handleCopy() {
const that = this;
uni.setClipboardData({
data: this.value.toString(),
success: function () {
uni.showToast({
title: that.$t('common.success.copy'),
icon: 'success',
duration: 2000
});
}
});
}
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

View File

@ -0,0 +1,102 @@
<template>
<view v-if="show">
<uni-popup ref="customModalRef" :isMaskClick="options.isMaskClick" :mask-background-color="options.type === 'message' ? 'transparent' : ''">
<view :class="['customModal', options.type === 'confirm' ? 'confirmModal' : 'messageModal']">
<image src="/static/closeIcon.svg" mode="aspectFit" class="customModalCloseIcon" @click="onClose"></image>
<view class="customModalDecoration">
<view class="customModalDecorationLeft"></view>
<view class="customModalDecorationRight"></view>
</view>
<view class="customModalContent">
<view class="customModalContentStatusIcon" v-if="options.type === 'message'">
<image v-if="options.status === 'success'" src="/static/success.png" mode="aspectFit" style="width: 35px; height: 35px"></image>
<image v-if="options.status === 'warning'" src="/static/warning.png" mode="aspectFit" style="width: 35px; height: 35px"></image>
</view>
<view class="customModalContentText">
<text>{{ options.contentText }}</text>
</view>
</view>
<view class="customModalConfirmBtns" v-if="options.type === 'confirm'">
<button class="secondaryButton cancelBtn" @click="onCancel">{{ options.cancelText }}</button>
<view :disabled="confirmBtnLoading" class="primaryButton confirmBtn" @click="onConfirm">
<image v-show="confirmBtnLoading" src="/static/loadingCircle.svg" mode="aspectFit" style="width: 16px; height: 16px"></image>
{{ options.confirmText }}
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
name: 'CustomModal',
data() {
return {
options: {
type: 'confirm',
isMaskClick: false,
closeAfterConfirm: true,
confirmText: this.$t('common.okText'),
cancelText: this.$t('common.cancelText')
},
confirmBtnLoading: false,
show: false
};
},
methods: {
showDialog(options) {
this.show = true
this.options = { ...this.options, ...options };
this.$nextTick(() => {
this.$refs.customModalRef.open();
})
},
async onConfirm() {
try {
if (this.options.onConfirm) {
this.confirmBtnLoading = true;
await this.options.onConfirm();
this.confirmBtnLoading = false;
}
} catch (e) {
console.log('e==>', e);
}
if (this.options.closeAfterConfirm) {
this.closeDialog();
}
},
async onCancel() {
try {
if (this.options.onCancel) {
await this.options.onCancel();
}
} catch (e) {
console.log('e==>', e);
}
await this.onClose();
this.closeDialog();
},
async onClose() {
try {
if (this.options.onClose) {
await this.options.onClose();
}
} catch (e) {
console.log('e==>', e);
}
this.closeDialog();
},
closeDialog() {
this.$refs.customModalRef.close();
this.$nextTick(() => {
this.show = false
})
}
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,82 @@
.customModal {
position: relative;
width: 90vw;
box-sizing: border-box;
background-color: #fff;
overflow: hidden;
box-shadow: 0px 2px 9px rgba(0, 0, 0, 0.25);
&.confirmModal {
max-width: 360px;
border-radius: 18px;
.customModalContent {
padding: 54px 54px 45px;
}
}
&.messageModal {
max-width: 264px;
border-radius: 12px;
.customModalContent {
padding: 16px 24px;
}
.customModalCloseIcon {
top: 14px;
right: 10px;
width: 14px;
height: 14px;
}
.customModalDecoration {
height: 5px;
.customModalDecorationLeft {
width: 60px;
}
}
.customModalContent {
.customModalContentText {
line-height: 29px;
}
}
}
.customModalCloseIcon {
position: absolute;
top: 26px;
right: 22px;
width: 16px;
height: 16px;
}
.customModalDecoration {
display: flex;
width: 100%;
height: 8px;
.customModalDecorationLeft {
width: 90px;
height: 100%;
background-color: #0f3675;
}
.customModalDecorationRight {
flex: 1;
height: 100%;
background-color: #29bbe4;
}
}
.customModalContent {
box-sizing: border-box;
.customModalContentStatusIcon {
display: flex;
justify-content: center;
margin-bottom: 4px;
}
.customModalContentText {
font-size: 20px;
font-weight: 500;
line-height: 35px;
text-align: center;
}
}
.customModalConfirmBtns {
display: flex;
align-items: center;
column-gap: 37px;
padding: 0px 41px 31px;
box-sizing: border-box;
}
}

View File

@ -0,0 +1,428 @@
<template>
<page-meta :page-style="`overflow:${show ? 'visible' : 'hidden'};height:100%;font-family:${fontFamily};`"></page-meta>
<!-- #ifdef APP-PLUS -->
<view class="status_bar">
<view class="top_view"></view>
</view>
<!-- #endif -->
<CustomModal ref="cusModal" />
<view class="navBarWrapper">
<view class="navBar" v-if="token">
<image src="/static/menu.png" mode="aspectFit" style="width: 16px; height: 13px" @click="toggleLeftDrawerVisible"></image>
<image src="/static/logo.png" alt="" style="width: 86px; height: 31px" @click="redirectToTarget('/pages/home/index')" />
<image src="/static/notification.png" mode="aspectFit" alt="" style="width: 17px; height: 19px; margin-right: 6px" @click="toggleRightDrawerVisible" />
<uni-drawer ref="showLeft" mode="left" :width="300" class="leftDrawer" @change="handleLeftDrawerChange">
<scroll-view class="scrollView" scroll-y="true">
<view class="menuItem userName" @click="toPersonalInfoPage">
<image style="width: 18px; height: 18px" src="/static/avatar.png" mode="aspectFit"></image>
<text>{{ userInfo?.name }}</text>
</view>
<view class="menuItem balance" v-if="isIb">
<image style="width: 18px; height: 18px" src="/static/wallet.png" mode="aspectFit"></image>
<view>
<text class="value">{{ ibFund?.amount }}</text>
USD
</view>
</view>
<uni-collapse accordion @change="change">
<view :class="['menuItem', isActived('/pages/home/index') ? 'active' : '']" @tap="goToTarget('/pages/home/index')">
<image
style="width: 18px; height: 18px"
:src="`/static/${isActived('/pages/home/index') ? 'homeActived' : 'home'}.svg`"
mode="aspectFit"
></image>
<text class="value">{{ $t('menu.home') }}</text>
</view>
<view v-for="(menu,index) in menuListData" :key="menu.saleRouteKey" class="selectorWrapper" >
<view class="menuItem selector" @click="toggleMenuOptionsVisible(index)">
<image class="menuIcon" :src="`/static/${menu.saleRouteKey}.svg`" mode="aspectFit"></image>
<text>{{menu.name}}</text>
</view>
<view ref="languageSelector" :class="['optionsWrapper', { visible: menuListData[index].optionsVisible }]">
<view v-for="menu2 in menu.children" :key="menu2.saleRouteKey">
<view :class="['option' , isActived((menuLinks[menu.saleRouteKey])[menu2.saleRouteKey]) ? 'active' : '']" @tap="goToTarget((menuLinks[menu.saleRouteKey])[menu2.saleRouteKey])">
<text >{{menu2.name}}</text>
</view>
</view>
</view>
<view class="menuRight" v-show="menuListData[index].optionsVisible">
<image class="menuIcon" :src="`/static/packUp.svg`" mode="aspectFit"></image>
</view>
<view class="menuRight" v-show="!menuListData[index].optionsVisible">
<image class="menuIcon" :src="`/static/unfold.svg`" mode="aspectFit"></image>
</view>
</view>
<view style="border-top: 1px solid #ececec; margin-top: 8px">
<MenuLanguageSelector />
</view>
<view class="menuItem" style="min-height: 24px" @click="handleLogout">
<image style="width: 18px; height: 18px" src="/static/logout.svg" mode="aspectFit"></image>
<text class="value">{{ $t('menu.logOut') }}</text>
</view>
</uni-collapse>
</scroll-view>
</uni-drawer>
<uni-drawer ref="showRight" mode="right" :width="300" class="rightDrawer" @change="handleRightDrawerChange">
<view class="rightContent">
<scroll-view v-if="noticeList.length" :style="{ height: '100%' }" scroll-y="true">
<view
v-for="notice in noticeList"
:key="notice.id"
class="noticeItem"
@click="
(e) => {
showDialog(notice);
}
"
>
<view class="noticeSubject">{{ notice.subject }}</view>
<view class="noticeSummary">{{ notice.summary }}</view>
</view>
</scroll-view>
<view v-else style="margin-top: 20px">{{ $t('notice.empty') }}</view>
</view>
</uni-drawer>
</view>
<view class="navBar" v-else>
<image src="/static/logo.png" alt="" style="width: 70px; height: 26px; margin-left: 4px" />
<LanguageSelector />
</view>
<!-- 普通弹窗 -->
<uni-popup ref="alertDialog" :isMaskClick="false">
<view class="detailModal">
<uni-icons type="closeempty" size="17" class="closeIcon" @click="closeDialog"></uni-icons>
<view class="detailContent">
<view class="notice-content" v-html="currentNotice.content"></view>
</view>
</view>
</uni-popup>
<view></view>
</view>
</template>
<script>
import { useUserStore } from '@/stores/user.ts';
import LanguageSelector from '@/pages/components/languageSelector/index.vue';
import MenuLanguageSelector from '@/pages/components/menuLanguageSelector/index.vue';
import { getToken } from '@/utils/const.ts';
import { queryMenuList, getNotice, setNoticeReadStatus } from '@/services/home/home';
import { logout } from '@/services/user/loginAndRegister';
import { UserLanguage } from '@/utils/const';
//获取屏幕边界到安全区域距离
export default {
data() {
return {
fontFamily: 'SourceHanSans',
type: 'center',
msgType: 'success',
messageText: '这是一条成功提示',
value: '',
show: true,
showDl: true,
userInfo: {},
isIb: false,
ibFund: {},
currentPage: '',
menuListData: [],
menuLinks: {
customer: {
sell:'/pages/customer/sell/index',
ib:'/pages/customer/ib/index',
customer:'/pages/customer/customer/index',
mt:'/pages/customer/mt/index'
},
apply: {
saleFundOut:"/pages/capital/salesWithdraw/index"
},
audit: {
goldIn:"/pages/audit/goldIn/index",
goldOut:"/pages/audit/goldOut/index",
salesGoldOut:"/pages/audit/salesGoldOut/index",
integral:"/pages/audit/integral/index",
},
report: {
report14:"/pages/report/report14/index",
report16:"/pages/report/report16/index",
report17:"/pages/report/report17/index",
report20:"/pages/report/report20/index",
report22:"/pages/report/report22/index",
report21:"/pages/report/report21/index",
report28:"/pages/report/report28/index",
report27:"/pages/report/report27/index",
report26:"/pages/report/report26/index",
report24:"/pages/report/report24/index",
report70:"/pages/report/report70/index",
report81:"/pages/report/report81/index"
},
applyRecord: {
mt:"/pages/applyRecord/mt/index",
leverage:"/pages/applyRecord/leverage/index",
userChange:"/pages/applyRecord/userChange/index",
goldIn:"/pages/applyRecord/goldIn/index",
goldOut:"/pages/applyRecord/goldOut/index",
transfer:"/pages/applyRecord/transfer/index",
fundOut:"/pages/applyRecord/fundOut/index",
fundTransfer:"/pages/applyRecord/fundTransfer/index",
}
},
noticeList: [],
currentNotice: {},
windowScrollY: 0,
contentSize: 0
};
},
emits: ["navLeft", "@navRight"],
computed: {
paddingTop: () => {
const { safeAreaInsets } = uni.getSystemInfoSync();
return safeAreaInsets?.top + 'px';
},
token: () => {
return getToken();
}
},
methods: {
toggleMenuOptionsVisible(index) {
this.menuListData[index].optionsVisible = !this.menuListData[index].optionsVisible;
},
initData() {
const userStore = useUserStore();
this.userInfo = Object.assign(this.userInfo, userStore.userInfo);
this.ibFund = Object.assign(this.ibFund, userStore.ibFund);
this.isIb = Number(userStore.userInfo.user_type) === 1;
},
isActived(path) {
return path.includes(this.currentPage);
},
showDialog(notice = {}) {
if (notice.content.length) {
notice.content = `<div style="zoom:${this.contentSize};">`+notice.content+'</div>'
}
this.currentNotice = notice;
this.show = false;
this.toggleRightDrawerVisible();
if (notice.status === '0') {
setNoticeReadStatus({ id: notice.id });
}
this.showDl = true;
this.$refs.alertDialog.open();
},
closeDialog() {
this.show = true;
this.toggleRightDrawerVisible();
this.showDl = false;
this.$refs.alertDialog.close();
},
goToTarget(target) {
const pages = getCurrentPages();
if (!target.endsWith(pages[pages.length - 1].route)) {
uni.navigateTo({
url: target
});
this.show = true;
this.$refs.showLeft.close();
}
},
toPersonalInfoPage() {
this.$refs.showLeft.close();
uni.navigateTo({ url: '/pages/user/index' });
},
redirectToTarget(target) {
uni.redirectTo({
url: target
});
},
async handleLogout() {
this.$cusModal.showModal({
type: 'confirm',
contentText: this.$t('modal.logoutContent'),
closeAfterConfirm: true,
onConfirm: async () => {
try {
const res = await logout();
if (res && res.code === 0) {
const userStore = useUserStore();
userStore.clear();
uni.removeStorageSync('access_token');
uni.redirectTo({
url: '/pages/login/index'
});
}
} catch (e) {
console.log('e===>', e);
}
}
});
},
async getMenuListData() {
const res = await queryMenuList();
if (res && res.code === 0) {
const menuList = [];
for (var i = 0; i < res.data.length; i++) {
const { saleRouteKey } = res.data[i]
res.data[i].optionsVisible = false
if (saleRouteKey == 'customer') {
res.data[i].rank = 2
if (res.data[i].children.length>0){
let childrenMenuList = []
for (let j = 0; j < res.data[i].children.length; j++) {
if (res.data[i].children[j].saleRouteKey !=null && res.data[i].children[j].saleRouteKey !='')
childrenMenuList.push( res.data[i].children[j])
}
res.data[i].children = childrenMenuList
}
menuList.push(res.data[i])
} else if (saleRouteKey == 'apply') {
res.data[i].rank = 3
if (res.data[i].children.length>0){
let childrenMenuList = []
for (let j = 0; j < res.data[i].children.length; j++) {
if (res.data[i].children[j].saleRouteKey !=null && res.data[i].children[j].saleRouteKey !='')
childrenMenuList.push( res.data[i].children[j])
}
res.data[i].children = childrenMenuList
}
menuList.push(res.data[i])
} else if (saleRouteKey == 'audit') {
res.data[i].rank = 4
if (res.data[i].children.length>0){
let childrenMenuList = []
for (let j = 0; j < res.data[i].children.length; j++) {
if (res.data[i].children[j].saleRouteKey !=null && res.data[i].children[j].saleRouteKey !='')
childrenMenuList.push( res.data[i].children[j])
}
res.data[i].children = childrenMenuList
}
menuList.push(res.data[i])
}else if (saleRouteKey == 'report') {
res.data[i].rank = 5
if (res.data[i].children.length>0){
let childrenMenuList = []
for (let j = 0; j < res.data[i].children.length; j++) {
if (res.data[i].children[j].saleRouteKey !=null && res.data[i].children[j].saleRouteKey !='')
childrenMenuList.push( res.data[i].children[j])
}
res.data[i].children = childrenMenuList
}
menuList.push(res.data[i])
}else if (saleRouteKey == 'applyRecord') {
res.data[i].rank = 6
if (res.data[i].children.length>0){
let childrenMenuList = []
for (let j = 0; j < res.data[i].children.length; j++) {
if (res.data[i].children[j].saleRouteKey !=null && res.data[i].children[j].saleRouteKey !='')
childrenMenuList.push( res.data[i].children[j])
}
res.data[i].children = childrenMenuList
}
menuList.push(res.data[i])
}
}
menuList.sort((a, b) => a.rank - b.rank)
this.menuListData = menuList;
}
},
async getNoticeList() {
const res = await getNotice();
if (res && res.code === 0) {
this.noticeList = res.data;
}
},
recordWindowScrollY() {
// #ifdef H5
this.windowScrollY = window.scrollY;
// #endif
},
windowScrollByRecord() {
// #ifdef H5
setTimeout(() => {
window.scroll(0, this.windowScrollY);
}, 0);
// #endif
},
handleLeftDrawerChange(e) {
if (!e) {
this.show = true;
this.windowScrollByRecord();
}
this.$emit('navLeft', e || this.showDl)
},
toggleLeftDrawerVisible() {
if (this.$refs.showLeft.visibleSync) {
this.show = true;
this.windowScrollByRecord();
this.$refs.showLeft.close();
} else {
this.initData();
this.show = false;
this.recordWindowScrollY();
this.$refs.showLeft.open();
//如果接口出错没请求到数据,则打开侧边栏的时候再请求一次
if (!this.menuListData.length) {
this.getMenuListData();
}
}
},
handleRightDrawerChange(e) {
if (!e) {
this.show = true;
this.windowScrollByRecord();
}
this.$emit('navRight', e || this.showDl)
},
toggleRightDrawerVisible() {
if (this.$refs.showRight.visibleSync) {
this.show = true;
this.windowScrollByRecord();
this.$refs.showRight.close();
} else {
this.getNoticeList();
this.show = false;
this.recordWindowScrollY();
this.$refs.showRight.open();
}
},
open() {
// 通过组件定义的ref调用uni-popup方法 ,如果传入参数 type 属性将失效 ,仅支持 ['top','left','bottom','right','center']
this.$refs.popup.open('top');
},
toggle(type) {
this.type = type;
// open 方法传入参数 等同在 uni-popup 组件上绑定 type属性
this.$refs.popup.open(type);
},
change(e) {
console.log('popup_change', e);
}
},
created() {
const pages = getCurrentPages();
this.currentPage = pages[pages.length - 1].route;
const locale = uni.getLocale();
if (['zh-CN', 'zh-TW'].includes(locale)) {
this.fontFamily = 'SourceHanSans';
} else {
this.fontFamily = 'Arial';
}
},
async mounted() {
if (getToken()) {
this.getMenuListData();
}
this.$cusModal.register(this.$refs.cusModal);
this.contentSize = uni.upx2px(666) / 800
},
components: {
LanguageSelector,
MenuLanguageSelector
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,234 @@
.status_bar {
position: sticky;
top: 0px;
z-index: 98;
height: var(--status-bar-height);
width: 100%;
background-color: #f8f8f8;
.top_view {
height: var(--status-bar-height);
width: 100%;
position: fixed;
background-color: #f8f8f8;
top: 0;
z-index: 98;
}
}
.navBarWrapper {
position: sticky;
top: var(--status-bar-height);
/* #ifdef H5 */
top: 0;
/* #endif */
z-index: 98;
background-color: #fff;
.navBar {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 28px;
padding-right: 16px;
height: 44px;
border: 1px solid rgba(236, 236, 236, 1);
border-left-width: 0px;
border-right-width: 0px;
}
}
.leftDrawer {
top: calc(46px + var(--status-bar-height));
.submenu {
height: 48px;
padding: 0 48px;
border-bottom: 1px solid #ebeef5;
display: flex;
align-items: center;
column-gap: 10px;
}
.scrollView {
height: 100%;
::v-deep .uni-scroll-view-content {
height: unset;
padding-bottom: 24px;
}
}
.menuItem {
min-height: 50px;
margin: 0px 28px 0px 16px;
padding: 0px 10px;
display: flex;
align-items: center;
column-gap: 10px;
&.active {
color: #fff;
background-color: #29bbe4;
}
.menuIcon {
width: 18px;
height: 18px;
}
&.userName {
height: 65px;
border-bottom: 1px solid #ECECEC;
margin: 0 0 26px 0;
padding: 0px 38px 0px 26px;
}
&.balance {
height: 65px;
font-size: 12px;
font-weight: 600;
.value {
font-size: 24px;
font-weight: 700;
}
}
}
}
.rightDrawer {
top: calc(46px + var(--status-bar-height));
.rightContent {
padding: 0 20px;
.noticeItem {
display: flex;
flex-direction: column;
row-gap: 10px;
padding: 20px 0 14px;
border-bottom: 1px solid rgba(236, 236, 236, 1);
&:last-child {
border-bottom: none;
}
.noticeSubject {
color: rgba(51, 51, 51, 1);
font-size: 16px;
font-weight: 700;
}
.noticeSummary {
color: rgba(102, 102, 102, 1);
font-size: 14px;
line-height: 20px;
}
}
}
}
.popup-content {
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
height: 50px;
background-color: #fff;
}
.popup-height {
height: 100%;
flex: 1;
width: 200px;
}
.detailModal {
position: relative;
width: calc(100vw - 50px);
width: 726upx;
background-color: #fff;
box-sizing: border-box;
.detailContent {
padding: 32px 30upx 24px;
&::-webkit-scrollbar {
display: none;
}
}
.closeIcon {
position: absolute;
z-index: 10;
top: 18px;
right: 21px;
width: 17px;
height: 17px;
}
.titleWrapper {
display: flex;
flex-direction: column;
align-items: center;
row-gap: 12px;
color: #333333;
padding: 12px;
margin: 24px 0;
box-shadow: -1px 0 5px 1px rgba(0, 0, 0, 0.1);
.title {
margin: 0;
font-size: 24px;
font-weight: 700;
line-height: 34px;
}
.value {
font-size: 36px;
font-weight: 700;
color: #29bbe4;
line-height: 52px;
}
}
.btnsWrapper {
display: flex;
align-items: center;
column-gap: 10px;
.delBtn {
background-color: #f56c6c;
}
}
}
.selectorWrapper {
position: relative;
// display: flex;
// justify-content: space-between;
// align-items: center;
.selector {
display: flex;
align-items: center;
column-gap: 10px;
margin: 0px 10px 0px 16px;
padding: 0px 10px;
.selectorLabel {
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
width: 100%;
}
}
.optionsWrapper {
flex-shrink: 0;
height: 0px;
opacity: 0;
overflow: hidden;
transition: all 0.3s ease-out;
&.visible {
display: flex;
flex-direction: column;
height: 100%;
opacity: 1;
padding-left: 16px;
}
.option {
display: flex;
align-items: center;
min-width: 100px;
padding: 8px 20px 8px 40px;
box-sizing: border-box;
background: #F7F8FA;
text-align: center;
white-space: nowrap;
&.active {
color: #fff;
background-color: #29bbe4;
}
}
}
.menuRight{
position: absolute;
right: 20px;
top: 15px;
.menuIcon {
width: 12px;
height: 12px;
}
}
}

View File

@ -0,0 +1,57 @@
<template>
<view class="titleWrapper" @click="back">
<image src="/static/backIcon.png" mode="aspectFit" class="titleIcon"></image>
<text class="titleText" :style="{ letterSpacing: letterSpacing ?? '' }">{{ title }}</text>
</view>
</template>
<script>
export default {
name: 'PageTitle',
props: {
title: {
type: String,
required: false,
default: ''
},
backTarget: {
type: String,
required: false,
default: ''
},
onBack: {
type: Function,
required: false
}
},
data() {
return {
letterSpacing: undefined
};
},
methods: {
back() {
if (this.onBack) {
this.onBack();
}
if (this.backTarget) {
uni.redirectTo({ url: this.backTarget });
} else {
uni.navigateBack();
}
}
},
created() {
const locale = uni.getLocale();
if (['zh-CN', 'zh-TW'].includes(locale)) {
this.letterSpacing = '2px';
} else {
this.letterSpacing = undefined;
}
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,18 @@
.titleWrapper {
display: flex;
column-gap: 11px;
font-size: 22px;
font-weight: 700;
color: rgba(51, 51, 51, 1);
cursor: pointer;
.titleIcon {
width: 23px;
height: 23px;
margin-top: 9px;
flex-shrink: 0;
}
.titleText {
word-break: break-all;
line-height: 41px;
}
}

View File

@ -0,0 +1,39 @@
<template>
<view v-if="Math.min(progress, 100) !== 100 || error" class="progressWrapper" :style="{ height: height, backgroundColor: backgroundColor }">
<view v-if="!error" class="progressBar" :style="{ width: `${Math.min(progress, 100)}%`, backgroundColor: activeColor }"></view>
<view v-else class="progressBar error"></view>
</view>
</template>
<script>
export default {
name: 'Progress',
props: {
progress: {
type: Number,
required: false,
default: 100
},
error: { type: Boolean, required: false, default: false },
height: {
type: String,
required: false,
default: '2px'
},
activeColor: {
type: String,
required: false,
default: '#29BBE4'
},
backgroundColor: {
type: String,
required: false,
default: '#EBEBEB'
}
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,14 @@
.progressWrapper {
width: 100%;
border-radius: 100px;
overflow: hidden;
.progressBar {
height: 100%;
border-radius: 100px;
transition: all 0.3s ease-out;
&.error {
width: 100%;
background-color: #ff5733;
}
}
}

31
components/Spin/Spin.vue Normal file
View File

@ -0,0 +1,31 @@
<template>
<view class="spinWrapper">
<view class="spin" :style="{ color: color }">
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
<view class="spinItem"></view>
</view>
</view>
</template>
<script>
export default {
name: 'Spin',
props: {
color: {
type: String,
required: false,
default: '#0E3673'
}
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

109
components/Spin/index.scss Normal file
View File

@ -0,0 +1,109 @@
.spinWrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 70px;
transform: scale(0.8);
margin-top: 64px; //临时
margin-bottom: 64px; //临时
.spin {
position: relative;
top: -10px;
left: -10px;
.spinItem:nth-child(1) {
top: 25px;
left: 0;
-webkit-animation: ball-spin-fade-loader 1s -0.12s infinite linear;
animation: ball-spin-fade-loader 1s -0.12s infinite linear;
}
.spinItem:nth-child(2) {
top: 17.04545px;
left: 17.04545px;
-webkit-animation: ball-spin-fade-loader 1s -0.24s infinite linear;
animation: ball-spin-fade-loader 1s -0.24s infinite linear;
}
.spinItem:nth-child(3) {
top: 0;
left: 25px;
-webkit-animation: ball-spin-fade-loader 1s -0.36s infinite linear;
animation: ball-spin-fade-loader 1s -0.36s infinite linear;
}
.spinItem:nth-child(4) {
top: -17.04545px;
left: 17.04545px;
-webkit-animation: ball-spin-fade-loader 1s -0.48s infinite linear;
animation: ball-spin-fade-loader 1s -0.48s infinite linear;
}
.spinItem:nth-child(5) {
top: -25px;
left: 0;
-webkit-animation: ball-spin-fade-loader 1s -0.6s infinite linear;
animation: ball-spin-fade-loader 1s -0.6s infinite linear;
}
.spinItem:nth-child(6) {
top: -17.04545px;
left: -17.04545px;
-webkit-animation: ball-spin-fade-loader 1s -0.72s infinite linear;
animation: ball-spin-fade-loader 1s -0.72s infinite linear;
}
.spinItem:nth-child(7) {
top: 0;
left: -25px;
-webkit-animation: ball-spin-fade-loader 1s -0.84s infinite linear;
animation: ball-spin-fade-loader 1s -0.84s infinite linear;
}
.spinItem:nth-child(8) {
top: 17.04545px;
left: -17.04545px;
-webkit-animation: ball-spin-fade-loader 1s -0.96s infinite linear;
animation: ball-spin-fade-loader 1s -0.96s infinite linear;
}
.spinItem {
background-color: currentColor;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
position: absolute;
}
}
}
@-webkit-keyframes ball-spin-fade-loader {
50% {
opacity: 0.3;
-webkit-transform: scale(0);
transform: scale(0);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes ball-spin-fade-loader {
50% {
opacity: 0.3;
-webkit-transform: scale(0);
transform: scale(0);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}