新版本官网优化完成

This commit is contained in:
2025-02-15 02:53:56 +08:00
parent 3e84bbba59
commit 4c601b31a2
55 changed files with 3046 additions and 915 deletions

View File

@@ -95,9 +95,9 @@ export async function removeBatchCmsWebsite(data: (number | undefined)[]) {
/** /**
* 根据id查询网站信息记录表 * 根据id查询网站信息记录表
*/ */
export async function getCmsWebsite(id: number) { export async function getCmsWebsiteAll(id: number) {
const res = await request.get<ApiResult<CmsWebsite>>( const res = await request.get<ApiResult<CmsWebsite>>(
'https://cms-api.websoft.top/api/cms/cms-website/' + id '/cms/cms-website/all/' + id
); );
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
return res.data; return res.data;
@@ -120,7 +120,7 @@ export async function removeSiteInfoCache(key?: string) {
export async function pageCmsWebsiteAll(params: CmsWebsiteParam) { export async function pageCmsWebsiteAll(params: CmsWebsiteParam) {
const res = await request.get<ApiResult<PageResult<CmsWebsite>>>( const res = await request.get<ApiResult<PageResult<CmsWebsite>>>(
'https://cms-api.websoft.top/api/cms/cms-website/pageAll', '/cms/cms-website/pageAll',
{ {
params params
} }

View File

@@ -19,6 +19,16 @@ export interface CmsWebsite {
websiteDarkLogo?: string; websiteDarkLogo?: string;
// 网站类型 // 网站类型
websiteType?: string; websiteType?: string;
// 评分
rate?: number;
// 点赞数
likes?: number;
// 点击量
clicks?: number;
// 购买量
buys?: number;
// 下载安装
downloads?: number;
// 网站截图 // 网站截图
files?: string; files?: string;
// 网站关键词 // 网站关键词
@@ -106,5 +116,6 @@ export interface CmsWebsite {
export interface CmsWebsiteParam extends PageParam { export interface CmsWebsiteParam extends PageParam {
websiteId?: number; websiteId?: number;
status?: number; status?: number;
recommend?: number;
keywords?: string; keywords?: string;
} }

View File

@@ -25,7 +25,11 @@ export async function getSiteInfo(params: CmsWebsiteParam) {
*/ */
export async function getUserInfo(): Promise<User> { export async function getUserInfo(): Promise<User> {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const res = await request.get<ApiResult<User>>(config.public.apiServer + '/auth/user'); const res = await request.get<ApiResult<User>>(config.public.apiServer + '/auth/user',{
headers: {
TenantId: `${localStorage.getItem('TID_ADMIN')}`
}
});
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
return res.data; return res.data;
} }

View File

@@ -8,6 +8,7 @@ import type {
SmsCaptchaResult SmsCaptchaResult
} from './model'; } from './model';
import { SERVER_API_URL } from '~/config'; import { SERVER_API_URL } from '~/config';
import type {UserParam} from "~/api/system/user/model";
/** /**
* 登录 * 登录
@@ -49,18 +50,22 @@ export async function loginBySms(data: LoginParam) {
data data
); );
if (res.code === 0) { if (res.code === 0) {
console.log(res.data,'登录成功') return res.data;
// setToken(res.data.data?.access_token, data.remember); }
// if (res.data.data?.user) { return Promise.reject(new Error(res.message));
// const user = res.data.data?.user; }
// localStorage.setItem('TenantId', String(user.tenantId));
// localStorage.setItem('Phone', String(user.phone));
// localStorage.setItem('UserId', String(user.userId));
// localStorage.setItem('MerchantId', String(user.merchantId));
// localStorage.setItem('MerchantName', String(user.merchantName));
// }
return res.message; /**
* 用户注册
* @param data
*/
export async function register(data: UserParam) {
const res = await request.post<ApiResult<LoginResult>>(
SERVER_API_URL + '/register',
data
);
if (res.code === 0) {
return res.data;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
} }

View File

@@ -15,6 +15,8 @@ export interface LoginParam {
phone?: string; phone?: string;
// 短信验证码 // 短信验证码
code?: string; code?: string;
// 是否超级管理员
isSuperAdmin?: boolean;
} }
/** /**

View File

@@ -135,6 +135,7 @@ export interface UserParam extends PageParam {
type?: any; type?: any;
userId?: number; userId?: number;
username?: string; username?: string;
password?: string;
nickname?: string; nickname?: string;
realName?: string; realName?: string;
gradeId?: unknown; gradeId?: unknown;
@@ -144,12 +145,15 @@ export interface UserParam extends PageParam {
cityMate?: string; cityMate?: string;
sex?: string; sex?: string;
phone?: string; phone?: string;
email?: string;
code?: string;
status?: number; status?: number;
organizationId?: number; organizationId?: number;
parentId?: number; parentId?: number;
sexName?: string; sexName?: string;
roleId?: string; roleId?: string;
isAdmin?: number; isAdmin?: number;
isSuperAdmin?: boolean;
showProfile?: boolean; showProfile?: boolean;
isStaff?: boolean; isStaff?: boolean;
} }

View File

@@ -2329,7 +2329,6 @@ h3.tag a:hover {
.relate h4 { .relate h4 {
line-height: 30px; line-height: 30px;
border-bottom: 1px solid #0c6fcd; border-bottom: 1px solid #0c6fcd;
padding-left: 5px;
} }
#relate_p .img img { #relate_p .img img {

View File

@@ -0,0 +1 @@
<svg width="70" height="70" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="17.621%" y1="50%" x2="100%" y2="50%" id="a"><stop stop-color="#FFE2B8" offset="0%"/><stop stop-color="#FFCA7C" offset="100%"/></linearGradient><filter x="-10.7%" y="-10.7%" width="121.4%" height="121.4%" filterUnits="objectBoundingBox" id="c"><feGaussianBlur stdDeviation="6" in="SourceAlpha" result="shadowBlurInner1"/><feOffset dx="3" in="shadowBlurInner1" result="shadowOffsetInner1"/><feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0" in="shadowInnerInner1"/></filter><path id="b" d="m1291 377 70 70v-70z"/></defs><g transform="translate(-1291 -377)" fill="none" fill-rule="evenodd"><use fill="url(#a)" xlink:href="#b"/><use fill="#000" filter="url(#c)" xlink:href="#b"/></g></svg>

After

Width:  |  Height:  |  Size: 953 B

View File

@@ -1,23 +1,74 @@
<template> <template>
<div class="fr clearfix flex items-center"> <div class="fr clearfix flex items-center">
<!-- <div class="fl mr-5">--> <div class="fl mr-5">
<!-- <el-input v-model="keyword" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="onSearch"/>--> <el-input v-model="keywords" :placeholder="`${$t('searchKeywords')}`" :suffix-icon="Search" @change="onSearch"/>
<!-- </div>--> </div>
<div class="lang flex justify-center text-center items-center"> <!-- 未登录 -->
<div v-if="!token" class="lang flex justify-center text-center items-center">
<el-space> <el-space>
<nuxt-link to="https://oa.websoft.top/register" type="text" class="text-sm text-gray-500">注册</nuxt-link> <nuxt-link to="/passport/login?type=register" type="text" class="text-sm text-gray-500">注册</nuxt-link>
<el-divider direction="vertical"/> <el-divider direction="vertical"/>
<nuxt-link to="https://oa.websoft.top/passport/login" type="text" class="text-sm text-gray-500">登录</nuxt-link> <nuxt-link to="/passport/login" type="text" class="text-sm text-gray-500">登录</nuxt-link>
</el-space> </el-space>
</div> </div>
<!-- 已登录 -->
<div v-else>
<div class="header__right items-center pr-4 xl:pr-0 md:flex hidden">
<el-space class="sm:flex hidden" size="large">
<ClientOnly>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-space class="flex items-center cursor-pointer">
<el-avatar class="cursor-pointer" :src="user?.avatar" :size="30" />
<span>{{ user?.tenantName }}</span>
</el-space>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user"><nuxt-link to="/user">用户中心</nuxt-link></el-dropdown-item>
<el-dropdown-item command="password"><nuxt-link to="/user/password">修改密码</nuxt-link></el-dropdown-item>
<el-dropdown-item command="auth"><nuxt-link to="/user/auth">实名认证</nuxt-link></el-dropdown-item>
<el-dropdown-item divided command="order"><nuxt-link to="/user/order">我的订单</nuxt-link></el-dropdown-item>
<el-dropdown-item divided command="logOut"><nuxt-link to="/user/logout">退出登录</nuxt-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<template v-else>
<el-button type="primary" v-if="!token" @click="navigateTo(`/passport/login`)">登录/注册</el-button>
<!-- <el-button v-if="config.showLoginButton" circle :icon="ElIconUserFilled" @click="goLogin"></el-button>-->
</template>
</ClientOnly>
</el-space>
</div>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
const keyword = ref<string>(''); const token = useToken();
const user = useUser();
const keywords = ref<string>();
const onSearch = () => { const onSearch = () => {
window.location.href = `/search/${keyword.value}`; navigateTo(`/search/${keywords.value || '关键词不能为空!'}`)
}
function handleCommand(command: string) {
switch (command) {
case 'logOut':
logOut();
break;
default:
navigateTo('/user');
break;
}
}
function logOut() {
token.value = '';
localStorage.clear();
navigateTo('/passport/login')
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,6 +1,6 @@
<template> <template>
<!-- 自定义banner --> <!-- 自定义banner -->
<div class="banner m-auto relative sm:flex hidden-sm-and-down flex justify-center"> <div v-if="layout?.banner" class="banner m-auto relative sm:flex hidden-sm-and-down flex justify-center">
<el-image :src="layout?.banner" class="min-h-sm sm:h-auto w-full"></el-image> <el-image :src="layout?.banner" class="min-h-sm sm:h-auto w-full"></el-image>
<div class="banner-bar absolute top-0 w-full sm:flex hidden"> <div class="banner-bar absolute top-0 w-full sm:flex hidden">
<div class="banner-text py-12 md:w-screen-xl m-auto opacity-90 flex flex-col justify-center"> <div class="banner-text py-12 md:w-screen-xl m-auto opacity-90 flex flex-col justify-center">

View File

@@ -35,7 +35,7 @@ watch(
<template> <template>
<div class="relatenew relate"><h4>{{ $t('articleRelated')}}</h4> <div class="relatenew relate"><h4>{{ $t('articleRelated')}}</h4>
<div class="p-1"> <div class="py-1">
<ul id="relate_n" class="news_list clearfix"> <ul id="relate_n" class="news_list clearfix">
<li v-for="(item,index) in list" :key="index"><a :href="detail(item)" :title="item.title">{{ item.title }}</a></li> <li v-for="(item,index) in list" :key="index"><a :href="detail(item)" :title="item.title">{{ item.title }}</a></li>
</ul> </ul>

View File

@@ -54,7 +54,7 @@ const reload = () => {
<template> <template>
<div class="relateproduct relate"> <div class="relateproduct relate">
<h4>{{ $t('recentlyViewed') }}</h4> <h4>{{ $t('recentlyViewed') }}</h4>
<div class="p-1" v-if="type == 'article'"> <div class="py-1" v-if="type == 'article'">
<ul id="relate_n" class="news_list clearfix"> <ul id="relate_n" class="news_list clearfix">
<li v-for="(item,index) in list" :key="index"><a :href="detail(item)" :title="item.title">{{ item.title }}</a></li> <li v-for="(item,index) in list" :key="index"><a :href="detail(item)" :title="item.title">{{ item.title }}</a></li>
</ul> </ul>

View File

@@ -10,19 +10,18 @@ const props = withDefaults(
</script> </script>
<template> <template>
<div class="text-[16px]" v-html="data"></div> <div v-html="data"></div>
</template> </template>
<style lang="scss"> <style lang="scss">
.content { .content {
padding-top: 15px;
overflow: hidden; overflow: hidden;
color: #333131; color: #333131;
} }
.content p{ .content p{
padding: 0; padding: 0;
line-height: 2.2rem; line-height: 2.2rem;
text-indent: 2rem; //text-indent: 2rem;
} }
.content img{ .content img{
padding: 8px 0; padding: 8px 0;

View File

@@ -29,14 +29,24 @@ nextArticle.value = await getNext({
</script> </script>
<template> <template>
<div class="page flex flex-col"> <div class="flex justify-between my-3 gap-4">
<span class="text-left"> <nuxt-link :to="previousArticle ? detail(previousArticle) : ''" class="w-[50%] flex flex-col justify-center p-4 border-1 border-solid border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50">
{{ $t('previous') }}<a :href="detail(previousArticle)">{{ previousArticle?.title }}</a> <span class="text-lg text-gray-800">{{ previousArticle?.title || '没有了' }}</span>
</span> <span class="text-gray-400">{{ $t('previous') }}</span>
<span class="text-left"> </nuxt-link>
{{ $t('next') }}<a :href="detail(nextArticle)">{{ nextArticle?.title }}</a> <nuxt-link :to="nextArticle ? detail(nextArticle) : ''" class="w-[50%] flex flex-col justify-center p-4 border-1 border-solid border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50">
</span> <span class="text-lg text-gray-800">{{ nextArticle?.title || '没有了' }}</span>
<span class="text-gray-400">{{ $t('next') }}</span>
</nuxt-link>
</div> </div>
<!-- <div class="page flex flex-col">-->
<!-- <span class="text-left">-->
<!-- {{ $t('previous') }}<a :href="detail(previousArticle)">{{ previousArticle?.title }}</a>-->
<!-- </span>-->
<!-- <span class="text-left">-->
<!-- {{ $t('next') }}<a :href="detail(nextArticle)">{{ nextArticle?.title }}</a>-->
<!-- </span>-->
<!-- </div>-->
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -9,6 +9,7 @@ const props = withDefaults(
data?: any; data?: any;
total?: number; total?: number;
size?: ComponentSize; size?: ComponentSize;
pageSize?: number;
}>(), }>(),
{} {}
); );
@@ -28,7 +29,7 @@ const mobile = useIsMobile();
const where = reactive<CmsArticleParam>({ const where = reactive<CmsArticleParam>({
keywords: '', keywords: '',
page: 1, page: 1,
limit: 20, limit: props.pageSize || 12,
status: 0, status: 0,
parentId: undefined, parentId: undefined,
categoryId: undefined, categoryId: undefined,

52
components/SearchBar.vue Normal file
View File

@@ -0,0 +1,52 @@
<template>
<el-space class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search"/>
</el-space>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
const i18n = useI18n();
const props = withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsArticle;
value?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: any): void
}>()
// 搜索表单
const where = reactive<any>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const reload = () => {
navigateTo(`/search/${where.keywords}`)
}
</script>
<style scoped lang="less">
.rounded-avatar {
border-radius: 30px;
}
.rounded-avatar-xs {
border-radius: 20px;
}
</style>

View File

@@ -56,7 +56,12 @@ export const useProductAffix = () =>
export const useSysDomain = () => useState('SysDomain', () => ''); export const useSysDomain = () => useState('SysDomain', () => '');
// 登录凭证 // 登录凭证
export const useToken = () => useState('token', () => ''); export const useToken = () => useState('token', () => {
if(localStorage.getItem('token')){
return localStorage.getItem('token')
}
return ''
});
// 用户信息 // 用户信息
export const useUser = () => export const useUser = () =>

View File

@@ -127,7 +127,7 @@
"label": "标签", "label": "标签",
"seeMore": "查看更多", "seeMore": "查看更多",
"onlineInquiry": "在线询价", "onlineInquiry": "在线询价",
"searchKeywords": "搜索关键词", "searchKeywords": "站内搜索",
"tel": "电话", "tel": "电话",
"map": "地图", "map": "地图",
"message": "留言" "message": "留言"

View File

@@ -6,13 +6,8 @@
<template #content> <template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span> <span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template> </template>
<template #extra>
<div class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</div>
</template>
<el-row :gutter="24" id="container" class="clearfix"> <el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left"> <el-col v-for="(item,index) in list" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/detail/${item.articleId}.html`)"> <el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/detail/${item.articleId}.html`)">
<el-image <el-image
:src="item.image" :src="item.image"
@@ -24,9 +19,9 @@
{{ item.title }} {{ item.title }}
</div> </div>
</div> </div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between"> <!-- <div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">-->
<div class="text-gray-500 line-clamp-2">{{ item.comments }}</div> <!-- <div class="text-gray-500 line-clamp-2">{{ item.comments }}</div>-->
</div> <!-- </div>-->
<div class="button-group flex justify-between items-center mt-3 text-sm"> <div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end"> <el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div> <div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
@@ -117,7 +112,6 @@ const reload = async () => {
const goBack = () => { const goBack = () => {
router.back(); // 返回上一页 router.back(); // 返回上一页
// window.history.back();
} }
/** /**

173
pages/case/index.vue Normal file
View File

@@ -0,0 +1,173 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title || '客户案例' }} </span>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/detail/${item.articleId}.html`)">
<el-image
:src="item.image"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.title }}
</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500 line-clamp-2">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
{{ dayjs(item.createTime).format('YYYY-MM-DD') }}
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-page-header>
<Pagination :total="total" @done="search" />
</div>
</template>
<script setup lang="ts">
import Banner from "@/components/Banner.vue";
import { ArrowLeft,View, Search } from '@element-plus/icons-vue'
import { ElNotification as notify } from 'element-plus'
import {useConfigInfo, useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import type { ComponentSize } from 'element-plus'
import { ElNotification } from 'element-plus'
import type { TabsPaneContext } from 'element-plus'
import { h } from 'vue'
import dayjs from "dayjs";
import {detail, getNavIdByParamsId, navTo, paramsId} from "~/utils/common";
import Left from "~/components/Left.vue";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
const route = useRoute();
const router = useRouter();
const navId = ref();
// 页面信息
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
const activeName = ref('2839');
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
model: 'case',
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const goBack = () => {
router.back();
}
// 加载页面数据
const reload = async () => {
pageCmsArticle(where).then(response => {
if(response){
total.value = response.count;
list.value = response.list;
}
})
// seo
// useSeoMeta({
// description: data.comments || data.title,
// keywords: data.title,
// titleTemplate: `${data?.title}` + ' - %s',
// })
// 二级栏目分类
// listCmsNavigation({}).then(navigation => {
// category.value = navigation;
// // 加载文章列表
// if(data.parentId == 0 && category.value.length > 0){
// where.parentId = page.value.navigationId;
// }else {
// where.categoryId = page.value.navigationId;
// }
//
// })
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
const value = ref('')
const options = [
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
watch(
() => route.params.id,
() => {
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.right .content img{
width: auto !important;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
</style>

View File

@@ -6,40 +6,61 @@
<template #content> <template #content>
<span class="font-600 mr-3"> 文章详情 </span> <span class="font-600 mr-3"> 文章详情 </span>
</template> </template>
<template #extra>
<div class="flex items-center"> <el-card shadow="hover" class=" my-5 px-2">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</div>
</template>
<el-card shadow="hover" class=" my-5">
<!-- 新闻详细 --> <!-- 新闻详细 -->
<div class="news_detail bg-white"> <div class=" bg-white">
<h1 class="pt-5 text-2xl">{{ form.title }}</h1> <h1 class="pt-5 text-3xl">{{ form.title }}</h1>
<div class="clearfix"> <div class="flex items-center justify-between py-4">
<h3 class=" text-gray-400"> <el-space size="large" class="text-gray-400">
{{ $t('createTime') }}<span>{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span> <span>{{ $t('createTime') }}{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span>
{{ $t('author') }}<span>{{ form.nickname }}</span> <span>{{ $t('author') }}{{ form.author }}</span>
{{ $t('click') }}<span>{{ getViews(form) }}</span> <span>{{ $t('click') }}{{ getViews(form) }}</span>
</h3> <span v-if="form.source">文章来源{{ form.source }}</span>
<div class="share"> </el-space>
<!-- Baidu Button BEGIN --> <!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox"> <el-space class="flex items-center">
<a href="#" class="bds_more" data-cmd="more"></a> <!-- <a href="#" class="bds_more" data-cmd="more">朋友圈</a>-->
<a href="#" class="bds_qzone" data-cmd="qzone"></a> <!-- <a href="#" class="bds_qzone" data-cmd="qzone">QQ空间</a>-->
<a href="#" class="bds_tsina" data-cmd="tsina"></a> <!-- <a href="#" class="bds_tsina" data-cmd="tsina">新浪</a>-->
<a href="#" class="bds_tqq" data-cmd="tqq"></a> <!-- <a href="#" class="bds_tqq" data-cmd="tqq">腾讯</a>-->
<a href="#" class="bds_renren" data-cmd="renren"></a> <!-- <a href="#" class="bds_weixin" data-cmd="weixin">微信</a>-->
<a href="#" class="bds_weixin" data-cmd="weixin"></a> </el-space>
</div> </div>
<!-- 文章摘要 -->
<div v-if="form.comments" class="screen-item bg-orange-50 text-gray-600 p-3 text-lg">
{{ form.comments }}
</div> </div>
<!-- 文章附件 -->
<div class="screen-item" v-if="form.files">
<div class="text-gray-400 py-3 text-sm">图片附件</div>
<el-scrollbar>
<div class="flex">
<el-image
v-for="(item,index) in JSON.parse(form.files)"
:key="index"
class="scrollbar-item w-[240px] max-h-[180px] mr-4 mb-4"
:src="item"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="srcList"
:initial-index="4"
fit="contain"
/>
</div>
</el-scrollbar>
</div> </div>
<!-- 内容组件 --> <!-- 内容组件 -->
<Content class="content" :data="form.content" /> <Content class="content text-lg py-5" :data="form.content" />
<NextArticle :articleId="articleId" />
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3> <h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" /> <Tags :data="form.tags" />
<NextArticle :articleId="articleId" />
</div> </div>
</el-card>
<!-- 最近浏览 --> <!-- 最近浏览 -->
<CmsArticleRecently :data="form" type="article" /> <CmsArticleRecently :data="form" type="article" />
@@ -49,7 +70,6 @@
<CmsArticleRelated :data="form" /> <CmsArticleRelated :data="form" />
</div> </div>
</el-card>
</el-page-header> </el-page-header>
</div> </div>
</template> </template>
@@ -61,9 +81,7 @@ import {getNavIdByParamsId, getViews, locationUrl, navTo, paramsId} from "~/util
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model"; import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import useFormData from "~/utils/use-form-data"; import useFormData from "~/utils/use-form-data";
import dayjs from "dayjs"; import dayjs from "dayjs";
import Banner from "@/components/Banner.vue";
import type {Layout} from "~/api/layout/model"; import type {Layout} from "~/api/layout/model";
import Left from "~/components/Left.vue";
import { import {
getCmsArticle getCmsArticle
} from "~/api/cms/cmsArticle"; } from "~/api/cms/cmsArticle";
@@ -71,8 +89,6 @@ import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import CmsArticleRecently from "~/components/CmsRecently.vue"; import CmsArticleRecently from "~/components/CmsRecently.vue";
import Tags from "~/components/Tags.vue"; import Tags from "~/components/Tags.vue";
import Content from "~/components/Content.vue"; import Content from "~/components/Content.vue";
import CmsArticleList from "~/components/CmsArticleList.vue";
import PageBanner from "~/pages/item/components/PageBanner.vue";
// 引入状态管理 // 引入状态管理
const route = useRoute(); const route = useRoute();
@@ -83,6 +99,7 @@ const i18n = useI18n();
const breadcrumb = ref<BreadcrumbItem>(); const breadcrumb = ref<BreadcrumbItem>();
const showPassword = ref<boolean>(); const showPassword = ref<boolean>();
const category = ref<CmsNavigation[]>([]); const category = ref<CmsNavigation[]>([]);
const srcList = ref<any[]>([]);
// 配置信息 // 配置信息
const {form, assignFields} = useFormData<CmsArticle>({ const {form, assignFields} = useFormData<CmsArticle>({
@@ -182,6 +199,13 @@ const reload = async () => {
await getCmsArticle(articleId.value).then(data => { await getCmsArticle(articleId.value).then(data => {
assignFields(data) assignFields(data)
layout.value.banner = data?.banner; layout.value.banner = data?.banner;
// 应用截图
if(data.files){
const imgArr = JSON.parse(data.files);
imgArr.map((item: any) => {
srcList.value.push(item)
})
}
// 二级栏目分类 // 二级栏目分类
if (data.parentId) { if (data.parentId) {
listCmsNavigation({parentId: data.parentId}).then(list => { listCmsNavigation({parentId: data.parentId}).then(list => {
@@ -223,4 +247,14 @@ watch(
<style lang="scss"> <style lang="scss">
.scrollbar-flex-content {
display: flex;
}
.scrollbar-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
</style> </style>

137
pages/docs/[id].vue Normal file
View File

@@ -0,0 +1,137 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/detail/${item.articleId}.html`)">
<el-image
:src="item.image"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex min-h-[56px]">
{{ item.title }}
</div>
</div>
<!-- <div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">-->
<!-- <div class="text-gray-500 line-clamp-2">{{ item.comments }}</div>-->
<!-- </div>-->
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
<el-avatar v-if="item.avatar" size="small" :src="`${item.avatar}`" />
{{ dayjs(item.createTime).format('YYYY-MM-DD') }}
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-page-header>
<Pagination :total="total" @done="search" />
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import {getNavIdByParamsId, getViews, paramsId} from "~/utils/common";
import dayjs from "dayjs";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import Banner from "~/components/Banner.vue";
const route = useRoute();
const router = useRouter();
const navId = ref();
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref<number>(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
// 加载页面数据
const reload = async () => {
getCmsNavigation(navId.value).then(data => {
// 获取栏目信息
page.value = data
layout.value.banner = data.banner;
// 设置页面标题
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(categoryData => {
category.value = categoryData;
console.log(categoryData)
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = data.navigationId;
}else {
where.categoryId = data.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}).catch(err => {
console.log(err,'加载失败...')
})
}
const goBack = () => {
router.back(); // 返回上一页
// window.history.back();
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>

123
pages/docs/index.vue Normal file
View File

@@ -0,0 +1,123 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 文档中心 </span>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-white cursor-pointer" @click="navigateTo(`/docs/${item.navigationId}.html`)">
<div class="flex-1 px-4 py-5 sm:p-4 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col items-center gap-1.5">
<el-avatar
:src="item.icon" shape="square" :size="100" style="background-color: white;"/>
<div class="flex-1 text-lg cursor-pointer flex flex-col text-left py-4">
{{ item.title }}
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import {getNavIdByParamsId, getViews, paramsId} from "~/utils/common";
import dayjs from "dayjs";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import Banner from "~/components/Banner.vue";
const route = useRoute();
const router = useRouter();
const navId = ref();
const list = ref<CmsNavigation[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref<number>(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
// 加载页面数据
const reload = async () => {
listCmsNavigation({model: 'docs'}).then(data => {
console.log(data,'123.')
list.value = data;
// 获取栏目信息
page.value = data
layout.value.banner = data.banner;
// 设置页面标题
useSeoMeta({
description: '文档中心',
keywords: '文档中心',
titleTemplate: `文档中心` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(categoryData => {
category.value = categoryData;
console.log(categoryData)
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = data.navigationId;
}else {
where.categoryId = data.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
// list.value = response?.list;
}
})
})
}).catch(err => {
console.log(err,'加载失败...')
})
}
const goBack = () => {
router.back(); // 返回上一页
// window.history.back();
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.params.id,
() => {
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,41 +1,26 @@
<!-- 文章详情 -->
<template> <template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 产品详情 </span>
</template>
<template #extra>
<div class="h-[32px]"></div>
</template>
<PageBanner :form="page" @done="reload"/> <PageBanner :form="page" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3"> <el-divider />
<el-row :gutter="24"> <AppInfo :form="form" />
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="mb-5"> <div class="screen-item my-6">
<el-descriptions title="参数信息" :column="2" border> <el-descriptions title="截屏" />
<el-descriptions-item :span="2" label="产品名称">{{page.title}}</el-descriptions-item> <el-scrollbar>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.title}}</span></el-descriptions-item> <div class="flex" v-if="form.files">
<el-descriptions-item v-if="form.isBuy" label="插件ID"><span class="text-orange-500">{{form.menuId || '-'}}</span></el-descriptions-item>
<el-descriptions-item label="控制台"><a class="cursor-pointer" @click="openUrl(`https://${form.domain}`)">{{form.domain}}</a></el-descriptions-item>
<el-descriptions-item v-for="(item,index) in form.parameters" :key="index" :label="item.name">{{ item.value }}</el-descriptions-item>
</el-descriptions>
<template v-if="form.accounts && form.accounts.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="登录账号" :column="1" border>
<template v-for="(item,index) in form.accounts" :key="index">
<el-descriptions-item :label="item.type" v-if="item.account">
还没有账号? <el-button type="text" @click="openSpmUrl(`/passport/regis`)">立即注册</el-button>
</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template v-if="form.gits && form.gits.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="代码仓库" :column="1" border>
<el-descriptions-item v-for="(item,index) in form.gits" :key="index" :label="item.title">
<el-input v-model="item.domain" readonly />
</el-descriptions-item>
</el-descriptions>
</template>
<template v-if="form.files && form.files.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="图文详情" />
<div v-for="(item,index) in JSON.parse(form.files)" :key="index" class="text item">
<el-image <el-image
:src="item" v-for="(item,index) in JSON.parse(form.files)"
:key="index"
class="scrollbar-item w-[240px] max-h-[625px] mr-4 mb-3"
:src="item.url"
:zoom-rate="1.2" :zoom-rate="1.2"
:max-scale="7" :max-scale="7"
:min-scale="0.2" :min-scale="0.2"
@@ -44,158 +29,147 @@
fit="contain" fit="contain"
/> />
</div> </div>
</template> </el-scrollbar>
<template v-if="form.content"> <p v-html="form?.content || '介绍'" class="content"></p>
<p v-html="form.content" class="content"></p> </div>
</template>
</el-card> <el-divider />
<!-- 产品评论 --> <!-- 评分及评价 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" /> <Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
</el-col> <div class="h-[100px]"></div>
<el-col :span="6" :xs="24"> </el-page-header>
<el-card shadow="hover" class="mb-5">
<template #header>
<div class="card-header font-bold text-xl">
<span>推荐产品</span>
</div>
</template>
<!-- <el-space class="flex items-center">-->
<!-- <div class="avatar">-->
<!-- <el-avatar :size="55" :src="form.image"/>-->
<!-- </div>-->
<!-- <div class="flex flex-col">-->
<!-- <span class="font-bold text-lg text-gray-600">{{ form.title }}</span>-->
<!-- <span class="text-gray-400 pb-1 line-clamp-2">{{ form.comments }}</span>-->
<!-- </div>-->
<!-- </el-space>-->
</el-card>
</el-col>
</el-row>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {Cpu,Download,Star,Coin,Tickets} from '@element-plus/icons-vue' import { ArrowLeft,View, Monitor, Search,Cpu, Platform, Avatar } from '@element-plus/icons-vue'
import type {ApiResult, PageResult} from "~/api"; import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import {useLayout, usePage, useWebsite} from "~/composables/configState"; import {usePage} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common"; import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data"; import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue'; import PageBanner from './components/PageBanner.vue';
import AppInfo from './components/AppInfo.vue';
import Comments from './components/Comments.vue'; import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model"; import type {CompanyComment} from "~/api/system/companyComment/model";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation"; import {getCmsWebsiteAll} from "~/api/cms/cmsWebsite";
import {getCmsArticle, pageCmsArticle} from "~/api/cms/cmsArticle"; import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
// 引入状态管理 // 引入状态管理
const route = useRoute(); const route = useRoute();
const router = useRouter();
const page = usePage(); const page = usePage();
const layout = useLayout();
const category = ref<CmsNavigation[]>([]);
const i18n = useI18n();
const total = ref(0);
const list = ref<CmsArticle[]>([]);
const website = useWebsite();
const breadcrumb = ref<BreadcrumbItem>();
const comments = ref<CompanyComment[]>([]); const comments = ref<CompanyComment[]>([]);
const commentsTotal = ref(0); const commentsTotal = ref(0);
const commentsPage = ref(1); const commentsPage = ref(1);
const navId = ref(); const navId = ref();
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]); const srcList = ref<any[]>([]);
// 配置信息 // 配置信息
const {form, assignFields} = useFormData<CmsArticle>({ const {form, assignFields} = useFormData<CmsWebsite>({
// 文章id // 站点ID
articleId: undefined, websiteId: undefined,
// 文章模型 // 网站名称
model: undefined, websiteName: undefined,
// 文章标题 // 网站标识
title: undefined, websiteCode: undefined,
// 分类类型 // 网站LOGO
type: undefined, websiteIcon: undefined,
// 展现方式 // 网站LOGO
showType: undefined, websiteLogo: undefined,
// 文章类型 // 网站LOGO(深色模式)
categoryId: undefined, websiteDarkLogo: undefined,
// 文章分类 // 网站类型
categoryName: undefined, websiteType: undefined,
parentId: undefined, // 评分
// 封面图 rate: undefined,
image: undefined, // 点赞数
// 附件
files: undefined,
// 附件
fileList: [],
// 缩列图
thumbnail: undefined,
// 视频地址
video: undefined,
// 上传的文件类型
accept: undefined,
// 来源
source: undefined,
// 标签
tags: undefined,
// 文章内容
content: undefined,
// 虚拟阅读量
virtualViews: undefined,
// 实际阅读量
actualViews: undefined,
// 访问权限
permission: undefined,
// 访问密码
password: undefined,
password2: undefined,
// 用户ID
userId: undefined,
// 用户昵称
nickname: undefined,
// 账号
username: undefined,
// 用户头像
// userAvatar: undefined,
author: undefined,
// 所属门店ID
shopId: undefined,
//
likes: undefined, likes: undefined,
// 排序 // 访问量
sortNumber: undefined, clicks: undefined,
// 下载量
downloads: undefined,
// 网站截图
files: undefined,
// 网站关键词
keywords: undefined,
// 域名前缀
prefix: undefined,
// 绑定域名
domain: undefined,
// 全局样式
style: undefined,
// 后台管理地址
adminUrl: undefined,
// 应用版本 10免费版 20专业版 30永久授权
version: undefined,
// 服务到期时间
expirationTime: undefined,
// 模版ID
templateId: undefined,
// 行业类型(父级)
industryParent: undefined,
// 行业类型(子级)
industryChild: undefined,
// 企业ID
companyId: undefined,
// 所在国家
country: undefined,
// 所在省份
province: undefined,
// 所在城市
city: undefined,
// 所在辖区
region: undefined,
// 经度
longitude: undefined,
// 纬度
latitude: undefined,
// 街道地址
address: undefined,
// 联系电话
phone: undefined,
// 电子邮箱
email: undefined,
// ICP备案号
icpNo: undefined,
// 公安备案
policeNo: undefined,
// 备注 // 备注
comments: undefined, comments: undefined,
// 状态 // 是否推荐
recommend: undefined,
// 运行状态
running: undefined,
// 状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停
status: undefined, status: undefined,
// 维护说明
statusText: undefined,
// 关闭说明
statusClose: undefined,
// 状态图标
statusIcon: undefined,
// 全局样式
styles: undefined,
content: undefined,
// 排序号
sortNumber: undefined,
// 用户ID
userId: undefined,
// 是否删除, 0否, 1是
deleted: undefined,
// 租户id
tenantId: undefined,
// 创建时间 // 创建时间
createTime: undefined, createTime: undefined,
// 更新时间 // 修改时间
updateTime: undefined, updateTime: undefined,
// 租户ID // 网站配置
tenantId: undefined, config: undefined,
// 租户名称 topNavs: undefined,
tenantName: undefined, bottomNavs: undefined,
// 租户logo loginUser: undefined
logo: undefined,
// 详情页路径
detail: undefined
});
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
}); });
const doComments = async (page: any) => { const doComments = async (page: any) => {
@@ -219,40 +193,37 @@ const reloadComments = async () => {
} }
} }
const goBack = () => {
router.back();
}
// 读取导航详情 // 读取导航详情
const reload = async () => { const reload = async () => {
getCmsArticle(navId.value).then(data => { getCmsWebsiteAll(navId.value).then(data => {
// 获取栏目信息 // 获取栏目信息
page.value = data
assignFields(data) assignFields(data)
layout.value.banner = data.banner; page.value = {
image: data.websiteLogo,
title: data.websiteName,
categoryName: '应用市场',
...data,
}
// 应用截图
if(data.files){
const imgArr = JSON.parse(data.files);
imgArr.map((item: any) => {
srcList.value.push(item.url)
})
}
// 设置页面标题 // 设置页面标题
useSeoMeta({ useSeoMeta({
description: data.comments || data.title, description: data.comments || data.websiteName,
keywords: data.title, keywords: data.websiteName,
titleTemplate: `${data?.title}` + ' - %s', titleTemplate: `${data?.websiteName}` + ' - %s',
}) })
// 二级栏目分类
// listCmsNavigation({
// parentId: data.parentId == 0 ? data.navigationId : data.parentId
// }).then(categoryData => {
// category.value = categoryData;
// // 加载文章列表
// if(data.parentId == 0 && category.value.length > 0){
// where.parentId = data.navigationId;
// }else {
// where.categoryId = data.navigationId;
// }
// pageCmsArticle(where).then(response => {
// if(response){
// total.value = response?.count;
// list.value = response?.list;
// }
// })
// })
}).catch(err => { }).catch(err => {
console.log(err,'加载失败...') console.log(err,'加载失败...')
}) })
@@ -275,4 +246,14 @@ watch(
height: auto !important; height: auto !important;
} }
} }
.scrollbar-flex-content {
display: flex;
}
.scrollbar-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
</style> </style>

View File

@@ -0,0 +1,82 @@
<template>
<div v-if="form" class="app-info flex justify-around items-center">
<div class="item text-center">
<div class="rate text-gray-400">评分</div>
<div class="text-2xl font-bold">3.1</div>
<el-rate v-model="form.rate" disabled size="small"/>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center flex flex-col items-center">
<div class="text-gray-400">插件ID</div>
<el-icon size="24" class="py-1"><Cpu /></el-icon>
<span class="text-gray-500">{{ form.websiteCode }}</span>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<nuxt-link class="item text-center flex flex-col items-center">
<div class="text-gray-400">类型</div>
<el-icon size="24" class="py-1"><Monitor /></el-icon>
<span class="text-gray-500">{{ '小程序' }}</span>
</nuxt-link>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<nuxt-link :to="`https://${form.tenantId}.wsdns.cn`" class="item text-center flex flex-col items-center">
<div class="text-gray-400">开发者</div>
<el-icon size="24" class="py-1"><Avatar /></el-icon>
<span class="text-gray-500">{{'WebSoft Inc.'}}</span>
</nuxt-link>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center flex flex-col items-center">
<div class="text-gray-400">下载次数</div>
<!-- <div>#<span class="text-2xl font-bold">13</span></div>-->
<el-icon size="24" class="py-1"><Download /></el-icon>
<span class="text-gray-500">{{ form.downloads }}</span>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center">
<div class="text-gray-400">大小</div>
<div class="text-2xl font-bold">26</div>
<span class="text-gray-400">MB</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View, Menu, Search, Cpu,Monitor, Download, Platform, Avatar } from '@element-plus/icons-vue'
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
import {getTenantIdByDomain} from "~/api/cms/cmsDomain";
import {listTenant} from "~/api/system/tenant";
const i18n = useI18n();
const props = withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsWebsite;
value?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: any): void
}>()
// 搜索表单
const where = reactive<any>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const reload = () => {
}
reload();
</script>
<style scoped lang="less">
</style>

View File

@@ -1,4 +1,10 @@
<template> <template>
<el-descriptions title="评分及评价">
<template #extra>
<el-button type="text" @click="onComplaint">投诉</el-button>
<el-button type="text" @click="onComments">发表评论</el-button>
</template>
</el-descriptions>
<form <form
ref="formRef" ref="formRef"
:model="form" :model="form"
@@ -8,18 +14,7 @@
size="large" size="large"
status-icon status-icon
> >
<el-card shadow="hover" v-if="comments" class="mb-5"> <template v-if="comments && comments.length > 0">
<template #header>
<div class="card-header font-bold text-xl flex justify-between">
<span>评分和评价</span>
<div class="comments">
<el-button @click="onComplaint">投诉</el-button>
<el-button type="primary" @click="onComments">发表评论</el-button>
</div>
</div>
</template>
<template #default>
<template v-if="comments.length > 0">
<div class="w-full"> <div class="w-full">
<div v-for="(item,index) in comments" :key="index" <div v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3" class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
@@ -54,8 +49,7 @@
<template v-else> <template v-else>
暂无用户评论 暂无用户评论
</template> </template>
</template>
</el-card>
<!-- 发表评论 --> <!-- 发表评论 -->
<el-dialog <el-dialog
v-model="visible" v-model="visible"

View File

@@ -1,54 +1,23 @@
<template> <template>
<div class="banner m-auto relative sm:flex mt-15"> <div class="banner m-auto relative sm:flex">
<svg viewBox="0 0 1440 181" fill="none" xmlns="http://www.w3.org/2000/svg" <div class="md:w-screen-xl m-auto py-10">
class="pointer-events-none absolute w-full top-[-2px] transition-all text-green-5 flex-shrink-0 opacity-100 duration-[400ms] opacity-80 -z-10">
<mask id="path-1-inside-1_414_5526" fill="white">
<path d="M0 0H1440V181H0V0Z"></path>
</mask>
<path d="M0 0H1440V181H0V0Z" fill="url(#paint0_linear_414_5526)" fill-opacity="0.22"></path>
<path d="M0 2H1440V-2H0V2Z" fill="url(#paint1_linear_414_5526)" mask="url(#path-1-inside-1_414_5526)"></path>
<defs>
<linearGradient id="paint0_linear_414_5526" x1="720" y1="0" x2="720" y2="181" gradientUnits="userSpaceOnUse">
<stop stop-color="currentColor"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0"></stop>
</linearGradient>
<linearGradient id="paint1_linear_414_5526" x1="0" y1="90.5" x2="1440" y2="90.5" gradientUnits="userSpaceOnUse">
<stop stop-color="currentColor" stop-opacity="0"></stop>
<stop offset="0.395" stop-color="currentColor"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0"></stop>
</linearGradient>
</defs>
</svg>
<div class="md:w-screen-xl m-auto">
<Breadcrumb :data="form" :categoryName="form?.categoryName"/>
<div class="py-8 sm:py-16" _path="/templates" _dir="" _draft="false" _partial="false" _locale=""
_id="content:4.templates.yml" _type="yaml" _source="content" _file="4.templates.yml" _stem="4.templates"
_extension="yml">
<div class="gap-8 sm:gap-y-16 lg:items-center" v-if="form"> <div class="gap-8 sm:gap-y-16 lg:items-center" v-if="form">
<div class="w-full sm:px-0 px-4"> <div class="w-full sm:px-0 px-4">
<div class="flex flex-1"> <div class="flex flex-1">
<template v-if="form.image"> <template v-if="form.websiteLogo">
<el-image :src="form.image" shape="square" <el-image :src="form.websiteLogo" shape="square"
class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-4"/> class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-6"/>
<!-- <el-image :src="form.image" shape="square" :size="80"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</template> </template>
<div class="title flex flex-col"> <div class="title flex flex-col">
<h1 <h1
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-4xl"> class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-3xl">
<span v-if="form.title">{{ form.title }}</span> <span v-if="form.websiteName">{{ form.websiteName }}</span>
</h1> </h1>
<div class="my-1 text-sm text-gray-500 w-auto sm:max-w-3xl max-w-xs flex-1 dark:text-gray-400"> <div class="my-1 text-sm text-gray-500 w-auto sm:max-w-3xl max-w-xs flex-1 dark:text-gray-400">
{{ form?.comments || desc }} {{ form?.comments }}
</div> </div>
<!-- <a class="company-name text-sm my-1">--> <el-space class="btn">
<!-- {{ form.companyName || 'WebSoft Inc.' }}--> <nuxt-link :to="`https://${form.websiteCode}.websoft.top`"><el-button type="primary" round>控制台</el-button></nuxt-link>
<!-- </a>-->
<el-rate v-model="form.rate" disabled />
<div class="btn">
<el-space class="mt-4">
<el-button>产品控制台</el-button>
<el-button>帮助文档</el-button>
</el-space> </el-space>
</div> </div>
</div> </div>
@@ -56,16 +25,12 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {FullScreen} from '@element-plus/icons-vue'
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {Company} from "~/api/system/company/model"; import type {Company} from "~/api/system/company/model";
import type {CmsArticle} from "~/api/cms/cmsArticle/model"; import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
const token = useToken(); const token = useToken();
@@ -74,7 +39,7 @@ const props = withDefaults(
title?: string; title?: string;
desc?: string; desc?: string;
buyUrl?: string; buyUrl?: string;
form?: CmsArticle; form?: CmsWebsite;
value?: number; value?: number;
}>(), }>(),
{} {}
@@ -95,7 +60,7 @@ const onBuy = (item: Company) => {
if (!token.value || token.value == '') { if (!token.value || token.value == '') {
ElMessage.error('请先登录'); ElMessage.error('请先登录');
setTimeout(() => { setTimeout(() => {
openSpmUrl(`/product/create`, item, item.companyId) openUrl(`/product/create`, item, item.companyId)
}, 500) }, 500)
} }

View File

@@ -0,0 +1,45 @@
<template>
<el-space class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
const i18n = useI18n();
const props = withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsArticle;
value?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: any): void
}>()
// 搜索表单
const where = reactive<any>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const reload = () => {
navigateTo(`/search/${where.keywords}`)
}
</script>
<style scoped lang="less">
</style>

View File

@@ -1,41 +1,26 @@
<!-- 文章详情 -->
<template> <template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 产品详情 </span>
</template>
<template #extra>
<div class="h-[32px]"></div>
</template>
<PageBanner :form="page" @done="reload"/> <PageBanner :form="page" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3"> <el-divider />
<el-row :gutter="24"> <AppInfo :form="form" />
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="mb-5"> <div class="screen-item my-6">
<el-descriptions title="参数信息" :column="2" border> <el-descriptions title="截屏" />
<el-descriptions-item :span="2" label="产品名称">{{page.title}}</el-descriptions-item> <el-scrollbar>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.title}}</span></el-descriptions-item> <div class="flex" v-if="form.files">
<el-descriptions-item v-if="form.isBuy" label="插件ID"><span class="text-orange-500">{{form.menuId || '-'}}</span></el-descriptions-item>
<el-descriptions-item label="控制台"><a class="cursor-pointer" @click="openUrl(`https://${form.domain}`)">{{form.domain}}</a></el-descriptions-item>
<el-descriptions-item v-for="(item,index) in form.parameters" :key="index" :label="item.name">{{ item.value }}</el-descriptions-item>
</el-descriptions>
<template v-if="form.accounts && form.accounts.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="登录账号" :column="1" border>
<template v-for="(item,index) in form.accounts" :key="index">
<el-descriptions-item :label="item.type" v-if="item.account">
还没有账号? <el-button type="text" @click="openSpmUrl(`/passport/regis`)">立即注册</el-button>
</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template v-if="form.gits && form.gits.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="代码仓库" :column="1" border>
<el-descriptions-item v-for="(item,index) in form.gits" :key="index" :label="item.title">
<el-input v-model="item.domain" readonly />
</el-descriptions-item>
</el-descriptions>
</template>
<template v-if="form.files && form.files.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="图文详情" />
<div v-for="(item,index) in JSON.parse(form.files)" :key="index" class="text item">
<el-image <el-image
:src="item" v-for="(item,index) in JSON.parse(form.files)"
:key="index"
class="scrollbar-item w-[240px] max-h-[625px] mr-4 mb-3"
:src="item.url"
:zoom-rate="1.2" :zoom-rate="1.2"
:max-scale="7" :max-scale="7"
:min-scale="0.2" :min-scale="0.2"
@@ -44,160 +29,149 @@
fit="contain" fit="contain"
/> />
</div> </div>
</template> </el-scrollbar>
<template v-if="form.content"> <p v-html="form?.content || '介绍'" class="content"></p>
<p v-html="form.content" class="content"></p> </div>
</template>
</el-card> <el-divider />
<!-- 产品评论 --> <!-- 评分及评价 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" /> <Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
</el-col> <div class="h-[100px]"></div>
<el-col :span="6" :xs="24"> </el-page-header>
<el-card shadow="hover" class="mb-5">
<template #header>
<div class="card-header font-bold text-xl">
<span>推荐产品</span>
</div>
</template>
<!-- <el-space class="flex items-center">-->
<!-- <div class="avatar">-->
<!-- <el-avatar :size="55" :src="form.image"/>-->
<!-- </div>-->
<!-- <div class="flex flex-col">-->
<!-- <span class="font-bold text-lg text-gray-600">{{ form.title }}</span>-->
<!-- <span class="text-gray-400 pb-1 line-clamp-2">{{ form.comments }}</span>-->
<!-- </div>-->
<!-- </el-space>-->
</el-card>
</el-col>
</el-row>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {Cpu,Download,Star,Coin,Tickets} from '@element-plus/icons-vue' import { ArrowLeft,View, Monitor, Search,Cpu, Platform, Avatar } from '@element-plus/icons-vue'
import type {ApiResult, PageResult} from "~/api"; import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import {useLayout, usePage, useWebsite} from "~/composables/configState"; import {usePage} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common"; import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data"; import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue'; import PageBanner from './components/PageBanner.vue';
import AppInfo from './components/AppInfo.vue';
import Comments from './components/Comments.vue'; import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model"; import type {CompanyComment} from "~/api/system/companyComment/model";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation"; import {getCmsWebsiteAll} from "~/api/cms/cmsWebsite";
import {getCmsArticle, pageCmsArticle} from "~/api/cms/cmsArticle";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model"; import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
import {getCmsWebsite} from "~/api/cms/cmsWebsite"; import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import Banner from "~/components/Banner.vue";
// 引入状态管理 // 引入状态管理
const route = useRoute(); const route = useRoute();
const router = useRouter();
const page = usePage(); const page = usePage();
const layout = useLayout();
const category = ref<CmsNavigation[]>([]);
const i18n = useI18n();
const total = ref(0);
const list = ref<CmsArticle[]>([]);
const website = useWebsite();
const breadcrumb = ref<BreadcrumbItem>();
const comments = ref<CompanyComment[]>([]); const comments = ref<CompanyComment[]>([]);
const commentsTotal = ref(0); const commentsTotal = ref(0);
const commentsPage = ref(1); const commentsPage = ref(1);
const navId = ref(); const navId = ref();
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]); const srcList = ref<any[]>([]);
// 配置信息 // 配置信息
const {form, assignFields} = useFormData<CmsWebsite>({ const {form, assignFields} = useFormData<CmsWebsite>({
// 文章id // 站点ID
articleId: undefined, websiteId: undefined,
// 文章模型 // 网站名称
model: undefined, websiteName: undefined,
// 文章标题 // 网站标识
title: undefined, websiteCode: undefined,
// 分类类型 // 网站LOGO
type: undefined, websiteIcon: undefined,
// 展现方式 // 网站LOGO
showType: undefined, websiteLogo: undefined,
// 文章类型 // 网站LOGO(深色模式)
categoryId: undefined, websiteDarkLogo: undefined,
// 文章分类 // 网站类型
categoryName: undefined, websiteType: undefined,
parentId: undefined, // 评分
// 封面图 rate: undefined,
image: undefined, // 点赞数
// 附件
files: undefined,
// 附件
fileList: [],
// 缩列图
thumbnail: undefined,
// 视频地址
video: undefined,
// 上传的文件类型
accept: undefined,
// 来源
source: undefined,
// 标签
tags: undefined,
// 文章内容
content: undefined,
// 虚拟阅读量
virtualViews: undefined,
// 实际阅读量
actualViews: undefined,
// 访问权限
permission: undefined,
// 访问密码
password: undefined,
password2: undefined,
// 用户ID
userId: undefined,
// 用户昵称
nickname: undefined,
// 账号
username: undefined,
// 用户头像
// userAvatar: undefined,
author: undefined,
// 所属门店ID
shopId: undefined,
//
likes: undefined, likes: undefined,
// 排序 // 访问量
sortNumber: undefined, clicks: undefined,
// 下载量
downloads: undefined,
// 网站截图
files: undefined,
// 网站关键词
keywords: undefined,
// 域名前缀
prefix: undefined,
// 绑定域名
domain: undefined,
// 全局样式
style: undefined,
// 后台管理地址
adminUrl: undefined,
// 应用版本 10免费版 20专业版 30永久授权
version: undefined,
// 服务到期时间
expirationTime: undefined,
// 模版ID
templateId: undefined,
// 行业类型(父级)
industryParent: undefined,
// 行业类型(子级)
industryChild: undefined,
// 企业ID
companyId: undefined,
// 所在国家
country: undefined,
// 所在省份
province: undefined,
// 所在城市
city: undefined,
// 所在辖区
region: undefined,
// 经度
longitude: undefined,
// 纬度
latitude: undefined,
// 街道地址
address: undefined,
// 联系电话
phone: undefined,
// 电子邮箱
email: undefined,
// ICP备案号
icpNo: undefined,
// 公安备案
policeNo: undefined,
// 备注 // 备注
comments: undefined, comments: undefined,
// 状态 // 是否推荐
recommend: undefined,
// 运行状态
running: undefined,
// 状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停
status: undefined, status: undefined,
// 维护说明
statusText: undefined,
// 关闭说明
statusClose: undefined,
// 状态图标
statusIcon: undefined,
// 全局样式
styles: undefined,
content: undefined,
// 排序号
sortNumber: undefined,
// 用户ID
userId: undefined,
// 是否删除, 0否, 1是
deleted: undefined,
// 租户id
tenantId: undefined,
// 创建时间 // 创建时间
createTime: undefined, createTime: undefined,
// 更新时间 // 修改时间
updateTime: undefined, updateTime: undefined,
// 租户ID // 网站配置
tenantId: undefined, config: undefined,
// 租户名称 topNavs: undefined,
tenantName: undefined, bottomNavs: undefined,
// 租户logo loginUser: undefined
logo: undefined,
// 详情页路径
detail: undefined
});
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
}); });
const doComments = async (page: any) => { const doComments = async (page: any) => {
@@ -221,9 +195,13 @@ const reloadComments = async () => {
} }
} }
const goBack = () => {
router.back();
}
// 读取导航详情 // 读取导航详情
const reload = async () => { const reload = async () => {
getCmsWebsite(navId.value).then(data => { getCmsWebsiteAll(navId.value).then(data => {
// 获取栏目信息 // 获取栏目信息
assignFields(data) assignFields(data)
page.value = { page.value = {
@@ -233,6 +211,14 @@ const reload = async () => {
...data, ...data,
} }
// 应用截图
if(data.files){
const imgArr = JSON.parse(data.files);
imgArr.map((item: any) => {
srcList.value.push(item.url)
})
}
// 设置页面标题 // 设置页面标题
useSeoMeta({ useSeoMeta({
description: data.comments || data.websiteName, description: data.comments || data.websiteName,
@@ -240,25 +226,6 @@ const reload = async () => {
titleTemplate: `${data?.websiteName}` + ' - %s', titleTemplate: `${data?.websiteName}` + ' - %s',
}) })
// 二级栏目分类
// listCmsNavigation({
// parentId: data.parentId == 0 ? data.navigationId : data.parentId
// }).then(categoryData => {
// category.value = categoryData;
// // 加载文章列表
// if(data.parentId == 0 && category.value.length > 0){
// where.parentId = data.navigationId;
// }else {
// where.categoryId = data.navigationId;
// }
// pageCmsArticle(where).then(response => {
// if(response){
// total.value = response?.count;
// list.value = response?.list;
// }
// })
// })
}).catch(err => { }).catch(err => {
console.log(err,'加载失败...') console.log(err,'加载失败...')
}) })
@@ -281,4 +248,14 @@ watch(
height: auto !important; height: auto !important;
} }
} }
.scrollbar-flex-content {
display: flex;
}
.scrollbar-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
</style> </style>

View File

@@ -0,0 +1,82 @@
<template>
<div v-if="form" class="app-info flex justify-around items-center">
<div class="item text-center">
<div class="rate text-gray-400">评分</div>
<div class="text-2xl font-bold">3.1</div>
<el-rate v-model="form.rate" disabled size="small"/>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center flex flex-col items-center">
<div class="text-gray-400">插件ID</div>
<el-icon size="24" class="py-1"><Cpu /></el-icon>
<span class="text-gray-500">{{ form.websiteCode }}</span>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<nuxt-link class="item text-center flex flex-col items-center">
<div class="text-gray-400">类型</div>
<el-icon size="24" class="py-1"><Monitor /></el-icon>
<span class="text-gray-500">{{ '小程序' }}</span>
</nuxt-link>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<nuxt-link :to="`https://${form.tenantId}.wsdns.cn`" class="item text-center flex flex-col items-center">
<div class="text-gray-400">开发者</div>
<el-icon size="24" class="py-1"><Avatar /></el-icon>
<span class="text-gray-500">{{'WebSoft Inc.'}}</span>
</nuxt-link>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center flex flex-col items-center">
<div class="text-gray-400">下载次数</div>
<!-- <div>#<span class="text-2xl font-bold">13</span></div>-->
<el-icon size="24" class="py-1"><Download /></el-icon>
<span class="text-gray-500">{{ form.downloads }}</span>
</div>
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
<div class="item text-center">
<div class="text-gray-400">大小</div>
<div class="text-2xl font-bold">26</div>
<span class="text-gray-400">MB</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View, Menu, Search, Cpu,Monitor, Download, Platform, Avatar } from '@element-plus/icons-vue'
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
import {getTenantIdByDomain} from "~/api/cms/cmsDomain";
import {listTenant} from "~/api/system/tenant";
const i18n = useI18n();
const props = withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsWebsite;
value?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: any): void
}>()
// 搜索表单
const where = reactive<any>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const reload = () => {
}
reload();
</script>
<style scoped lang="less">
</style>

View File

@@ -1,4 +1,10 @@
<template> <template>
<el-descriptions title="评分及评价">
<template #extra>
<el-button type="text" @click="onComplaint">投诉</el-button>
<el-button type="text" @click="onComments">发表评论</el-button>
</template>
</el-descriptions>
<form <form
ref="formRef" ref="formRef"
:model="form" :model="form"
@@ -8,18 +14,7 @@
size="large" size="large"
status-icon status-icon
> >
<el-card shadow="hover" v-if="comments" class="mb-5"> <template v-if="comments && comments.length > 0">
<template #header>
<div class="card-header font-bold text-xl flex justify-between">
<span>评分和评价</span>
<div class="comments">
<el-button @click="onComplaint">投诉</el-button>
<el-button type="primary" @click="onComments">发表评论</el-button>
</div>
</div>
</template>
<template #default>
<template v-if="comments.length > 0">
<div class="w-full"> <div class="w-full">
<div v-for="(item,index) in comments" :key="index" <div v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3" class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
@@ -54,8 +49,7 @@
<template v-else> <template v-else>
暂无用户评论 暂无用户评论
</template> </template>
</template>
</el-card>
<!-- 发表评论 --> <!-- 发表评论 -->
<el-dialog <el-dialog
v-model="visible" v-model="visible"

View File

@@ -1,71 +1,39 @@
<template> <template>
<div class="banner m-auto relative sm:flex mt-15"> <div class="banner m-auto relative sm:flex">
<svg viewBox="0 0 1440 181" fill="none" xmlns="http://www.w3.org/2000/svg" <div class="md:w-screen-xl m-auto py-10">
class="pointer-events-none absolute w-full top-[-2px] transition-all text-green-5 flex-shrink-0 opacity-100 duration-[400ms] opacity-80 -z-10">
<mask id="path-1-inside-1_414_5526" fill="white">
<path d="M0 0H1440V181H0V0Z"></path>
</mask>
<path d="M0 0H1440V181H0V0Z" fill="url(#paint0_linear_414_5526)" fill-opacity="0.22"></path>
<path d="M0 2H1440V-2H0V2Z" fill="url(#paint1_linear_414_5526)" mask="url(#path-1-inside-1_414_5526)"></path>
<defs>
<linearGradient id="paint0_linear_414_5526" x1="720" y1="0" x2="720" y2="181" gradientUnits="userSpaceOnUse">
<stop stop-color="currentColor"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0"></stop>
</linearGradient>
<linearGradient id="paint1_linear_414_5526" x1="0" y1="90.5" x2="1440" y2="90.5" gradientUnits="userSpaceOnUse">
<stop stop-color="currentColor" stop-opacity="0"></stop>
<stop offset="0.395" stop-color="currentColor"></stop>
<stop offset="1" stop-color="currentColor" stop-opacity="0"></stop>
</linearGradient>
</defs>
</svg>
<div class="md:w-screen-xl m-auto">
<Breadcrumb :data="form" :categoryName="form?.categoryName"/>
<div class="py-8 sm:py-16" _path="/templates" _dir="" _draft="false" _partial="false" _locale=""
_id="content:4.templates.yml" _type="yaml" _source="content" _file="4.templates.yml" _stem="4.templates"
_extension="yml">
<div class="gap-8 sm:gap-y-16 lg:items-center" v-if="form"> <div class="gap-8 sm:gap-y-16 lg:items-center" v-if="form">
<div class="w-full sm:px-0 px-4"> <div class="w-full sm:px-0 px-4">
<div class="flex flex-1"> <div class="flex flex-1">
<template v-if="form.image"> <template v-if="form.websiteLogo">
<el-image :src="form.image" shape="square" <el-image :src="form.websiteLogo" shape="square"
class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-4"/> class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-6"/>
<!-- <el-image :src="form.image" shape="square" :size="80"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</template> </template>
<div class="title flex flex-col"> <div class="title flex flex-col">
<h1 <h1
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-4xl"> class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-3xl">
<span v-if="form.title">{{ form.title }}</span> <el-space>
<span>{{ form.websiteName }}</span>
</el-space>
</h1> </h1>
<div class="my-1 text-sm text-gray-500 w-auto sm:max-w-3xl max-w-xs flex-1 dark:text-gray-400"> <div class="my-1 text-sm text-gray-500 w-auto sm:max-w-3xl max-w-xs flex-1 dark:text-gray-400">
{{ form?.comments || desc }} {{ form?.comments || desc }}
</div> </div>
<!-- <a class="company-name text-sm my-1">--> <el-space class="btn">
<!-- {{ form.companyName || 'WebSoft Inc.' }}--> <nuxt-link target="_blank"><el-button type="primary" round>获取</el-button></nuxt-link>
<!-- </a>-->
<el-rate v-model="form.rate" disabled />
<div class="btn">
<el-space class="mt-4">
<el-button>产品控制台</el-button>
<el-button>帮助文档</el-button>
</el-space> </el-space>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <!-- {{ form }}-->
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {FullScreen} from '@element-plus/icons-vue'
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {Company} from "~/api/system/company/model"; import type {Company} from "~/api/system/company/model";
import type {CmsArticle} from "~/api/cms/cmsArticle/model"; import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
const token = useToken(); const token = useToken();
@@ -74,7 +42,7 @@ const props = withDefaults(
title?: string; title?: string;
desc?: string; desc?: string;
buyUrl?: string; buyUrl?: string;
form?: CmsArticle; form?: CmsWebsite;
value?: number; value?: number;
}>(), }>(),
{} {}
@@ -95,7 +63,7 @@ const onBuy = (item: Company) => {
if (!token.value || token.value == '') { if (!token.value || token.value == '') {
ElMessage.error('请先登录'); ElMessage.error('请先登录');
setTimeout(() => { setTimeout(() => {
openSpmUrl(`/product/create`, item, item.companyId) navigateTo(`/product/create`)
}, 500) }, 500)
} }

View File

@@ -0,0 +1,45 @@
<template>
<el-space class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
const i18n = useI18n();
const props = withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsArticle;
value?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: any): void
}>()
// 搜索表单
const where = reactive<any>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const reload = () => {
navigateTo(`/search/${where.keywords}`)
}
</script>
<style scoped lang="less">
</style>

View File

@@ -16,13 +16,13 @@
<!-- :value="item.value"--> <!-- :value="item.value"-->
<!-- />--> <!-- />-->
<!-- </el-select>--> <!-- </el-select>-->
<el-button-group> <el-input v-model="where.keywords" style="width: 400px" :placeholder="`应用搜索`" :suffix-icon="Search" @change="reload"/>
<el-button-group v-model:value="where" @tab-click="handleClick" @change="reload">
<el-button>综合</el-button>
<el-button>最新</el-button> <el-button>最新</el-button>
<el-button>免费</el-button> <el-button>免费</el-button>
<el-button>付费</el-button> <el-button>付费</el-button>
<el-button>综合</el-button>
</el-button-group> </el-button-group>
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space> </el-space>
</template> </template>
<el-row :gutter="24" id="container" class="clearfix"> <el-row :gutter="24" id="container" class="clearfix">
@@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<div class="item-image pt-3"> <div class="item-image pt-3">
<el-image v-if="item.files" :src="`${JSON.parse(item.files)[0].url}`" class="w-full h-1/2" /> <el-image v-if="item.files" :src="`${JSON.parse(item.files)[0].url}`" class="w-full h-1/2 max-h-[220px]" />
<el-image v-else class="w-full h-[220px]" /> <el-image v-else class="w-full h-[220px]" />
</div> </div>
</div> </div>
@@ -89,10 +89,11 @@ const layout = useLayout();
const where = reactive<CmsWebsiteParam>({ const where = reactive<CmsWebsiteParam>({
keywords: '', keywords: '',
page: 1, page: 1,
limit: 20, limit: 12,
status: 0, status: undefined,
recommend: undefined,
categoryId: undefined, categoryId: undefined,
lang: i18n.locale.value lang: undefined
}); });
const goBack = () => { const goBack = () => {

201
pages/market/search.vue Normal file
View File

@@ -0,0 +1,201 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 应用市场 </span>
</template>
<template #extra>
<el-space class="flex items-center">
<!-- <el-select v-model="value" clearable placeholder="筛选" style="width: 240px">-->
<!-- <el-option-->
<!-- v-for="item in options"-->
<!-- :key="item.value"-->
<!-- :label="item.label"-->
<!-- :value="item.value"-->
<!-- />-->
<!-- </el-select>-->
<el-button-group>
<el-button>最新</el-button>
<el-button>免费</el-button>
<el-button>付费</el-button>
<el-button>综合</el-button>
</el-button-group>
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="8" class="left mb-8">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/item/${item.websiteId}.html`)">
<div class="flex-1 px-4 py-5 sm:p-4 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex gap-1.5">
<el-avatar
:src="item.websiteLogo" shape="square" :size="55" style="background-color: white;"/>
<div class="flex-1 text-lg cursor-pointer flex flex-col">
{{ item.websiteName }}
<div class="flex justify-between items-center">
<sapn class="text-xs text-gray-400 font-normal line-clamp-1">{{ item.comments || '暂无描述' }}</sapn>
<el-button size="small" round>获取</el-button>
</div>
</div>
</div>
<div class="item-image pt-3">
<el-image v-if="item.files" :src="`${JSON.parse(item.files)[0].url}`" class="w-full h-1/2 max-h-[220px]" />
<el-image v-else class="w-full h-[220px]" />
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-page-header>
<Pagination :total="total" @done="search" />
</div>
</template>
<script setup lang="ts">
import { Picture as IconPicture } from '@element-plus/icons-vue'
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import { ElNotification as notify } from 'element-plus'
import { useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import type { ComponentSize } from 'element-plus'
import { ElNotification } from 'element-plus'
import type { TabsPaneContext } from 'element-plus'
import dayjs from "dayjs";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
const route = useRoute();
const router = useRouter();
const navId = ref();
// 页面信息
const list = ref<CmsWebsite[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
const activeName = ref('2839');
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsWebsiteParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
categoryId: undefined,
lang: i18n.locale.value
});
const goBack = () => {
router.back();
}
// 加载页面数据
const reload = async () => {
await pageCmsWebsiteAll(where).then(response => {
if(response?.list){
list.value = response?.list;
total.value = response.count;
}
}).catch(() => {})
}
const bakReload = async () => {
await getCmsNavigation(navId.value).then(data => {
page.value = data;
layout.value.banner = data.banner;
// seo
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(navigation => {
category.value = navigation;
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = page.value.navigationId;
}else {
where.categoryId = page.value.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response.count;
list.value = response.list;
}
})
})
}).catch(() => {})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
const value = ref('')
const options = [
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
watch(
() => route.params.id,
(id) => {
// navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.right .content img{
width: auto !important;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
</style>

View File

@@ -8,8 +8,14 @@
<span class="text-large font-600 mr-3"> {{ page.title }} </span> <span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template> </template>
<el-card shadow="hover" class=" my-5"> <el-card shadow="hover" class=" my-5">
<!-- 新闻详细 -->
<div class=" bg-white">
<!-- 内容组件 --> <!-- 内容组件 -->
<Content class="content bg-white mt-5" :data="page.design?.content" /> <Content class="content text-lg py-3" :data="page.design?.content" />
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
</div>
</el-card> </el-card>
</el-page-header> </el-page-header>
</div> </div>
@@ -18,11 +24,12 @@
<script setup lang="ts"> <script setup lang="ts">
import {useLayout, usePage} from "~/composables/configState"; import {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model"; import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {getNavIdByParamsId, getViews, paramsId} from "~/utils/common"; import {getNavIdByParamsId, getViews, locationUrl, paramsId} from "~/utils/common";
import Left from "~/components/Left.vue"; import Left from "~/components/Left.vue";
import { ArrowLeft,View } from '@element-plus/icons-vue' import { ArrowLeft,View } from '@element-plus/icons-vue'
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation"; import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import Content from "~/components/Content.vue"; import Content from "~/components/Content.vue";
import Tags from "~/components/Tags.vue";
// 引入状态管理 // 引入状态管理
const route = useRoute(); const route = useRoute();

View File

@@ -3,24 +3,21 @@
<div class="flash"> <div class="flash">
</div> </div>
<el-card class="m-5 w-screen-sm sm:w-[430px] sm:h-[520px] flex justify-around relative border-0" style="border: 0;"> <el-card class="m-5 w-screen-sm sm:w-[420px] sm:h-[450px] flex justify-around relative border-0" style="border: 0;">
<div class="login-bar absolute top-0 right-0 cursor-pointer" @click="onLoginBar"> <div class="login-bar absolute top-0 right-0 cursor-pointer">
<div class="go-to-register cursor-pointer"> <div class="go-to-register cursor-pointer">
<img src="https://img.alicdn.com/imgextra/i3/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg" alt=""/> <img src="/assets/images/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg" alt=""/>
</div> </div>
<span class="absolute top-3 right-1.5 text-sm text-white font-bold cursor-pointer">{{ loginBar ? '注册' : '登录' }}</span> <span class="absolute top-3.5 right-2 text-sm text-white font-bold cursor-pointer"><el-icon size="20"><FullScreen /></el-icon></span>
</div> </div>
<!-- 登录界面 --> <!-- 登录界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="loginBar"> <el-space class="tabs pt-3 text-xl flex justify-center" v-if="loginBar">
<el-tabs v-model="activeName" class="demo-tabs "> <el-tabs v-model="activeName" class="">
<el-tab-pane label="账号登录" name="account"> <el-tab-pane label="密码登录" name="account">
<div class="custom-style my-4"> <div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]"> <el-form :model="form" label-width="auto" class="w-[330px]">
<!-- <el-form-item>-->
<!-- <el-input class="w-full" size="large" placeholder="租户ID" :prefix-icon="Shop" v-model="form.tenantId" />-->
<!-- </el-form-item>-->
<el-form-item> <el-form-item>
<el-input class="w-full" size="large" placeholder="登录账号" :prefix-icon="Avatar" v-model="form.username" /> <el-input class="w-full" size="large" placeholder="登录账号" maxlength="20" :prefix-icon="Avatar" v-model="form.username" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-input type="password" size="large" maxlength="100" placeholder="登录密码" :prefix-icon="Briefcase" v-model="form.password" /> <el-input type="password" size="large" maxlength="100" placeholder="登录密码" :prefix-icon="Briefcase" v-model="form.password" />
@@ -67,20 +64,15 @@
</el-tabs> </el-tabs>
</el-space> </el-space>
<!-- 快捷登录 ---> <!-- 快捷登录 --->
<template v-if="loginBar && activeName == 'account'"> <template v-if="loginBar">
<!-- <div class="clearfix flex justify-center">--> <div class="clearfix flex justify-center">
<!-- <el-divider>--> <nuxt-link to="/passport/login?type=register"><span class="text-sm text-blue-400">免费注册</span></nuxt-link>
<!-- <span class="text-gray-400">其他登录方式</span>--> </div>
<!-- </el-divider>-->
<!-- </div>-->
<!-- <div class="clearfix flex justify-center">-->
<!-- <el-button circle :icon="ElIconUserFilled"></el-button>-->
<!-- </div>-->
</template> </template>
<!-- 注册界面 --> <!-- 注册界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="!loginBar"> <el-space class="tabs pt-3 text-xl flex justify-center" v-if="!loginBar">
<el-tabs v-model="activeName" class="demo-tabs "> <el-tabs v-model="activeName">
<el-tab-pane label="手机号注册" name="sms"> <el-tab-pane label="手机号注册" name="account">
<span class="text-sm text-gray-400"> <span class="text-sm text-gray-400">
未注册手机号验证通过后将自动注册 未注册手机号验证通过后将自动注册
</span> </span>
@@ -104,46 +96,13 @@
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox> <el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a> <a href="#" class="text-gray-700">用户协议</a>
<a href="#" class="text-gray-700">隐私政策</a> <a href="#" class="text-gray-700">隐私政策</a>
<a href="#" class="text-gray-700">产品服务协议</a>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button> <el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>
</el-form-item> </el-form-item>
</el-form> <div class="clearfix flex justify-center">
<nuxt-link to="/passport/login"><span class="text-sm text-blue-400">已有账号立即登录</span></nuxt-link>
</div> </div>
</el-tab-pane>
<el-tab-pane label="账号密码注册" name="account">
<div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item>
<el-input class="w-full" size="large" maxlength="30" placeholder="登录账号" v-model="form.username" />
</el-form-item>
<el-form-item>
<el-input type="password" size="large" maxlength="30" placeholder="登录密码" v-model="form.password" />
</el-form-item>
<el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.phone">
<template #prepend>+86</template>
</el-input>
</el-form-item>
<el-form-item>
<el-space class="flex justify-between w-full">
<el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a>
<a href="#" class="text-gray-700">隐私政策</a>
<a href="#" class="text-gray-700">产品服务协议</a>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>
</el-form-item>
</el-form> </el-form>
</div> </div>
</el-tab-pane> </el-tab-pane>
@@ -157,14 +116,15 @@ import {useConfigInfo, useToken, useUser, useWebsite} from "~/composables/config
import useFormData from '@/utils/use-form-data'; import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model'; import type { User } from '@/api/system/user/model';
import { ref } from 'vue' import { ref } from 'vue'
import { Shop, Key, Avatar, Briefcase } from '@element-plus/icons-vue' import { Shop, Key, Avatar, Briefcase, FullScreen } from '@element-plus/icons-vue'
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import type {CaptchaResult, LoginResult} from "~/api/passport/login/model"; import type {CaptchaResult, LoginResult} from "~/api/passport/login/model";
import {getCaptcha, sendSmsCaptcha} from "~/api/passport/login"; import {getCaptcha, loginBySms, register, sendSmsCaptcha} from "~/api/passport/login";
// 配置信息 // 配置信息
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite(); const website = useWebsite();
const config = useConfigInfo(); const config = useConfigInfo();
const token = useToken(); const token = useToken();
@@ -295,20 +255,17 @@ const onSubmit = async () => {
* 短信验证码登录 * 短信验证码登录
*/ */
const onSubmitBySms = async () => { const onSubmitBySms = async () => {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/loginBySms',{baseURL: runtimeConfig.public.apiServer,method: "post",body: { loginBySms({
phone: form.phone, phone: form.phone,
code: form.code, code: form.code,
isSuperAdmin: true isSuperAdmin: true
}}) }).then(response => {
// 登录成功 ElMessage.success('登录成功')
if(response.value?.code == 0){ doLogin(response)
ElMessage.success(response.value?.message) }).catch(() => {
await doLogin(response.value.data) ElMessage.error('验证码错误')
} changeCaptcha()
if(response.value?.code != 0){ })
ElMessage.error(response.value?.message)
await changeCaptcha()
}
} }
/** /**
@@ -319,7 +276,8 @@ const onRegister = async () => {
lock: true, lock: true,
text: 'Loading' text: 'Loading'
}) })
await useClientRequest<ApiResult<LoginResult>>('/register',{method: "post",body: {
register({
companyName: '应用名称', companyName: '应用名称',
username: form.phone, username: form.phone,
phone: form.phone, phone: form.phone,
@@ -327,23 +285,38 @@ const onRegister = async () => {
code: form.code, code: form.code,
email: form.email, email: form.email,
isSuperAdmin: true isSuperAdmin: true
}}).then(response => { }).then(response => {
// 登录成功
if(response?.code == 0){
loading.close(); loading.close();
ElMessage.success(response?.message) ElMessage.success('注册成功')
doLogin(response.data) doLogin(response)
}
if(response?.code != 0){
loading.close;
ElMessage.error(response?.message)
changeCaptcha()
}
}).catch(() => { }).catch(() => {
loading.close(); loading.close();
}).finally(() => {
loading.close();
}) })
// await useClientRequest<ApiResult<LoginResult>>('/register',{method: "post",body: {
// companyName: '应用名称',
// username: form.phone,
// phone: form.phone,
// password: form.password,
// code: form.code,
// email: form.email,
// isSuperAdmin: true
// }}).then(response => {
// // 登录成功
// if(response?.code == 0){
// loading.close();
// ElMessage.success(response?.message)
// doLogin(response.data)
// }
// if(response?.code != 0){
// loading.close;
// ElMessage.error(response?.message)
// changeCaptcha()
// }
// }).catch(() => {
// loading.close();
// }).finally(() => {
// loading.close();
// })
} }
@@ -366,14 +339,26 @@ const doLogin = async (data: any) => {
localStorage.setItem('UserId',data.user.userId); localStorage.setItem('UserId',data.user.userId);
localStorage.setItem('Avatar',data.user.avatar); localStorage.setItem('Avatar',data.user.avatar);
localStorage.setItem('TID_ADMIN',data.user.tenantId); localStorage.setItem('TID_ADMIN',data.user.tenantId);
// localStorage.setItem('TenantId',data.user.tenantId);
} }
setTimeout(() => { setTimeout(() => {
navigateTo('/') navigateTo('/')
return;
},500) },500)
} }
changeCaptcha(); changeCaptcha();
watch(
() => route.query.type,
(type) => {
loginBar.value = true
activeName.value = 'account'
if(type == 'register'){
loginBar.value = false
}
},
{ immediate: true }
);
</script> </script>
<style lang="less"> <style lang="less">
.login{ .login{

View File

@@ -1,32 +1,35 @@
<template> <template>
<PageBanner title="入驻" desc="Register Account"/> <!-- 主体部分 -->
<div class="login-layout m-auto sm:w-screen-xl w-full"> <div class="xl:w-screen-xl m-auto py-4 mt-20">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3 "> <el-page-header :icon="ArrowLeft" @back="goBack">
<div class="flash bg-white rounded-lg px-8 py-4 w-full"> <template #content>
<span class="text-large font-600 mr-3"> 注册 </span>
</template>
</el-page-header>
<el-card shadow="hover" class=" my-5 px-2">
<Auth @done="reload"/> <Auth @done="reload"/>
</div> </el-card>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ArrowLeft, View, Search} from '@element-plus/icons-vue'
import {useConfigInfo, useWebsite} from "~/composables/configState"; import {useConfigInfo, useWebsite} from "~/composables/configState";
import { ref } from 'vue' import { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest"; import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api"; import type {ApiResult} from "~/api";
import Auth from './components/Auth.vue'; import Auth from './components/Auth.vue';
import type {ShopMerchantApply} from "~/api/shop/shopMerchantApply/model";
import Base from "~/pages/user/components/Base.vue";
// 配置信息 // 配置信息
const runtimeConfig = useRuntimeConfig(); const runtimeConfig = useRuntimeConfig();
const route = useRoute(); const route = useRoute();
const router = useRouter();
const activeIndex = ref(''); const activeIndex = ref('');
const website = useWebsite() const website = useWebsite()
const config = useConfigInfo(); const config = useConfigInfo();
const merchantApply = ref<ShopMerchantApply>(); const merchantApply = ref<any>();
const reload = async () => { const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<ShopMerchantApply>>('/shop/shop-merchant-apply/getByUserId', {baseURL: runtimeConfig.public.apiServer}) const {data: response} = await useServerRequest<ApiResult<any>>('/shop/shop-merchant-apply/getByUserId', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) { if (response.value?.data) {
merchantApply.value = response.value.data; merchantApply.value = response.value.data;
} }
@@ -38,11 +41,15 @@ const reload = async () => {
} }
} }
const goBack = () => {
router.back(); // 返回上一页
}
watch( watch(
() => route.path, () => route.path,
(path) => { (path) => {
activeIndex.value = path; activeIndex.value = path;
reload(); // reload();
}, },
{immediate: true} {immediate: true}
); );

View File

@@ -1,146 +0,0 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-row :gutter="24" id="container" class="clearfix">
<el-col :span="5" class="left">
<!-- 内页左侧组件 -->
<Left :category="category" />
</el-col>
<el-col :span="19" class="right">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ $t('searchKeywords') }}{{ where.keywords }}
</h2>
</div>
<el-alert v-if="where.keywords" :title="`${$t('search.results')}${$t('search.find')} ${total} ${$t('search.nums')}`" type="warning" :closable="false" />
<div class="content">
<ul class="news_listn clearfix">
<template v-for="(item,index) in list" key="index">
<li class="clearfix">
<a :href="detail(item)" target="_blank" class="n-left fl">
<el-image :src="item.image" :fit="`scale-down`" class="w-[240px] h-[158px]" :alt="item.title" />
</a>
<div class="n-right fr">
<h3><a :href="detail(item)" target="_blank" :title="item.title" v-html="replaceKeywords(item.title)"></a></h3>
<div v-html="replaceKeywords(item.comments)" class="line-clamp-2"></div>
<div class="date">{{ $t('createTime') }}{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</div>
<div class="date">{{ $t('search.column') }}{{ item.categoryName }}</div>
<div class="n-more"><a :href="detail(item)">{{ $t('seeMore') }}>></a></div>
</div>
</li>
</template>
<div class="clearboth"></div>
</ul>
<Pagination :total="total" @done="search" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import Banner from "@/components/Banner.vue";
import {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import dayjs from "dayjs";
import {ArrowRight} from '@element-plus/icons-vue'
import {detail, getPath} from "~/utils/common";
import Left from "~/components/Left.vue";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsModel} from "~/api/cms/cmsModel";
const route = useRoute();
// 页面信息
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 10,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const replaceKeywords = (text: any) => {
return text.replace(`${where.keywords}`,'<font color=#ff0000>' + where.keywords + '</font>');
}
// 加载页面数据
const reload = async () => {
listCmsModel({
model: getPath(1)
}).then(response => {
const data = response[0];
if(data){
// 获取栏目信息
page.value = data
layout.value.banner = data.banner;
// 设置页面标题
useSeoMeta({
description: data?.comments || `${route.params.id}`,
keywords: `${route.params.id}`,
titleTemplate: `【搜索结果】${route.params.id}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: 2847,
}).then(categoryData => {
category.value = categoryData;
// 加载文章列表
if(!getPath(1)){
return ElMessage.error('请输入搜索关键词!');
}
where.keywords = `${route.params.id}`;
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}
}).catch(err => {
console.log(err,'加载失败...')
})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.path,
() => {
reload();
},
{immediate: true}
);
</script>
<style scoped lang="less">
.sitemp h2{
width: 500px !important;
}
</style>

123
pages/search/[keywords].vue Normal file
View File

@@ -0,0 +1,123 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ '站内搜索' }} </span>
</template>
<template #extra>
<el-radio-group v-model="where.model" @change="reload">
<el-radio-button label="文档" value="docs" />
<el-radio-button label="资讯" value="article" />
</el-radio-group>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer">
<nuxt-link :to="`/${item.detail}/${item.articleId}.html`">
<el-image
:src="item.image"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex min-h-[56px]">
<span v-html="replaceKeywords(item.title)"></span>
</div>
</div>
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
{{ dayjs(item.createTime).format('YYYY-MM-DD') }}
</div>
</div>
</div>
</nuxt-link>
</el-card>
</el-col>
</el-row>
<Pagination :total="total" @done="search" :keywords="where.keywords" />
</el-page-header>
</div>
</template>
<script setup lang="ts">
import Banner from "@/components/Banner.vue";
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useLayout, usePage} from "~/composables/configState";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import dayjs from "dayjs";
import {getViews} from "~/utils/common";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
const route = useRoute();
const router = useRouter();
// 页面信息
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const total = ref(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: undefined,
page: 1,
limit: 12,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
// 加载页面数据
const reload = async () => {
list.value = [];
pageCmsArticle(where).then(response => {
list.value = response?.list || [];
total.value = response?.count || 0;
})
// 设置页面标题
useSeoMeta({
description: `${where.keywords || route.params.keywords}`,
keywords: `${where.keywords || route.params.keywords}`,
titleTemplate: `【搜索结果】${where.keywords || route.params.keywords}` + ' - %s',
})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
const goBack = () => {
router.back(); // 返回上一页
}
const replaceKeywords = (text: any) => {
return text.replace(`${where.keywords}`,'<font color=#ff0000>' + where.keywords + '</font>');
}
watch(
() => route.params.keywords,
(keywords) => {
where.keywords = String(keywords);
if(where.keywords == '关键词不能为空!'){
where.keywords = undefined;
}
reload();
},
{ immediate: true }
);
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,53 @@
<template>
<el-col v-for="(item,index) in data" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer">
<nuxt-link :to="`/${item.detail}/${item.articleId}.html`">
<el-image
:src="item.image"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex min-h-[56px]">
<span v-html="replaceKeywords(item.title)"></span>
</div>
</div>
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
{{ dayjs(item.createTime).format('YYYY-MM-DD') }}
</div>
</div>
</div>
</nuxt-link>
</el-card>
</el-col>
</template>
<script setup lang="ts">
import { View } from '@element-plus/icons-vue'
import {getViews} from "~/utils/common";
import dayjs from "dayjs";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
const props = withDefaults(
defineProps<{
data?: CmsArticle[];
keywords?: string;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: CmsArticleParam): void
}>()
const replaceKeywords = (text: any) => {
return text.replace(`${props.keywords}`,'<font color=#ff0000>' + props.keywords + '</font>');
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,53 @@
<template>
<el-col v-for="(item,index) in data" :key="index" :span="6" class="left mb-6">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer">
<nuxt-link :to="`/item/${item.websiteId}.html`">
<el-image
:src="item.websiteLogo"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex min-h-[56px]">
<span v-html="replaceKeywords(item.websiteName)"></span>
</div>
</div>
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
{{ dayjs(item.createTime).format('YYYY-MM-DD') }}
</div>
</div>
</div>
</nuxt-link>
</el-card>
</el-col>
</template>
<script setup lang="ts">
import { View } from '@element-plus/icons-vue'
import {getViews} from "~/utils/common";
import dayjs from "dayjs";
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
const props = withDefaults(
defineProps<{
data?: CmsWebsite[];
keywords?: string;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', where: CmsWebsiteParam): void
}>()
const replaceKeywords = (text: any) => {
return text.replace(`${props.keywords}`,'<font color=#ff0000>' + props.keywords + '</font>');
}
</script>
<style scoped lang="less">
</style>

View File

@@ -29,7 +29,7 @@
<div class="flex flex-1"> <div class="flex flex-1">
<template v-if="form.image"> <template v-if="form.image">
<el-image :src="form.image" shape="square" <el-image :src="form.image" shape="square"
class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-4"/> class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-6"/>
<!-- <el-image :src="form.image" shape="square" :size="80"--> <!-- <el-image :src="form.image" shape="square" :size="80"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>--> <!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</template> </template>

72
pages/user/auth.vue Normal file
View File

@@ -0,0 +1,72 @@
<template>
<div class="xl:w-screen-xl m-auto py-4 my-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 实名认证 </span>
</template>
<div class="login-layout m-auto mt-10 sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3 ">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="reload"/>
<div class="flash bg-white rounded-lg w-full">
<div class="sm:w-screen-md w-full sm:px-4 sm:py-2">
<Auth @done="reload"/>
</div>
</div>
</div>
</div>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useConfigInfo, useWebsite} from "~/composables/configState";
import {ref} from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "./components/UserMenu.vue";
import Auth from './components/Auth.vue';
import type {ShopMerchantApply} from "~/api/shop/shopMerchantApply/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const router = useRouter();
const activeIndex = ref('');
const website = useWebsite()
const config = useConfigInfo();
const merchantApply = ref<ShopMerchantApply>();
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
const {data: response} = await useServerRequest<ApiResult<ShopMerchantApply>>('/shop/shop-merchant-apply/getByUserId', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
merchantApply.value = response.value.data;
}
if(config.value){
useHead({
title: `实名认证 - ${config.value?.siteName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
}
}
const goBack = () => {
router.back(); // 返回上一页
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{immediate: true}
);
</script>

View File

@@ -0,0 +1,460 @@
<template>
<el-form
ref="formRef"
v-loading="loading"
:model="form"
:rules="rules"
label-position="top"
class="w-full sm:py-2 ml-4 mt-1"
size="large"
status-icon
>
<el-tabs v-model="form.type" class="flash bg-white ml-0">
<el-tab-pane :name="0" label="个人认证"/>
<el-tab-pane :name="1" label="企业认证"/>
</el-tabs>
<!-- 已完成认证 -->
<template v-if="form.status === 1">
<template v-if="form.type == 0">
<el-result
icon="success"
title="个人认证已通过"
:sub-title="`认证完成时间 ${form.completedTime}`"
>
<template #extra>
<el-button type="text" @click="onUpdate">修改认证信息</el-button>
</template>
</el-result>
</template>
<template v-else>
<el-result
icon="success"
title="企业认证已通过"
:sub-title="`认证完成时间 ${form.completedTime}`"
>
<template #extra>
<el-button type="text" @click="onUpdate">修改认证信息</el-button>
</template>
</el-result>
</template>
</template>
<!-- 申请被驳回 -->
<template v-if="form.status === 2">
<el-result
icon="error"
title="您的申请已被驳回"
:sub-title="`${form.reason}`"
>
<template #extra>
<el-button type="text" @click="onUpdate">修改认证信息</el-button>
</template>
</el-result>
</template>
<!-- 未完成认证 -->
<template v-if="form.status === 0 && form.checkStatus">
<el-result
icon="warning"
title="审核中"
:sub-title="`您的申请已提交,请耐心等待工作人员的审核,非常感谢`"
>
</el-result>
</template>
<template v-if="form.status === 0 && !form.checkStatus">
<template v-if="form.type == 1">
<el-form-item label="企业名称" prop="merchantName">
<el-input v-model="form.merchantName" placeholder="请输入企业名称"/>
</el-form-item>
<el-form-item label="社会信用代码" prop="merchantCode">
<el-input v-model="form.merchantCode" placeholder="请输入社会信用代码"/>
</el-form-item>
<el-form-item label="营业执照" required>
<el-upload
v-model:file-list="yyzzFile"
action="https://common-api.websoft.top/api/oss/upload"
:headers="{
Authorization: token,
TenantId: tenantId,
}"
:limit="1"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="yyzzRemove"
:on-success="yyzzOnSuccess"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<div class="flex justify-center">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</div>
</el-dialog>
</el-form-item>
<el-form-item label="门头照片">
<el-upload
v-model:file-list="image"
action="https://common-api.websoft.top/api/oss/upload"
:headers="{
Authorization: token,
TenantId: tenantId,
}"
:limit="1"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="imageRemove"
:on-success="imageOnSuccess"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<div class="flex justify-center">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</div>
</el-dialog>
</el-form-item>
<el-form-item label="其他证件">
<el-upload
v-model:file-list="files"
action="https://common-api.websoft.top/api/oss/upload"
:headers="{
Authorization: token,
TenantId: tenantId,
}"
:limit="9"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="filesRemove"
:on-success="filesOnSuccess"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<div class="flex justify-center">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</div>
</el-dialog>
</el-form-item>
</template>
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入真实姓名"/>
</el-form-item>
<el-form-item label="手机号码" prop="phone">
<el-input v-model="form.phone" maxlength="11" placeholder="请输入真实有效的手机号码"/>
</el-form-item>
<el-form-item label="身份证号码" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入证件号码"/>
</el-form-item>
<el-form-item label="身份证" required>
<el-upload
v-model:file-list="sfzFile"
action="https://common-api.websoft.top/api/oss/upload"
:headers="{
Authorization: token,
TenantId: tenantId,
}"
:limit="2"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="sfzRemove"
:on-success="sfzSuccess"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="所属行业" prop="category">
<el-cascader
v-model="industry"
:options="industryData"
placeholder="请选择所属行业"
class="w-full"
@change="handleChange"
/>
</el-form-item>
<el-form-item label="业务描述" prop="comments">
<el-input v-model="form.comments" placeholder="请输入公司业务介绍" :rows="5" type="textarea" />
</el-form-item>
<el-form-item label="注册协议">
<el-checkbox v-model="isAgree">
请务必提供真实信息我司有权自行或委托第三方审查您提供的身份信息是否属真实有效若提供虚假信息由此的全部后果由您承担
</el-checkbox>
</el-form-item>
<el-space class="flex">
<el-button type="primary" size="large" :disabled="!isAgree" @click="submitForm(formRef)">
{{ isUpdate ? '提交修改' : '提交申请' }}
</el-button>
</el-space>
</template>
</el-form>
<el-dialog v-model="dialogVisible">
<div class="flex justify-center">
<el-image w-full :src="dialogImageUrl" alt="查看证件" />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import {reactive, ref} from 'vue'
import {Plus} from '@element-plus/icons-vue'
import type {FormInstance, FormRules, UploadProps, UploadUserFile} from 'element-plus'
import type {ShopMerchantApply} from "~/api/shop/shopMerchantApply/model";
import industryData from '@/assets/json/industry-data.json';
import {useClientRequest} from "~/composables/useClientRequest";
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ShopMerchant} from "~/api/shop/shopMerchant/model";
import useFormData from "~/utils/use-form-data";
const token = useToken();
const tenantId = localStorage.getItem('TID_ADMIN')
const formRef = ref<FormInstance>()
const yyzzFile = ref<UploadUserFile[]>([])
const sfzFile = ref<UploadUserFile[]>([])
const sfzStr = ref<string[]>([]);
const files = ref<UploadUserFile[]>([])
const filesStr = ref<string[]>([])
const image = ref<UploadUserFile[]>([])
const industry = ref<any[]>([])
const loading = ref<boolean>(true)
const isUpdate = ref<boolean>(false)
const isAgree = ref<boolean>(false)
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const {form, assignFields, resetFields} = useFormData<ShopMerchantApply>({
applyId: undefined,
type: 0,
merchantName: undefined,
merchantCode: undefined,
image: undefined,
phone: undefined,
realName: undefined,
idCard: undefined,
sfz1: undefined,
sfz2: undefined,
yyzz: undefined,
name2: undefined,
shopType: undefined,
parentId: undefined,
categoryId: undefined,
category: undefined,
commission: undefined,
keywords: undefined,
files: undefined,
ownStore: undefined,
recommend: undefined,
completedTime: undefined,
goodsReview: undefined,
userId: undefined,
comments: undefined,
reason: undefined,
checkStatus: undefined,
status: 0,
sortNumber: undefined
})
const rules = reactive<FormRules<ShopMerchantApply>>({
realName: [
{required: true, message: '请输入真实姓名', trigger: 'blur'},
{min: 2, max: 5, message: '长度应为2-5个字符', trigger: 'blur'},
],
phone: [
{required: true, message: '请输入手机号码', trigger: 'blur'},
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
],
idCard: [
{required: true, message: '请输入证件号码', trigger: 'blur'},
{min: 18, max: 18, message: '证件号码长度应为18位', trigger: 'blur'},
],
merchantName: [
{required: true, message: '请输入企业名称', trigger: 'blur'}
],
merchantCode: [
{required: true, message: '请输入社会信用代码', trigger: 'blur'}
],
category: [
{required: true, message: '请选择所属行业', trigger: 'change'}
]
})
const yyzzRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
form.yyzz = '';
}
const yyzzOnSuccess = (e: any) => {
form.yyzz = e.data.downloadUrl
}
const sfzRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
form.sfz1 = '';
form.sfz2 = '';
}
const sfzSuccess = (e:any) => {
sfzStr.value.push(e.data.downloadUrl)
}
const imageOnSuccess = (e: any) => {
form.image = e.data.downloadUrl
}
const imageRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
form.image = '';
}
const filesRemove: UploadProps['onRemove'] = (uploadFile) => {
const index = filesStr.value.findIndex(f => f == uploadFile.url);
filesStr.value.splice(index, 1)
}
const filesOnSuccess = (e: any) => {
filesStr.value.push(e.data.downloadUrl)
}
// 所属行业
const handleChange = (value: any) => {
let parent = ''
let category = ''
industryData.map(d => {
if (d.value == value[0]) {
form.parentId = d.value
parent = d.label
if (d.children) {
d.children.map(c => {
if (c.value == value[1]) {
category = c.label
form.categoryId = c.value
}
})
}
}
})
form.category = `${parent}/${category}`
}
const onUpdate = () => {
form.status = 0;
form.checkStatus = false
}
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
dialogImageUrl.value = uploadFile.url!
dialogVisible.value = true
}
const runtimeConfig = useRuntimeConfig();
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
console.log('submit!',valid)
if (form.type == 0) {
form.shopType = '个人开发者';
form.merchantName = form.realName;
}
if(form.type == 1){
form.shopType = '企业开发者';
if(form.yyzz == ''){
return ElMessage.error('请上营业执照');
}
if(filesStr.value.length > 0){
form.files = JSON.stringify(filesStr.value)
}
}
if(sfzStr.value.length == 1){
return ElMessage.error('请上传身份证正反面');
}
form.sfz1 = sfzStr.value[0];
form.sfz2 = sfzStr.value[1];
useClientRequest<ApiResult<any>>(`/shop/shop-merchant-apply`, {
baseURL: runtimeConfig.public.apiServer,
method: isUpdate.value ? 'PUT' : 'POST',
body: form
}).then(res => {
if (res.code == 0) {
ElMessage.success(res.message)
reload();
} else {
return ElMessage.error(res.message)
}
})
} else {
console.log('error submit!', fields)
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
resetFields();
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<ShopMerchant>>('/shop/shop-merchant-apply/getByUserId')
if (response.value?.data) {
isUpdate.value = true;
assignFields(response.value.data)
industry.value = []
industry.value.push(form.parentId)
industry.value.push(form.categoryId)
files.value = []
filesStr.value = []
yyzzFile.value = []
sfzFile.value = []
sfzStr.value = []
image.value = []
if(form.sfz1){
sfzFile.value.push({
uid: 1,
url: form.sfz1,
name: '身份证正面',
})
}
if(form.sfz2){
sfzFile.value.push({
uid: 2,
url: form.sfz2,
name: '身份证反面',
})
}
if(form.yyzz){
yyzzFile.value.push({
uid: 3,
url: form.yyzz,
name: '营业执照',
})
}
if(form.image){
image.value.push({
uid: 4,
url: form.image,
name: '',
})
}
if (form.files) {
const arr = JSON.parse(form.files)
let i = 1;
arr.map(d => {
files.value.push({
uid: i++,
url: d,
name: '',
})
filesStr.value.push(d)
})
}
}
loading.value = false
}
reload();
</script>
<style scoped>
/* 自定义 el-dialog 的样式 */
.el-dialog {
background: rgba(255, 255, 255, 0.5); /* 设置背景颜色为半透明白色 */
backdrop-filter: blur(10px); /* 可选:为背景添加模糊效果 */
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<el-form :model="form" label-width="auto" size="large" label-position="top">
<el-form-item label="租户ID" class="px-4">
<el-input disabled v-model="form.tenantId"/>
</el-form-item>
<el-form-item label="手机号码" class="px-4">
<el-input disabled v-model="form.mobile"/>
</el-form-item>
<el-form-item label="应用名称" class="px-4">
<el-input v-model="form.tenantName"/>
</el-form-item>
<el-form-item label="昵称" class="px-4">
<el-input v-model="form.nickname"/>
</el-form-item>
<el-form-item label="邮箱账号" class="px-4">
<el-input v-model="form.email" placeholder="邮箱账号"/>
</el-form-item>
<el-form-item label="性别" class="px-4">
<el-radio-group v-model="form.sex">
<el-radio value="1"></el-radio>
<el-radio value="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="个人签名" class="px-4">
<el-input v-model="form.comments" type="textarea" placeholder="个人签名" :rows="4"/>
</el-form-item>
<el-form-item class="px-4">
<el-button type="primary" class="sm:w-auto w-full" size="large" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
form?: any;
title?: string;
desc?: string;
}>(),
{}
);
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,45 @@
<template>
<el-table :data="tableData">
<el-table-column prop="orderId" label="订单号" />
<el-table-column prop="comments" label="产品名称" />
<el-table-column prop="payPrice" label="订单金额(元)" />
<el-table-column prop="month" label="购买时长(月)" />
<el-table-column prop="appName" label="应用名称" />
<el-table-column prop="createTime" label="下单时间" />
<el-table-column prop="action" label="操作">
<template #default="scope">
<el-button @click="openSpmUrl(`https://${scope.row.tenantId}.websoft.top`,form,scope.row.tenantId,true)">控制台</el-button></template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import {ref} from "vue";
import type {ApiResult, PageResult} from "~/api";
import type {OrderGoods} from "~/api/system/orderGoods/model";
withDefaults(
defineProps<{
form?: any;
title?: string;
desc?: string;
}>(),
{}
);
const tableData = ref<OrderGoods[]>([]);
const {data: response} = await useServerRequest<ApiResult<PageResult<OrderGoods>>>('/system/order-goods/page',{
params: {
userId: localStorage.getItem('UserId')
}
})
if(response.value?.data){
tableData.value = response.value.data.list;
console.log(tableData.value,'tableData')
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,31 @@
<template>
<el-form :model="form" label-width="auto" size="large" label-position="top">
<el-form-item label="旧密码" class="px-4">
<el-input v-model="form.oldPassword" placeholder="请输入旧密码" />
</el-form-item>
<el-form-item label="新密码" class="px-4">
<el-input v-model="form.password" type="password" placeholder="请输入新密码" />
</el-form-item>
<el-form-item label="确认密码" class="px-4">
<el-input v-model="form.password2" type="password" placeholder="请确认新密码" />
</el-form-item>
<el-form-item class="px-4">
<el-button type="primary" size="large" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
withDefaults(
defineProps<{
form?: any;
title?: string;
desc?: string;
}>(),
{}
);
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,60 @@
<template>
<el-space direction="vertical" class="sm:w-[140px] sm:flex sm:mb-0 mb-5 w-full pr-7">
<div class="py-2" v-for="(item,index) in activities" :index="`${item.path}`" :key="index">
<el-button :icon="item.icon" link class="text-gray-500" plain @click="navigateTo(item.path)">{{ item.name }}</el-button>
</div>
</el-space>
</template>
<script setup lang="ts">
import {User,Lock,Postcard,Tickets,SwitchButton} from '@element-plus/icons-vue'
import {navigateTo} from "#imports";
withDefaults(
defineProps<{
layout?: any;
activeIndex?: string;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done', index: string): void;
(e: 'update:visible', visible: boolean): void;
}>();
const activities = [
{
icon: User,
name: '账号信息',
path: '/user'
},
{
icon: Lock,
name: '密码修改',
path: '/user/password'
},
{
icon: Postcard,
name: '实名认证',
path: '/user/auth'
},
{
icon: SwitchButton,
name: '退出登录',
path: '/user/logout'
},
]
const handleSelect = (index: string) => {
emit('done', index)
}
</script>
<style lang="scss">
.custom-menu-item:hover {
background-color: #ffffff;
}
</style>

108
pages/user/index.vue Normal file
View File

@@ -0,0 +1,108 @@
<template>
<div class="xl:w-screen-xl m-auto py-4 my-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 用户中心 </span>
</template>
<div class="login-layout mt-10 sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" class="sm:flex hidden"/>
<div class="flash bg-white rounded-lg w-full">
<div class="title text-xl text-gray-700 md:px-8 p-4 md:mt-3 font-500">账号信息</div>
<div class="sm:w-screen-md w-full sm:px-4 sm:py-2">
<Base :form="form"/>
</div>
</div>
</div>
</div>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type {User} from '@/api/system/user/model';
import {ref} from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "./components/UserMenu.vue";
import Base from './components/Base.vue';
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const router = useRouter();
const website = useWebsite()
const userInfo = ref<User>();
const activeIndex = ref('');
// 配置信息
const {form, assignFields} = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true,
tenantId: undefined,
tenantName: undefined
});
useHead({
title: `用户中心`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify} = await useServerRequest<ApiResult<User>>('/auth/user', {
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if (modify.value?.code == 0) {
ElMessage.success('修改成功')
}
}
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
userInfo.value = response.value?.data;
assignFields(response.value?.data);
}
}
const goBack = () => {
router.back(); // 返回上一页
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>

34
pages/user/logout.vue Normal file
View File

@@ -0,0 +1,34 @@
<template>
<div class="xl:w-screen-xl m-auto py-4 my-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 退出登录 </span>
</template>
<div class="login-layout mt-10 sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3">
</div>
</div>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useToken} from "~/composables/configState";
import UserMenu from "~/pages/user/components/UserMenu.vue";
import Base from "~/pages/user/components/Base.vue";
const token = useToken();
const router = useRouter();
token.value = '';
localStorage.clear();
setTimeout(() => {
navigateTo('/')
return;
}, 1000)
const goBack = () => {
router.back(); // 返回上一页
}
</script>

102
pages/user/order.vue Normal file
View File

@@ -0,0 +1,102 @@
<template>
<div class="xl:w-screen-xl m-auto py-4 my-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 订单列表 </span>
</template>
<div class="login-layout m-auto mt-10 sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3">
<div class=" bg-white rounded-lg w-full">
<div class="flash bg-white rounded-lg px-8 py-4 w-auto">
<Order :form="form" />
</div>
</div>
</div>
</div>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useConfigInfo, useToken, useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
import { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "./components/UserMenu.vue";
import Order from './components/Order.vue';
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const router = useRouter();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const activeIndex = ref('');
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
const tableData = ref<any[]>();
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
// const {data: response} = await useServerRequest<ApiResult<Order>>('/system/order',{baseURL: runtimeConfig.public.apiServer})
// if(response.value?.data){
// console.log(response.value,'order')
// // userInfo.value = response.value?.data;
// // assignFields(response.value?.data);
// }
}
const goBack = () => {
router.back(); // 返回上一页
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{ immediate: true }
);
</script>

151
pages/user/password.vue Normal file
View File

@@ -0,0 +1,151 @@
<template>
<div class="xl:w-screen-xl m-auto py-4 my-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 用户中心 </span>
</template>
<div class="login-layout m-auto mt-10 sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" class="sm:flex hidden"/>
<div class="flash bg-white rounded-lg w-full">
<div class="title text-xl text-gray-700 md:px-8 p-4 md:mt-3 font-500">修改密码</div>
<div class="sm:w-screen-md w-full sm:px-4 sm:py-2">
<Password :form="form"/>
</div>
</div>
</div>
</div>
</el-page-header>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import {useConfigInfo, useToken, useWebsite} from "~/composables/configState";
import useFormData from '@/utils/use-form-data';
import type {User} from '@/api/system/user/model';
import {ref} from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "./components/UserMenu.vue";
import Password from './components/Password.vue';
import type {CaptchaResult} from "~/api/passport/login/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const router = useRouter();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const activeIndex = ref('');
// 验证码 base64 数据
const captcha = ref('');
// 验证码内容, 实际项目去掉
const text = ref('');
// 图形验证码
const imgCode = ref('');
// 发送验证码按钮loading
const codeLoading = ref(false);
// 验证码倒计时时间
const countdownTime = ref(0);
// 验证码倒计时定时器
let countdownTimer: number | null = null;
// 配置信息
const {form, assignFields} = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
oldPassword: '',
password: '',
password2: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
/* 发送短信验证码 */
const sendCode = async () => {
if (!form.phone) {
ElMessage.error('请输入手机号码');
return;
}
imgCode.value = text.value;
codeLoading.value = true;
const {data: smsCode} = await useServerRequest<ApiResult<CaptchaResult>>('/sendSmsCaptcha', {
baseURL: runtimeConfig.public.apiServer, method: "post", body: {
phone: form.phone
}
});
if (smsCode.value) {
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
}
};
const onSubmit = async () => {
const {data: modify} = await useServerRequest<ApiResult<User>>('/auth/password', {
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if (modify.value?.code == 0) {
ElMessage.success('修改成功')
}
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
userInfo.value = response.value?.data;
assignFields(response.value?.data);
}
}
const goBack = () => {
router.back(); // 返回上一页
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{immediate: true}
);
</script>

View File

@@ -11,7 +11,7 @@ export const request = <T>(url:string, options?: UseFetchOptions<T, unknown>) =>
onRequest({ options }: any){ onRequest({ options }: any){
let token = '' let token = ''
if(import.meta.client){ if(import.meta.client){
token = useToken().value token = `${useToken().value}`
} }
options.headers = { options.headers = {
TenantId, TenantId,

BIN
归档.zip Normal file

Binary file not shown.