update web
@ -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')
|
||||
}
|
||||
20
web/app.vue
@ -13,7 +13,6 @@ import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import en from 'element-plus/dist/locale/en.mjs'
|
||||
import Language from '~~/utils/language'
|
||||
import useSystemStore from '@/stores/system'
|
||||
import useAppStore from '@/stores/app'
|
||||
import useMemberStore from '@/stores/member'
|
||||
// 引入全局样式
|
||||
import '@/assets/styles/index.scss'
|
||||
@ -32,22 +31,21 @@ const locale = computed(() => (systemStore.lang === 'zh-cn' ? zhCn : en))
|
||||
const configStore = useConfigStore()
|
||||
configStore.getLoginConfig()
|
||||
|
||||
// 查询站点信息
|
||||
systemStore.getSitenfo()
|
||||
|
||||
// 如果已登录
|
||||
getToken() && useMemberStore().setToken(getToken())
|
||||
|
||||
const route = useRoute()
|
||||
watch(route, (nval, oval) => {
|
||||
useAppStore().$patch(state => {
|
||||
state.route = route.path
|
||||
})
|
||||
// 加载语言包
|
||||
const fileinfo = nval.matched[0].components.default.__file.split('/pages/')
|
||||
const file = fileinfo[1].replace('.vue', '')
|
||||
const app = fileinfo[0].substring(fileinfo[0].lastIndexOf('/') + 1)
|
||||
|
||||
const language = new Language(useNuxtApp().$getI18n())
|
||||
language.loadLocaleMessages(app, file, useSystemStore().lang)
|
||||
|
||||
// 设置页面title
|
||||
let path = route.path == '/' ? '/index' : route.path
|
||||
// 处理部署后不知道为什么url会自动拼接上 / 的问题
|
||||
if (path.slice(-1) == '/') path = path.slice(0, -1)
|
||||
path = !path.lastIndexOf('/') ? `${path}/index` : path
|
||||
let key = path.replace('/', '').replaceAll('/', '.')
|
||||
|
||||
@ -58,10 +56,6 @@ watch(route, (nval, oval) => {
|
||||
}, !oval ? 500 : 0)
|
||||
}, { immediate: true })
|
||||
|
||||
// 语言包初始化加载
|
||||
const language = new Language(useNuxtApp().$getI18n())
|
||||
language.loadLocaleMessages(route.path, useSystemStore().lang)
|
||||
|
||||
// 设置title模板
|
||||
useHead({
|
||||
titleTemplate: (title) => {
|
||||
|
||||
@ -66,11 +66,4 @@ export function fetchBase64Image(data: AnyObject) {
|
||||
*/
|
||||
export function getCopyRight(data: AnyObject) {
|
||||
return request.get('copyright', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点信息
|
||||
*/
|
||||
export function getSiteInfo() {
|
||||
return request.get('site')
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
2
web/app/lang/en/common.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
33
web/app/lang/zh-cn/common.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"getSmsCode": "获取短信验证码",
|
||||
"smsCodeChangeText": "秒后重新获取",
|
||||
"captchaTitle": "请先完成安全验证",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"captchaPlaceholder": "请输入验证码",
|
||||
"mobilePlaceholder": "请输入手机号码",
|
||||
"mobileError": "请输入正确的手机号",
|
||||
"codePlaceholder": "请输入手机验证码",
|
||||
"userAgreement": "用户协议",
|
||||
"privacyAgreement": "隐私协议",
|
||||
"protocolNotConfigured": "未配置协议",
|
||||
"request": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
"401": "请重新登录",
|
||||
"403": "拒绝访问",
|
||||
"404": "请求错误",
|
||||
"405": "请求方法未允许",
|
||||
"408": "请求超时",
|
||||
"409": "请求跨域",
|
||||
"500": "服务器端出错,错误原因:",
|
||||
"501": "网络未实现",
|
||||
"502": "网络错误",
|
||||
"503": "服务不可用",
|
||||
"504": "网络超时",
|
||||
"505": "http版本不支持该请求",
|
||||
"timeout": "网络请求超时!"
|
||||
}
|
||||
}
|
||||
@ -5,20 +5,12 @@
|
||||
},
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "登录",
|
||||
"register": "注册",
|
||||
"bind": "手机号绑定"
|
||||
},
|
||||
"member": {
|
||||
"index": "欢迎页",
|
||||
"center": "个人中心"
|
||||
},
|
||||
"article": {
|
||||
"list": "文章",
|
||||
"detail": "文章"
|
||||
},
|
||||
"site": {
|
||||
"close": "站点已关闭",
|
||||
"nosite": "站点不存在"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getAgreementInfo } from '@/api/system'
|
||||
import { getAgreementInfo } from '@/app/api/system'
|
||||
|
||||
const agreement = ref<any | null>(null)
|
||||
const route = useRoute()
|
||||
@ -26,4 +26,4 @@ getAgreementInfo(route.query.key).then(({ data }) => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped></style>
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="w-full h-full bg-page flex items-center justify-center">
|
||||
<div class="flex bg-white">
|
||||
|
||||
|
||||
<div class="bg-white w-[380px] p-[30px]">
|
||||
<div class="flex items-end mb-[30px] mt-[15px]">
|
||||
<div class="mr-[20px] text-base cursor-pointer leading-none font-bold">{{t('mobileBind')}}</div>
|
||||
<div class="mr-[20px] text-base cursor-pointer leading-none font-bold">{{ t('mobileBind') }}</div>
|
||||
</div>
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" :validate-on-rule-change="false">
|
||||
<div>
|
||||
@ -15,14 +15,16 @@
|
||||
<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>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key"
|
||||
@click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleRegister" :loading="loading">{{ loading ? t('binding') : t('bind') }}</el-button>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleRegister"
|
||||
:loading="loading">{{ loading ? t('binding') : t('bind') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@ -32,8 +34,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { bind } from '@/api/auth'
|
||||
import { bindMobile } from '@/api/member'
|
||||
import { bind } from '@/app/api/auth'
|
||||
import { bindMobile } from '@/app/api/member'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import { FormInstance } from 'element-plus'
|
||||
definePageMeta({
|
||||
@ -61,7 +63,7 @@ const formRules = computed(() => {
|
||||
},
|
||||
{
|
||||
validator(rule: any, value: string, callback: any) {
|
||||
return test.mobile(value)
|
||||
return test.mobile(value)
|
||||
},
|
||||
message: t('mobileError'),
|
||||
trigger: ['change', 'blur'],
|
||||
@ -135,4 +137,4 @@ const sendSmsCode = async () => {
|
||||
:deep(.el-form-item__error) {
|
||||
padding-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</style>~/app/api/auth~/app/api/member
|
||||
@ -7,7 +7,8 @@
|
||||
<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">
|
||||
<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>
|
||||
@ -17,16 +18,19 @@
|
||||
|
||||
<div class="bg-white w-[380px] p-[30px]">
|
||||
<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 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>
|
||||
<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">
|
||||
</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">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@ -38,7 +42,8 @@
|
||||
<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>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key"
|
||||
@click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@ -54,7 +59,8 @@
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleLogin" :loading="loading">{{ loading ? t('logining') : t('login') }}</el-button>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleLogin"
|
||||
:loading="loading">{{ loading ? t('logining') : t('login') }}</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<div class="text-xs py-[50rpx] flex justify-center w-full" v-if="configStore.login.agreement_show">
|
||||
@ -77,15 +83,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { FormInstance } from 'element-plus'
|
||||
import { usernameLogin, mobileLogin, scanlogin, checkscan } from '@/api/auth'
|
||||
import { usernameLogin, mobileLogin, scanlogin, checkscan } from '@/app/api/auth'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import QRCode from "qrcode";
|
||||
|
||||
definePageMeta({
|
||||
layout: "container"
|
||||
});
|
||||
|
||||
// 校验二维码
|
||||
const checkScanFn = (key) => {
|
||||
let parameter = { key };
|
||||
@ -119,7 +121,7 @@ const checkScanFn = (key) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 扫码登录,微信二维码
|
||||
// 扫码登录,微信二维码
|
||||
const weixinCode = ref({
|
||||
url: '',
|
||||
key: '',
|
||||
@ -226,7 +228,7 @@ const sendSmsCode = async () => {
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-input__wrapper {
|
||||
@ -254,4 +256,4 @@ const sendSmsCode = async () => {
|
||||
.text-color {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
</style>~/app/api/auth
|
||||
@ -5,26 +5,32 @@
|
||||
<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>
|
||||
<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="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 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">
|
||||
</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">
|
||||
</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">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@ -36,7 +42,8 @@
|
||||
<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>
|
||||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key"
|
||||
@click="sendSmsCode" ref="smsCodeRef"></sms-code>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@ -46,7 +53,8 @@
|
||||
<el-input v-model="formData.captcha_code" :placeholder="t('captchaPlaceholder')">
|
||||
<template #suffix>
|
||||
<div class="py-0 leading-none">
|
||||
<el-image :src="captcha.image.value" class="h-[30px] cursor-pointer" @click="captcha.refresh()"></el-image>
|
||||
<el-image :src="captcha.image.value" class="h-[30px] cursor-pointer"
|
||||
@click="captcha.refresh()"></el-image>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
@ -60,7 +68,8 @@
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleRegister" :loading="loading">{{ loading ? t('registering') : t('register') }}</el-button>
|
||||
<el-button type="primary" class="mt-[20px] w-full" size="large" @click="handleRegister"
|
||||
:loading="loading">{{ loading ? t('registering') : t('register') }}</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<div class="text-xs py-[50rpx] flex justify-center w-full" v-if="configStore.login.agreement_show">
|
||||
@ -80,7 +89,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usernameRegister, mobileRegister } from '@/api/auth'
|
||||
import { usernameRegister, mobileRegister } from '@/app/api/auth'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useConfigStore from '@/stores/config'
|
||||
import { FormInstance } from 'element-plus'
|
||||
@ -234,5 +243,4 @@ const sendSmsCode = async () => {
|
||||
|
||||
:deep(.el-form-item__error) {
|
||||
padding-top: 5px;
|
||||
}
|
||||
</style>
|
||||
}</style>~/app/api/auth
|
||||
@ -13,7 +13,7 @@
|
||||
<div class="flex justify-between mt-[100px]">
|
||||
<div class="w-[280px]">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/course.jpg" /></div>
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/app/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]">
|
||||
@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<div class="w-[280px]">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/api.jpg" /></div>
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/app/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]">
|
||||
@ -41,7 +41,7 @@
|
||||
</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>
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/app/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]">
|
||||
@ -55,7 +55,7 @@
|
||||
</div>
|
||||
<div class="w-[280px]">
|
||||
<div class="flex items-center">
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/assets/images/word/wx.jpg"></div>
|
||||
<div class="w-[30px] h-[30px] mr-[10px]"><img src="@/app/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]">
|
||||
@ -79,8 +79,8 @@
|
||||
}
|
||||
|
||||
.word {
|
||||
background-image: url(@/assets/images/word/word-back.jpg);
|
||||
background-image: url(@/app/assets/images/word/word-back.jpg);
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@ -5,7 +5,7 @@
|
||||
<el-card class="box-card flex-1 ml-4" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{t('myBalance')}}</span>
|
||||
<span>{{ t('myBalance') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-6">
|
||||
@ -17,7 +17,10 @@
|
||||
<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" />
|
||||
<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>
|
||||
</div>
|
||||
</el-card>
|
||||
@ -28,7 +31,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { getBalanceList } from '@/api/member'
|
||||
import { getBalanceList } from '@/app/api/member'
|
||||
// 获取会员列表
|
||||
const balanceTableData = reactive({
|
||||
page: 1,
|
||||
@ -57,10 +60,11 @@ loadBalanceList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-card{
|
||||
.box-card {
|
||||
border: none !important;
|
||||
}
|
||||
.text-color{
|
||||
|
||||
.text-color {
|
||||
color: var(--jjext-color-brand);
|
||||
}
|
||||
</style>
|
||||
</style>~/app/api/member
|
||||
@ -12,7 +12,8 @@
|
||||
<el-form :model="info" class="form-wrap" label-width="120px">
|
||||
<el-form-item :label="t('memberHeadimg')">
|
||||
<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-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>
|
||||
@ -22,7 +23,8 @@
|
||||
<el-form-item :label="t('nickname')">
|
||||
<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 class="cursor-pointer text-color" @click="updateNickname.modal = true">{{
|
||||
t('edit') }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -50,7 +52,7 @@
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useAppStore from '@/stores/app'
|
||||
import { modifyMember } from '@/api/member'
|
||||
import { modifyMember } from '@/app/api/member'
|
||||
import { ElMessage, UploadFile, UploadFiles } from 'element-plus'
|
||||
import request from '@/utils/request'
|
||||
import storage from '@/utils/storage'
|
||||
@ -77,7 +79,6 @@ definePageMeta({ middleware: 'auth' })
|
||||
const upload = computed(() => {
|
||||
const headers: Record<string, any> = {}
|
||||
headers.token = getToken()
|
||||
headers['site-id'] = storage.get('siteId') || 1
|
||||
return {
|
||||
action: `${request.options.baseURL}/file/image`,
|
||||
limit: 1,
|
||||
@ -120,4 +121,4 @@ const updateNicknameConfirm = () => {
|
||||
::v-deep .form-wrap .el-form-item {
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</style>~/app/api/member
|
||||
@ -5,19 +5,21 @@
|
||||
<el-card class="box-card flex-1 ml-4" v-loading="loading" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{t('welcomePage')}}</span>
|
||||
<span>{{ t('welcomePage') }}++++++</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-10 py-5" v-if="info">
|
||||
<div class="flex items-center border-gray-300 border-b-1 pb-5 px-5">
|
||||
<img v-if="!info.headimg" class="w-[65px] h-[65px] rounded-full" src="@/assets/images/default_headimg.png" alt="">
|
||||
<img v-if="!info.headimg" class="w-[65px] h-[65px] rounded-full"
|
||||
src="@/assets/images/default_headimg.png" alt="">
|
||||
<img v-else :src="img(info.headimg)" class="w-[65px] h-[65px] rounded-full" alt="">
|
||||
<div class="ml-4">
|
||||
<div>
|
||||
<span class="text-base font-bold">{{info.nickname}}</span>
|
||||
<span class="text-xs">({{t('mobile')}}:{{info.mobile ? info.mobile : t('notBound') }})</span>
|
||||
<span class="text-base font-bold">{{ info.nickname }}</span>
|
||||
<span class="text-xs">({{ t('mobile') }}:{{ info.mobile ? info.mobile : t('notBound')
|
||||
}})</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{t('registrationTime')}}:{{info.create_time}}</p>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ t('registrationTime') }}:{{ info.create_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-8 statistic-wrap px-8">
|
||||
@ -41,24 +43,30 @@ import useMemberStore from '@/stores/member'
|
||||
import useAppStore from '@/stores/app'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
|
||||
definePageMeta({ middleware: 'auth' })
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const loading = ref(true)
|
||||
const info = computed(() =>{
|
||||
if(memberStore.info) loading.value = false;
|
||||
const info = computed(() => {
|
||||
if (memberStore.info) loading.value = false;
|
||||
return memberStore.info;
|
||||
})
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .box-card{
|
||||
::v-deep .box-card {
|
||||
border: none !important;
|
||||
.el-card__header{
|
||||
|
||||
.el-card__header {
|
||||
border-color: #F1F1F1;
|
||||
}
|
||||
}
|
||||
::v-deep .statistic-wrap{
|
||||
.el-statistic__head, .el-statistic__content{
|
||||
|
||||
::v-deep .statistic-wrap {
|
||||
|
||||
.el-statistic__head,
|
||||
.el-statistic__content {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
<el-card class="box-card flex-1 ml-4" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{t('myPoint')}}</span>
|
||||
<span>{{ t('myPoint') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-6">
|
||||
@ -17,7 +17,9 @@
|
||||
<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" />
|
||||
<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>
|
||||
</div>
|
||||
</el-card>
|
||||
@ -28,7 +30,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { getPointList } from '@/api/member'
|
||||
import { getPointList } from '@/app/api/member'
|
||||
// 获取会员列表
|
||||
const pointTableData = reactive({
|
||||
page: 1,
|
||||
@ -57,10 +59,10 @@ loadPointList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-card{
|
||||
.box-card {
|
||||
border: none !important;
|
||||
}
|
||||
.text-color{
|
||||
|
||||
.text-color {
|
||||
color: var(--jjext-color-brand);
|
||||
}
|
||||
</style>
|
||||
}</style>~/app/api/member
|
||||
59
web/app/pages/routes.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export default [
|
||||
{
|
||||
path: "/",
|
||||
component: () => import('~/app/pages/index.vue')
|
||||
},
|
||||
{
|
||||
path: "/auth/login",
|
||||
component: () => import('~/app/pages/auth/login.vue'),
|
||||
meta: {
|
||||
layout: "container"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/auth/register",
|
||||
component: () => import('~/app/pages/auth/register.vue'),
|
||||
meta: {
|
||||
layout: "container"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/auth/bind",
|
||||
component: () => import('~/app/pages/auth/bind.vue'),
|
||||
meta: {
|
||||
layout: "container"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/auth/agreement",
|
||||
component: () => import('~/app/pages/auth/agreement.vue')
|
||||
},
|
||||
{
|
||||
path: "/member",
|
||||
component: () => import('~/app/pages/member/index.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/center",
|
||||
component: () => import('~/app/pages/member/center.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/balance",
|
||||
component: () => import('~/app/pages/member/balance.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/member/point",
|
||||
component: () => import('~/app/pages/member/point.vue'),
|
||||
meta: {
|
||||
middleware: ["auth"]
|
||||
}
|
||||
}
|
||||
]
|
||||
16
web/app/router.options.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { RouterConfig } from '@nuxt/schema'
|
||||
|
||||
const routeFiles = import.meta.glob('../**/routes.ts')
|
||||
const routes = []
|
||||
|
||||
for (const key of Object.keys(routeFiles)) {
|
||||
await routeFiles[key]().then(res => {
|
||||
routes.push(...res.default)
|
||||
})
|
||||
}
|
||||
|
||||
// https://router.vuejs.org/api/interfaces/routeroptions.html
|
||||
export default <RouterConfig>{
|
||||
routes: (_routes) => routes,
|
||||
strict: false
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { getCaptcha } from '@/api/system'
|
||||
import { getCaptcha } from '~/app/api/system'
|
||||
|
||||
interface formData {
|
||||
captcha_code: string,
|
||||
|
||||
@ -2,10 +2,7 @@ import useAppStore from '~/stores/app'
|
||||
|
||||
export function t(message: string) {
|
||||
const i18n = useNuxtApp().$getI18n()
|
||||
let path = useAppStore().route
|
||||
// 处理部署后不知道为什么url会自动拼接上 / 的问题
|
||||
if (path != '/' && path.slice(-1) == '/') path = path.slice(0, -1)
|
||||
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
|
||||
const key = `${file}.${message}`
|
||||
const langKey = useAppStore().langKey
|
||||
const key = `${langKey}.${message}`
|
||||
return i18n.global.t(key) != key ? i18n.global.t(key) : i18n.global.t(message)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { sendSms } from '@/api/system'
|
||||
import { sendSms } from '~/app/api/system'
|
||||
|
||||
export function useSendSms() {
|
||||
const canGetCode = ref(true),
|
||||
|
||||
6
web/env/.env.dev
vendored
@ -4,17 +4,11 @@ VITE_APP_BASE_URL=''
|
||||
# 图片服务器地址
|
||||
VITE_IMG_DOMAIN=''
|
||||
|
||||
# 本地开发时站点id
|
||||
VITE_SITE_ID = 1
|
||||
|
||||
# 本地存储时token的参数名
|
||||
VITE_REQUEST_STORAGE_TOKEN_KEY='webToken'
|
||||
|
||||
# 请求时header中token的参数名
|
||||
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
||||
|
||||
# 请求时header中站点的参数名
|
||||
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
||||
|
||||
# 请求时header中来源场景的参数名
|
||||
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
|
||||
6
web/env/.env.product
vendored
@ -4,17 +4,11 @@ VITE_APP_BASE_URL=''
|
||||
# 图片服务器地址
|
||||
VITE_IMG_DOMAIN=''
|
||||
|
||||
# 本地开发时站点id
|
||||
VITE_SITE_ID = 1
|
||||
|
||||
# 本地存储时token的参数名
|
||||
VITE_REQUEST_STORAGE_TOKEN_KEY='webToken'
|
||||
|
||||
# 请求时header中token的参数名
|
||||
VITE_REQUEST_HEADER_TOKEN_KEY='token'
|
||||
|
||||
# 请求时header中站点的参数名
|
||||
VITE_REQUEST_HEADER_SITEID_KEY='site-id'
|
||||
|
||||
# 请求时header中来源场景的参数名
|
||||
VITE_REQUEST_HEADER_CHANNEL_KEY='channel'
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "文章"
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "文章"
|
||||
}
|
||||
@ -13,8 +13,6 @@
|
||||
"userAgreement": "用户协议",
|
||||
"privacyAgreement": "隐私协议",
|
||||
"protocolNotConfigured": "未配置协议",
|
||||
"siteClose": "站点已关闭",
|
||||
"noSite": "站点不存在",
|
||||
"request": {
|
||||
"unknownError": "未知错误",
|
||||
"400": "错误的请求",
|
||||
|
||||
@ -1,53 +1,51 @@
|
||||
<template>
|
||||
<div class="flex h-[220px] min-w-[1200px] bg-[#3F4045]">
|
||||
<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]">
|
||||
<span>友情链接:</span>
|
||||
<NuxtLink to="https://www.bt.cn">
|
||||
<span class="mr-[10px]">宝塔</span>|
|
||||
<span class="mr-[10px]">宝塔</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://www.oschina.net">
|
||||
<span class="mr-[10px]">开源中国</span>|
|
||||
<span class="mr-[10px]">开源中国</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://www.aliyun.com">
|
||||
<span class="mr-[10px]">阿里云</span>|
|
||||
<span class="mr-[10px]">阿里云</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://gitee.com/">
|
||||
<span class="mr-[10px]">码云Gitee</span>|
|
||||
<span class="mr-[10px]">码云Gitee</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://cloud.tencent.com/">
|
||||
<span class="mr-[10px]">腾讯云</span>|
|
||||
<span class="mr-[10px]">腾讯云</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="https://mp.weixin.qq.com">
|
||||
<span class="mr-[10px]">微信公众平台</span>|
|
||||
<span class="mr-[10px]">微信公众平台</span>|
|
||||
</NuxtLink>
|
||||
<NuxtLink to="http://www.thinkphp.cn">
|
||||
<span class="mr-[10px]">Thinkphp</span>
|
||||
<span class="mr-[10px]">Thinkphp</span>
|
||||
</NuxtLink>
|
||||
</p>
|
||||
<p class="text-center mt-[20px] text-[#999]">
|
||||
<p class="text-center mt-[20px] text-[#999]">
|
||||
<NuxtLink to="https://beian.miit.gov.cn/">
|
||||
<span>备案号:{{ copy }}</span>
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getCopyRight } from '@/api/system';
|
||||
import { getCopyRight } from '@/app/api/system';
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
const copy = ref();
|
||||
const getCopy = () => {
|
||||
getCopyRight({
|
||||
}).then(res => {
|
||||
getCopyRight({
|
||||
}).then(res => {
|
||||
copy.value = res.data.icp
|
||||
})
|
||||
})
|
||||
}
|
||||
getCopy()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -9,12 +9,12 @@
|
||||
</div>
|
||||
|
||||
<div class="mx-auto flex-shrink">
|
||||
<el-menu :default-active="appStore.route" class="h-full" mode="horizontal" :ellipsis="false" :router="true">
|
||||
<el-menu :default-active="route.path" class="h-full" mode="horizontal" :ellipsis="false" :router="true">
|
||||
<el-menu-item index="/" route="/">
|
||||
<span class="text-base mx-4">首页</span>
|
||||
<span></span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/article/list" route="/article/list">
|
||||
<el-menu-item index="/cms/article/list" route="/cms/article/list">
|
||||
<span class="text-base mx-4">文章</span>
|
||||
<span></span>
|
||||
</el-menu-item>
|
||||
@ -42,7 +42,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useMemberStore from '@/stores/member'
|
||||
import useAppStore from '@/stores/app'
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
const info = computed(() => memberStore.info)
|
||||
@ -52,7 +51,7 @@ const logoutFn = () => {
|
||||
navigateTo(`/auth/login`)
|
||||
}
|
||||
|
||||
const appStore = useAppStore()
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import useSystemStore from '~/stores/system'
|
||||
import Language from '~~/utils/language'
|
||||
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
const language = new Language(useNuxtApp().$getI18n())
|
||||
language.loadLocaleMessages(to.path, useSystemStore().lang)
|
||||
})
|
||||
@ -1,5 +1,6 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
import { loadEnv } from 'vite'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
|
||||
const envScript = (process.env as any).npm_lifecycle_script.split(' ')
|
||||
const envName = envScript[envScript.length - 1]
|
||||
@ -18,6 +19,14 @@ export default defineNuxtConfig({
|
||||
},
|
||||
vite: {
|
||||
envDir: '~/env',
|
||||
plugins: [
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: '__tla',
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
})
|
||||
]
|
||||
},
|
||||
ssr: false
|
||||
})
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"nuxt": "^3.4.1",
|
||||
"nuxt-windicss": "^2.6.0",
|
||||
"sass": "^1.60.0",
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
"vue-i18n": "^9.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -24,4 +25,4 @@
|
||||
"qrcode": "^1.5.1",
|
||||
"sass": "^1.60.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 '@/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 '@/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>
|
||||
@ -2,15 +2,13 @@ import { createI18n } from 'vue-i18n'
|
||||
|
||||
import zhCn from "~/lang/zh-cn/common.json";
|
||||
import en from "~/lang/en/common.json"
|
||||
import zhCnPages from "~/lang/zh-cn/pages.json";
|
||||
import enPages from "~/lang/en/pages.json"
|
||||
|
||||
export default defineNuxtPlugin((NuxtApp) => {
|
||||
const i18n = createI18n({
|
||||
globalInjection: true, //是否全局注入
|
||||
messages: {
|
||||
"zh-cn": Object.assign(zhCn, zhCnPages),
|
||||
"en": Object.assign(en, enPages)
|
||||
"zh-cn": zhCn,
|
||||
"en": en
|
||||
},
|
||||
silentFallbackWarn: true,
|
||||
silentTranslationWarn: true
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
interface App {
|
||||
route: string
|
||||
langKey: string
|
||||
}
|
||||
|
||||
const useAppStore = defineStore('app', {
|
||||
state: (): App => {
|
||||
return {
|
||||
route: ''
|
||||
langKey: ''
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getConfig } from '@/api/auth'
|
||||
import { getConfig } from '~/app/api/auth'
|
||||
|
||||
interface loginConfig {
|
||||
is_username: number | boolean,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getMemberInfo } from '@/api/member'
|
||||
import { logout } from '@/api/auth'
|
||||
import { getMemberInfo } from '~/app/api/member'
|
||||
import { logout } from '~/app/api/auth'
|
||||
|
||||
interface Member {
|
||||
token: string | null
|
||||
@ -21,6 +21,7 @@ const useMemberStore = defineStore('member', {
|
||||
await this.getMemberInfo()
|
||||
},
|
||||
async getMemberInfo() {
|
||||
if (!this.token) return
|
||||
await getMemberInfo()
|
||||
.then((res: any) => {
|
||||
this.info = res.data
|
||||
@ -30,6 +31,7 @@ const useMemberStore = defineStore('member', {
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
if (!this.token) return
|
||||
logout().then(() => {
|
||||
this.$reset()
|
||||
useCookie('token').value = null
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import storage from '@/utils/storage'
|
||||
import { getSiteInfo } from '@/api/system'
|
||||
|
||||
interface System {
|
||||
lang: string,
|
||||
@ -18,16 +17,7 @@ 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 })
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { nextTick } from 'vue'
|
||||
import useAppStore from '~/stores/app'
|
||||
|
||||
class Language {
|
||||
private i18n: any;
|
||||
@ -28,27 +29,41 @@ class Language {
|
||||
* @param locale
|
||||
* @returns
|
||||
*/
|
||||
public async loadLocaleMessages(path: string, locale: string) {
|
||||
public async loadLocaleMessages(app: string, path: string, locale: string) {
|
||||
try {
|
||||
const file = path == '/' ? 'index' : path.replace('/', '').replaceAll('/', '.')
|
||||
const file = path.replaceAll('/', '.')
|
||||
|
||||
if (this.loadLocale.includes(`${locale}/${file}`)) {
|
||||
if (this.loadLocale.includes(`${app}/${locale}/${file}`)) {
|
||||
return nextTick()
|
||||
}
|
||||
this.loadLocale.push(`${locale}/${file}`)
|
||||
|
||||
// 加载pages语言包
|
||||
if (!this.loadLocale.includes(`${app}/${locale}/pages`)) {
|
||||
// 引入语言包文件
|
||||
const pagesMessages = await import(`@/${app}/lang/${locale}/pages.json`)
|
||||
this.i18n.global.mergeLocaleMessage(locale, pagesMessages.default)
|
||||
this.loadLocale.push(`${app}/${locale}/pages`)
|
||||
}
|
||||
|
||||
this.loadLocale.push(`${app}/${locale}/${file}`)
|
||||
|
||||
// 引入语言包文件
|
||||
const messages = await import(`~/lang/${locale}/${file}.json`)
|
||||
const messages = await import(`@/${app}/lang/${locale}/${file}.json`)
|
||||
|
||||
let data: Record<string, string> = {}
|
||||
Object.keys(messages.default).forEach(key => {
|
||||
data[`${file}.${key}`] = messages.default[key]
|
||||
data[`${app}.${file}.${key}`] = messages.default[key]
|
||||
})
|
||||
|
||||
useAppStore().$patch(state => {
|
||||
state.langKey = `${app}.${file}`
|
||||
})
|
||||
|
||||
this.i18n.global.mergeLocaleMessage(locale, data)
|
||||
this.setI18nLanguage(locale)
|
||||
return nextTick()
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.setI18nLanguage(locale)
|
||||
return nextTick()
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@ class Http {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
||||
this.options.baseURL = runtimeConfig.public.VITE_APP_BASE_URL || `${location.origin}/api/`
|
||||
this.options.headers[runtimeConfig.public.VITE_REQUEST_HEADER_SITEID_KEY] = useCookie('siteId').value || runtimeConfig.public.VITE_SITE_ID
|
||||
this.options.headers[runtimeConfig.public.VITE_REQUEST_HEADER_CHANNEL_KEY] = 'pc'
|
||||
if (getToken()) this.options.headers[runtimeConfig.public.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||
}
|
||||
|
||||