up
37
web/app.vue
@ -8,6 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref,computed,watch } from 'vue'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import en from 'element-plus/dist/locale/en.mjs'
|
||||
@ -16,23 +17,16 @@ import useAppStore from '@/stores/app'
|
||||
import useMemberStore from '@/stores/member'
|
||||
// 引入全局样式
|
||||
import '@/assets/styles/index.scss'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
if (process.client) {
|
||||
const match = location.href.match(/\/web\/(\d*)\//)
|
||||
const cookie = useCookie('siteId')
|
||||
match ? cookie.value = match[1] : cookie.value = null
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
// 初始化设置语言
|
||||
const systemStore = useSystemStore()
|
||||
const locale = computed(() => (systemStore.lang === 'zh-cn' ? zhCn : en))
|
||||
|
||||
// 初始化查询一些配置
|
||||
const configStore = useConfigStore()
|
||||
configStore.getLoginConfig()
|
||||
|
||||
// 查询站点信息
|
||||
systemStore.getSitenfo()
|
||||
configStore.getLoginConfig(router)
|
||||
|
||||
// 如果已登录
|
||||
getToken() && useMemberStore().setToken(getToken())
|
||||
@ -57,11 +51,20 @@ watch(route, (nval, oval) => {
|
||||
}, !oval ? 500 : 0)
|
||||
}, { immediate: true })
|
||||
|
||||
// 设置title模板
|
||||
useHead({
|
||||
titleTemplate: (title) => {
|
||||
const siteTitle = systemStore.site.front_end_name || systemStore.site.site_name
|
||||
return title ? `${title} - ${siteTitle}` : siteTitle
|
||||
}
|
||||
})
|
||||
watch(() => systemStore.site, () => {
|
||||
useHead({
|
||||
titleTemplate: (title) => {
|
||||
const siteTitle = systemStore.site.front_end_name || systemStore.site.site_name
|
||||
if(title){
|
||||
if(siteTitle){
|
||||
return `${title} - ${siteTitle}`;
|
||||
}else {
|
||||
return title;
|
||||
}
|
||||
}else{
|
||||
return siteTitle;
|
||||
}
|
||||
}
|
||||
})
|
||||
}, { deep: true, immediate: true })
|
||||
</script>
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleList(params: Record<string, any>) {
|
||||
return request.get('article/article', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleAll(params: Record<string, any>) {
|
||||
return request.get('article/article/all', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情
|
||||
*/
|
||||
export function getArticleDetail(id: number) {
|
||||
return request.get(`article/article/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类
|
||||
*/
|
||||
export function getArticleCategory() {
|
||||
return request.get('article/category')
|
||||
}
|
||||
@ -31,7 +31,7 @@ export function logout() {
|
||||
*/
|
||||
export function usernameRegister(data: AnyObject) {
|
||||
let url = 'register'
|
||||
data.pid && (url += `?pid=${data.pid}`)
|
||||
data.pid && (url += `?pid=${ data.pid }`)
|
||||
return request.post(url, data)
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ export function usernameRegister(data: AnyObject) {
|
||||
*/
|
||||
export function mobileRegister(data: AnyObject) {
|
||||
let url = 'register/mobile'
|
||||
data.pid && (url += `?pid=${data.pid}`)
|
||||
data.pid && (url += `?pid=${ data.pid }`)
|
||||
return request.post(url, data)
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ export function weappLogin(data: AnyObject) {
|
||||
*/
|
||||
export function bind(data: AnyObject) {
|
||||
let url = 'bind'
|
||||
data.pid && (url += `?pid=${data.pid}`)
|
||||
data.pid && (url += `?pid=${ data.pid }`)
|
||||
return request.post(url, data)
|
||||
}
|
||||
|
||||
@ -79,4 +79,11 @@ export function scanlogin() {
|
||||
*/
|
||||
export function checkscan(data: AnyObject) {
|
||||
return request.get('checkscan', data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验扫码信息
|
||||
*/
|
||||
export function wechatCheck() {
|
||||
return request.get('wechat/check')
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ export function getMemberInfo() {
|
||||
* 会员信息修改
|
||||
*/
|
||||
export function modifyMember(data: AnyObject) {
|
||||
return request.put(`member/modify/${data.field}`, data)
|
||||
return request.put(`member/modify/${ data.field }`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -16,6 +16,13 @@ export function getPointList(data: AnyObject) {
|
||||
return request.get('member/account/point', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人积分
|
||||
*/
|
||||
export function getMemberAccountPointcount() {
|
||||
return request.get(`member/account/pointcount`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取余额流水
|
||||
*/
|
||||
@ -23,9 +30,23 @@ export function getBalanceList(data: AnyObject) {
|
||||
return request.get('member/account/balance', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取余额流水
|
||||
*/
|
||||
export function getBalanceListAll(data: AnyObject) {
|
||||
return request.get('member/account/balance_list', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录会员绑定手机号
|
||||
*/
|
||||
export function bindMobile(data: AnyObject) {
|
||||
return request.put('member/mobile', data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员等级
|
||||
*/
|
||||
export function getMemberLevel() {
|
||||
return request.get(`member/level`);
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export function wechatSync(data: AnyObject) {
|
||||
* 获取协议信息
|
||||
*/
|
||||
export function getAgreementInfo(key: string) {
|
||||
return request.get(`agreement/${key}`)
|
||||
return request.get(`agreement/${ key }`)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +37,7 @@ export function resetPassword(data: AnyObject) {
|
||||
* 发送短信验证码
|
||||
*/
|
||||
export function sendSms(data: AnyObject) {
|
||||
return request.post(`send/mobile/${data.type}`, data)
|
||||
return request.post(`send/mobile/${ data.type }`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,3 +74,23 @@ export function getCopyRight() {
|
||||
export function getSiteInfo() {
|
||||
return request.get('site')
|
||||
}
|
||||
/**
|
||||
* 获取广告位
|
||||
*/
|
||||
export function getAdvInfo(params: Record<string, any>) {
|
||||
return request.get(`web/adv`, params, { showErrorMessage: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导航列表
|
||||
*/
|
||||
export function getNavList() {
|
||||
return request.get(`web/nav`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取友情链接
|
||||
*/
|
||||
export function getFriendlyLink() {
|
||||
return request.get(`web/friendly_link`)
|
||||
}
|
||||
|
||||
43
web/app/api/verify.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取核销信息
|
||||
*/
|
||||
export function getVerifyCode(type: string, params: AnyObject) {
|
||||
return request.get('verify', { type, data: params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核销记录
|
||||
*/
|
||||
export function getVerifyRecords(params: Record<string, any>) {
|
||||
return request.get('verify_records', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是核销员
|
||||
*/
|
||||
export function getCheckVerifier() {
|
||||
return request.get('check_verifier')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核销信息
|
||||
*/
|
||||
export function getVerifierInfo(code: string) {
|
||||
return request.get(`get_verify_by_code/${ code }`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 核销
|
||||
*/
|
||||
export function verify(code: string) {
|
||||
return request.post(`verify/${ code }`, {}, { showSuccessMessage: true, showErrorMessage: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取核销详情
|
||||
*/
|
||||
export function getVerifyDetail(code: string) {
|
||||
return request.get(`verify_detail/${ code }`, {}, { showErrorMessage: true })
|
||||
}
|
||||
|
Before Width: | Height: | Size: 16 KiB |
8
web/app/lang/zh-cn/app.member.balance.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"myBalance": "我的余额",
|
||||
"accountType": "账户类型",
|
||||
"changeInAmount": "金额变化",
|
||||
"modeOfOccurrence": "发生方式",
|
||||
"remark": "备注",
|
||||
"occurrenceTime": "发生时间"
|
||||
}
|
||||
15
web/app/lang/zh-cn/app.member.center.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"personageInfo": "个人信息",
|
||||
"memberHeadimg": "会员头像",
|
||||
"edit": "修改",
|
||||
"nickname": "会员昵称",
|
||||
"username":"用户名",
|
||||
"mobile":"手机号",
|
||||
"updateMobile":"修改手机号",
|
||||
"mobilePlaceholder": "请输入手机号",
|
||||
"mobileTips":"请输入正确的手机号",
|
||||
"mobileError": "请输入正确的手机号",
|
||||
"codePlaceholder": "请输入手机验证码",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定"
|
||||
}
|
||||
8
web/app/lang/zh-cn/app.member.point.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"myPoint": "我的积分",
|
||||
"accountType": "账户类型",
|
||||
"changeInAmount": "金额变化",
|
||||
"modeOfOccurrence": "发生方式",
|
||||
"remark": "备注",
|
||||
"occurrenceTime": "发生时间"
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
{
|
||||
|
||||
"bind": "绑定",
|
||||
"binding": "绑定中",
|
||||
"usernamePlaceholder": "请输入账号",
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
"privacyAgreement": "隐私协议",
|
||||
"protocolNotConfigured": "未配置协议",
|
||||
"siteClose": "站点已关闭",
|
||||
"noSite": "站点不存在",
|
||||
|
||||
"request": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
@ -32,4 +32,4 @@
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,13 @@
|
||||
"memberHeadimg": "会员头像",
|
||||
"edit": "修改",
|
||||
"nickname": "会员昵称",
|
||||
"username":"用户名",
|
||||
"mobile":"手机号",
|
||||
"updateMobile":"修改手机号",
|
||||
"mobilePlaceholder": "请输入手机号",
|
||||
"mobileTips":"请输入正确的手机号",
|
||||
"mobileError": "请输入正确的手机号",
|
||||
"codePlaceholder": "请输入手机验证码",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定"
|
||||
}
|
||||
@ -5,5 +5,6 @@
|
||||
"point": "积分",
|
||||
"balance": "余额",
|
||||
"looseChange": "零钱",
|
||||
"notBound": "未绑定"
|
||||
"notBound": "未绑定",
|
||||
"coupon":"优惠劵"
|
||||
}
|
||||
@ -1,20 +1,34 @@
|
||||
{
|
||||
"pages": {
|
||||
"index": {
|
||||
"index": "首页"
|
||||
},
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "登录",
|
||||
"bind": "手机号绑定"
|
||||
},
|
||||
"member": {
|
||||
"index": "欢迎页",
|
||||
"center": "个人中心"
|
||||
},
|
||||
"site": {
|
||||
"close": "站点已关闭",
|
||||
"nosite": "站点不存在"
|
||||
}
|
||||
"pages": {
|
||||
"article": {
|
||||
"list": "文章资讯",
|
||||
"detail": "文章详情"
|
||||
},
|
||||
"index": {
|
||||
"index": "首页"
|
||||
},
|
||||
"app":{
|
||||
"index": "首页",
|
||||
"member":{
|
||||
"center": "个人中心",
|
||||
"point": "我的积分",
|
||||
"balance": "我的余额"
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"agreement": "协议",
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"bind": "手机号绑定"
|
||||
},
|
||||
"member": {
|
||||
"index": "欢迎页",
|
||||
"center": "个人中心",
|
||||
"point": "我的积分",
|
||||
"balance": "我的余额"
|
||||
},
|
||||
"site": {
|
||||
"close": "站点已关闭"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full min-h-[100%] main-container pt-5">
|
||||
<div class="mt-[20px] mb-[50px]" v-if="articleDeatail">
|
||||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item :to="{ path: '/article/list' }">文章</el-breadcrumb-item>
|
||||
<el-breadcrumb-item >{{ articleDeatail.category_name }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div >
|
||||
<p class="py-[20px] text-center text-[24px]">{{ articleDeatail.title }}</p>
|
||||
<div class="flex justify-center">
|
||||
<!-- <div class="mr-3 flex items-center text-gray-500 text-sm text-[#999]"><el-icon><View /></el-icon> <span class="ml-1">浏览量:158</span></div> -->
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm text-[#999]"><el-icon><Clock /></el-icon><span class="ml-1">时间:{{ articleDeatail.create_time }}</span></div>
|
||||
</div>
|
||||
<div class="mt-[50px]" v-html="articleDeatail.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { getArticleDetail } from '@/app/api/article'
|
||||
import { ArrowRight } from '@element-plus/icons-vue'
|
||||
import { nMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const Route = useRoute(); //获取到值
|
||||
const articleDeatail = ref();
|
||||
onMounted(() => {
|
||||
obtainArticleInfo(Route.query.id)
|
||||
});
|
||||
|
||||
const obtainArticleInfo = (id) => {
|
||||
getArticleDetail(id).then(res => {
|
||||
articleDeatail.value = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.index-carousel {
|
||||
background-image: url('@/assets/images/index_carousel.png');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.article-wrap{
|
||||
span{
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 5px var(--el-color-primary-light-7);
|
||||
&.active{
|
||||
background-image: linear-gradient(to right,var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
}
|
||||
&:hover{
|
||||
background-image: linear-gradient(to right,var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tow-line-overflow{
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.text-color{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.custom-tabs-label span{
|
||||
font-size: 20px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
</style>
|
||||
@ -1,162 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full main-container pt-5">
|
||||
<el-carousel height="350px" indicator-position="none" arrow="never">
|
||||
<el-carousel-item>
|
||||
<div class="h-full index-carousel"></div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="mt-[20px] mb-[50px]">
|
||||
<div>
|
||||
<div class="w-full">
|
||||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item :to="{ path: '/article/list' }">文章</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-if="selectedCategoryName">{{ selectedCategoryName }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div class="flex mt-[20px] items-start">
|
||||
<div class="w-[50px]">类目:</div>
|
||||
<el-row>
|
||||
<el-button class="mb-[10px]" @click="selectedCategory(categoryItem)" v-for="(categoryItem, categoryIndex) in activeCategoryLsit" :key="categoryIndex">{{ categoryItem.name }}</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="article-list mb-[20px] cursor-pointer" v-for="(activeItem, activeIndex) in articleTableData.data" :key="activeIndex" @click="toLink(activeItem.id)">
|
||||
<div class="flex justify-between relative py-[20px] border-b-1 border-gray-300 border-solid">
|
||||
<div class="w-[150px] h-[150px] flex items-center">
|
||||
<img :src="img(activeItem.image)"/>
|
||||
</div>
|
||||
<div class="w-[1030px]">
|
||||
<p class="text-xl font-bold">{{ activeItem.title }}</p>
|
||||
<span class="overflow-ellipsis mt-2 mb-2 tow-line-overflow text-gray-500">{{ activeItem.intro }}</span>
|
||||
</div>
|
||||
<!-- <div class="activeBo flex items-right mt-2 justify-end absolute">
|
||||
<span class="mr-5 text-sm text-gray-500">{{ activeItem.create_time }}</span>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><View /></el-icon> <span class="ml-1">158</span></div>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><Pointer /></el-icon> <span class="ml-1">22</span></div>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><Star /></el-icon> <span class="ml-1">55</span></div>
|
||||
<div class="flex items-center text-gray-500 text-sm"><el-icon><ChatDotRound /></el-icon> <span class="ml-1">655</span></div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<el-pagination
|
||||
class="justify-center"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
:page-size="articleTableData.limit"
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:total="articleTableData.total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getArticleCategory,getArticleList } from '@/app/api/article'
|
||||
import { ArrowRight } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const activeCategoryLsit = ref([])
|
||||
const selectedCategoryName = ref()
|
||||
const articleTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
category_id:''
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
const loadArticleList = (page: number = 1) => {
|
||||
articleTableData.loading = true
|
||||
articleTableData.page = page
|
||||
|
||||
getArticleList({
|
||||
page: articleTableData.page,
|
||||
limit: articleTableData.limit,
|
||||
...articleTableData.searchParam
|
||||
}).then(res => {
|
||||
articleTableData.loading = false
|
||||
articleTableData.data = res.data.data
|
||||
articleTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
articleTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadArticleList()
|
||||
|
||||
const checkArticleCategory = () => {
|
||||
getArticleCategory().then(res => {
|
||||
activeCategoryLsit.value = res.data.data;
|
||||
})
|
||||
}
|
||||
checkArticleCategory()
|
||||
|
||||
const selectedCategory = (item) => {
|
||||
articleTableData.searchParam.category_id = item.category_id;
|
||||
selectedCategoryName.value = item.name
|
||||
}
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
loadArticleList(val)
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
loadArticleList(val)
|
||||
}
|
||||
const toLink = (id) => {
|
||||
router.push(`/article/detail?id=${id}`)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.index-carousel {
|
||||
background-image: url('@/assets/images/index_carousel.png');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.article-wrap{
|
||||
span{
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 5px var(--el-color-primary-light-7);
|
||||
&.active{
|
||||
background-image: linear-gradient(to right,var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
}
|
||||
&:hover{
|
||||
background-image: linear-gradient(to right,var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tow-line-overflow{
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.text-color{
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.custom-tabs-label span{
|
||||
font-size: 20px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
.activeBo {
|
||||
bottom : 20px;
|
||||
right : 0px
|
||||
}
|
||||
</style>
|
||||
@ -1,29 +1,46 @@
|
||||
<template>
|
||||
<div class="w-full pt-6 min-h-[100%] flex flex-col justify-center">
|
||||
<template v-if="agreement">
|
||||
<div class="main-container" v-if="agreement.title && agreement.content">
|
||||
<h2 class="text-center">{{ agreement.title }}</h2>
|
||||
<div v-html="agreement.content"></div>
|
||||
<div class="ml-[20px] min-h-[70vh] px-[20px] py-[30px] w-[1000px] bg-[#fff] rounded-[var(--rounded-big)]">
|
||||
<div>
|
||||
<div>
|
||||
<template v-if="agreement">
|
||||
<div class="" v-if="agreement.title && agreement.content">
|
||||
<h2 class="text-center">{{ agreement.title }}</h2>
|
||||
<div v-html="agreement.content"></div>
|
||||
</div>
|
||||
<el-empty :description="t('protocolNotConfigured')" :image-size="200" :image="img('static/resource/images/system/empty.png')" v-else />
|
||||
</template>
|
||||
</div>
|
||||
<el-empty :description="t('protocolNotConfigured')" v-else />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { getAgreementInfo } from '@/app/api/system'
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const agreement = ref<any | null>(null)
|
||||
const route = useRoute()
|
||||
watch(() => route.query.key, (newVal, oldVal) => {
|
||||
if(route.query.key){
|
||||
getAgreementInfo(route.query.key).then(({ data }) => {
|
||||
agreement.value = data
|
||||
|
||||
getAgreementInfo(route.query.key).then(({ data }) => {
|
||||
agreement.value = data
|
||||
if(data.title){
|
||||
useHead({
|
||||
title: data.title
|
||||
})
|
||||
}else {
|
||||
useHead({
|
||||
title: route.query.key == 'service' ? '用户协议' : '隐私协议'
|
||||
})
|
||||
}
|
||||
|
||||
}).catch(err => {
|
||||
})
|
||||
}
|
||||
},{immediate: true})
|
||||
|
||||
useHead({
|
||||
title: data.title
|
||||
})
|
||||
}).catch(err => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -36,10 +36,12 @@ import { bind } from '@/app/api/auth'
|
||||
import { bindMobile } from '@/app/api/member'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
let router = useRouter()
|
||||
const memberStore = useMemberStore()
|
||||
const info = computed(() => memberStore.info)
|
||||
const loading = ref(false)
|
||||
@ -88,7 +90,7 @@ const handleRegister = async () => {
|
||||
|
||||
request(formData).then((res: responseResult) => {
|
||||
memberStore.setToken(res.data.token)
|
||||
useLogin().handleLoginBack()
|
||||
router.push({ path: '/' })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
captcha.refresh()
|
||||
|
||||
@ -1,21 +1,7 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page flex items-center justify-center">
|
||||
<div class="flex bg-white">
|
||||
<div class="flex flex-col items-center w-[330px] py-[100px] border-r">
|
||||
<div class="title font-bold text-xl">打开手机微信</div>
|
||||
<div class="tips text-sm mt-[5px]">点击右上角打开扫一扫</div>
|
||||
<div class="qrcode p-[10px] mt-[30px] border h-[120px] leading-none box-content">
|
||||
<div class="relative">
|
||||
<el-image :src="weixinCode.url" class="w-[120px]" />
|
||||
<div class="flex flex-col justify-center items-center absolute inset-0 bg-gray-50" v-if="weixinCode.pastDue">
|
||||
<span class="text-xs text-gray-600">{{ weixinCode.pastDueContent }}</span>
|
||||
<span @click="scanLoginFn()" class="text-xs cursor-pointer text-color mt-2">点击刷新</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white w-[380px] p-[30px]">
|
||||
<div class="bg-white" v-if="loginType.length" >
|
||||
<div class="bg-white w-[380px] p-[30px] h-[424px]" v-if="active">
|
||||
<div class="flex items-end my-[30px]">
|
||||
<div class="mr-[20px] text-base cursor-pointer leading-none" :class="{ 'font-bold': type == item.type }" v-for="item in loginType" @click="type = item.type">{{item.title }}</div>
|
||||
</div>
|
||||
@ -67,25 +53,57 @@
|
||||
<span class="text-primary">{{ t('privacyAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="mt-[20px] flex justify-center" v-if="show">
|
||||
<span class="iconfont icon-weixin1 text-[#1AAD19] !text-[24px] cursor-pointer" @click="handleChange"></span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="flex flex-col items-center w-[380px] py-[60px] h-[424px]" v-else>
|
||||
<div class="title font-bold text-xl">打开手机微信</div>
|
||||
<div class="tips text-sm mt-[5px]">点击右上角打开扫一扫</div>
|
||||
<div class="qrcode p-[10px] mt-[30px] border h-[120px] leading-none box-content">
|
||||
<div class="relative">
|
||||
<el-image :src="weixinCode.url" class="w-[120px]" />
|
||||
<div class="flex flex-col justify-center items-center absolute inset-0 bg-gray-50" v-if="weixinCode.pastDue">
|
||||
<span class="text-xs text-gray-600">{{ weixinCode.pastDueContent }}</span>
|
||||
<span @click="scanLoginFn()" class="text-xs cursor-pointer text-color mt-2">点击刷新</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[60px] text-base cursor-pointer leading-none" @click="handleChange">账号登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref,reactive,watch,computed } from 'vue'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { usernameLogin, mobileLogin, scanlogin, checkscan } from '@/app/api/auth'
|
||||
import { usernameLogin, mobileLogin, scanlogin, checkscan, wechatCheck } from '@/app/api/auth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import QRCode from "qrcode";
|
||||
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
let router = useRouter()
|
||||
let active = ref(true)
|
||||
let timer:any = null
|
||||
const handleChange = () => {
|
||||
active.value = !active.value
|
||||
if(!active.value){
|
||||
scanLoginFn();
|
||||
}else{
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
(toPath) => {
|
||||
if (toPath != '/auth/login') {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}, { immediate: true, deep: true }
|
||||
)
|
||||
// 校验二维码
|
||||
const checkScanFn = (key) => {
|
||||
let parameter = { key };
|
||||
@ -94,18 +112,17 @@ const checkScanFn = (key) => {
|
||||
let data = res.data;
|
||||
switch (data.status) {
|
||||
case 'wait':
|
||||
setTimeout(() => {
|
||||
timer = setTimeout(() => {
|
||||
checkScanFn(weixinCode.value.key);
|
||||
}, 1000);
|
||||
break;
|
||||
|
||||
case 'success':
|
||||
if (!data.login_data.token) {
|
||||
useCookie('openId').value = data.login_data.openid
|
||||
navigateTo(`/auth/bind`)
|
||||
} else {
|
||||
memberStore.setToken(data.login_data.token)
|
||||
useLogin().handleLoginBack()
|
||||
router.push({ path: '/' })
|
||||
}
|
||||
break;
|
||||
case 'fail':
|
||||
@ -139,7 +156,14 @@ const scanLoginFn = async () => {
|
||||
checkScanFn(weixinCode.value.key);
|
||||
}, 1000);
|
||||
}
|
||||
scanLoginFn();
|
||||
|
||||
let show = ref(false)
|
||||
const wechatCheckFn = () =>{
|
||||
wechatCheck().then((res:any) =>{
|
||||
show.value = res.data
|
||||
})
|
||||
}
|
||||
wechatCheckFn()
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
|
||||
@ -153,7 +177,6 @@ const loginType = computed(() => {
|
||||
type.value = value[0] ? value[0].type : ''
|
||||
return value
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const type = ref('')
|
||||
const formData = reactive({
|
||||
@ -209,7 +232,7 @@ const handleLogin = async () => {
|
||||
|
||||
login(formData).then(async (res) => {
|
||||
await memberStore.setToken(res.data.token)
|
||||
useLogin().handleLoginBack()
|
||||
router.push({ path: '/app/index' })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
@ -1,30 +1,22 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page flex items-center justify-center">
|
||||
<div class="flex bg-white">
|
||||
<div class="flex flex-col items-center w-[330px] py-[100px] border-r">
|
||||
<div class="title font-bold text-xl">打开手机微信</div>
|
||||
<div class="tips text-sm mt-[5px]">点击右上角打开扫一扫</div>
|
||||
<div class="qrcode mt-[30px] border leading-none">
|
||||
<el-image :src="img('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQHU7zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySlJSbU1Sb0hiMlQxOEcwSGhBY1AAAgTSfStkAwRYAgAA')" class="w-[120px]"></el-image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white w-[380px] p-[30px]">
|
||||
<div class="bg-white w-[380px] p-[30px] h-[686px]" v-if="active">
|
||||
<div class="flex items-end my-[30px]">
|
||||
<div class="mr-[20px] text-base cursor-pointer leading-none" :class="{ 'font-bold': type == item.type }" v-for="item in registerType" @click="type = item.type">{{item.title }}</div>
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" :validate-on-rule-change="false">
|
||||
<div v-show="type == 'username'">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="formData.username" :placeholder="t('usernamePlaceholder')" clearable :inline-message="true">
|
||||
<el-input v-model="formData.username" :placeholder="t('usernamePlaceholder')" clearable :inline-message="true" :readonly="real_name_input" @click="real_name_input = false" @blur="real_name_input = true">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" type="password" clearable :show-password="true">
|
||||
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" type="password" clearable :show-password="true" :readonly="password_input" @click="password_input = false" @blur="password_input = true">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirm_password">
|
||||
<el-input v-model="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" type="password" clearable :show-password="true">
|
||||
<el-input v-model="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" type="password" clearable :show-password="true" :readonly="confirm_password_input" @click="confirm_password_input = false" @blur="confirm_password_input = true">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@ -73,22 +65,36 @@
|
||||
<span class="text-primary">{{ t('privacyAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mt-[20px] flex justify-center" v-if="show">
|
||||
<span class="iconfont icon-weixin1 text-[#1AAD19] !text-[24px]" @click="active = !active"></span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="flex flex-col items-center w-[380px] py-[100px] h-[556px]" v-else>
|
||||
<div class="title font-bold text-xl">打开手机微信</div>
|
||||
<div class="tips text-sm mt-[5px]">点击右上角打开扫一扫</div>
|
||||
<div class="qrcode mt-[30px] border leading-none">
|
||||
<el-image :src="img('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQHU7zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySlJSbU1Sb0hiMlQxOEcwSGhBY1AAAgTSfStkAwRYAgAA')" class="w-[120px]"></el-image>
|
||||
</div>
|
||||
<div class="mt-[60px] text-base cursor-pointer leading-none" @click="active = !active">账号注册</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usernameRegister, mobileRegister } from '@/app/api/auth'
|
||||
import { ref,reactive,watch,computed } from 'vue'
|
||||
import { usernameRegister, mobileRegister, wechatCheck } from '@/app/api/auth'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
let router = useRouter()
|
||||
const memberStore = useMemberStore()
|
||||
|
||||
const configStore = useConfigStore()
|
||||
@ -104,7 +110,7 @@ const registerType = computed(() => {
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
let active = ref(true)
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
@ -188,7 +194,7 @@ const handleRegister = async () => {
|
||||
|
||||
register(formData).then((res: responseResult) => {
|
||||
memberStore.setToken(res.data.token)
|
||||
useLogin().handleLoginBack()
|
||||
router.push({ path: '/' })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
captcha.refresh()
|
||||
@ -197,6 +203,14 @@ const handleRegister = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
let show = ref(false)
|
||||
const wechatCheckFn = () =>{
|
||||
wechatCheck().then((res:any) =>{
|
||||
show.value = res.data
|
||||
})
|
||||
}
|
||||
wechatCheckFn()
|
||||
|
||||
// 验证码
|
||||
const captcha = useCaptcha(formData)
|
||||
captcha.refresh()
|
||||
@ -210,6 +224,11 @@ const sendSmsCode = async () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const real_name_input = ref(true)
|
||||
const password_input = ref(true)
|
||||
const confirm_password_input = ref(true)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="w-full bg-[#fff]">
|
||||
<el-carousel height="500px" arrow="never">
|
||||
<el-carousel-item>
|
||||
<div class="h-full index-carousel"></div>
|
||||
@ -16,9 +16,8 @@
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/course.jpg" /></div>
|
||||
<p class="text-[20px] text-[#666] font-bold">官方教程</p>
|
||||
</div>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">
|
||||
详尽细致的逐步官方教程,帮助您系统全面的接触NIUCLOUD,建议在使用前阅读。</p>
|
||||
<NuxtLink to="https://www.kancloud.cn/cui18734824089/niucloud-admin-develop/3148343" target="_blank">
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">详尽细致的逐步官方教程,帮助您系统全面的接触NIUCLOUD,建议在使用前阅读。</p>
|
||||
<NuxtLink to="https://doc.niucloud.com/v6" target="_blank">
|
||||
<div class="flex justify-between items-center w-[280px] h-[40px] leading-[40px] rounded-[5px] border-[1px] border-[solid] border-[#508BFE]">
|
||||
<span class="block ml-[20px] text-[14px] text-[#333]">前往教程</span>
|
||||
<span class="block mr-[20px] text-[24px] text-[#333]">→</span>
|
||||
@ -30,23 +29,21 @@
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/api.jpg" /></div>
|
||||
<p class="text-[20px] text-[#666] font-bold">API文档</p>
|
||||
</div>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">
|
||||
您可以通过API文档了解niucloud的正确使用方法,也可以更加深入地理解niucloud的运行逻辑。</p>
|
||||
<NuxtLink to="https://www.niucloud.com/apidoc.html" target="_blank">
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">您可以通过API文档了解niucloud的正确使用方法,也可以更加深入地理解niucloud的运行逻辑。</p>
|
||||
<!-- <NuxtLink to="https://www.niucloud.com/apidoc.html" target="_blank">-->
|
||||
<div class="flex justify-between items-center w-[280px] h-[40px] leading-[40px] rounded-[5px] border-[1px] border-[solid] border-[#508BFE]">
|
||||
<span class="block ml-[20px] text-[14px] text-[#333]">前往API文档</span>
|
||||
<span class="block mr-[20px] text-[24px] text-[#333]">→</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<!-- </NuxtLink>-->
|
||||
</div>
|
||||
<div class="w-[280px]">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/community.jpg" /></div>
|
||||
<p class="text-[20px] text-[#666] font-bold">问答社区</p>
|
||||
</div>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">
|
||||
便捷地浏览其它用户关于niucloud的问题,并从解答中获取niucloud的使用方法,当然您可以进行提问。</p>
|
||||
<NuxtLink>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">便捷地浏览其它用户关于niucloud的问题,并从解答中获取niucloud的使用方法,当然您可以进行提问。</p>
|
||||
<NuxtLink to="https://www.niushop.com/bbs.html" target="_blank">
|
||||
<div class="flex justify-between items-center w-[280px] h-[40px] leading-[40px] rounded-[5px] border-[1px] border-[solid] border-[#508BFE]">
|
||||
<span class="block ml-[20px] text-[14px] text-[#333]">前往问答社区</span>
|
||||
<span class="block mr-[20px] text-[24px] text-[#333]">→</span>
|
||||
@ -58,8 +55,7 @@
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/wx.jpg"></div>
|
||||
<p class="text-[20px] text-[#666] font-bold">关注公众号</p>
|
||||
</div>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">
|
||||
您可以扫描页面底部的二维码来关注我们的官方公众号,获得一手咨询及使用技巧。</p>
|
||||
<p class="text-[14px] w-[280px] h-[100px] text-[#666666] leading-[22px] mt-[30px] mb-[20px]">您可以扫描页面底部的二维码来关注我们的官方公众号,获得一手咨询及使用技巧。</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -83,4 +79,4 @@
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,50 +1,78 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page pt-6">
|
||||
<div class="main-container flex justify-between" v-loading="balanceTableData.loading">
|
||||
<sidebar></sidebar>
|
||||
<el-card class="box-card flex-1 ml-4" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{t('myBalance')}}</span>
|
||||
<div class="ml-[20px] min-h-[70vh] px-[20px] py-[30px] w-[1000px] bg-[#fff] rounded-[var(--rounded-big)]">
|
||||
<div class="h-full" v-loading="balanceTableData.loading">
|
||||
<div>
|
||||
<div class="text-[18px] text-[#333] mb-[30px]">{{t('myBalance')}}</div>
|
||||
<div class="text-center mb-[40px]">
|
||||
<div class="text-[32px] text-[#333] font-600 mb-[10px]">{{ memberStore.info ? moneyFormat((parseFloat(memberStore.info.balance) + parseFloat(memberStore.info.money)).toString()) : '0.00' }}</div>
|
||||
<div class="text-[#999] text-[14px]">账户余额(元)</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<div v-for="(item,index) in accountTypeList" :key="index" class="cursor-pointer relative text-[16px] mr-[50px] text-[#666] flex items-center justify-center" :class="{'class-select': item.key == balanceTableData.params.trade_type}" @click="fromTypeFn(item.key)">{{item.name}}</div>
|
||||
</div>
|
||||
<div v-if="balanceTableData.data.length && !balanceTableData.loading">
|
||||
<div class="pt-[20px] px-[20px] rounded-[12px] min-h-[63%] mt-[30px] bg-[#fafafa]" >
|
||||
<div >
|
||||
<div class="flex items-center justify-between mb-[20px] border-b-[1px] border-dashed border-[#eee]" v-for="(item,index) in balanceTableData.data" :key="index">
|
||||
<div>
|
||||
<div class="font-[16px] mb-[14px] text-[#333]">{{ item.from_type_name }}</div>
|
||||
<div class="text-[14px] text-[#999] mb-[20px]">{{ item.create_time }}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="mb-[14px] text-[18px] price-font" :class="{'text-[#EF000C]' :item.account_data > 0, 'text-[#03B521]':item.account_data <= 0}">{{ item.account_data > 0 ? '+' + item.account_data : item.account_data }}</div>
|
||||
<div class="text-[14px] text-[#999] mb-[20px]">余额 {{item.account_sum}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-6">
|
||||
<el-table :data="balanceTableData.data" stripe>
|
||||
<el-table-column prop="account_type_name" :label="t('accountType')" width="180" />
|
||||
<el-table-column prop="account_data" :label="t('changeInAmount')" width="120" />
|
||||
<el-table-column prop="from_type_name" :label="t('modeOfOccurrence')" width="180" />
|
||||
<el-table-column prop="memo" :label="t('remark')" width="180" />
|
||||
<el-table-column prop="create_time" :label="t('occurrenceTime')" />
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="balanceTableData.page" v-model:page-size="balanceTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="balanceTableData.total" @size-change="loadBalanceList()" @current-change="loadBalanceList" />
|
||||
<div class="mt-[30px] flex justify-center">
|
||||
<el-pagination v-model:current-page="balanceTableData.page" v-model:page-size="balanceTableData.limit" layout="prev, pager, next" background hide-on-single-page :total="balanceTableData.total" @size-change="loadBalanceList()" @current-change="loadBalanceList" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<div v-if="!balanceTableData.data.length && !balanceTableData.loading">
|
||||
<el-empty description="暂无数据" :image-size="200" :image="img('static/resource/images/system/empty.png')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { getBalanceList } from '@/app/api/member'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import { moneyFormat } from '@/utils/common'
|
||||
import { getBalanceListAll } from '@/app/api/member'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
|
||||
//获取数据来源类型
|
||||
const accountTypeList = ref([
|
||||
{name:'全部',key:''},
|
||||
{name:'收入',key:'income'},
|
||||
{name:'支出',key:'disburse'},
|
||||
{name:'提现',key:'cash_out'},
|
||||
])
|
||||
|
||||
// 获取会员列表
|
||||
const balanceTableData = reactive({
|
||||
const balanceTableData = reactive<any>({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: []
|
||||
data: [],
|
||||
params:{
|
||||
trade_type:''
|
||||
}
|
||||
})
|
||||
const loadBalanceList = (page: number = 1) => {
|
||||
balanceTableData.loading = true
|
||||
balanceTableData.page = page
|
||||
|
||||
getBalanceList({
|
||||
getBalanceListAll({
|
||||
page: balanceTableData.page,
|
||||
limit: balanceTableData.limit,
|
||||
}).then(res => {
|
||||
...balanceTableData.params
|
||||
}).then((res: any) => {
|
||||
balanceTableData.loading = false
|
||||
balanceTableData.data = res.data.data
|
||||
balanceTableData.total = res.data.total
|
||||
@ -54,13 +82,27 @@ const loadBalanceList = (page: number = 1) => {
|
||||
}
|
||||
loadBalanceList()
|
||||
|
||||
// 分类点击
|
||||
const fromTypeFn = (key: string)=>{
|
||||
balanceTableData.params.trade_type = key
|
||||
loadBalanceList()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-card{
|
||||
border: none !important;
|
||||
}
|
||||
.text-color{
|
||||
color: var(--jjext-color-brand);
|
||||
}
|
||||
.class-select{
|
||||
color: var( --el-color-primary);
|
||||
&::after{
|
||||
background: #e93323;
|
||||
content: "";
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
width: 22px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,35 +1,47 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page pt-6">
|
||||
<div class="main-container flex justify-between">
|
||||
<sidebar></sidebar>
|
||||
<el-card class="box-card flex-1 ml-4" v-loading="loading" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('personageInfo') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="pr-15" v-if="info">
|
||||
<el-form :model="info" class="form-wrap" label-width="120px">
|
||||
<el-form-item :label="t('memberHeadimg')">
|
||||
<div class="ml-[20px] min-h-[70vh] px-[20px] py-[30px] w-[1000px] bg-[#fff] rounded-[var(--rounded-big)]">
|
||||
<div class="h-full" v-loading="loading">
|
||||
<div>
|
||||
<div class="text-[18px] text-[#333] mb-[50px]">我的信息</div>
|
||||
<div v-if="info">
|
||||
<el-form :model="info" class="form-wrap" label-width="120px" label-position="left">
|
||||
<el-form-item :label="t('memberHeadimg')" class="pb-[20px] border-b-[1px] border-dashed border-[#ddd]">
|
||||
<div class="w-full flex justify-between content-center items-center">
|
||||
<img v-if="!info.headimg" class="w-[80px] h-[80px]" src="@/assets/images/default_headimg.png" alt="">
|
||||
<img v-else :src="img(info.headimg)" class="w-[80px] h-[80px]" alt="">
|
||||
<el-upload class="avatar-uploader" :show-file-list="false" v-bind="upload">
|
||||
<span class="cursor-pointer text-color">{{ t('edit') }}</span>
|
||||
<el-upload class="avatar-uploader" :show-file-list="false" v-bind="upload" ref="uploadRef">
|
||||
<span class="cursor-pointer text-primary">{{ t('edit') }}</span>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('nickname')">
|
||||
<el-form-item :label="t('username')" class="pb-[20px] border-b-[1px] border-dashed border-[#ddd]">
|
||||
<div class="w-full flex justify-between content-center">
|
||||
<span>{{ updateNickname.value }}</span>
|
||||
<span class="cursor-pointer text-color" @click="updateNickname.modal = true">{{ t('edit')}}</span>
|
||||
<span>{{ info.username }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('nickname')" class="pb-[20px] border-b-[1px] border-dashed border-[#ddd]">
|
||||
<div class="w-full flex justify-between content-center">
|
||||
<div>
|
||||
<span>{{ updateNickname.value }}</span>
|
||||
<span v-if="currentLevel">(当前等级:{{currentLevel}})</span>
|
||||
</div>
|
||||
<span class="cursor-pointer text-primary" @click="updateNickname.modal = true">{{ t('edit')}}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mobile')" class="pb-[20px] border-b-[1px] border-dashed border-[#ddd]">
|
||||
<div class="w-full flex justify-between content-center">
|
||||
<span>{{ info.mobile }}</span>
|
||||
<span v-if="!info.mobile" class="cursor-pointer text-primary" @click="updateMobileDialog = true">{{ t('edit')}}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-end mt-[38px]">
|
||||
<span class="cursor-pointer w-[130px] h-[40px] leading-[40px] text-center rounded-[4px] bg-[var(--el-color-primary)] text-white text-[14px]" @click="logoutFn">退出</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="updateNickname.modal" :title="t('nickname')">
|
||||
</div>
|
||||
<!-- 更改昵称 -->
|
||||
<el-dialog v-model="updateNickname.modal" :title="t('nickname')" width="380">
|
||||
<el-form :model="info">
|
||||
<el-form-item>
|
||||
<el-input v-model="updateNickname.value" autocomplete="off" />
|
||||
@ -42,6 +54,28 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 更改手机号码 -->
|
||||
<el-dialog v-model="updateMobileDialog" :title="t('updateMobile')" width="420">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" :validate-on-rule-change="false">
|
||||
<el-form-item prop="mobile">
|
||||
<el-input v-model="formData.mobile" :placeholder="t('mobilePlaceholder')" clearable>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="mobile_code">
|
||||
<el-input v-model="formData.mobile_code" :placeholder="t('codePlaceholder')">
|
||||
<template #suffix>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key" @click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="updateMobileDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="mobileLoading" @click="updateMobileConfirm">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -50,13 +84,12 @@
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useAppStore from '@/stores/app'
|
||||
import { modifyMember } from '@/app/api/member'
|
||||
import { ElMessage, UploadFile, UploadFiles } from 'element-plus'
|
||||
import { modifyMember, bindMobile, getMemberLevel} from '@/app/api/member'
|
||||
import { ElMessage, ElMessageBox, UploadFile, UploadFiles, FormInstance } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import storage from '@/utils/storage'
|
||||
import { getToken } from '@/utils/common'
|
||||
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const loading = ref(true)
|
||||
|
||||
@ -66,14 +99,28 @@ const updateNickname = reactive({
|
||||
value: ''
|
||||
})
|
||||
|
||||
const info = computed(() => {
|
||||
const info:any = computed(() => {
|
||||
updateNickname.value = memberStore.info?.nickname;
|
||||
if (memberStore.info) loading.value = false;
|
||||
return memberStore.info;
|
||||
})
|
||||
const appStore = useAppStore()
|
||||
definePageMeta({ middleware: 'auth' })
|
||||
|
||||
// 获取会员等级
|
||||
let currentLevel = ref('')
|
||||
const getMemberLevelFn = () =>{
|
||||
getMemberLevel().then((res:any) =>{
|
||||
if(info.value && res.data && res.data.length){
|
||||
res.data.forEach((item:any,index:number)=>{
|
||||
if(item.level_id == info.value.member_level){
|
||||
currentLevel.value = item.level_name
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
getMemberLevelFn()
|
||||
const uploadRef = ref<any>(null)
|
||||
const upload = computed(() => {
|
||||
const headers: Record<string, any> = {}
|
||||
headers.token = getToken()
|
||||
@ -84,12 +131,13 @@ const upload = computed(() => {
|
||||
headers,
|
||||
onSuccess: (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
let img = uploadFile?.response?.data?.url;
|
||||
if (response.code == 200) {
|
||||
if (response.code == 200 || response.code == 1) {
|
||||
modifyMember({
|
||||
field: 'headimg',
|
||||
value: img
|
||||
}).then(() => {
|
||||
memberStore.info.headimg = img
|
||||
uploadRef.value.clearFiles()
|
||||
})
|
||||
} else {
|
||||
uploadFile.status = 'fail'
|
||||
@ -110,6 +158,84 @@ const updateNicknameConfirm = () => {
|
||||
updateNickname.modal = false
|
||||
})
|
||||
}
|
||||
|
||||
// 手机号
|
||||
const updateMobileDialog = ref(false)
|
||||
const formData = reactive({
|
||||
mobile: '',
|
||||
mobile_code: '',
|
||||
mobile_key: ''
|
||||
})
|
||||
const mobileLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
'mobile': [
|
||||
{
|
||||
required: true,
|
||||
message: t('mobilePlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
{
|
||||
validator(rule: any, value: string, callback: any) {
|
||||
const phonePattern = /^1[3456789]\d{9}$/
|
||||
if (!phonePattern.test(value)) {
|
||||
return callback(new Error(t('mobileTips')))
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
},
|
||||
message: t('mobileError'),
|
||||
trigger: ['blur'],
|
||||
}
|
||||
],
|
||||
'mobile_code': {
|
||||
required: true,
|
||||
message: t('codePlaceholder'),
|
||||
trigger: ['change']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const smsCodeRef = ref<AnyObject | null>(null)
|
||||
const sendSmsCode = async () => {
|
||||
await formRef.value?.validateField('mobile', async (valid, fields) => {
|
||||
if (valid) {
|
||||
smsCodeRef.value?.send()
|
||||
}
|
||||
})
|
||||
}
|
||||
const updateMobileConfirm = async () => {
|
||||
await formRef.value?.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
if (mobileLoading.value) return
|
||||
mobileLoading.value = true
|
||||
bindMobile(formData).then((res) => {
|
||||
memberStore.getMemberInfo()
|
||||
mobileLoading.value = false
|
||||
updateMobileDialog.value = false
|
||||
}).catch(() => {
|
||||
mobileLoading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const logoutFn = () => {
|
||||
ElMessageBox.confirm('您确定要退出账号吗?', '提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
confirmButtonClass:'!bg-[var(--el-color-primary)] !border-[var(--el-color-primary)]',
|
||||
cancelButtonClass:'!border-[#dcdfe6]',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
memberStore.logout()
|
||||
navigateTo(`/`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,34 +1,64 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page pt-6">
|
||||
<div class="main-container flex justify-between" v-loading="pointTableData.loading">
|
||||
<sidebar></sidebar>
|
||||
<el-card class="box-card flex-1 ml-4" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{t('myPoint')}}</span>
|
||||
<div class="ml-[20px] min-h-[70vh] px-[20px] py-[30px] w-[1000px] bg-[#fff] rounded-[var(--rounded-big)]">
|
||||
<div class="h-full" v-loading="pointTableData.loading">
|
||||
<div>
|
||||
<div class="text-[18px] text-[#333] mb-[30px]">{{t('myPoint')}}</div>
|
||||
<div class="flex justify-center mb-[40px]">
|
||||
<div class="mr-[160px] text-center">
|
||||
<div class="text-[32px] text-primary font-600 mb-[10px]">{{ pointInfo.point||0 }}</div>
|
||||
<div class="text-[#999] text-[14px]">当前积分</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-6">
|
||||
<el-table :data="pointTableData.data" stripe>
|
||||
<el-table-column prop="account_type_name" :label="t('accountType')" width="180" />
|
||||
<el-table-column prop="account_data" :label="t('changeInAmount')" width="120" />
|
||||
<el-table-column prop="from_type_name" :label="t('modeOfOccurrence')" width="180" />
|
||||
<el-table-column prop="memo" :label="t('remark')" width="180" />
|
||||
<el-table-column prop="create_time" :label="t('occurrenceTime')" />
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="pointTableData.page" v-model:page-size="pointTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="pointTableData.total" @size-change="loadPointList()" @current-change="loadPointList" />
|
||||
<div class="mr-[160px] text-center">
|
||||
<div class="text-[32px] text-[#333] font-600 mb-[10px]">{{ pointInfo.point_get||0 }}</div>
|
||||
<div class="text-[#999] text-[14px]">累计积分</div>
|
||||
</div>
|
||||
<div class="mr-[160px] text-center">
|
||||
<div class="text-[32px] text-[#333] font-600 mb-[10px]">{{ pointInfo.use||0 }}</div>
|
||||
<div class="text-[#999] text-[14px]">累计消费</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-[32px] text-[#333] font-600 mb-[10px]">{{ pointInfo.point||0 }}</div>
|
||||
<div class="text-[#999] text-[14px]">可用积分</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<div v-if="pointTableData.data.length && !pointTableData.loading">
|
||||
<div class="pt-[20px] px-[20px] rounded-[12px] min-h-[70%] mt-[30px] bg-[#fafafa]">
|
||||
<div>
|
||||
<template v-for="(item,index) in pointTableData.data" :key="index">
|
||||
<div class="flex items-center justify-between mb-[20px] border-b-[1px] border-dashed border-[#eee]" v-for="(subItem,subIndex) in item.month_data" :key="subIndex">
|
||||
<div>
|
||||
<div class="font-[16px] mb-[14px] text-[#333]">{{ subItem.from_type_name }}</div>
|
||||
<div class="text-[14px] text-[#999] mb-[20px]">{{ subItem.create_time }}</div>
|
||||
</div>
|
||||
<div class="mb-[14px] text-[18px] price-font" :class="{'text-[#EF000C]' : subItem.account_data > 0, 'text-[#03B521]':subItem.account_data <= 0}">{{ subItem.account_data > 0 ? '+' + subItem.account_data : subItem.account_data }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="pointTableData.page" v-model:page-size="pointTableData.limit" layout="prev, pager, next" background hide-on-single-page :total="pointTableData.total" @size-change="loadPointList()" @current-change="loadPointList" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!pointTableData.data.length && !pointTableData.loading">
|
||||
<el-empty description="暂无数据" :image-size="200" :image="img('static/resource/images/system/empty.png')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { getPointList } from '@/app/api/member'
|
||||
import { getPointList, getMemberAccountPointcount } from '@/app/api/member'
|
||||
|
||||
//个人积分信息
|
||||
const pointInfo = ref({});
|
||||
const getMemberAccountPointcountFn = () =>{
|
||||
getMemberAccountPointcount().then((res: any) =>{
|
||||
pointInfo.value = res.data
|
||||
})
|
||||
}
|
||||
getMemberAccountPointcountFn()
|
||||
// 获取会员列表
|
||||
const pointTableData = reactive({
|
||||
page: 1,
|
||||
@ -44,7 +74,7 @@ const loadPointList = (page: number = 1) => {
|
||||
getPointList({
|
||||
page: pointTableData.page,
|
||||
limit: pointTableData.limit,
|
||||
}).then(res => {
|
||||
}).then((res: any) => {
|
||||
pointTableData.loading = false
|
||||
pointTableData.data = res.data.data
|
||||
pointTableData.total = res.data.total
|
||||
|
||||
@ -3,6 +3,10 @@ export default [
|
||||
path: "/",
|
||||
component: () => import('~/app/pages/index.vue')
|
||||
},
|
||||
{
|
||||
path: "/app/index",
|
||||
component: () => import('~/app/pages/index.vue')
|
||||
},
|
||||
{
|
||||
path: "/auth/login",
|
||||
component: () => import('~/app/pages/auth/login.vue'),
|
||||
@ -26,34 +30,40 @@ export default [
|
||||
},
|
||||
{
|
||||
path: "/auth/agreement",
|
||||
component: () => import('~/app/pages/auth/agreement.vue')
|
||||
},
|
||||
{
|
||||
path: "/member",
|
||||
component: () => import('~/app/pages/member/index.vue'),
|
||||
component: () => import('~/app/pages/auth/agreement.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
layout: "member"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/center",
|
||||
path: "/app/auth/agreement",
|
||||
component: () => import('~/app/pages/auth/agreement.vue'),
|
||||
meta: {
|
||||
layout: "member"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/app/member/center",
|
||||
component: () => import('~/app/pages/member/center.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
middleware: ["auth"],
|
||||
layout: "member"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/balance",
|
||||
path: "/app/member/balance",
|
||||
component: () => import('~/app/pages/member/balance.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
middleware: ["auth"],
|
||||
layout: "member"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/point",
|
||||
path: "/app/member/point",
|
||||
component: () => import('~/app/pages/member/point.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
middleware: ["auth"],
|
||||
layout: "member"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -62,12 +72,5 @@ export default [
|
||||
meta: {
|
||||
layout: "container"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/site/nosite",
|
||||
component: () => import('~/app/pages/site/nosite.vue'),
|
||||
meta: {
|
||||
layout: "container"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-screen h-screen flex flex-col items-center justify-center">
|
||||
<el-empty :description="t('siteClose')" :image="img('static/resource/images/site/close.png')" image-size="300px" />
|
||||
<div class="w-screen h-screen bg-[#fff] flex flex-col items-center justify-center">
|
||||
<el-empty :description="t('siteClose')" :image="img('static/resource/images/site/close.png')" image-size="200px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<div class="w-screen h-screen flex flex-col items-center justify-center">
|
||||
<el-empty :description="t('noSite')" :image="img('static/resource/images/site/close.png')" image-size="300px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@ -5,7 +5,8 @@ const addonRoutes = import.meta.globEager('@/addon/**/pages/routes.ts')
|
||||
|
||||
for (const key in addonRoutes) {
|
||||
const addon = key.split('/')[2]
|
||||
routes.push(...addonRoutes[key].default.map((item) => {
|
||||
// 先加载插件路由后加载app路由
|
||||
routes.unshift(...addonRoutes[key].default.map((item) => {
|
||||
item.meta = item.meta ? Object.assign(item.meta, { addon }) : { addon }
|
||||
return item
|
||||
}))
|
||||
|
||||
BIN
web/assets/images/camera.png
Normal file
|
After Width: | Height: | Size: 843 B |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 14 KiB |
BIN
web/assets/images/fiexd/cart.png
Normal file
|
After Width: | Height: | Size: 694 B |
BIN
web/assets/images/fiexd/customer.png
Normal file
|
After Width: | Height: | Size: 764 B |
BIN
web/assets/images/fiexd/member.png
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
web/assets/images/fiexd/mobile.png
Normal file
|
After Width: | Height: | Size: 487 B |
BIN
web/assets/images/fiexd/top.png
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
web/assets/images/pay/pay.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
web/assets/images/user.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
web/assets/styles/OPPOSans-M.ttf
Normal file
BIN
web/assets/styles/OPPOSans-R.ttf
Normal file
@ -1,4 +1,64 @@
|
||||
@font-face {
|
||||
font-family: 'oppoSans-M';
|
||||
src: url('@/assets/styles/OPPOSans-M.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'oppoSans-R';
|
||||
src: url('@/assets/styles/OPPOSans-R.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'myFont';
|
||||
src: url('@/assets/styles/custom.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.oppoSans-M{
|
||||
font-family: 'oppoSans-M';
|
||||
font-weight: 500;
|
||||
}
|
||||
.oppoSans-R{
|
||||
font-family: 'oppoSans-R';
|
||||
}
|
||||
.price-font{
|
||||
font-family: 'myFont';
|
||||
}
|
||||
body{
|
||||
font-family: 'oppoSans-M';
|
||||
background-color: #f5f5f5;
|
||||
color: #303133;
|
||||
}
|
||||
div{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
// 标签
|
||||
.tag-item{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.flex-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.main-container {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 多行超出隐藏 */
|
||||
.multi-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
.el-button:focus-visible{
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
BIN
web/assets/styles/custom.ttf
Normal file
@ -1,178 +1,455 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4174881 */
|
||||
src: url('//at.alicdn.com/t/c/font_4174881_3g0m7bfru3s.woff2?t=1703736772336') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_4174881_3g0m7bfru3s.woff?t=1703736772336') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_4174881_3g0m7bfru3s.ttf?t=1703736772336') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
src: url('//at.alicdn.com/t/c/font_4174881_kzu1qah5r4j.woff2?t=1733801795296') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_4174881_kzu1qah5r4j.woff?t=1733801795296') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_4174881_kzu1qah5r4j.ttf?t=1733801795296') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-shoucang:before {
|
||||
}
|
||||
|
||||
.icon-gouwucheV6xx6:before {
|
||||
content: "\e835";
|
||||
}
|
||||
|
||||
.icon-zuoV6xx:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.icon-a-guanbi34:before {
|
||||
content: "\e82b";
|
||||
}
|
||||
|
||||
.icon-zujiV6xx:before {
|
||||
content: "\e765";
|
||||
}
|
||||
|
||||
.icon-gouwucheV6xx-2:before {
|
||||
content: "\e6fe";
|
||||
}
|
||||
|
||||
.icon-woV6xx1:before {
|
||||
content: "\e709";
|
||||
}
|
||||
|
||||
.icon-mima:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.icon-shoujiV6xx:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.icon-tubiaoV6-3:before {
|
||||
content: "\e664";
|
||||
}
|
||||
|
||||
.icon-youV6xx:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.icon-riliV6xx:before {
|
||||
content: "\e73e";
|
||||
}
|
||||
|
||||
.icon-xiugaiV6xx:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.icon-shanchu-yuangaizhiV6xx:before {
|
||||
content: "\e6e7";
|
||||
}
|
||||
|
||||
.icon-shenhezhong:before {
|
||||
content: "\e6cb";
|
||||
}
|
||||
|
||||
.icon-jishibenV6xx:before {
|
||||
content: "\e6fb";
|
||||
}
|
||||
|
||||
.icon-fapiao:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.icon-checkbox_nol:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.icon-shangV6xx-1:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.icon-xiaV6xx:before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.icon-aixin1:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.icon-fujian:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-group-num:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.icon-zhaopian:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.icon-duanxinV6xx1:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
.icon-aixin:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.icon-paixujiantoushang:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.icon-paixujiantouxia:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.icon-xuanze1:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-weixinzhifu:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.icon-zhifubaozhifu:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.icon-yuezhifu:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.icon-shijian_o:before {
|
||||
content: "\ebb1";
|
||||
}
|
||||
|
||||
.icon-shangjiantou:before {
|
||||
content: "\e79d";
|
||||
}
|
||||
|
||||
.icon-xiajiantou1:before {
|
||||
content: "\e79e";
|
||||
}
|
||||
|
||||
.icon-shouye:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-Vector-25:before {
|
||||
content: "\e70b";
|
||||
}
|
||||
|
||||
.icon-youhuiquan:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-ruzhushenqing:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-dizhiguanli:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-shoucang1:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.icon-shoucang2:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.icon-fanhui:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.icon-sousuo:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.icon-gouwuche1:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.icon-yanjing_kai:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.icon-shijian:before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.icon-bofang:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.icon-weixin1:before {
|
||||
content: "\e6ea";
|
||||
}
|
||||
|
||||
.icon-gengduo-lan:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.icon-shoucang01:before {
|
||||
content: "\ea33";
|
||||
}
|
||||
|
||||
.icon-a-dingdan2:before {
|
||||
content: "\eba3";
|
||||
}
|
||||
|
||||
.icon-a-qian2:before {
|
||||
content: "\eb44";
|
||||
}
|
||||
|
||||
.icon-caigou6:before {
|
||||
content: "\eba1";
|
||||
}
|
||||
|
||||
.icon-a-zhibao5:before {
|
||||
content: "\ebc9";
|
||||
}
|
||||
|
||||
.icon-shizhong:before {
|
||||
content: "\e74f";
|
||||
}
|
||||
|
||||
.icon-zhanghaodenglu:before {
|
||||
content: "\e698";
|
||||
}
|
||||
|
||||
.icon-mianxing_denglu_erweimadenglu:before {
|
||||
content: "\e6fd";
|
||||
}
|
||||
|
||||
.icon-yishoucang:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-dizhi-tianjia:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.icon-xuanzhong4:before {
|
||||
content: "\e9ec";
|
||||
}
|
||||
|
||||
.icon-jiageshaixuanshang:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.icon-shangjiashijian:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.icon-kefu:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.icon-xiangshangjiantou:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-dijia:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-zhifa:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-hanghuo:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.icon-pinzhong:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.icon-gouwuche:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.icon-shoucang:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.icon-geren:before {
|
||||
}
|
||||
|
||||
.icon-geren:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.icon-c:before {
|
||||
}
|
||||
|
||||
.icon-c:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.icon-wenhao:before {
|
||||
}
|
||||
|
||||
.icon-wenhao:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.icon-jiahao:before {
|
||||
}
|
||||
|
||||
.icon-jiahao:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.icon-biaoqian:before {
|
||||
}
|
||||
|
||||
.icon-biaoqian:before {
|
||||
content: "\e63d";
|
||||
}
|
||||
|
||||
.icon-xiajiantou:before {
|
||||
}
|
||||
|
||||
.icon-xiajiantou:before {
|
||||
content: "\e63c";
|
||||
}
|
||||
|
||||
.icon-rili1:before {
|
||||
}
|
||||
|
||||
.icon-rili1:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.icon-huangguan:before {
|
||||
}
|
||||
|
||||
.icon-huangguan:before {
|
||||
content: "\e6bb";
|
||||
}
|
||||
|
||||
.icon-05_success:before {
|
||||
}
|
||||
|
||||
.icon-05_success:before {
|
||||
content: "\e6b2";
|
||||
}
|
||||
|
||||
.icon-huojian1:before {
|
||||
}
|
||||
|
||||
.icon-huojian1:before {
|
||||
content: "\e6c0";
|
||||
}
|
||||
|
||||
.icon-shouquanliebiao:before {
|
||||
}
|
||||
|
||||
.icon-shouquanliebiao:before {
|
||||
content: "\e696";
|
||||
}
|
||||
|
||||
.icon-icon_huojian:before {
|
||||
}
|
||||
|
||||
.icon-icon_huojian:before {
|
||||
content: "\e694";
|
||||
}
|
||||
|
||||
.icon-xiaoxi1:before {
|
||||
}
|
||||
|
||||
.icon-xiaoxi1:before {
|
||||
content: "\e695";
|
||||
}
|
||||
|
||||
.icon-huojian:before {
|
||||
}
|
||||
|
||||
.icon-huojian:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.icon-tishi2:before {
|
||||
}
|
||||
|
||||
.icon-tishi2:before {
|
||||
content: "\e6fc";
|
||||
}
|
||||
|
||||
.icon-kaifazhe:before {
|
||||
}
|
||||
|
||||
.icon-kaifazhe:before {
|
||||
content: "\e692";
|
||||
}
|
||||
|
||||
.icon-xiaoxi:before {
|
||||
}
|
||||
|
||||
.icon-xiaoxi:before {
|
||||
content: "\e690";
|
||||
}
|
||||
|
||||
.icon--_shengchengyanshi:before {
|
||||
}
|
||||
|
||||
.icon--_shengchengyanshi:before {
|
||||
content: "\e68f";
|
||||
}
|
||||
|
||||
.icon-tishi1:before {
|
||||
}
|
||||
|
||||
.icon-tishi1:before {
|
||||
content: "\e691";
|
||||
}
|
||||
|
||||
.icon-dian:before {
|
||||
}
|
||||
|
||||
.icon-dian:before {
|
||||
content: "\ec1e";
|
||||
}
|
||||
|
||||
.icon-huanyingye:before {
|
||||
}
|
||||
|
||||
.icon-huanyingye:before {
|
||||
content: "\e68e";
|
||||
}
|
||||
|
||||
.icon-gerenxinxi:before {
|
||||
}
|
||||
|
||||
.icon-gerenxinxi:before {
|
||||
content: "\e6f4";
|
||||
}
|
||||
|
||||
.icon-shimingrenzheng:before {
|
||||
}
|
||||
|
||||
.icon-shimingrenzheng:before {
|
||||
content: "\e6f9";
|
||||
}
|
||||
|
||||
.icon-kaifashangzhongxin:before {
|
||||
}
|
||||
|
||||
.icon-kaifashangzhongxin:before {
|
||||
content: "\e6fa";
|
||||
}
|
||||
|
||||
.icon-wodezhanghu:before {
|
||||
}
|
||||
|
||||
.icon-wodezhanghu:before {
|
||||
content: "\e6f8";
|
||||
}
|
||||
|
||||
.icon-wodeshouquan:before {
|
||||
}
|
||||
|
||||
.icon-wodeshouquan:before {
|
||||
content: "\e6f6";
|
||||
}
|
||||
|
||||
.icon-wodezhandian:before {
|
||||
}
|
||||
|
||||
.icon-wodezhandian:before {
|
||||
content: "\e6f5";
|
||||
}
|
||||
|
||||
.icon-wodedingdan:before {
|
||||
}
|
||||
|
||||
.icon-wodedingdan:before {
|
||||
content: "\e6f2";
|
||||
}
|
||||
|
||||
.icon-lingdang-xianxing:before {
|
||||
}
|
||||
|
||||
.icon-lingdang-xianxing:before {
|
||||
content: "\e8c0";
|
||||
}
|
||||
|
||||
.icon-weixin:before {
|
||||
}
|
||||
|
||||
.icon-weixin:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.icon-fenxiang:before {
|
||||
}
|
||||
|
||||
.icon-fenxiang:before {
|
||||
content: "\e86e";
|
||||
}
|
||||
|
||||
.icon-erweima:before {
|
||||
}
|
||||
|
||||
.icon-erweima:before {
|
||||
content: "\e680";
|
||||
}
|
||||
|
||||
.icon-shoujihao:before {
|
||||
}
|
||||
|
||||
.icon-shoujihao:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.icon-tishi:before {
|
||||
}
|
||||
|
||||
.icon-tishi:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.icon-shimingrenzheng-xian:before {
|
||||
}
|
||||
|
||||
.icon-shimingrenzheng-xian:before {
|
||||
content: "\e89c";
|
||||
}
|
||||
|
||||
.icon-icon-selected:before {
|
||||
}
|
||||
|
||||
.icon-icon-selected:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-yangshi_icon_tongyong_shield:before {
|
||||
}
|
||||
|
||||
.icon-yangshi_icon_tongyong_shield:before {
|
||||
content: "\e668";
|
||||
}
|
||||
|
||||
.icon-xiangyoujiantou:before {
|
||||
}
|
||||
|
||||
.icon-xiangyoujiantou:before {
|
||||
content: "\e65f";
|
||||
}
|
||||
|
||||
.icon-xiangzuojiantou:before {
|
||||
}
|
||||
|
||||
.icon-xiangzuojiantou:before {
|
||||
content: "\e660";
|
||||
}
|
||||
|
||||
.icon-qiye:before {
|
||||
}
|
||||
|
||||
.icon-qiye:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,21 @@
|
||||
@import 'iconfont.css';
|
||||
@import 'common.scss';
|
||||
@import 'common.scss';
|
||||
|
||||
// 主色调修改
|
||||
:root{
|
||||
--el-color-primary: #EF000C;
|
||||
--el-color-primary-light-3: #EF000C;
|
||||
--el-color-primary-light-5: #f49991;
|
||||
--el-color-primary-light-7: #f8c2bd;
|
||||
--el-color-primary-light-8: #fbd6d3;
|
||||
--el-color-primary-light-9: #fff;
|
||||
--el-color-primary-dark-2: #EF000C;
|
||||
--el-price:#E4221C;
|
||||
// 圆角大小
|
||||
--rounded-xl: 100px;
|
||||
--rounded-big: 16px;
|
||||
--rounded-mid: 12px;
|
||||
--rounded-med: 8px;
|
||||
--rounded-small: 6px;
|
||||
}
|
||||
|
||||
|
||||
76
web/components/login-dialog/index.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="loginPopup">
|
||||
<el-dialog v-model="dialogVisible" align-center width="430" :before-close="beforeClose" custom-class="login !rounded-[var(--rounded-big)]" :show-close="false" append-to-body>
|
||||
<div class="relative">
|
||||
<span class="iconfont icon-tubiaoV6-3 absolute top-[-33px] right-[-33px] text-[#fff] !text-[24px]" @click="handleClose"></span>
|
||||
<login v-if="type === 'login' && dialogVisible" @typeChange="typeChange"/>
|
||||
<register v-if="type === 'register' && dialogVisible" @typeChange="typeChange" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref,computed,watch } from 'vue'
|
||||
import login from './login.vue'
|
||||
import register from './register.vue'
|
||||
import useMemberStore from '@/stores/member'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
//弹框状态
|
||||
const dialogVisible = computed(()=>{
|
||||
return memberStore.loginPopup
|
||||
})
|
||||
//弹框关闭
|
||||
const beforeClose = (next)=>{
|
||||
memberStore.logClose()
|
||||
type.value = 'login'
|
||||
next()
|
||||
}
|
||||
const handleClose = ()=>{
|
||||
memberStore.logClose()
|
||||
type.value = 'login'
|
||||
}
|
||||
//判断当前是登录还是注册
|
||||
let type = ref('login')
|
||||
const typeChange = (val:any)=>{
|
||||
type.value = val
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.login .el-dialog__header{
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.login .el-dialog__body{
|
||||
padding: 0 !important;
|
||||
}
|
||||
.login .el-dialog__headerbtn{
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-input__wrapper {
|
||||
box-shadow: unset !important;
|
||||
border-radius: 0;
|
||||
&.is-focus {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-error {
|
||||
.el-input__wrapper {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-form-item__error) {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
</style>
|
||||
278
web/components/login-dialog/login.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="active" class="iconfont icon-mianxing_denglu_erweimadenglu !text-[#333] !text-[50px] absolute top-0 right-0 cursor-pointer" @click="handleChange"></span>
|
||||
<span v-else class="iconfont icon-zhanghaodenglu !text-[#333] !text-[50px] absolute top-0 right-0 cursor-pointer" @click="handleChange"></span>
|
||||
<div v-if="active" class="bg-white w-full py-[60px] px-[30px] !rounded-[var(--rounded-big)]">
|
||||
<div class="flex items-end justify-center mb-[30px]">
|
||||
<div class="text-[18px] cursor-pointer text-[#999] leading-[24px] oppoSans-R" :class="{ '!text-[#333] font-600': type == item.type,'mr-[70px]': (index+1) != loginType.length }" v-for="(item,index) in loginType" @click="type = item.type">{{item.title }}</div>
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" :validate-on-rule-change="false">
|
||||
<div v-show="type == 'username'">
|
||||
<el-form-item prop="username">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.username" :placeholder="t('usernamePlaceholder')" clearable :inline-message="true" :readonly="real_name_input" @click="real_name_input = false" @blur="real_name_input = true">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-woV6xx1 !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" type="password" clearable show-password >
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-mima !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-show="type == 'mobile'">
|
||||
<el-form-item prop="mobile">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.mobile" :placeholder="t('mobilePlaceholder')" clearable>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-shoujiV6xx !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="mobile_code">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.mobile_code" :placeholder="t('codePlaceholder')">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-a-zhibao5 !mr-[14px]"></span>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key" @click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<el-button type="primary" link @click="typeChange" class="!text-[12px]">{{ t('noAccount') }},{{ t('toRegister') }}</el-button>
|
||||
</div>
|
||||
<div class="mt-[20px]">
|
||||
<el-button type="primary" class="w-full !h-[50px] !rounded-[8px] oppoSans-M" size="large" @click="handleLogin" :loading="loading">{{ loading ? t('logining') : t('login') }}</el-button>
|
||||
</div>
|
||||
<div class="text-[12px] leading-[24px] flex items-center w-full mt-[20px]" v-if="configStore.login.agreement_show">
|
||||
<span class="iconfont text-primary mr-[5px]" :class="isAgree ? 'icon-xuanze1' : 'icon-checkbox_nol'" @click="isAgree = !isAgree"></span>
|
||||
{{ t('agreeTips') }}
|
||||
<NuxtLink :to="service" target="_blank">
|
||||
<span class="text-primary mx-[4px]">{{ t('userAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
{{ t('and') }}
|
||||
<NuxtLink :to="privacy" target="_blank">
|
||||
<span class="text-primary mx-[4px]">{{ t('privacyAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center py-[60px] px-[30px]">
|
||||
<div class="text-[18px] cursor-pointer text-[#999] leading-[24px] oppoSans-R !text-[#333] font-600">微信扫码登录</div>
|
||||
<div class="qrcode p-[20px] mt-[30px] border leading-none box-content rounded-[var(--rounded-small)]">
|
||||
<div class="relative">
|
||||
<el-image v-if="weixinCode.url" :src="weixinCode.url" class="w-[200px] h-[200px]"/>
|
||||
<div v-else class="w-[202px] h-[202px]"></div>
|
||||
<div class="flex flex-col justify-center items-center absolute inset-0 bg-gray-50" v-if="weixinCode.pastDue">
|
||||
<span class="text-xs text-gray-600">{{ weixinCode.pastDueContent }}</span>
|
||||
<span @click="scanLoginFn()" class="text-xs cursor-pointer text-color mt-2">点击刷新</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[22px] flex items-center justify-center">
|
||||
<span class="iconfont icon-weixin1 text-[#00c22c]"></span>
|
||||
<span class="text-[14px] text-[#999] ml-[4px]">微信扫一扫</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref,reactive,computed,onUnmounted } from 'vue'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { usernameLogin, mobileLogin, scanlogin, checkscan } from '@/app/api/auth'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import QRCode from "qrcode";
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const configStore = useConfigStore()
|
||||
|
||||
// 跳转
|
||||
const service = ref('')
|
||||
const privacy = ref('')
|
||||
if(location.pathname.indexOf('web') != -1){
|
||||
service.value = '/web/auth/agreement?key=service'
|
||||
privacy.value = '/web/auth/agreement?key=privacy'
|
||||
}else{
|
||||
service.value = '/auth/agreement?key=service'
|
||||
privacy.value = '/auth/agreement?key=privacy'
|
||||
}
|
||||
|
||||
//当前为二维码还是账户登录
|
||||
let active = ref(true)
|
||||
let timer:any = null
|
||||
const handleChange = () => {
|
||||
active.value = !active.value
|
||||
if(!active.value){
|
||||
scanLoginFn();
|
||||
}else{
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
onUnmounted(() => {
|
||||
clearTimeout(timer)
|
||||
});
|
||||
// 校验二维码
|
||||
const checkScanFn = (key) => {
|
||||
let parameter = { key };
|
||||
|
||||
checkscan(parameter).then((res) => {
|
||||
let data = res.data;
|
||||
switch (data.status) {
|
||||
case 'wait':
|
||||
timer = setTimeout(() => {
|
||||
checkScanFn(weixinCode.value.key);
|
||||
}, 1000);
|
||||
break;
|
||||
case 'success':
|
||||
if (!data.login_data.token) {
|
||||
useCookie('openId').value = data.login_data.openid
|
||||
navigateTo(`/auth/bind`)
|
||||
} else {
|
||||
memberStore.setToken(data.login_data.token)
|
||||
memberStore.logClose()
|
||||
}
|
||||
break;
|
||||
case 'fail':
|
||||
weixinCode.value.pastDueContent = data.fail_reason
|
||||
weixinCode.value.pastDue = true;
|
||||
break;
|
||||
}
|
||||
}).catch((res) => {
|
||||
weixinCode.value.pastDue = true;
|
||||
weixinCode.value.pastDueContent = res.msg;
|
||||
})
|
||||
}
|
||||
|
||||
// 扫码登录,微信二维码
|
||||
const weixinCode = ref({
|
||||
url: '',
|
||||
key: '',
|
||||
pastDue: false,
|
||||
pastDueContent: '二维码生成失败'
|
||||
})
|
||||
|
||||
const scanLoginFn = async () => {
|
||||
let data = await (await scanlogin()).data;
|
||||
weixinCode.value.key = data.key
|
||||
if(data.url) {
|
||||
QRCode.toDataURL(data.url, { errorCorrectionLevel: 'L', margin: 0, width: 100 }).then(url => {
|
||||
weixinCode.value.url = url
|
||||
});
|
||||
weixinCode.value.pastDue = false;
|
||||
setTimeout(() => {
|
||||
checkScanFn(weixinCode.value.key);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
configStore.getLoginConfig()
|
||||
|
||||
const loginType = computed(() => {
|
||||
const value = []
|
||||
configStore.login.is_username && (value.push({ type: 'username', title: t('usernameLogin') }))
|
||||
configStore.login.is_mobile && (value.push({ type: 'mobile', title: t('mobileLogin') }))
|
||||
type.value = value[0] ? value[0].type : ''
|
||||
return value
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const type = ref('')
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
mobile: '',
|
||||
mobile_code: '',
|
||||
mobile_key: ''
|
||||
})
|
||||
const formRef = ref<FormInstance>()
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
'username': {
|
||||
required: type.value == 'username',
|
||||
message: t('usernamePlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
'password': {
|
||||
required: type.value == 'username',
|
||||
message: t('passwordPlaceholder'),
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
'mobile': [
|
||||
{
|
||||
required: type.value == 'mobile',
|
||||
message: t('mobilePlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
{
|
||||
validator(rule: any, value: string, callback: any) {
|
||||
if (type.value != 'mobile') return true
|
||||
else return test.mobile(value)
|
||||
},
|
||||
message: t('mobileError'),
|
||||
trigger: ['blur'],
|
||||
}
|
||||
],
|
||||
'mobile_code': {
|
||||
required: type.value == 'mobile',
|
||||
message: t('codePlaceholder'),
|
||||
trigger: ['change']
|
||||
}
|
||||
}
|
||||
})
|
||||
const isAgree = ref(false)
|
||||
const handleLogin = async () => {
|
||||
await formRef.value?.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
if (configStore.login.agreement_show && !isAgree.value) {
|
||||
ElMessage.error(t('isAgreeTips'))
|
||||
return false;
|
||||
}
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
const login = type.value == 'username' ? usernameLogin : mobileLogin
|
||||
|
||||
login(formData).then(async (res) => {
|
||||
await memberStore.setToken(res.data.token)
|
||||
memberStore.logClose()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const smsCodeRef = ref<AnyObject | null>(null)
|
||||
const sendSmsCode = async () => {
|
||||
await formRef.value?.validateField('mobile', async (valid, fields) => {
|
||||
if (valid) {
|
||||
smsCodeRef.value?.send()
|
||||
}
|
||||
})
|
||||
}
|
||||
//去注册
|
||||
const emit = defineEmits(['typeChange'])
|
||||
const typeChange = ()=>{
|
||||
emit('typeChange','register')
|
||||
}
|
||||
|
||||
const real_name_input = ref(true)
|
||||
const password_input = ref(true)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
271
web/components/login-dialog/register.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-white w-full py-[60px] px-[30px] !rounded-[var(--rounded-big)]">
|
||||
<div class="flex items-end justify-center mb-[30px]">
|
||||
<div class="text-[18px] cursor-pointer text-[#999] leading-[24px] oppoSans-R" :class="{ '!text-[#333] font-600': type == item.type,'mr-[70px]': (index+1) != registerType.length }" v-for="(item,index) in registerType" @click="type = item.type">{{item.title }}</div>
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" :validate-on-rule-change="false">
|
||||
<div v-show="type == 'username'">
|
||||
<el-form-item prop="username">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.username" :placeholder="t('usernamePlaceholder')" clearable :inline-message="true" :readonly="real_name_input" @click="real_name_input = false" @blur="real_name_input = true">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-woV6xx1 !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.password" :placeholder="t('passwordPlaceholder')" type="password" clearable :show-password="true" >
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-mima !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirm_password">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.confirm_password" :placeholder="t('confirmPasswordPlaceholder')" type="password" clearable :show-password="true" >
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-mima !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-show="type == 'mobile' || configStore.login.is_bind_mobile">
|
||||
<el-form-item prop="mobile">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.mobile" :placeholder="t('mobilePlaceholder')" clearable>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-shoujiV6xx !mr-[14px]"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="mobile_code">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.mobile_code" :placeholder="t('codePlaceholder')">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-a-zhibao5 !mr-[14px]"></span>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key" @click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-show="type == 'username'">
|
||||
<el-form-item prop="captcha_code">
|
||||
<div class="flex-1 h-[50px] border-[1px] border-solid border-[#ccc] rounded-[8px] flex items-center">
|
||||
<el-input v-model="formData.captcha_code" :placeholder="t('captchaPlaceholder')">
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-a-zhibao5 !mr-[14px]"></span>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<div class="py-0 leading-none">
|
||||
<el-image :src="captcha.image.value" class="h-[30px] cursor-pointer" @click="captcha.refresh()"></el-image>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<el-button type="primary" link @click="typeChange" class="!text-[12px]">{{ t('haveAccount') }},{{ t('toLogin') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="mt-[20px]">
|
||||
<el-button type="primary" class="w-full !h-[50px] !rounded-[8px] oppoSans-M" size="large" @click="handleRegister" :loading="loading">{{ loading ? t('registering') : t('register') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="text-[12px] leading-[24px] flex items-center w-full mt-[20px]" v-if="configStore.login.agreement_show">
|
||||
<span class="iconfont text-primary mr-[5px]" :class="isAgree ? 'icon-xuanze1' : 'icon-checkbox_nol'" @click="isAgree = !isAgree"></span>
|
||||
{{ t('registerAgreeTips') }}
|
||||
<NuxtLink :to="service" target="_blank">
|
||||
<span class="text-primary mx-[4px]">{{ t('userAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
{{ t('and') }}
|
||||
<NuxtLink :to="privacy" target="_blank">
|
||||
<span class="text-primary mx-[4px]">{{ t('privacyAgreement') }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref,reactive,computed } from 'vue'
|
||||
import { usernameRegister, mobileRegister, wechatCheck } from '@/app/api/auth'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import { FormInstance } from 'element-plus'
|
||||
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const configStore = useConfigStore()
|
||||
configStore.getLoginConfig()
|
||||
|
||||
// 跳转
|
||||
const service = ref('')
|
||||
const privacy = ref('')
|
||||
if(location.pathname.indexOf('web') != -1){
|
||||
service.value = '/web/auth/agreement?key=service'
|
||||
privacy.value = '/web/auth/agreement?key=privacy'
|
||||
}else{
|
||||
service.value = '/auth/agreement?key=service'
|
||||
privacy.value = '/auth/agreement?key=privacy'
|
||||
}
|
||||
|
||||
|
||||
const type = ref('')
|
||||
const registerType = computed(() => {
|
||||
const value = []
|
||||
configStore.login.is_username && (value.push({ type: 'username', title: t('usernameRegister') }))
|
||||
configStore.login.is_mobile && !configStore.login.is_bind_mobile && (value.push({ type: 'mobile', title: t('mobileRegister') }))
|
||||
type.value = value[0] ? value[0].type : ''
|
||||
return value
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
mobile: '',
|
||||
mobile_code: '',
|
||||
mobile_key: '',
|
||||
captcha_key: '',
|
||||
captcha_code: ''
|
||||
})
|
||||
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
'username': {
|
||||
type: 'string',
|
||||
required: type.value == 'username',
|
||||
message: t('usernamePlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
'password': {
|
||||
type: 'string',
|
||||
required: type.value == 'username',
|
||||
message: t('passwordPlaceholder'),
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
'confirm_password': [
|
||||
{
|
||||
type: 'string',
|
||||
required: type.value == 'username',
|
||||
message: t('confirmPasswordPlaceholder'),
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
{
|
||||
validator(rule: any, value: string, callback: any) {
|
||||
return value == formData.password
|
||||
},
|
||||
message: t('confirmPasswordError'),
|
||||
trigger: ['change', 'blur'],
|
||||
}
|
||||
],
|
||||
'mobile': [
|
||||
{
|
||||
type: 'string',
|
||||
required: type.value == 'mobile' || configStore.login.is_bind_mobile,
|
||||
message: t('mobilePlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
{
|
||||
validator(rule: any, value: string, callback: any) {
|
||||
if (type.value != 'mobile' && !configStore.login.is_bind_mobile) return true
|
||||
else return test.mobile(value)
|
||||
},
|
||||
message: t('mobileError'),
|
||||
trigger: ['change', 'blur'],
|
||||
}
|
||||
],
|
||||
'mobile_code': {
|
||||
type: 'string',
|
||||
required: type.value == 'mobile' || configStore.login.is_bind_mobile,
|
||||
message: t('codePlaceholder'),
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
'captcha_code': {
|
||||
type: 'string',
|
||||
required: type.value == 'username',
|
||||
message: t('captchaPlaceholder'),
|
||||
trigger: ['blur', 'change'],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isAgree = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const handleRegister = async () => {
|
||||
await formRef.value?.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
if (configStore.login.agreement_show && !isAgree.value) {
|
||||
ElMessage.error(t('isAgreeTips'))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
const register = type.value == 'username' ? usernameRegister : mobileRegister
|
||||
|
||||
register(formData).then((res: any) => {
|
||||
memberStore.setToken(res.data.token)
|
||||
memberStore.logClose()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
captcha.refresh()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
let show = ref(false)
|
||||
const wechatCheckFn = () =>{
|
||||
wechatCheck().then((res:any) =>{
|
||||
show.value = res.data
|
||||
})
|
||||
}
|
||||
wechatCheckFn()
|
||||
|
||||
// 验证码
|
||||
const captcha = useCaptcha(formData)
|
||||
captcha.refresh()
|
||||
|
||||
// 获取手机验证码
|
||||
const smsCodeRef = ref<AnyObject | null>(null)
|
||||
const sendSmsCode = async () => {
|
||||
await formRef.value?.validateField('mobile', async (valid, fields) => {
|
||||
if (valid) {
|
||||
smsCodeRef.value?.send()
|
||||
}
|
||||
})
|
||||
}
|
||||
//去登录
|
||||
const emit = defineEmits(['typeChange'])
|
||||
const typeChange = ()=>{
|
||||
emit('typeChange','login')
|
||||
}
|
||||
|
||||
const real_name_input = ref(true)
|
||||
const password_input = ref(true)
|
||||
const confirm_password_input = ref(true)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-checkbox.el-checkbox--large){
|
||||
height: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
@ -1,34 +1,64 @@
|
||||
<template>
|
||||
<el-menu
|
||||
:default-active="appStore.route"
|
||||
:ellipsis="false" :router="true"
|
||||
class="el-menu-vertical-demo w-[200px]"
|
||||
>
|
||||
<el-menu-item index="/member" route="/member" class="divide-y">
|
||||
<span>欢迎页</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/member/center" route="/member/center" class="divide-y">
|
||||
<span>个人信息</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/member/balance" route="/member/balance" class="divide-y">
|
||||
<span>我的余额</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/member/point" route="/member/point" class="divide-y">
|
||||
<span>我的积分</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="w-[180px] flex-shrink-0">
|
||||
<div class="bg-[#fff] pt-[30px] pr-[30px] pb-[21px] pl-[45px] rounded-[var(--rounded-big)]">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="1" class="!mb-[24px]">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[16px]">账户设置</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="text-[14px] leading-[24px] cursor-pointer text-[#666]" :class="{'!text-primary': appStore.route == '/app/member/center'}" @click="router.push('/app/member/center')">个人资料</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="2" class="!mb-[24px]">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[16px]">我的账户</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="text-[14px] leading-[24px] cursor-pointer text-[#666] mb-[24px]" :class="{'!text-primary': appStore.route == '/app/member/point'}" @click="router.push('/app/member/point')">我的积分</div>
|
||||
<div class="text-[14px] leading-[24px] cursor-pointer text-[#666]" :class="{'!text-primary': appStore.route == '/app/member/balance'}" @click="router.push('/app/member/balance')">我的余额</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="7" class="!mb-[24px]">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<span class="text-[16px] oppoSans-M">规则协议</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="text-[14px] leading-[24px] cursor-pointer text-[#666] mb-[24px]" :class="{'!text-primary': appStore.route == '/app/auth/agreement' && route.query.key == 'service'}" @click="router.push({path:'/app/auth/agreement',query:{key:'service'}})">用户协议</div>
|
||||
<div class="text-[14px] leading-[24px] cursor-pointer text-[#666]" :class="{'!text-primary': appStore.route == '/app/auth/agreement' && route.query.key == 'privacy'}" @click="router.push({path:'/app/auth/agreement',query:{key:'privacy'}})">隐私协议</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import useAppStore from '@/stores/app'
|
||||
import { useRouter, useRoute} from 'vue-router'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const activeNames = ref(['1','2','3','4','5','6','7'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-menu-vertical-demo{
|
||||
border: none !important;
|
||||
.el-menu-item{
|
||||
border-bottom: 1px solid #F1F1F1;
|
||||
}
|
||||
:deep(.el-collapse){
|
||||
border:none !important;
|
||||
}
|
||||
:deep(.el-collapse-item__wrap){
|
||||
border:none !important;
|
||||
}
|
||||
:deep(.el-collapse-item__header){
|
||||
--el-collapse-header-height: auto !important;
|
||||
--el-collapse-header-font-size: 16px;
|
||||
border:none !important;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
:deep(.el-collapse-item__content){
|
||||
padding-bottom:0 !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="h-[30px]">
|
||||
<el-button type="primary" link :disabled="!sendSms.canGetCode.value" @click="handleClick">{{ sendSms.text.value }}</el-button>
|
||||
<div class="h-[30px] leading-[28px]">
|
||||
<el-button type="primary" link class="!text-[12px]" :disabled="!sendSms.canGetCode.value" @click="handleClick">{{ sendSms.text.value }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="captchaDialog" :title="t('captchaTitle')" width="350px" :append-to-body="true" :align-center="true">
|
||||
@ -59,7 +59,7 @@ const formRules = reactive({
|
||||
captcha_code: {
|
||||
required: true,
|
||||
message: t('captchaPlaceholder'),
|
||||
trigger: ['blur', 'change']
|
||||
trigger: ['blur']
|
||||
}
|
||||
})
|
||||
const formRef = ref<AnyObject | null>(null)
|
||||
@ -86,6 +86,8 @@ const confirm = async () => {
|
||||
if (sendRes) {
|
||||
value.value = sendRes
|
||||
captchaDialog.value = false
|
||||
captcha.refresh()
|
||||
formData.captcha_code = ''
|
||||
loading.value = false
|
||||
} else if (sendRes === false) {
|
||||
captcha.refresh()
|
||||
|
||||
184
web/components/upload-file/index.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<template v-if="limit == 1">
|
||||
<div class="rounded cursor-pointer overflow-hidden relative border border-solid border-color mr-[10px]" :style="style">
|
||||
<div class="w-full h-full relative image-wrap" v-if="images.data.length">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<el-image :src="img(images.data[0])" fit="contain"></el-image>
|
||||
</div>
|
||||
<div class="absolute z-[1] inset-0 image-mask hidden">
|
||||
<div class="flex items-center justify-center w-full h-full bg-black bg-opacity-60 operation">
|
||||
<icon name="element-ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage()" />
|
||||
<icon name="element-Delete" color="#fff" size="18px" v-if="status" @click="removeImage" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload v-bind="upload" class="upload-file w-full h-full" :show-file-list="false" >
|
||||
<div class="w-full h-full flex items-center justify-center flex-col">
|
||||
<icon name="element-Plus" size="20px" color="var(--el-text-color-secondary)" />
|
||||
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="rounded cursor-pointer overflow-hidden relative border border-solid border-color mr-[10px] mb-[10px]" :style="style" v-for="(item, index) in images.data" :key="index">
|
||||
<div class="w-full h-full relative image-wrap">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<el-image :src="img(item)" fit="contain"></el-image>
|
||||
</div>
|
||||
<div class="absolute z-[1] inset-0 image-mask hidden">
|
||||
<div class=" flex items-center justify-center w-full h-full bg-black bg-opacity-60 operation">
|
||||
<icon name="element-ZoomIn" color="#fff" size="18px" class="mr-[10px]" @click="previewImage(index)" />
|
||||
<icon name="element-Delete" color="#fff" size="18px" v-if="status" @click="removeImage(index)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[6px] cursor-pointer overflow-hidden relative border border-dashed border-color bg-[#fafafa] hover:border-primary" :style="style" v-if="images.data.length < limit && status">
|
||||
<el-upload v-bind="upload" class="upload-file w-full h-full" :show-file-list="false" :multiple="true" :limit="limit">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<icon name="element-Plus" size="28px" color="var(--el-text-color-secondary)" />
|
||||
<div class="leading-none text-xs mt-[10px] text-secondary">{{ imageText }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-image-viewer :url-list="previewImageList" v-if="imageViewer.show" @close="imageViewer.show = false"
|
||||
:initial-index="imageViewer.index" :zoom-rate="1" :hide-on-click-modal="true" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive,computed,watch,toRaw } from 'vue'
|
||||
import { getToken,img } from '@/utils/common'
|
||||
import { UploadFile, ElMessage, UploadFiles } from 'element-plus'
|
||||
|
||||
const prop = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
// 上传图片的文字
|
||||
imageText: {
|
||||
type: String
|
||||
},
|
||||
// 限制图片的数量
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 控制删除按钮的展示
|
||||
status:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue','success'])
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return prop.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const images: Record<string, any> = reactive({
|
||||
data: []
|
||||
})
|
||||
|
||||
let previewImageList: string[] = reactive([])
|
||||
|
||||
const setValue = () => {
|
||||
value.value = toRaw(images.data).toString()
|
||||
previewImageList = toRaw(images.data).map((url: string) => { return img(url) })
|
||||
}
|
||||
|
||||
watch(() => value.value, () => {
|
||||
images.data = [
|
||||
...value.value.split(',').filter((item: string) => { return item })
|
||||
]
|
||||
setValue()
|
||||
}, { immediate: true })
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
width: prop.width,
|
||||
height: prop.height
|
||||
}
|
||||
})
|
||||
|
||||
const headers: Record<string, any> = {}
|
||||
headers.token = getToken()
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
let url = runtimeConfig.public.VITE_APP_BASE_URL || `${location.origin}/api/`
|
||||
const upload: Record<string, any> = {
|
||||
action: `${url}/file/image`,
|
||||
headers,
|
||||
accept: '.png,.jpg,.jpeg',
|
||||
// 检查是否超过数量限制
|
||||
beforeUpload: (file: File) => {
|
||||
if (images.data.length >= prop.limit) {
|
||||
ElMessage.error(`最多只能上传 ${prop.limit} 张图片`);
|
||||
return false;
|
||||
}
|
||||
return true; // 允许上传
|
||||
},
|
||||
onSuccess: (response: any) => {
|
||||
images.data.push(response.data.url);
|
||||
setValue();
|
||||
},
|
||||
onExceed: () => {
|
||||
ElMessage.error(`最多只能上传 ${prop.limit} 张图片`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
* @param index
|
||||
*/
|
||||
const removeImage = (index: number = 0) => {
|
||||
images.data.splice(index, 1)
|
||||
setValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看图片
|
||||
*/
|
||||
const imageViewer = reactive({
|
||||
show: false,
|
||||
index: 0
|
||||
})
|
||||
|
||||
const previewImage = (index: number = 0) => {
|
||||
imageViewer.show = true
|
||||
imageViewer.index = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-file .el-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.image-wrap:hover .image-mask {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +1,4 @@
|
||||
import { ref } from 'vue'
|
||||
import { getCaptcha } from '@/app/api/system'
|
||||
|
||||
interface formData {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { LocationQueryRaw } from 'vue-router'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
export function useLogin() {
|
||||
/**
|
||||
@ -17,17 +18,12 @@ export function useLogin() {
|
||||
/**
|
||||
* 执行登录后跳转
|
||||
*/
|
||||
const handleLoginBack = () => {
|
||||
const data = storage.get('loginBack')
|
||||
if (data) {
|
||||
useRouter().push({ path: data.path, query: data.query })
|
||||
} else {
|
||||
useRouter().push({ path: '/' })
|
||||
}
|
||||
const handleLoginBack = (callbak:any) => {
|
||||
if(callbak) callbak()
|
||||
}
|
||||
|
||||
return {
|
||||
setLoginBack,
|
||||
handleLoginBack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { reactive, ref } from 'vue'
|
||||
import { sendSms } from '@/app/api/system'
|
||||
|
||||
export function useSendSms() {
|
||||
|
||||
@ -4,6 +4,25 @@
|
||||
"getSmsCode": "获取短信验证码",
|
||||
"smsCodeChangeText": "秒后重新获取",
|
||||
"captchaTitle": "请先完成安全验证",
|
||||
"logining": "登录中",
|
||||
"usernamePlaceholder": "请输入账号",
|
||||
"passwordPlaceholder": "请输入密码",
|
||||
"resetpwd": "忘记密码",
|
||||
"noAccount": "还没有账号",
|
||||
"toRegister": "去注册",
|
||||
"registering": "注册中",
|
||||
"confirmPasswordPlaceholder": "请再次确认密码",
|
||||
"confirmPasswordError": "两次输入的密码不一致",
|
||||
"haveAccount": "已有账号",
|
||||
"toLogin": "去登录",
|
||||
"and": "和",
|
||||
"registerAgreeTips": "注册代表您同意",
|
||||
"usernameRegister": "账号注册",
|
||||
"mobileRegister": "手机号注册",
|
||||
"agreeTips": "请阅读并同意",
|
||||
"isAgreeTips":"请先阅读并同意协议",
|
||||
"usernameLogin": "密码登录",
|
||||
"mobileLogin": "验证码登录",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"captchaPlaceholder": "请输入验证码",
|
||||
@ -14,7 +33,6 @@
|
||||
"privacyAgreement": "隐私协议",
|
||||
"protocolNotConfigured": "未配置协议",
|
||||
"siteClose": "站点已关闭",
|
||||
"noSite": "站点不存在",
|
||||
"request": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
|
||||
22
web/layouts/default/components/error/index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="ml-[20px] min-h-[70vh] w-[1000px] flex">
|
||||
<div class="m-auto">
|
||||
<div class="text-[#333] text-center text-[24px] mt-[35px] mb-[26px] ">请登录查看</div>
|
||||
<div class="w-[100px] h-[40px] leading-[40px] border-[1px] border-solid border-[#ccc] rounded-full text-center text-[14px] mx-auto cursor-pointer" @click="handleLogin">登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed,watch} from 'vue'
|
||||
import useMemberStore from '@/stores/member'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const handleLogin = ()=>{
|
||||
memberStore.logOpen()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@ -1,29 +1,14 @@
|
||||
<template>
|
||||
<div class="flex h-[220px] min-w-[1200px] bg-[#3F4045]">
|
||||
<div class="mt-[70px] w-full">
|
||||
<p class="text-center text-[#999]">
|
||||
<p class="text-center text-[#999]" v-if="friendlyLink.length">
|
||||
<span>友情链接:</span>
|
||||
<NuxtLink to="https://www.bt.cn">
|
||||
<span class="mr-[10px]">宝塔</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://www.oschina.net">
|
||||
<span class="mr-[10px]">开源中国</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://www.aliyun.com">
|
||||
<span class="mr-[10px]">阿里云</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://gitee.com/">
|
||||
<span class="mr-[10px]">码云Gitee</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://cloud.tencent.com/">
|
||||
<span class="mr-[10px]">腾讯云</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://mp.weixin.qq.com">
|
||||
<span class="mr-[10px]">微信公众平台</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="http://www.thinkphp.cn">
|
||||
<span class="mr-[10px]">Thinkphp</span>
|
||||
</NuxtLink>
|
||||
<template v-for="(item,index) in friendlyLink" :key="index">
|
||||
<NuxtLink :to="item.link_url" target="_blank">
|
||||
<span>{{item.link_title}}</span>
|
||||
<span class="mx-[10px] text-[#D9D9D9]" v-if="(index + 1) != friendlyLink.length">|</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</p>
|
||||
<p class="text-center mt-[20px] text-[#999]" v-if="copyright">
|
||||
<NuxtLink :to="copyright.gov_url" v-if="copyright.gov_record">
|
||||
@ -44,6 +29,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { getCopyRight } from '@/app/api/system';
|
||||
import { reactive, ref } from 'vue'
|
||||
import { getFriendlyLink } from '@/app/api/system'
|
||||
|
||||
const copyright = ref(null);
|
||||
const getCopy = () => {
|
||||
@ -52,6 +38,15 @@ const getCopy = () => {
|
||||
})
|
||||
}
|
||||
getCopy()
|
||||
|
||||
const friendlyLink = ref([]) // 格式:{ link_title: '', link_url: '' }
|
||||
|
||||
const getFriendlyLinkFn = () =>{
|
||||
getFriendlyLink().then((res:any) =>{
|
||||
friendlyLink.value = res.data
|
||||
})
|
||||
}
|
||||
getFriendlyLinkFn()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex h-full min-w-[1200px]">
|
||||
<div class="flex h-full min-w-[1200px] bg-[#fff]">
|
||||
<div class="flex items-center ml-[20px]">
|
||||
<NuxtLink to="/">
|
||||
<div class="w-[132px] mr-[10px]"><img src="@/assets/images/index/logo.jpg" /></div>
|
||||
@ -10,48 +10,57 @@
|
||||
|
||||
<div class="mx-auto flex-shrink">
|
||||
<el-menu :default-active="appStore.route" class="h-full" mode="horizontal" :ellipsis="false" :router="true">
|
||||
<el-menu-item index="/" route="/">
|
||||
<el-menu-item index="/" route="/app/index">
|
||||
<span class="text-base mx-4">首页</span>
|
||||
<span></span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/article/list" route="/article/list">
|
||||
<span class="text-base mx-4">文章</span>
|
||||
<span></span>
|
||||
</el-menu-item>
|
||||
<el-menu-item route="/">
|
||||
<span class="text-base mx-4">社区</span>
|
||||
<el-menu-item index="bbs">
|
||||
<span class="text-base mx-4" @click.stop="openBbs">社区</span>
|
||||
<span></span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mr-[20px] ml-auto whitespace-pre">
|
||||
<div class="flex items-center justify-end mr-[20px] ml-auto whitespace-pre-wrap">
|
||||
<div v-if="info">
|
||||
<NuxtLink to="/member/center">
|
||||
<NuxtLink to="/app/member/center">
|
||||
<span class="cursor-pointer">{{ info.nickname }}</span>
|
||||
</NuxtLink>
|
||||
<span class="mx-2">|</span>
|
||||
<span class="cursor-pointer" @click="logoutFn">退出</span>
|
||||
</div>
|
||||
<NuxtLink to="/auth/login" v-else>
|
||||
<el-button type="primary" link>{{ t('login') }} / {{ t('register') }}</el-button>
|
||||
</NuxtLink>
|
||||
<el-button type="primary" link v-else @click="toLogin">{{ t('login') }} / {{ t('register') }}</el-button>
|
||||
</div>
|
||||
<LoadingDialog/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getToken } from '@/utils/common'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useAppStore from '@/stores/app'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import LoadingDialog from '@/components/login-dialog/index.vue'
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const memberStore = useMemberStore()
|
||||
const info = computed(() => memberStore.info)
|
||||
|
||||
const toLogin = () => {
|
||||
if(!getToken() && !configStore.login.is_username && !configStore.login.is_mobile && !configStore.login.is_bind_mobile){
|
||||
ElMessage.error('商家未开启普通账号登录注册')
|
||||
return false
|
||||
}
|
||||
memberStore.logOpen()
|
||||
}
|
||||
const logoutFn = () => {
|
||||
memberStore.logout()
|
||||
navigateTo(`/auth/login`)
|
||||
navigateTo(`/app/index`)
|
||||
}
|
||||
|
||||
const openBbs = () => {
|
||||
window.open('https://www.niushop.com/bbs.html')
|
||||
}
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
|
||||
44
web/layouts/member.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-container class="w-screen h-screen">
|
||||
<el-header>
|
||||
<layout-header />
|
||||
</el-header>
|
||||
<el-main class="p-0 min-w-[1200px]">
|
||||
<div class="bg-page pt-6 pb-6">
|
||||
<div class="main-container flex justify-between">
|
||||
<sidebar></sidebar>
|
||||
<div v-if="agreeShow"><slot></slot></div>
|
||||
<div v-else><layout-error ></layout-error></div>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import layoutHeader from './default/components/header/index.vue'
|
||||
import layoutError from './default/components/error/index.vue'
|
||||
import sidebar from '@/components/sidebar/index.vue'
|
||||
import { getToken } from '@/utils/common'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
let agreeShow = ref(false)
|
||||
watch(()=> router.currentRoute.value.path ,(newValue)=>{
|
||||
if(router.currentRoute.value.path == '/auth/agreement' || router.currentRoute.value.path == '/app/auth/agreement' || getToken() ){
|
||||
agreeShow.value = true
|
||||
}else{
|
||||
agreeShow.value = false
|
||||
}
|
||||
},{immediate:true,deep: true})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-header {
|
||||
--el-header-padding: 0;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
--el-main-padding: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,12 @@
|
||||
|
||||
import useAppStore from '@/stores/app'
|
||||
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
if (!getToken()) {
|
||||
useLogin().setLoginBack(to)
|
||||
return navigateTo('/auth/login')
|
||||
useAppStore().$patch(state => {
|
||||
state.route = to.path
|
||||
})
|
||||
// useLogin().setLoginBack(to)
|
||||
// return navigateTo('/auth/login')
|
||||
}
|
||||
})
|
||||
@ -24,7 +24,7 @@ export default defineNuxtConfig({
|
||||
promiseExportName: '__tla',
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
})
|
||||
}),
|
||||
]
|
||||
},
|
||||
ssr: false
|
||||
|
||||
54
web/package-lock.json
generated
@ -10,10 +10,13 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"aos": "^2.3.4",
|
||||
"element-plus": "^2.3.2",
|
||||
"pinia": "^2.0.33",
|
||||
"qrcode": "^1.5.1",
|
||||
"sass": "^1.60.0"
|
||||
"qs": "6.7.0",
|
||||
"sass": "^1.60.0",
|
||||
"swiper": "^11.1.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@element-plus/nuxt": "^1.0.4",
|
||||
@ -2389,6 +2392,16 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/aos": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/aos/-/aos-2.3.4.tgz",
|
||||
"integrity": "sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==",
|
||||
"dependencies": {
|
||||
"classlist-polyfill": "^1.0.3",
|
||||
"lodash.debounce": "^4.0.6",
|
||||
"lodash.throttle": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.0.0.tgz",
|
||||
@ -2829,6 +2842,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/classlist-polyfill": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz",
|
||||
"integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ=="
|
||||
},
|
||||
"node_modules/clear": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/clear/-/clear-0.1.0.tgz",
|
||||
@ -4962,8 +4980,7 @@
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"dev": true
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
@ -5026,6 +5043,11 @@
|
||||
"lodash._reinterpolate": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
|
||||
},
|
||||
"node_modules/lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
@ -6887,6 +6909,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -7600,6 +7630,24 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "11.1.15",
|
||||
"resolved": "https://registry.npmmirror.com/swiper/-/swiper-11.1.15.tgz",
|
||||
"integrity": "sha512-IzWeU34WwC7gbhjKsjkImTuCRf+lRbO6cnxMGs88iVNKDwV+xQpBCJxZ4bNH6gSrIbbyVJ1kuGzo3JTtz//CBw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/swiperjs"
|
||||
},
|
||||
{
|
||||
"type": "open_collective",
|
||||
"url": "http://opencollective.com/swiper"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz",
|
||||
|
||||
@ -15,14 +15,17 @@
|
||||
"nuxt": "^3.4.1",
|
||||
"nuxt-windicss": "^2.6.0",
|
||||
"sass": "^1.60.0",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vite-plugin-top-level-await": "^1.3.1"
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
"vue-i18n": "^9.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"aos": "^2.3.4",
|
||||
"element-plus": "^2.3.2",
|
||||
"pinia": "^2.0.33",
|
||||
"qrcode": "^1.5.1",
|
||||
"sass": "^1.60.0"
|
||||
"sass": "^1.60.0",
|
||||
"swiper": "^11.1.15",
|
||||
"qs": "6.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
web/plugins/aos.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import AOS from 'aos';
|
||||
import "aos/dist/aos.css";
|
||||
export default defineNuxtPlugin((NuxtApp) => {
|
||||
AOS.init(); // 初始化
|
||||
NuxtApp.vueApp.use(AOS)
|
||||
})
|
||||
@ -9,12 +9,10 @@ interface loginConfig {
|
||||
agreement_show: number | boolean
|
||||
}
|
||||
|
||||
|
||||
interface Config {
|
||||
login: loginConfig
|
||||
}
|
||||
|
||||
|
||||
const useConfigStore = defineStore('config', {
|
||||
state: (): Config => {
|
||||
return {
|
||||
@ -28,15 +26,17 @@ const useConfigStore = defineStore('config', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getLoginConfig() {
|
||||
async getLoginConfig(router: any = null) {
|
||||
await getConfig().then(({ data }) => {
|
||||
this.login.is_username = parseInt(data.is_username)
|
||||
this.login.is_mobile = parseInt(data.is_mobile)
|
||||
this.login.is_auth_register = parseInt(data.is_auth_register)
|
||||
this.login.is_bind_mobile = parseInt(data.is_bind_mobile)
|
||||
this.login.agreement_show = parseInt(data.agreement_show)
|
||||
if(data && router && router.currentRoute.value.path === '/site/close'){
|
||||
navigateTo('/', { replace: true })
|
||||
}
|
||||
}).catch(() => {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,16 @@ import { logout } from '@/app/api/auth'
|
||||
|
||||
interface Member {
|
||||
token: string | null
|
||||
info: Record<string, any> | null
|
||||
info: Record<string, any> | null,
|
||||
loginPopup:boolean
|
||||
}
|
||||
|
||||
const useMemberStore = defineStore('member', {
|
||||
state: (): Member => {
|
||||
return {
|
||||
token: useCookie('token').value,
|
||||
info: null
|
||||
info: null,
|
||||
loginPopup:false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -36,6 +38,12 @@ const useMemberStore = defineStore('member', {
|
||||
this.info = null
|
||||
useCookie('token').value = null
|
||||
logout().then().catch()
|
||||
},
|
||||
logOpen(){
|
||||
this.loginPopup = true
|
||||
},
|
||||
logClose(){
|
||||
this.loginPopup = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -5,6 +5,7 @@ import { getSiteInfo } from '@/app/api/system'
|
||||
interface System {
|
||||
lang: string,
|
||||
site: Record<string, any>
|
||||
|
||||
}
|
||||
|
||||
const useSystemStore = defineStore('system', {
|
||||
@ -18,15 +19,16 @@ const useSystemStore = defineStore('system', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getSitenfo() {
|
||||
await getSiteInfo()
|
||||
.then((res: any) => {
|
||||
this.site = res.data
|
||||
if (this.site.status == 3) navigateTo('/site/close', { replace: true })
|
||||
})
|
||||
.catch((err) => {
|
||||
navigateTo('/site/nosite', { replace: true })
|
||||
})
|
||||
async getSiteInfoFn() {
|
||||
await getSiteInfo().then((res: any) => {
|
||||
this.site = res.data
|
||||
if (!('shop_web' in this.site.site_addons)) {
|
||||
navigateTo('/app/index', { replace: true })
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import useMemberStores from '@/stores/member'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
@ -7,11 +8,29 @@ import useMemberStores from '@/stores/member'
|
||||
export function getToken(): null | string {
|
||||
return useMemberStores().token
|
||||
}
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param fn
|
||||
* @param delay
|
||||
* @returns
|
||||
*/
|
||||
export function debounce(fn: (args?: any) => any, delay: number = 300) {
|
||||
let timer: null | number = null
|
||||
return function (...args: any) {
|
||||
if (timer != null) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
fn.call(this, ...args)
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是url
|
||||
* @param str
|
||||
* @returns
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
export function isUrl(str: string): boolean {
|
||||
return str.indexOf('http://') != -1 || str.indexOf('https://') != -1
|
||||
@ -19,10 +38,75 @@ export function isUrl(str: string): boolean {
|
||||
|
||||
/**
|
||||
* 图片输出
|
||||
* @param path
|
||||
* @returns
|
||||
* @param path
|
||||
* @returns
|
||||
*/
|
||||
export function img(path: string): string {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
return isUrl(path) ? path : `${runtimeConfig.public.VITE_IMG_DOMAIN || location.origin}/${path}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额格式化
|
||||
*/
|
||||
export function moneyFormat(money: string): string {
|
||||
return isNaN(parseFloat(money)) ? money : parseFloat(money).toFixed(2)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 深度克隆
|
||||
* @param {object} obj 需要深度克隆的对象
|
||||
* @returns {*} 克隆后的对象或者原值(不是对象)
|
||||
*/
|
||||
export function deepClone(obj: object) {
|
||||
// 对常见的“非”值,直接返回原来值
|
||||
if ([null, undefined, NaN, false].includes(obj)) return obj
|
||||
if (typeof obj !== 'object' && typeof obj !== 'function') {
|
||||
// 原始类型直接返回
|
||||
return obj
|
||||
}
|
||||
const o = isArray(obj) ? [] : {}
|
||||
for (const i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
const isArray = (value: any) => {
|
||||
if (typeof Array.isArray === 'function') {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
return Object.prototype.toString.call(value) === '[object Array]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制
|
||||
* @param {Object} value
|
||||
* @param {Object} callback
|
||||
*/
|
||||
export function copy(value, callback) {
|
||||
var oInput = document.createElement('input'); //创建一个隐藏input(重要!)
|
||||
oInput.value = value; //赋值
|
||||
oInput.setAttribute("readonly", "readonly");
|
||||
document.body.appendChild(oInput);
|
||||
oInput.select(); // 选择对象
|
||||
document.execCommand("Copy"); // 执行浏览器复制命令
|
||||
oInput.className = 'oInput';
|
||||
oInput.style.display = 'none';
|
||||
document.body.removeChild(oInput); // 删除创建的对象
|
||||
ElMessage({
|
||||
message: '复制成功',
|
||||
type: 'success',
|
||||
})
|
||||
typeof callback == 'function' && callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤特殊字符
|
||||
* @param event
|
||||
*/
|
||||
export function filterSpecial(event:any){
|
||||
event.target.value = event.target.value.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '')
|
||||
event.target.value = event.target.value.replace(/[`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g,'')
|
||||
}
|
||||
|
||||
@ -63,7 +63,6 @@ class Language {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param app
|
||||
* @param path
|
||||
*/
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { breakpointsTailwind } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import qs from 'qs'
|
||||
|
||||
interface ConfigOption {
|
||||
showErrorMessage?: boolean
|
||||
@ -48,7 +48,8 @@ class Http {
|
||||
if (data.code == 1) {
|
||||
if (options.showSuccessMessage) ElMessage({ message: data.msg, type: 'success' })
|
||||
} else {
|
||||
if (data.code == 0) {
|
||||
if (options.showErrorMessage === false) return;
|
||||
if (data.code == 0 || data.code == 400) {
|
||||
ElMessage({ message: data.msg, type: 'error' })
|
||||
} else {
|
||||
this.handleAuthError(data.code)
|
||||
@ -59,7 +60,8 @@ class Http {
|
||||
}
|
||||
|
||||
public get(url: string, query = {}, config: ConfigOption = {}) {
|
||||
return this.request(url, 'GET', { query }, config)
|
||||
url += '?' + qs.stringify(query)
|
||||
return this.request(url, 'GET', {}, config)
|
||||
}
|
||||
|
||||
public post(url: string, body = {}, config: ConfigOption = {}) {
|
||||
@ -78,8 +80,8 @@ class Http {
|
||||
* 发送请求
|
||||
* @param url
|
||||
* @param method
|
||||
* @param showMessageConfig
|
||||
* @returns
|
||||
* @param param
|
||||
* @param config
|
||||
*/
|
||||
private request(url: string, method: string, param: AnyObject = {}, config: ConfigOption = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -88,7 +90,6 @@ class Http {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
!this.options.baseURL && (this.options.baseURL = runtimeConfig.public.VITE_APP_BASE_URL || `${location.origin}/api/`)
|
||||
this.options.baseURL.substr(-1) != '/' && (this.options.baseURL += '/')
|
||||
|
||||
// 处理数组格式
|
||||
for (const key in param.query) {
|
||||
if (param.query[key] instanceof Array) {
|
||||
@ -98,7 +99,6 @@ class Http {
|
||||
delete param.query[key]
|
||||
}
|
||||
}
|
||||
|
||||
useFetch(url, { ...this.options, method, ...config, ...param }).then((response) => {
|
||||
const { data: { value }, error } = response
|
||||
if (value) {
|
||||
|
||||
@ -222,8 +222,7 @@ const test = {
|
||||
},
|
||||
/**
|
||||
* 是否为正则对象
|
||||
* @param {Object}
|
||||
* @return {Boolean}
|
||||
* @param o
|
||||
*/
|
||||
regExp(o) {
|
||||
return o && Object.prototype.toString.call(o) === '[object RegExp]'
|
||||
|
||||