新版本官网优化完成

This commit is contained in:
2025-02-12 16:37:07 +08:00
parent 43a2e17a80
commit 3efdbfc662
547 changed files with 23001 additions and 28169 deletions

19
pages/[...404].vue Normal file
View 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>

View File

@@ -1,76 +0,0 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 简单模式(常规单页面) -->
<PageContainer :form="form" :layout="layout" />
<!-- 高级模式(自定义组件) -->
<div class="flex flex-col" v-if="layout?.showLayout">
{{ layout }}
</div>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm} from "~/utils/common";
import PageContainer from "~/components/PageContainer.vue";
// 引入状态管理
const route = useRoute();
const website = useWebsite();
const layout = ref<any>();
const config = useConfigInfo();
const token = useToken();
const form = useForm();
const breadcrumb = ref<BreadcrumbItem>();
// 请求数据
const reload = async () => {
// 存在spm(优先级高)
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/' + getIdBySpm(5))
if (nav.value?.data) {
form.value = nav.value.data
}else{
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: route.path}})
if(nav.value?.data){
form.value = nav.value?.data;
}
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
// seo
useHead({
title: `${form.value.title} - ${website.value.websiteName}`,
meta: [{ name: form.value.design?.keywords, content: form.value.design?.description }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: `console.log(${JSON.stringify(form.value)})`,
},
],
});
// 面包屑
breadcrumb.value = form.value
}
watch(
() => route.path,
(path) => {
console.log(path,'=>Path')
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,89 +0,0 @@
<template>
<PageBanner :title="`${form?.title}`" :desc="`${form?.comments}`" />
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import type {Article} from "~/api/cms/article/model";
import CardList from './components/CardList.vue';
import type {ArticleCategory} from "~/api/cms/category/model";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
const activeName = ref(undefined)
// 获取状态
const form = ref<ArticleCategory>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const handleClick = (e:any) => {
console.log(e.index)
}
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,limit: 8,categoryId: route.params.categoryId, keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: route.path}})
if(nav.value?.data){
form.value = nav.value?.data;
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
useHead({
title: `文章列表 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

141
pages/article/[id].vue Normal file
View File

@@ -0,0 +1,141 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template>
<template #extra>
<div class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</div>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left">
<el-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";
const route = useRoute();
const router = useRouter();
const navId = ref();
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref<number>(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
// 加载页面数据
const reload = async () => {
getCmsNavigation(navId.value).then(data => {
// 获取栏目信息
page.value = data
layout.value.banner = data.banner;
// 设置页面标题
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(categoryData => {
category.value = categoryData;
console.log(categoryData)
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = data.navigationId;
}else {
where.categoryId = data.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}).catch(err => {
console.log(err,'加载失败...')
})
}
const goBack = () => {
router.back(); // 返回上一页
// window.history.back();
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,52 +0,0 @@
<template>
<div class="md:w-screen-xl m-auto relative sm:flex" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/detail`,item,item.articleId,true)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full h-[150px] cursor-pointer" />
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="line-clamp-2 font-semibold text-gray-700 dark:text-white text-base gap-1.5 text-xl">
<el-icon v-if="item.permission > 0"><Lock class="text-gray-400 pr-1"/></el-icon>
{{ item.title }}
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500 line-clamp-3">{{ item.comments }}</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20"/>
<span>{{ item.author }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,211 +0,0 @@
<template>
<UserCard :form="form" title="社区" desc="分享研发成果 交流技术经验" :avatar="user?.avatar"/>
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm, openSpmUrl} from "~/utils/common";
import type {Article} from "~/api/cms/article/model";
import useFormData from "~/utils/use-form-data";
import LikeArticle from "~/pages/detail/components/LikeArticle.vue";
import CardList from "~/pages/ask/components/CardList.vue";
import type {Company, CompanyParam} from "~/api/system/company/model";
import type {User} from "~/api/system/user/model";
// 引入状态管理
const route = useRoute();
const website = useWebsite();
const user = useUser();
const breadcrumb = ref<BreadcrumbItem>();
const previousArticle = ref<Article>();
const nextArticle = ref<Article>();
const disabled = ref<boolean>(false);
const showContent = ref<boolean>();
const showPassword = ref<boolean>();
const list = ref<Article[]>([]);
const page = ref<number>(1);
// 配置信息
const {form, assignFields} = useFormData<Article>({
// 文章id
articleId: undefined,
// 文章标题
title: undefined,
// 分类类型
type: undefined,
// 展现方式
showType: undefined,
// 文章类型
categoryId: undefined,
// 文章分类
categoryName: undefined,
parentId: undefined,
parentName: undefined,
parentPath: 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
});
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
// 验证密码
const checkPassword = async () => {
const {data: response} = await useServerRequest<ApiResult<unknown>>('/cms/cms-article/checkArticlePassword', {
query: {
password: form?.password,
password2: form.password2
}
})
if (response.value?.code === 0) {
showPassword.value = false;
showContent.value = true;
} else {
ElMessage.error(response.value?.message);
}
}
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
// const reload = async (path: string) => {
//
// if (response.value?.data) {
// if (list.value.length < response.value?.data.count) {
// disabled.value = false;
// if (response.value?.data.list) {
// list.value = list.value.concat(response.value?.data.list);
// }
// } else {
// disabled.value = true;
// }
// if (response.value.data.count == 0) {
// resultText.value = '暂无相关结果'
// }
// }
// }
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page', {
params: {
page: page.value,
limit: 8,
userId: route.params.userId,
keywords: where.keywords
}
})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
}
const { data: userInfo } = await useServerRequest<ApiResult<User>>(`/system/user/getByUserId/${getIdBySpm(5)}`)
console.log(userInfo.value)
// if (response.value?.data) {
// assignFields(response.value.data)
// if (form.permission === 1) {
// console.log('登录可见')
// return;
// }
// if (form.permission === 2) {
// console.log('需要密码')
// showPassword.value = true;
// return;
// }
// form.parentPath = getSpmUrl(`/category`, form, form.articleId);
// console.log(form.parentPath)
// }
// seo
useHead({
title: `${form.title} - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
// 面包屑
breadcrumb.value = form
}
watch(
() => route.path,
(path) => {
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="less">
.content {
img {
max-width: 100%;
}
}
</style>

View File

@@ -1,55 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer"
@click="openSpmUrl(`/detail`,item,item.articleId,true)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer"/>
<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 items-center gap-1.5">-->
<!-- <div class="flex items-center text-xl cursor-pointer max-w-md line-clamp-2"><el-icon-->
<!-- v-if="item.permission > 0"><Lock class="text-gray-400 pr-1"/></el-icon>{{ item.title }}</div>-->
<!-- </div>-->
<div class="line-clamp-2 font-semibold text-gray-700 dark:text-white text-base gap-1.5 text-xl">
{{ item.title }}
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.logo" :size="20" @click.stop="openSpmUrl(`/ask/${item.userId}`,item,item.userId)"/>
<span>{{ item.tenantName }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
<el-icon v-if="item.permission > 0" :size="24"><Lock class="text-gray-400 px-1"/></el-icon>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {Lock} from '@element-plus/icons-vue'
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if (!props.disabled) {
emit('done')
}
}
</script>

View File

@@ -1,103 +0,0 @@
<template>
<PageBanner :layout="layout" title="社区" desc="分享研发成果 交流技术经验" />
<el-tabs v-model="activeName" class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative pb-2" @tab-click="handleClick">
<el-tab-pane label="轻松一刻" name="happy"></el-tab-pane>
<el-tab-pane label="企业官网" name="website"></el-tab-pane>
<el-tab-pane label="企业商城" name="weShop"></el-tab-pane>
<el-tab-pane label="内容管理" name="cms"></el-tab-pane>
<el-tab-pane label="点餐外卖" name="food"></el-tab-pane>
<el-tab-pane label="派单系统" name="task"></el-tab-pane>
<el-tab-pane label="办公OA" name="oa"></el-tab-pane>
<el-tab-pane label="问答" name="ask"></el-tab-pane>
<el-tab-pane label="分享" name="share"></el-tab-pane>
</el-tabs>
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import type {Article} from "~/api/cms/article/model";
import CardList from './components/CardList.vue';
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
const activeName = ref(undefined)
// 获取状态
const form = ref<Navigation>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const handleClick = (e:any) => {
console.log(e.index)
}
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
tenantId: getIdBySpm(1),
keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: route.path}})
if(nav.value?.data){
form.value = nav.value?.data;
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
useHead({
title: `社区 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

193
pages/case/[id].vue Normal file
View File

@@ -0,0 +1,193 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template>
<template #extra>
<el-space class="flex items-center">
<el-select v-model="value" clearable placeholder="筛选" style="width: 200px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/show/${item.articleId}.html`)">
<el-image
:src="item.image"
fit="cover"
:lazy="true" class="w-full md:h-[166px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.title }}
</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500 line-clamp-2">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-between items-center mt-3 text-sm">
<el-space class="flex items-end">
<div class="text-gray-400 gap-1 flex items-center"><el-icon><View /></el-icon><span>{{ getViews(item) }}</span></div>
</el-space>
<div class="text-gray-400">
<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: 20,
status: 0,
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>

View File

@@ -1,63 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/item`, item,item.companyId,true)">
<el-image
:src="item.image"
:fit="fit" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.shortName }}
</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">
<el-space class="flex items-end">
<el-avatar size="small" :src="item.companyLogo" />
<span class="text-gray-400 line-clamp-1 pr-2">{{ item.companyName }}</span>
</el-space>
<el-button @click.stop="loginByToken(item)">
控制台
</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {loginByToken, openSpmUrl} from "~/utils/common";
import type {Company} from "~/api/system/company/model";
const props = withDefaults(
defineProps<{
list?: Company[];
disabled?: boolean;
fit?: any;
}>(),
{
fit: 'cover'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,115 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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 md:p-0 px-4">
<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 grid lg:grid-cols-2 lg:items-center" v-if="layout">
<div class="">
<h1
class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
<span v-if="layout.title">{{ layout.title }}</span>
<span v-if="layout.name">{{ layout.name }}</span>
</h1>
<div class="mt-4 text-lg text-gray-500 dark:text-gray-400">
{{ layout.description }}
</div>
<div class="flex justify-between w-full items-center mt-4">
<el-space>
<div class="w-[500px]">
<el-input
v-model="where.keywords"
placeholder="搜索"
:prefix-icon="Search"
@keydown.enter="handleClick"
>
<template #append>
<el-button size="large" type="primary" @click="handleClick">搜索</el-button>
</template>
</el-input>
</div>
<el-button
size="large"
@click="openSpmUrl(`/passport/login`)"
>
创建
</el-button>
</el-space>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import {useConfigInfo} from "~/composables/configState";
import {openSpmUrl} from "~/utils/common";
import type {CompanyParam} from "~/api/system/company/model";
const token = useToken();
const sysDomain = useSysDomain();
withDefaults(
defineProps<{
layout?: any;
title?: string;
desc?: string;
buyUrl?: string;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates'
}
);
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const config = useConfigInfo();
// 搜索结果
const handleClick = async () => {
if (where.keywords == '') {
return false;
}
if(activeName.value == 'web'){
const {data: response} = await useServerRequest<ApiResult<PageResult<Website>>>('/cms/cms-website/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value, keywords: where.keywords
}})
if(response.value?.data){
if (response.value?.data.list) {
websites.value = response.value?.data.list;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
}
</script>

View File

@@ -1,84 +0,0 @@
<template>
<PageBanner :layout="layout" :title="`${form?.categoryName}`" :desc="`${form?.comments}`" />
<CardList :param="{type: 0,official: true}" :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Navigation} from "~/api/cms/navigation/model";
import type {Company, CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Company[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
// 获取状态
const form = ref<Navigation>();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Company>>>('/system/company/pageAll',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
categoryId: getIdBySpm(5),
keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-navigation/${getIdBySpm(5)}`)
if(nav.value?.data){
form.value = nav.value?.data;
useHead({
title: `${form.value.title} - WEBSOFT`,
bodyAttrs: {
class: "page-container",
}
});
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

View File

@@ -1,47 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/detail`,item,item.articleId,true)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full h-[150px] cursor-pointer" />
<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">{{ item.title }}</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,91 +0,0 @@
<template>
<PageBanner :title="`${form?.title}`" :desc="`${form?.comments}`" />
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import type {Article} from "~/api/cms/article/model";
import CardList from './components/CardList.vue';
import type {ArticleCategory} from "~/api/cms/category/model";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
const activeName = ref(undefined)
// 获取状态
const form = ref<ArticleCategory>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const handleClick = (e:any) => {
console.log(e.index)
}
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,limit: 8,categoryId: getIdBySpm(4), keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-navigation/${getIdBySpm(4)}`)
if(nav.value?.data){
form.value = nav.value?.data;
console.log(form);
}
// 页面布局
// if(form.value?.layout){
// layout.value = JSON.parse(form.value?.layout)
// }
useHead({
title: `文章列表 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

View File

@@ -1,104 +0,0 @@
<template>
<div class="text-center flex flex-col items-center py-10">
<h2 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
{{ title }}
</h2>
<div class="sub-title">
<p class="text-gray-500 dark:text-gray-400 py-3">
{{ comments }}
</p>
</div>
</div>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer"
@click="openSpmUrl(`/detail`,item,item.articleId,true)">
<el-image :src="item.image" fit="cover" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer"/>
<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 items-center gap-1.5">
<span class="flex-1 text-xl cursor-pointer max-h-[57px] overflow-hidden">{{ item.title }}</span>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20"/>
<span>{{ item.author }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="p-4 text-center text-gray-500">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Article} from "~/api/cms/article/model";
import type {CompanyParam} from "~/api/system/company/model";
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const disabled = ref(false);
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const load = () => {
if (!props.disabled) {
page.value++;
reload();
}
}
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page', {
baseURL: runtimeConfig.public.apiServer, params: {
page: page.value, limit: 8, keywords: where.keywords
}
})
if (response.value?.data) {
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
} else {
disabled.value = true;
}
if (response.value.data.count == 0) {
resultText.value = '暂无相关结果'
}
}
}
reload();
</script>

View File

@@ -1,96 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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">
<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 grid lg:grid-cols-2 lg:items-center" v-if="layout">
<div class="">
<h1
class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
<span>WEBSOFT</span>
<span v-if="layout.name">{{ layout.name }}</span>
</h1>
<div class="mt-4 text-lg text-gray-500 dark:text-gray-400">
{{ layout.description }}
</div>
<el-space class="mt-4">
<el-button
:icon="ElIconView"
size="large"
v-if="layout.demoUrl"
@click="openSpmUrl(layout.demoUrl)"
>
演示地址
</el-button>
<el-button
v-if="layout.buyUrl"
:icon="ElIconBottom"
size="large"
@click="openSpmUrl(layout.buyUrl)"
>
下载模版
</el-button>
<el-button
:icon="ElIconMemo"
size="large"
v-if="layout.docUrl"
@click="openSpmUrl(layout.docUrl)"
>
帮助文档
</el-button>
</el-space>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo} from "~/composables/configState";
import {openSpmUrl} from "~/utils/common";
const token = useToken();
const sysDomain = useSysDomain();
withDefaults(
defineProps<{
layout?: any;
title?: string;
desc?: string;
buyUrl?: string;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates'
}
);
const config = useConfigInfo();
</script>

View File

@@ -1,87 +0,0 @@
<template>
<div class="text-center flex flex-col items-center pb-10">
<h2 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
{{ title }}
</h2>
<div class="sub-title">
<p class="text-gray-500 dark:text-gray-400 py-3">
{{ comments }}
</p>
</div>
</div>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer">
<el-image
:src="`https://oss.wsdns.cn/20240925/e5e47100f4b6471395b3b81f834d2902.jpg?x-oss-process=image/resize,m_fixed,w_750/quality,Q_90`"
fit="contain" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer"/>
<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">{{ item.title }}</div>
<div class="text-red-500">{{ item.price }}</div>
</div>
<div v-if="item.price && item.price > 0" class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" size="large" :icon="ElIconView" @click="openSpmUrl('/item', item,item.goodsId,true)">
查看详情
</el-button>
<el-button type="primary" size="large" v-if="item.price && item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买
</el-button>
<el-button v-else class="w-full" size="large" :icon="ElIconShoppingCart">下载</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Product} from "~/api/oa/product/model";
const props = withDefaults(
defineProps<{
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Product[]>([]);
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Product>>>('/cms/cms-product/page', {
baseURL: runtimeConfig.public.apiServer, params: {
limit: 8
}
})
if (response.value?.data) {
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
reload();
</script>

View File

@@ -0,0 +1,79 @@
<template>
<div>
<div class="w-full bg-white mb-3 hidden-sm-and-down" v-if="ad">
<el-carousel :height="'750px'">
<el-carousel-item v-for="(item,index) in ad?.imageList" :key="index">
<div class="item relative flex justify-center items-center">
<el-image :src="item.url" />
</div>
</el-carousel-item>
</el-carousel>
</div>
<!-- 移动端 -->
<div class="sm:hidden w-full bg-white mt-[48px] mb-3 hidden-sm-and-up" v-if="ad">
<el-carousel indicator-position="none" height="150">
<el-carousel-item v-for="(item,index) in ad?.imageList" :key="index">
<el-image :src="item.url" />
</el-carousel-item>
</el-carousel>
</div>
</div>
</template>
<script setup lang="ts">
import type {CompanyParam} from "~/api/system/company/model";
import type {CmsAd} from "~/api/cms/cmsAd/model";
import {pageCmsAd} from "~/api/cms/cmsAd";
const props = withDefaults(
defineProps<{
config?: any;
list?: any[];
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const ad = ref<CmsAd>();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
// 请求数据
const reload = async () => {
pageCmsAd({
type: 1,
lang: getLang()
}).then(res => {
console.log(res)
if(res){
ad.value = res.list[0];
}
})
}
watch(
() => props.config,
() => {
reload();
},
{immediate: true}
);
</script>
<style>
.hidden-sm-and-up .el-carousel{
height: 160px;
}
</style>

View File

@@ -19,8 +19,8 @@
</defs>
</svg>
<div class="py-24 sm:py-32 md:py-20 relative dark:bg-gradient-to-b from-gray-950 to-gray-900 md:pb-36">
<svg data-v-835f5c7a="" width="100%" height="869" viewBox="0 0 1440 869" fill="none"
<div class="mt-10 py-24 sm:py-32 md:py-20 relative dark:bg-gradient-to-b from-gray-950 to-gray-900 md:pb-36">
<svg data-v-835f5c7a="" width="100%" height="700" viewBox="0 0 1440 869" fill="none"
xmlns="http://www.w3.org/2000/svg" class="absolute top-0 inset-x-0 w-full hidden lg:block">
<g clip-path="url(#clip0_184_92145)" data-v-835f5c7a="">
<path
@@ -85,23 +85,45 @@
<div class="mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl gap-16 sm:gap-y-24 flex flex-col ">
<div class="text-center relative z-[1]">
<div class="mb-8 cursor-pointer">
<el-tag type="warning" round @click="openSpmUrl(`/detail`, {},730,true)">v3.0 版本发布
<el-tag type="warning" round @click="navTo(`/detail`)">v3.0 版本发布
</el-tag>
</div>
<h1 class="text-5xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-7xl">
<span>构建现代Web应用</span><br/>
<span class="text-primary block lg:inline-block text-green-500">Vue 框架</span>
<!-- <span class="text-primary block lg:inline-block text-green-500">Vue 框架</span>-->
</h1>
<div class="mt-6 text-lg tracking-tight text-gray-600 dark:text-gray-300"> WEBSOFT是一个基于Vue和Nuxt构建的web框架使web开发更直观而强大<br>
<div class="mt-6 text-lg tracking-tight text-gray-400 dark:text-gray-300"> 基于Java SpringBoot构建的SaaS软件平台支持VueReact前端框架<br>
自信地创建高性能和生产级的全栈web应用程序和网站
</div>
<div class="mt-10 flex flex-wrap gap-x-6 gap-y-3 justify-center">
<div class="flex flex-col gap-4">
<div class="flex items-center">
<el-button size="large" type="primary" :icon="ElIconArrowRight" @click="openSpmUrl(`/passport/login`)">立即开始</el-button>
<!-- <el-button size="large" type="primary" v-else :icon="ElIconArrowRight" @click="loginAdminByToken">进入控制台</el-button>-->
<el-button size="large" type="success" :icon="ElIconDownload" @click="openSpmUrl(`http://git.gxwebsoft.com/free`, {},0,true)">源码下载</el-button>
<el-button size="large" type="danger" @click="openSpmUrl(`https://website.websoft.top`, {},123)">商业版演示</el-button>
<div class="flex items-center gap-4">
<!-- <el-button size="large" type="danger" :icon="ElIconUser" @click="openUrl(`https://site.websoft.top`)">立即登录</el-button>-->
<!-- <el-button size="large" type="primary" :icon="ElIconArrowRight" @click="openUrl(`https://site.websoft.top/register`)">免费注册</el-button>-->
<div class="flex flex-col justify-center cursor-pointer text-white items-center bg-gray-400 hover:bg-gray-500 w-[100px] h-[100px] rounded-full">
<img alt="Windows" src="data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E %3Cg fill='%23FFF' fill-rule='evenodd'%3E %3Cpath d='M6.316 21.826v9.025l10.99 1.539V21.826zm12.045 0v10.712l14.463 2.026V21.826zM6.316 11.767v9.004h10.99V10.205zM32.824 8l-14.463 2.055v10.716h14.463z' opacity='1'/%3E %3Cpath fill='none' d='M0 0h40v40H0z'/%3E %3C/g%3E%3C/svg%3E" />
<span class="text-sm">Windows</span>
</div>
<div class="flex flex-col justify-center cursor-pointer text-white items-center bg-gray-400 hover:bg-green-600 w-[100px] h-[100px] rounded-full" @click="openUrl(`https://oss.wsdns.cn/download/%E7%BD%91%E5%AE%BF%E8%BD%AF%E4%BB%B6%28OA%29-3.0.0-arm64.dmg`)">
<img alt="macOs" src="data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='40' height='40' viewBox='0 0 40 40'%3E %3Cdefs%3E %3Cpath id='47f1323c-993c-4d45-aab1-bebb1206c2bb-a' d='M.001.592h26.926v24.039H.001z'/%3E %3C/defs%3E %3Cg fill='none' fill-rule='evenodd'%3E %3Cpath d='M0 0h40v40H0z'/%3E %3Cg opacity='1' transform='translate(6.316 2.737)'%3E %3Cg transform='translate(0 7.045)'%3E %3Cmask id='47f1323c-993c-4d45-aab1-bebb1206c2bb-b' fill='%23fff'%3E %3Cuse xlink:href='%2347f1323c-993c-4d45-aab1-bebb1206c2bb-a'/%3E %3C/mask%3E %3Cpath fill='%23FFF' d='M20.005.608c-2.722-.193-5.031 1.456-6.32 1.456-1.308 0-3.324-1.415-5.461-1.376-2.806.04-5.393 1.564-6.84 3.972C-1.531 9.506.64 16.687 3.479 20.62c1.389 1.922 3.045 4.088 5.221 4.009 2.094-.08 2.887-1.298 5.418-1.298 2.53 0 3.243 1.298 5.458 1.259 2.254-.04 3.683-1.962 5.061-3.894 1.596-2.23 2.251-4.39 2.29-4.505-.05-.018-4.394-1.614-4.437-6.408-.04-4.008 3.415-5.935 3.573-6.028C24.102.989 21.073.684 20.005.608' mask='url(%2347f1323c-993c-4d45-aab1-bebb1206c2bb-b)'/%3E %3C/g%3E %3Cpath fill='%23FFF' d='M18.325 5.057C19.482 3.718 20.26 1.854 20.045 0c-1.663.063-3.676 1.062-4.87 2.4-1.07 1.185-2.008 3.083-1.753 4.899 1.855.137 3.748-.902 4.903-2.242'/%3E %3C/g%3E %3C/g%3E%3C/svg%3E" />
<span class="text-sm">macOs</span>
</div>
<div class="flex flex-col justify-center cursor-pointer text-white items-center bg-gray-400 hover:bg-gray-500 w-[100px] h-[100px] rounded-full">
<img alt="iOS" src="data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='40' height='40' viewBox='0 0 40 40'%3E %3Cdefs%3E %3Cpath id='f46e5a6d-7d66-4f5f-8628-7f4ceb95844a-a' d='M0 0h27v24.104H0z'/%3E %3C/defs%3E %3Cg fill='none' fill-rule='evenodd' transform='translate(7 4)'%3E %3Cg transform='translate(0 7.658)'%3E %3Cmask id='f46e5a6d-7d66-4f5f-8628-7f4ceb95844a-b' fill='%23fff'%3E %3Cuse xlink:href='%23f46e5a6d-7d66-4f5f-8628-7f4ceb95844a-a'/%3E %3C/mask%3E %3Cpath fill='%23FFF' d='M20.06.016c-2.73-.194-5.046 1.46-6.339 1.46-1.311 0-3.332-1.418-5.475-1.38-2.814.04-5.408 1.57-6.86 3.983-2.922 4.86-.745 12.06 2.102 16.004 1.392 1.927 3.053 4.098 5.235 4.02 2.1-.08 2.895-1.302 5.433-1.302 2.536 0 3.252 1.301 5.473 1.262 2.26-.04 3.692-1.967 5.075-3.904 1.6-2.237 2.257-4.403 2.296-4.517-.05-.018-4.406-1.619-4.45-6.426-.039-4.019 3.425-5.951 3.583-6.044C24.167.398 21.129.092 20.059.016z' mask='url(%23f46e5a6d-7d66-4f5f-8628-7f4ceb95844a-b)'/%3E %3C/g%3E %3Cpath fill='%23FFF' d='M18.375 5.07c1.16-1.342 1.94-3.21 1.725-5.07-1.669.063-3.687 1.065-4.884 2.406-1.072 1.188-2.013 3.091-1.757 4.912 1.86.138 3.758-.904 4.916-2.247z'/%3E %3C/g%3E%3C/svg%3E">
<span class="text-sm">iOS</span>
</div>
<div class="flex flex-col justify-center cursor-pointer text-white items-center bg-gray-400 hover:bg-gray-500 w-[100px] h-[100px] rounded-full">
<img alt="Android" src="data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='40' height='40' viewBox='0 0 40 40'%3E %3Cdefs%3E %3Cpath id='a688108f-309f-4e18-a0dc-ed026741fa12-a' d='M0 0h33.684v18.947H0z'/%3E %3C/defs%3E %3Cg fill='none' fill-rule='evenodd'%3E %3Cpath d='M0 0h40v40H0z'/%3E %3Cg opacity='1' transform='translate(3.158 11.158)'%3E %3Cmask id='a688108f-309f-4e18-a0dc-ed026741fa12-b' fill='%23fff'%3E %3Cuse xlink:href='%23a688108f-309f-4e18-a0dc-ed026741fa12-a'/%3E %3C/mask%3E %3Cpath fill='%23FFF' d='M24.594 14.156a1.402 1.402 0 1 1-.003-2.803 1.402 1.402 0 0 1 .003 2.803m-15.504 0a1.402 1.402 0 1 1-.002-2.803 1.402 1.402 0 0 1 .002 2.803M25.097 5.72L27.9.874a.584.584 0 0 0-1.01-.583L24.05 5.2c-2.17-.989-4.608-1.54-7.21-1.54-2.6 0-5.038.552-7.209 1.54L6.794.291a.584.584 0 0 0-1.01.582l2.803 4.848C3.774 8.335.482 13.2 0 18.947h33.684C33.202 13.2 29.91 8.335 25.097 5.721' mask='url(%23a688108f-309f-4e18-a0dc-ed026741fa12-b)'/%3E %3C/g%3E %3C/g%3E%3C/svg%3E">
<span class="text-sm">Android</span>
</div>
<!-- <el-button size="large" :icon="ElIconDownload" @click="openUrl(`https://file.gxwebsoft.com/download/website-3.0.1-arm64.dmg`)">macOs</el-button>-->
</div>
</div>
</div>
@@ -112,7 +134,8 @@
</template>
<script setup lang="ts">
import {useToken} from "~/composables/configState";
import {loginAdminByToken, openSpmUrl} from "~/utils/common";
import {loginAdminByToken, navTo, openUrl} from "~/utils/common";
import {navigateTo} from "#imports";
const token = useToken();
</script>

View File

@@ -1,83 +0,0 @@
<template>
<div class="text-center flex flex-col items-center py-10">
<h2 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
插件市场
</h2>
<div class="sub-title">
<p class="text-gray-500 dark:text-gray-400 py-3">
安装插件几秒钟内即可启动并运行
</p>
</div>
</div>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer">
<el-image
:src="item.image"
fit="contain" :lazy="true" class="w-full md:h-[150px] 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">{{ item.title }}</div>
<div class="text-red-500">{{ item.price }}</div>
</div>
<div v-if="item.price && item.price > 0" class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" size="large" :icon="ElIconView" @click="openSpmUrl(`/item`, item,item.productId,true)">查看详情</el-button>
<el-button type="primary" size="large" v-if="item.price && item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买
</el-button>
<el-button v-else class="w-full" size="large" :icon="ElIconShoppingCart">下载</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Plug} from "~/api/system/plug/model";
import type {Product} from "~/api/oa/product/model";
const props = withDefaults(
defineProps<{
disabled?: boolean;
type?: number;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Product[]>([]);
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Product>>>('/cms/cms-product/page', {
baseURL: runtimeConfig.public.apiServer, params: {
limit: 8,
type: props.type,
}
})
if (response.value?.data) {
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
reload();
</script>

View File

@@ -1,92 +0,0 @@
<template>
<div class="text-center flex flex-col items-center py-12">
<h2 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
{{ title }}
</h2>
<div class="sub-title">
<p class="text-gray-500 dark:text-gray-400 py-3">
{{ comments }}
</p>
</div>
</div>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/item`, item,item.productId,true)">
<el-image
:src="item.image"
fit="cover" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.title }}
<el-tag v-if="item.tag == '1'" size="small" type="success" class="text-white ml-2" effect="dark">免费</el-tag>
<el-tag v-if="item.tag == '2'" size="small" type="success" class="text-white ml-2" effect="dark">开源</el-tag>
<el-tag v-if="item.tag == '3'" size="small" type="danger" class="text-white ml-2" effect="dark">付费</el-tag>
</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500 line-clamp-3">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-between items-center mt-3">
<div v-if="item.price && item.price > 0" class="text-red-500 text-xl">{{ item.price }} </div>
<el-button v-if="item.price && item.price > 0" @click.stop="openSpmUrl(`/product/create`,item,item.productId,true)">立即开通
</el-button>
<el-button v-else>下载</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Product} from "~/api/oa/product/model";
const props = withDefaults(
defineProps<{
disabled?: boolean;
title?: string;
type?: number;
comments?: string;
}>(),
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Product[]>([]);
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Product>>>('/cms/cms-product/page', {
baseURL: runtimeConfig.public.apiServer, params: {
type: props.type,
limit: 8,
status: 0
}
})
if (response.value?.data) {
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
reload();
</script>

226
pages/detail/[id].vue Normal file
View File

@@ -0,0 +1,226 @@
<!-- 文章详情 -->
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="font-600 mr-3"> 文章详情 </span>
</template>
<template #extra>
<div class="flex items-center">
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</div>
</template>
<el-card shadow="hover" class=" my-5">
<!-- 新闻详细 -->
<div class="news_detail bg-white">
<h1 class="pt-5 text-2xl">{{ form.title }}</h1>
<div class="clearfix">
<h3 class=" text-gray-400">
{{ $t('createTime') }}<span>{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span>
{{ $t('author') }}<span>{{ form.nickname }}</span>
{{ $t('click') }}<span>{{ getViews(form) }}</span>
</h3>
<div class="share">
<!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox">
<a href="#" class="bds_more" data-cmd="more"></a>
<a href="#" class="bds_qzone" data-cmd="qzone"></a>
<a href="#" class="bds_tsina" data-cmd="tsina"></a>
<a href="#" class="bds_tqq" data-cmd="tqq"></a>
<a href="#" class="bds_renren" data-cmd="renren"></a>
<a href="#" class="bds_weixin" data-cmd="weixin"></a>
</div>
</div>
</div>
<!-- 内容组件 -->
<Content class="content" :data="form.content" />
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" />
<NextArticle :articleId="articleId" />
</div>
<!-- 最近浏览 -->
<CmsArticleRecently :data="form" type="article" />
<!-- 相关产品和相关新闻 -->
<div class="relate_list">
<CmsProductRelated :data="form" />
<CmsArticleRelated :data="form" />
</div>
</el-card>
</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 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 CmsArticleRecently from "~/components/CmsRecently.vue";
import Tags from "~/components/Tags.vue";
import Content from "~/components/Content.vue";
import CmsArticleList from "~/components/CmsArticleList.vue";
import PageBanner from "~/pages/item/components/PageBanner.vue";
// 引入状态管理
const route = useRoute();
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 {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 goBack = () => {
router.back(); // 返回上一页
// window.history.back();
}
// 请求数据
const reload = async () => {
await getCmsArticle(articleId.value).then(data => {
assignFields(data)
layout.value.banner = data?.banner;
// 二级栏目分类
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;
}
})
// 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">
</style>

View File

@@ -1,64 +0,0 @@
<template>
<el-affix class="lg:p-0 p-3" v-if="form" :offset="offset" @change="onAffix">
<el-button type="text" color="gray">
<div class="text-xl text-gray-600 ">你可能还喜欢</div>
<el-icon class="el-icon--right text-gray-600" color="#999999">
<ArrowRightBold/>
</el-icon>
</el-button>
<template v-for="(item,index) in list" :key="index">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer mt-3"
@click="openSpmUrl(`/detail`,item,item.articleId,true)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer"/>
<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 items-center gap-1.5">
<span class="flex-1 text-xl cursor-pointer max-h-[57px] overflow-hidden">{{ item.title }}</span>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20"/>
<span>{{ item.author }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</div>
</div>
</el-card>
</template>
</el-affix>
</template>
<script setup lang="ts">
import { ArrowRightBold } from '@element-plus/icons-vue'
import {getIdBySpm, openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import type {Article} from "~/api/cms/article/model";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
const props = withDefaults(
defineProps<{
title?: string;
form?: Article;
}>(),
{}
);
const list = ref<Article[]>([]);
const isAffix = ref<any>();
const offset = ref(70)
const onAffix = (e: any) => {
isAffix.value = e;
}
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page', {
params: {
categoryId: getIdBySpm(4),
limit: 3
}
})
if (response.value?.data) {
list.value = response.value.data.list;
}
</script>
<style scoped lang="less">
</style>

View File

@@ -1,94 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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 px-3">
<Breadcrumb :data="form" />
<div class="md:py-8 sm:py-16 md:px-0 px-4 py-8" _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">
<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>
<el-space class="mt-4 text-gray-500 dark:text-gray-400">
<el-avatar size="small" :src="form.logo" />
<span class="cursor-pointer text-gray-500" @click="openSpmUrl(`/ask/${form.userId}`)">{{ form.tenantName }}</span> · {{ dayjs(form.createTime).format('YYYY-MM-DD hh:mm') }}
</el-space>
<!-- <el-space class="mt-4">-->
<!-- <el-button-->
<!-- :icon="ElIconView"-->
<!-- size="large"-->
<!-- v-if="form.demoUrl"-->
<!-- @click="openSpmUrl(form.demoUrl)"-->
<!-- >-->
<!-- 演示地址-->
<!-- </el-button>-->
<!-- <el-button-->
<!-- v-if="form.buyUrl"-->
<!-- :icon="ElIconBottom"-->
<!-- size="large"-->
<!-- @click="openSpmUrl(form.buyUrl)"-->
<!-- >-->
<!-- 下载模版-->
<!-- </el-button>-->
<!-- <el-button-->
<!-- :icon="ElIconMemo"-->
<!-- size="large"-->
<!-- v-if="form.docUrl"-->
<!-- @click="openSpmUrl(form.docUrl)"-->
<!-- >-->
<!-- 帮助文档-->
<!-- </el-button>-->
<!-- </el-space>-->
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {Article} from "~/api/cms/article/model";
import dayjs from "dayjs";
withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: Article;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates'
}
);
</script>

View File

@@ -1,232 +0,0 @@
<!-- 文章详情 -->
<template>
<PageBanner :form="form"/>
<div class="page-main md:w-screen-xl m-auto md:px-3" ref="container">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="12" :lg="18" class="min-w-xs">
<template v-if="form.permission === 0 || showContent">
<div class="p-6 leading-7 bg-white md:rounded-lg text-lg line-height-loose text-gray-600 content">
<p v-html="form?.content"></p>
<template v-for="(item,index) in form.fileList" :key="index" class="text item">
<el-image
:src="item"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="form.fileList"
:initial-index="4"
fit="contain"
/>
</template>
</div>
</template>
<template v-if="form.permission === 1">
<el-result
v-if="!showContent && form.permission === 1"
icon="warning"
title="无访问权限"
>
</el-result>
</template>
<template v-if="form.permission === 2">
<el-result
v-if="!showContent && form.permission === 2"
icon="warning"
title="请输入访问密码"
>
<template #extra>
<el-space>
<el-input type="password" v-model="form.password2" show-password placeholder="请输入查看密码">请输入查看密码</el-input>
<el-button type="primary" @click="checkPassword">确定</el-button>
</el-space>
</template>
</el-result>
</template>
<div class="page md:gap-xl gap-xs md:mt-4 m-auto md:p-0 p-4 flex justify-between">
<div v-if="previousArticle" class="bg-white text-gray-600 hover:shadow hover:text-gray-800 hover:font-bold rounded-lg p-4 cursor-pointer w-[50%]"
@click="openSpmUrl(`/detail`,previousArticle,previousArticle?.articleId)">
<span>上一篇{{ previousArticle?.title }}</span>
</div>
<div v-if="nextArticle" class="bg-white text-gray-600 hover:shadow hover:text-gray-800 hover:font-bold rounded-lg p-4 cursor-pointer w-[50%]"
@click="openSpmUrl(`/detail`,nextArticle,nextArticle?.articleId)">
<span>下一篇{{ nextArticle?.title }}</span>
</div>
</div>
</el-col>
<!-- <el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="6">-->
<el-col :xs="24" :sm="6" :md="6" :lg="6">
<!-- 你可能感兴趣 -->
<LikeArticle v-if="form" :form="form" />
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm, openSpmUrl} from "~/utils/common";
import type {Article} from "~/api/cms/article/model";
import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue';
import LikeArticle from "./components/LikeArticle.vue";
// 引入状态管理
const route = useRoute();
const website = useWebsite();
const breadcrumb = ref<BreadcrumbItem>();
const previousArticle = ref<Article>();
const nextArticle = ref<Article>();
const showContent = ref<boolean>();
const showPassword = ref<boolean>();
// 配置信息
const {form, assignFields} = useFormData<Article>({
// 文章id
articleId: undefined,
// 文章标题
title: undefined,
// 分类类型
type: undefined,
// 展现方式
showType: undefined,
// 文章类型
categoryId: undefined,
// 文章分类
categoryName: undefined,
parentId: undefined,
parentName: undefined,
parentPath: undefined,
// 封面图
image: undefined,
// 附件
files: undefined,
// 附件
fileList: [],
// 缩列图
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,
// 租户名称
tenantName: undefined,
// 租户logo
logo: undefined
});
// 验证密码
const checkPassword = async () => {
const {data: response} = await useServerRequest<ApiResult<unknown>>('/cms/cms-article/checkArticlePassword', {
query: {
password: form?.password,
password2: form.password2
}
})
if(response.value?.code === 0){
showPassword.value = false;
showContent.value = true;
}else {
ElMessage.error(response.value?.message);
}
}
// 请求数据
const reload = async () => {
// 存在spm(优先级高)
const {data: nav} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/' + getIdBySpm(5))
if (nav.value?.data) {
assignFields(nav.value.data)
if(form.permission === 1){
console.log('登录可见')
return;
}
if(form.permission === 2){
console.log('需要密码')
showPassword.value = true;
return;
}
form.parentPath = getSpmUrl(`/category`,form,form.articleId);
console.log(form.parentPath)
}
// 上一篇
const {data: previous} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/getPrevious/' + getIdBySpm(5))
if (previous.value?.data) {
previousArticle.value = previous.value.data
}
// 下一篇
const {data: next} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/getNext/' + getIdBySpm(5))
if (next.value?.data) {
nextArticle.value = next.value.data
}
// seo
useHead({
title: `${form.title} - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
// 面包屑
breadcrumb.value = form
}
watch(
() => route.path,
(path) => {
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="less">
.content {
img {
max-width: 100%;
}
}
</style>

View File

@@ -1,47 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/detail`,item,item.articleId)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full h-[150px] cursor-pointer" />
<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">{{ item.title }}</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,154 +0,0 @@
<template>
<PageBanner :layout="layout" title="入驻成为开发者" @done="onDone" />
<div v-if="showContent" class="md:w-screen-xl md:px-0 px-4 m-auto relative sm:flex bg-white rounded-lg py-4 ">
1231232
<el-tabs class="px-4 bg-white" v-if="form">
<el-tab-pane label="个人开发者认证">
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:py-2">
<el-form-item label="真实姓名">
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="证件号码">
<el-input v-model="form.idCard" placeholder="请输入证件号码" />
</el-form-item>
<el-form-item label="身份证(正面)">
<Upload />
</el-form-item>
<el-form-item label="身份证(反面)">
<Upload />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
<el-result
icon="success"
title="个人认证成功"
sub-title="认证完成时间 2024-09-30"
>
<template #extra>
<el-button type="text">修改认证信息</el-button>
</template>
</el-result>
</el-tab-pane>
<el-tab-pane label="企业开发者认证">
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:py-2">
<el-form-item label="企业名称">
<el-input v-model="form.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="社会信用代码">
<el-input v-model="form.idCard" placeholder="请输入社会信用代码" />
</el-form-item>
<el-form-item label="营业执照">
<Upload />
</el-form-item>
<el-form-item label="所属行业">
<el-select v-model="form.city" placeholder="请选择所属行业">
</el-select>
</el-form-item>
<el-form-item label="网站信息">
<el-input v-model="form.idCard" placeholder="请输入网站信息" />
</el-form-item>
<el-form-item label="您的身份">
<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="用户协议">
<el-checkbox v-model="form.status">请务必提供真实信息我司有权自行或委托第三方审查您提供的身份信息是否属真实有效若提供虚假信息由此的全部后果由您承担</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
<el-result
icon="success"
title="企业认证成功"
sub-title="认证完成时间 2024-09-30"
>
<template #extra>
<el-button type="text">修改认证信息</el-button>
</template>
</el-result>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import type {Article} from "~/api/cms/article/model";
import type {ShopMerchant} from "~/api/shop/shopMerchant/model";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
// 获取状态
const form = ref<Navigation>();
const showContent = ref<boolean>(false);
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const onDone = (index: boolean) => {
showContent.value = index;
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
userId: 0,
keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const {data: response} = await useServerRequest<ApiResult<ShopMerchant>>('/shop/shop-merchant-apply/getByUserId')
if (response.value?.data) {
if (response.value.data.status == 2) {
loginDeveloperCenterByToken();
}
}
useHead({
title: `开发者中心 - ${website.value?.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

View File

@@ -1,149 +0,0 @@
<template>
<PageBanner :layout="layout" />
<div class="login-layout mt-[100px] m-auto sm:w-screen-xl w-full">
<div class="mt-[100px] m-auto flex sm:flex-row flex-col sm:p-0 p-3">
<div class="flash ml-8 bg-white rounded-lg px-7 py-4 w-full">
<el-tabs class="flash bg-white ml-0">
<el-tab-pane label="个人开发者">
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:py-2">
<el-form-item label="真实姓名">
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="证件号码">
<el-input v-model="form.idCard" placeholder="请输入证件号码" />
</el-form-item>
<el-form-item label="身份证(正面)">
<Upload />
</el-form-item>
<el-form-item label="身份证(反面)">
<Upload />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
<el-result
icon="success"
title="个人认证成功"
sub-title="认证完成时间 2024-09-30"
>
<template #extra>
<el-button type="text">修改认证信息</el-button>
</template>
</el-result>
</el-tab-pane>
<el-tab-pane label="企业开发者">
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:py-2">
<el-form-item label="企业名称">
<el-input v-model="form.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="社会信用代码">
<el-input v-model="form.idCard" placeholder="请输入社会信用代码" />
</el-form-item>
<el-form-item label="营业执照">
<Upload />
</el-form-item>
<el-form-item label="所属行业">
<el-select v-model="form.city" placeholder="请选择所属行业">
</el-select>
</el-form-item>
<el-form-item label="网站信息">
<el-input v-model="form.idCard" placeholder="请输入网站信息" />
</el-form-item>
<el-form-item label="您的身份">
<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="用户协议">
<el-checkbox v-model="form.status">请务必提供真实信息我司有权自行或委托第三方审查您提供的身份信息是否属真实有效若提供虚假信息由此的全部后果由您承担</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
<el-result
icon="success"
title="企业认证成功"
sub-title="认证完成时间 2024-09-30"
>
<template #extra>
<el-button type="text">修改认证信息</el-button>
</template>
</el-result>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup lang="ts">
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";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const activeIndex = ref('');
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const reload = async () => {
const {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);
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path,'=>Path')
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,47 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/detail`,item,item.articleId)">
<el-image :src="item.image" fit="fill" :lazy="true" class="w-full h-[150px] cursor-pointer" />
<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">{{ item.title }}</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,103 +0,0 @@
<template>
<PageBanner :layout="layout" title="文档" />
<el-tabs v-model="activeName" class="md:w-screen-xl m-auto relative sm:flex pb-2" @tab-click="handleClick">
<el-tab-pane label="企业官网" name="website"></el-tab-pane>
<el-tab-pane label="企业商城" name="weShop"></el-tab-pane>
<el-tab-pane label="内容管理" name="cms"></el-tab-pane>
<el-tab-pane label="点餐外卖" name="food"></el-tab-pane>
<el-tab-pane label="派单系统" name="task"></el-tab-pane>
<el-tab-pane label="办公OA" name="oa"></el-tab-pane>
<el-tab-pane label="常见问题" name="ask"></el-tab-pane>
</el-tabs>
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import type {Article} from "~/api/cms/article/model";
import CardList from './components/CardList.vue';
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Article[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
const activeName = ref(undefined)
// 获取状态
const form = ref<Navigation>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const handleClick = (e:any) => {
console.log(e.index)
}
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Article>>>('/cms/cms-article/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
userId: 0,
keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: route.path}})
console.log(nav.value?.data)
if(nav.value?.data){
form.value = nav.value?.data;
console.log(form.value,'form...')
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
useHead({
title: `文档 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

View File

@@ -1,52 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer">
<el-image :src="`${item.image}`" fit="contain" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer" />
<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">{{ item.title }}</div>
</div>
<div class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<div class="text-gray-500">{{ item.comments }}</div>
</div>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" size="large" :icon="ElIconView" @click="openSpmUrl(`/item`, item, item.productId,true)">在线体验</el-button>
<el-button type="primary" size="large" class="w-full" :icon="ElIconShoppingCart">下载源码</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
const props = withDefaults(
defineProps<{
list?: CmsProduct[];
disabled?: boolean;
}>(),
{}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,96 +0,0 @@
<template>
<PageBanner :layout="layout" :title="`演示中心`"
:desc="`拥抱开源、坚守品质致力于打造安全稳定高可用的WEB应用`"/>
<!-- <div class="text-3xl py-5">{{ count }}</div>-->
<!-- <el-button @click="increment">点击</el-button>-->
<CardList :list="list" :disabled="disabled" @done="onSearch"/>
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
import { v4 as uuidv4 } from 'uuid';
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<CmsProduct[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
// 获取状态
const form = ref<Navigation>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const onSearch = () => {
if (!disabled.value) {
page.value++;
reload(route.path);
}
}
const count = useToken()
function increment() {
count.value = uuidv4()
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<CmsProduct>>>('/cms/cms-product/page', {
baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
price: 0,
keywords: where.keywords
}
})
if (response.value?.data) {
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
} else {
disabled.value = true;
}
if (response.value.data.count == 0) {
resultText.value = '暂无相关结果'
}
}
}
const {data: nav} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath', {query: {path: route.path}})
if (nav.value?.data) {
form.value = nav.value?.data;
}
// 页面布局
if (form.value?.layout) {
layout.value = JSON.parse(form.value?.layout)
}
useHead({
title: `产品 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
watch(
() => route.path,
(path) => {
reload(path);
},
{immediate: true}
);
</script>

View File

@@ -1,68 +1,442 @@
<template>
<Flash/>
<CompanyList :param="{official: true,recommend: true,limit: 4}" :fit="`cover`" />
<!-- <ProductList :param="{type:0, official: true,limit: 4}" :fit="`cover`" title="产品服务" comments="拥抱开源、坚守品质致力于打造安全稳定高可用的WEB应用"/>-->
<!-- <ArticleList title="开发者社区" comments="分享研发成果 交流技术经验"/>-->
</template>
<script setup lang="ts">
import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {useConfigInfo} from "~/composables/configState";
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 Flash from './components/Flash.vue';
import ArticleList from './components/ArticleList.vue';
// 引入状态管理
const route = useRoute();
const layout = ref<any>();
const token = useToken();
const form = useForm();
const breadcrumb = ref<BreadcrumbItem>();
const i18n = useI18n();
const config = useConfigInfo();
const productList = ref<CmsNavigation[]>([]);
const caseList = ref<CmsArticle[]>([]);
const topNews = ref<CmsArticle[]>([]);
const topNewsImage = ref<string>('');
const hotNews = ref<CmsArticle[]>([]);
const links = ref<CmsLink[]>([]);
const formRef = ref<FormInstance>()
// 验证码 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 () => {
// 页面布局
if (form.value?.layout) {
layout.value = JSON.parse(form.value?.layout)
}
// 读取产品系列
pageCmsNavigation({
limit: 8,
parentId: i18n.locale.value == 'en' ? 1073 : 998,
lang: i18n.locale.value
}).then(data => {
if (data) {
productList.value = data?.list;
}
})
// 未登录状态(是否强制登录)
if (!token.value || token.value == '') {
// if (config.value.MustLogin) {
// navigateTo('/passport/login');
// return false;
// }
}
// 读取案例
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;
caseMaxScroll = caseList.value.length * 303 - 1200;
setCaseScrollTimer()
}
})
// seo
useHead({
title: `构建现代WEB应用 · WEBSOFT`,
meta: [{name: form.value.design?.keywords, content: form.value.design?.description}],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: `console.log(${JSON.stringify(form.value)})`,
},
],
});
// 面包屑
breadcrumb.value = form.value
// 新闻头条
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;
linksMaxScroll = links.value.length * 148 - 1200;
setLinksScroll()
}
})
setTimeout(() => {
setRegionNum()
setCountryNum()
setOrgNum()
setProjectNum()
}, 1500)
}
const regionNum = ref(0);
const countryNum = ref(0);
const orgNum = ref(480);
const projectNum = ref(970);
const setRegionNum = () => {
if (regionNum.value < 20) {
setTimeout(() => {
regionNum.value += 1;
setRegionNum();
}, 70)
}
}
const setCountryNum = () => {
if (countryNum.value < 15) {
setTimeout(() => {
countryNum.value += 1;
setCountryNum();
}, 70)
}
}
const setOrgNum = () => {
if (orgNum.value < 500) {
setTimeout(() => {
orgNum.value += 1;
setOrgNum();
}, 70)
}
}
const setProjectNum = () => {
if (projectNum.value < 999) {
setTimeout(() => {
projectNum.value += 1;
setProjectNum();
}, 70)
}
}
const onScroll = (e: any) => {
currentScrollLeft = e.scrollLeft
if (currentScrollLeft === 0) currentScrollLeftEnd = 0
// console.log(currentScrollLeft)
}
let currentScrollLeft = 0
let currentScrollLeftEnd = 0
let currentScroll = 0
let scrollNum = 0
const scrollbarRef = ref()
const scroll = (dir: string) => {
const maxScrollLeft = 309 * 5 - 1120 - 20
if (dir === 'left') {
if (currentScrollLeft === 0) scrollNum = maxScrollLeft
else if (currentScrollLeft < 309) {
scrollNum = -currentScrollLeft
} else scrollNum = -309
} else {
if (currentScrollLeftEnd === maxScrollLeft) scrollNum = -maxScrollLeft
else if (maxScrollLeft - currentScrollLeftEnd < 309 && maxScrollLeft - currentScrollLeftEnd > 0) {
scrollNum = maxScrollLeft - currentScrollLeftEnd
} else if ((maxScrollLeft + 10) === currentScrollLeft || (maxScrollLeft - 10) === currentScrollLeft) {
scrollNum = -maxScrollLeft
} else scrollNum = 309
}
setScroll()
}
const setScroll = () => {
if (scrollNum > 0) {
if (currentScroll < scrollNum) {
setTimeout(() => {
currentScroll += 5
scrollbarRef.value?.setScrollLeft(currentScrollLeftEnd + currentScroll)
setScroll()
}, 5)
} else {
currentScroll = 0
currentScrollLeftEnd += scrollNum
}
} else {
if (currentScroll > scrollNum) {
setTimeout(() => {
currentScroll -= 5
scrollbarRef.value?.setScrollLeft(currentScrollLeftEnd + currentScroll)
setScroll()
}, 5)
} else {
currentScroll = 0
currentScrollLeftEnd += scrollNum
}
}
// console.log(currentScrollLeftEnd, scrollNum)
}
const setScrollTimer = () => {
setInterval(() => {
scroll('right')
}, 2000)
}
setScrollTimer()
const linksScrollbarRef = ref()
let linksCurrentScroll = 0
const onLinksScroll = (e: any) => {
linksCurrentScroll = e.scrollLeft
}
let linksMaxScroll = 0
const setLinksScroll = () => {
setTimeout(() => {
if (linksCurrentScroll < linksMaxScroll) {
linksCurrentScroll += 1
} else {
linksCurrentScroll = 0
}
// console.log(linksCurrentScroll, linksMaxScroll)
linksScrollbarRef.value?.setScrollLeft(linksCurrentScroll)
setLinksScroll()
}, 15)
}
const caseScrollbarRef = ref()
let caseCurrentScroll = 0
const onCaseScroll = (e: any) => {
caseCurrentScroll = e.scrollLeft
}
let caseMaxScroll = 0
let caseScrollNum = 0
let caseCurrentScrollLeftEnd = 0
const setCaseScroll = () => {
if (caseCurrentScrollLeftEnd === caseMaxScroll) {
caseScrollNum = 0
caseCurrentScrollLeftEnd = 0
} else if (caseMaxScroll - caseCurrentScrollLeftEnd < 303 && caseMaxScroll - caseCurrentScrollLeftEnd > 0) {
caseScrollNum = caseMaxScroll - caseCurrentScrollLeftEnd
} else if ((caseMaxScroll + 20) === caseCurrentScroll || (caseMaxScroll - 20) === caseCurrentScroll) {
scrollNum = 0
} else caseScrollNum = 303
console.log(caseCurrentScrollLeftEnd, caseMaxScroll, caseScrollNum)
setCaseScrollJob()
}
const setCaseScrollJob = () => {
if (caseScrollNum > 0) {
if (caseCurrentScroll < caseScrollNum) {
setTimeout(() => {
caseCurrentScroll += 5
caseScrollbarRef.value?.setScrollLeft(caseCurrentScrollLeftEnd + caseCurrentScroll)
setCaseScrollJob()
}, 5)
} else {
caseCurrentScroll = 0
caseCurrentScrollLeftEnd += caseScrollNum
}
} else {
if (caseCurrentScroll > caseScrollNum) {
setTimeout(() => {
caseCurrentScroll -= 5
caseScrollbarRef.value?.setScrollLeft(caseCurrentScrollLeftEnd + caseCurrentScroll)
setCaseScrollJob()
}, 5)
} else {
caseCurrentScroll = 0
caseCurrentScrollLeftEnd += caseScrollNum
}
}
}
let caseScrollTimer = null
const setCaseScrollTimer = () => {
if (caseScrollTimer) clearInterval(caseScrollTimer)
caseScrollTimer = setInterval(() => {
setCaseScroll()
}, 2000)
}
watch(
() => route.path,
(path) => {
console.log(path, '=>Path')
reload();
},
{immediate: true}
() => 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>

278
pages/item/[id].vue Normal file
View File

@@ -0,0 +1,278 @@
<!-- 文章详情 -->
<template>
<PageBanner :form="page" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="mb-5">
<el-descriptions title="参数信息" :column="2" border>
<el-descriptions-item :span="2" label="产品名称">{{page.title}}</el-descriptions-item>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.title}}</span></el-descriptions-item>
<el-descriptions-item v-if="form.isBuy" label="插件ID"><span class="text-orange-500">{{form.menuId || '-'}}</span></el-descriptions-item>
<el-descriptions-item label="控制台"><a class="cursor-pointer" @click="openUrl(`https://${form.domain}`)">{{form.domain}}</a></el-descriptions-item>
<el-descriptions-item v-for="(item,index) in form.parameters" :key="index" :label="item.name">{{ item.value }}</el-descriptions-item>
</el-descriptions>
<template v-if="form.accounts && form.accounts.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="登录账号" :column="1" border>
<template v-for="(item,index) in form.accounts" :key="index">
<el-descriptions-item :label="item.type" v-if="item.account">
还没有账号? <el-button type="text" @click="openSpmUrl(`/passport/regis`)">立即注册</el-button>
</el-descriptions-item>
</template>
</el-descriptions>
</template>
<template v-if="form.gits && form.gits.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="代码仓库" :column="1" border>
<el-descriptions-item v-for="(item,index) in form.gits" :key="index" :label="item.title">
<el-input v-model="item.domain" readonly />
</el-descriptions-item>
</el-descriptions>
</template>
<template v-if="form.files && form.files.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="图文详情" />
<div v-for="(item,index) in JSON.parse(form.files)" :key="index" class="text item">
<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>
</div>
</template>
<script setup lang="ts">
import {Cpu,Download,Star,Coin,Tickets} from '@element-plus/icons-vue'
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useLayout, usePage, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {getIdBySpm, getNavIdByParamsId, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue';
import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {getCmsArticle, pageCmsArticle} from "~/api/cms/cmsArticle";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
// 引入状态管理
const route = useRoute();
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 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>

View File

@@ -8,7 +8,7 @@
size="large"
status-icon
>
<el-card shadow="hover" v-if="comments" class="hover:border-green-50 hover:border-2 mb-5">
<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>

View File

@@ -1,5 +1,5 @@
<template>
<div class="banner m-auto relative sm:flex">
<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">
@@ -20,23 +20,23 @@
</defs>
</svg>
<div class="md:w-screen-xl m-auto">
<Breadcrumb :data="form"/>
<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.companyLogo">
<el-avatar :src="form.companyLogo" shape="square" :size="180"
class="hidden-sm-and-down rounded-avatar shadow-sm hover:shadow mr-4"/>
<el-avatar :src="form.companyLogo" shape="square" :size="80"
class="hidden-sm-and-up rounded-avatar-xs shadow-sm hover:shadow mr-4"/>
<template v-if="form.image">
<el-image :src="form.image" shape="square"
class="hidden-sm-and-down bg-white w-[128px] h-[128px] cursor-pointer rounded-avatar shadow-sm hover:shadow mr-4"/>
<!-- <el-image :src="form.image" shape="square" :size="80"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</template>
<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.tenantName">{{ form.tenantName }}</span>
<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 }}
@@ -45,43 +45,10 @@
<!-- {{ form.companyName || 'WebSoft Inc.' }}-->
<!-- </a>-->
<el-rate v-model="form.rate" disabled />
<div class="btn" v-if="form.companyId">
<div class="btn">
<el-space class="mt-4">
<template v-if="form.isBuy">
<el-button v-if="form.installed" type="primary" @click.stop="loginDeveloperCenterByToken(form)">控制台</el-button>
<el-button v-else type="primary" @click.stop="loginDeveloperCenterByToken(form)">控制台</el-button>
</template>
<template v-else>
<el-button v-if="form.chargingMethod == 0" type="primary" @click.stop="loginDeveloperCenterByToken(form)">控制台</el-button>
<el-button v-else type="warning" @click.stop="openSpmUrl(`/product/create`,form,form.companyId,true)">立即开通
</el-button>
</template>
<!-- <el-button @click.stop="openSpmUrl(`https://${form.domain}`,form,form.companyId,true)">产品控制台</el-button>-->
<el-button @click="openSpmUrl(`/ask`,form,form.companyId,true)">帮助文档</el-button>
<!-- <template v-for="(item,index) in form.links" :key="index">-->
<!-- <div v-if="item.qrcode">-->
<!-- <el-popover-->
<!-- placement="top-start"-->
<!-- :width="200"-->
<!-- trigger="hover"-->
<!-- >-->
<!-- <template #default>-->
<!-- <div class=" p-2 flex justify-center">-->
<!-- <el-image :src="item.qrcode" :size="160"/>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template #reference>-->
<!-- <el-button :icon="ElIconFullScreen">{{ item.type }}</el-button>-->
<!-- </template>-->
<!-- </el-popover>-->
<!-- </div>-->
<!-- <el-button-->
<!-- v-else-->
<!-- @click="openSpmUrl(`${item.domain}`,item,item.tenantId,true)"-->
<!-- >-->
<!-- {{ item.type }}-->
<!-- </el-button>-->
<!-- </template>-->
<el-button>产品控制台</el-button>
<el-button>帮助文档</el-button>
</el-space>
</div>
</div>
@@ -98,7 +65,7 @@ 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 {loginAdminByToken, loginDeveloperCenterByToken, openSpmUrl} from "~/utils/common";
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
const token = useToken();
@@ -107,7 +74,7 @@ const props = withDefaults(
title?: string;
desc?: string;
buyUrl?: string;
form?: Company;
form?: CmsArticle;
value?: number;
}>(),
{}

View File

@@ -1,293 +0,0 @@
<!-- 文章详情 -->
<template>
<PageBanner :form="form" @done="reload"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="18" :xs="24">
<el-card shadow="hover" class="hover:border-green-50 hover:border-2 mb-5">
<template #header>
<div class="card-header font-bold text-xl">
<span>应用介绍</span>
</div>
</template>
<el-descriptions title="应用参数" :column="2" border>
<el-descriptions-item :span="2" label="应用名称">{{form.tenantName}}</el-descriptions-item>
<el-descriptions-item v-if="form.isBuy" label="租户ID"><span class="text-orange-500">{{form.tenantId}}</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.content">
<div class="h-[24px]"></div>
<el-descriptions title="详细说明" />
<p v-html="form.content" class="content"></p>
</template>
<template v-if="form.files && form.files.length > 0">
<div class="h-[24px]"></div>
<el-descriptions title="应用截图" />
<div v-for="(item,index) in form.files" :key="index" class="text item">
<el-image
:src="item.url"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="srcList"
:initial-index="4"
fit="contain"
/>
</div>
</template>
</el-card>
<!-- <el-card shadow="hover" v-if="form.files?.length" class="hover:border-green-50 hover:border-2 mb-5">-->
<!-- <template #header>-->
<!-- <div class="card-header font-bold text-xl">-->
<!-- <span>应用截图</span>-->
<!-- </div>-->
<!-- </template>-->
<!-- <div class="flex gap-xl">-->
<!-- <template v-for="(item,index) in form.files" :key="index" class="text item">-->
<!-- <el-image-->
<!-- :src="item.url"-->
<!-- :zoom-rate="1.2"-->
<!-- :max-scale="7"-->
<!-- :min-scale="0.2"-->
<!-- :preview-src-list="srcList"-->
<!-- :initial-index="4"-->
<!-- fit="contain"-->
<!-- />-->
<!-- </template>-->
<!-- </div>-->
<!-- </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="hover:border-green-50 hover:border-2 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.companyLogo"/>
</div>
<div class="flex flex-col">
<span class="font-bold text-lg text-gray-600">{{ form.companyName }}</span>
<span class="text-gray-400 pb-1 line-clamp-2">{{ form.comments }}</span>
</div>
</el-space>
<div class="flex flex-col text-gray-500 justify-between leading-7 mt-3">
<el-space class="flex items-center"><el-icon><Download /></el-icon>下载6</el-space>
<el-space class="flex items-center"><el-icon><Star /></el-icon>收藏0</el-space>
<el-space class="flex items-center"><el-icon><Coin /></el-icon>赞赏0</el-space>
<el-space class="flex items-center"><el-icon><Tickets /></el-icon>文档0</el-space>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import {Cpu,Download,Star,Coin,Tickets} from '@element-plus/icons-vue'
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {getIdBySpm, openUrl} from "~/utils/common";
import useFormData from "~/utils/use-form-data";
import PageBanner from './components/PageBanner.vue';
import Comments from './components/Comments.vue';
import type {Company} from "~/api/system/company/model";
import type {CompanyComment} from "~/api/system/companyComment/model";
// 引入状态管理
const route = useRoute();
const website = useWebsite();
const breadcrumb = ref<BreadcrumbItem>();
const comments = ref<CompanyComment[]>([]);
const commentsTotal = ref(0);
const commentsPage = ref(1);
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]);
// 配置信息
const {form, assignFields} = useFormData<Company>({
companyId: undefined,
type: undefined,
shortName: undefined,
companyName: undefined,
companyType: undefined,
companyTypeMultiple: undefined,
appType: undefined,
companyLogo: undefined,
image: undefined,
files: undefined,
content: undefined,
companyCode: undefined,
domain: undefined,
phone: undefined,
tel: undefined,
email: undefined,
InvoiceHeader: undefined,
startTime: undefined,
expirationTime: undefined,
version: undefined,
versionName: undefined,
versionCode: undefined,
members: undefined,
storage: undefined,
storageMax: undefined,
buys: undefined,
clicks: undefined,
users: undefined,
departments: undefined,
industryParent: undefined,
industryChild: undefined,
country: undefined,
province: undefined,
city: undefined,
region: undefined,
address: undefined,
latitude: undefined,
longitude: undefined,
businessEntity: undefined,
comments: undefined,
authentication: undefined,
industryId: undefined,
industryName: undefined,
status: undefined,
userId: undefined,
official: undefined,
price: undefined,
planId: undefined,
sortNumber: undefined,
authoritative: undefined,
menuId: undefined,
merchantId: undefined,
tenantId: undefined,
tenantName: undefined,
tenantCode: undefined,
modules: undefined,
requestUrl: undefined,
socketUrl: undefined,
serverUrl: undefined,
modulesUrl: undefined,
merchantUrl: undefined,
websiteUrl: undefined,
mpWeixinCode: undefined,
mpAlipayCode: undefined,
h5Code: undefined,
androidUrl: undefined,
iosUrl: undefined,
avatar: undefined,
nickname: undefined,
code: undefined,
createTime: undefined,
updateTime: undefined,
password: undefined,
password2: undefined,
collection: undefined,
recommend: undefined,
title: undefined,
parentName: undefined,
categoryName: undefined,
parameters: undefined,
links: undefined,
accounts: undefined,
gits: undefined,
isBuy: undefined,
installed: 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 reload = async () => {
// 存在spm(优先级高)
const {data: item} = await useServerRequest<ApiResult<Company>>('/system/company/' + getIdBySpm(5))
if (item.value?.data) {
assignFields(item.value.data)
form.title = item.value?.data?.title;
form.parentName = '产品';
form.categoryName = '产品详情';
if (item.value.data.files) {
form.files = JSON.parse(item.value?.data?.files)
srcList.value = form.files?.map(d => d.url)
}
form.comments = item.value?.data?.comments;
}
await reloadComments();
// seo
useHead({
title: `${form.tenantName} - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
// 面包屑
breadcrumb.value = form
}
watch(
() => route.path,
(path) => {
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="less">
.content {
img {
max-width: 100%;
height: auto !important;
}
}
</style>

120
pages/links/[id].vue Normal file
View File

@@ -0,0 +1,120 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto">
<el-row :gutter="24" id="container" class="clearfix">
<el-col :span="5" class="left">
<!-- 内页左侧组件 -->
<Left :category="category" />
</el-col>
<el-col :span="19" class="right">
<div class="sitemp h-[32px] flex justify-between">
<h2>{{ page.title }}</h2>
<Breadcrumb :data="page" :categoryName="$t('articleTitle')" />
</div>
<div class="content">
<!-- 文章模型 -->
<ul class="news_listn clearfix">
<CmsArticleList :data="list" />
</ul>
<Pagination :total="total" @done="search" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import Banner from "@/components/Banner.vue";
import {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import {getNavIdByParamsId, paramsId} from "~/utils/common";
import Left from "~/components/Left.vue";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import CmsArticleList from "~/components/CmsArticleList.vue";
const route = useRoute();
const navId = ref();
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref<number>(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
// 加载页面数据
const reload = async () => {
getCmsNavigation(navId.value).then(data => {
// 获取栏目信息
page.value = data
layout.value.banner = data.banner;
// 设置页面标题
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(categoryData => {
category.value = categoryData;
console.log(categoryData)
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = data.navigationId;
}else {
where.categoryId = data.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}).catch(err => {
console.log(err,'加载失败...')
})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>

28
pages/m.vue Normal file
View File

@@ -0,0 +1,28 @@
<script setup lang="ts">
import 'assets/css/base.css';
import 'assets/css/main.css';
import 'assets/css/model.css';
import 'assets/m/reset.css';
import 'assets/m/ui.css';
import {getSiteInfo} from "~/api/layout";
import {useWebsite} from "~/composables/configState";
definePageMeta({
layout: 'mobile'
})
</script>
<template>
<meta content="width=device-width,minimum-scale=1.0,maximum-scale=1.0" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta content="telephone=no" name="format-detection" />
<div class="mobile-page">
<NuxtPage />
</div>
</template>
<style scoped lang="scss">
.mobile-page{
}
</style>

131
pages/m/article/[id].vue Normal file
View File

@@ -0,0 +1,131 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ page.title }}
</h2>
</div>
<div class="content">
<!-- 文章模型 -->
<ul class="news_listn clearfix pt-2">
<template v-for="(item,index) in list" key="index">
<li class="clearfix">
<a :href="mDetail(item)" class=" fl mr-1">
<el-image :src="item.image" :fit="`scale-down`" class="w-[120px] h-[80px]" :alt="item.title" />
</a>
<div class="list">
<h3 class="text-lg"><a :href="mDetail(item)" :title="item.title" class="line-clamp-1 text-left">{{ item.title }}</a></h3>
<div v-html="item.comments" class="line-clamp-2"></div>
<!-- <div class="date">发布日期{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</div>-->
<!-- <div class="n-more" ><a :href="detail(item)" >查看更多>></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 type { ComponentSize } from 'element-plus'
import {mDetail, paramsId} from "~/utils/common";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
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,
() => {
reload();
},
{immediate: true}
);
</script>

129
pages/m/case/[id].vue Normal file
View File

@@ -0,0 +1,129 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ page.title }}
</h2>
</div>
<div class="content py-3">
<el-row :gutter="16">
<el-col :span="24" v-for="(item,index) in list" key="index">
<div class="cast-item">
<a :href="mDetail(item)" :title="item.title" class="img"><el-image :fit="`scale-down`" :src="item.image" :alt="item.title" /></a>
<div class="flex flex-col leading-7">
<a :href="mDetail(item)" class="text-[16px] font-bold" :title="item.title">{{ item.title }}</a>
<a :href="mDetail(item)" class="more"><span class="text-gray-400">{{ $t('case.detail') }}>></span></a>
</div>
</div>
</el-col>
</el-row>
<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 type { ComponentSize } from 'element-plus'
import {paramsId} from "~/utils/common";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import CmsArticleList from "~/components/CmsArticleList.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 () => {
await getCmsNavigation(paramsId()).then(data => {
page.value = data;
// layout.value = data.design;
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;
}
})
})
})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.path,
() => {
reload();
},
{immediate: true}
);
</script>
<style scoped lang="less">
.case_listn li{
max-width: 200px;
}
.cast-item{
border: 1px solid #eeeeee;
padding: 10px;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<!-- 移动端 -->
<div class="sm:hidden w-full bg-white mb-3 " v-if="ad">
<el-carousel indicator-position="none">
<el-carousel-item v-for="(item,index) in ad?.imageList" :key="index">
<el-image :src="item.url" />
</el-carousel-item>
</el-carousel>
</div>
</template>
<script setup lang="ts">
import type {CompanyParam} from "~/api/system/company/model";
import type {CmsAd} from "~/api/cms/cmsAd/model";
import {pageCmsAd} from "~/api/cms/cmsAd";
const props = withDefaults(
defineProps<{
config?: any;
list?: any[];
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const ad = ref<CmsAd>();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
// 请求数据
const reload = async () => {
pageCmsAd({
type: 1,
lang: getLang()
}).then(res => {
if(res){
ad.value = res.list[0];
}
})
}
watch(
() => props.config,
() => {
reload();
},
{immediate: true}
);
</script>
<style>
.el-carousel__container{
height: 240px !important;
}
</style>

202
pages/m/detail/[id].vue Normal file
View File

@@ -0,0 +1,202 @@
<!-- 文章详情 -->
<template>
<Banner :layout="layout"/>
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ form.categoryName }}
</h2>
</div>
<div class="content">
<!-- 新闻详细 -->
<div class="news_detail">
<h1 class="title">{{ form.title }}</h1>
<div class="info_title clearfix">
<h3 class="text-center text-gray-400">
{{ $t('createTime') }}<span>{{ dayjs(form.createTime).format('YYYY-MM-DD') }}</span>
{{ $t('author') }}<span>{{ form.nickname }}</span>
{{ $t('click') }}<span>{{ form.actualViews }}</span>
</h3>
<div class="share">
<!-- Baidu Button BEGIN -->
<div class="bdsharebuttonbox">
<a href="#" class="bds_more" data-cmd="more"></a>
<a href="#" class="bds_qzone" data-cmd="qzone"></a>
<a href="#" class="bds_tsina" data-cmd="tsina"></a>
<a href="#" class="bds_tqq" data-cmd="tqq"></a>
<a href="#" class="bds_renren" data-cmd="renren"></a>
<a href="#" class="bds_weixin" data-cmd="weixin"></a>
</div>
</div>
</div>
<div class="content text-lg" v-html="form.content"></div>
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" />
<NextArticle />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type {BreadcrumbItem} from "~/types/global";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {locationUrl, paramsId} from "~/utils/common";
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
import useFormData from "~/utils/use-form-data";
import dayjs from "dayjs";
import Banner from "@/components/Banner.vue";
import type {Layout} from "~/api/layout/model";
import {getCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import Tags from "~/components/Tags.vue";
// 引入状态管理
const route = useRoute();
const layout = ref<Layout>({});
const breadcrumb = ref<BreadcrumbItem>();
const showPassword = ref<boolean>();
const category = ref<CmsNavigation[]>([]);
// 配置信息
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,
// 文章内容
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 reload = async () => {
await getCmsArticle(paramsId()).then(data => {
assignFields(data)
layout.value.banner = data?.banner;
// 二级栏目分类
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;
}
})
// seo
useSeoMeta({
description: form?.comments,
keywords: form.title,
titleTemplate: `${form?.title}` + ' - %s',
})
// 面包屑
breadcrumb.value = form
}
watch(
() => route.path,
(path) => {
reload();
},
{immediate: true}
);
</script>
<style lang="scss">
.content {
padding-top: 15px;
overflow: hidden;
}
.content p{
}
.content img{
padding: 10px 0;
max-width: 100%;
height: auto;
}
.content video {
width: 100%;
height: auto;
}
</style>

183
pages/m/index.vue Normal file
View File

@@ -0,0 +1,183 @@
<template>
<!-- 首页banner -->
<Carousel ref="CarouselRef"/>
<div class="search">
<el-input v-model="keyword" :placeholder="$t('index.keyword')" :suffix-icon="Search" @change="onSearch"/>
</div>
<!-- 产品分类 -->
<div class="cp p-3" v-if="cpCategory">
<el-row :gutter="16">
<el-col :span="6" v-for="(item,index) in cpCategory" :key="index">
<div class="item flex flex-col justify-center mb-2" v-if="index < 8">
<a :href="navTo(item)" class="img" style="border-radius: 100%;">
<el-avatar fit="fill" :size="65" :src="`${item.icon}`" style="border: 1px solid #FFFFFF; box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);"/>
</a>
<div class="text-center py-1"><a :href="navTo(item)">{{ item.title }}</a></div>
</div>
</el-col>
</el-row>
</div>
<div class="clearboth"></div>
<div class="advs"><p><img :src="config?.MobileIndexInfoTopImage"
title="1729219778290291.jpg" alt="1.jpg"/></p>
<p><br/></p>
<p><br/></p></div>
<div class="clearboth"></div>
<div class="products">
<div class="products_title">
{{ $t('index.companyProfile') }}
</div>
<div class="contentss clearfix">
<div class="tps"><p><a :href="i18n.locale.value == 'en' ? `/en/m/page/1100.html` : `/m/page/1025.html`" target="_self"><img
:src="config.MobileIndexInfoImage" title="1729219913337024.jpg"
alt="2.jpg"/></a></p>
<p><br/></p></div>
<div class="nrs">
{{ config.MobileIndexInfo }}
</div>
</div>
</div>
<div class="products" v-if="caseCategory">
<div class="products_title">
{{ $t('index.parentNameCase') }}
</div>
<ul>
<li v-for="(item,index) in caseCategory" :key="index">
<a :href="navTo(item)" title="轨道交通建设项目" class="img">
<img :src="item.icon" alt="轨道交通建设项目"/>
<h3>{{ item.title }}</h3>
</a>
</li>
</ul>
</div>
<div class="tu"><p><img :src="config.MobileIndexInfoHZGYImage" title="活动房安装"
alt="活动房安装" border="0" vspace="0" style="white-space: normal;"/></p>
<p><br/></p></div>
<div class="hezuo">
<div class="products_title">
<a><span class="text-[#0c6fcd]" >{{ $t('index.partner') }}</span></a>
</div>
<div class="con"><p><a><img
:src="config.MobileIndexInfoLogoList" title="1587462469987062.jpg"
alt="手机合作.jpg"/></a></p></div>
</div>
<!-- 服务中国 走向海外 -->
<div v-html="config.MobileIndexCode" class="mservice"></div>
<div class="advs"><a :href="`${i18n.locale.value == 'en' ? '/en/m/video/1075.html' : '/m/video/1000.html'}`"><img :src="config.MobileIndexBottomBanner"
title="1553249693490374.jpg" alt="555.jpg" border="0" vspace="0"/></a></div>
<div class="news">
<div class="products_title">
{{ $t('index.news') }}
</div>
<ul>
<li v-for="(item,index) in hotNews" :key="index">
<a :href="mDetail(item)" :title="item.title">
{{ item.title }}
</a>
<div class="line-clamp-1 max-w-[320px]" v-html="item.comments"></div>
</li>
</ul>
</div>
<div class="clearboth"></div>
<div class="products">
<div class="products_title">
{{ $t('index.contact') }}
</div>
<div class="flex flex-col p-3 leading-8">
<span class="text-left">{{ $t('page.tel') }}{{ config.tel }}</span>
<span class="text-left">{{ $t('page.phone') }}{{ config.fax }}</span>
<span class="text-left">{{ $t('page.fax') }}{{ config.phone }}</span>
<span class="text-left">{{ $t('page.email') }}{{ config.email }}</span>
<span class="text-left">{{ $t('page.address') }}{{ config.address }}</span>
<span class="text-left">{{ $t('page.factory') }}{{ config.address2 }}</span>
<span class="text-left">{{ $t('page.domain') }}{{ config.pageLeftInfoUrl }}</span>
</div>
</div>
<div class="distraction">
</div>
</template>
<script setup lang="ts">
import Carousel from "~/pages/m/components/Carousel.vue";
import {useConfigInfo, useWebsite} from "~/composables/configState";
import {Search} from '@element-plus/icons-vue'
import type {CmsArticle} from "~/api/cms/cmsArticle/model";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {navTo} from "~/utils/common";
import {getSiteInfo} from "~/api/layout";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
const props = withDefaults(
defineProps<{
name?: any;
}>(),
{}
);
const config = useConfigInfo();
const website = useWebsite();
const keyword = ref();
const i18n = useI18n();
const hotNews = ref<CmsArticle[]>([]);
const topNavs = ref<any>([]);
const cpCategory = ref<CmsNavigation[]>([]);
const caseCategory = ref<any>([]);
const onSearch = () => {
window.location.href = `/m/search/${keyword.value}`;
}
// TODO 读取服务器缓存数据
await getSiteInfo({
lang: i18n.locale.value
}).then(data => {
website.value = data
topNavs.value = data.topNavs?.filter(d => d.title === '产品系列' || d.title === 'Product')
if(topNavs.value[0].children){
cpCategory.value = topNavs.value[0].children;
}
caseCategory.value = data.topNavs?.filter(d => d.title === '项目展示' || d.title === 'Case')[0].children
})
const reload = async () => {
// 新闻头条
pageCmsArticle({
limit: 4,
status: 0,
recommend: 1,
parentId: i18n.locale.value == 'en' ? 1077 : 1002,
lang: i18n.locale.value
}).then(res => {
if(res){
hotNews.value = res?.list;
}
})
}
reload();
</script>
<style scoped lang="less">
</style>

220
pages/m/item/[id].vue Normal file
View File

@@ -0,0 +1,220 @@
<script setup lang="ts">
import type {BreadcrumbItem} from "~/types/global";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {locationUrl, navTo, 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 {getCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import Tags from "~/components/Tags.vue";
// 引入状态管理
const route = useRoute();
const layout = ref<Layout>({});
const breadcrumb = ref<BreadcrumbItem>();
const showPassword = ref<boolean>();
const category = ref<CmsNavigation[]>([]);
const fileList = 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,
// 缩列图
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.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;
}
})
// seo
useSeoMeta({
description: form.comments || form.title,
keywords: form.title,
titleTemplate: `${form?.title}` + ' - %s',
})
// 面包屑
breadcrumb.value = form
}
watch(
() => route.path,
() => {
reload();
},
{immediate: true}
);
</script>
<template>
<Banner :layout="layout"/>
<!-- 主体部分 -->
<div class="clearfix p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ form.categoryName || '分类名称' }}
</h2>
</div>
<div class="detail-container">
<!-- 产品详细 -->
<div class="product_detail" id="pd1">
<div class="allcontent clearfix">
<div style="float: left;">
<div class="img clearfix" id="play">
<el-carousel v-if="form.files" :interval="4000">
<el-carousel-item v-for="item in JSON.parse(form.files)" :key="item" style="display: flex; align-items: center; justify-content: center">
<el-image fit="cover" :src="item" />
</el-carousel-item>
</el-carousel>
</div>
</div>
<div class="list mb-10">
<ul class="list_p">
<h1 class="title">{{ form.title }}</h1>
<li><h2>{{ $t('categoryName') }}<a :href="navTo(form,`/product/${form.categoryId}`)"><strong>{{ form.categoryName }}</strong></a></h2></li>
<li class="text-left">{{ $t('createTime') }}<span>{{ form.createTime }}</span></li>
<li class="text-left">{{ $t('overview') }}</li>
<div class="bg-gray-100 p-2 w-[360px]">{{ form.comments }}</div>
<li class="inquiry"><a :href="navTo(form,`/m/order/${form.articleId}.html}`)">{{ $t('onlineInquiry') }}</a></li>
</ul>
</div>
</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>
<div class="content tab-content text-sm" v-html="form.content"></div>
</div>
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" />
<NextArticle />
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.content {
padding-top: 15px;
overflow: hidden;
}
.content p{
text-indent: 0;
}
.content img{
padding: 5px 0;
max-width: 100%;
height: auto;
}
.content video {
width: 100%;
height: auto;
}
.product_detail{
padding: 5px 0 !important;
}
.product_detail .list{
margin-left: 0 !important;
}
.product_detail .img{
width: 90vw !important;
}
</style>

232
pages/m/order/[id].vue Normal file
View File

@@ -0,0 +1,232 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="clearfix p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>{{ $t('order.title') }}</h2>
<Breadcrumb :data="page" :categoryName="page.categoryName" />
</div>
<div class="form-box p-5">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120"
label-position="left"
status-icon
>
<el-form-item :label="$t('order.title')" prop="title" class="hover:bg-gray-50 p-2">
<el-input v-model="form.title" :placeholder="$t('order.title')"/>
</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="$t('order.content')"/>
</el-form-item>
<el-form-item :label="$t('order.realName')" prop="realName" class="hover:bg-gray-50 p-2">
<el-input v-model="form.realName" :placeholder="$t('order.realName')"/>
</el-form-item>
<el-form-item :label="$t('order.phone')" prop="phone" class="hover:bg-gray-50 p-2">
<el-input v-model="form.phone" :maxlength="11" :placeholder="$t('order.phone')"/>
</el-form-item>
<el-form-item :label="$t('order.email')" prop="email" class="hover:bg-gray-50 p-2">
<el-input v-model="form.email" :placeholder="$t('order.email')"/>
</el-form-item>
<el-form-item :label="$t('order.address')" prop="address" class="hover:bg-gray-50 p-2">
<el-input v-model="form.address" :placeholder="$t('order.address')"/>
</el-form-item>
<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 type="primary" size="large" @click="submitForm(formRef)">
{{ $t('order.submit') }}
</el-button>
</div>
</el-form-item>
</el-form>
</div>
</div>
<el-dialog v-model="dialogVisible">
<div class="flex justify-center">
<el-image w-full :src="dialogImageUrl" alt="查看证件" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {useLayout, usePage} from "~/composables/configState";
import {paramsId} from "~/utils/common";
import {ArrowRight} from '@element-plus/icons-vue'
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 {getCmsArticle} from "~/api/cms/cmsArticle";
import {getCaptcha} from "~/api/passport/login";
// 引入状态管理
const route = useRoute();
const layout = useLayout();
const page = usePage();
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,
// 真实姓名
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 () => {
getCmsArticle(paramsId()).then(data => {
form.articleId = data.articleId;
layout.value.banner = data.banner;
form.content = ''
// seo
useSeoMeta({
description: form.comments || form.title,
keywords: form.title,
titleTemplate: `${form?.title}` + ' - %s',
})
changeCaptcha();
})
}
// 提交表单
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)
}
})
}
})
}
onMounted(() => {
// 在这里放置你想要在组件渲染完成后执行的代码
console.log('组件已挂载');
});
watch(
() => route.path,
() => {
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.content {
padding-top: 15px;
overflow: hidden;
text-indent: 2em;
}
.content p{
line-height: 2em;
}
.content img{
padding: 10px;
max-width: 100%;
}
</style>

78
pages/m/page/[id].vue Normal file
View File

@@ -0,0 +1,78 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="clearfix p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>{{ page.title }}</h2>
</div>
<div class="content text-lg" v-html="page.design?.content"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {paramsId} from "~/utils/common";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
// 引入状态管理
const route = useRoute();
const layout = useLayout();
const page = usePage();
const category = ref<CmsNavigation[]>([]);
// 加载页面布局
const reload = async () => {
getCmsNavigation(paramsId()).then(data => {
page.value = data
layout.value.banner = data.banner;
// 二级栏目分类
if(data.parentId && data.parentId > 0){
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(list => {
category.value = list
})
}
// seo
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
})
}
watch(
() => route.path,
() => {
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.content {
padding-top: 15px;
padding-bottom: 30px;
overflow: hidden;
}
.content p{
}
.content img{
padding: 5px 0;
max-width: 100%;
height: auto;
}
.content video {
width: 100%;
height: auto;
}
</style>

122
pages/m/product/[id].vue Normal file
View File

@@ -0,0 +1,122 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="clearfix p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ page.title }}
</h2>
</div>
<div class="content">
<!-- 产品列表 -->
<el-row :gutter="20">
<template v-for="(item,index) in list" key="index">
<el-col :span="24" class="my-3">
<div class="item p-2 border-1 border-gray-200 border-solid hover:border-blue-500">
<a :href="mDetail(item)" :title="item.title" class="img"><el-image :fit="`scale-down`" :src="item.image" :alt="item.title" /></a>
<h3>
<a :href="mDetail(item)" :title="item.title" class="text-lg font-bold">{{ item.title }}</a>
<span class="line-clamp-1 text-gray-400 text-sm font-normal">{{ item.comments }}</span>
</h3>
</div>
</el-col>
</template>
</el-row>
<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 {mDetail, paramsId} from "~/utils/common";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
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 () => {
console.log(paramsId())
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,
() => {
reload();
},
{immediate: true}
);
</script>

147
pages/m/search/[id].vue Normal file
View File

@@ -0,0 +1,147 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ $t('searchKeywords') }}{{ where.keywords }}
</h2>
</div>
<el-alert v-if="where.keywords" :title="`${$t('search.results')}${$t('search.find')} ${total} ${$t('search.nums')}`" type="warning" :closable="false" />
<div class="content">
<ul class="news_listn clearfix">
<template v-for="(item,index) in list" key="index">
<li class="clearfix">
<div class="">
<h3 class="text-lg"><a :href="mDetail(item)" :title="item.title" v-html="replaceKeywords(item.title)"></a></h3>
<div v-html="replaceKeywords(item.comments)" class="line-clamp-2 text-gray-400"></div>
<div class="date text-gray-400">{{ $t('createTime') }}{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</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 {useConfigInfo, 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 {getPath} from "~/utils/common";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsModel} from "~/api/cms/cmsModel";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
// 获取状态
const page = usePage();
const layout = useLayout();
const config = useConfigInfo();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 10,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const replaceKeywords = (text: any) => {
return text.replace(`${where.keywords}`,'<font color=#ff0000>' + where.keywords + '</font>');
}
// 加载页面数据
const reload = async () => {
listCmsModel({
model: getPath(2)
}).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.keywords = `${route.params.id}`;
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}
}).catch(err => {
console.log(err,'加载失败...')
})
}
useHead({
title: `搜索结果 - ${config.value.siteName || runtimeConfig.public.siteName}`,
bodyAttrs: {
class: "page-container",
}
});
/**
* 搜索
* @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>

185
pages/m/show/[id].vue Normal file
View File

@@ -0,0 +1,185 @@
<!-- 文章详情 -->
<template>
<Banner :layout="layout"/>
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ form.categoryName || '分类名称' }}
</h2>
</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-2">{{ form.title }}</div>
<el-carousel v-if="form.files" :interval="4000">
<el-carousel-item v-for="item in form.files" :key="item" class="" 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>
<div class="content tab-content text-sm" v-html="form.content"></div>
</div>
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" />
<NextArticle />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {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 {getCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import Tags from "~/components/Tags.vue";
// 引入状态管理
const route = useRoute();
const layout = ref<Layout>({});
const category = ref<CmsNavigation[]>([]);
// 配置信息
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,
// 缩列图
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
}).then(list => {
category.value = list
})
// 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">
.content {
padding-top: 15px;
overflow: hidden;
}
.content p{
}
.content img{
padding: 10px;
max-width: 100%;
height: auto;
}
.content video {
width: 100%;
height: auto;
}
</style>

135
pages/m/tags/[id].vue Normal file
View File

@@ -0,0 +1,135 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ $t('label') }}{{ where.tags }}
</h2>
</div>
<el-alert v-if="where.keywords" :title="`${$t('search.results')}${$t('search.find')} ${total} ${$t('search.nums')}`" type="warning" :closable="false" />
<div class="content">
<ul class="news_listn clearfix">
<template v-for="(item,index) in list" key="index">
<li class="clearfix">
<div class="">
<h3 class="text-lg"><a :href="mDetail(item)" :title="item.title" v-html="replaceKeywords(item.title)"></a></h3>
<div v-html="replaceKeywords(item.comments)" class="line-clamp-2 text-gray-400"></div>
<div class="date text-gray-400">{{ $t('createTime') }}{{ dayjs(item.createTime).format('YYYY-MM-DD') }}</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 {getPath} from "~/utils/common";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsModel} from "~/api/cms/cmsModel";
const route = useRoute();
// 页面信息
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
tags: '',
page: 1,
limit: 10,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const replaceKeywords = (text: any) => {
return text.replace(`${where.keywords}`,'<font color=#ff0000>' + where.keywords + '</font>');
}
// 加载页面数据
const reload = async () => {
listCmsModel({
model: getPath(2)
}).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}`;
pageCmsArticle(where).then(response => {
if(response){
total.value = response?.count;
list.value = response?.list;
}
})
})
}
}).catch(err => {
console.log(err,'加载失败...')
})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
watch(
() => route.path,
() => {
reload();
},
{immediate: true}
);
</script>
<style scoped lang="less">
.sitemp h2{
width: 500px !important;
}
</style>

110
pages/m/video/[id].vue Normal file
View File

@@ -0,0 +1,110 @@
<template>
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ page.title }}
</h2>
</div>
<div class="content">
<!-- 产品列表 -->
<MCmsProductList :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 {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import MCmsProductList from "~/components/MCmsProductList.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,
() => {
reload();
},
{immediate: true}
);
</script>

View File

@@ -0,0 +1,183 @@
<!-- 文章详情 -->
<template>
<Banner :layout="layout"/>
<!-- 主体部分 -->
<div class="p-5">
<div class="m-page">
<div class="sitemp h-[32px] flex justify-between">
<h2>
{{ form.categoryName || '分类名称' }}
</h2>
</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>
<!-- 内容组件 -->
<MContent class="text-sm" :data="form.content" />
</div>
<h3 class="tag">{{ $t('articleUrl') }}{{ locationUrl() }} </h3>
<Tags :data="form.tags" />
<NextArticle />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import {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 {getCmsArticle} from "~/api/cms/cmsArticle";
import {listCmsNavigation} from "~/api/cms/cmsNavigation";
import Tags from "~/components/Tags.vue";
import MContent from "~/components/MContent.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>

View File

@@ -1,358 +0,0 @@
<template>
<div class="common-layout">
<el-container>
<!-- 顶部菜单 -->
<el-header class="bg-black flex items-center justify-between" height="50px">
<el-space class="flex items-center">
<el-avatar src="https://oss.wsdns.cn/20240331/7f7f7f57e12c45338beb7bfc7ecccfe9.png" class="cursor-pointer" shape="square" :size="32" @click="navigateTo('/manage', { replace: false })" />
<div class="expand flex items-center justify-center block hover:bg-gray-700 h-[32px] w-[32px] cursor-pointer" @click="visible = !visible"><el-icon color="white" class="block" size="20"><Grid /></el-icon></div>
</el-space>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-header>
<!-- 主体 -->
<el-container>
<!-- 左侧菜单区域 -->
<el-aside width="200px" class="bg-white" :style="{ minHeight: 'calc(100vh - 50px)' }">
<div class="flex items-center justify-between py-4 px-5 text-lg font-bold text-center bg-white border-b-1 border-b-gray-100 border-b-solid">
企业官网 WDS
<el-icon><Memo /></el-icon>
</div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border: none"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="1">
<span>概况</span>
</el-menu-item>
<el-menu-item index="2">
<span>栏目管理</span>
</el-menu-item>
<el-menu-item index="3">
<span>文章列表</span>
</el-menu-item>
<el-menu-item index="4">
<span>素材管理</span>
</el-menu-item>
<el-menu-item index="5">
<span>广告管理</span>
</el-menu-item>
<el-menu-item index="6">
<span>订单管理</span>
</el-menu-item>
<el-menu-item index="7">
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="8">
<span>系统设置</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 左侧内容区域 -->
<el-container>
<el-main class="overflow-y-hidden relative">
<div class="absolute inset-0 bg-black opacity-10 z-1" v-if="visible" @click="visible = false"></div>
<div v-if="visible" class="drawer w-screen-lg h-full bg-white left-0 top-[50px] fixed z-2 shadow-lg">
<div class="flex items-center justify-between p-3 border-b border-b-solid border-b-gray-200">
<div class="text-lg font-bold">产品与服务</div>
<el-space class="flex items-center">
<el-input
class="w-20"
placeholder="站内搜索"
:suffix-icon="ElIconSearch"
v-model="form.comments"
@keyup.enter.native="handleSearch"
/>
<!-- <el-input v-model="search" placeholder="搜索应用" class="w-[200px]" />-->
<div class="cursor-pointer mt-1 ml-5" @click="visible = false"><el-icon size="24" color="gray"><Close /></el-icon></div>
</el-space>
</div>
<div class="menu-content flex">
<div class="w-[200px] bg-gray-50">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border-right: 1px solid #f9fafb"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="2">
<span>精选</span>
</el-menu-item>
<el-menu-item index="3">
<span>网页</span>
</el-menu-item>
<el-menu-item index="4">
<span>移动</span>
</el-menu-item>
<el-menu-item index="5">
<span>办公</span>
</el-menu-item>
<el-menu-item index="6">
<span>其他</span>
</el-menu-item>
</el-menu>
</div>
<div class="w-full p-3">
<el-scrollbar class="w-full" height="calc(100vh - 108px)">
<el-row :gutter="16">
<template v-for="item in 20" :key="item">
<el-col :span="8">
<div class="px-3 py-1 mb-2 text-sm text-gray-700 font-bold flex justify-between items-center">API 与工具</div>
<p class="scrollbar-demo-item hover:bg-gray-100 px-3 py-1 mb-2 cursor-pointer text-sm text-gray-500 flex justify-between items-center">
<span class="product-name">{{ item }} 云服务器 ECS</span>
<span class="icon flex items-center hover:flex">
<el-icon color="gray"><Star /></el-icon>
</span>
</p>
</el-col>
</template>
</el-row>
</el-scrollbar>
</div>
</div>
</div>
<div class="my-3">
<el-alert type="warning">
<template #title>
<text type="text" class="cursor-pointer">关于小程序技术平台服务协议更新公告</text>
</template>
</el-alert>
</div>
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow rounded-lg">
<template #header>我的应用</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 bg-white mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<el-avatar :src="item?.logo" shape="square" />
<div class="info ml-2">
<div class="app-item-title font-bold flex items-center">{{ item.tenantName }}<el-tag type="warning" class="ml-2" size="small">网页</el-tag></div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
<!-- <el-card shadow="hover" class="mt-4">-->
<!-- <el-tabs v-model="activeIndex" type="">-->
<!-- <el-tab-pane label="微信小程序" name="mp-weixin">-->
<!-- <el-table :data="tableData" stripe style="width: 100%">-->
<!-- <el-table-column prop="date" label="Date" width="180" />-->
<!-- <el-table-column prop="name" label="Name" width="180" />-->
<!-- <el-table-column prop="address" label="Address" />-->
<!-- </el-table>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="网页/移动应用" name="web">-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </el-card>-->
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow ">
<template #header>开发工具推荐</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<div class="info ml-2">
<div class="app-item-title">{{ item.tenantName }}</div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
</el-main>
<el-footer class="fixed bottom-0 items-center flex justify-center text-gray-400" :style="{ width: 'calc(100vw - 200px)' }">
<span>Copyright © {{ new Date().getFullYear() }} {{ config?.copyright }}</span>
</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
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, PageResult} from "~/api";
import type {Company} from "~/api/system/company/model";
import type {Tenant} from "~/api/system/tenant/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const visible = ref(false);
const showIcon = ref(false);
const activeIndex = ref('mp-weixin');
const list = ref<Tenant[]>();
const avatar = ref(localStorage.getItem('avatar'))
import {
Document,
Menu as IconMenu,
Location,
Expand,
MoreFilled,
Grid,
Memo,
Close,
Star,
Setting,
} from '@element-plus/icons-vue'
import {navigateTo} from "#imports";
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
function handleCommand(command: string) {
switch (command) {
case 'logOut':
logOut();
break;
default:
navigateTo('/user');
break;
}
}
function logOut() {
token.value = ''
navigateTo('/passport/login')
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const handleSearch = () => {
console.log('搜索')
}
const getUserInfo = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Tenant>>>('/system/tenant/page',{baseURL: runtimeConfig.public.apiServer, params: {
userId: localStorage.getItem('UserId')
}})
if(response.value?.data){
console.log(response.value,'tenantList')
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
watch(
() => route.path,
() => {
reload();
getUserInfo();
},
{ immediate: true }
);
</script>
<style lang="less">
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
</style>

View File

@@ -1,373 +0,0 @@
<template>
<div class="common-layout">
<el-container>
<!-- 顶部菜单 -->
<el-header class="bg-black flex items-center justify-between" height="50px">
<el-space class="flex items-center">
<el-avatar src="https://oss.wsdns.cn/20240331/7f7f7f57e12c45338beb7bfc7ecccfe9.png" class="cursor-pointer" shape="square" :size="32" @click="navigateTo('/manage', { replace: false })" />
<div class="expand flex items-center justify-center block hover:bg-gray-700 h-[32px] w-[32px] cursor-pointer" @click="visible = !visible"><el-icon color="white" class="block" size="20"><Grid /></el-icon></div>
</el-space>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-header>
<!-- 主体 -->
<el-container>
<!-- 左侧菜单区域 -->
<el-aside width="200px" class="bg-white" :style="{ minHeight: 'calc(100vh - 50px)' }">
<div class="flex items-center justify-between py-4 px-5 text-lg font-bold text-center bg-white border-b-1 border-b-gray-100 border-b-solid">
企业官网 WDS
<el-icon><Memo /></el-icon>
</div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border: none"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item v-for="(item,index) in menus" :key="item.menuId" :index="`${item.menuId}`">
<span>{{ item.title }}</span>
</el-menu-item>
<!-- <el-menu-item index="1">-->
<!-- <span>概况</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="2">-->
<!-- <span>栏目管理</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="3">-->
<!-- <span>文章列表</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="4">-->
<!-- <span>素材管理</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="5">-->
<!-- <span>广告管理</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="6">-->
<!-- <span>订单管理</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="7">-->
<!-- <span>用户管理</span>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index="8">-->
<!-- <span>系统设置</span>-->
<!-- </el-menu-item>-->
</el-menu>
</el-aside>
<!-- 左侧内容区域 -->
<el-container>
<el-main class="overflow-y-hidden relative">
<div class="absolute inset-0 bg-black opacity-10 z-1" v-if="visible" @click="visible = false"></div>
<div v-if="visible" class="drawer w-screen-lg h-full bg-white left-0 top-[50px] fixed z-2 shadow-lg">
<div class="flex items-center justify-between p-3 border-b border-b-solid border-b-gray-200">
<div class="text-lg font-bold">产品与服务</div>
<el-space class="flex items-center">
<el-input
class="w-20"
placeholder="站内搜索"
:suffix-icon="ElIconSearch"
v-model="form.comments"
@keyup.enter.native="handleSearch"
/>
<!-- <el-input v-model="search" placeholder="搜索应用" class="w-[200px]" />-->
<div class="cursor-pointer mt-1 ml-5" @click="visible = false"><el-icon size="24" color="gray"><Close /></el-icon></div>
</el-space>
</div>
<div class="menu-content flex">
<div class="w-[200px] bg-gray-50">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border-right: 1px solid #f9fafb"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="2">
<span>精选</span>
</el-menu-item>
<el-menu-item index="3">
<span>网页</span>
</el-menu-item>
<el-menu-item index="4">
<span>移动</span>
</el-menu-item>
<el-menu-item index="5">
<span>办公</span>
</el-menu-item>
<el-menu-item index="6">
<span>其他</span>
</el-menu-item>
</el-menu>
</div>
<div class="w-full p-3">
<el-scrollbar class="w-full" height="calc(100vh - 108px)">
<el-row :gutter="16">
<template v-for="item in 20" :key="item">
<el-col :span="8">
<div class="px-3 py-1 mb-2 text-sm text-gray-700 font-bold flex justify-between items-center">API 与工具</div>
<p class="scrollbar-demo-item hover:bg-gray-100 px-3 py-1 mb-2 cursor-pointer text-sm text-gray-500 flex justify-between items-center">
<span class="product-name">{{ item }} 云服务器 ECS</span>
<span class="icon flex items-center hover:flex">
<el-icon color="gray"><Star /></el-icon>
</span>
</p>
</el-col>
</template>
</el-row>
</el-scrollbar>
</div>
</div>
</div>
<div class="my-3">
<el-alert type="warning">
<template #title>
<text type="text" class="cursor-pointer" @click="openSpmUrl(`/detail`,{},729,true)">关于小程序技术平台服务协议更新公告</text>
</template>
</el-alert>
</div>
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow rounded-lg">
<template #header>我的应用</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 bg-white mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<el-avatar :src="item?.logo" shape="square" />
<div class="info ml-2">
<div class="app-item-title font-bold flex items-center">{{ item.tenantName }}<el-tag type="warning" class="ml-2" size="small">网页</el-tag></div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
<!-- <el-card shadow="hover" class="mt-4">-->
<!-- <el-tabs v-model="activeIndex" type="">-->
<!-- <el-tab-pane label="微信小程序" name="mp-weixin">-->
<!-- <el-table :data="tableData" stripe style="width: 100%">-->
<!-- <el-table-column prop="date" label="Date" width="180" />-->
<!-- <el-table-column prop="name" label="Name" width="180" />-->
<!-- <el-table-column prop="address" label="Address" />-->
<!-- </el-table>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="网页/移动应用" name="web">-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </el-card>-->
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow ">
<template #header>开发工具推荐</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<div class="info ml-2">
<div class="app-item-title">{{ item.tenantName }}</div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
</el-main>
<el-footer class="fixed bottom-0 items-center flex justify-center text-gray-400" :style="{ width: 'calc(100vw - 200px)' }">
<span>Copyright © {{ new Date().getFullYear() }} {{ config?.copyright }}</span>
</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
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, PageResult} from "~/api";
import type {Company} from "~/api/system/company/model";
import type {Tenant} from "~/api/system/tenant/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const visible = ref(false);
const showIcon = ref(false);
const activeIndex = ref('mp-weixin');
const list = ref<Tenant[]>();
const menus = ref<Menu[]>();
const avatar = ref(localStorage.getItem('avatar'))
import {
Document,
Menu as IconMenu,
Location,
Expand,
MoreFilled,
Grid,
Memo,
Close,
Star,
Setting,
} from '@element-plus/icons-vue'
import {navigateTo} from "#imports";
import type {Menu} from "~/api/system/menu/model";
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
function handleCommand(command: string) {
switch (command) {
case 'logOut':
logOut();
break;
default:
navigateTo('/user');
break;
}
}
function logOut() {
token.value = ''
navigateTo('/passport/login')
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const handleSearch = () => {
console.log('搜索')
}
const getUserInfo = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Tenant>>>('/system/tenant/page',{baseURL: runtimeConfig.public.apiServer, params: {
userId: localStorage.getItem('UserId')
}})
if(response.value?.data){
console.log(response.value,'tenantList')
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
const {data: response2} = await useServerRequest<ApiResult<PageResult<Menu>>>('/system/menu',{baseURL: runtimeConfig.public.apiServer, params: {
parentId: 64688,
menuType: 0,
hide: 0
}})
if(response2.value?.data){
if (response2.value?.data) {
menus.value = response2.value.data
}
}
}
watch(
() => route.path,
() => {
reload();
getUserInfo();
},
{ immediate: true }
);
</script>
<style lang="less">
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
</style>

View File

@@ -1,358 +0,0 @@
<template>
<div class="common-layout">
<el-container>
<!-- 顶部菜单 -->
<el-header class="bg-black flex items-center justify-between" height="50px">
<el-space class="flex items-center">
<el-avatar src="https://oss.wsdns.cn/20240331/7f7f7f57e12c45338beb7bfc7ecccfe9.png" class="cursor-pointer" shape="square" :size="32" @click="navigateTo('/manage', { replace: false })" />
<div class="expand flex items-center justify-center block hover:bg-gray-700 h-[32px] w-[32px] cursor-pointer" @click="visible = !visible"><el-icon color="white" class="block" size="20"><Grid /></el-icon></div>
</el-space>
<template v-if="token">
<el-dropdown @command="handleCommand">
<el-avatar class="cursor-pointer" :src="userInfo?.avatar" :size="30" />
<el-button circle :icon="ElIconUserFilled" color="#155FAA"></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-header>
<!-- 主体 -->
<el-container>
<!-- 左侧菜单区域 -->
<el-aside width="200px" class="bg-white" :style="{ minHeight: 'calc(100vh - 50px)' }">
<div class="flex items-center justify-between py-4 px-5 text-lg font-bold text-center bg-white border-b-1 border-b-gray-100 border-b-solid">
企业商城 EMall
<el-icon><Memo /></el-icon>
</div>
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border: none"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="1">
<span>概况</span>
</el-menu-item>
<el-menu-item index="2">
<span>栏目管理</span>
</el-menu-item>
<el-menu-item index="3">
<span>文章列表</span>
</el-menu-item>
<el-menu-item index="4">
<span>素材管理</span>
</el-menu-item>
<el-menu-item index="5">
<span>广告管理</span>
</el-menu-item>
<el-menu-item index="6">
<span>订单管理</span>
</el-menu-item>
<el-menu-item index="7">
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="8">
<span>系统设置</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 左侧内容区域 -->
<el-container>
<el-main class="overflow-y-hidden relative">
<div class="absolute inset-0 bg-black opacity-10 z-1" v-if="visible" @click="visible = false"></div>
<div v-if="visible" class="drawer w-screen-lg h-full bg-white left-0 top-[50px] fixed z-2 shadow-lg">
<div class="flex items-center justify-between p-3 border-b border-b-solid border-b-gray-200">
<div class="text-lg font-bold">产品与服务</div>
<el-space class="flex items-center">
<el-input
class="w-20"
placeholder="站内搜索"
:suffix-icon="ElIconSearch"
v-model="form.comments"
@keyup.enter.native="handleSearch"
/>
<!-- <el-input v-model="search" placeholder="搜索应用" class="w-[200px]" />-->
<div class="cursor-pointer mt-1 ml-5" @click="visible = false"><el-icon size="24" color="gray"><Close /></el-icon></div>
</el-space>
</div>
<div class="menu-content flex">
<div class="w-[200px] bg-gray-50">
<el-menu
default-active="2"
class="el-menu-vertical-demo"
style="border-right: 1px solid #f9fafb"
@open="handleOpen"
@close="handleClose"
>
<el-menu-item index="2">
<span>精选</span>
</el-menu-item>
<el-menu-item index="3">
<span>网页</span>
</el-menu-item>
<el-menu-item index="4">
<span>移动</span>
</el-menu-item>
<el-menu-item index="5">
<span>办公</span>
</el-menu-item>
<el-menu-item index="6">
<span>其他</span>
</el-menu-item>
</el-menu>
</div>
<div class="w-full p-3">
<el-scrollbar class="w-full" height="calc(100vh - 108px)">
<el-row :gutter="16">
<template v-for="item in 20" :key="item">
<el-col :span="8">
<div class="px-3 py-1 mb-2 text-sm text-gray-700 font-bold flex justify-between items-center">API 与工具</div>
<p class="scrollbar-demo-item hover:bg-gray-100 px-3 py-1 mb-2 cursor-pointer text-sm text-gray-500 flex justify-between items-center">
<span class="product-name">{{ item }} 云服务器 ECS</span>
<span class="icon flex items-center hover:flex">
<el-icon color="gray"><Star /></el-icon>
</span>
</p>
</el-col>
</template>
</el-row>
</el-scrollbar>
</div>
</div>
</div>
<div class="my-3">
<el-alert type="warning">
<template #title>
<text type="text" class="cursor-pointer">关于小程序技术平台服务协议更新公告</text>
</template>
</el-alert>
</div>
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow rounded-lg">
<template #header>我的应用</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 bg-white mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<el-avatar :src="item?.logo" shape="square" />
<div class="info ml-2">
<div class="app-item-title font-bold flex items-center">{{ item.tenantName }}<el-tag type="warning" class="ml-2" size="small">网页</el-tag></div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
<!-- <el-card shadow="hover" class="mt-4">-->
<!-- <el-tabs v-model="activeIndex" type="">-->
<!-- <el-tab-pane label="微信小程序" name="mp-weixin">-->
<!-- <el-table :data="tableData" stripe style="width: 100%">-->
<!-- <el-table-column prop="date" label="Date" width="180" />-->
<!-- <el-table-column prop="name" label="Name" width="180" />-->
<!-- <el-table-column prop="address" label="Address" />-->
<!-- </el-table>-->
<!-- </el-tab-pane>-->
<!-- <el-tab-pane label="网页/移动应用" name="web">-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </el-card>-->
<el-card shadow="hover" class="flash mt-4 bg-white hover:shadow ">
<template #header>开发工具推荐</template>
<el-row :gutter="16">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" @click="openSpmUrl(`https://${item.tenantId}.websoft.top/token-login`,item,item.tenantId)">
<div class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-1.5 cursor-pointer">
<div class="info ml-2">
<div class="app-item-title">{{ item.tenantName }}</div>
<div class="app-item-desc text-gray-400">{{ item.comments }}</div>
</div>
</div>
</el-col>
</template>
</el-row>
</el-card>
</el-main>
<el-footer class="fixed bottom-0 items-center flex justify-center text-gray-400" :style="{ width: 'calc(100vw - 200px)' }">
<span>Copyright © {{ new Date().getFullYear() }} {{ config?.copyright }}</span>
</el-footer>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
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, PageResult} from "~/api";
import type {Company} from "~/api/system/company/model";
import type {Tenant} from "~/api/system/tenant/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const visible = ref(false);
const showIcon = ref(false);
const activeIndex = ref('mp-weixin');
const list = ref<Tenant[]>();
const avatar = ref(localStorage.getItem('avatar'))
import {
Document,
Menu as IconMenu,
Location,
Expand,
MoreFilled,
Grid,
Memo,
Close,
Star,
Setting,
} from '@element-plus/icons-vue'
import {navigateTo} from "#imports";
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
function handleCommand(command: string) {
switch (command) {
case 'logOut':
logOut();
break;
default:
navigateTo('/user');
break;
}
}
function logOut() {
token.value = ''
navigateTo('/passport/login')
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const handleSearch = () => {
console.log('搜索')
}
const getUserInfo = async () => {
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user',{baseURL: runtimeConfig.public.apiServer})
if(response.value?.data){
userInfo.value = response.value?.data;
}
}
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Tenant>>>('/system/tenant/page',{baseURL: runtimeConfig.public.apiServer, params: {
userId: localStorage.getItem('UserId')
}})
if(response.value?.data){
console.log(response.value,'tenantList')
if (response.value?.data.list) {
list.value = response.value?.data.list;
}
}
}
watch(
() => route.path,
() => {
reload();
getUserInfo();
},
{ immediate: true }
);
</script>
<style lang="less">
.el-row {
margin-bottom: 20px;
}
.el-row:last-child {
margin-bottom: 0;
}
.el-col {
border-radius: 4px;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
</style>

View File

@@ -1,86 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in data?.list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer" @click="openSpmUrl(`/item`, item,item.companyId,true)">
<el-image
:src="item.image"
:fit="fit" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.shortName }}
<el-tag v-if="item.chargingMethod === 0" size="small" type="success" class="text-white ml-2" effect="dark">免费</el-tag>
</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">
<el-space class="flex items-end">
<el-avatar size="small" :src="item.companyLogo" />
<span class="text-gray-400 line-clamp-1 pr-2">{{ item.companyName }}</span>
</el-space>
<!-- <el-button @click.stop="loginDeveloperCenterByToken(item)">控制台</el-button>-->
<!-- 已购买 -->
<!-- <template v-if="item.isBuy">-->
<!-- <el-button v-if="item.isBuy && item.installed" type="success" @click.stop="openSpmUrl(`https://${item.domain}`,item,item.companyId,true)">进入控制台</el-button>-->
<!-- <el-button v-if="item.isBuy && !item.installed" type="primary" @click.stop="openSpmUrl(`/product/create`,item,item.companyId,true)">立即开通</el-button>-->
<!-- </template>-->
<!-- 未够买 -->
<!-- <template v-if="!item.isBuy">-->
<!-- <div class="flex items-center cursor-pointer" v-if="!item.isBuy" @click.stop="openSpmUrl(`/product/create`,item,item.companyId,true)">-->
<!-- <div class="flex items-center text-red-600">-->
<!-- <span>{{ item.price }}</span>-->
<!-- <span v-if="item.chargingMethod == 2">/</span>-->
<!-- <span v-if="item.chargingMethod == 3">/</span>-->
<!-- <span v-if="item.chargingMethod == 4">/</span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div class="flex justify-center py-3">
<el-pagination background layout="prev, pager, next" :total="data?.count" @change="onPage" />
</div>
<!-- <div v-if="disabled" class="px-1 text-center text-gray-500">-->
<!-- 没有更多了-->
<!-- </div>-->
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import type {Company} from "~/api/system/company/model";
import type {PageResult} from "~/api";
const props = withDefaults(
defineProps<{
data?: PageResult<Company>;
disabled?: boolean;
fit?: any;
}>(),
{
fit: 'cover'
}
);
const emit = defineEmits<{
(e: 'done', index: number): void;
}>();
// const load = () => {
// if(!props.disabled){
// emit('done')
// }
// }
const onPage = (index: number) => {
emit('done',index)
}
</script>

View File

@@ -1,75 +1,198 @@
<template>
<PageBanner :layout="layout" :title="`${form?.categoryName}`" :desc="`${form?.comments}`" />
<CardList :param="{type: 0,official: true}" :data="data" :disabled="disabled" @done="onSearch" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> 应用市场 </span>
</template>
<template #extra>
<el-space class="flex items-center">
<!-- <el-select v-model="value" clearable placeholder="筛选" style="width: 240px">-->
<!-- <el-option-->
<!-- v-for="item in options"-->
<!-- :key="item.value"-->
<!-- :label="item.label"-->
<!-- :value="item.value"-->
<!-- />-->
<!-- </el-select>-->
<el-button-group>
<el-button>最新</el-button>
<el-button>免费</el-button>
<el-button>付费</el-button>
<el-button>综合</el-button>
</el-button-group>
<el-input v-model="where.keywords" :placeholder="`${$t('searchKeywords')}...`" :suffix-icon="Search" @change="reload"/>
</el-space>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="8" class="left mb-8">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer">
<div class="flex-1 px-4 py-5 sm:p-4 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex gap-1.5">
<el-avatar
:src="item.websiteLogo" shape="square" :size="55" style="background-color: white;"/>
<div class="flex-1 text-lg cursor-pointer flex flex-col">
{{ item.websiteName }}
<div class="flex justify-between items-center">
<sapn class="text-xs text-gray-400 font-normal line-clamp-1">{{ item.comments || '暂无描述' }}</sapn>
<el-button size="small" round>获取</el-button>
</div>
</div>
</div>
<div class="item-image pt-3">
<el-image :src="`https://oss.wsdns.cn/20250212/a00f01813e474e1fb2f0732182bc82d3.jpg?x-oss-process=image/resize,m_fixed,w_1680/quality,Q_90`" class="w-full h-1/2" />
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-page-header>
<Pagination :total="total" @done="search" />
</div>
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {Company, CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
import {getIdBySpm} from "~/utils/common";
import { ArrowLeft,View,Search } from '@element-plus/icons-vue'
import { ElNotification as notify } from 'element-plus'
import { useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
import type {CmsArticle, CmsArticleParam} from "~/api/cms/cmsArticle/model";
import type { ComponentSize } from 'element-plus'
import { ElNotification } from 'element-plus'
import type { TabsPaneContext } from 'element-plus'
import dayjs from "dayjs";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
import {pageCmsArticle} from "~/api/cms/cmsArticle";
import {pageCmsWebsiteAll} from "~/api/cms/cmsWebsite";
import type {CmsWebsite, CmsWebsiteParam} from "~/api/cms/cmsWebsite/model";
const route = useRoute();
const router = useRouter();
const navId = ref();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const data = ref<PageResult<Company>>();
const page = ref<number>(0);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
const list = ref<CmsWebsite[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
const activeName = ref('2839');
// 获取状态
const form = ref<Navigation>();
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
const where = reactive<CmsWebsiteParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
categoryId: undefined,
lang: i18n.locale.value
});
const onSearch = (index: number) => {
page.value = index;
const goBack = () => {
router.back();
}
// 加载页面数据
const reload = async () => {
await pageCmsWebsiteAll(where).then(response => {
if(response?.list){
list.value = response?.list;
total.value = response.count;
}
}).catch(() => {})
}
const bakReload = async () => {
await getCmsNavigation(navId.value).then(data => {
page.value = data;
layout.value.banner = data.banner;
// seo
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(navigation => {
category.value = navigation;
// 加载文章列表
if(data.parentId == 0 && category.value.length > 0){
where.parentId = page.value.navigationId;
}else {
where.categoryId = page.value.navigationId;
}
pageCmsArticle(where).then(response => {
if(response){
total.value = response.count;
list.value = response.list;
}
})
})
}).catch(() => {})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Company>>>('/system/company/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 12,
categoryId: getIdBySpm(5),
keywords: where.keywords
}})
if(response.value?.data){
data.value = response.value?.data;
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-navigation/${getIdBySpm(5)}`)
if(nav.value?.data){
form.value = nav.value?.data;
useHead({
title: `${form.value.title} - WEBSOFT`,
bodyAttrs: {
class: "page-container",
}
});
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
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(
() => getIdBySpm(5),
() => route.params.id,
(id) => {
console.log(id,'id = .>>>>')
// navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.right .content img{
width: auto !important;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm} from "~/utils/common";
// 引入状态管理
const route = useRoute();
const user = useUser();
const layout = ref<any>();
const config = useConfigInfo();
const token = useToken();
const form = useForm();
const breadcrumb = ref<BreadcrumbItem>();
// 请求数据
const reload = async () => {
// 存在spm(优先级高)
const {data: nav} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/' + getIdBySpm(5))
if (nav.value?.data) {
form.value = nav.value.data
} else {
const {data: nav} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath', {query: {path: route.path}})
if (nav.value?.data) {
form.value = nav.value?.data;
}
}
// 页面布局
if (form.value?.layout) {
layout.value = JSON.parse(form.value?.layout)
}
// 未登录状态(是否强制登录)
if (!token.value || token.value == '') {
if (config.value.MustLogin) {
navigateTo('/passport/login');
return false;
}
}
// 判断是否开发者身份
if(!user.value?.merchantId){
navigateTo('/user/auth');
return false;
}
// 开发者身份者跳转控制台
if(user.value.merchantId){
console.log('已认证未开发者>')
}
// seo
useHead({
title: `现代Web应用开发(Vue)框架 · WEBSOFT`,
meta: [{name: form.value.design?.keywords, content: form.value.design?.description}],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: `console.log(${JSON.stringify(form.value)})`,
},
],
});
// 面包屑
breadcrumb.value = form.value
}
watch(
() => route.path,
(path) => {
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>

228
pages/order/[id].vue Normal file
View File

@@ -0,0 +1,228 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto bg-white">
<el-row :gutter="50" id="container" class="clearfix">
<el-col :span="5" class="left">
<!-- 内页左侧组件 -->
<Left :category="category" />
</el-col>
<el-col :span="19" class="right">
<div class="sitemp h-[32px] flex justify-between pt-5">
<h2>{{ page.title }}</h2>
<Breadcrumb :data="page" />
</div>
<div class="form-box p-5">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120"
label-position="left"
status-icon
>
<el-form-item :label="$t('order.title')" prop="title" class="hover:bg-gray-50 p-2">
<el-input v-model="form.title" :placeholder="$t('order.title')"/>
</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="$t('order.content')"/>
</el-form-item>
<el-form-item :label="$t('order.realName')" prop="realName" class="hover:bg-gray-50 p-2">
<el-input v-model="form.realName" :placeholder="$t('order.realName')"/>
</el-form-item>
<el-form-item :label="$t('order.phone')" prop="phone" class="hover:bg-gray-50 p-2">
<el-input v-model="form.phone" :maxlength="11" :placeholder="$t('order.phone')"/>
</el-form-item>
<el-form-item :label="$t('order.email')" prop="email" class="hover:bg-gray-50 p-2">
<el-input v-model="form.email" :placeholder="$t('order.email')"/>
</el-form-item>
<el-form-item :label="$t('order.address')" prop="address" class="hover:bg-gray-50 p-2">
<el-input v-model="form.address" :placeholder="$t('order.address')"/>
</el-form-item>
<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 type="primary" size="large" @click.stop="submitForm(formRef)">
{{ $t('order.submit') }}
</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-col>
</el-row>
</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 {useLayout, usePage} from "~/composables/configState";
import type {CmsNavigation} from "~/api/cms/cmsNavigation/model";
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 Left from "~/components/Left.vue";
import {addCmsOrder} from "~/api/cms/cmsOrder";
import {getCaptcha} from "~/api/passport/login";
import {getCmsNavigation, listCmsNavigation} from "~/api/cms/cmsNavigation";
// 引入状态管理
const route = useRoute();
const navId = ref();
const layout = useLayout();
const page = usePage();
const category = ref<CmsNavigation[]>([]);
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,
// 真实姓名
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;
// 二级栏目分类
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(categoryData => {
category.value = categoryData;
})
// seo
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
changeCaptcha();
})
}
// 提交表单
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)
}
})
}
})
}
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
</style>

72
pages/page/[id].vue Normal file
View File

@@ -0,0 +1,72 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-2">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template>
<el-card shadow="hover" class=" my-5">
<!-- 内容组件 -->
<Content class="content bg-white mt-5" :data="page.design?.content" />
</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 {getNavIdByParamsId, getViews, 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";
// 引入状态管理
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 () => {
getCmsNavigation(navId.value).then(data => {
page.value = data
layout.value.banner = data.banner;
// 二级栏目分类
if(data.parentId && data.parentId > 0){
listCmsNavigation({
parentId: data.parentId == 0 ? data.navigationId : data.parentId
}).then(list => {
category.value = list
})
}
// seo
useSeoMeta({
description: data.comments || data.title,
keywords: data.title,
titleTemplate: `${data?.title}` + ' - %s',
})
})
}
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
</style>

View File

@@ -1,76 +0,0 @@
<template>
<!-- Banner -->
<Banner :layout="layout" />
<!-- 简单模式(常规单页面) -->
<PageContainer :form="form" :layout="layout" />
<!-- 高级模式(自定义组件) -->
<div class="flex flex-col" v-if="layout?.showLayout">
{{ layout }}
</div>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm} from "~/utils/common";
import PageContainer from "~/components/PageContainer.vue";
// 引入状态管理
const route = useRoute();
const website = useWebsite();
const layout = ref<any>();
const config = useConfigInfo();
const token = useToken();
const form = useForm();
const breadcrumb = ref<BreadcrumbItem>();
// 请求数据
const reload = async () => {
// 存在spm(优先级高)
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/' + getIdBySpm(5))
if (nav.value?.data) {
form.value = nav.value.data
}else{
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: route.path}})
if(nav.value?.data){
form.value = nav.value?.data;
}
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
// seo
useHead({
title: `${form.value.title} - ${website.value.websiteName}`,
meta: [{ name: form.value.design?.keywords, content: form.value.design?.description }],
bodyAttrs: {
class: "page-container",
},
script: [
{
children: `console.log(${JSON.stringify(form.value)})`,
},
],
});
// 面包屑
breadcrumb.value = form.value
}
watch(
() => route.path,
(path) => {
console.log(path,'=>Path')
reload();
},
{ immediate: true }
);
</script>

View File

@@ -15,17 +15,17 @@
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="账号登录" name="account">
<div class="custom-style my-4">
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="auto" class="w-[330px]">
<el-form :model="form" label-width="auto" class="w-[330px]">
<!-- <el-form-item>-->
<!-- <el-input class="w-full" size="large" placeholder="租户ID" :prefix-icon="Shop" v-model="form.tenantId" />-->
<!-- </el-form-item>-->
<el-form-item prop="username">
<el-form-item>
<el-input class="w-full" size="large" placeholder="登录账号" :prefix-icon="Avatar" v-model="form.username" />
</el-form-item>
<el-form-item prop="password">
<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 prop="code">
<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" />
@@ -43,13 +43,13 @@
</el-tab-pane>
<el-tab-pane label="短信登录" name="sms">
<div class="custom-style my-4">
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="auto" class="w-[330px]">
<el-form-item prop="phone">
<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 prop="code">
<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">
@@ -80,18 +80,18 @@
<!-- 注册界面 -->
<el-space class="tabs pt-5 text-xl flex justify-center" v-if="!loginBar">
<el-tabs v-model="activeName" class="demo-tabs ">
<el-tab-pane label="注册账号" name="sms">
<div class="custom-style ">
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="auto" class="w-[330px]">
<el-form-item prop="phone">
<span class="text-sm text-gray-400 mb-4">
未注册手机号验证通过后将自动注册
</span>
<el-tab-pane label="手机号注册" name="sms">
<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 prop="code">
<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">
@@ -100,12 +100,6 @@
</el-button>
</el-space>
</el-form-item>
<el-form-item prop="companyName">
<el-input class="w-full" size="large" placeholder="企业名称" v-model="form.companyName" />
</el-form-item>
<el-form-item prop="email">
<el-input class="w-full" size="large" placeholder="邮箱地址" v-model="form.email" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a>
@@ -118,41 +112,41 @@
</el-form>
</div>
</el-tab-pane>
<!-- <el-tab-pane label="账号密码注册" name="account">-->
<!-- <div class="custom-style my-4">-->
<!-- <el-form :model="form" label-width="auto" class="w-[330px]">-->
<!-- <el-form-item>-->
<!-- <el-input class="w-full" size="large" maxlength="30" placeholder="登录账号" v-model="form.username" />-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-input type="password" size="large" maxlength="30" placeholder="登录密码" v-model="form.password" />-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.phone">-->
<!-- <template #prepend>+86</template>-->
<!-- </el-input>-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-space class="flex justify-between w-full">-->
<!-- <el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />-->
<!-- <el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">-->
<!-- <span v-if="!countdownTime">发送验证码</span>-->
<!-- <span v-else>已发送 {{ countdownTime }} s</span>-->
<!-- </el-button>-->
<!-- </el-space>-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>-->
<!-- <a href="#" class="text-gray-700">用户协议</a>-->
<!-- <a href="#" class="text-gray-700">隐私政策</a>-->
<!-- <a href="#" class="text-gray-700">产品服务协议</a>-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- </div>-->
<!-- </el-tab-pane>-->
<el-tab-pane label="账号密码注册" name="account">
<div class="custom-style my-4">
<el-form :model="form" label-width="auto" class="w-[330px]">
<el-form-item>
<el-input class="w-full" size="large" maxlength="30" placeholder="登录账号" v-model="form.username" />
</el-form-item>
<el-form-item>
<el-input type="password" size="large" maxlength="30" placeholder="登录密码" v-model="form.password" />
</el-form-item>
<el-form-item>
<el-input class="w-full" size="large" maxlength="11" placeholder="请输入手机号码" v-model="form.phone">
<template #prepend>+86</template>
</el-input>
</el-form-item>
<el-form-item>
<el-space class="flex justify-between w-full">
<el-input size="large" placeholder="短信验证码" maxlength="6" class="w-full" v-model="form.code" @keyup.enter.prevent="onSubmitBySms" />
<el-button size="large" class="w-full" :disabled="!!countdownTime" @click="checkUser">
<span v-if="!countdownTime">发送验证码</span>
<span v-else>已发送 {{ countdownTime }} s</span>
</el-button>
</el-space>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.isAgree">我已阅读并同意</el-checkbox>
<a href="#" class="text-gray-700">用户协议</a>
<a href="#" class="text-gray-700">隐私政策</a>
<a href="#" class="text-gray-700">产品服务协议</a>
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" class="w-full" :disabled="!form.isAgree" @click="onRegister">注册</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</el-space>
</el-card>
@@ -163,13 +157,11 @@ import {useConfigInfo, useToken, useUser, useWebsite} from "~/composables/config
import useFormData from '@/utils/use-form-data';
import type { User } from '@/api/system/user/model';
import { ref } from 'vue'
import type {FormInstance, FormRules, UploadProps, UploadUserFile} from 'element-plus'
import { Shop, Key, Avatar, Briefcase } 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 type {ShopMerchantApply} from "~/api/shop/shopMerchantApply/model";
import {useClientRequest} from "~/composables/useClientRequest";
import {getCaptcha, sendSmsCaptcha} from "~/api/passport/login";
// 配置信息
const runtimeConfig = useRuntimeConfig();
@@ -177,7 +169,6 @@ const website = useWebsite();
const config = useConfigInfo();
const token = useToken();
const user = useUser();
const formRef = ref<FormInstance>()
const activeName = ref('account')
@@ -196,20 +187,14 @@ const countdownTime = ref(0);
// 验证码倒计时定时器
let countdownTimer: number | null = null;
if(getIdBySpm(0) == 'register'){
loginBar.value = false;
}
// 配置信息
const { form,assignFields, resetFields } = useFormData<User>({
const { form } = useFormData<User>({
userId: undefined,
companyName: undefined,
email: undefined,
username: undefined,
phone: undefined,
password: undefined,
code: undefined,
smsCode: undefined,
username: '',
phone: '',
password: '',
code: '',
smsCode: '',
isAgree: false,
remember: true,
isSuperAdmin: true
@@ -236,33 +221,6 @@ const checkUser = async () => {
}
};
// 验证规则
const rules = reactive<FormRules<User>>({
phone: [
{required: true, message: '请输入手机号码', trigger: 'blur'},
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur'},
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'},
],
code: [
{required: true, message: '请输入验证码', trigger: 'blur'},
],
smsCode: [
{required: true, message: '请输入短信验证码', trigger: 'blur'},
],
username: [
{required: true, message: '请输入用户名', trigger: 'blur'},
],
companyName: [
{required: true, message: '请输入公司名称', trigger: 'blur'},
],
email: [
{required: true, message: '请输入邮箱', trigger: 'blur'},
{pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/, message: '请输入正确的邮箱', trigger: 'blur'},
]
})
/* 发送短信验证码 */
const sendCode = async () => {
@@ -273,10 +231,9 @@ const sendCode = async () => {
imgCode.value = text.value;
codeLoading.value = true;
const {data: smsCode } = await useServerRequest<ApiResult<CaptchaResult>>('/sendSmsCaptcha',{baseURL: runtimeConfig.public.apiServer,method: "post",body: {
sendSmsCaptcha({
phone: form.phone
}});
if(smsCode.value?.code == 0){
}).then(res => {
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
@@ -287,10 +244,9 @@ const sendCode = async () => {
}
countdownTime.value--;
}, 1000);
}
if(smsCode.value?.code != 0){
ElMessage.error(smsCode.value?.message);
}
}).catch(msg => {
ElMessage.error(msg)
})
};
const navigateTo = (url: string) => {
@@ -298,24 +254,19 @@ const navigateTo = (url: string) => {
}
const onLoginBar = () => {
// if(loginBar.value){
// return navigateTo(`/passport/register`)
// }
loginBar.value = !loginBar.value
activeName.value = loginBar.value ? 'account' : 'sms'
}
/* 获取图形验证码 */
const changeCaptcha = async () => {
const {data: captchaInfo } = await useServerRequest<ApiResult<CaptchaResult>>('/captcha',{baseURL: runtimeConfig.public.apiServer});
const captchaData = captchaInfo.value?.data
if(captchaData){
getCaptcha().then(captchaData => {
captcha.value = captchaData.base64;
text.value = captchaData.text;
}
// 已经登录跳转首页
if(token.value && token.value.length > 0){
navigateTo('/user')
return;
}
})
};
@@ -328,19 +279,7 @@ useHead({
* 执行登录
*/
const onSubmit = async () => {
if(!form.username){
ElMessage.error('请填登录账号')
return;
}
if(!form.password){
ElMessage.error('请填密码')
return;
}
if(!form.code){
ElMessage.error('请填验证码')
return;
}
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/login',{baseURL: runtimeConfig.public.apiServer,method: "post",body: form})
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)
@@ -356,14 +295,6 @@ const onSubmit = async () => {
* 短信验证码登录
*/
const onSubmitBySms = async () => {
if(!form.phone){
ElMessage.error('请填手机号码')
return;
}
if(!form.code){
ElMessage.error('请填验证码')
return;
}
const {data: response} = await useServerRequest<ApiResult<LoginResult>>('/loginBySms',{baseURL: runtimeConfig.public.apiServer,method: "post",body: {
phone: form.phone,
code: form.code,
@@ -384,28 +315,12 @@ const onSubmitBySms = async () => {
* 账号密码注册
*/
const onRegister = async () => {
if(!form.phone){
ElMessage.error('请填手机号码')
return;
}
if(!form.companyName){
ElMessage.error('请填写企业名称')
return;
}
if(!form.code){
ElMessage.error('请填验证码')
return;
}
if(!form.email){
ElMessage.error('请填邮箱')
return;
}
const loading = ElLoading.service({
lock: true,
text: 'Loading'
})
await useClientRequest<ApiResult<LoginResult>>('/register',{method: "post",body: {
companyName: form.companyName,
companyName: '应用名称',
username: form.phone,
phone: form.phone,
password: form.password,
@@ -471,21 +386,4 @@ changeCaptcha();
color: #606266;
font-size: 18px;
}
/* 改变激活标签的颜色 */
//.el-tabs__item.is-active {
// color: #cf1313;
//}
//
///* 改变标签边界的颜色 */
//.el-tabs__item:not(.is-disabled):focus,
//.el-tabs__item:not(.is-disabled):hover {
// border-color: #cf1313;
//}
//
///* 改变标签栏指示器的颜色 */
//.el-tabs--top .el-tabs__active-bar,
//.el-tabs--bottom .el-tabs__active-bar {
// background: #cf1313;
//}
</style>

185
pages/product/[id].vue Normal file
View File

@@ -0,0 +1,185 @@
<template>
<!-- 主体部分 -->
<div class="xl:w-screen-xl m-auto py-4 mt-20">
<el-page-header :icon="ArrowLeft" @back="goBack">
<template #content>
<span class="text-large font-600 mr-3"> {{ page.title }} </span>
</template>
<template #extra>
<div class="flex items-center">
<el-select v-model="value" clearable placeholder="筛选" style="width: 200px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template>
<el-row :gutter="24" id="container" class="clearfix">
<el-col v-for="(item,index) in list" :key="index" :span="6" class="left">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class=" hover:bg-gray-50 cursor-pointer" @click="navigateTo(`/item/${item.articleId}.html`)">
<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 gap-1.5">
<el-avatar
:src="item.image" shape="square" :size="44" style="background-color: white;"/>
<div class="flex-1 text-xl cursor-pointer flex flex-col">
{{ item.title }}
<sapn class="text-xs text-gray-400">site.websoft.top</sapn>
</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>-->
<!-- <el-button>控制台</el-button>-->
<!-- </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 } 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 navId = ref();
// 页面信息
const list = ref<CmsArticle[]>([]);
const i18n = useI18n();
const category = ref<CmsNavigation[]>([]);
const total = ref(0);
const activeName = ref('2839');
// 获取状态
const page = usePage();
const layout = useLayout();
// 搜索表单
const where = reactive<CmsArticleParam>({
keywords: '',
page: 1,
limit: 20,
status: 0,
parentId: undefined,
categoryId: undefined,
lang: i18n.locale.value
});
const goBack = () => {
console.log('go back')
notify('Back')
route.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;
// 加载文章列表
where.categoryId = page.value.navigationId;
pageCmsArticle(where).then(response => {
if(response){
total.value = response.count;
list.value = response.list;
}
})
})
}).catch(() => {})
}
/**
* 搜索
* @param data
*/
const search = (data: CmsArticleParam) => {
where.page = data.page;
reload();
}
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
const value = ref('')
const options = [
{
value: 'Option1',
label: 'Option1',
},
{
value: 'Option2',
label: 'Option2',
},
{
value: 'Option3',
label: 'Option3',
},
{
value: 'Option4',
label: 'Option4',
},
{
value: 'Option5',
label: 'Option5',
},
]
watch(
() => route.params.id,
(id) => {
navId.value = getNavIdByParamsId(id);
reload();
},
{ immediate: true }
);
</script>
<style lang="scss">
.right .content img{
width: auto !important;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
</style>

View File

@@ -1,67 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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 px-3">
<Breadcrumb :data="form" />
<div class="md:py-8 sm:py-16 md:px-0 px-4 py-8" _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">
<el-steps
style="max-width: 600px"
:space="200"
class="px-3"
:active="active"
finish-status="success"
>
<el-step title="选择套餐" />
<el-step title="订单确认" />
<el-step title="支付订单" />
<el-step title="完成订购" />
</el-steps>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {Article} from "~/api/cms/article/model";
withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: Article;
active?: number;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates',
}
);
</script>

View File

@@ -1,226 +0,0 @@
<template>
<PageBanner :form="form" :active="active" />
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="16" :xs="24">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>订单确认</span>
</div>
</template>
<el-descriptions :title="`订购产品`" :column="1" class="mb-4">
<el-descriptions-item label="产品名称:">{{ form.title }}</el-descriptions-item>
<el-descriptions-item label="产品类型:">{{ form.type == 1 ? '插件' : '完整应用' }}</el-descriptions-item>
<el-descriptions-item label="套餐版本:">标准版</el-descriptions-item>
<el-descriptions-item label="购买数量:">1 {{ form.unitName }}</el-descriptions-item>
<el-descriptions-item label="购买时长:">1 </el-descriptions-item>
<el-descriptions-item label="到期时间:">2025-10-06</el-descriptions-item>
</el-descriptions>
<el-descriptions title="其他服务" :column="1" class="mb-4">
<el-descriptions-item label="UI设计服务">不包含</el-descriptions-item>
</el-descriptions>
<el-descriptions title="合计:" :column="1" class="mb-4">
<el-descriptions-item label="总金额:">3000.00</el-descriptions-item>
<el-descriptions-item label="优惠价格:">1280.00</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>服务协议</span>
</div>
</template>
<div class="flex items-center">
<el-checkbox v-model="isAgree" @click="changeIsAgree"></el-checkbox>
<span class="ml-1">我已阅读并同意</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer" @click="openSpmUrl(`/detail`, {}, 357, true)">用户协议</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer" @click="openSpmUrl(`/detail`, {}, 69, true)">隐私协议</span>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>扫码支付</span>
</div>
</template>
<div class="flex justify-center">
<el-radio-group v-model="form.code">
<el-radio-button value="1" border>微信支付</el-radio-button>
<el-radio-button value="12" border>支付宝</el-radio-button>
<el-radio-button value="24" border>余额支付</el-radio-button>
</el-radio-group>
</div>
<div class="flex justify-center py-4">
<el-avatar :size="250" src="https://oss.wsdns.cn/20240409/247a492abda94b08ace33fa5405628ca.jpeg"
shape="square" />
</div>
<el-form-item>
<el-button type="danger" :disabled="!isAgree" class="w-full" size="large" @click="onSubmit">去支付</el-button>
</el-form-item>
</el-card>
</el-col>
</el-row>
</div>
{{ form }}
<!-- <div class="login-layout mt-[100px] m-auto sm:w-screen-xl w-full">-->
<!-- <div class="mt-[100px] m-auto flex sm:flex-row flex-col sm:p-0 p-3">-->
<!-- <div class="flash bg-white rounded-lg px-7 py-4 w-full">-->
<!-- <el-tabs class="flash bg-white ml-0">-->
<!-- <el-tab-pane label="个人开发者">-->
<!-- <el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:py-2">-->
<!-- <el-form-item label="商品类型">-->
<!-- <el-input v-model="form.title" placeholder="请输入真实姓名" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="资源包类型">-->
<!-- <el-input v-model="form.productId" placeholder="请输入证件号码" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="身份证(正面)">-->
<!-- <Upload />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="身份证(反面)">-->
<!-- <Upload />-->
<!-- </el-form-item>-->
<!-- <el-form-item>-->
<!-- <el-button type="primary" size="large" @click="onSubmit">提交</el-button>-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</template>
<script setup lang="ts">
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 type {Product} from "~/api/oa/product/model";
import PageBanner from "./components/PageBanner.vue";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const activeIndex = ref('');
const website = useWebsite()
const isAgree = ref<boolean>(false);
const active = ref<number>(2);
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
// 配置信息
const {form, assignFields} = useFormData<Product>({
// 自增ID
productId: undefined,
// 类型 0软件产品 1实物商品 2虚拟商品
type: undefined,
// 产品编码
code: undefined,
// 产品标题
title: undefined,
// 封面图
image: undefined,
// 产品详情
content: undefined,
// 父级分类ID
parentId: undefined,
// 产品分类ID
categoryId: undefined,
// 产品规格 0单规格 1多规格
specs: undefined,
// 货架
position: undefined,
// 单位名称 (个)
unitName: undefined,
// 进货价格
price: undefined,
// 销售价格
salePrice: undefined,
// 库存计算方式(10下单减库存 20付款减库存)
deductStockType: undefined,
// 轮播图
files: undefined,
// 销量
sales: undefined,
// 库存
stock: undefined,
// 消费赚取积分
gainIntegral: undefined,
// 推荐
recommend: undefined,
// 商户ID
merchantId: undefined,
// 状态0未上架1上架
isShow: undefined,
// 状态, 0上架 1待上架 2待审核 3审核不通过
status: undefined,
// 备注
comments: undefined,
// 排序号
sortNumber: undefined,
// 用户ID
userId: undefined,
// 是否删除, 0否, 1是
deleted: undefined,
// 租户id
tenantId: undefined,
// 创建时间
createTime: undefined,
// 修改时间
updateTime: undefined,
});
const changeIsAgree = (index: number) => {
if(index){
active.value = 3;
}
}
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 {data: response} = await useServerRequest<ApiResult<Product>>(`/cms/cms-product/${getIdBySpm(5)}`, {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
assignFields(response.value?.data);
form.categoryName = '订单确认'
useHead({
title: `${form.title}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="scss">
.text-a123 {
color: #f0f2f5;
}
</style>

View File

@@ -1,81 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in data?.list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer"
@click="openSpmUrl(`/item`, item,item.companyId,true)">
<el-image
:src="item.image"
:fit="fit" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.shortName }}
<el-tag v-if="item.chargingMethod === 0" size="small" type="success" class="text-white ml-2"
effect="dark">免费
</el-tag>
</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">
<el-space class="flex items-end">
<el-avatar size="small" :src="item.companyLogo"/>
<span class="text-gray-400 line-clamp-1 pr-2">{{ item.companyName }}</span>
</el-space>
<template v-if="item.isBuy">
<el-button v-if="item.installed" type="primary" @click.stop="loginDeveloperCenterByToken(item)">控制台</el-button>
<el-button v-else type="primary" @click.stop="loginDeveloperCenterByToken(item)">控制台</el-button>
</template>
<template v-else>
<el-button v-if="item.chargingMethod == 0" type="primary" @click.stop="loginDeveloperCenterByToken(item)">控制台</el-button>
<el-button v-else type="warning" @click.stop="openSpmUrl(`/product/create`,item,item.companyId,true)">立即开通
</el-button>
</template>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div class="flex justify-center py-3">
<el-pagination background layout="prev, pager, next" :total="data?.count" @change="onPage"/>
</div>
</template>
<script setup lang="ts">
import {loginAdminByToken, loginByToken, openSpmUrl} from "~/utils/common";
import type {Company} from "~/api/system/company/model";
import type {PageResult} from "~/api";
const props = withDefaults(
defineProps<{
data?: PageResult<Company>;
disabled?: boolean;
fit?: any;
}>(),
{
fit: 'cover'
}
);
const tid = ref<number>();
const emit = defineEmits<{
(e: 'done', index: number): void;
}>();
// const load = () => {
// if(!props.disabled){
// emit('done')
// }
// }
const onPage = (index: number) => {
emit('done', index)
}
tid.value = localStorage.getItem('TenantId');
</script>

View File

@@ -1,67 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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 px-3">
<Breadcrumb :data="form" />
<div class="md:py-8 sm:py-16 md:px-0 px-4 py-8" _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">
<el-steps
style="max-width: 600px"
:space="200"
class="px-3"
:active="active"
finish-status="success"
>
<el-step title="选择套餐" />
<el-step title="订单确认" />
<el-step title="支付订单" />
<el-step title="完成订购" />
</el-steps>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {Article} from "~/api/cms/article/model";
withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: Article;
active?: number;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates'
}
);
</script>

View File

@@ -1,439 +0,0 @@
<template>
<PageBanner :form="form" :active="active"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="16" :xs="24">
<el-form :model="form" label-width="auto" label-position="left" class="w-full">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span v-if="form.type === 0">产品</span>
<span v-if="form.type === 1">插件</span>
<span>{{ form.shortName }}</span>
</div>
</template>
<div class="flex flex-col">
<el-form-item label="套餐版本">
<el-radio-group v-model="form.image">
<el-radio-button value="1" border>基础版</el-radio-button>
<el-radio-button value="2" border disabled>专业版</el-radio-button>
<el-radio-button value="3" border disabled>定制版</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="项目名称">
<el-input v-model="cart.appName" style="width: 360px" placeholder="网宿软件"></el-input>
</el-form-item>
<el-form-item label="二级域名">
<el-input
v-model="cart.domain"
style="width: 360px"
placeholder="websoft"
>
<template #prepend>Https://</template>
<template #append>.wsdns.cn</template>
</el-input>
</el-form-item>
<!-- <el-form-item label="类型">-->
<!-- <el-radio-button v-if="form.type == 0" border>完整应用</el-radio-button>-->
<!-- <el-radio-button v-if="form.type == 1" border>插件</el-radio-button>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="交付方式" v-if="form.deliveryMethod">-->
<!-- <el-radio-button v-if="form.deliveryMethod == 1" border>SaaS交付</el-radio-button>-->
<!-- <el-radio-button v-if="form.deliveryMethod == 2" border>源码交付</el-radio-button>-->
<!-- </el-form-item>-->
<el-form-item label="购买时长" v-if="form.chargingMethod && form.chargingMethod > 1">
<el-radio-group v-model="cart.month" @change="handleChargingMethod">
<el-radio-button :value="1" border>1个月</el-radio-button>
<el-radio-button :value="12" border>1</el-radio-button>
<el-radio-button :value="24" border>2</el-radio-button>
<el-radio-button :value="36" border>3</el-radio-button>
<el-radio-button :value="60" border>5</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 单价 {{ form.price }} x 购买时长 {{ cart.month }} x 数量 {{ cart.num }} x 折扣 0.1= {{ cart.totalPrice }}-->
<!-- <el-form-item label="购买数量">-->
<!-- <el-input-number v-model="cart.num" :min="1" :max="form.canBuyNumber" @change="handleChange"/>-->
<!-- </el-form-item>-->
</div>
</el-card>
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>服务协议</span>
</div>
</template>
<div class="flex items-center">
<el-checkbox v-model="isAgree" @change="changeIsAgree"></el-checkbox>
<span class="ml-1">我已阅读并同意</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 357, true)">用户协议</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 69, true)">隐私协议</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 69, true)">产品购买协议</span>
</div>
</el-card>
</el-form>
</el-col>
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>订单详情</span>
</div>
</template>
<el-descriptions :title="`订购产品`" :column="1" class="mb-4">
<el-descriptions-item label="产品名称:">{{ form.shortName }}</el-descriptions-item>
<el-descriptions-item label="产品类型:">{{ form.type == 1 ? '插件' : '完整应用' }}</el-descriptions-item>
<el-descriptions-item label="交付方式:">{{ form.deliveryMethod == 1 ? 'SaaS交付' : '源码交付' }}</el-descriptions-item>
<el-descriptions-item label="应用名称:">{{ cart.appName }}</el-descriptions-item>
<!-- <el-descriptions-item label="套餐版本:">标准版</el-descriptions-item>-->
<el-descriptions-item label="购买数量:">{{ cart.num }} </el-descriptions-item>
<el-descriptions-item label="购买时长:">{{ cart.month }} 个月</el-descriptions-item>
<!-- <el-descriptions-item label="到期时间:">2025-10-06</el-descriptions-item>-->
</el-descriptions>
<el-descriptions title="合计:" :column="1" class="mb-4">
<el-descriptions-item label="订单金额:"><span class="line-through"></span><span
class="text-xl line-through">{{ cart.totalPrice }}</span></el-descriptions-item>
<el-descriptions-item label="实付金额:"><span class="text-red-600"></span><span
class="font-bold text-xl text-red-600">{{ cart.payPrice }}</span></el-descriptions-item>
</el-descriptions>
<el-form-item>
<el-button type="danger" class="w-full" :disabled="!isAgree" size="large" @click="onPay">去支付</el-button>
</el-form-item>
</el-card>
</el-col>
</el-row>
</div>
<el-dialog
v-model="visible"
title="订单确认"
align-center
width="450"
:before-close="() => visible = false"
>
<div class="flex justify-center pt-3">
<el-radio-group v-model="cart.payType" @change="handlePayType">
<el-radio-button :value="102" border>微信支付</el-radio-button>
<!-- <el-radio-button :value="3" border>支付宝</el-radio-button>-->
<el-radio-button :value="0" border>余额支付</el-radio-button>
</el-radio-group>
</div>
<div class="flex justify-center py-4" v-if="cart.payType == 102">
<el-avatar :size="250" :src="wxPayQrCode"
shape="square"/>
</div>
<template #footer>
<div class="dialog-footer text-center pb-3" v-if="cart.payType != 0">
<el-tag type="success" size="large">
使用微信扫码完成支付
</el-tag>
</div>
<div class="flex flex-col justify-center w-1/2 m-auto pb-12" v-if="cart.payType === 0">
<span class="py-2 text-center">可用余额:¥{{ userInfo?.balance || 0.00 }}</span>
<div class="flex flex-col">
<el-input type="password" class="py-2" size="large" v-model="payPassword" maxlength="6" show-password placeholder="请输入支付密码" />
<el-button type="danger" class="py-2 my-3" size="large" @click="onDone">
确定支付
</el-button>
</div>
</div>
</template>
</el-dialog>
</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 QRCode from 'qrcode';
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import PageBanner from './components/PageBanner.vue';
import type {FormInstance} from "element-plus";
import {useClientRequest} from "~/composables/useClientRequest";
import type {Company} from "~/api/system/company/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const activeIndex = ref('');
const website = useWebsite()
const isAgree = ref<boolean>(true);
const active = ref<number>(1);
const visible = ref<boolean>(false);
const wxPayQrCode = ref<string>()
const payPassword = ref<string>()
const formRef = ref<FormInstance>()
const config = useConfigInfo();
const token = useToken();
const userInfo = useUser();
const cart = useCart();
const qrOptions = ref({
width: 200, // 二维码宽度
margin: 10, // 二维码边距
color: { dark: '#000', light: '#fff' }, // 二维码颜色
});
// 配置信息
const {form, assignFields} = useFormData<Company>({
companyId: undefined,
menuId: undefined,
type: undefined,
appName: undefined,
shortName: undefined,
companyName: undefined,
companyType: undefined,
companyTypeMultiple: undefined,
appType: undefined,
companyLogo: undefined,
image: undefined,
companyCode: undefined,
domain: undefined,
phone: undefined,
tel: undefined,
email: undefined,
InvoiceHeader: undefined,
startTime: undefined,
expirationTime: undefined,
version: undefined,
versionName: undefined,
versionCode: undefined,
members: undefined,
storage: undefined,
storageMax: undefined,
buys: undefined,
clicks: undefined,
users: undefined,
departments: undefined,
industryParent: undefined,
industryChild: undefined,
country: undefined,
province: undefined,
city: undefined,
region: undefined,
address: undefined,
latitude: undefined,
longitude: undefined,
businessEntity: undefined,
comments: undefined,
authentication: undefined,
industryId: undefined,
industryName: undefined,
status: undefined,
userId: undefined,
official: undefined,
deliveryMethod: undefined,
chargingMethod: undefined,
price: undefined,
planId: undefined,
sortNumber: undefined,
authoritative: undefined,
merchantId: undefined,
tenantId: undefined,
tenantName: undefined,
tenantCode: undefined,
modules: undefined,
requestUrl: undefined,
socketUrl: undefined,
serverUrl: undefined,
modulesUrl: undefined,
merchantUrl: undefined,
websiteUrl: undefined,
mpWeixinCode: undefined,
mpAlipayCode: undefined,
h5Code: undefined,
androidUrl: undefined,
iosUrl: undefined,
avatar: undefined,
nickname: undefined,
code: undefined,
createTime: undefined,
updateTime: undefined,
password: undefined,
password2: undefined,
collection: undefined,
recommend: undefined,
title: undefined,
parentName: undefined,
categoryName: undefined,
});
const handleChange = (index:any): void => {
cart.value.num = index;
computeTotalPrice();
}
const handleChargingMethod = (index: any): void => {
cart.value.month = index;
computeTotalPrice();
}
const computeTotalPrice = () => {
// 计算公式 = (商品价格 * 数量 * 月份) / 折扣率
cart.value.totalPrice = Math.round(Number(form.price)*Number(cart.value.num)*Number(cart.value.month));
cart.value.payPrice = Math.round(Number(cart.value.totalPrice) * Number(0.1));
}
const changeIsAgree = (index: any) => {
if (index) {
active.value = 2;
}
}
const handlePayType = (index: any) => {
cart.value.payType = index;
onPay();
}
// 统一下单
const onPay = () => {
visible.value = true;
active.value = 3;
// 余额支付
if(cart.value.payType == 0){
return;
}
// 微信支付
if(cart.value.payType == 102){
useClientRequest<ApiResult<any>>(`/system/wx-native-pay/codeUrl`, {
method: 'POST',
body: cart.value
}).then(async res => {
if (res.code == 0) {
try {
// 生成二维码图像数据URL
wxPayQrCode.value = await QRCode.toDataURL(res.data);
} catch (error) {
console.error('Error generating QR code:', error);
}
} else {
return ElMessage.error(res.message)
}
})
}
// 支付宝支付
if(cart.value.payType == 3){
return;
}
}
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 () => {
// 要求登录
if (!token.value || token.value == '') {
navigateTo(`/passport/login`)
return false;
}
// useClientRequest(`/auth/user`, {}).then(res => {
// if(res?.data?.balance){
// cart.value.balance = res?.data?.balance;
// }
// })
const {data: response} = await useServerRequest<ApiResult<Company>>(`/system/company/${getIdBySpm(5)}`, {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
assignFields(response.value?.data);
form.categoryName = '选择套餐';
cart.value.comments = `购买${response.value?.data.shortName}`;
if(response.value?.data.price){
cart.value.payPrice = response.value?.data.price;
cart.value.adminUrl = response.value?.data.domain;
cart.value.menuId = response.value?.data.menuId;
computeTotalPrice();
}
useHead({
title: `${form.shortName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
}
}
// 扫码支付
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
ElMessage.error('还没有评分哦!')
// if (form.rate === 0) {
// ElMessage.error('还没有评分哦!')
// return false;
// }
// form.productId = props.productId;
// useClientRequest<ApiResult<any>>(`/cms/cms-product-comment/`, {
// method: 'POST',
// body: form
// }).then(res => {
// if (res.code == 0) {
// ElMessage.success(res.message)
// visible.value = false
// resetFields();
// emit('done',1)
// } else {
// return ElMessage.error(res.message)
// }
// })
}
// 余额支付
const onDone = () => {
cart.value.type = 1;
cart.value.list = [];
cart.value.list?.push(form);
useClientRequest<ApiResult<any>>(`/system/order/createOrder`, {
method: 'POST',
body: cart.value
}).then(res => {
if(res.code == 0){
ElMessage.success('购买成功');
visible.value = !visible.value;
setTimeout(() => {
openSpmUrl(`https://console.websoft.top/cost-center/order`)
},500)
}
if(res.code == 1){
ElMessage.error(res.message);
}
}).catch(res => {
})
// useClientRequest<ApiResult<any>>(`/system/menu/install`, {
// method: 'POST',
// body: {
// companyId: getIdBySpm(5)
// }
// }).then(res => {
// if (res.code == 0) {
// ElMessage.success(res.message)
// } else {
// return ElMessage.error(res.message)
// }
// })
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="scss">
.text-a123 {
color: #f0f2f5;
}
</style>

View File

@@ -1,389 +0,0 @@
<template>
<PageBanner :form="form" :active="active"/>
<div class="page-main md:w-screen-xl m-auto p-3">
<el-row :gutter="24">
<el-col :span="16" :xs="24">
<el-form :model="form" label-width="auto" label-position="left" class="w-full">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span v-if="form.type === 0">产品</span>
<span v-if="form.type === 1">插件</span>
<span>{{ form.title }}</span>
</div>
</template>
<div class="flex flex-col">
<el-form-item label="类型">
<el-radio-button v-if="form.type == 0" border>完整应用</el-radio-button>
<el-radio-button v-if="form.type == 1" border>插件</el-radio-button>
</el-form-item>
<el-form-item label="套餐版本">
<el-radio-group v-model="form.image">
<el-radio-button value="1" border>基础版</el-radio-button>
<el-radio-button value="2" border>标准版</el-radio-button>
<el-radio-button value="3" border>专业版</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- <el-form-item label="交付方式" v-if="form.deliveryMethod">-->
<!-- <el-tag v-if="form.deliveryMethod == 1">SaaS交付</el-tag>-->
<!-- <el-tag v-if="form.deliveryMethod == 2">源码交付</el-tag>-->
<!-- </el-form-item>-->
<el-form-item label="购买时长" v-if="form.chargingMethod && form.chargingMethod > 1">
<el-radio-group v-model="form.chargingMethod" @change="handleChargingMethod">
<el-radio-button :value="1" border>1个月</el-radio-button>
<el-radio-button :value="12" border>1</el-radio-button>
<el-radio-button :value="24" border>2</el-radio-button>
<el-radio-button :value="36" border>3</el-radio-button>
<el-radio-button :value="60" border>5</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- <el-form-item label="购买数量">-->
<!-- <el-input-number v-model="cart.num" :min="1" :max="form.canBuyNumber" @change="handleChange"/>-->
<!-- </el-form-item>-->
</div>
</el-card>
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>服务协议</span>
</div>
</template>
<div class="flex items-center">
<el-checkbox v-model="isAgree" @change="changeIsAgree"></el-checkbox>
<span class="ml-1">我已阅读并同意</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 357, true)">用户协议</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 69, true)">隐私协议</span>
<span class="text-blue-400 hover:text-blue-500 cursor-pointer"
@click="openSpmUrl(`/detail`, {}, 69, true)">产品购买协议</span>
</div>
</el-card>
</el-form>
</el-col>
<el-col :span="8" :xs="24">
<el-card shadow="hover" class="mb-4">
<template #header>
<div class="card-header font-bold">
<span>配置清单</span>
</div>
</template>
<el-descriptions :title="`订购产品`" :column="1" class="mb-4">
<el-descriptions-item label="产品名称:">{{ form.title }}</el-descriptions-item>
<el-descriptions-item label="产品类型:">{{ form.type == 1 ? '插件' : '完整应用' }}</el-descriptions-item>
<el-descriptions-item label="套餐版本:">标准版</el-descriptions-item>
<el-descriptions-item label="购买数量:">{{ cart.num }} </el-descriptions-item>
<el-descriptions-item label="购买时长:">{{ cart.month }} 个月</el-descriptions-item>
<el-descriptions-item label="到期时间:">2025-10-06</el-descriptions-item>
</el-descriptions>
<el-descriptions title="合计:" :column="1" class="mb-4">
<el-descriptions-item label="订单金额:"><span class="line-through"></span><span
class="text-xl line-through">{{ cart.totalPrice }}</span></el-descriptions-item>
<el-descriptions-item label="实付金额:"><span class="text-red-600"></span><span
class="font-bold text-2xl text-red-600">{{ cart.totalPrice }}</span></el-descriptions-item>
</el-descriptions>
<el-form-item>
<el-button type="danger" class="w-full" :disabled="!isAgree" size="large" @click="onPay">去支付</el-button>
</el-form-item>
</el-card>
</el-col>
</el-row>
</div>
<el-dialog
v-model="visible"
title="订单确认"
align-center
width="400"
:before-close="() => visible = false"
>
<div class="flex justify-center pt-3">
<el-radio-group v-model="cart.payType" @change="handlePayType">
<el-radio-button :value="102" border>微信支付</el-radio-button>
<el-radio-button :value="3" border>支付宝</el-radio-button>
<el-radio-button :value="0" border>余额支付</el-radio-button>
</el-radio-group>
</div>
<div class="flex justify-center py-4">
<el-avatar :size="250" src="https://oss.wsdns.cn/20240409/247a492abda94b08ace33fa5405628ca.jpeg"
shape="square"/>
</div>
<template #footer>
<div class="dialog-footer text-center pb-3">
<el-button type="success" @click="onDone">
已完成支付
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
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 PageBanner from './components/PageBanner.vue';
import type {FormInstance} from "element-plus";
import {useClientRequest} from "~/composables/useClientRequest";
import type {Company} from "~/api/system/company/model";
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
import type {CmsProductParameter} from "~/api/cms/cmsProductParameter/model";
import type {CmsProductUrl} from "~/api/cms/cmsProductUrl/model";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const activeIndex = ref('');
const website = useWebsite()
const isAgree = ref<boolean>(true);
const active = ref<number>(1);
const visible = ref<boolean>(false);
const formRef = ref<FormInstance>()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const cart = useCart();
// 配置信息
const {form, assignFields} = useFormData<CmsProduct>({
// 自增ID
productId: undefined,
// 类型 0软件产品 1实物商品 2虚拟商品
type: undefined,
// 产品编码
code: undefined,
// 产品标题
title: undefined,
// 封面图
image: undefined,
// 产品详情
content: undefined,
// 父级分类ID
parentId: undefined,
// 产品分类ID
categoryId: undefined,
// 产品规格 0单规格 1多规格
specs: undefined,
// 货架
position: undefined,
// 单位名称 (个)
unitName: undefined,
// 进货价格
price: undefined,
// 销售价格
salePrice: undefined,
// 库存计算方式(10下单减库存 20付款减库存)
deductStockType: undefined,
// 轮播图
files: undefined,
// 销量
sales: undefined,
// 库存
stock: undefined,
// 安装次数
install: undefined,
// 消费赚取积分
gainIntegral: undefined,
// 计费方式
durationMethod: undefined,
// 推荐
recommend: undefined,
// 商户ID
merchantId: undefined,
merchantName: undefined,
merchantAvatar: undefined,
merchantComments: undefined,
// 状态0未上架1上架
isShow: undefined,
// 状态, 0上架 1待上架 2待审核 3审核不通过
status: undefined,
// 备注
comments: undefined,
// 排序号
sortNumber: undefined,
// 用户ID
userId: undefined,
// 是否删除, 0否, 1是
deleted: undefined,
// 租户id
tenantId: undefined,
// 创建时间
createTime: undefined,
// 修改时间
updateTime: undefined,
// 父级分类名称
parentName: undefined,
// 父级分类路径
parentPath: undefined,
// 分类名称
categoryName: undefined,
// 评分
rate: undefined,
// 是否已购买
isBuy: undefined,
// 是否已安装插件
installed: undefined,
// 产品参数
parameters: undefined,
// 产品链接
links: undefined,
// 插件入口
path: undefined,
// 标签
tag: undefined,
// 菜单ID
menuId: undefined,
});
const handleChange = (index:any): void => {
cart.value.num = index;
computeTotalPrice();
}
const handleChargingMethod = (index: any): void => {
cart.value.month = index;
computeTotalPrice();
}
const computeTotalPrice = () => {
// cart.value.totalPrice = cart.value.num * cart.value.payPrice * cart.value.month;
// cart.value.totalPrice = Math.round(cart.value.totalPrice * 100) / 100;
}
const changeIsAgree = (index: any) => {
if (index) {
active.value = 2;
}
}
const handlePayType = (index: any) => {
cart.value.payType = index;
onPay();
}
// 统一下单
const onPay = () => {
visible.value = true;
active.value = 3;
useClientRequest<ApiResult<any>>(`/shop/shop-order/createOrder`, {
method: 'POST',
body: cart.value
}).then(res => {
if (res.code == 0) {
ElMessage.success(res.message)
} else {
return ElMessage.error(res.message)
}
})
}
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 () => {
// 要求登录
if (!token.value || token.value == '') {
navigateTo(`/passport/login`)
return false;
}
const {data: response} = await useServerRequest<ApiResult<CmsProduct>>(`/cms/cms-product/${getIdBySpm(5)}`)
console.log(response.value?.data,'response')
if (response.value?.data) {
assignFields(response.value?.data);
form.categoryName = '选择套餐';
if(response.value?.data.price){
cart.value.payPrice = response.value?.data.price;
cart.value.comments = `${response.value?.data.title}`;
if(cart.value.num){
cart.value.totalPrice = response.value?.data.price * cart.value.num
}
let goodsName = '';
// if(form.type == 0){
// goodsName = `【软件】${form.title}`
// }
// if(form.type == 1){
// goodsName = `【插件】${form.title}`
// }
// cart.value.orderProduct?.push({
// goodsId: form.productId,
// goodsName: goodsName,
// image: form.image,
// price: response.value?.data.price,
// totalPrice: cart.value.totalPrice,
// num: cart.value.num,
// month: cart.value?.month,
// payType: cart.value.payType
// });
}
useHead({
title: `${form.title}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
}
}
// 扫码支付
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
ElMessage.error('还没有评分哦!')
// if (form.rate === 0) {
// ElMessage.error('还没有评分哦!')
// return false;
// }
// form.productId = props.productId;
// useClientRequest<ApiResult<any>>(`/cms/cms-product-comment/`, {
// method: 'POST',
// body: form
// }).then(res => {
// if (res.code == 0) {
// ElMessage.success(res.message)
// visible.value = false
// resetFields();
// emit('done',1)
// } else {
// return ElMessage.error(res.message)
// }
// })
}
// 完成支付
const onDone = () => {
useClientRequest<ApiResult<any>>(`/system/menu/install`, {
method: 'POST',
body: {
productId: getIdBySpm(5)
}
}).then(res => {
if (res.code == 0) {
ElMessage.success(res.message)
} else {
return ElMessage.error(res.message)
}
})
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
console.log(path, '=>Path')
reload();
},
{immediate: true}
);
</script>
<style lang="scss">
.text-a123 {
color: #f0f2f5;
}
</style>

View File

@@ -1,25 +0,0 @@
<template>
</template>
<script setup lang="ts">
// 创建站点
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import type {Company} from "~/api/system/company/model";
const {data: website} = await useServerRequest<ApiResult<Company>>('/cms/website/createWebsite', {
baseURL: 'http://127.0.0.1:9002/api',
method: 'post',
body: company.value?.data
})
if (website.value?.code == 401) {
console.log('网站不存在', website.value)
}
if (website.value?.code == 0) {
console.log('网站存在', website.value)
}
</script>
<style scoped lang="less">
</style>

View File

@@ -1,25 +0,0 @@
<template>
</template>
<script setup lang="ts">
// 创建站点
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import type {Company} from "~/api/system/company/model";
const {data: website} = await useServerRequest<ApiResult<Company>>('/cms/website/createWebsite', {
baseURL: 'http://127.0.0.1:9002/api',
method: 'post',
body: company.value?.data
})
if (website.value?.code == 401) {
console.log('网站不存在', website.value)
}
if (website.value?.code == 0) {
console.log('网站存在', website.value)
}
</script>
<style scoped lang="less">
</style>

View File

@@ -1,168 +0,0 @@
<template>
<el-affix :offset="0" @change="onAffix">
<div class="affix justify-between p-2 opacity-90 border-b-solid border-gray-200 border-b-1" :class="affix ? 'bg-white w-full' : 'hidden'">
<div class="w-3/4 m-auto flex justify-between">
<a class="goods-name text-xl font-bold cursor-pointer hover:text-gray-900">{{ goods?.goodsName }}</a>
<div class="affix-bar">
<el-anchor :offset="100" direction="horizontal" :marker="false">
<el-anchor-link :href="`#basic`">
参数信息
</el-anchor-link>
<el-anchor-link :href="`#photo`">
图文详情
</el-anchor-link>
<el-anchor-link :href="`#comment`">
用户评价
</el-anchor-link>
<el-anchor-link :href="`#buynow`">
<el-button type="danger" size="small">立即购买</el-button>
</el-anchor-link>
</el-anchor>
</div>
</div>
</div>
</el-affix>
<div id="buynow" class="flex flex-col w-full md:w-screen-xl m-auto md:pt-[60px]" v-if="goods">
<Breadcrumb :data="goods" />
<div class="content bg-white rounded-xl">
<!-- <ProductShopInfo :data="goods?.merchant" />-->
<div id="buynow" class="bg-white p-4 mt-4 flex gap-xl rounded-xl">
<div class="goods-image flex gap-xl">
<div class="gap-xs flex flex-col" v-if="goods?.files">
<el-avatar v-for="item in JSON.parse(goods.files)" :src="item.url" size="large" shape="square" />
</div>
<el-image :src="goods?.image" fit="contain" class="w-2xl h-2xl bg-gray-100 border-radius:30px"></el-image>
</div>
<div class="goods-info flex flex-col gap-xs">
<div class="goods-name text-2xl">{{ goods.goodsName }}</div>
<div class="goods-price text-2xl red">{{ Number(goods?.salePrice) * Number(goods?.num) }}</div>
<div class="text-green-7">购买得积分</div>
<div class="text-gray-4">配送无需配送</div>
<div class="text-gray-4">保障假一赔四 退货包运费 极速退款</div>
<div class="text-gray-4">销量 {{ goods.sales }}</div>
<!-- <template v-for="spec in goods?.goodsSpecValue">-->
<!-- <div class="flex items-center">-->
<!-- <div class="text-gray-4">{{ spec.value }}</div>-->
<!-- <el-radio-group v-model="goods.radio">-->
<!-- <el-radio v-for="(specValue,specIndex) in spec.detail" :label="specIndex" border>{{ specValue }}</el-radio>-->
<!-- </el-radio-group>-->
<!-- </div>-->
<!-- </template>-->
<div class="text-gray-4">
已选中{{ goods.radio }}
</div>
<div class="text-gray-4">
数量
<el-input-number v-model="goods.num" :min="1" :max="10" label="描述文字"></el-input-number>
</div>
<div class="py-5">
<el-button-group size="large">
<el-button type="danger">立即购买</el-button>
<el-button type="warning">加入购物车</el-button>
</el-button-group>
<el-button size="large" class="ml-3">收藏</el-button>
</div>
</div>
</div>
</div>
<div id="basic" class="bg-white p-4 mt-4 flex gap-xl rounded-xl">
<el-descriptions class="margin-top" title="参数信息" :column="1" border>
<el-descriptions-item label="品牌">websoft</el-descriptions-item>
<el-descriptions-item label="版本">2.0</el-descriptions-item>
<el-descriptions-item label="开发语言">JavaVue3Nuxt</el-descriptions-item>
<el-descriptions-item label="版本">
<el-tag size="small">授权版</el-tag>
</el-descriptions-item>
<el-descriptions-item label="备注">江苏省苏州市吴中区吴中大道 1188 </el-descriptions-item>
</el-descriptions>
</div>
<div id="photo" class="bg-white p-4 mt-4 flex gap-xl rounded-xl flex-col">
<div class="font-bold">图文详情</div>
<div class="files flex flex-col w-3/4" v-if="goods?.files">
<el-image v-for="item in JSON.parse(goods.files)" :src="item.url" />
</div>
</div>
<div id="comment" class="bg-white p-4 mt-4 flex gap-xl rounded-xl">
<div class="font-bold">用户评价</div>
</div>
<div v-if="!goods">
<el-empty description="404 该商品找不到了222"></el-empty>
</div>
</div>
</template>
<script setup lang="ts">
import type {ApiResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Goods} from "~/api/shop/goods/model";
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {Navigation} from "~/api/cms/navigation/model";
import {useProductAffix} from "~/composables/configState";
import {getIdBySpm} from "~/utils/common";
const route = useRoute();
const affix = useProductAffix();
// 商品ID
const goodsId = ref();
// 页面信息
const form = ref<Navigation>();
// 商品信息
const goods = ref<Goods>();
const onAffix = (index: boolean) => {
affix.value = index;
}
// 加载数据
const reload = async () => {
// TODO 请求导航页面数据
// const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/navigation/getNavigationByPath',{
// query: {
// path: goodsId.value
// }
// })
// if(nav.value?.data){
// form.value = nav.value.data;
// }
// TODO 获取商品详情
console.log(goodsId.value,'sss')
const { data: info } = await useServerRequest<ApiResult<Goods>>('/shop/goods/' + getIdBySpm(5))
goods.value = info.value?.data;
console.log(goods.value)
}
watch(
() => route.query.spm,
(spm) => {
console.log(spm)
// TODO 方案一从spm参数提取商品ID
const spmValue = String(spm).split('.')
if(spmValue[5]){
goodsId.value = spmValue[5];
}
console.log(goodsId.value)
// TODO 方案二(优先级更高)从params获取商品ID
const { detail } = route.params
const split = String(detail).split('.');
if(Number(split[0]) > 0){
goodsId.value = split[0];
}
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,75 +0,0 @@
<template>
<PageBanner :layout="layout" :title="`${form?.categoryName}`" :desc="`${form?.comments}`" />
<CardList :param="{type: 0}" :data="data" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import type {Company, CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
import {getIdBySpm} from "~/utils/common";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const data = ref<PageResult<Company>>();
const page = ref<number>(0);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
// 获取状态
const form = ref<Navigation>();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const onSearch = (index: number) => {
page.value = index;
reload();
}
// 请求数据
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Company>>>('/system/company/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 12,
categoryId: getIdBySpm(5),
keywords: where.keywords
}})
if(response.value?.data){
data.value = response.value?.data;
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-navigation/${getIdBySpm(5)}`)
if(nav.value?.data){
form.value = nav.value?.data;
useHead({
title: `${form.value.title} - WEBSOFT`,
bodyAttrs: {
class: "page-container",
}
});
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
watch(
() => getIdBySpm(5),
(id) => {
console.log(id,'id = .>>>>')
reload();
},
{ immediate: true }
);
</script>

146
pages/search/[id].vue Normal file
View File

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

View File

@@ -1,138 +0,0 @@
<template>
<div class="login-layout mt-[100px] min-h-2xl m-auto sm:w-screen-sm w-full">
<div class="title text-xl text-gray-700 px-4 py-2 font-500 text-center">
<div class="sm:w-screen-sm w-full">
<el-input
v-model="where.keywords"
class="w-full"
size="large"
placeholder="搜索"
:prefix-icon="Search"
@keydown.enter="handleClick"
>
<template #append>
<el-button size="large" type="primary" @click="handleClick">搜索</el-button>
</template>
</el-input>
<el-tabs v-model="activeName" class="my-3" @tab-click="handleClick">
<el-tab-pane label="应用" name="web"></el-tab-pane>
<el-tab-pane label="公众号" name="mp-official"></el-tab-pane>
<el-tab-pane label="小程序" name="mp-weixin"></el-tab-pane>
<el-tab-pane label="移动应用" name="app"></el-tab-pane>
<el-tab-pane label="小商店" name="mp-shop"></el-tab-pane>
<el-tab-pane label="其他" name="other"></el-tab-pane>
</el-tabs>
</div>
</div>
<template v-if="activeName === 'web'">
<div class="search bg-white rounded-lg p-3 w-full" v-if="websites.length > 0">
<div class="title text-gray-400 px-4 py-2 mb-4">网站应用</div>
<div class="result px-3">
<div v-for="(item,index) in websites" :key="index" class="app-item block border-solid rounded-lg border-gray-300 border-1 mb-4 p-3 flex flex-row items-center hover:border-blue-4 hover:border-2 cursor-pointer">
<el-space @click="navTo(item)">
<div class="avatar">
<el-avatar :src="item.websiteLogo" style="background-color: #f3f3f3" size="large" />
</div>
<div class="app-info flex flex-col">
<div class="text-lg">{{ item.websiteName }}</div>
<div class="text-gray-400">{{ item.comments }}</div>
<div class="text-gray-300 text-xs-1">{{ item.websiteType }}</div>
</div>
</el-space>
</div>
</div>
</div>
</template>
<div v-else class="px-1 text-center text-gray-500 min-h-xs">
{{ resultText }}
</div>
</div>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {Navigation} from "~/api/cms/navigation/model";
import {getPath} from "~/utils/common";
import type {CompanyParam} from "~/api/system/company/model";
import type {Tenant} from "~/api/system/tenant/model";
import {navigateTo} from "#imports";
import type {Website} from "~/api/cms/website/model";
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Tenant[]>([]);
const websites = ref<Website[]>([]);
const activeName = ref('web');
const page = ref<number>(0);
const resultText = ref('');
const layout = ref<any>();
// 获取状态
const form = ref<Navigation>();
const website = useWebsite();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const navTo = (item: Website) => {
if(item.domain){
openSpmUrl(`https://${item.domain}`,item,item.tenantId)
return;
}
openSpmUrl(`https://${item.websiteCode}.wsdns.cn`,item,item.tenantId)
}
// 请求数据
const reload = async () => {
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-navigation/getNavigationByPath',{query: {path: getPath()}})
if(nav.value?.data){
form.value = nav.value?.data;
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
useHead({
title: `搜索结果 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}
});
}
// 搜索结果
const handleClick = async () => {
if (where.keywords == '') {
return false;
}
if(activeName.value == 'web'){
const {data: response} = await useServerRequest<ApiResult<PageResult<Website>>>('/cms/cms-website/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value, keywords: where.keywords
}})
if(response.value?.data){
if (response.value?.data.list) {
websites.value = response.value?.data.list;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
}
watch(
() => route,
() => {
reload();
},
{ immediate: true }
);
</script>

286
pages/show/[id].vue Normal file
View 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 mr-3"> {{ 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>

View 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>

View 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-4"/>
<!-- <el-image :src="form.image" shape="square" :size="80"-->
<!-- class="hidden-sm-and-up bg-white rounded-avatar-xs shadow-sm hover:shadow mr-4"/>-->
</template>
<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>

151
pages/tags/[id].vue Normal file
View 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>

View File

@@ -1,57 +0,0 @@
<template>
<div class="xl:w-screen-xl sm:flex xl:p-0 p-4 m-auto relative" v-infinite-scroll="load">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb-5 min-w-xs">
<el-card shadow="hover" :body-style="{ padding: '0px' }" class="hover:bg-gray-50 cursor-pointer">
<el-image
:src="item.image"
:fit="fit" :lazy="true" class="w-full md:h-[150px] h-[199px] cursor-pointer bg-gray-50"/>
<div class="flex-1 px-4 py-5 sm:p-6 !p-4">
<div class="text-gray-700 dark:text-white text-base font-semibold flex flex-col gap-1.5">
<div class="flex-1 text-xl cursor-pointer flex items-center">
{{ item.shortName }}
</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">
<el-button class="w-full" @click="openSpmUrl(`https://${item.tenantId}.wsdns.cn`,item,item.companyId,true)">预览</el-button>
</div>
</div>
</el-card>
</el-col>
</template>
</el-row>
</div>
<div v-if="disabled" class="px-1 text-center text-gray-500 min-h-xs">
没有更多了
</div>
</template>
<script setup lang="ts">
import {openSpmUrl} from "~/utils/common";
import type {Company} from "~/api/system/company/model";
const props = withDefaults(
defineProps<{
list?: Company[];
disabled?: boolean;
fit?: any;
}>(),
{
fit: 'cover'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
}
}
</script>

View File

@@ -1,115 +0,0 @@
<template>
<div class="banner m-auto relative sm:flex">
<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 md:p-0 px-4">
<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 grid lg:grid-cols-2 lg:items-center" v-if="layout">
<div class="">
<h1
class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
<span v-if="layout.title">{{ layout.title }}</span>
<span v-if="layout.name">{{ layout.name }}</span>
</h1>
<div class="mt-4 text-lg text-gray-500 dark:text-gray-400">
{{ layout.description }}
</div>
<div class="flex justify-between w-full items-center mt-4">
<el-space>
<div class="w-[500px]">
<el-input
v-model="where.keywords"
placeholder="搜索"
:prefix-icon="Search"
@keydown.enter="handleClick"
>
<template #append>
<el-button size="large" type="primary" @click="handleClick">搜索</el-button>
</template>
</el-input>
</div>
<el-button
size="large"
@click="openSpmUrl(`/passport/login`)"
>
创建
</el-button>
</el-space>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import {useConfigInfo} from "~/composables/configState";
import {openSpmUrl} from "~/utils/common";
import type {CompanyParam} from "~/api/system/company/model";
const token = useToken();
const sysDomain = useSysDomain();
withDefaults(
defineProps<{
layout?: any;
title?: string;
desc?: string;
buyUrl?: string;
}>(),
{
title: 'Templates',
desc: 'Explore community templates to get up and running in a few seconds.',
demoUrl: '/product/website',
buyUrl: 'https://github.com/websoft9/ansible-templates'
}
);
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const config = useConfigInfo();
// 搜索结果
const handleClick = async () => {
if (where.keywords == '') {
return false;
}
if(activeName.value == 'web'){
const {data: response} = await useServerRequest<ApiResult<PageResult<Website>>>('/cms/cms-website/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value, keywords: where.keywords
}})
if(response.value?.data){
if (response.value?.data.list) {
websites.value = response.value?.data.list;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
}
</script>

View File

@@ -1,84 +0,0 @@
<template>
<PageBanner :layout="layout" :title="`${form?.categoryName}`" :desc="`${form?.comments}`" />
<CardList :param="{type: 0,official: true}" :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
import type {ApiResult, PageResult} from "~/api";
import {useServerRequest} from "~/composables/useServerRequest";
import type {Navigation} from "~/api/cms/navigation/model";
import type {Company, CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
const route = useRoute();
// 页面信息
const runtimeConfig = useRuntimeConfig();
const list = ref<Company[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
const disabled = ref<boolean>(false);
// 获取状态
const form = ref<Navigation>();
// 搜索表单
const where = reactive<CompanyParam>({
keywords: ''
});
const onSearch = () => {
if(!disabled.value){
page.value++;
reload(route.path);
}
}
// 请求数据
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Company>>>('/system/company/pageAll',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
categoryId: getIdBySpm(5),
keywords: where.keywords
}})
if(response.value?.data){
if (list.value.length < response.value?.data.count) {
disabled.value = false;
if (response.value?.data.list) {
list.value = list.value.concat(response.value?.data.list);
}
}else {
disabled.value = true;
}
if(response.value.data.count == 0){
resultText.value = '暂无相关结果'
}
}
}
const { data: nav } = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-navigation/${getIdBySpm(5)}`)
if(nav.value?.data){
form.value = nav.value?.data;
useHead({
title: `${form.value.title} - WEBSOFT`,
bodyAttrs: {
class: "page-container",
}
});
}
// 页面布局
if(form.value?.layout){
layout.value = JSON.parse(form.value?.layout)
}
watch(
() => route.path,
(path) => {
reload(path);
},
{ immediate: true }
);
</script>

View File

@@ -1,58 +0,0 @@
<template>
<PageBanner title="实名认证" desc="Authentication"/>
<div class="login-layout m-auto sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3 ">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="reload"/>
<div class="flash bg-white rounded-lg px-8 py-4 w-full">
<Auth @done="reload"/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo, useWebsite} from "~/composables/configState";
import {ref} from 'vue'
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import UserMenu from "./components/UserMenu.vue";
import Auth from './components/Auth.vue';
import type {ShopMerchantApply} from "~/api/shop/shopMerchantApply/model";
import Base from "~/pages/user/components/Base.vue";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const activeIndex = ref('');
const website = useWebsite()
const config = useConfigInfo();
const merchantApply = ref<ShopMerchantApply>();
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
const {data: response} = await useServerRequest<ApiResult<ShopMerchantApply>>('/shop/shop-merchant-apply/getByUserId', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
merchantApply.value = response.value.data;
}
if(config.value){
useHead({
title: `实名认证 - ${config.value?.siteName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{immediate: true}
);
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
<template>
</template>
<script setup lang="ts">
import {useToken} from "~/composables/configState";
const token = useToken();
token.value = '';
localStorage.clear();
setTimeout(() => {
navigateTo('/')
return;
}, 1000)
</script>

View File

@@ -1,94 +0,0 @@
<template>
<PageBanner title="订单列表" desc="Order List" />
<div class="login-layout m-auto sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:p-0 p-3">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" />
<div class=" 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="flash bg-white rounded-lg px-8 py-4 w-auto">
<Order :form="form" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
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';
import Auth from "~/pages/user/auth.vue";
// 配置信息
const runtimeConfig = useRuntimeConfig();
const route = useRoute();
const website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const activeIndex = ref('');
// 配置信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
password: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
const tableData = ref<any[]>();
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
const onSubmit = async () => {
const {data: modify } = await useServerRequest<ApiResult<User>>('/auth/user',{
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if(modify.value?.code == 0){
ElMessage.success('修改成功')
}
}
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
// const {data: response} = await useServerRequest<ApiResult<Order>>('/system/order',{baseURL: runtimeConfig.public.apiServer})
// if(response.value?.data){
// console.log(response.value,'order')
// // userInfo.value = response.value?.data;
// // assignFields(response.value?.data);
// }
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{ immediate: true }
);
</script>

View File

@@ -1,139 +0,0 @@
<template>
<PageBanner title="修改密码" desc="Change Password"/>
<div class="login-layout m-auto sm:w-screen-xl w-full">
<div class="m-auto flex sm:flex-row flex-col sm:px-0 px-3">
<!-- 用户菜单 -->
<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>
</template>
<script setup lang="ts">
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 website = useWebsite()
const config = useConfigInfo();
const token = useToken();
const userInfo = ref<User>();
const activeIndex = ref('');
// 验证码 base64 数据
const captcha = ref('');
// 验证码内容, 实际项目去掉
const text = ref('');
// 图形验证码
const imgCode = ref('');
// 发送验证码按钮loading
const codeLoading = ref(false);
// 验证码倒计时时间
const countdownTime = ref(0);
// 验证码倒计时定时器
let countdownTimer: number | null = null;
// 配置信息
const {form, assignFields} = useFormData<User>({
userId: undefined,
nickname: '',
username: '',
phone: '',
mobile: '',
sex: '',
sexName: '',
email: '',
oldPassword: '',
password: '',
password2: '',
code: '',
smsCode: '',
comments: '',
remember: true
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
meta: [{name: website.value.keywords, content: website.value.comments}]
});
/* 发送短信验证码 */
const sendCode = async () => {
if (!form.phone) {
ElMessage.error('请输入手机号码');
return;
}
imgCode.value = text.value;
codeLoading.value = true;
const {data: smsCode} = await useServerRequest<ApiResult<CaptchaResult>>('/sendSmsCaptcha', {
baseURL: runtimeConfig.public.apiServer, method: "post", body: {
phone: form.phone
}
});
if (smsCode.value) {
codeLoading.value = false;
countdownTime.value = 30;
// 开始对按钮进行倒计时
countdownTimer = window.setInterval(() => {
if (countdownTime.value <= 1) {
countdownTimer && clearInterval(countdownTimer);
countdownTimer = null;
}
countdownTime.value--;
}, 1000);
}
};
const onSubmit = async () => {
const {data: modify} = await useServerRequest<ApiResult<User>>('/auth/password', {
baseURL: runtimeConfig.public.apiServer,
method: 'put',
body: form
})
if (modify.value?.code == 0) {
ElMessage.success('修改成功')
}
}
const onDone = (index: string) => {
activeIndex.value = index;
}
const reload = async () => {
// 未登录状态(是否强制登录)
const token = localStorage.getItem('token');
if (!token || token == '') {
navigateTo('/passport/login');
return false;
}
const {data: response} = await useServerRequest<ApiResult<User>>('/auth/user', {baseURL: runtimeConfig.public.apiServer})
if (response.value?.data) {
userInfo.value = response.value?.data;
assignFields(response.value?.data);
}
}
watch(
() => route.path,
(path) => {
activeIndex.value = path;
reload();
},
{immediate: true}
);
</script>

119
pages/video/[id].vue Normal file
View 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
View 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>