1
This commit is contained in:
19
pages/[...404].vue
Normal file
19
pages/[...404].vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
path: '/404'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card class="m-5 w-screen-sm mt-[300px] mb-[200px] m-auto">
|
||||
<!-- 异常状态 -->
|
||||
<el-result
|
||||
icon="warning"
|
||||
:title="`404 页面不存在`"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
12
pages/app/index.vue
Normal file
12
pages/app/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<!-- 底部菜单 -->
|
||||
<TabBar />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import TabBar from "~/components/AppFooter/TabBar/TabBar.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
329
pages/article/[id].vue
Normal file
329
pages/article/[id].vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<div class="content-wrapper mt-5">
|
||||
<!-- 左侧二级分类导航 -->
|
||||
<div v-if="category.length > 0" class="category-sidebar">
|
||||
<div class="category-header">
|
||||
<span class="category-title">分类导航</span>
|
||||
</div>
|
||||
<div class="category-list">
|
||||
<div
|
||||
v-for="(item, index) in category"
|
||||
:key="index"
|
||||
class="category-item"
|
||||
:class="{ 'active': currentCategoryId === item.navigationId }"
|
||||
@click="switchCategory(item)"
|
||||
>
|
||||
<span class="category-name">{{ item.title }}</span>
|
||||
<el-icon v-if="currentCategoryId === item.navigationId" class="active-icon">
|
||||
<Check />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="section-content" :class="{ 'with-sidebar': category.length > 0 }">
|
||||
<div class="news-list">
|
||||
<div v-for="(item, index) in list" :key="index" class="news-item">
|
||||
<a :href="`/detail/${item.articleId}`" class="news-link">
|
||||
<span class="news-title">{{ item.title }}</span>
|
||||
<span class="news-date">{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-page-header>
|
||||
<Pagination :pageSize="where.limit" :total="total" @done="search"/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft, Check} 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} from "~/utils/common";
|
||||
import dayjs from "dayjs";
|
||||
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<number>(0);
|
||||
const currentCategoryId = ref<number | undefined>();
|
||||
|
||||
// 获取状态
|
||||
const page = usePage();
|
||||
const layout = useLayout();
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsArticleParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 12,
|
||||
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;
|
||||
|
||||
// 设置当前选中的分类ID
|
||||
if (data.parentId == 0 && category.value.length > 0) {
|
||||
where.parentId = data.navigationId;
|
||||
currentCategoryId.value = undefined; // 显示所有子分类的文章
|
||||
} else {
|
||||
where.categoryId = data.navigationId;
|
||||
currentCategoryId.value = data.navigationId;
|
||||
}
|
||||
|
||||
// 加载文章列表
|
||||
loadArticles();
|
||||
})
|
||||
}).catch(err => {
|
||||
console.log(err, '加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
// 加载文章列表
|
||||
const loadArticles = () => {
|
||||
pageCmsArticle(where).then(response => {
|
||||
if (response) {
|
||||
total.value = response?.count;
|
||||
list.value = response?.list;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const switchCategory = (categoryItem: CmsNavigation) => {
|
||||
currentCategoryId.value = categoryItem.navigationId;
|
||||
|
||||
// 重置搜索条件
|
||||
where.page = 1;
|
||||
where.parentId = undefined;
|
||||
where.categoryId = categoryItem.navigationId;
|
||||
|
||||
// 重新加载文章列表
|
||||
loadArticles();
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.category-sidebar {
|
||||
width: 200px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
height: fit-content;
|
||||
|
||||
.category-header {
|
||||
background: #1b850c;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
|
||||
.category-title {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.category-list {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #e8f5e8;
|
||||
color: #1b850c;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #1b850c;
|
||||
color: white;
|
||||
|
||||
.category-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.active-icon {
|
||||
font-size: 16px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-content {
|
||||
flex: 1;
|
||||
padding: 16px 20px;
|
||||
overflow: hidden;
|
||||
|
||||
&.with-sidebar {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.news-list {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.news-link {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #1b850c;
|
||||
background-color: rgba(27, 133, 12, 0.05);
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.news-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.news-date {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.content-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.category-sidebar {
|
||||
width: 100%;
|
||||
|
||||
.category-list {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding: 8px;
|
||||
gap: 8px;
|
||||
|
||||
.category-item {
|
||||
white-space: nowrap;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
min-width: fit-content;
|
||||
|
||||
&.active {
|
||||
border-color: #1b850c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-content.with-sidebar {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
pages/article/photo/[id].vue
Normal file
131
pages/article/photo/[id].vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<el-row :gutter="24" id="container" class="clearfix mt-5">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="6" :xs="24" 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 transition-transform duration-300 ease-in-out hover:scale-110"/>
|
||||
<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="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 :pageSize="where.limit" :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";
|
||||
|
||||
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: 12,
|
||||
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;
|
||||
// 加载文章列表
|
||||
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(); // 返回上一页
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsArticleParam) => {
|
||||
where.page = data.page;
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
</script>
|
||||
183
pages/case/[id].vue
Normal file
183
pages/case/[id].vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<el-row :gutter="24" id="container" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :span="6" :xs="24" class="left mb-8">
|
||||
<el-card :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" shadow="hover" @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="text-xl cursor-pointer items-center line-clamp-1">
|
||||
{{ 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 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: 12,
|
||||
status: 0,
|
||||
recommend: 1,
|
||||
order: 'desc',
|
||||
sort: 'actualViews',
|
||||
parentId: undefined,
|
||||
categoryId: undefined,
|
||||
lang: i18n.locale.value
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 加载页面数据
|
||||
const reload = 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>
|
||||
89
pages/case/components/AppInfo.vue
Normal file
89
pages/case/components/AppInfo.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div v-if="form" class="app-info bg-white py-5 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">类别</div>
|
||||
<el-icon size="24" class="py-1"><Monitor /></el-icon>
|
||||
<span class="text-gray-500">{{ form.industryParent || '网站' }}</span>
|
||||
</div>
|
||||
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
|
||||
<nuxt-link :to="`https://${form.domain}`" class="item text-center flex flex-col items-center">
|
||||
<div class="text-gray-400">域名</div>
|
||||
<div>
|
||||
<svg t="1739721752315" class="icon pt-2" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11933" width="22" height="22"><path d="M319.9 428.9h37.7l-66.1 216.7H259c-28.9-98-43.9-149.1-44.9-154.3-2.1-4.6-3.1-12.4-3.1-23.2h-1c0 4.6-1 12.4-3.1 23.2-2.1 4.6-18.1 56.2-48 154.3h-32.5l-63-216.7h37.7c23.7 91.3 36.6 141.4 38.7 149.6 0 0.5 0.5 1.5 1 3.1 1.5 9.8 2.1 17 2.1 21.2h1c0-7.7 1.5-15.5 4.1-24.3 2.1-5.7 17-55.7 44.9-149.6h35.6c25.8 94.4 39.7 144.5 41.8 149.6 2.6 8.3 4.1 16.5 4.1 24.3h1c0-4.1 0.5-11.4 2.1-21.2 0.5-1.5 1-2.6 1-3.1 2.7-8.2 16.1-58.2 41.4-149.6z m300.8 0h37.7l-66.1 216.7h-32.5c-28.9-98-43.9-149.1-44.9-154.3-2.1-4.6-3.1-12.4-3.1-23.2h-1c0 4.6-1 12.4-3.1 23.2-2.1 4.6-18.1 56.2-48 154.3h-32.5l-63-216.7h37.7c23.7 91.3 36.6 141.4 38.7 149.6 0 0.5 0.5 1.5 1 3.1 1.5 9.8 2.1 17 2.1 21.2h0.5c0-7.7 1.5-15.5 4.1-24.3 2.1-5.7 17-55.7 44.9-149.6h35.6c25.8 94.4 39.7 144.5 41.8 149.6 2.6 8.3 4.1 16.5 4.1 24.3h1c0-4.1 0.5-11.4 2.1-21.2 0.5-1.5 1-2.6 1-3.1 3.2-8.2 16.6-58.2 41.9-149.6z m300.9 0h37.7l-66.1 216.7h-32.5c-28.9-98-43.9-149.1-44.9-154.3-2.1-4.6-3.1-12.4-3.1-23.2h-1c0 4.6-1 12.4-3.1 23.2-2.1 4.6-18.1 56.2-48 154.3h-32.5l-63-216.7h37.7c23.7 91.3 36.6 141.4 38.7 149.6 0 0.5 0.5 1.5 1 3.1 1.5 9.8 2.1 17 2.1 21.2h1c0-7.7 1.5-15.5 4.1-24.3 2.1-5.7 17-55.7 44.9-149.6h35.6C856 523.4 870 573.4 872 578.6c2.6 8.3 4.1 16.5 4.1 24.3h1c0-4.1 0.5-11.4 2.1-21.2 0.5-1.5 1-2.6 1-3.1 2.7-8.3 16.1-58.3 41.4-149.7z m-755.5-39.2C222.9 254 357 158.5 513.4 158.5S803.9 254 860.7 389.7h45.9c-58.8-160-212.6-274-393.2-274s-333.9 114-393.2 274h45.9z m691 299.3c-58.8 131.1-190.4 222.9-343.7 222.9-153.3 0-284.8-91.3-343.7-222.9h-46.4c61.4 155.3 212.6 265.2 390.1 265.2 177 0 328.7-109.9 390.1-265.2h-46.4z" fill="" p-id="11934"></path><path d="M513.4 217.8c-123.2 0-230.1 69.7-283.4 171.9h-14.9c54.4-109.8 167.6-185.2 298.4-185.2" fill="" p-id="11935"></path></svg>
|
||||
</div>
|
||||
<!-- <el-icon size="24" class="py-1"><Compass /></el-icon>-->
|
||||
<span class="text-gray-500">{{ form.domain }}</span>
|
||||
</nuxt-link>
|
||||
<el-divider class="opacity-40" style="height: 40px" direction="vertical" />
|
||||
<nuxt-link :to="`/market/user/${form.userId}`" 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" />
|
||||
<nuxt-link :to="`http://git.gxwebsoft.com`" class="item text-center">
|
||||
<div class="text-gray-400">仓库</div>
|
||||
<div class="text-2xl font-bold">
|
||||
<!-- <svg t="1739721615244" class="icon pt-2" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10773" width="22" height="22"><path d="M512 64c81.636 0 156.8 19.911 225.493 59.733s122.951 94.08 162.773 162.773S960 430.364 960 512s-19.911 156.8-59.733 225.493-94.08 122.951-162.773 162.773S593.636 960 512 960s-156.8-19.911-225.493-59.733-122.951-94.08-162.773-162.773S64 593.636 64 512s19.911-156.8 59.733-225.493 94.08-122.951 162.773-162.773S430.364 64 512 64z m119.467 812.373c57.742-17.92 108.516-48.782 152.32-92.587 43.804-43.805 75.164-94.578 94.08-152.32 18.916-57.742 23.396-117.476 13.44-179.2-9.956-61.724-32.853-117.476-68.693-167.253-35.84-49.778-81.138-88.604-135.893-116.48C631.964 140.658 573.724 126.72 512 126.72s-119.964 13.938-174.72 41.813-100.053 66.702-135.893 116.48-58.738 105.529-68.693 167.253c-9.956 61.724-5.476 121.458 13.44 179.2s50.276 108.516 94.08 152.32c43.804 43.804 94.578 74.667 152.32 92.587h2.987c5.973 0 10.951-1.493 14.933-4.48 3.982-2.987 5.973-7.467 5.973-13.44v-65.707l-20.907 2.987H377.6c-19.911 1.991-38.329-1.991-55.253-11.947S293.974 759.893 288 741.973l-17.92-32.853-11.947-11.947-23.893-17.92c-3.982-3.982-5.973-6.969-5.973-8.96s0.996-3.982 2.987-5.973l14.933-2.987c5.973 0 11.947 1.493 17.92 4.48 5.973 2.987 11.947 5.476 17.92 7.467l11.947 14.933 11.947 11.947c7.964 13.938 17.422 24.889 28.373 32.853 10.951 7.964 23.893 11.449 38.827 10.453 14.933-0.996 29.369-4.48 43.307-10.453 1.991-9.956 4.978-19.413 8.96-28.373 3.982-8.96 8.96-16.427 14.933-22.4-25.884-1.991-49.778-7.467-71.68-16.427-21.902-8.96-40.818-21.404-56.747-37.333-15.929-15.929-26.88-34.844-32.853-56.747-7.964-25.884-11.947-52.764-11.947-80.64 0-17.92 3.484-35.84 10.453-53.76 6.969-17.92 16.427-33.849 28.373-47.787-1.991-7.964-3.484-15.431-4.48-22.4s-1.493-14.933-1.493-23.893 0.996-17.92 2.987-26.88c1.991-8.96 3.982-18.418 5.973-28.373h8.96c7.964 0 16.427 0.996 25.387 2.987s17.422 4.978 25.387 8.96l26.88 14.933 20.907 11.947c63.716-17.92 127.431-17.92 191.147 0l20.907-11.947 26.88-14.933c7.964-3.982 15.929-6.969 23.893-8.96 7.964-1.991 16.924-2.987 26.88-2.987h5.973c3.982 9.956 6.969 19.413 8.96 28.373 1.991 8.96 2.987 17.92 2.987 26.88 0 8.96-0.498 16.924-1.493 23.893s-2.489 14.436-4.48 22.4c11.947 13.938 21.404 29.867 28.373 47.787 6.969 17.92 10.453 35.84 10.453 53.76 0 27.876-3.982 54.756-11.947 80.64-5.973 21.902-16.924 40.818-32.853 56.747-15.929 15.929-35.342 28.373-58.24 37.333-22.898 8.96-47.289 14.436-73.173 16.427 9.956 7.964 16.924 18.418 20.907 31.36 3.982 12.942 5.973 26.382 5.973 40.32v104.533c0 5.973 1.991 10.453 5.973 13.44 3.982 2.987 7.964 4.48 11.947 4.48h5.972z" p-id="10774"></path></svg>-->
|
||||
<svg t="1739720696276" class="icon pt-2" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4176" width="22" height="22"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="4177"></path></svg>
|
||||
</div>
|
||||
<span class="text-gray-500">3.12 MB</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">插件ID</div>
|
||||
<el-icon size="24" class="py-1"><Cpu /></el-icon>
|
||||
<span class="text-gray-500">{{ form.websiteId }}</span>
|
||||
</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">下载次数</div>-->
|
||||
<!-- <el-icon size="24" class="py-1"><Download /></el-icon>-->
|
||||
<!-- <span class="text-gray-500">{{ form.downloads }}</span>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft,View, Menu, Search,Compass, Cpu,Monitor, Download, Platform, Avatar } from '@element-plus/icons-vue'
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
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>
|
||||
195
pages/case/components/Comments.vue
Normal file
195
pages/case/components/Comments.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<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"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="w-full sm:py-2"
|
||||
size="large"
|
||||
status-icon
|
||||
>
|
||||
<template v-if="comments && comments.length > 0">
|
||||
<div class="w-full p-4 bg-white">
|
||||
<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>
|
||||
</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"
|
||||
title="发表评论"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible = false"
|
||||
>
|
||||
<el-form-item prop="rate">
|
||||
<el-rate v-model="form.rate" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="comments">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="最多300字,支持 markdown 语法。请认真填写评论内容,以便于帮助作者更好完善插件,如需反馈问题,请到下方插件提问进行提交。"/>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 发起投诉 -->
|
||||
<el-dialog
|
||||
v-model="visible2"
|
||||
title="为什么举报此内容?"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible2 = false"
|
||||
>
|
||||
<el-checkbox-group v-model="checkList">
|
||||
<el-checkbox label="与我无关" value="与我无关"/>
|
||||
<el-checkbox label="文章过时" value="文章过时"/>
|
||||
<el-checkbox label="标题有误" value="标题有误"/>
|
||||
<el-checkbox label="图像质量差或视觉缺陷" value="图像质量差或视觉缺陷"/>
|
||||
<el-checkbox label="垃圾邮件" value="垃圾邮件"/>
|
||||
<el-checkbox label="成人或违法违规内容" value="成人或违法违规内容"/>
|
||||
<el-checkbox label="侵犯知识产权" value="侵犯知识产权"/>
|
||||
</el-checkbox-group>
|
||||
<div class="py-3">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="在此处输入反馈。请记住不要包含个人信息,如电话号码。"/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible2 = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {FullScreen} from '@element-plus/icons-vue'
|
||||
import type {ApiResult} from "~/api";
|
||||
import type {FormInstance, FormRules} from "element-plus";
|
||||
import {useClientRequest} from "~/composables/useClientRequest";
|
||||
import {reactive, ref} from "vue";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import type {CompanyComment} from "~/api/system/companyComment/model";
|
||||
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
companyId?: number;
|
||||
comments?: CompanyComment[];
|
||||
count?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const visible = ref<boolean>(false);
|
||||
const visible2 = ref<boolean>(false);
|
||||
const checkList = ref<string[]>([]);
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', page: number): void
|
||||
}>()
|
||||
|
||||
// 配置信息
|
||||
const {form, resetFields} = useFormData<CompanyComment>({
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
userId: undefined,
|
||||
companyId: undefined,
|
||||
rate: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules<any>>({
|
||||
rate: [
|
||||
{required: true, message: '请输入评分', trigger: 'blur'},
|
||||
],
|
||||
comments: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
const onComments = () => {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const onComplaint = () => {
|
||||
visible2.value = true;
|
||||
}
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
emit('done', page)
|
||||
}
|
||||
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.rate === 0) {
|
||||
ElMessage.error('还没有评分哦!')
|
||||
return false;
|
||||
}
|
||||
form.companyId = Number(getIdBySpm(5));
|
||||
useClientRequest<ApiResult<any>>(`/system/company-comment`, {
|
||||
method: 'POST',
|
||||
body: form
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
visible.value = false
|
||||
resetFields();
|
||||
emit('done',0)
|
||||
} else {
|
||||
return ElMessage.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
102
pages/case/components/PageBanner.vue
Normal file
102
pages/case/components/PageBanner.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<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.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-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>
|
||||
<el-space class="btn">
|
||||
<nuxt-link target="_blank"><el-button type="primary" round>获取</el-button></nuxt-link>
|
||||
</el-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {ApiResult} from "~/api";
|
||||
import type {Company} from "~/api/system/company/model";
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const token = useToken();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
desc?: string;
|
||||
buyUrl?: string;
|
||||
form?: CmsWebsite;
|
||||
value?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void
|
||||
}>()
|
||||
|
||||
const onBuy = (item: Company) => {
|
||||
// if(item.type === 1){
|
||||
// // 插件
|
||||
// openSpmUrl(`/product/checkout`,item,item.productId)
|
||||
// }else {
|
||||
// // 产品
|
||||
// openSpmUrl(`/product/create`,item,item.productId)
|
||||
// }
|
||||
if (!token.value || token.value == '') {
|
||||
ElMessage.error('请先登录');
|
||||
setTimeout(() => {
|
||||
navigateTo(`/product/create`)
|
||||
}, 500)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 安装插件
|
||||
const installPlug = () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '安装中...'
|
||||
})
|
||||
useClientRequest<ApiResult<any>>(`/system/menu/install`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
companyId: getIdBySpm(5)
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
setTimeout(() => {
|
||||
ElMessage.success(res.message);
|
||||
loading.close()
|
||||
emit('done')
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.rounded-avatar {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.rounded-avatar-xs {
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
45
pages/case/components/SearchBar.vue
Normal file
45
pages/case/components/SearchBar.vue
Normal 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>
|
||||
154
pages/case/index.vue
Normal file
154
pages/case/index.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600 line-clamp-1"> 客户案例 </span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-space class="flex items-center">
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
class="hidden-sm-and-down"
|
||||
:suffix-icon="Search"
|
||||
:style="{ width: inputWidth }"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@change="reload"
|
||||
/>
|
||||
<el-button class="hidden-sm-and-up" :icon="Search" @click="showSearch = true"></el-button>
|
||||
</el-space>
|
||||
<el-dialog
|
||||
v-model="showSearch"
|
||||
fullscreen
|
||||
>
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
size="large"
|
||||
class="w-full my-5"
|
||||
:suffix-icon="Search"
|
||||
@change="reload"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<el-row :gutter="24" id="container" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :span="6" :xs="24" class="left mb-8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="cursor-pointer" @mouseover="showDomain(item)"
|
||||
@mouseleave="hideDomain">
|
||||
<nuxt-link :to="`/market/${item.websiteId}`">
|
||||
<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">
|
||||
{{ id == item.websiteId ? item.domain : item.comments || '暂无描述' }}
|
||||
</sapn>
|
||||
<el-button size="small" round>获取</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-image pt-3 overflow-hidden">
|
||||
<el-image v-if="item.files" :src="`${JSON.parse(item.files)[0]}`" class="w-full h-1/2 max-h-[220px] transition-transform duration-300 ease-in-out hover:scale-110"/>
|
||||
<el-image v-else class="w-full h-[220px]"/>
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-page-header>
|
||||
|
||||
<Pagination :total="total" @done="search"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft, Search} from '@element-plus/icons-vue'
|
||||
import type {CmsArticleParam} from "~/api/cms/cmsArticle/model";
|
||||
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
// 页面信息
|
||||
const list = ref<CmsWebsite[]>([]);
|
||||
const total = ref(0);
|
||||
const id = ref<number>();
|
||||
const inputWidth = ref<string>('180px');
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsWebsiteParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 12,
|
||||
status: undefined,
|
||||
recommend: undefined,
|
||||
search: true,
|
||||
websiteType: undefined,
|
||||
categoryId: undefined,
|
||||
lang: undefined
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
const handleFocus = () => {
|
||||
inputWidth.value = '400px'; // 聚焦时宽度
|
||||
}
|
||||
const handleBlur = () => {
|
||||
inputWidth.value = '180px'; // 聚焦时宽度
|
||||
}
|
||||
|
||||
const showDomain = (item: CmsWebsite) => {
|
||||
id.value = Number(item.websiteId);
|
||||
};
|
||||
|
||||
const hideDomain = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
await pageCmsWebsiteAll({
|
||||
...where,
|
||||
plugin: false
|
||||
}).then(response => {
|
||||
if (response?.list) {
|
||||
list.value = response?.list;
|
||||
total.value = response.count;
|
||||
}
|
||||
}).catch(() => {
|
||||
}).finally(() => showSearch.value = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsArticleParam) => {
|
||||
where.page = data.page;
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-input {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
263
pages/detail/[id].vue
Normal file
263
pages/detail/[id].vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<!-- 文章详情 -->
|
||||
<template>
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="font-600 mr-3"> 文章详情 </span>
|
||||
</template>
|
||||
|
||||
<el-card shadow="hover" class="my-5 sm:my-10 sm:px-2">
|
||||
|
||||
<!-- 新闻详细 -->
|
||||
<div class=" bg-white">
|
||||
<h1 class="pt-5 text-3xl text-center">{{ form.title }}</h1>
|
||||
<div class="flex items-center justify-center 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 hidden" v-if="form.files">
|
||||
<div class="text-gray-400 py-3 text-sm">图片附件</div>
|
||||
<el-scrollbar>
|
||||
<div class="flex">
|
||||
<el-image
|
||||
v-for="(item,index) in JSON.parse(form.files)"
|
||||
:key="index"
|
||||
class="scrollbar-item w-[240px] max-h-[180px] mr-4 mb-4"
|
||||
:src="item"
|
||||
:zoom-rate="1.2"
|
||||
:max-scale="7"
|
||||
:min-scale="0.2"
|
||||
:preview-src-list="srcList"
|
||||
:initial-index="4"
|
||||
fit="contain"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<!-- 内容组件 -->
|
||||
<Content class="content text-lg py-5" :editor="form?.editor" :data="form.content" />
|
||||
<NextArticle :articleId="articleId" />
|
||||
<h3 class="tag">{{ $t('articleUrl') }}:{{ locationUrl() }} </h3>
|
||||
<Tags :data="form.tags" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
|
||||
<!-- 最近浏览 -->
|
||||
<!-- <CmsArticleRecently :data="form" type="article" />-->
|
||||
<!-- 相关产品和相关新闻 -->
|
||||
<!-- <div class="relate_list">-->
|
||||
<!-- <CmsProductRelated :data="form" />-->
|
||||
<!-- <CmsArticleRelated :data="form" />-->
|
||||
<!-- </div>-->
|
||||
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
|
||||
import type {BreadcrumbItem} from "~/types/global";
|
||||
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
|
||||
import {getNavIdByParamsId, getViews, locationUrl, navTo, paramsId} from "~/utils/common";
|
||||
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import dayjs from "dayjs";
|
||||
import type {Layout} from "~/api/layout/model";
|
||||
import {
|
||||
getCmsArticle
|
||||
} from "~/api/cms/cmsArticle";
|
||||
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import CmsArticleRecently from "~/components/CmsRecently.vue";
|
||||
import Tags from "~/components/Tags.vue";
|
||||
import Content from "~/components/Content.vue";
|
||||
|
||||
// 引入状态管理
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const layout = ref<Layout>({});
|
||||
const articleId = ref();
|
||||
const i18n = useI18n();
|
||||
const breadcrumb = ref<BreadcrumbItem>();
|
||||
const showPassword = ref<boolean>();
|
||||
const category = ref<CmsNavigation[]>([]);
|
||||
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,
|
||||
// 文章编辑器
|
||||
editor: undefined,
|
||||
// 虚拟阅读量
|
||||
virtualViews: undefined,
|
||||
// 实际阅读量
|
||||
actualViews: undefined,
|
||||
// 访问权限
|
||||
permission: undefined,
|
||||
// 访问密码
|
||||
password: undefined,
|
||||
password2: undefined,
|
||||
// 用户ID
|
||||
userId: undefined,
|
||||
// 用户昵称
|
||||
nickname: undefined,
|
||||
// 账号
|
||||
username: undefined,
|
||||
// 用户头像
|
||||
// userAvatar: undefined,
|
||||
author: undefined,
|
||||
// 所属门店ID
|
||||
shopId: undefined,
|
||||
//
|
||||
likes: undefined,
|
||||
// 排序
|
||||
sortNumber: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 状态
|
||||
status: 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
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back(); // 返回上一页
|
||||
// window.history.back();
|
||||
}
|
||||
|
||||
// 请求数据
|
||||
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 => {
|
||||
category.value = list;
|
||||
})
|
||||
}
|
||||
|
||||
if(form.permission === 1){
|
||||
console.log('登录可见')
|
||||
return;
|
||||
}
|
||||
if(form.permission === 2){
|
||||
console.log('需要密码')
|
||||
showPassword.value = true;
|
||||
return;
|
||||
}
|
||||
}).catch(() => {
|
||||
navigateTo('/404');
|
||||
})
|
||||
|
||||
// seo
|
||||
useSeoMeta({
|
||||
description: form?.comments,
|
||||
keywords: form.title,
|
||||
titleTemplate: `${form?.title}` + ' - %s',
|
||||
})
|
||||
|
||||
// 面包屑
|
||||
breadcrumb.value = form
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
articleId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<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>
|
||||
136
pages/docs/[id].vue
Normal file
136
pages/docs/[id].vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<Banner :layout="layout" />
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<el-row :gutter="24" id="container" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :xs="24" :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;
|
||||
// 加载文章列表
|
||||
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>
|
||||
122
pages/docs/index.vue
Normal file
122
pages/docs/index.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<Banner :layout="layout" />
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 文档中心 </span>
|
||||
</template>
|
||||
<el-row :gutter="24" id="container" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="6" :xs="12" 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;
|
||||
// 加载文章列表
|
||||
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>
|
||||
262
pages/index.vue
Normal file
262
pages/index.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<!-- 幻灯片轮播 -->
|
||||
<Carousel/>
|
||||
<!-- 新闻中心 -->
|
||||
<Category />
|
||||
|
||||
<!-- 合作伙伴 -->
|
||||
<Partners/>
|
||||
|
||||
<!-- 下单定制 -->
|
||||
<!-- <Customized />-->
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type {FormRules, FormInstance} from 'element-plus'
|
||||
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
|
||||
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import type {CmsOrder} from "~/api/cms/cmsOrder/model";
|
||||
import type {CmsLink} from "~/api/cms/cmsLink/model";
|
||||
import {pageCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {pageCmsArticle} from "~/api/cms/cmsArticle";
|
||||
import {pageCmsLink} from "~/api/cms/cmsLink";
|
||||
import {addCmsOrder} from "~/api/cms/cmsOrder";
|
||||
import {getCaptcha} from "~/api/passport/login";
|
||||
import Carousel from "~/components/Index/Carousel.vue";
|
||||
import LandingHero from "~/components/Index/LandingHero.vue";
|
||||
import SiteList from "~/components/Index/SiteList.vue";
|
||||
import MarketList from "~/components/Index/MarketList.vue";
|
||||
import HotNews from "~/components/Index/HotNews.vue";
|
||||
import CaseList from "~/components/Index/CaseList.vue"
|
||||
import Partners from "~/components/Index/Partners.vue";
|
||||
import Customized from "~/components/Index/Customized.vue";
|
||||
import Category from '~/components/Index/Category.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const productList = ref<CmsNavigation[]>([]);
|
||||
const caseList = ref<CmsArticle[]>([]);
|
||||
const topNews = ref<CmsArticle[]>([]);
|
||||
const topNewsImage = ref<string>('');
|
||||
const hotNews = ref<CmsArticle[]>([]);
|
||||
const links = ref<CmsLink[]>([]);
|
||||
// 验证码 base64 数据
|
||||
const captcha = ref('');
|
||||
const text = ref<string>('');
|
||||
|
||||
const scrollTop = ref(0)
|
||||
window.onscroll = e => {
|
||||
scrollTop.value = window.document.documentElement.scrollTop
|
||||
}
|
||||
|
||||
|
||||
const {form, assignFields, resetFields} = useFormData<CmsOrder>({
|
||||
// 订单号
|
||||
orderId: undefined,
|
||||
// 模型名称
|
||||
model: 'order',
|
||||
// 订单标题
|
||||
title: '-',
|
||||
// 订单编号
|
||||
orderNo: undefined,
|
||||
// 订单类型,0商城 1询价 2留言
|
||||
type: undefined,
|
||||
// 关联项目ID,配合订单类型使用
|
||||
articleId: undefined,
|
||||
// 真实姓名
|
||||
realName: undefined,
|
||||
// 手机号码
|
||||
phone: undefined,
|
||||
// 电子邮箱
|
||||
email: undefined,
|
||||
// 收货地址
|
||||
address: undefined,
|
||||
// 订单内容
|
||||
content: undefined,
|
||||
// 订单总额
|
||||
totalPrice: '0.00',
|
||||
// 实际付款
|
||||
payPrice: '0.00',
|
||||
// 报价询价
|
||||
price: '0.00',
|
||||
// 购买数量
|
||||
totalNum: undefined,
|
||||
// 二维码地址,保存订单号,支付成功后才生成
|
||||
qrcode: undefined,
|
||||
// 下单渠道,0网站 1小程序 2其他
|
||||
channel: undefined,
|
||||
// 过期时间
|
||||
expirationTime: undefined,
|
||||
// 订单是否已结算(0未结算 1已结算)
|
||||
isSettled: undefined,
|
||||
// 用户id
|
||||
userId: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 排序号
|
||||
sortNumber: undefined,
|
||||
// 是否删除, 0否, 1是
|
||||
deleted: undefined,
|
||||
// 租户id
|
||||
tenantId: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 图像验证码
|
||||
code: '',
|
||||
})
|
||||
const rules = reactive<FormRules<CmsOrder>>({
|
||||
title: [
|
||||
{required: true, message: '请输入产品名称', trigger: 'blur'},
|
||||
],
|
||||
phone: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
realName: [
|
||||
{required: true, message: '请输入联系人姓名', trigger: 'blur'},
|
||||
],
|
||||
content: [
|
||||
{required: true, message: '请输入留言内容', trigger: 'blur'},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.code !== text.value) {
|
||||
await reload();
|
||||
ElMessage.error('验证码不正确!');
|
||||
return false;
|
||||
}
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
addCmsOrder(form).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
resetFields();
|
||||
reload();
|
||||
} else {
|
||||
return ElMessage.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* 获取图形验证码 */
|
||||
const changeCaptcha = async () => {
|
||||
getCaptcha().then(captchaData => {
|
||||
captcha.value = captchaData.base64;
|
||||
text.value = captchaData.text;
|
||||
})
|
||||
};
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
|
||||
// 读取产品系列
|
||||
pageCmsNavigation({
|
||||
limit: 8,
|
||||
parentId: i18n.locale.value == 'en' ? 1073 : 998,
|
||||
lang: i18n.locale.value
|
||||
}).then(data => {
|
||||
if (data) {
|
||||
productList.value = data?.list;
|
||||
}
|
||||
})
|
||||
|
||||
// 读取案例
|
||||
pageCmsArticle({
|
||||
limit: 6,
|
||||
status: 0,
|
||||
recommend: 1,
|
||||
parentId: i18n.locale.value == 'en' ? 1074 : 999,
|
||||
lang: i18n.locale.value
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
caseList.value = res?.list;
|
||||
}
|
||||
})
|
||||
|
||||
// 新闻头条
|
||||
pageCmsArticle({
|
||||
limit: 1,
|
||||
status: 0,
|
||||
recommend: 1,
|
||||
categoryId: i18n.locale.value == 'en' ? 1080 : 1005,
|
||||
lang: i18n.locale.value
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
topNews.value = res?.list;
|
||||
topNewsImage.value = res?.list[0]?.image;
|
||||
}
|
||||
})
|
||||
|
||||
// 热门推荐
|
||||
pageCmsArticle({
|
||||
limit: 2,
|
||||
status: 0,
|
||||
recommend: 1,
|
||||
categoryId: i18n.locale.value == 'en' ? 1081 : 1006,
|
||||
lang: i18n.locale.value
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
hotNews.value = res?.list;
|
||||
}
|
||||
})
|
||||
|
||||
// 品牌展示
|
||||
pageCmsLink({
|
||||
limit: 50,
|
||||
lang: i18n.locale.value,
|
||||
categoryId: 1067,
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
links.value = res?.list;
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
console.log(path, '=>Path')
|
||||
reload();
|
||||
changeCaptcha();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
|
||||
//.imgbig{
|
||||
// width: 289px;
|
||||
// height: 200px;
|
||||
// overflow: hidden;
|
||||
//}
|
||||
.product-image {
|
||||
width: 289px;
|
||||
height: 425px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scrollbar-flex-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.scrollbar-demo-item {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 10px;
|
||||
width: 289px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
259
pages/item/[id].vue
Normal file
259
pages/item/[id].vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<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"> 产品详情 </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
|
||||
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"
|
||||
:preview-src-list="srcList"
|
||||
:initial-index="4"
|
||||
fit="contain"
|
||||
/>
|
||||
</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 { ArrowLeft,View, Monitor, Search,Cpu, Platform, Avatar } from '@element-plus/icons-vue'
|
||||
import type {ApiResult, PageResult} from "~/api";
|
||||
import {useServerRequest} from "~/composables/useServerRequest";
|
||||
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 {CompanyComment} from "~/api/system/companyComment/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 comments = ref<CompanyComment[]>([]);
|
||||
const commentsTotal = ref(0);
|
||||
const commentsPage = ref(1);
|
||||
const navId = ref();
|
||||
const srcList = ref<any[]>([]);
|
||||
|
||||
// 配置信息
|
||||
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,
|
||||
// 访问量
|
||||
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,
|
||||
// 网站配置
|
||||
config: undefined,
|
||||
topNavs: undefined,
|
||||
bottomNavs: undefined,
|
||||
loginUser: undefined
|
||||
});
|
||||
|
||||
const doComments = async (page: any) => {
|
||||
commentsPage.value = page;
|
||||
await reloadComments();
|
||||
}
|
||||
|
||||
|
||||
// 加载评论
|
||||
const reloadComments = async () => {
|
||||
const {data: commentsResponse} = await useServerRequest<ApiResult<PageResult<CompanyComment>>>('/system/company-comment/page', {
|
||||
params: {
|
||||
companyId: getIdBySpm(5),
|
||||
page: commentsPage.value,
|
||||
// status: 1
|
||||
}
|
||||
})
|
||||
if(commentsResponse.value && commentsResponse.value?.data){
|
||||
comments.value = commentsResponse.value?.data?.list
|
||||
commentsTotal.value = commentsResponse.value?.data?.count;
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 读取导航详情
|
||||
const reload = async () => {
|
||||
getCmsWebsiteAll(navId.value).then(data => {
|
||||
// 获取栏目信息
|
||||
assignFields(data)
|
||||
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.websiteName,
|
||||
keywords: data.websiteName,
|
||||
titleTemplate: `${data?.websiteName}` + ' - %s',
|
||||
})
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err,'加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.content {
|
||||
img {
|
||||
max-width: 100%;
|
||||
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>
|
||||
82
pages/item/components/AppInfo.vue
Normal file
82
pages/item/components/AppInfo.vue
Normal 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>
|
||||
195
pages/item/components/Comments.vue
Normal file
195
pages/item/components/Comments.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<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"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="w-full sm:py-2"
|
||||
size="large"
|
||||
status-icon
|
||||
>
|
||||
<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 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"
|
||||
title="发表评论"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible = false"
|
||||
>
|
||||
<el-form-item prop="rate">
|
||||
<el-rate v-model="form.rate" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="comments">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="最多300字,支持 markdown 语法。请认真填写评论内容,以便于帮助作者更好完善插件,如需反馈问题,请到下方插件提问进行提交。"/>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 发起投诉 -->
|
||||
<el-dialog
|
||||
v-model="visible2"
|
||||
title="为什么举报此内容?"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible2 = false"
|
||||
>
|
||||
<el-checkbox-group v-model="checkList">
|
||||
<el-checkbox label="与我无关" value="与我无关"/>
|
||||
<el-checkbox label="文章过时" value="文章过时"/>
|
||||
<el-checkbox label="标题有误" value="标题有误"/>
|
||||
<el-checkbox label="图像质量差或视觉缺陷" value="图像质量差或视觉缺陷"/>
|
||||
<el-checkbox label="垃圾邮件" value="垃圾邮件"/>
|
||||
<el-checkbox label="成人或违法违规内容" value="成人或违法违规内容"/>
|
||||
<el-checkbox label="侵犯知识产权" value="侵犯知识产权"/>
|
||||
</el-checkbox-group>
|
||||
<div class="py-3">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="在此处输入反馈。请记住不要包含个人信息,如电话号码。"/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible2 = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {FullScreen} from '@element-plus/icons-vue'
|
||||
import type {ApiResult} from "~/api";
|
||||
import type {FormInstance, FormRules} from "element-plus";
|
||||
import {useClientRequest} from "~/composables/useClientRequest";
|
||||
import {reactive, ref} from "vue";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import type {CompanyComment} from "~/api/system/companyComment/model";
|
||||
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
companyId?: number;
|
||||
comments?: CompanyComment[];
|
||||
count?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const visible = ref<boolean>(false);
|
||||
const visible2 = ref<boolean>(false);
|
||||
const checkList = ref<string[]>([]);
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', page: number): void
|
||||
}>()
|
||||
|
||||
// 配置信息
|
||||
const {form, resetFields} = useFormData<CompanyComment>({
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
userId: undefined,
|
||||
companyId: undefined,
|
||||
rate: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules<any>>({
|
||||
rate: [
|
||||
{required: true, message: '请输入评分', trigger: 'blur'},
|
||||
],
|
||||
comments: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
const onComments = () => {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const onComplaint = () => {
|
||||
visible2.value = true;
|
||||
}
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
emit('done', page)
|
||||
}
|
||||
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.rate === 0) {
|
||||
ElMessage.error('还没有评分哦!')
|
||||
return false;
|
||||
}
|
||||
form.companyId = Number(getIdBySpm(5));
|
||||
useClientRequest<ApiResult<any>>(`/system/company-comment`, {
|
||||
method: 'POST',
|
||||
body: form
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
visible.value = false
|
||||
resetFields();
|
||||
emit('done',0)
|
||||
} else {
|
||||
return ElMessage.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
100
pages/item/components/PageBanner.vue
Normal file
100
pages/item/components/PageBanner.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<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.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-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 }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {ApiResult} from "~/api";
|
||||
import type {Company} from "~/api/system/company/model";
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const token = useToken();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
desc?: string;
|
||||
buyUrl?: string;
|
||||
form?: CmsWebsite;
|
||||
value?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void
|
||||
}>()
|
||||
|
||||
const onBuy = (item: Company) => {
|
||||
// if(item.type === 1){
|
||||
// // 插件
|
||||
// openSpmUrl(`/product/checkout`,item,item.productId)
|
||||
// }else {
|
||||
// // 产品
|
||||
// openSpmUrl(`/product/create`,item,item.productId)
|
||||
// }
|
||||
if (!token.value || token.value == '') {
|
||||
ElMessage.error('请先登录');
|
||||
setTimeout(() => {
|
||||
openUrl(`/product/create`, item, item.companyId)
|
||||
}, 500)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 安装插件
|
||||
const installPlug = () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '安装中...'
|
||||
})
|
||||
useClientRequest<ApiResult<any>>(`/system/menu/install`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
companyId: getIdBySpm(5)
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
setTimeout(() => {
|
||||
ElMessage.success(res.message);
|
||||
loading.close()
|
||||
emit('done')
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.rounded-avatar {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.rounded-avatar-xs {
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
45
pages/item/components/SearchBar.vue
Normal file
45
pages/item/components/SearchBar.vue
Normal 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>
|
||||
150
pages/links/[id].vue
Normal file
150
pages/links/[id].vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-space class="flex items-center">
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
:suffix-icon="Search"
|
||||
class="hidden-sm-and-down"
|
||||
@change="reload"
|
||||
/>
|
||||
<el-button :icon="Search" class="hidden-sm-and-up" @click="showSearch = true"></el-button>
|
||||
</el-space>
|
||||
<el-dialog
|
||||
v-model="showSearch"
|
||||
fullscreen
|
||||
>
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
:suffix-icon="Search"
|
||||
class="w-full my-5"
|
||||
size="large"
|
||||
@change="reload"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<el-row id="container" :gutter="24" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="4" :xs="12" class="left mb-8">
|
||||
<nuxt-link :to="`${item.url}`" class="flex-1 cursor-pointer flex flex-col text-center" target="_blank">
|
||||
<el-card :body-style="{ padding: '0px' }" class="items-center flex justify-center" shadow="hover">
|
||||
<el-popover
|
||||
:content="item.comments"
|
||||
:width="200"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="flex-1 py-5 sm:p-4 !p-4">
|
||||
<el-space class="text-gray-700 dark:text-white text-base font-semibold h-[28px] flex justify-center items-center">
|
||||
<el-image v-if="item.icon" :alt="item.name" :src="item.icon" style="height: 32px"/>
|
||||
<span :title="item.name" class="text-lg line-clamp-1">{{ item.name }}</span>
|
||||
</el-space>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-card>
|
||||
</nuxt-link>
|
||||
</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 {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {pageCmsLink} from "~/api/cms/cmsLink";
|
||||
import type {CmsLink, CmsLinkParam} from "~/api/cms/cmsLink/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navId = ref();
|
||||
const list = ref<CmsLink[]>([]);
|
||||
const category = ref<CmsNavigation[]>([]);
|
||||
const total = ref<number>(0);
|
||||
|
||||
// 获取状态
|
||||
const page = usePage();
|
||||
const layout = useLayout();
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsLinkParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
categoryId: undefined
|
||||
});
|
||||
|
||||
const id = ref<number>(0);
|
||||
|
||||
const showMenu = (item: CmsLink) => {
|
||||
id.value = Number(item.id);
|
||||
};
|
||||
|
||||
const hideMenu = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
getCmsNavigation(navId.value).then(data => {
|
||||
// 获取栏目信息
|
||||
page.value = data
|
||||
layout.value.banner = data.banner;
|
||||
if(navId.value){
|
||||
where.categoryId = navId.value;
|
||||
}
|
||||
pageCmsLink(where).then(response => {
|
||||
if(response){
|
||||
total.value = response?.count;
|
||||
list.value = response?.list;
|
||||
}
|
||||
}).finally(() => showSearch.value = false)
|
||||
|
||||
// 设置页面标题
|
||||
useSeoMeta({
|
||||
description: data.comments || data.title,
|
||||
keywords: data.title,
|
||||
titleTemplate: `${data?.title}` + ' - %s',
|
||||
})
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err,'加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.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>
|
||||
142
pages/links/index.vue
Normal file
142
pages/links/index.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600 line-clamp-1"> 网址导航 </span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-space class="flex items-center">
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
:suffix-icon="Search"
|
||||
class="hidden-sm-and-down"
|
||||
@change="reload"
|
||||
/>
|
||||
<el-button :icon="Search" class="hidden-sm-and-up" @click="showSearch = true"></el-button>
|
||||
</el-space>
|
||||
<el-dialog
|
||||
v-model="showSearch"
|
||||
fullscreen
|
||||
>
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
:suffix-icon="Search"
|
||||
class="w-full my-5"
|
||||
size="large"
|
||||
@change="reload"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<el-row id="container" :gutter="24" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="4" :xs="12" class="left mb-8">
|
||||
<nuxt-link :to="`${item.url}`" class="flex-1 cursor-pointer flex flex-col text-center" target="_blank">
|
||||
<el-card :body-style="{ padding: '0px' }" class="items-center flex justify-center" shadow="hover">
|
||||
<el-popover
|
||||
:content="item.comments"
|
||||
:width="200"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="flex-1 py-5 sm:p-4 !p-4">
|
||||
<el-space class="text-gray-700 dark:text-white text-base font-semibold h-[28px] flex justify-center items-center">
|
||||
<el-image v-if="item.icon" :alt="item.name" :src="item.icon" style="height: 32px"/>
|
||||
<span :title="item.name" class="text-lg line-clamp-1">{{ item.name }}</span>
|
||||
</el-space>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-card>
|
||||
</nuxt-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-page-header>
|
||||
|
||||
<Pagination :page-size="48" :total="total" @done="search"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ArrowLeft, Search} from '@element-plus/icons-vue'
|
||||
import type {CmsArticleParam} from "~/api/cms/cmsArticle/model";
|
||||
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
|
||||
import type {CmsLink, CmsLinkParam} from "~/api/cms/cmsLink/model";
|
||||
import {pageCmsLink} from "~/api/cms/cmsLink";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
// 页面信息
|
||||
const list = ref<CmsLink[]>([]);
|
||||
const total = ref(0);
|
||||
const id = ref<number>();
|
||||
const inputWidth = ref<string>('180px');
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsLinkParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 48,
|
||||
categoryId: undefined
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
const handleFocus = () => {
|
||||
inputWidth.value = '400px'; // 聚焦时宽度
|
||||
}
|
||||
const handleBlur = () => {
|
||||
inputWidth.value = '180px'; // 聚焦时宽度
|
||||
}
|
||||
|
||||
const showDomain = (item: CmsWebsite) => {
|
||||
id.value = Number(item.websiteId);
|
||||
};
|
||||
|
||||
const hideDomain = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
await pageCmsLink({
|
||||
...where
|
||||
}).then(response => {
|
||||
if (response?.list) {
|
||||
list.value = response?.list;
|
||||
total.value = response.count;
|
||||
}
|
||||
}).catch(() => {
|
||||
}).finally(() => showSearch.value = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsArticleParam) => {
|
||||
where.page = data.page;
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-input {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
374
pages/order/[id].vue
Normal file
374
pages/order/[id].vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
|
||||
<!-- Banner -->
|
||||
<Banner :layout="layout"/>
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<template #extra>
|
||||
</template>
|
||||
<el-card shadow="hover" class="my-5 sm:my-10 sm:px-2">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-8">
|
||||
<div class="col-span-2">
|
||||
<div class="my-2">
|
||||
<el-alert title="填写您的需求,为您量身定制." type="warning"/>
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80"
|
||||
label-position="left"
|
||||
status-icon
|
||||
>
|
||||
<el-form-item :label="$t('order.title')" prop="title" class="hover:bg-gray-50 p-2">
|
||||
<el-select
|
||||
v-model="form.title"
|
||||
filterable
|
||||
placeholder="选择产品"
|
||||
@change="onWebsite"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in siteList"
|
||||
:key="item.websiteId"
|
||||
:label="item.websiteName"
|
||||
:value="item.websiteId"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ item.websiteName }}</span>
|
||||
<span class="text-gray-300">
|
||||
{{ `${item.websiteCode}.websoft.top` }}
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.content')" prop="content" class="hover:bg-gray-50 p-2">
|
||||
<el-input type="textarea" :rows="5" cols="80" v-model="form.content"
|
||||
placeholder="请填写您的项目需求"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.reference')" class="hover:bg-gray-50 p-2" prop="reference">
|
||||
<el-input v-model="form.reference" :placeholder="$t('order.reference')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.files')" class="hover:bg-gray-50 p-2" prop="reference">
|
||||
<el-upload
|
||||
v-model:file-list="files"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
:limit="2"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-remove="filesRemove"
|
||||
:on-success="filesOnSuccess"
|
||||
action="https://server.gxwebsoft.com/api/oss/upload"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-icon>
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<template v-if="token">
|
||||
|
||||
</template>
|
||||
<template v-if="!token">
|
||||
<el-form-item :label="$t('order.realName')" class="hover:bg-gray-50 p-2" prop="realName">
|
||||
<el-input v-model="form.realName" :placeholder="$t('order.realName')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.phone')" class="hover:bg-gray-50 p-2" prop="phone">
|
||||
<el-input v-model="form.phone" :maxlength="11" :placeholder="$t('order.phone')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.email')" class="hover:bg-gray-50 p-2" prop="email">
|
||||
<el-input v-model="form.email" :placeholder="$t('order.email')"/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item :label="$t('order.code')" prop="code" class="hover:bg-gray-50 p-2">
|
||||
<el-space class="flex">
|
||||
<el-input size="large" :placeholder="$t('order.imgCode')" maxlength="5" v-model="form.code"/>
|
||||
<el-image :alt="$t('order.imgCode')" v-if="captcha" :src="captcha" @click="changeCaptcha"/>
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="submitForm ml-2">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="large"
|
||||
type="primary"
|
||||
@click="submitForm(formRef)"
|
||||
>
|
||||
{{ $t('order.submit') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="hidden-sm-and-down">
|
||||
<el-image class="py-2" v-if="page.icon" :src="page.icon"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<div class="flex justify-center">
|
||||
<el-image w-full :src="dialogImageUrl" alt="查看证件"/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft, View, Search, Plus} from '@element-plus/icons-vue'
|
||||
import type {FormInstance, FormRules, UploadProps, UploadUserFile} from 'element-plus'
|
||||
import {useLayout, usePage, useUser} from "~/composables/configState";
|
||||
import {getNavIdByParamsId} from "~/utils/common";
|
||||
import type {CmsOrder} from "~/api/cms/cmsOrder/model";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import {addCmsOrder} from "~/api/cms/cmsOrder";
|
||||
import {getCaptcha} from "~/api/passport/login";
|
||||
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {listCmsWebsite, pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
import {useHead} from "nuxt/app";
|
||||
|
||||
// 在最顶部添加
|
||||
definePageMeta({
|
||||
title: '订单页面'
|
||||
})
|
||||
|
||||
// 引入状态管理
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const token = useToken();
|
||||
const navId = ref();
|
||||
const layout = useLayout();
|
||||
const user = useUser();
|
||||
const page = usePage();
|
||||
const siteList = ref<CmsWebsite[]>([]);
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const dialogImageUrl = ref('')
|
||||
const files = ref<UploadUserFile[]>([])
|
||||
const filesStr = ref<string[]>([])
|
||||
// 验证码 base64 数据
|
||||
const captcha = ref('');
|
||||
const text = ref<string>('');
|
||||
const loading = ref(false)
|
||||
|
||||
|
||||
const {form, resetFields} = useFormData<CmsOrder>({
|
||||
// 订单号
|
||||
orderId: undefined,
|
||||
// 模型名称
|
||||
model: 'order',
|
||||
// 订单标题
|
||||
title: undefined,
|
||||
// 订单编号
|
||||
orderNo: undefined,
|
||||
// 订单类型,0商城 1询价 2留言
|
||||
type: undefined,
|
||||
// 关联项目ID,配合订单类型使用
|
||||
articleId: undefined,
|
||||
// 关联网站ID
|
||||
websiteId: undefined,
|
||||
// 真实姓名
|
||||
realName: undefined,
|
||||
// 手机号码
|
||||
phone: undefined,
|
||||
// 电子邮箱
|
||||
email: undefined,
|
||||
// 收货地址
|
||||
address: undefined,
|
||||
// 订单内容
|
||||
content: undefined,
|
||||
// 附件
|
||||
files: undefined,
|
||||
// 订单总额
|
||||
totalPrice: '0.00',
|
||||
// 实际付款
|
||||
payPrice: '0.00',
|
||||
// 报价询价
|
||||
price: '0.00',
|
||||
// 购买数量
|
||||
totalNum: undefined,
|
||||
// 二维码地址,保存订单号,支付成功后才生成
|
||||
qrcode: undefined,
|
||||
// 下单渠道,0网站 1小程序 2其他
|
||||
channel: undefined,
|
||||
// 过期时间
|
||||
expirationTime: undefined,
|
||||
// 订单是否已结算(0未结算 1已结算)
|
||||
isSettled: undefined,
|
||||
// 用户id
|
||||
userId: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 排序号
|
||||
sortNumber: undefined,
|
||||
// 是否删除, 0否, 1是
|
||||
deleted: undefined,
|
||||
// 租户id
|
||||
tenantId: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 图像验证码
|
||||
code: '',
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules<CmsOrder>>({
|
||||
title: [
|
||||
{required: true, message: '请输入产品名称', trigger: 'blur'},
|
||||
],
|
||||
phone: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
realName: [
|
||||
{required: true, message: '请输入联系人姓名', trigger: 'blur'},
|
||||
],
|
||||
content: [
|
||||
{required: true, message: '请输入您的开发需求', trigger: 'blur'},
|
||||
]
|
||||
})
|
||||
|
||||
/* 获取图形验证码 */
|
||||
const changeCaptcha = async () => {
|
||||
getCaptcha().then(captchaData => {
|
||||
captcha.value = captchaData.base64;
|
||||
text.value = captchaData.text;
|
||||
})
|
||||
};
|
||||
|
||||
// 将 SEO 相关的逻辑修改为
|
||||
const updateSeo = (data: any) => {
|
||||
const title = data?.title || '';
|
||||
const description = data?.comments || data?.title || '';
|
||||
const appName = useRuntimeConfig().public.appName;
|
||||
|
||||
// 使用 definePageMeta 设置页面元数据
|
||||
useHead({
|
||||
title,
|
||||
meta: [
|
||||
{ name: 'description', content: description },
|
||||
{ name: 'keywords', content: title },
|
||||
{ property: 'og:title', content: title },
|
||||
{ property: 'og:description', content: description }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
try {
|
||||
const data = await getCmsNavigation(navId.value)
|
||||
if (!data) return
|
||||
|
||||
page.value = data
|
||||
layout.value.banner = data.banner
|
||||
|
||||
// 更新 SEO
|
||||
updateSeo(data)
|
||||
|
||||
// 二级栏目分类
|
||||
const res = await pageCmsWebsiteAll({
|
||||
official: true,
|
||||
sort: 'websiteId',
|
||||
order: 'asc'
|
||||
})
|
||||
siteList.value = res?.list || []
|
||||
|
||||
// 用户信息
|
||||
if (user.value) {
|
||||
form.realName = user.value.realName
|
||||
form.phone = user.value.phone
|
||||
form.email = user.value.email
|
||||
}
|
||||
|
||||
changeCaptcha()
|
||||
} catch (error) {
|
||||
console.error('Failed to load page data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
dialogImageUrl.value = uploadFile.url!
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
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 onWebsite = (item: CmsWebsite) => {
|
||||
form.articleId = item.websiteId;
|
||||
form.websiteId = item.websiteId;
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (loading.value) return // 防止重复提交
|
||||
|
||||
if (form.code !== text.value) {
|
||||
changeCaptcha();
|
||||
ElMessage.error('验证码不正确!');
|
||||
return false;
|
||||
}
|
||||
|
||||
if(process.server){
|
||||
return false;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const valid = await formEl.validate()
|
||||
if (valid) {
|
||||
// 如果reference不为空,将其添加到content前面
|
||||
if (form.reference) {
|
||||
form.content = `参考网站:${form.reference}\n\n${form.content}`
|
||||
}
|
||||
|
||||
if (filesStr.value.length > 0) {
|
||||
form.files = JSON.stringify(filesStr.value);
|
||||
}
|
||||
const res = await addCmsOrder(form)
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
resetFields();
|
||||
} else {
|
||||
ElMessage.error(res.message)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
272
pages/order/index.vue
Normal file
272
pages/order/index.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
|
||||
<!-- Banner -->
|
||||
<Banner :layout="layout"/>
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<template #extra>
|
||||
</template>
|
||||
<el-card class="my-5 sm:my-10 sm:px-2" shadow="hover">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-8">
|
||||
<div class="col-span-2">
|
||||
<div class="my-2">
|
||||
<el-alert title="填写您的需求,为您量身定制." type="warning"/>
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="left"
|
||||
label-width="80"
|
||||
status-icon
|
||||
>
|
||||
<el-form-item :label="$t('order.title')" class="hover:bg-gray-50 p-2" prop="title">
|
||||
<el-select
|
||||
v-model="form.title"
|
||||
filterable
|
||||
placeholder="选择产品"
|
||||
@change="onWebsite"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in siteList"
|
||||
:key="item.websiteId"
|
||||
:label="item.websiteName"
|
||||
:value="item.websiteId"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ item.websiteName }}</span>
|
||||
<span class="text-gray-300">
|
||||
{{ `${item.websiteCode}.websoft.top` }}
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.content')" class="hover:bg-gray-50 p-2" prop="content">
|
||||
<el-input v-model="form.content" :rows="5" cols="80" placeholder="填写您的开发需求"
|
||||
type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.realName')" class="hover:bg-gray-50 p-2" prop="realName">
|
||||
<el-input v-model="form.realName" :placeholder="$t('order.realName')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.phone')" class="hover:bg-gray-50 p-2" prop="phone">
|
||||
<el-input v-model="form.phone" :maxlength="11" :placeholder="$t('order.phone')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.email')" class="hover:bg-gray-50 p-2" prop="email">
|
||||
<el-input v-model="form.email" :placeholder="$t('order.email')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('order.code')" class="hover:bg-gray-50 p-2" prop="code">
|
||||
<el-space class="flex">
|
||||
<el-input v-model="form.code" :placeholder="$t('order.imgCode')" maxlength="5" size="large"/>
|
||||
<el-image v-if="captcha" :alt="$t('order.imgCode')" :src="captcha" @click="changeCaptcha"/>
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="submitForm ml-2">
|
||||
<el-button size="large" type="primary" @click.stop="submitForm(formRef)">
|
||||
{{ $t('order.submit') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="hidden-sm-and-down">
|
||||
<el-image v-if="page.icon" :src="page.icon" class="py-2"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<div class="flex justify-center">
|
||||
<el-image :src="dialogImageUrl" alt="查看证件" w-full/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ArrowLeft, View, Search} from '@element-plus/icons-vue'
|
||||
import {useLayout, usePage, useUser} from "~/composables/configState";
|
||||
import {getNavIdByParamsId} from "~/utils/common";
|
||||
import type {FormInstance, FormRules} from 'element-plus'
|
||||
import type {CmsOrder} from "~/api/cms/cmsOrder/model";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import {addCmsOrder} from "~/api/cms/cmsOrder";
|
||||
import {getCaptcha} from "~/api/passport/login";
|
||||
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {listCmsWebsite, pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
// 引入状态管理
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navId = ref();
|
||||
const layout = useLayout();
|
||||
const user = useUser();
|
||||
const page = usePage();
|
||||
const siteList = ref<CmsWebsite[]>([]);
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const dialogImageUrl = ref('')
|
||||
// 验证码 base64 数据
|
||||
const captcha = ref('');
|
||||
const text = ref<string>('');
|
||||
|
||||
|
||||
const {form, resetFields} = useFormData<CmsOrder>({
|
||||
// 订单号
|
||||
orderId: undefined,
|
||||
// 模型名称
|
||||
model: 'order',
|
||||
// 订单标题
|
||||
title: undefined,
|
||||
// 订单编号
|
||||
orderNo: undefined,
|
||||
// 订单类型,0商城 1询价 2留言
|
||||
type: undefined,
|
||||
// 关联项目ID,配合订单类型使用
|
||||
articleId: undefined,
|
||||
// 关联网站ID
|
||||
websiteId: undefined,
|
||||
// 真实姓名
|
||||
realName: undefined,
|
||||
// 手机号码
|
||||
phone: undefined,
|
||||
// 电子邮箱
|
||||
email: undefined,
|
||||
// 收货地址
|
||||
address: undefined,
|
||||
// 订单内容
|
||||
content: undefined,
|
||||
// 订单总额
|
||||
totalPrice: '0.00',
|
||||
// 实际付款
|
||||
payPrice: '0.00',
|
||||
// 报价询价
|
||||
price: '0.00',
|
||||
// 购买数量
|
||||
totalNum: undefined,
|
||||
// 二维码地址,保存订单号,支付成功后才生成
|
||||
qrcode: undefined,
|
||||
// 下单渠道,0网站 1小程序 2其他
|
||||
channel: undefined,
|
||||
// 过期时间
|
||||
expirationTime: undefined,
|
||||
// 订单是否已结算(0未结算 1已结算)
|
||||
isSettled: undefined,
|
||||
// 用户id
|
||||
userId: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 排序号
|
||||
sortNumber: undefined,
|
||||
// 是否删除, 0否, 1是
|
||||
deleted: undefined,
|
||||
// 租户id
|
||||
tenantId: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 图像验证码
|
||||
code: '',
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules<CmsOrder>>({
|
||||
title: [
|
||||
{required: true, message: '请输入产品名称', trigger: 'blur'},
|
||||
],
|
||||
phone: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
realName: [
|
||||
{required: true, message: '请输入联系人姓名', trigger: 'blur'},
|
||||
],
|
||||
content: [
|
||||
{required: true, message: '请输入您的项目需求', trigger: 'blur'},
|
||||
]
|
||||
})
|
||||
|
||||
/* 获取图形验证码 */
|
||||
const changeCaptcha = async () => {
|
||||
getCaptcha().then(captchaData => {
|
||||
captcha.value = captchaData.base64;
|
||||
text.value = captchaData.text;
|
||||
})
|
||||
};
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
getCmsNavigation(navId.value).then(data => {
|
||||
page.value = data
|
||||
layout.value.banner = data.banner;
|
||||
// 二级栏目分类
|
||||
pageCmsWebsiteAll({
|
||||
official: true,
|
||||
sort: 'websiteId',
|
||||
order: 'asc'
|
||||
}).then(res => {
|
||||
siteList.value = res?.list || [];
|
||||
})
|
||||
// 用户信息
|
||||
if (user.value) {
|
||||
form.realName = user.value.realName;
|
||||
form.phone = user.value.phone;
|
||||
form.email = user.value.email;
|
||||
}
|
||||
// seo
|
||||
useSeoMeta({
|
||||
description: data.comments || data.title,
|
||||
keywords: data.title,
|
||||
titleTemplate: `${data?.title}` + ' - %s',
|
||||
})
|
||||
changeCaptcha();
|
||||
})
|
||||
}
|
||||
|
||||
const onWebsite = (item: CmsWebsite) => {
|
||||
form.articleId = item.websiteId;
|
||||
form.websiteId = item.websiteId;
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.code !== text.value) {
|
||||
changeCaptcha();
|
||||
ElMessage.error('验证码不正确!');
|
||||
return false;
|
||||
}
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
addCmsOrder(form).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
resetFields();
|
||||
} else {
|
||||
return ElMessage.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
94
pages/page/[id].vue
Normal file
94
pages/page/[id].vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<!-- Banner -->
|
||||
<Banner :layout="layout" />
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 px-4 sm:px-0">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title || '页面标题' }} </span>
|
||||
</template>
|
||||
<el-card shadow="hover" class="my-5 sm:my-10 sm:px-2">
|
||||
<el-image v-if="page?.design?.photo" :src="page?.design?.photo" class="right max-w-lg" />
|
||||
<!-- 新闻详细 -->
|
||||
<div class=" bg-white">
|
||||
<!-- 内容组件 -->
|
||||
<Content class="content text-lg py-3" :data="page.design?.content" />
|
||||
|
||||
<h3 class="tag" @click="copyText(locationUrl())">{{ $t('articleUrl') }}:{{ locationUrl() }} </h3>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {useLayout, usePage} from "~/composables/configState";
|
||||
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
|
||||
import {copyText, 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";
|
||||
import { useHead } from 'unhead';
|
||||
|
||||
// 引入状态管理
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navId = ref();
|
||||
const layout = useLayout();
|
||||
const page = usePage();
|
||||
const category = ref<CmsNavigation[]>([]);
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 加载页面布局
|
||||
const reload = async () => {
|
||||
try {
|
||||
const data = await getCmsNavigation(navId.value)
|
||||
if (!data) return
|
||||
|
||||
page.value = data
|
||||
layout.value.banner = data.banner
|
||||
|
||||
// 更新 SEO
|
||||
const title = data?.title || ''
|
||||
const description = data.comments || data.title || ''
|
||||
const appName = useRuntimeConfig().public.appName
|
||||
|
||||
useHead({
|
||||
title,
|
||||
meta: [
|
||||
{ name: 'description', content: description },
|
||||
{ name: 'keywords', content: title },
|
||||
{ property: 'og:title', content: title },
|
||||
{ property: 'og:description', content: description }
|
||||
]
|
||||
})
|
||||
|
||||
// 二级栏目分类
|
||||
if(data.parentId && data.parentId > 0){
|
||||
const list = await listCmsNavigation({
|
||||
parentId: data.parentId == 0 ? data.navigationId : data.parentId
|
||||
})
|
||||
category.value = list
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load page data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
312
pages/passport/components/Auth.vue
Normal file
312
pages/passport/components/Auth.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="loading"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="w-full sm:py-2"
|
||||
size="large"
|
||||
status-icon
|
||||
>
|
||||
<el-tabs v-model="form.type" class="flash bg-white ml-0">
|
||||
<el-tab-pane label="个人认证"/>
|
||||
<el-tab-pane label="企业认证"/>
|
||||
</el-tabs>
|
||||
<!-- 已完成认证 -->
|
||||
<template v-if="form.status === 1">
|
||||
<template v-if="form.merchantCode == ''">
|
||||
<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="所属行业" prop="category">
|
||||
<el-cascader
|
||||
v-model="industry"
|
||||
:options="industryData"
|
||||
placeholder="请选择所属行业"
|
||||
class="w-full"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="LOGO">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
:limit="1"
|
||||
class="upload-demo"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
list-type="picture"
|
||||
>
|
||||
<el-button size="default">上传文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务描述" prop="comments">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="营业执照">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
:limit="1"
|
||||
class="upload-demo"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
list-type="picture"
|
||||
>
|
||||
<el-button size="default">上传文件</el-button>
|
||||
</el-upload>
|
||||
</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="注册协议">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive, ref} from 'vue'
|
||||
import {UploadFilled} from '@element-plus/icons-vue'
|
||||
import type {ComponentSize, 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 formRef = ref<FormInstance>()
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
const industry = ref<any[]>([])
|
||||
const loading = ref<boolean>(true)
|
||||
const isUpdate = ref<boolean>(false)
|
||||
const showEdit = ref<boolean>(false)
|
||||
const isAgree = ref<boolean>(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,
|
||||
tenantId: undefined,
|
||||
createTime: 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'},
|
||||
],
|
||||
sfz1: [
|
||||
{required: true, message: '请上传身份证正面', trigger: 'change'}
|
||||
],
|
||||
sfz2: [
|
||||
{required: true, message: '请上传身份证反面', trigger: 'change'}
|
||||
],
|
||||
merchantName: [
|
||||
{required: true, message: '请输入企业名称', trigger: 'blur'}
|
||||
],
|
||||
merchantCode: [
|
||||
{required: true, message: '请输入社会信用代码', trigger: 'blur'}
|
||||
],
|
||||
yyzz: [
|
||||
{required: true, message: '请上传营业执照', trigger: 'change'}
|
||||
],
|
||||
category: [
|
||||
{required: true, message: '请选择所属行业', trigger: 'change'}
|
||||
]
|
||||
})
|
||||
|
||||
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
console.log(uploadFile, uploadFiles)
|
||||
}
|
||||
|
||||
const handlePreview: UploadProps['onPreview'] = (file) => {
|
||||
console.log(file)
|
||||
}
|
||||
|
||||
const handleClick = (index: number) => {
|
||||
// form.type = index
|
||||
}
|
||||
const props = {
|
||||
expandTrigger: 'hover' as const,
|
||||
}
|
||||
|
||||
// 所属行业
|
||||
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 onEdit = () => {
|
||||
showEdit.value = !showEdit.value;
|
||||
}
|
||||
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
console.log('submit!')
|
||||
if (form.type == '0') {
|
||||
form.shopType = '个人开发者';
|
||||
}
|
||||
if(form.type == '1'){
|
||||
form.shopType = '企业开发者';
|
||||
}
|
||||
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', {baseURL: runtimeConfig.public.apiServer})
|
||||
if (response.value?.data) {
|
||||
console.log(response.value.data)
|
||||
isUpdate.value = true;
|
||||
assignFields(response.value.data)
|
||||
industry.value = []
|
||||
industry.value.push(form.parentId)
|
||||
industry.value.push(form.categoryId)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
reload();
|
||||
</script>
|
||||
373
pages/passport/login.vue
Normal file
373
pages/passport/login.vue
Normal file
@@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<div class="login flex justify-around py-24 h-[700px] items-center">
|
||||
<div class="flash">
|
||||
|
||||
</div>
|
||||
<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="/assets/images/O1CN01yz6fEl1MwaRtkJyvf_!!6000000001499-55-tps-70-70.svg" alt=""/>
|
||||
</div>
|
||||
<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-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="登录账号" 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" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-space class="flex justify-between w-full">
|
||||
<el-input size="large" placeholder="图形验证码" maxlength="5" v-model="form.code" @keyup.enter.prevent="onSubmit" />
|
||||
<el-image alt="" :src="captcha" @click="changeCaptcha" />
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="记住密码">
|
||||
<el-switch v-model="form.remember" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="large" class="w-full" @click="onSubmit">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="短信登录" name="sms">
|
||||
<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="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-button type="primary" size="large" class="w-full" @click="onSubmitBySms">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-space>
|
||||
<!-- 快捷登录 --->
|
||||
<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-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>
|
||||
<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="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" />
|
||||
<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>
|
||||
</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>
|
||||
</el-tabs>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {useConfigInfo, useToken, useUser, useWebsite} from "~/composables/configState";
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import { ref } from '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, loginBySms, register, sendSmsCaptcha} from "~/api/passport/login";
|
||||
|
||||
// 配置信息
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const route = useRoute();
|
||||
const website = useWebsite();
|
||||
const config = useConfigInfo();
|
||||
const token = useToken();
|
||||
const user = useUser();
|
||||
|
||||
const activeName = ref('account')
|
||||
|
||||
// 登录注册切换
|
||||
const loginBar = ref<boolean>(true)
|
||||
// 验证码 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 } = useFormData<User>({
|
||||
userId: undefined,
|
||||
username: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
code: '',
|
||||
smsCode: '',
|
||||
isAgree: false,
|
||||
remember: true,
|
||||
isSuperAdmin: true
|
||||
});
|
||||
|
||||
/* 显示发送短信验证码弹窗 */
|
||||
const openImgCodeModal = () => {
|
||||
if (!form.phone) {
|
||||
ElMessage.error('请输入手机号码');
|
||||
return;
|
||||
}
|
||||
// imgCode.value = text.value;
|
||||
};
|
||||
|
||||
const checkUser = async () => {
|
||||
const {data: hasUser } = await useServerRequest<ApiResult<CaptchaResult>>('/existence',{baseURL: runtimeConfig.public.apiServer,method: "get",params: {
|
||||
field: 'phone', value: form.phone
|
||||
}});
|
||||
if(hasUser.value?.code == 0 || loginBar.value){
|
||||
await sendCode();
|
||||
}
|
||||
if(hasUser.value?.code != 0 && loginBar.value){
|
||||
ElMessage.error('该手机号码未注册');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* 发送短信验证码 */
|
||||
const sendCode = async () => {
|
||||
if (!form.phone) {
|
||||
ElMessage.error('请输入手机号码');
|
||||
return;
|
||||
}
|
||||
imgCode.value = text.value;
|
||||
codeLoading.value = true;
|
||||
|
||||
sendSmsCaptcha({
|
||||
phone: form.phone
|
||||
}).then(res => {
|
||||
codeLoading.value = false;
|
||||
countdownTime.value = 30;
|
||||
// 开始对按钮进行倒计时
|
||||
countdownTimer = window.setInterval(() => {
|
||||
if (countdownTime.value <= 1) {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
countdownTime.value--;
|
||||
}, 1000);
|
||||
}).catch(msg => {
|
||||
ElMessage.error(msg)
|
||||
})
|
||||
};
|
||||
|
||||
const navigateTo = (url: string) => {
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
const onLoginBar = () => {
|
||||
// if(loginBar.value){
|
||||
// return navigateTo(`/passport/register`)
|
||||
// }
|
||||
loginBar.value = !loginBar.value
|
||||
activeName.value = loginBar.value ? 'account' : 'sms'
|
||||
}
|
||||
|
||||
/* 获取图形验证码 */
|
||||
const changeCaptcha = async () => {
|
||||
getCaptcha().then(captchaData => {
|
||||
captcha.value = captchaData.base64;
|
||||
text.value = captchaData.text;
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
useHead({
|
||||
title: `登录页 - ${config.value?.siteName || 'WEB应用开发平台'}`
|
||||
});
|
||||
|
||||
/**
|
||||
* 执行登录
|
||||
*/
|
||||
const onSubmit = async () => {
|
||||
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: 'https://server.gxwebsoft.com/api',method: "post",body: form})
|
||||
// 登录成功
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信验证码登录
|
||||
*/
|
||||
const onSubmitBySms = async () => {
|
||||
loginBySms({
|
||||
phone: form.phone,
|
||||
code: form.code,
|
||||
isSuperAdmin: true
|
||||
}).then(response => {
|
||||
ElMessage.success('登录成功')
|
||||
doLogin(response)
|
||||
}).catch(() => {
|
||||
ElMessage.error('验证码错误')
|
||||
changeCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号密码注册
|
||||
*/
|
||||
const onRegister = async () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'Loading'
|
||||
})
|
||||
|
||||
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('注册成功')
|
||||
doLogin(response)
|
||||
}).catch(() => {
|
||||
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();
|
||||
// })
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 登录成功执行
|
||||
const doLogin = async (data: any) => {
|
||||
const access_token = data?.access_token
|
||||
if(access_token){
|
||||
localStorage.setItem('token',access_token);
|
||||
token.value = access_token;
|
||||
console.log(access_token,'access_token')
|
||||
}
|
||||
if(data.user){
|
||||
user.value.userId = data.user.userId;
|
||||
user.value.phone = data.user.phone;
|
||||
user.value.gradeId = data.user.gradeId;
|
||||
user.value.gradeName = data.user.gradeName;
|
||||
user.value.avatar = data.user.avatar;
|
||||
user.value.balance = data.user.balance;
|
||||
localStorage.setItem('UserId',data.user.userId);
|
||||
localStorage.setItem('Avatar',data.user.avatar);
|
||||
localStorage.setItem('ServerTenantId',data.user.tenantId);
|
||||
// localStorage.setItem('TenantId',data.user.tenantId);
|
||||
}
|
||||
setTimeout(() => {
|
||||
navigateTo('/')
|
||||
},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{
|
||||
background: url("https://oss.wsdns.cn/20240904/6f5dc87c37334c4da3453826352a37d1.jpg");
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
/* 改变未激活标签的颜色 */
|
||||
.el-tabs__item {
|
||||
color: #606266;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
56
pages/passport/register.vue
Normal file
56
pages/passport/register.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<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"> 注册 </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 {useServerRequest} from "~/composables/useServerRequest";
|
||||
import type {ApiResult} from "~/api";
|
||||
import Auth from './components/Auth.vue';
|
||||
|
||||
// 配置信息
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const activeIndex = ref('');
|
||||
const website = useWebsite()
|
||||
const config = useConfigInfo();
|
||||
const merchantApply = ref<any>();
|
||||
|
||||
const reload = async () => {
|
||||
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) {
|
||||
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>
|
||||
131
pages/product/code/[id].vue
Normal file
131
pages/product/code/[id].vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 源码下载 </span>
|
||||
</template>
|
||||
<div class="text-lg">
|
||||
<el-result
|
||||
:title="`权限不足`"
|
||||
icon="warning"
|
||||
/>
|
||||
</div>
|
||||
<el-row id="container" :gutter="24" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="4" :xs="12" class="left mb-8">
|
||||
<nuxt-link :to="`${item.url}`" class="flex-1 cursor-pointer flex flex-col text-center" target="_blank">
|
||||
<el-card :body-style="{ padding: '0px' }" class="items-center flex justify-center" shadow="hover">
|
||||
<el-popover
|
||||
:content="item.comments"
|
||||
:width="200"
|
||||
placement="bottom"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="flex-1 py-5 sm:p-4 !p-4">
|
||||
<el-space class="text-gray-700 dark:text-white text-base font-semibold h-[28px] flex justify-center items-center">
|
||||
<el-image v-if="item.icon" :alt="item.name" :src="item.icon" style="height: 32px"/>
|
||||
<span :title="item.name" class="text-lg line-clamp-1">{{ item.name }}</span>
|
||||
</el-space>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-card>
|
||||
</nuxt-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-page-header>
|
||||
<Pagination :total="total" @done="search" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
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 {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {pageCmsLink} from "~/api/cms/cmsLink";
|
||||
import type {CmsLink, CmsLinkParam} from "~/api/cms/cmsLink/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navId = ref();
|
||||
const list = ref<CmsLink[]>([]);
|
||||
const category = ref<CmsNavigation[]>([]);
|
||||
const total = ref<number>(0);
|
||||
|
||||
// 获取状态
|
||||
const page = usePage();
|
||||
const layout = useLayout();
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsLinkParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
categoryId: undefined
|
||||
});
|
||||
|
||||
const id = ref<number>(0);
|
||||
|
||||
const showMenu = (item: CmsLink) => {
|
||||
id.value = Number(item.id);
|
||||
};
|
||||
|
||||
const hideMenu = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
getCmsNavigation(navId.value).then(data => {
|
||||
// 获取栏目信息
|
||||
page.value = data
|
||||
layout.value.banner = data.banner;
|
||||
if(navId.value){
|
||||
where.categoryId = navId.value;
|
||||
}
|
||||
pageCmsLink(where).then(response => {
|
||||
if(response){
|
||||
total.value = response?.count;
|
||||
list.value = response?.list;
|
||||
}
|
||||
}).finally(() => showSearch.value = false)
|
||||
|
||||
// 设置页面标题
|
||||
useSeoMeta({
|
||||
description: data.comments || data.title,
|
||||
keywords: data.title,
|
||||
titleTemplate: `${data?.title}` + ' - %s',
|
||||
})
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err,'加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.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>
|
||||
131
pages/product/docs/[id].vue
Normal file
131
pages/product/docs/[id].vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<Banner :layout="layout" />
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ '产品文档' }} </span>
|
||||
</template>
|
||||
<!-- <div class="text-lg">-->
|
||||
<!-- <el-result-->
|
||||
<!-- icon="warning"-->
|
||||
<!-- :title="`权限不足`"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<el-row id="container" :gutter="24" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :span="6" :xs="24" class="left mb-6">
|
||||
<el-card :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" shadow="hover" @click="navigateTo(`/detail/${item.articleId}.html`)">
|
||||
<el-image
|
||||
:lazy="true"
|
||||
:src="item.image"
|
||||
class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50" fit="cover"/>
|
||||
<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" :src="`${item.avatar}`" size="small" />
|
||||
{{ 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 lang="ts" setup>
|
||||
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";
|
||||
import {getCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navId = ref();
|
||||
const website = ref<CmsWebsite>();
|
||||
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,
|
||||
status: 0,
|
||||
parentId: undefined,
|
||||
categoryId: undefined
|
||||
});
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
getCmsWebsiteAll(navId.value).then(data => {
|
||||
// 获取栏目信息
|
||||
website.value = data
|
||||
where.tenantId = data.tenantId;
|
||||
pageCmsArticle(where).then(response => {
|
||||
if(response){
|
||||
total.value = response?.count;
|
||||
list.value = response?.list;
|
||||
}
|
||||
})
|
||||
|
||||
// 设置页面标题
|
||||
useSeoMeta({
|
||||
description: data.comments || data.websiteName,
|
||||
keywords: data.websiteName,
|
||||
titleTemplate: `${data.websiteName}` + ' - %s',
|
||||
})
|
||||
|
||||
}).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>
|
||||
141
pages/product/index.vue
Normal file
141
pages/product/index.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<div class="text-large font-600"> 产品中心 </div>
|
||||
</template>
|
||||
<!-- 应用列表 -->
|
||||
<el-row id="container" :gutter="24" class="clearfix">
|
||||
<el-col v-for="(item,index) in list" :key="index" :sm="6" :xs="12" class="left mb-8">
|
||||
<el-card :body-style="{ padding: '0px' }" class="h-[180px] items-center flex justify-center" shadow="hover" @mouseleave="hideMenu" @mouseover="showMenu(item)">
|
||||
<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">
|
||||
<nuxt-link :to="`/market/${item.websiteId}`" target="_blank">
|
||||
<el-avatar
|
||||
:size="55" :src="item.websiteLogo" shape="square" style="background-color: white;"/>
|
||||
</nuxt-link>
|
||||
<div class="flex-1 cursor-pointer flex flex-col text-center">
|
||||
<nuxt-link :to="`/market/${item.websiteId}`" target="_blank">
|
||||
<el-button type="text"><span class="text-lg text-gray-800">{{ item.websiteName }}</span></el-button>
|
||||
</nuxt-link>
|
||||
<div v-if="id == item.websiteId" class="flex text-gray-400 text-sm font-normal py2 justify-between items-center">
|
||||
<div>
|
||||
<nuxt-link :to="`/market/${item.websiteId}`"><span class="text-gray-400 hover:text-green-700">详情</span></nuxt-link>
|
||||
<el-divider direction="vertical" />
|
||||
<nuxt-link :to="`/market/${item.websiteId}`"><span class="text-gray-400 hover:text-green-700">评论</span></nuxt-link>
|
||||
<el-divider direction="vertical" />
|
||||
<nuxt-link :to="`/product/code/${item.websiteId}`"><span class="text-gray-400 hover:text-green-700">源码</span></nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-page-header>
|
||||
|
||||
<Pagination :total="total" @done="search"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ArrowLeft} from '@element-plus/icons-vue'
|
||||
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
// 页面信息
|
||||
const list = ref<CmsWebsite[]>([]);
|
||||
const total = ref(0);
|
||||
const id = ref<number>();
|
||||
|
||||
const inputWidth = ref<string>('180px');
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
const where = ref<CmsWebsiteParam>({
|
||||
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
const showDomain = (item: CmsWebsite) => {
|
||||
id.value = Number(item.websiteId);
|
||||
};
|
||||
|
||||
const hideDomain = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
const showMenu = (item: CmsWebsite) => {
|
||||
id.value = Number(item.websiteId);
|
||||
};
|
||||
|
||||
const hideMenu = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async (where: any) => {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
pageCmsWebsiteAll({
|
||||
...where,
|
||||
market: true,
|
||||
official: true,
|
||||
sort: 'websiteId',
|
||||
order: 'asc',
|
||||
limit: where.limit || 12
|
||||
}).then(response => {
|
||||
if (response?.list) {
|
||||
list.value = response?.list.map(d => {
|
||||
if (d.domain == '' || d.domain == null) {
|
||||
d.domain = `${d.websiteCode}.wsdns.cn`;
|
||||
}
|
||||
return d;
|
||||
});
|
||||
total.value = response.count;
|
||||
}
|
||||
loading.value = false;
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
}).finally(() => {
|
||||
showSearch.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsWebsiteParam) => {
|
||||
navigateTo({
|
||||
path: '/market',
|
||||
query: {
|
||||
market: 1,
|
||||
page: data.page,
|
||||
limit: 12
|
||||
}
|
||||
})
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(query) => {
|
||||
where.value = query;
|
||||
console.log(where.value,'>>>>>')
|
||||
reload(query);
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
|
||||
123
pages/search/[keywords].vue
Normal file
123
pages/search/[keywords].vue
Normal 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"> {{ '站内搜索' }} </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 mt-5 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>
|
||||
53
pages/search/components/ArticleList.vue
Normal file
53
pages/search/components/ArticleList.vue
Normal 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>
|
||||
53
pages/search/components/SiteList.vue
Normal file
53
pages/search/components/SiteList.vue
Normal 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>
|
||||
286
pages/show/[id].vue
Normal file
286
pages/show/[id].vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<!-- 文章详情 -->
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-20">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<!-- <template #breadcrumb>-->
|
||||
<!-- <Breadcrumb :data="form" :categoryName="form?.categoryName" />-->
|
||||
<!-- </template>-->
|
||||
<template #content>
|
||||
<span class="text-large font-600"> {{ page.title }} </span>
|
||||
</template>
|
||||
<el-row :gutter="24" class="mt-5">
|
||||
<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">
|
||||
<el-image
|
||||
:src="item"
|
||||
:zoom-rate="1.2"
|
||||
:max-scale="7"
|
||||
:min-scale="0.2"
|
||||
:preview-src-list="srcList"
|
||||
: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>
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type {ApiResult, PageResult} from "~/api";
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import {useServerRequest} from "~/composables/useServerRequest";
|
||||
import {useLayout, usePage, useWebsite} from "~/composables/configState";
|
||||
import type {BreadcrumbItem} from "~/types/global";
|
||||
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import Comments from './components/Comments.vue';
|
||||
import type {CompanyComment} from "~/api/system/companyComment/model";
|
||||
import {getCmsArticle, pageCmsArticle} from "~/api/cms/cmsArticle";
|
||||
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
|
||||
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/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,
|
||||
//
|
||||
likes: undefined,
|
||||
// 排序
|
||||
sortNumber: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 状态
|
||||
status: 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
|
||||
});
|
||||
|
||||
const doComments = async (page: any) => {
|
||||
commentsPage.value = page;
|
||||
await reloadComments();
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back(); // 返回上一页
|
||||
}
|
||||
|
||||
// 加载评论
|
||||
const reloadComments = async () => {
|
||||
const {data: commentsResponse} = await useServerRequest<ApiResult<PageResult<CompanyComment>>>('/system/company-comment/page', {
|
||||
params: {
|
||||
companyId: getIdBySpm(5),
|
||||
page: commentsPage.value,
|
||||
// status: 1
|
||||
}
|
||||
})
|
||||
if(commentsResponse.value && commentsResponse.value?.data){
|
||||
comments.value = commentsResponse.value?.data?.list
|
||||
commentsTotal.value = commentsResponse.value?.data?.count;
|
||||
}
|
||||
}
|
||||
|
||||
// 读取导航详情
|
||||
const reload = async () => {
|
||||
getCmsArticle(navId.value).then(data => {
|
||||
// 获取栏目信息
|
||||
page.value = data
|
||||
assignFields(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;
|
||||
// // 加载文章列表
|
||||
// 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,'加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(id) => {
|
||||
navId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.content {
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
201
pages/show/components/Comments.vue
Normal file
201
pages/show/components/Comments.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="w-full sm:py-2"
|
||||
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>
|
||||
</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>
|
||||
<!-- 发表评论 -->
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="发表评论"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible = false"
|
||||
>
|
||||
<el-form-item prop="rate">
|
||||
<el-rate v-model="form.rate" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="comments">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="最多300字,支持 markdown 语法。请认真填写评论内容,以便于帮助作者更好完善插件,如需反馈问题,请到下方插件提问进行提交。"/>
|
||||
</el-form-item>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 发起投诉 -->
|
||||
<el-dialog
|
||||
v-model="visible2"
|
||||
title="为什么举报此内容?"
|
||||
align-center
|
||||
width="500"
|
||||
:before-close="() => visible2 = false"
|
||||
>
|
||||
<el-checkbox-group v-model="checkList">
|
||||
<el-checkbox label="与我无关" value="与我无关"/>
|
||||
<el-checkbox label="文章过时" value="文章过时"/>
|
||||
<el-checkbox label="标题有误" value="标题有误"/>
|
||||
<el-checkbox label="图像质量差或视觉缺陷" value="图像质量差或视觉缺陷"/>
|
||||
<el-checkbox label="垃圾邮件" value="垃圾邮件"/>
|
||||
<el-checkbox label="成人或违法违规内容" value="成人或违法违规内容"/>
|
||||
<el-checkbox label="侵犯知识产权" value="侵犯知识产权"/>
|
||||
</el-checkbox-group>
|
||||
<div class="py-3">
|
||||
<el-input v-model="form.comments" :rows="5" type="textarea"
|
||||
placeholder="在此处输入反馈。请记住不要包含个人信息,如电话号码。"/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="visible2 = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)">
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {FullScreen} from '@element-plus/icons-vue'
|
||||
import type {ApiResult} from "~/api";
|
||||
import type {FormInstance, FormRules} from "element-plus";
|
||||
import {useClientRequest} from "~/composables/useClientRequest";
|
||||
import {reactive, ref} from "vue";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
import type {CompanyComment} from "~/api/system/companyComment/model";
|
||||
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
companyId?: number;
|
||||
comments?: CompanyComment[];
|
||||
count?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const visible = ref<boolean>(false);
|
||||
const visible2 = ref<boolean>(false);
|
||||
const checkList = ref<string[]>([]);
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', page: number): void
|
||||
}>()
|
||||
|
||||
// 配置信息
|
||||
const {form, resetFields} = useFormData<CompanyComment>({
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
userId: undefined,
|
||||
companyId: undefined,
|
||||
rate: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules<any>>({
|
||||
rate: [
|
||||
{required: true, message: '请输入评分', trigger: 'blur'},
|
||||
],
|
||||
comments: [
|
||||
{required: true, message: '请输入手机号码', trigger: 'blur'},
|
||||
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
const onComments = () => {
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const onComplaint = () => {
|
||||
visible2.value = true;
|
||||
}
|
||||
|
||||
const onPageChange = (page: number) => {
|
||||
emit('done', page)
|
||||
}
|
||||
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (form.rate === 0) {
|
||||
ElMessage.error('还没有评分哦!')
|
||||
return false;
|
||||
}
|
||||
form.companyId = Number(getIdBySpm(5));
|
||||
useClientRequest<ApiResult<any>>(`/system/company-comment`, {
|
||||
method: 'POST',
|
||||
body: form
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.message)
|
||||
visible.value = false
|
||||
resetFields();
|
||||
emit('done',0)
|
||||
} else {
|
||||
return ElMessage.error(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
135
pages/show/components/PageBanner.vue
Normal file
135
pages/show/components/PageBanner.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<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="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-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>
|
||||
<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>
|
||||
</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>
|
||||
</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";
|
||||
|
||||
const token = useToken();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
desc?: string;
|
||||
buyUrl?: string;
|
||||
form?: CmsArticle;
|
||||
value?: number;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void
|
||||
}>()
|
||||
|
||||
const onBuy = (item: Company) => {
|
||||
// if(item.type === 1){
|
||||
// // 插件
|
||||
// openSpmUrl(`/product/checkout`,item,item.productId)
|
||||
// }else {
|
||||
// // 产品
|
||||
// openSpmUrl(`/product/create`,item,item.productId)
|
||||
// }
|
||||
if (!token.value || token.value == '') {
|
||||
ElMessage.error('请先登录');
|
||||
setTimeout(() => {
|
||||
openUrl(`/product/create`, item, item.companyId)
|
||||
}, 500)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 安装插件
|
||||
const installPlug = () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '安装中...'
|
||||
})
|
||||
useClientRequest<ApiResult<any>>(`/system/menu/install`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
companyId: getIdBySpm(5)
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
setTimeout(() => {
|
||||
ElMessage.success(res.message);
|
||||
loading.close()
|
||||
emit('done')
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.rounded-avatar {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.rounded-avatar-xs {
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
||||
113
pages/support/authorize/index.vue
Normal file
113
pages/support/authorize/index.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600 line-clamp-1"> 商业授权查询 </span>
|
||||
</template>
|
||||
<div class="w-screen-sm m-auto">
|
||||
<el-input
|
||||
v-model="where.keywords"
|
||||
:placeholder="`搜索关键词`"
|
||||
:suffix-icon="Search"
|
||||
class="w-full my-5"
|
||||
size="large"
|
||||
@change="reload"
|
||||
/>
|
||||
<el-card class="my-5 sm:my-10 sm:px-2" shadow="hover">
|
||||
<el-result
|
||||
:title="`websoft.top 该域名已授权`"
|
||||
icon="success"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
</el-page-header>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ArrowLeft, Search} from '@element-plus/icons-vue'
|
||||
import type {CmsArticleParam} from "~/api/cms/cmsArticle/model";
|
||||
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
|
||||
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
// 页面信息
|
||||
const list = ref<CmsWebsite[]>([]);
|
||||
const total = ref(0);
|
||||
const id = ref<number>();
|
||||
const inputWidth = ref<string>('180px');
|
||||
const showSearch = ref<boolean>(false);
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<CmsWebsiteParam>({
|
||||
keywords: '',
|
||||
page: 1,
|
||||
limit: 12,
|
||||
status: undefined,
|
||||
recommend: undefined,
|
||||
search: true,
|
||||
websiteType: undefined,
|
||||
categoryId: undefined,
|
||||
lang: undefined
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
const handleFocus = () => {
|
||||
inputWidth.value = '400px'; // 聚焦时宽度
|
||||
}
|
||||
const handleBlur = () => {
|
||||
inputWidth.value = '180px'; // 聚焦时宽度
|
||||
}
|
||||
|
||||
const showDomain = (item: CmsWebsite) => {
|
||||
id.value = Number(item.websiteId);
|
||||
};
|
||||
|
||||
const hideDomain = () => {
|
||||
id.value = 0;
|
||||
};
|
||||
|
||||
// 加载页面数据
|
||||
const reload = async () => {
|
||||
await pageCmsWebsiteAll({
|
||||
...where,
|
||||
plugin: false
|
||||
}).then(response => {
|
||||
if (response?.list) {
|
||||
list.value = response?.list;
|
||||
total.value = response.count;
|
||||
}
|
||||
}).catch(() => {
|
||||
}).finally(() => showSearch.value = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsArticleParam) => {
|
||||
where.page = data.page;
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.el-input {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
151
pages/tags/[id].vue
Normal file
151
pages/tags/[id].vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
|
||||
<Banner :layout="layout" />
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div id="container" class="clearfix xl:w-screen-xl m-auto">
|
||||
|
||||
<div class="left">
|
||||
<!-- 内页左侧组件 -->
|
||||
<Left :category="category" :title="$t('product.title')" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="right">
|
||||
<div class="sitemp h-[32px] flex justify-between">
|
||||
<h2>
|
||||
{{ $t('label') }}:{{ where.tags }}
|
||||
</h2>
|
||||
<Breadcrumb :data="page" :categoryName="$t('label')" />
|
||||
</div>
|
||||
<el-alert v-if="where.tags" :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>
|
||||
</div>
|
||||
</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 {findTags, pageCmsArticle, pageTags} 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>({
|
||||
tags: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
status: 0,
|
||||
parentId: undefined,
|
||||
categoryId: undefined,
|
||||
lang: i18n.locale.value
|
||||
});
|
||||
|
||||
const replaceKeywords = (text: any) => {
|
||||
return text.replace(`${where.tags}`,'<font color=#ff0000>' + where.tags + '</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: i18n.locale.value == 'en' ? 1073 : 998,
|
||||
}).then(categoryData => {
|
||||
category.value = categoryData;
|
||||
// 加载文章列表
|
||||
if(!getPath(1)){
|
||||
return ElMessage.error('请输入搜索关键词!');
|
||||
}
|
||||
where.tags = `${route.params.id}`;
|
||||
pageTags({
|
||||
tags: `${route.params.id}`
|
||||
}).then(response => {
|
||||
if(response){
|
||||
total.value = response?.length;
|
||||
list.value = response;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}).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>
|
||||
33
pages/tailwind-css/mobile/index.vue
Normal file
33
pages/tailwind-css/mobile/index.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<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"> TailwindCSS 测试</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-space class="flex items-center">
|
||||
</el-space>
|
||||
</template>
|
||||
<div class="flex mb-20 mt-10 justify-center">
|
||||
<div class="hidden-sm-and-up w-full">
|
||||
<h1 class="text-xl text-left my-4">手机版</h1>
|
||||
<div class="my-1 bg-white text-center h-[100px] items-center flex justify-center text-xl">小屏幕≥640px</div>
|
||||
</div>
|
||||
<div class="hidden-sm-and-down text-center w-full">
|
||||
<h1 class="text-xl text-left my-4">PC版</h1>
|
||||
<div class="my-1 bg-white text-center h-[100px] items-center flex justify-center text-xl">640px≥屏幕</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
44
pages/tailwind-css/screen/index.vue
Normal file
44
pages/tailwind-css/screen/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<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"> TailwindCSS 测试</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-space class="flex items-center">
|
||||
</el-space>
|
||||
</template>
|
||||
</el-page-header>
|
||||
</div>
|
||||
<div class="flex mb-20 mt-10 justify-start">
|
||||
<div class="hidden-sm-and-up text-2xl flex justify-center">
|
||||
<span class="my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">hidden-sm-and-up</span>
|
||||
</div>
|
||||
<div class="hidden-sm-and-down text-center">
|
||||
<span class="my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">hidden-sm-and-down</span>
|
||||
</div>
|
||||
<div class="w-auto hidden-sm-and-down">
|
||||
<h2 class="text-lg">w-screen-sm:宽度小于640px</h2>
|
||||
<div class="w-screen-sm sm:flex my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">w-screen-sm</div>
|
||||
<h2 class="text-lg">w-screen-md:宽度小于768px</h2>
|
||||
<div class="w-screen-md md:flex my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">w-screen-md</div>
|
||||
<h2 class="text-lg">w-screen-lg:宽度小于1024px</h2>
|
||||
<div class="w-screen-lg lg:flex my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">w-screen-lg</div>
|
||||
<h2 class="text-lg">w-screen-xl:宽度小于1280px</h2>
|
||||
<div class="w-screen-xl xl:flex my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">w-screen-xl</div>
|
||||
<h2 class="text-lg">w-screen-2xl:宽度小于1536px</h2>
|
||||
<div class="w-screen-2xl 2xl:flex my-1 bg-gray-400 h-[50px] text-center items-center flex justify-center text-white text-xl">w-screen-2xl</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const goBack = () => {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
65
pages/user/auth.vue
Normal file
65
pages/user/auth.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 实名认证 </span>
|
||||
</template>
|
||||
<el-card shadow="hover" class="my-10 px-2">
|
||||
<el-row :gutter="30" justify="space-between">
|
||||
<el-col :sm="16" :xs="24">
|
||||
<Auth @done="reload"/>
|
||||
</el-col>
|
||||
<el-col :md="5" :xs="24">
|
||||
<div class="w-full mt-2 text-center" v-if="isCheck">
|
||||
<el-alert type="warning" :closable="false" :title="`扫二维码完成实名认证`" />
|
||||
<el-image :src="config.wxQrcode" shape="square" class="mt-2" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
|
||||
import {useConfigInfo} from "~/composables/configState";
|
||||
import {ref} from 'vue'
|
||||
import Auth from './components/Auth.vue';
|
||||
|
||||
// 配置信息
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const activeIndex = ref('');
|
||||
const config = useConfigInfo();
|
||||
const isCheck = ref(true);
|
||||
|
||||
const reload = async (status?: boolean) => {
|
||||
// 未登录状态(是否强制登录)
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token || token == '') {
|
||||
navigateTo('/passport/login');
|
||||
return false;
|
||||
}
|
||||
|
||||
if(status){
|
||||
isCheck.value = false
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: `实名认证 - ${config.value?.siteName}`
|
||||
});
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back(); // 返回上一页
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
activeIndex.value = path;
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
564
pages/user/components/Auth.vue
Normal file
564
pages/user/components/Auth.vue
Normal file
@@ -0,0 +1,564 @@
|
||||
<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">
|
||||
<el-tab-pane :name="0" label="个人认证">
|
||||
<!-- 未完成认证 -->
|
||||
<template v-if="form.status === 0 && form.checkStatus">
|
||||
<el-result
|
||||
icon="warning"
|
||||
title="审核中"
|
||||
:sub-title="`您的申请已提交,请耐心等待工作人员的审核,非常感谢`"
|
||||
>
|
||||
</el-result>
|
||||
</template>
|
||||
<!-- 已完成认证 -->
|
||||
<template v-if="form.status === 1">
|
||||
<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-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-form-item label="手机号码" prop="phone">
|
||||
<el-input v-model="form.phone" disabled maxlength="11" placeholder="请输入真实有效的手机号码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名" prop="realName">
|
||||
<el-input v-model="form.realName" placeholder="请输入真实姓名"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号码" prop="idCard" required>
|
||||
<el-input v-model="form.idCard" placeholder="请输入证件号码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证">
|
||||
<el-upload
|
||||
v-model:file-list="sfzFile"
|
||||
action="https://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
: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-tab-pane>
|
||||
<el-tab-pane :name="1" label="企业认证">
|
||||
<!-- 未完成认证 -->
|
||||
<template v-if="form.status === 0 && form.checkStatus">
|
||||
<el-result
|
||||
icon="warning"
|
||||
title="审核中"
|
||||
:sub-title="`您的申请已提交,请耐心等待工作人员的审核,非常感谢`"
|
||||
>
|
||||
</el-result>
|
||||
</template>
|
||||
<!-- 已完成认证 -->
|
||||
<template v-if="form.status === 1">
|
||||
<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-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-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://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
: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://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
: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://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
: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>
|
||||
<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" disabled maxlength="11" placeholder="请输入真实有效的手机号码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号码" prop="idCard" required>
|
||||
<el-input v-model="form.idCard" placeholder="请输入证件号码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证">
|
||||
<el-upload
|
||||
v-model:file-list="sfzFile"
|
||||
action="https://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
: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-tab-pane>
|
||||
</el-tabs>
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<div class="flex justify-center">
|
||||
<el-image w-full :src="dialogImageUrl" alt="查看证件"/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-form>
|
||||
</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 useFormData from "~/utils/use-form-data";
|
||||
import {
|
||||
addShopMerchantApply, getShopMerchantApplyByPhone,
|
||||
getShopMerchantApplyByUserId,
|
||||
updateShopMerchantApply
|
||||
} from "~/api/shop/shopMerchantApply";
|
||||
import {useUser} from "~/composables/configState";
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', status: boolean): void
|
||||
}>()
|
||||
|
||||
const token = useToken();
|
||||
const user = useUser();
|
||||
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: user.value.phone,
|
||||
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 submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
|
||||
console.log('submit!', valid);
|
||||
|
||||
if (form.type === 0) {
|
||||
form.shopType = '个人开发者';
|
||||
form.merchantName = form.realName;
|
||||
}
|
||||
|
||||
if (form.type === 1) {
|
||||
form.shopType = '企业开发者';
|
||||
if (!form.yyzz) {
|
||||
ElMessage.error('请上传营业执照');
|
||||
return;
|
||||
}
|
||||
if (filesStr.value.length > 0) {
|
||||
form.files = JSON.stringify(filesStr.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (sfzStr.value.length < 2) {
|
||||
ElMessage.error('请上传身份证正反面');
|
||||
return;
|
||||
}
|
||||
|
||||
form.sfz1 = sfzStr.value[0];
|
||||
form.sfz2 = sfzStr.value[1];
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopMerchantApply : addShopMerchantApply;
|
||||
|
||||
try {
|
||||
const res = await saveOrUpdate({
|
||||
...form,
|
||||
files: filesStr.value.length ? JSON.stringify(filesStr.value) : undefined,
|
||||
yyzz: form.yyzz || undefined,
|
||||
sfz1: form.sfz1 || undefined,
|
||||
sfz2: form.sfz2 || undefined,
|
||||
});
|
||||
console.log(res);
|
||||
ElMessage.success('提交成功');
|
||||
await reload()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const reload = async () => {
|
||||
getShopMerchantApplyByPhone()
|
||||
.then(data => {
|
||||
assignFields(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 (user.value.phone) {
|
||||
form.phone = user.value.phone;
|
||||
}
|
||||
if (form.sfz1) {
|
||||
sfzFile.value.push({
|
||||
uid: 1,
|
||||
url: form.sfz1,
|
||||
name: '身份证正面',
|
||||
})
|
||||
sfzStr.value.push(form.sfz1)
|
||||
}
|
||||
if (form.sfz2) {
|
||||
sfzFile.value.push({
|
||||
uid: 2,
|
||||
url: form.sfz2,
|
||||
name: '身份证反面',
|
||||
})
|
||||
sfzStr.value.push(form.sfz2)
|
||||
}
|
||||
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((item: any) => {
|
||||
files.value.push({
|
||||
uid: i++,
|
||||
url: item,
|
||||
name: '',
|
||||
})
|
||||
filesStr.value.push(item)
|
||||
})
|
||||
}
|
||||
if(form.status == 1){
|
||||
emit('done',true)
|
||||
}
|
||||
isUpdate.value = true
|
||||
})
|
||||
.catch(() => {
|
||||
resetFields();
|
||||
emit('done',false)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
reload();
|
||||
</script>
|
||||
55
pages/user/components/Base.vue
Normal file
55
pages/user/components/Base.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<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;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', where: any): void
|
||||
}>()
|
||||
|
||||
const onSubmit = () => {
|
||||
emit('done')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
||||
45
pages/user/components/Order.vue
Normal file
45
pages/user/components/Order.vue
Normal 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="openUrl(`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>
|
||||
31
pages/user/components/Password.vue
Normal file
31
pages/user/components/Password.vue
Normal 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>
|
||||
60
pages/user/components/UserMenu.vue
Normal file
60
pages/user/components/UserMenu.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-space class="hidden-sm-and-down sm:w-[140px] sm:flex sm:mb-0 mb-5 w-full pr-7" direction="vertical">
|
||||
<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>
|
||||
121
pages/user/index.vue
Normal file
121
pages/user/index.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 用户中心</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<nuxt-link to="/user/modify" class="text-gray-400 text-sm">修改资料</nuxt-link>
|
||||
</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">
|
||||
<div class="flash bg-white rounded-lg w-full">
|
||||
<div class="lg:w-screen-lg w-full sm:px-4 sm:py-4 mb-10 mt-5">
|
||||
<el-descriptions :column="columnClasses" border class="px-4" title="用户资料">
|
||||
<el-descriptions-item
|
||||
label="头像"
|
||||
>
|
||||
<el-image
|
||||
style="width: 70px; height: 70px"
|
||||
:src="user?.avatar"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号码">{{ user?.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户ID">{{ user?.userId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="昵称">{{ user?.nickname }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ user?.sexName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ user?.email }}</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="所在省份">{{ user?.province }}</el-descriptions-item>-->
|
||||
<!-- <el-descriptions-item label="所在城市">{{ user?.city }}</el-descriptions-item>-->
|
||||
<el-descriptions-item label="可用余额">{{ user?.balance }}</el-descriptions-item>
|
||||
<el-descriptions-item label="可用积分">{{ user?.points }}</el-descriptions-item>
|
||||
<!-- <el-descriptions-item :rowspan="1" :span="2" label="生日">{{ user?.birthday }}</el-descriptions-item>-->
|
||||
<el-descriptions-item :rowspan="1" :span="2" label="个人签名">
|
||||
<p class="min-h-[60px]">{{ user?.introduction }}</p>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {ArrowLeft, View, Search} from '@element-plus/icons-vue'
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type {User} from '@/api/system/user/model';
|
||||
import {ref} from 'vue'
|
||||
import {updateUser} from "~/api/layout";
|
||||
|
||||
// 配置信息
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const user = useUser();
|
||||
const activeIndex = ref('');
|
||||
|
||||
// 配置信息
|
||||
const {form, assignFields} = useFormData<User>({
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
username: undefined,
|
||||
phone: undefined,
|
||||
mobile: undefined,
|
||||
sex: undefined,
|
||||
sexName: undefined,
|
||||
email: undefined,
|
||||
password: undefined,
|
||||
code: undefined,
|
||||
smsCode: undefined,
|
||||
comments: undefined,
|
||||
remember: true,
|
||||
tenantName: undefined
|
||||
});
|
||||
|
||||
const reload = async () => {
|
||||
// 未登录状态(是否强制登录)
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token || token == '') {
|
||||
navigateTo('/passport/login');
|
||||
return false;
|
||||
}
|
||||
if (user.value) {
|
||||
form.userId = user.value.userId;
|
||||
form.nickname = user.value.nickname;
|
||||
form.realName = user.value.realName;
|
||||
form.mobile = user.value.mobile;
|
||||
form.email = user.value.email;
|
||||
form.sex = user.value.sex;
|
||||
form.comments = user.value.comments;
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back(); // 返回上一页
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
updateUser(form).then(() => {
|
||||
ElMessage.success('修改成功');
|
||||
});
|
||||
}
|
||||
|
||||
const columnClasses = computed(() => {
|
||||
const width = window.innerWidth;
|
||||
if (width < 640) { // 小于 640px 时为 1 列
|
||||
return 1;
|
||||
} else { // 大于等于 1024px 时为 4 列
|
||||
return 2;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
activeIndex.value = path;
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
34
pages/user/logout.vue
Normal file
34
pages/user/logout.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 退出登录 </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">
|
||||
|
||||
</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>
|
||||
168
pages/user/modify.vue
Normal file
168
pages/user/modify.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 修改资料 </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">
|
||||
<div class="flash bg-white rounded-lg w-full">
|
||||
<div class="lg:w-screen-lg w-full sm:px-4 sm:py-4 mb-10 mt-4">
|
||||
<el-form :model="form" label-width="auto" size="large" label-position="top">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8" :xs="24">
|
||||
<el-form-item label="上传头像" class="px-4">
|
||||
<el-upload
|
||||
v-model:file-list="avatar"
|
||||
action="https://server.gxwebsoft.com/api/oss/upload"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
TenantId: 5,
|
||||
}"
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-remove="avatarRemove"
|
||||
:on-success="avatarSuccess"
|
||||
>
|
||||
<el-icon>
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16" :xs="24">
|
||||
<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.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.introduction" 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-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-page-header>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft, View, Search, Plus} from '@element-plus/icons-vue'
|
||||
import type {UploadProps, UploadUserFile} from 'element-plus'
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type {User} from '@/api/system/user/model';
|
||||
import {ref} from 'vue'
|
||||
import {updateUser} from "~/api/layout";
|
||||
|
||||
// 配置信息
|
||||
const token = useToken();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const user = useUser();
|
||||
const activeIndex = ref('');
|
||||
const avatar = ref<UploadUserFile[]>([])
|
||||
const dialogImageUrl = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
// 配置信息
|
||||
const {form, assignFields} = useFormData<User>({
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
username: undefined,
|
||||
avatar: undefined,
|
||||
phone: undefined,
|
||||
mobile: undefined,
|
||||
sex: undefined,
|
||||
sexName: undefined,
|
||||
email: undefined,
|
||||
password: undefined,
|
||||
code: undefined,
|
||||
smsCode: undefined,
|
||||
comments: undefined,
|
||||
introduction: undefined,
|
||||
remember: true,
|
||||
tenantName: undefined
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: `用户中心`
|
||||
});
|
||||
|
||||
const reload = async () => {
|
||||
// 未登录状态(是否强制登录)
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token || token == '') {
|
||||
navigateTo('/passport/login');
|
||||
return false;
|
||||
}
|
||||
if (user.value) {
|
||||
form.userId = user.value.userId;
|
||||
form.nickname = user.value.nickname;
|
||||
form.realName = user.value.realName;
|
||||
form.avatar = user.value.avatar;
|
||||
form.mobile = user.value.mobile;
|
||||
form.email = user.value.email;
|
||||
form.sex = user.value.sex;
|
||||
form.comments = user.value.comments;
|
||||
form.introduction = user.value.introduction
|
||||
avatar.value = []
|
||||
if(form.avatar){
|
||||
avatar.value.push({
|
||||
uid: form.userId,
|
||||
url: form.avatar,
|
||||
name: '用户头像',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back(); // 返回上一页
|
||||
}
|
||||
|
||||
const avatarRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
|
||||
form.avatar = '';
|
||||
}
|
||||
|
||||
const avatarSuccess = (e: any) => {
|
||||
form.avatar = e.data.downloadUrl
|
||||
}
|
||||
|
||||
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
||||
dialogImageUrl.value = uploadFile.url!
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
form.tenantId = Number(localStorage.getItem('ServerTenantId'));
|
||||
updateUser(form).then(() => {
|
||||
ElMessage.success('修改成功');
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
activeIndex.value = path;
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
101
pages/user/order.vue
Normal file
101
pages/user/order.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 我的订单 </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">
|
||||
<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}`
|
||||
});
|
||||
|
||||
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>
|
||||
150
pages/user/password.vue
Normal file
150
pages/user/password.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="xl:w-screen-xl m-auto py-4 mt-12 px-4 sm:px-0 sm:mt-2">
|
||||
<el-page-header :icon="ArrowLeft" @back="goBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600"> 用户中心 </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">
|
||||
<!-- 用户菜单 -->
|
||||
<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}`
|
||||
});
|
||||
|
||||
|
||||
/* 发送短信验证码 */
|
||||
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>
|
||||
119
pages/video/[id].vue
Normal file
119
pages/video/[id].vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
|
||||
<Banner :layout="layout" />
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div id="container" class="clearfix xl:w-screen-xl m-auto">
|
||||
|
||||
<div class="left">
|
||||
<!-- 内页左侧组件 -->
|
||||
<Left :category="category" />
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="sitemp h-[32px] flex justify-between">
|
||||
<h2>
|
||||
{{ page.title }}
|
||||
</h2>
|
||||
<Breadcrumb :data="page" :categoryName="page.parentName || page.categoryName" />
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<CmsProductList :data="list" />
|
||||
|
||||
<Pagination :total="total" @done="search" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</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 {paramsId} from "~/utils/common";
|
||||
import Left from "~/components/Left.vue";
|
||||
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import {pageCmsArticle} from "~/api/cms/cmsArticle";
|
||||
import CmsProductList from "~/components/CmsProductList.vue";
|
||||
|
||||
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: 20,
|
||||
status: 0,
|
||||
parentId: undefined,
|
||||
categoryId: undefined,
|
||||
lang: i18n.locale.value
|
||||
});
|
||||
|
||||
const reload = async () => {
|
||||
getCmsNavigation(paramsId()).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;
|
||||
// 加载文章列表
|
||||
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,'加载失败...')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param data
|
||||
*/
|
||||
const search = (data: CmsArticleParam) => {
|
||||
where.page = data.page;
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
console.log('路由变化了',route.path)
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
198
pages/video/detail/[id].vue
Normal file
198
pages/video/detail/[id].vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<!-- 文章详情 -->
|
||||
<template>
|
||||
<Banner :layout="layout"/>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<div id="container" class="clearfix xl:w-screen-xl m-auto">
|
||||
|
||||
<div class="left">
|
||||
<!-- 内页左侧组件 -->
|
||||
<Left :category="category" :title="$t('video.title')" />
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="sitemp h-[32px] flex justify-between">
|
||||
<h2>
|
||||
{{ form.categoryName || '分类名称' }}
|
||||
</h2>
|
||||
<Breadcrumb :data="form" :categoryName="form.categoryName" :detail="$t('detail')" />
|
||||
</div>
|
||||
<div class="detail-container">
|
||||
|
||||
<!-- 产品详细 -->
|
||||
<div class="product_detail" id="pd1">
|
||||
<div class="allcontent clearfix">
|
||||
<div class="text-center text-xl text-gray-800 py-5">{{ form.title }}</div>
|
||||
<el-carousel v-if="form.files" :interval="4000" height="400px">
|
||||
<el-carousel-item v-for="item in form.files" :key="item" style="display: flex; align-items: center; justify-content: center">
|
||||
<el-image :src="item" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
|
||||
<div class="clearboth"></div>
|
||||
<div class="p_detail">
|
||||
<ul id="product-tab" class="product-tab clearfix">
|
||||
<li class="cur">{{ $t('show.detail') }}</li>
|
||||
|
||||
</ul>
|
||||
<!-- 内容组件 -->
|
||||
<Content class="text-sm" :data="form.content" />
|
||||
</div>
|
||||
<h3 class="tag">{{ $t('articleUrl') }}:{{ locationUrl() }} </h3>
|
||||
<Tags :data="form.tags" />
|
||||
<NextArticle />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 相关产品和相关新闻 -->
|
||||
<!-- <div class="relate_list">-->
|
||||
<!-- <CmsProductRelated :data="form" />-->
|
||||
<!-- <CmsArticleRelated :data="form" />-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
|
||||
import {getPath, locationUrl, paramsId} from "~/utils/common";
|
||||
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
|
||||
import useFormData from "~/utils/use-form-data";
|
||||
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";
|
||||
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
|
||||
import Tags from "~/components/Tags.vue";
|
||||
import Content from "~/components/Content.vue";
|
||||
|
||||
// 引入状态管理
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const layout = ref<Layout>({});
|
||||
const category = ref<CmsNavigation[]>([]);
|
||||
const parentName = ref('项目展示');
|
||||
|
||||
// 配置信息
|
||||
const {form, assignFields} = useFormData<CmsArticle>({
|
||||
// 文章id
|
||||
articleId: undefined,
|
||||
// 文章模型
|
||||
model: undefined,
|
||||
// 文章标题
|
||||
title: undefined,
|
||||
// 分类类型
|
||||
type: undefined,
|
||||
tags: undefined,
|
||||
// 展现方式
|
||||
showType: undefined,
|
||||
// 文章类型
|
||||
categoryId: undefined,
|
||||
// 文章分类
|
||||
categoryName: undefined,
|
||||
parentId: undefined,
|
||||
// 封面图
|
||||
image: undefined,
|
||||
// 附件
|
||||
files: undefined,
|
||||
// 缩列图
|
||||
thumbnail: undefined,
|
||||
// 视频地址
|
||||
video: undefined,
|
||||
// 上传的文件类型
|
||||
accept: undefined,
|
||||
// 来源
|
||||
source: undefined,
|
||||
// 文章内容
|
||||
content: undefined,
|
||||
// 虚拟阅读量
|
||||
virtualViews: undefined,
|
||||
// 实际阅读量
|
||||
actualViews: undefined,
|
||||
// 访问权限
|
||||
permission: undefined,
|
||||
// 访问密码
|
||||
password: undefined,
|
||||
password2: undefined,
|
||||
// 用户ID
|
||||
userId: undefined,
|
||||
// 用户昵称
|
||||
nickname: undefined,
|
||||
// 账号
|
||||
username: undefined,
|
||||
// 用户头像
|
||||
// userAvatar: undefined,
|
||||
author: undefined,
|
||||
// 所属门店ID
|
||||
shopId: undefined,
|
||||
//
|
||||
likes: undefined,
|
||||
// 排序
|
||||
sortNumber: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 状态
|
||||
status: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 更新时间
|
||||
updateTime: undefined,
|
||||
tenantId: undefined,
|
||||
// 租户名称
|
||||
tenantName: undefined,
|
||||
// 租户logo
|
||||
logo: undefined,
|
||||
// 详情页路径
|
||||
detail: undefined
|
||||
});
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
await getCmsArticle(paramsId()).then(data => {
|
||||
|
||||
assignFields(data);
|
||||
|
||||
layout.value.banner = data?.banner;
|
||||
if (data.files) {
|
||||
form.files = JSON.parse(data.files);
|
||||
}
|
||||
|
||||
// 二级栏目分类
|
||||
listCmsNavigation({
|
||||
parentId: data.parentId,
|
||||
lang: i18n.locale.value
|
||||
}).then(list => {
|
||||
category.value = list
|
||||
// 宣传视频
|
||||
if(data.categoryName == '宣传视频'){
|
||||
parentName.value = '宣传视频'
|
||||
category.value = [];
|
||||
}
|
||||
})
|
||||
|
||||
// seo
|
||||
useSeoMeta({
|
||||
description: form.comments || form.title,
|
||||
keywords: form.title,
|
||||
titleTemplate: `${form?.title}` + ' - %s',
|
||||
})
|
||||
|
||||
}).catch(err => {
|
||||
console.log(err,'err')
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
reload();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
230
pages/xieyi/[id].vue
Normal file
230
pages/xieyi/[id].vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<script setup lang="ts">
|
||||
// 引入状态管理
|
||||
import { getCmsArticle } from '~/api/cms/cmsArticle';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
import { getNavIdByParamsId } from '~/utils/common';
|
||||
import { listCmsNavigation } from '~/api/cms/cmsNavigation';
|
||||
import useFormData from '~/utils/use-form-data';
|
||||
import type { CmsArticle } from '~/api/cms/cmsArticle/model';
|
||||
import Content from '~/components/Content.vue';
|
||||
|
||||
definePageMeta({
|
||||
layout: false
|
||||
});
|
||||
|
||||
const articleId = ref();
|
||||
|
||||
// 配置信息
|
||||
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,
|
||||
// 文章编辑器
|
||||
editor: undefined,
|
||||
// 虚拟阅读量
|
||||
virtualViews: undefined,
|
||||
// 实际阅读量
|
||||
actualViews: undefined,
|
||||
// 访问权限
|
||||
permission: undefined,
|
||||
// 访问密码
|
||||
password: undefined,
|
||||
password2: undefined,
|
||||
// 用户ID
|
||||
userId: undefined,
|
||||
// 用户昵称
|
||||
nickname: undefined,
|
||||
// 账号
|
||||
username: undefined,
|
||||
// 用户头像
|
||||
// userAvatar: undefined,
|
||||
author: undefined,
|
||||
// 所属门店ID
|
||||
shopId: undefined,
|
||||
//
|
||||
likes: undefined,
|
||||
// 排序
|
||||
sortNumber: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 状态
|
||||
status: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 更新时间
|
||||
updateTime: undefined,
|
||||
// 租户ID
|
||||
tenantId: undefined,
|
||||
// 租户名称
|
||||
tenantName: undefined,
|
||||
// 租户logo
|
||||
logo: undefined,
|
||||
// 详情页路径
|
||||
detail: undefined
|
||||
});
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
await getCmsArticle(articleId.value)
|
||||
.then(data => {
|
||||
assignFields(data);
|
||||
})
|
||||
.catch(() => {
|
||||
navigateTo('/404');
|
||||
});
|
||||
|
||||
// seo
|
||||
useSeoMeta({
|
||||
description: form?.comments,
|
||||
keywords: form.title,
|
||||
titleTemplate: `${form?.title}` + ' - %s'
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
id => {
|
||||
articleId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<h1 class="title">{{ form?.title }}</h1>
|
||||
<div class="head"></div>
|
||||
<!-- 内容组件 -->
|
||||
<Content class="content text-lg py-5" :editor="form?.editor" :data="form.content" />
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
body {
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
background-color: #999;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Helvetica', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #27ae60;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #aaa;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #d5d5d5;
|
||||
border: 1px solid #aaa;
|
||||
padding: 5px 15px 5px 6px;
|
||||
text-align: left;
|
||||
vertical-align: baseline;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
table td {
|
||||
background-color: #efefef;
|
||||
border: 1px solid #aaa;
|
||||
padding: 6px 15px 6px 6px;
|
||||
vertical-align: text-top;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page {
|
||||
background-color: #fff;
|
||||
padding: 20px 40px;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 50px;
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
font-family:
|
||||
PingFang SC,
|
||||
Verdana,
|
||||
Helvetica Neue,
|
||||
Microsoft Yahei,
|
||||
Hiragino Sans GB,
|
||||
Microsoft Sans Serif,
|
||||
WenQuanYi Micro Hei,
|
||||
sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.head {
|
||||
line-height: 50px;
|
||||
margin: 0 0 40px 0;
|
||||
border-bottom: 1px dashed #ccc;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.foot {
|
||||
padding: 20px 0;
|
||||
line-height: 24px;
|
||||
border-top: 1px dashed #ccc;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
230
pages/xieyi/detail/[id].vue
Normal file
230
pages/xieyi/detail/[id].vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<script setup lang="ts">
|
||||
// 引入状态管理
|
||||
import { getCmsArticle } from '~/api/cms/cmsArticle';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
import { getNavIdByParamsId } from '~/utils/common';
|
||||
import { listCmsNavigation } from '~/api/cms/cmsNavigation';
|
||||
import useFormData from '~/utils/use-form-data';
|
||||
import type { CmsArticle } from '~/api/cms/cmsArticle/model';
|
||||
import Content from '~/components/Content.vue';
|
||||
|
||||
definePageMeta({
|
||||
layout: false
|
||||
});
|
||||
|
||||
const articleId = ref();
|
||||
|
||||
// 配置信息
|
||||
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,
|
||||
// 文章编辑器
|
||||
editor: undefined,
|
||||
// 虚拟阅读量
|
||||
virtualViews: undefined,
|
||||
// 实际阅读量
|
||||
actualViews: undefined,
|
||||
// 访问权限
|
||||
permission: undefined,
|
||||
// 访问密码
|
||||
password: undefined,
|
||||
password2: undefined,
|
||||
// 用户ID
|
||||
userId: undefined,
|
||||
// 用户昵称
|
||||
nickname: undefined,
|
||||
// 账号
|
||||
username: undefined,
|
||||
// 用户头像
|
||||
// userAvatar: undefined,
|
||||
author: undefined,
|
||||
// 所属门店ID
|
||||
shopId: undefined,
|
||||
//
|
||||
likes: undefined,
|
||||
// 排序
|
||||
sortNumber: undefined,
|
||||
// 备注
|
||||
comments: undefined,
|
||||
// 状态
|
||||
status: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 更新时间
|
||||
updateTime: undefined,
|
||||
// 租户ID
|
||||
tenantId: undefined,
|
||||
// 租户名称
|
||||
tenantName: undefined,
|
||||
// 租户logo
|
||||
logo: undefined,
|
||||
// 详情页路径
|
||||
detail: undefined
|
||||
});
|
||||
|
||||
// 请求数据
|
||||
const reload = async () => {
|
||||
await getCmsArticle(articleId.value)
|
||||
.then(data => {
|
||||
assignFields(data);
|
||||
})
|
||||
.catch(() => {
|
||||
navigateTo('/404');
|
||||
});
|
||||
|
||||
// seo
|
||||
useSeoMeta({
|
||||
description: form?.comments,
|
||||
keywords: form.title,
|
||||
titleTemplate: `${form?.title}` + ' - %s'
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
id => {
|
||||
articleId.value = getNavIdByParamsId(id);
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<h1 class="title">{{ form?.title }}</h1>
|
||||
<div class="head"></div>
|
||||
<!-- 内容组件 -->
|
||||
<Content class="content text-lg py-5" :editor="form?.editor" :data="form.content" />
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
body {
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
background-color: #999;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Helvetica', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #27ae60;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #aaa;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #d5d5d5;
|
||||
border: 1px solid #aaa;
|
||||
padding: 5px 15px 5px 6px;
|
||||
text-align: left;
|
||||
vertical-align: baseline;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
table td {
|
||||
background-color: #efefef;
|
||||
border: 1px solid #aaa;
|
||||
padding: 6px 15px 6px 6px;
|
||||
vertical-align: text-top;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page {
|
||||
background-color: #fff;
|
||||
padding: 20px 40px;
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 50px;
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
font-family:
|
||||
PingFang SC,
|
||||
Verdana,
|
||||
Helvetica Neue,
|
||||
Microsoft Yahei,
|
||||
Hiragino Sans GB,
|
||||
Microsoft Sans Serif,
|
||||
WenQuanYi Micro Hei,
|
||||
sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.head {
|
||||
line-height: 50px;
|
||||
margin: 0 0 40px 0;
|
||||
border-bottom: 1px dashed #ccc;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.foot {
|
||||
padding: 20px 0;
|
||||
line-height: 24px;
|
||||
border-top: 1px dashed #ccc;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user