新版本官网优化完成

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

@@ -6,13 +6,8 @@
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</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-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-image
:src="item.image"
@@ -24,9 +19,9 @@
{{ 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="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>
@@ -117,7 +112,6 @@ const reload = async () => {
const goBack = () => {
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,50 +6,70 @@
<template #content>
<span class="font-600 mr-3"> 文章详情 </span>
</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-card shadow="hover" class=" my-5">
<el-card shadow="hover" class=" my-5 px-2">
<!-- 新闻详细 -->
<div class="news_detail bg-white">
<h1 class="pt-5 text-2xl">{{ form.title }}</h1>
<div class="clearfix">
<h3 class=" text-gray-400">
{{ $t('createTime') }}<span>{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span>
{{ $t('author') }}<span>{{ form.nickname }}</span>
{{ $t('click') }}<span>{{ getViews(form) }}</span>
</h3>
<div class="share">
<!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox">
<a href="#" class="bds_more" data-cmd="more"></a>
<a href="#" class="bds_qzone" data-cmd="qzone"></a>
<a href="#" class="bds_tsina" data-cmd="tsina"></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>
<div class=" bg-white">
<h1 class="pt-5 text-3xl">{{ form.title }}</h1>
<div class="flex items-center justify-between py-4">
<el-space size="large" class="text-gray-400">
<span>{{ $t('createTime') }}{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span>
<span>{{ $t('author') }}{{ form.author }}</span>
<span>{{ $t('click') }}{{ getViews(form) }}</span>
<span v-if="form.source">文章来源{{ form.source }}</span>
</el-space>
<!-- Baidu Button BEGIN -->
<el-space class="flex items-center">
<!-- <a href="#" class="bds_more" data-cmd="more">朋友圈</a>-->
<!-- <a href="#" class="bds_qzone" data-cmd="qzone">QQ空间</a>-->
<!-- <a href="#" class="bds_tsina" data-cmd="tsina">新浪</a>-->
<!-- <a href="#" class="bds_tqq" data-cmd="tqq">腾讯</a>-->
<!-- <a href="#" class="bds_weixin" data-cmd="weixin">微信</a>-->
</el-space>
</div>
<!-- 文章摘要 -->
<div v-if="form.comments" class="screen-item bg-orange-50 text-gray-600 p-3 text-lg">
{{ form.comments }}
</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>
</div>
</el-scrollbar>
</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>
<Tags :data="form.tags" />
<NextArticle :articleId="articleId" />
</div>
<!-- 最近浏览 -->
<CmsArticleRecently :data="form" type="article" />
<!-- 相关产品和相关新闻 -->
<div class="relate_list">
<CmsProductRelated :data="form" />
<CmsArticleRelated :data="form" />
</div>
</el-card>
<!-- 最近浏览 -->
<CmsArticleRecently :data="form" type="article" />
<!-- 相关产品和相关新闻 -->
<div class="relate_list">
<CmsProductRelated :data="form" />
<CmsArticleRelated :data="form" />
</div>
</el-page-header>
</div>
</template>
@@ -61,9 +81,7 @@ import {getNavIdByParamsId, getViews, locationUrl, navTo, paramsId} from "~/util
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import useFormData from "~/utils/use-form-data";
import dayjs from "dayjs";
import Banner from "@/components/Banner.vue";
import type {Layout} from "~/api/layout/model";
import Left from "~/components/Left.vue";
import {
getCmsArticle
} from "~/api/cms/cmsArticle";
@@ -71,8 +89,6 @@ import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import CmsArticleRecently from "~/components/CmsRecently.vue";
import Tags from "~/components/Tags.vue";
import Content from "~/components/Content.vue";
import CmsArticleList from "~/components/CmsArticleList.vue";
import PageBanner from "~/pages/item/components/PageBanner.vue";
// 引入状态管理
const route = useRoute();
@@ -83,6 +99,7 @@ const i18n = useI18n();
const breadcrumb = ref<BreadcrumbItem>();
const showPassword = ref<boolean>();
const category = ref<CmsNavigation[]>([]);
const srcList = ref<any[]>([]);
// 配置信息
const {form, assignFields} = useFormData<CmsArticle>({
@@ -182,6 +199,13 @@ const reload = async () => {
await getCmsArticle(articleId.value).then(data => {
assignFields(data)
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) {
listCmsNavigation({parentId: data.parentId}).then(list => {
@@ -223,4 +247,14 @@ watch(
<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>

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>
<PageBanner :form="page" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="mb-5">
<el-descriptions title="参数信息" :column="2" border>
<el-descriptions-item :span="2" label="产品名称">{{page.title}}</el-descriptions-item>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.title}}</span></el-descriptions-item>
<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">
<!-- 主体部分 -->
<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"/>
<el-divider />
<AppInfo :form="form" />
<div class="screen-item my-6">
<el-descriptions title="截屏" />
<el-scrollbar>
<div class="flex" v-if="form.files">
<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"
:max-scale="7"
:min-scale="0.2"
@@ -43,159 +28,148 @@
:initial-index="4"
fit="contain"
/>
</div>
</template>
<template v-if="form.content">
<p v-html="form.content" class="content"></p>
</template>
</el-card>
<!-- 产品评论 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
</el-col>
<el-col :span="6" :xs="24">
<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>
</el-scrollbar>
<p v-html="form?.content || '介绍'" class="content"></p>
</div>
<el-divider />
<!-- 评分及评价 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
<div class="h-[100px]"></div>
</el-page-header>
</div>
</template>
<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 {useServerRequest} from "~/composables/useServerRequest";
import {useLayout, usePage, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {usePage} from "~/composables/configState";
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue';
import AppInfo from './components/AppInfo.vue';
import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
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 {getCmsWebsiteAll} from "~/api/cms/cmsWebsite";
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
// 引入状态管理
const route = useRoute();
const router = useRouter();
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 commentsTotal = ref(0);
const commentsPage = ref(1);
const navId = ref();
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]);
// 配置信息
const {form, assignFields} = useFormData<CmsArticle>({
// 文章id
articleId: undefined,
// 文章模型
model: undefined,
// 文章标题
title: undefined,
// 分类类型
type: undefined,
// 展现方式
showType: undefined,
// 文章类型
categoryId: undefined,
// 文章分类
categoryName: undefined,
parentId: 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,
//
const {form, assignFields} = useFormData<CmsWebsite>({
// 站点ID
websiteId: undefined,
// 网站名称
websiteName: undefined,
// 网站标识
websiteCode: undefined,
// 网站LOGO
websiteIcon: undefined,
// 网站LOGO
websiteLogo: undefined,
// 网站LOGO(深色模式)
websiteDarkLogo: undefined,
// 网站类型
websiteType: undefined,
// 评分
rate: 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,
// 状态
// 是否推荐
recommend: undefined,
// 运行状态
running: undefined,
// 状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停
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,
// 更新时间
// 修改时间
updateTime: undefined,
// 租户ID
tenantId: undefined,
// 租户名称
tenantName: undefined,
// 租户logo
logo: undefined,
// 详情页路径
detail: undefined
});
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
// 网站配置
config: undefined,
topNavs: undefined,
bottomNavs: undefined,
loginUser: undefined
});
const doComments = async (page: any) => {
@@ -219,40 +193,37 @@ const reloadComments = async () => {
}
}
const goBack = () => {
router.back();
}
// 读取导航详情
const reload = async () => {
getCmsArticle(navId.value).then(data => {
getCmsWebsiteAll(navId.value).then(data => {
// 获取栏目信息
page.value = 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({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
description: data.comments || data.websiteName,
keywords: data.websiteName,
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 => {
console.log(err,'加载失败...')
})
@@ -275,4 +246,14 @@ watch(
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>

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>
<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
ref="formRef"
:model="form"
@@ -8,54 +14,42 @@
size="large"
status-icon
>
<el-card shadow="hover" v-if="comments" class="mb-5">
<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 v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
style="border-bottom:1px solid #f3f3f3">
<el-space class="user-info flex items-start" style="align-items:normal">
<div class="avatar">
<el-avatar :src="item.logo"/>
</div>
<div class="nickname flex flex-col">
<el-space class="text-sm text-gray-900">
<span class="font-bold">{{ item.tenantName }}</span>
<el-rate v-model="item.rate" disabled size="small"/>
</el-space>
<span class="text-xs text-gray-400">{{ item.createTime }}</span>
<div class="comments py-2" v-html="item.comments"></div>
<template v-if="item.children" v-for="(sub,index2) in item.children" :key="index2">
<el-space class="text-sm text-gray-900">
<el-avatar :src="sub.logo" size="small"/>
<span class="font-bold">{{ sub.tenantName }}</span>
<span class="text-xs text-gray-400">{{ sub.createTime }}</span>
</el-space>
<div class="comments py-2" v-html="sub.comments"></div>
</template>
</div>
</el-space>
<template v-if="comments && comments.length > 0">
<div class="w-full">
<div v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
style="border-bottom:1px solid #f3f3f3">
<el-space class="user-info flex items-start" style="align-items:normal">
<div class="avatar">
<el-avatar :src="item.logo"/>
</div>
</div>
<div class="pagination flex justify-center">
<el-pagination background layout="prev, pager, next" size="small" :total="count" @change="onPageChange"/>
</div>
</template>
<template v-else>
暂无用户评论
</template>
</template>
</el-card>
<div class="nickname flex flex-col">
<el-space class="text-sm text-gray-900">
<span class="font-bold">{{ item.tenantName }}</span>
<el-rate v-model="item.rate" disabled size="small"/>
</el-space>
<span class="text-xs text-gray-400">{{ item.createTime }}</span>
<div class="comments py-2" v-html="item.comments"></div>
<template v-if="item.children" v-for="(sub,index2) in item.children" :key="index2">
<el-space class="text-sm text-gray-900">
<el-avatar :src="sub.logo" size="small"/>
<span class="font-bold">{{ sub.tenantName }}</span>
<span class="text-xs text-gray-400">{{ sub.createTime }}</span>
</el-space>
<div class="comments py-2" v-html="sub.comments"></div>
</template>
</div>
</el-space>
</div>
</div>
<div class="pagination flex justify-center">
<el-pagination background layout="prev, pager, next" size="small" :total="count" @change="onPageChange"/>
</div>
</template>
<template v-else>
暂无用户评论
</template>
<!-- 发表评论 -->
<el-dialog
v-model="visible"

View File

@@ -1,71 +1,36 @@
<template>
<div class="banner m-auto relative sm:flex mt-15">
<svg viewBox="0 0 1440 181" fill="none" xmlns="http://www.w3.org/2000/svg"
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="banner m-auto relative sm:flex">
<div class="md:w-screen-xl m-auto py-10">
<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="flex flex-1">
<template v-if="form.image">
<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"/>
<!-- <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 v-if="form.websiteLogo">
<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-6"/>
</template>
<div class="title flex flex-col">
<h1
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-4xl">
<span v-if="form.title">{{ form.title }}</span>
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-3xl">
<span v-if="form.websiteName">{{ form.websiteName }}</span>
</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">
{{ form?.comments || desc }}
</div>
<!-- <a class="company-name text-sm my-1">-->
<!-- {{ form.companyName || 'WebSoft Inc.' }}-->
<!-- </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>
{{ form?.comments }}
</div>
<el-space class="btn">
<nuxt-link :to="`https://${form.websiteCode}.websoft.top`"><el-button type="primary" round>控制台</el-button></nuxt-link>
</el-space>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {FullScreen} from '@element-plus/icons-vue'
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {ApiResult} from "~/api";
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();
@@ -74,7 +39,7 @@ const props = withDefaults(
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsArticle;
form?: CmsWebsite;
value?: number;
}>(),
{}
@@ -95,7 +60,7 @@ const onBuy = (item: Company) => {
if (!token.value || token.value == '') {
ElMessage.error('请先登录');
setTimeout(() => {
openSpmUrl(`/product/create`, item, item.companyId)
openUrl(`/product/create`, item, item.companyId)
}, 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>
<PageBanner :form="page" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="mb-5">
<el-descriptions title="参数信息" :column="2" border>
<el-descriptions-item :span="2" label="产品名称">{{page.title}}</el-descriptions-item>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.title}}</span></el-descriptions-item>
<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">
<!-- 主体部分 -->
<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"/>
<el-divider />
<AppInfo :form="form" />
<div class="screen-item my-6">
<el-descriptions title="截屏" />
<el-scrollbar>
<div class="flex" v-if="form.files">
<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"
:max-scale="7"
:min-scale="0.2"
@@ -43,161 +28,150 @@
:initial-index="4"
fit="contain"
/>
</div>
</template>
<template v-if="form.content">
<p v-html="form.content" class="content"></p>
</template>
</el-card>
<!-- 产品评论 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
</el-col>
<el-col :span="6" :xs="24">
<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>
</el-scrollbar>
<p v-html="form?.content || '介绍'" class="content"></p>
</div>
<el-divider />
<!-- 评分及评价 -->
<Comments :productId="form.companyId" :comments="comments" :count="commentsTotal" @done="doComments" />
<div class="h-[100px]"></div>
</el-page-header>
</div>
</template>
<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 {useServerRequest} from "~/composables/useServerRequest";
import {useLayout, usePage, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {usePage} from "~/composables/configState";
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue';
import AppInfo from './components/AppInfo.vue';
import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
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 {getCmsWebsiteAll} from "~/api/cms/cmsWebsite";
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 router = useRouter();
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 commentsTotal = ref(0);
const commentsPage = ref(1);
const navId = ref();
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]);
// 配置信息
const {form, assignFields} = useFormData<CmsWebsite>({
// 文章id
articleId: undefined,
// 文章模型
model: undefined,
// 文章标题
title: undefined,
// 分类类型
type: undefined,
// 展现方式
showType: undefined,
// 文章类型
categoryId: undefined,
// 文章分类
categoryName: undefined,
parentId: 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,
//
// 站点ID
websiteId: undefined,
// 网站名称
websiteName: undefined,
// 网站标识
websiteCode: undefined,
// 网站LOGO
websiteIcon: undefined,
// 网站LOGO
websiteLogo: undefined,
// 网站LOGO(深色模式)
websiteDarkLogo: undefined,
// 网站类型
websiteType: undefined,
// 评分
rate: 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,
// 状态
// 是否推荐
recommend: undefined,
// 运行状态
running: undefined,
// 状态 0未开通 1运行中 2维护中 3已关闭 4已欠费停机 5违规关停
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,
// 更新时间
// 修改时间
updateTime: undefined,
// 租户ID
tenantId: undefined,
// 租户名称
tenantName: undefined,
// 租户logo
logo: undefined,
// 详情页路径
detail: undefined
});
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
// 网站配置
config: undefined,
topNavs: undefined,
bottomNavs: undefined,
loginUser: undefined
});
const doComments = async (page: any) => {
@@ -221,9 +195,13 @@ const reloadComments = async () => {
}
}
const goBack = () => {
router.back();
}
// 读取导航详情
const reload = async () => {
getCmsWebsite(navId.value).then(data => {
getCmsWebsiteAll(navId.value).then(data => {
// 获取栏目信息
assignFields(data)
page.value = {
@@ -231,7 +209,15 @@ const reload = async () => {
title: data.websiteName,
categoryName: '应用市场',
...data,
}
}
// 应用截图
if(data.files){
const imgArr = JSON.parse(data.files);
imgArr.map((item: any) => {
srcList.value.push(item.url)
})
}
// 设置页面标题
useSeoMeta({
@@ -240,25 +226,6 @@ const reload = async () => {
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 => {
console.log(err,'加载失败...')
})
@@ -281,4 +248,14 @@ watch(
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>

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>
<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
ref="formRef"
:model="form"
@@ -8,54 +14,42 @@
size="large"
status-icon
>
<el-card shadow="hover" v-if="comments" class="mb-5">
<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 v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
style="border-bottom:1px solid #f3f3f3">
<el-space class="user-info flex items-start" style="align-items:normal">
<div class="avatar">
<el-avatar :src="item.logo"/>
</div>
<div class="nickname flex flex-col">
<el-space class="text-sm text-gray-900">
<span class="font-bold">{{ item.tenantName }}</span>
<el-rate v-model="item.rate" disabled size="small"/>
</el-space>
<span class="text-xs text-gray-400">{{ item.createTime }}</span>
<div class="comments py-2" v-html="item.comments"></div>
<template v-if="item.children" v-for="(sub,index2) in item.children" :key="index2">
<el-space class="text-sm text-gray-900">
<el-avatar :src="sub.logo" size="small"/>
<span class="font-bold">{{ sub.tenantName }}</span>
<span class="text-xs text-gray-400">{{ sub.createTime }}</span>
</el-space>
<div class="comments py-2" v-html="sub.comments"></div>
</template>
</div>
</el-space>
<template v-if="comments && comments.length > 0">
<div class="w-full">
<div v-for="(item,index) in comments" :key="index"
class="flex flex-col border-b-2 border-gray-200 pb-2 mb-3"
style="border-bottom:1px solid #f3f3f3">
<el-space class="user-info flex items-start" style="align-items:normal">
<div class="avatar">
<el-avatar :src="item.logo"/>
</div>
</div>
<div class="pagination flex justify-center">
<el-pagination background layout="prev, pager, next" size="small" :total="count" @change="onPageChange"/>
</div>
</template>
<template v-else>
暂无用户评论
</template>
</template>
</el-card>
<div class="nickname flex flex-col">
<el-space class="text-sm text-gray-900">
<span class="font-bold">{{ item.tenantName }}</span>
<el-rate v-model="item.rate" disabled size="small"/>
</el-space>
<span class="text-xs text-gray-400">{{ item.createTime }}</span>
<div class="comments py-2" v-html="item.comments"></div>
<template v-if="item.children" v-for="(sub,index2) in item.children" :key="index2">
<el-space class="text-sm text-gray-900">
<el-avatar :src="sub.logo" size="small"/>
<span class="font-bold">{{ sub.tenantName }}</span>
<span class="text-xs text-gray-400">{{ sub.createTime }}</span>
</el-space>
<div class="comments py-2" v-html="sub.comments"></div>
</template>
</div>
</el-space>
</div>
</div>
<div class="pagination flex justify-center">
<el-pagination background layout="prev, pager, next" size="small" :total="count" @change="onPageChange"/>
</div>
</template>
<template v-else>
暂无用户评论
</template>
<!-- 发表评论 -->
<el-dialog
v-model="visible"

View File

@@ -1,71 +1,39 @@
<template>
<div class="banner m-auto relative sm:flex mt-15">
<svg viewBox="0 0 1440 181" fill="none" xmlns="http://www.w3.org/2000/svg"
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="banner m-auto relative sm:flex">
<div class="md:w-screen-xl m-auto py-10">
<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="flex flex-1">
<template v-if="form.image">
<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"/>
<!-- <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 v-if="form.websiteLogo">
<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-6"/>
</template>
<div class="title flex flex-col">
<h1
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-4xl">
<span v-if="form.title">{{ form.title }}</span>
class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-3xl lg:text-3xl">
<el-space>
<span>{{ form.websiteName }}</span>
</el-space>
</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">
{{ form?.comments || desc }}
</div>
<!-- <a class="company-name text-sm my-1">-->
<!-- {{ form.companyName || 'WebSoft Inc.' }}-->
<!-- </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>
</div>
<el-space class="btn">
<nuxt-link target="_blank"><el-button type="primary" round>获取</el-button></nuxt-link>
</el-space>
</div>
</div>
</div>
</div>
</div>
<!-- {{ form }}-->
</div>
</div>
</template>
<script setup lang="ts">
import {FullScreen} from '@element-plus/icons-vue'
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {ApiResult} from "~/api";
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();
@@ -74,7 +42,7 @@ const props = withDefaults(
title?: string;
desc?: string;
buyUrl?: string;
form?: CmsArticle;
form?: CmsWebsite;
value?: number;
}>(),
{}
@@ -95,7 +63,7 @@ const onBuy = (item: Company) => {
if (!token.value || token.value == '') {
ElMessage.error('请先登录');
setTimeout(() => {
openSpmUrl(`/product/create`, item, item.companyId)
navigateTo(`/product/create`)
}, 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"-->
<!-- />-->
<!-- </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-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">
@@ -41,7 +41,7 @@
</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" />
<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>
@@ -89,10 +89,11 @@ const layout = useLayout();
const where = reactive<CmsWebsiteParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
limit: 12,
status: undefined,
recommend: undefined,
categoryId: undefined,
lang: i18n.locale.value
lang: undefined
});
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>
</template>
<el-card shadow="hover" class=" my-5">
<!-- 内容组件 -->
<Content class="content bg-white mt-5" :data="page.design?.content" />
<!-- 新闻详细 -->
<div class=" bg-white">
<!-- 内容组件 -->
<Content class="content text-lg py-3" :data="page.design?.content" />
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
</div>
</el-card>
</el-page-header>
</div>
@@ -18,11 +24,12 @@
<script setup lang="ts">
import {useLayout, usePage} from "~/composables/configState";
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 { ArrowLeft,View } from '@element-plus/icons-vue'
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import Content from "~/components/Content.vue";
import Tags from "~/components/Tags.vue";
// 引入状态管理
const route = useRoute();

View File

@@ -3,24 +3,21 @@
<div class="flash">
</div>
<el-card class="m-5 w-screen-sm sm:w-[430px] sm:h-[520px] flex justify-around relative border-0" style="border: 0;">
<div class="login-bar absolute top-0 right-0 cursor-pointer" @click="onLoginBar">
<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">
<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>
<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>
<!-- 登录界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="loginBar">
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="账号登录" name="account">
<el-space class="tabs pt-3 text-xl flex justify-center" v-if="loginBar">
<el-tabs v-model="activeName" class="">
<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" placeholder="租户ID" :prefix-icon="Shop" v-model="form.tenantId" />-->
<!-- </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-input type="password" size="large" maxlength="100" placeholder="登录密码" :prefix-icon="Briefcase" v-model="form.password" />
@@ -67,20 +64,15 @@
</el-tabs>
</el-space>
<!-- 快捷登录 --->
<template v-if="loginBar && activeName == 'account'">
<!-- <div class="clearfix flex justify-center">-->
<!-- <el-divider>-->
<!-- <span class="text-gray-400">其他登录方式</span>-->
<!-- </el-divider>-->
<!-- </div>-->
<!-- <div class="clearfix flex justify-center">-->
<!-- <el-button circle :icon="ElIconUserFilled"></el-button>-->
<!-- </div>-->
<template v-if="loginBar">
<div class="clearfix flex justify-center">
<nuxt-link to="/passport/login?type=register"><span class="text-sm text-blue-400">免费注册</span></nuxt-link>
</div>
</template>
<!-- 注册界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="!loginBar">
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="手机号注册" name="sms">
<el-space class="tabs pt-3 text-xl flex justify-center" v-if="!loginBar">
<el-tabs v-model="activeName">
<el-tab-pane label="手机号注册" name="account">
<span class="text-sm text-gray-400">
未注册手机号验证通过后将自动注册
</span>
@@ -104,46 +96,13 @@
<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>
</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>
<div class="clearfix flex justify-center">
<nuxt-link to="/passport/login"><span class="text-sm text-blue-400">已有账号立即登录</span></nuxt-link>
</div>
</el-form>
</div>
</el-tab-pane>
@@ -157,14 +116,15 @@ import {useConfigInfo, useToken, useUser, useWebsite} from "~/composables/config
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
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 type {ApiResult} from "~/api";
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 route = useRoute();
const website = useWebsite();
const config = useConfigInfo();
const token = useToken();
@@ -295,20 +255,17 @@ const onSubmit = async () => {
* 短信验证码登录
*/
const onSubmitBySms = async () => {
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/loginBySms',{baseURL: runtimeConfig.public.apiServer,method: "post",body: {
phone: form.phone,
code: form.code,
isSuperAdmin: true
}})
// 登录成功
if(response.value?.code == 0){
ElMessage.success(response.value?.message)
await doLogin(response.value.data)
}
if(response.value?.code != 0){
ElMessage.error(response.value?.message)
await changeCaptcha()
}
loginBySms({
phone: form.phone,
code: form.code,
isSuperAdmin: true
}).then(response => {
ElMessage.success('登录成功')
doLogin(response)
}).catch(() => {
ElMessage.error('验证码错误')
changeCaptcha()
})
}
/**
@@ -319,31 +276,47 @@ const onRegister = async () => {
lock: true,
text: 'Loading'
})
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){
register({
companyName: '应用名称',
username: form.phone,
phone: form.phone,
password: form.password,
code: form.code,
email: form.email,
isSuperAdmin: true
}).then(response => {
loading.close();
ElMessage.success(response?.message)
doLogin(response.data)
}
if(response?.code != 0){
loading.close;
ElMessage.error(response?.message)
changeCaptcha()
}
ElMessage.success('注册成功')
doLogin(response)
}).catch(() => {
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('Avatar',data.user.avatar);
localStorage.setItem('TID_ADMIN',data.user.tenantId);
// localStorage.setItem('TenantId',data.user.tenantId);
}
setTimeout(() => {
navigateTo('/')
return;
},500)
}
changeCaptcha();
watch(
() => route.query.type,
(type) => {
loginBar.value = true
activeName.value = 'account'
if(type == 'register'){
loginBar.value = false
}
},
{ immediate: true }
);
</script>
<style lang="less">
.login{

View File

@@ -1,36 +1,39 @@
<template>
<PageBanner title="入驻" desc="Register Account"/>
<div class="login-layout m-auto sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3 ">
<div class="flash bg-white rounded-lg px-8 py-4 w-full">
<Auth @done="reload"/>
</div>
</div>
<!-- 主体部分 -->
<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-page-header>
<el-card shadow="hover" class=" my-5 px-2">
<Auth @done="reload"/>
</el-card>
</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 { ref } from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
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 route = useRoute();
const router = useRouter();
const activeIndex = ref('');
const website = useWebsite()
const config = useConfigInfo();
const merchantApply = ref<ShopMerchantApply>();
const merchantApply = ref<any>();
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) {
merchantApply.value = response.value.data;
}
if(config.value){
if (config.value) {
useHead({
title: `实名认证 - ${config.value?.siteName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
@@ -38,11 +41,15 @@ const reload = async () => {
}
}
const goBack = () => {
router.back(); // 返回上一页
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
// reload();
},
{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">
<template v-if="form.image">
<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"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</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>