Browse Source

新增:产品管理模块

master
科技小王子 8 months ago
parent
commit
ead8acb8bb
  1. 2
      api/cms/article/model/index.ts
  2. 117
      api/cms/cmsProduct/index.ts
  3. 82
      api/cms/cmsProduct/model/index.ts
  4. 106
      api/cms/cmsProductSpec/index.ts
  5. 35
      api/cms/cmsProductSpec/model/index.ts
  6. 106
      api/cms/cmsProductSpecValue/index.ts
  7. 29
      api/cms/cmsProductSpecValue/model/index.ts
  8. 106
      api/cms/cmsProductUrl/index.ts
  9. 39
      api/cms/cmsProductUrl/model/index.ts
  10. 1
      api/cms/navigation/model/index.ts
  11. 91
      api/oa/product/model/index.ts
  12. 20
      components/AppHeader.vue
  13. 15
      components/Breadcrumb.vue
  14. 75
      components/PageBanner.vue
  15. 87
      components/ProductList.vue
  16. 16
      layouts/default.vue
  17. 8
      nuxt.config.ts
  18. 8
      pages/article/[categoryId].vue
  19. 104
      pages/article/components/PageBanner.vue
  20. 103
      pages/ask/[userId].vue
  21. 2
      pages/ask/index.vue
  22. 87
      pages/case/components/PageBanner.vue
  23. 5
      pages/case/index.vue
  24. 48
      pages/category/components/CardList.vue
  25. 91
      pages/category/index.vue
  26. 35
      pages/components/ArticleList.vue
  27. 63
      pages/components/CardList.vue
  28. 5
      pages/components/PlugList.vue
  29. 23
      pages/components/ProductList.vue
  30. 64
      pages/detail/components/LikeArticle.vue
  31. 4
      pages/detail/components/PageBanner.vue
  32. 41
      pages/detail/index.vue
  33. 3
      pages/developer/index.vue
  34. 2
      pages/docs/index.vue
  35. 10
      pages/index.vue
  36. 89
      pages/item/components/PageBanner.vue
  37. 254
      pages/item/index.vue
  38. 3
      pages/market/components/PageBanner.vue
  39. 6
      pages/market/index.vue
  40. 13
      pages/product/components/CardList.vue
  41. 15
      pages/product/index.vue
  42. 1
      pages/user/auth.vue
  43. 7
      pages/user/index.vue
  44. 1
      pages/user/order.vue
  45. 1
      pages/user/password.vue
  46. 23
      utils/common.ts

2
api/cms/article/model/index.ts

@ -20,6 +20,8 @@ export interface Article {
parentId?: number;
// 父级分类名称
parentName?: string;
// 父级分类路径
parentPath?: string;
// 封面图
image?: string;
// 附件

117
api/cms/cmsProduct/index.ts

@ -0,0 +1,117 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsProduct, CmsProductParam } from './model';
import { SERVER_API_URL } from '~/config';
import type { ArticleParam } from "@/api/cms/article/model";
/**
*
*/
export async function pageCmsProduct(params: CmsProductParam) {
const res = await request.get<ApiResult<PageResult<CmsProduct>>>(
SERVER_API_URL + '/cms/cms-product/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function listCmsProduct(params?: CmsProductParam) {
const res = await request.get<ApiResult<CmsProduct[]>>(
SERVER_API_URL + '/cms/cms-product',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function addCmsProduct(data: CmsProduct) {
const res = await request.post<ApiResult<unknown>>(
SERVER_API_URL + '/cms/cms-product',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function updateCmsProduct(data: CmsProduct) {
const res = await request.put<ApiResult<unknown>>(
SERVER_API_URL + '/cms/cms-product',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeCmsProduct(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/cms/cms-product/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeBatchCmsProduct(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
SERVER_API_URL + '/cms/cms-product/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* id查询产品
*/
export async function getCmsProduct(id: number) {
const res = await request.get<ApiResult<CmsProduct>>(
SERVER_API_URL + '/cms/cms-product/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
export async function getCount(params: ArticleParam) {
const res = await request.get(SERVER_API_URL + '/cms/cms-product/data', {
params
});
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

82
api/cms/cmsProduct/model/index.ts

@ -0,0 +1,82 @@
import type { PageParam } from '@/api';
/**
*
*/
export interface CmsProduct {
// 自增ID
productId?: number;
// 类型 0软件产品 1实物商品 2虚拟商品
type?: number;
// 产品编码
code?: string;
// 产品标题
title?: string;
// 封面图
image?: string;
// 产品详情
content?: string;
// 父级分类ID
parentId?: number;
// 产品分类ID
categoryId?: number;
// 产品规格 0单规格 1多规格
specs?: number;
// 货架
position?: string;
// 单位名称 (个)
unitName?: string;
// 进货价格
price?: number;
// 销售价格
salePrice?: number;
// 库存计算方式(10下单减库存 20付款减库存)
deductStockType?: number;
// 轮播图
files?: any;
// 销量
sales?: number;
// 库存
stock?: number;
// 消费赚取积分
gainIntegral?: string;
// 推荐
recommend?: number;
// 商户ID
merchantId?: number;
// 状态(0:未上架,1:上架)
isShow?: string;
// 状态, 0上架 1待上架 2待审核 3审核不通过
status?: number;
// 备注
comments?: string;
// 排序号
sortNumber?: number;
// 用户ID
userId?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 父级分类名称
parentName?: string;
// 父级分类路径
parentPath?: string;
// 分类名称
categoryName?: string;
// 评分
rate?: number;
}
/**
*
*/
export interface CmsProductParam extends PageParam {
productId?: number;
status?: number;
keywords?: string;
}

106
api/cms/cmsProductSpec/index.ts

@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsProductSpec, CmsProductSpecParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
*
*/
export async function pageCmsProductSpec(params: CmsProductSpecParam) {
const res = await request.get<ApiResult<PageResult<CmsProductSpec>>>(
MODULES_API_URL + '/cms/cms-product-spec/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function listCmsProductSpec(params?: CmsProductSpecParam) {
const res = await request.get<ApiResult<CmsProductSpec[]>>(
MODULES_API_URL + '/cms/cms-product-spec',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function addCmsProductSpec(data: CmsProductSpec) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function updateCmsProductSpec(data: CmsProductSpec) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeCmsProductSpec(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeBatchCmsProductSpec(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* id查询规格
*/
export async function getCmsProductSpec(id: number) {
const res = await request.get<ApiResult<CmsProductSpec>>(
MODULES_API_URL + '/cms/cms-product-spec/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

35
api/cms/cmsProductSpec/model/index.ts

@ -0,0 +1,35 @@
import type { PageParam } from '@/api';
/**
*
*/
export interface CmsProductSpec {
// 规格ID
specId?: number;
// 规格名称
specName?: string;
// 规格值
specValue?: string;
// 创建用户
userId?: number;
// 更新者
updater?: number;
// 备注
comments?: string;
// 状态, 0正常, 1待修,2异常已修,3异常未修
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
*
*/
export interface CmsProductSpecParam extends PageParam {
specId?: number;
keywords?: string;
}

106
api/cms/cmsProductSpecValue/index.ts

@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsProductSpecValue, CmsProductSpecValueParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
*
*/
export async function pageCmsProductSpecValue(params: CmsProductSpecValueParam) {
const res = await request.get<ApiResult<PageResult<CmsProductSpecValue>>>(
MODULES_API_URL + '/cms/cms-product-spec-value/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function listCmsProductSpecValue(params?: CmsProductSpecValueParam) {
const res = await request.get<ApiResult<CmsProductSpecValue[]>>(
MODULES_API_URL + '/cms/cms-product-spec-value',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function addCmsProductSpecValue(data: CmsProductSpecValue) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function updateCmsProductSpecValue(data: CmsProductSpecValue) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeCmsProductSpecValue(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec-value/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeBatchCmsProductSpecValue(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-spec-value/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* id查询规格值
*/
export async function getCmsProductSpecValue(id: number) {
const res = await request.get<ApiResult<CmsProductSpecValue>>(
MODULES_API_URL + '/cms/cms-product-spec-value/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

29
api/cms/cmsProductSpecValue/model/index.ts

@ -0,0 +1,29 @@
import type { PageParam } from '@/api';
/**
*
*/
export interface CmsProductSpecValue {
// 规格值ID
specValueId?: number;
// 规格组ID
specId?: number;
// 规格值
specValue?: string;
// 备注
comments?: string;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
*
*/
export interface CmsProductSpecValueParam extends PageParam {
specValueId?: number;
keywords?: string;
}

106
api/cms/cmsProductUrl/index.ts

@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsProductUrl, CmsProductUrlParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
*
*/
export async function pageCmsProductUrl(params: CmsProductUrlParam) {
const res = await request.get<ApiResult<PageResult<CmsProductUrl>>>(
MODULES_API_URL + '/cms/cms-product-url/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function listCmsProductUrl(params?: CmsProductUrlParam) {
const res = await request.get<ApiResult<CmsProductUrl[]>>(
MODULES_API_URL + '/cms/cms-product-url',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function addCmsProductUrl(data: CmsProductUrl) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-url',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function updateCmsProductUrl(data: CmsProductUrl) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-url',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeCmsProductUrl(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-url/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
*
*/
export async function removeBatchCmsProductUrl(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/cms/cms-product-url/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* id查询域名
*/
export async function getCmsProductUrl(id: number) {
const res = await request.get<ApiResult<CmsProductUrl>>(
MODULES_API_URL + '/cms/cms-product-url/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

39
api/cms/cmsProductUrl/model/index.ts

@ -0,0 +1,39 @@
import type { PageParam } from '@/api';
/**
*
*/
export interface CmsProductUrl {
// 自增ID
id?: number;
// 产品ID
productId?: number;
// 域名类型
type?: string;
// 域名
domain?: string;
// 账号
account?: string;
// 密码
password?: string;
// 商户ID
merchantId?: number;
// 备注
comments?: string;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1待确认
status?: number;
// 创建时间
createTime?: string;
// 租户id
tenantId?: number;
}
/**
*
*/
export interface CmsProductUrlParam extends PageParam {
id?: number;
keywords?: string;
}

1
api/cms/navigation/model/index.ts

@ -13,6 +13,7 @@ export interface Navigation{
type?: number;
sortNumber?: number;
hide?: number;
permission?: number;
home?: number;
position?: number;
meta?: string;

91
api/oa/product/model/index.ts

@ -1,61 +1,74 @@
import type { PageParam } from '@/api';
import {Company} from "@/api/system/company/model";
/**
*
*/
export interface Product {
// 自增ID
productId?: number;
name?: string;
// 类型 0软件产品 1实物商品 2虚拟商品
type?: number;
// 产品编码
code?: string;
type?: string;
logo?: string;
money?: number;
salesInitial?: number;
salesActual?: number;
stockTotal?: number;
// 产品标题
title?: string;
// 封面图
image?: string;
backgroundColor?: string;
backgroundImage?: string;
backgroundGif?: string;
buyUrl?: string;
adminUrl?: string;
downUrl?: string;
source?: string;
// 产品详情
content?: string;
virtualViews?: string;
actualViews?: string;
userId?: string;
companyId?: number;
nickname?: string;
username?: string;
userAvatar?: string;
shopId?: string;
// 父级分类ID
parentId?: number;
// 产品分类ID
categoryId?: number;
// 产品规格 0单规格 1多规格
specs?: number;
// 货架
position?: string;
// 单位名称 (个)
unitName?: string;
// 进货价格
price?: string;
// 销售价格
salePrice?: string;
// 库存计算方式(10下单减库存 20付款减库存)
deductStockType?: number;
// 轮播图
files?: string;
sortNumber?: number;
comments?: string;
serverUrl?: string;
// 销量
sales?: number;
// 库存
stock?: number;
// 消费赚取积分
gainIntegral?: string;
// 推荐
recommend?: number;
// 商户ID
merchantId?: number;
// 状态(0:未上架,1:上架)
isShow?: string;
// 状态, 0上架 1待上架 2待审核 3审核不通过
status?: number;
callbackUrl?: string;
// 备注
comments?: string;
// 排序号
sortNumber?: number;
// 用户ID
userId?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
company?: Company;
tenantId?: number;
}
/**
*
*/
export interface ProductParam extends PageParam {
title?: string;
code?: string;
productId?: number;
categoryId?: string;
status?: string;
sortNumber?: string;
createTime?: string;
username?: string;
nickname?: string;
// 商户编号
merchantCode?: string;
status?: number;
keywords?: string;
}

20
components/AppHeader.vue

@ -44,9 +44,6 @@
</el-sub-menu>
<el-menu-item v-else :index="`${item.path}`"><h3>{{ item.title }}</h3></el-menu-item>
</template>
<el-menu-item v-if="user.roleId === 858" :index="`/merchant`"><h3>商家中心</h3></el-menu-item>
<el-menu-item v-if="user.roleId === 859" :index="`/developer`"><h3>开发者中心</h3></el-menu-item>
<el-menu-item v-else :index="`/developer`"><h3>入驻</h3></el-menu-item>
</el-menu>
</div>
</div>
@ -64,7 +61,7 @@
<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 command="user">账户中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出
</el-dropdown-item>
</el-dropdown-menu>
@ -81,7 +78,8 @@
<div class="md:hidden flex md:p-0 px-4">
<el-dropdown>
<span class="el-dropdown-link">
<el-button :icon="ElIconMenu"></el-button>
<el-avatar v-if="token" class="cursor-pointer" :src="user?.avatar" :size="30"/>
<el-button v-else :icon="ElIconMenu"></el-button>
</span>
<template #dropdown>
<el-dropdown-menu>
@ -90,10 +88,12 @@
<span @click="navigateTo(item.path)">{{ item.title }}</span>
</el-dropdown-item>
</template>
<el-dropdown-item v-if="token && sysDomain"
@click="loginAdminByToken">控制台
</el-dropdown-item>
<el-dropdown-item v-else divided @click="navigateTo(`/passport/login`)">登录</el-dropdown-item>
<template v-if="token">
<el-dropdown-item divided v-if="sysDomain" @click="loginAdminByToken">控制台</el-dropdown-item>
<el-dropdown-item divided command="user" @click="navigateTo(`/user`)">账户中心</el-dropdown-item>
<el-dropdown-item divided command="logOut" @click="navigateTo('/user/logout')">退出</el-dropdown-item>
</template>
<el-dropdown-item v-if="!token" divided @click="navigateTo(`/passport/login`)">登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -126,14 +126,12 @@ import {loginAdminByToken} from "~/utils/common";
const route = useRoute();
//
const runtimeConfig = useRuntimeConfig();
const website = useWebsite()
const showPassport = ref<boolean>(false);
const token = useToken();
const user = useUser();
const navigations = useMenu();
const config = useConfigInfo();
const affix = useProductAffix();
const TID_ADMIN = localStorage.getItem('TID_ADMIN');
const sysDomain = useSysDomain();
//

15
components/Breadcrumb.vue

@ -1,8 +1,14 @@
<template>
<div class="sm:py-4 sm:px-0 mx-3 py-2">
<el-breadcrumb :separator-icon="ArrowRight">
<el-breadcrumb-item :to="{ path: '/' }"><el-icon class="cursor-pointer"><ElIconHouse /></el-icon></el-breadcrumb-item>
<el-breadcrumb-item v-if="data?.parentName" :to="{ path: data.parentPath }">{{ data.parentName }}</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/' }">
<el-icon class="cursor-pointer">
<ElIconHouse/>
</el-icon>
</el-breadcrumb-item>
<el-breadcrumb-item v-if="data?.parentName">
<span class="cursor-pointer hover:font-bold" @click="openSpmUrl(`/category`,data,data.categoryId)">{{ data.parentName }}</span>
</el-breadcrumb-item>
<el-breadcrumb-item v-if="data?.categoryName">{{ title ? title : data.categoryName }}</el-breadcrumb-item>
<el-breadcrumb-item v-if="title">{{ title }}</el-breadcrumb-item>
</el-breadcrumb>
@ -10,8 +16,9 @@
</template>
<script setup lang="ts">
import { ArrowRight } from '@element-plus/icons-vue'
withDefaults(
import {ArrowRight} from '@element-plus/icons-vue'
withDefaults(
defineProps<{
data?: any;
title?: string;

75
components/PageBanner.vue

@ -1,7 +1,7 @@
<template>
<div class="banner m-auto relative sm:flex">
<div :class="form?.style" 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 md:top-[-2px] top-[2px] transition-all text-green-5 flex-shrink-0 opacity-100 duration-[400ms] opacity-80 -z-10">
class="pointer-events-none absolute w-full top-[-2px] transition-all text-green-5 flex-shrink-0 opacity-100 duration-[400ms] opacity-80 -z-10">
<mask id="path-1-inside-1_414_5526" fill="white">
<path d="M0 0H1440V181H0V0Z"></path>
</mask>
@ -19,49 +19,38 @@
</linearGradient>
</defs>
</svg>
<div class="md:w-screen-xl m-auto md:px-0 px-4">
<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="gap-8 sm:gap-y-16 grid lg:grid-cols-2 lg:items-center">
<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 class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl lg:text-5xl">
<span>{{ layout?.title || title }}</span>
</h1>
<div class="mt-4 text-lg text-gray-500 dark:text-gray-400">
{{ layout.description }}
{{ layout?.description || desc }}
</div>
<el-space class="mt-4">
<el-space class="mt-4" v-if="layout?.buyUrl || layout?.demoUrl">
<el-button
:icon="ElIconView"
v-if="layout?.buyUrl"
:icon="ElIconBottom"
size="large"
v-if="layout.demoUrl"
@click="openSpmUrl(layout.demoUrl)"
@click="openSpmUrl(`http://git.gxwebsoft.com/gxwebsoft/websoft-cms.git`, {},0,true)"
>
演示地址
下载模版
</el-button>
<el-button
v-if="layout.buyUrl"
:icon="ElIconBottom"
:icon="ElIconMemo"
size="large"
@click="openSpmUrl(layout.buyUrl)"
v-if="layout?.docUrl"
@click="openSpmUrl(`/docs`)"
>
下载模版
帮助文档
</el-button>
<!-- <el-button-->
<!-- :icon="ElIconMemo"-->
<!-- size="large"-->
<!-- v-if="layout.docUrl"-->
<!-- @click="openSpmUrl(layout.docUrl)"-->
<!-- >-->
<!-- 帮助文档-->
<!-- </el-button>-->
</el-space>
</div>
</div>
</div>
@ -70,11 +59,10 @@
</template>
<script setup lang="ts">
import {useConfigInfo} from "~/composables/configState";
import {openSpmUrl} from "~/utils/common";
const token = useToken();
const sysDomain = useSysDomain();
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
import type {Navigation} from "~/api/cms/navigation/model";
withDefaults(
defineProps<{
@ -84,13 +72,28 @@ withDefaults(
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'
title: 'PageName',
desc: 'Description',
demoUrl: '/product',
buyUrl: '/buy'
}
);
const config = useConfigInfo();
const route = useRoute();
const form = ref<Navigation>();
const layout = ref<any>();
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(form.value.permission === 1){
navigateTo(`/passport/login`)
}
}
</script>

87
components/ProductList.vue

@ -0,0 +1,87 @@
<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="md:w-screen-xl m-auto relative md:flex md:p-0 p-4">
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" :xs="24" 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="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">
<p 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>
</p>
<p v-if="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>
</p>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl('/item', item,item.goodsId,true)">
查看详情
</el-button>
<el-button type="primary" v-if="item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买
</el-button>
<el-button v-else 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 {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>

16
layouts/default.vue

@ -1,6 +1,6 @@
<template>
<!-- 加载应用 -->
<template v-if="!loading">
<template v-if="!loading" @scroll="handleScroll">
<!-- 管理中心界面 -->
<template v-if="getPath().startsWith('/manage')">
<slot/>
@ -86,6 +86,12 @@ const reload = async () => {
website.value = websiteInfo.value?.data;
config.value = website.value.config;
if (website.value.topNavs) {
// if(localStorage.getItem('UserId')){
// website.value?.topNavs.push({
// title: ``,
// path: `/developer`
// })
// }
menu.value = website.value?.topNavs;
}
if (website.value.bottomNavs) {
@ -146,10 +152,10 @@ const reload = async () => {
// }
// TODO 9 SEO
useHead({
title: `网宿软件首页`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});
// useHead({
// title: `宿`,
// meta: [{ name: website.value.keywords, content: website.value.comments }]
// });
}
reload()

8
nuxt.config.ts

@ -33,12 +33,12 @@ export default defineNuxtConfig({
public: {
// 开发环境配置
// tenantId: '5',
// apiBase: 'http://127.0.0.1:9090/api',
// apiServer: 'http://127.0.0.1:9090/api',
apiBase: 'http://127.0.0.1:9090/api',
apiServer: 'http://127.0.0.1:9090/api',
// 生产环境
apiBase: 'https://server.gxwebsoft.com/api',
apiServer: 'https://server.gxwebsoft.com/api',
// apiBase: 'https://server.gxwebsoft.com/api',
// apiServer: 'https://server.gxwebsoft.com/api',
globalTitle: '网宿软件',
domain: 'websoft.top'
},

8
pages/article/[categoryId].vue

@ -1,5 +1,5 @@
<template>
<PageBanner />
<PageBanner :title="`${form?.title}`" :desc="`${form?.comments}`" />
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
@ -10,8 +10,8 @@ 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 PageBanner from './components/PageBanner.vue';
import CardList from './components/CardList.vue';
import type {ArticleCategory} from "~/api/cms/category/model";
const route = useRoute();
@ -25,7 +25,7 @@ const disabled = ref<boolean>(false);
const activeName = ref(undefined)
//
const form = ref<Navigation>();
const form = ref<ArticleCategory>();
const website = useWebsite();
//
@ -74,7 +74,7 @@ if(form.value?.layout){
}
useHead({
title: `社区 - ${website.value.websiteName}`,
title: `文章列表 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}

104
pages/article/components/PageBanner.vue

@ -1,104 +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">
<Breadcrumb :data="form" />
<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">
<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="mt-4 text-lg text-gray-500 dark:text-gray-400">
{{ form.description }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {useConfigInfo} from "~/composables/configState";
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {Article} from "~/api/cms/article/model";
import type {ArticleCategory} from "~/api/cms/category/model";
import useFormData from "~/utils/use-form-data";
import type {Navigation} from "~/api/cms/navigation/model";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult} from "~/api";
const route = useRoute();
const token = useToken();
const sysDomain = useSysDomain();
withDefaults(
defineProps<{
form?: Article;
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();
//
const { form, assignFields } = useFormData<ArticleCategory>({
categoryId: undefined,
categoryName: undefined,
parentId: undefined,
parentName: undefined,
parentPath: undefined,
parentStatus: undefined,
categoryPath: undefined,
currentTitle: undefined,
style: undefined,
title: undefined,
description: undefined
});
//
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<Navigation>>(`/cms/cms-article-category/${route.params.categoryId}`)
if(response.value?.data){
assignFields(response.value.data)
form.categoryName = response.value.data.title
form.description = response.value.data.title
}
}
reload();
</script>

103
pages/ask/[userId].vue

@ -0,0 +1,103 @@
<template>
<PageBanner :layout="layout" title="社区" desc="分享研发成果 交流技术经验" />
<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="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,
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;
}
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>

2
pages/ask/index.vue

@ -1,5 +1,5 @@
<template>
<PageBanner :layout="layout" />
<PageBanner :layout="layout" title="社区" desc="分享研发成果 交流技术经验" />
<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="happy"></el-tab-pane>
<el-tab-pane label="企业官网" name="website"></el-tab-pane>

87
pages/case/components/PageBanner.vue

@ -1,87 +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>
<el-space class="mt-4">
<el-button
v-if="layout.buyUrl"
:icon="ElIconBottom"
size="large"
@click="openSpmUrl(`http://git.gxwebsoft.com/gxwebsoft/websoft-cms.git`, {},0,true)"
>
下载模版
</el-button>
<el-button
:icon="ElIconMemo"
size="large"
v-if="layout.docUrl"
@click="openSpmUrl(`/docs`)"
>
帮助文档
</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>

5
pages/case/index.vue

@ -1,5 +1,5 @@
<template>
<PageBanner :layout="layout" />
<PageBanner :layout="layout" title="案例" desc="客户案例推荐,或许更快找到灵感" />
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
@ -9,7 +9,6 @@ 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 PageBanner from "./components/PageBanner.vue";
import type {App} from "~/api/oa/app/model";
const route = useRoute();
@ -75,7 +74,7 @@ if(form.value?.layout){
}
useHead({
title: `产品 - ${website.value.websiteName}`,
title: `案例 - ${website.value.websiteName}`,
bodyAttrs: {
class: "page-container",
}

48
pages/category/components/CardList.vue

@ -0,0 +1,48 @@
<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">
<p 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>
</p>
<p class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20" />
<span>{{ item.nickname }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</p>
</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>

91
pages/category/index.vue

@ -0,0 +1,91 @@
<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>

35
pages/components/ArticleList.vue

@ -1,11 +1,11 @@
<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>
@ -13,14 +13,15 @@
<el-row :gutter="24" class="flex">
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" :xs="24" 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" />
<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">
<p 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>
</p>
<p class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20" />
<el-avatar :src="item.avatar" :size="20"/>
<span>{{ item.nickname }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</p>
</div>
@ -41,14 +42,18 @@ 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 route = useRoute();
const props = withDefaults(
defineProps<{
list?: any[];
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{}
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
@ -67,7 +72,7 @@ const where = reactive<CompanyParam>({
});
const load = () => {
if(!props.disabled){
if (!props.disabled) {
page.value++;
reload();
}
@ -75,19 +80,21 @@ const load = () => {
//
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){
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 {
} else {
disabled.value = true;
}
if(response.value.data.count == 0){
if (response.value.data.count == 0) {
resultText.value = '暂无相关结果'
}
}

63
pages/components/CardList.vue

@ -1,20 +1,36 @@
<template>
<div class="md:w-screen-xl m-auto relative sm:flex" v-infinite-scroll="load">
<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="md:w-screen-xl m-auto relative md:flex md:p-0 p-4">
<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-col :span="6" :xs="24" 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.companyLogo" fit="fill" :lazy="true" class="w-full 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="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">
<p 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">{{ item.tenantName }}</span>
<p 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>
</p>
<p class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<span class="text-gray-500">{{ item.comments }} </span>
<p v-if="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>
</p>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl(`/case/${item.companyId}`,item,item.companyId)">查看详情</el-button>
<el-button v-if="item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买</el-button>
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl('/item', item,item.goodsId,true)">
查看详情
</el-button>
<el-button type="primary" v-if="item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买
</el-button>
<el-button v-else class="w-full" :icon="ElIconShoppingCart">下载</el-button>
</div>
</div>
@ -31,22 +47,41 @@
<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<{
list?: any[];
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{}
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
(e: 'done'): void;
}>();
const load = () => {
if(!props.disabled){
emit('done')
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>

5
pages/components/PlugList.vue

@ -48,6 +48,7 @@ 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<{
@ -61,11 +62,11 @@ const emit = defineEmits<{
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Plug[]>([]);
const list = ref<Product[]>([]);
//
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Plug>>>('/system/plug/page', {
const {data: response} = await useServerRequest<ApiResult<PageResult<Product>>>('/system/plug/page', {
baseURL: runtimeConfig.public.apiServer, params: {
limit: 8
}

23
pages/components/ProductList.vue

@ -1,11 +1,11 @@
<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">
拥抱开源坚守品质致力于打造安全稳定高可用的WEB应用
{{ comments }}
</p>
</div>
</div>
@ -19,14 +19,16 @@
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">
<p 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.goodsName }}</div>
<div class="flex-1 text-xl cursor-pointer">{{ item.title }}</div>
<div class="text-red-500">{{ item.price }}</div>
</p>
<p v-if="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>
</p>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl('/item', item,item.goodsId,true)">查看详情</el-button>
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl('/item', item,item.goodsId,true)">
查看详情
</el-button>
<el-button type="primary" v-if="item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买
</el-button>
<el-button v-else class="w-full" :icon="ElIconShoppingCart">下载</el-button>
@ -47,13 +49,18 @@ import {openSpmUrl} from "~/utils/common";
import dayjs from "dayjs";
import {useServerRequest} from "~/composables/useServerRequest";
import type {ApiResult, PageResult} from "~/api";
import type {Goods} from "~/api/shop/goods/model";
import type {Product} from "~/api/oa/product/model";
const props = withDefaults(
defineProps<{
disabled?: boolean;
title?: string;
comments?: string;
}>(),
{}
{
title: '卡片标题',
comments: '卡片描述'
}
);
const emit = defineEmits<{
@ -61,11 +68,11 @@ const emit = defineEmits<{
}>();
const runtimeConfig = useRuntimeConfig();
const list = ref<Goods[]>([]);
const list = ref<Product[]>([]);
//
const reload = async () => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Goods>>>('/shop/shop-goods/page', {
const {data: response} = await useServerRequest<ApiResult<PageResult<Product>>>('/cms/cms-product/page', {
baseURL: runtimeConfig.public.apiServer, params: {
limit: 8
}

64
pages/detail/components/LikeArticle.vue

@ -0,0 +1,64 @@
<template>
<el-affix 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">
<p 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>
</p>
<p class="flex items-center gap-1.5 py-2 text-gray-500">
<el-avatar :src="item.avatar" :size="20"/>
<span>{{ item.nickname }} · {{ dayjs(item.createTime).format('MM-DD hh:mm') }}</span>
</p>
</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>

4
pages/detail/components/PageBanner.vue

@ -21,7 +21,7 @@
</svg>
<div class="md:w-screen-xl m-auto p-1">
<Breadcrumb :data="form" />
<div class="md:py-8 sm:py-16 px-4 py-8" _path="/templates" _dir="" _draft="false" _partial="false" _locale=""
<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">
@ -32,7 +32,7 @@
</h1>
<div class="mt-4 text-gray-500 dark:text-gray-400">
<span>{{ form.nickname }}</span> · {{ form.createTime }}
<span class="cursor-pointer text-gray-500" @click="navigateTo(`/ask/${form.userId}`)">{{ form.nickname }}</span> · {{ form.createTime }}
</div>
<!-- <el-space class="mt-4">-->

41
pages/detail/index.vue

@ -1,30 +1,27 @@
<!-- 文章详情 -->
<template>
<PageBanner :form="form" />
<PageBanner :form="form"/>
<div class="page-main md:w-screen-xl m-auto">
<div class="page-main md:w-screen-xl m-auto" ref="container">
<el-row :gutter="24">
<el-col :span="18" :xs="24" class="min-w-xs">
<div class="p-6 leading-7 bg-white md:rounded-lg text-lg line-height-loose text-gray-600 content" v-html="form?.content">
<div class="p-6 leading-7 bg-white md:rounded-lg text-lg line-height-loose text-gray-600 content"
v-html="form?.content">
</div>
<div class="page md:gap-xl gap-xs m-auto my-[30px] md:p-0 p-4 flex justify-between">
<div v-if="previousArticle" class="bg-white rounded-lg p-4 cursor-pointer hover:shadow w-[50%]" @click="openSpmUrl(`/detail`,previousArticle,previousArticle?.articleId)">
<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 rounded-lg p-4 cursor-pointer hover:shadow w-[50%]" @click="openSpmUrl(`/detail`,nextArticle,nextArticle?.articleId)">
<span>下一篇{{ nextArticle?.title}}</span>
<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 :span="6" :xs="24">
<div class="right-box md:p-0 p-4">
<div class="p-4 leading-7 bg-white rounded-lg">
点击排行
</div>
<div class="p-4 leading-7 bg-white rounded-lg mt-4">
推荐文章
</div>
</div>
<!-- 你可能感兴趣 -->
<LikeArticle v-if="form" :form="form" />
</el-col>
</el-row>
</div>
@ -40,6 +37,7 @@ 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();
@ -50,7 +48,7 @@ const nextArticle = ref<Article>();
//
const { form, assignFields } = useFormData<Article>({
const {form, assignFields} = useFormData<Article>({
// id
articleId: undefined,
//
@ -65,6 +63,7 @@ const { form, assignFields } = useFormData<Article>({
categoryName: undefined,
parentId: undefined,
parentName: undefined,
parentPath: undefined,
//
image: undefined,
//
@ -111,19 +110,21 @@ const { form, assignFields } = useFormData<Article>({
const reload = async () => {
// spm()
const { data: nav } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/' + getIdBySpm(5))
const {data: nav} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/' + getIdBySpm(5))
if (nav.value?.data) {
assignFields(nav.value.data)
form.parentPath = getSpmUrl(`/category`,form,form.articleId);
console.log(form.parentPath)
}
//
const { data: previous } = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/getPrevious/' + getIdBySpm(5))
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))
const {data: next} = await useServerRequest<ApiResult<Navigation>>('/cms/cms-article/getNext/' + getIdBySpm(5))
if (next.value?.data) {
nextArticle.value = next.value.data
}
@ -142,11 +143,11 @@ const reload = async () => {
watch(
() => route.path,
(path) => {
console.log(path,'=>Path')
console.log(path, '=>Path')
reload();
},
{ immediate: true }
{immediate: true}
);
</script>

3
pages/developer/index.vue

@ -1,5 +1,5 @@
<template>
<PageBanner :layout="layout" />
<PageBanner :layout="layout" title="入驻成为开发者" />
<div class="md:w-screen-xl m-auto relative sm:flex bg-white rounded-lg py-4 ">
<el-tabs class="px-7 bg-white">
<el-tab-pane label="个人开发者认证">
@ -92,7 +92,6 @@ 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>();

2
pages/docs/index.vue

@ -1,5 +1,5 @@
<template>
<PageBanner :layout="layout" />
<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>

10
pages/index.vue

@ -1,12 +1,12 @@
<template>
<Flash />
<Flash/>
<ProductList/>
<ProductList title="产品服务" comments="拥抱开源、坚守品质;致力于打造安全稳定高可用的WEB应用!"/>
<PlugList/>
<ProductList title="插件市场" comments="安装插件,几秒钟内即可启动并运行"/>
<ArticleList/>
<ArticleList title="开发者社区" comments="分享研发成果 交流技术经验"/>
</template>
<script setup lang="ts">
@ -16,10 +16,8 @@ import {useConfigInfo, useForm, useToken, useWebsite} from "~/composables/config
import type {BreadcrumbItem} from "~/types/global";
import type {Navigation} from "~/api/cms/navigation/model";
import {getIdBySpm} from "~/utils/common";
import {navigateTo} from "#imports";
import Flash from './components/Flash.vue';
import ProductList from "./components/ProductList.vue";
import PlugList from './components/PlugList.vue';
import ArticleList from './components/ArticleList.vue';
//

89
pages/item/components/PageBanner.vue

@ -27,44 +27,46 @@
<div class="gap-8 sm:gap-y-16 lg:items-center" v-if="form">
<div class="w-full">
<div class="flex">
<el-avatar :src="form.appIcon" shape="square" class="mr-2" />
<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-avatar :src="form.image" shape="square" :size="180" class="rounded-avatar shadow-sm hover:shadow mr-4" />
<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 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" />
<div class="btn">
<el-space class="mt-4">
<el-button
:icon="ElIconView"
size="large"
@click="openSpmUrl(form.demoUrl)"
>
演示地址
</el-button>
<el-button
:icon="ElIconBottom"
size="large"
@click="openSpmUrl(form.buyUrl)"
>
下载源码
</el-button>
<el-button
:icon="ElIconHomeFilled"
size="large"
@click="navigateTo(`/ask/${form.userId}`)"
>
个人主页
</el-button>
</el-space>
</div>
</div>
</div>
<div class="mt-4 text-gray-500 dark:text-gray-400">
<span>{{ form.nickname }}</span> · {{ form.tenantId }}
</div>
<!-- <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>
@ -74,22 +76,27 @@
<script setup lang="ts">
import Breadcrumb from "~/components/Breadcrumb.vue";
import type {App} from "~/api/oa/app/model";
withDefaults(
defineProps<{
title?: string;
desc?: string;
buyUrl?: string;
form?: App;
form?: any;
value?: 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'
buyUrl: 'https://github.com/websoft9/ansible-templates',
value: 4.2
}
);
</script>
<style scoped lang="less">
.rounded-avatar{
border-radius: 30px;
}
</style>

254
pages/item/index.vue

@ -5,34 +5,58 @@
<div class="page-main md:w-screen-xl m-auto">
<el-row :gutter="24">
<el-col :span="18">
<div class="leading-7 bg-white rounded-lg text-lg line-height-loose text-gray-600 content">
<el-tabs
v-model="activeName"
type="card"
class="demo-tabs"
@tab-click="reload"
>
<el-tab-pane label="基本信息" name="base">基本信息</el-tab-pane>
<el-tab-pane label="成员管理" name="users">成员管理</el-tab-pane>
<el-tab-pane label="项目参数" name="param">项目参数</el-tab-pane>
<el-tab-pane label="账号密码" name="domain">账号密码</el-tab-pane>
<el-tab-pane label="开发文档" name="profile">协同文档</el-tab-pane>
<el-tab-pane label="项目附件" name="annex">项目附件</el-tab-pane>
<el-tab-pane label="项目工单" name="task">项目工单</el-tab-pane>
<el-tab-pane label="应用截图" name="photo">应用截图</el-tab-pane>
<el-tab-pane label="项目介绍" name="about">
<div class="p-6 content" v-html="form?.content"></div>
</el-tab-pane>
</el-tabs>
</div>
<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>
<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>
<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>
<p v-html="form.content"></p>
</el-card>
<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-card>
<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-card>
</el-col>
<el-col :span="6">
<div class="p-4 leading-7 bg-white rounded-lg">
点击排行
</div>
<div class="p-4 leading-7 bg-white rounded-lg mt-4">
推荐文章
</div>
<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-card>
</el-col>
</el-row>
</div>
@ -44,152 +68,74 @@ import {useServerRequest} from "~/composables/useServerRequest";
import {useWebsite} from "~/composables/configState";
import type {BreadcrumbItem} from "~/types/global";
import {getIdBySpm} from "~/utils/common";
import type {Article} from "~/api/cms/article/model";
import useFormData from "~/utils/use-form-data";
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
import PageBanner from './components/PageBanner.vue';
import type {App} from "~/api/oa/app/model";
//
const route = useRoute();
const website = useWebsite();
const breadcrumb = ref<BreadcrumbItem>();
const previousArticle = ref<Article>();
const nextArticle = ref<Article>();
const activeName = ref();
const url =
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'
const srcList = ref<any[]>([]);
//
const { form, assignFields } = useFormData<App>({
// id
appId: undefined,
//
appSecret: '',
enName: '',
//
appName: '',
// id, 0
const { form, assignFields } = useFormData<CmsProduct>({
productId: undefined,
type: undefined,
code: undefined,
title: undefined,
image: undefined,
content: undefined,
parentId: undefined,
//
appCode: '',
//
appIcon: '',
appQrcode: '',
//
images: '',
//
appType: '',
appTypeMultiple: undefined,
//
menuType: undefined,
//
appUrl: '',
//
adminUrl: undefined,
//
downUrl: undefined,
serverUrl: undefined,
callbackUrl: undefined,
gitUrl: undefined,
docsUrl: undefined,
prototypeUrl: undefined,
ipAddress: undefined,
fileUrl: undefined,
//
packageName: '',
//
clicks: '',
//
installs: '',
//
content: '',
// ()
developer: '',
director: '',
projectDirector: '',
salesman: '',
//
price: '',
//
score: '',
//
star: '',
//
component: '',
//
path: '',
//
authority: '',
//
target: '',
// , 0, 1()
hide: undefined,
// path
active: '',
//
meta: '',
//
edition: '',
//
version: '',
//
isUse: undefined,
//
sortNumber: undefined,
//
comments: '',
tenantName: '',
companyName: '',
//
tenantCode: '',
// id
tenantId: undefined,
//
createTime: '',
appStatus: '开发中',
//
status: undefined,
//
userId: '',
//
nickname: '',
//
children: [],
// , 0, 1
checked: false,
//
key: undefined,
//
value: undefined,
//
parentIds: [],
//
openType: undefined,
//
search: undefined,
//
users: [],
//
requirement: '',
file1: '[]',
file2: '[]',
file3: '[]',
showCase: undefined,
showIndex: undefined,
categoryId: undefined,
specs: undefined,
position: undefined,
unitName: undefined,
price: undefined,
salePrice: undefined,
deductStockType: undefined,
files: undefined,
sales: undefined,
stock: undefined,
gainIntegral: undefined,
recommend: undefined,
categoryName: '',
title: ''
merchantId: undefined,
isShow: undefined,
status: undefined,
userId: undefined,
deleted: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
comments: '',
sortNumber: 100,
parentName: undefined,
parentPath: undefined,
categoryName: undefined,
rate: 0,
});
//
const reload = async () => {
// spm()
const { data: nav } = await useServerRequest<ApiResult<App>>('/oa/oa-app/' + getIdBySpm(5))
if (nav.value?.data) {
assignFields(nav.value.data)
form.nickname = nav.value.data?.companyName;
form.title = nav.value.data?.appName;
form.categoryName = '应用详情';
const { data: item } = await useServerRequest<ApiResult<CmsProduct>>('/cms/cms-product/' + getIdBySpm(5))
if (item.value?.data) {
assignFields(item.value.data)
form.title = item.value?.data?.title;
form.parentName = '产品';
form.parentPath = '/product';
form.categoryName = '产品详情';
form.rate = item.value.data.tenantId;
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;
}
// seo

3
pages/market/components/PageBanner.vue

@ -53,8 +53,7 @@
</el-button>
<el-input
v-model="where.keywords"
class="w-full"
style="width: 360px"
class="sm:w-[360px] w-full"
size="large"
placeholder="搜索"
:prefix-icon="Search"

6
pages/market/index.vue

@ -23,13 +23,13 @@ import type {Navigation} from "~/api/cms/navigation/model";
import type {CompanyParam} from "~/api/system/company/model";
import CardList from './components/CardList.vue';
import PageBanner from './components/PageBanner.vue';
import type {Plug} from "~/api/system/plug/model";
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
const route = useRoute();
//
const runtimeConfig = useRuntimeConfig();
const list = ref<Plug[]>([]);
const list = ref<CmsProduct[]>([]);
const page = ref<number>(1);
const resultText = ref('');
const layout = ref<any>();
@ -59,7 +59,7 @@ const onSearch = () => {
//
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Plug>>>('/system/plug/page',{baseURL: runtimeConfig.public.apiServer, params: {
const {data: response} = await useServerRequest<ApiResult<PageResult<CmsProduct>>>('/cms/cms-product/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,limit: 8, keywords: where.keywords
}})
if(response.value?.data){

13
pages/product/components/CardList.vue

@ -4,18 +4,18 @@
<template v-for="(item,index) in list" :key="index">
<el-col :span="6" :xs="24" 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="fill" :lazy="true" class="w-full md:h-[150px] h-[199px] 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">
<p 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.goodsName }}</div>
<div class="flex-1 text-xl cursor-pointer">{{ item.title }}</div>
<div class="text-red-500">{{ item.price }} </div>
</p>
<p v-if="item.price > 0" class="flex items-center gap-1.5 py-2 text-gray-500 justify-between">
<p 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>
</p>
<div class="button-group flex justify-center mt-3">
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl(`/item`, item, item.goodsId,true)">查看详情</el-button>
<el-button type="primary" v-if="item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买</el-button>
<el-button class="w-full" :icon="ElIconView" @click="openSpmUrl(`/item`, item, item.productId,true)">查看详情</el-button>
<el-button type="primary" v-if="item.price && item.price > 0" class="w-full" :icon="ElIconShoppingCart">购买</el-button>
<el-button v-else class="w-full" :icon="ElIconShoppingCart">下载</el-button>
</div>
</div>
@ -32,10 +32,11 @@
<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?: any[];
list?: CmsProduct[];
disabled?: boolean;
}>(),
{}

15
pages/product/index.vue

@ -1,5 +1,5 @@
<template>
<PageBanner :layout="layout" />
<PageBanner :layout="layout" :title="`产品`" />
<CardList :list="list" :disabled="disabled" @done="onSearch" />
</template>
<script setup lang="ts">
@ -8,21 +8,18 @@ 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 {Tenant} from "~/api/system/tenant/model";
import type {Article} from "~/api/cms/article/model";
import CardList from './components/CardList.vue';
import type {Goods} from "~/api/shop/goods/model";
import type {CmsProduct} from "~/api/cms/cmsProduct/model";
const route = useRoute();
//
const runtimeConfig = useRuntimeConfig();
const list = ref<Goods[]>([]);
const list = ref<CmsProduct[]>([]);
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>();
@ -33,10 +30,6 @@ const where = reactive<CompanyParam>({
keywords: ''
});
const handleClick = (e:any) => {
console.log(e.index)
}
const onSearch = () => {
if(!disabled.value){
page.value++;
@ -46,7 +39,7 @@ const onSearch = () => {
//
const reload = async (path: string) => {
const {data: response} = await useServerRequest<ApiResult<PageResult<Goods>>>('/shop/shop-goods/page',{baseURL: runtimeConfig.public.apiServer, params: {
const {data: response} = await useServerRequest<ApiResult<PageResult<CmsProduct>>>('/cms/cms-product/page',{baseURL: runtimeConfig.public.apiServer, params: {
page: page.value,
limit: 8,
keywords: where.keywords

1
pages/user/auth.vue

@ -1,4 +1,5 @@
<template>
<PageBanner title="实名认证" desc="Authentication" />
<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">
<!-- 用户菜单 -->

7
pages/user/index.vue

@ -1,9 +1,10 @@
<template>
<PageBanner title="账户中心" desc="Account Center" />
<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">
<!-- 用户菜单 -->
<UserMenu :activeIndex="activeIndex" @done="onDone" />
<div class="flash ml-8 bg-white rounded-lg p-3 w-full">
<UserMenu :activeIndex="activeIndex" @done="onDone" class="sm:flex hidden" />
<div class="flash md:ml-8 ml-0 bg-white rounded-lg p-3 w-full">
<div class="title text-xl text-gray-700 px-4 py-2 mb-4 font-500">账号信息</div>
<el-form :model="form" label-width="auto" size="large" label-position="top" class="sm:w-screen-md w-full sm:px-4 sm:py-2">
<el-form-item label="手机号码">
@ -69,7 +70,7 @@ const { form, assignFields } = useFormData<User>({
});
useHead({
title: `用户中心 - ${config.value?.siteName}`,
title: `用户中心`,
meta: [{ name: website.value.keywords, content: website.value.comments }]
});

1
pages/user/order.vue

@ -1,4 +1,5 @@
<template>
<PageBanner title="订单列表" desc="Order List" />
<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">
<!-- 用户菜单 -->

1
pages/user/password.vue

@ -1,4 +1,5 @@
<template>
<PageBanner title="修改密码" desc="Change Password" />
<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">
<!-- 用户菜单 -->

23
utils/common.ts

@ -142,12 +142,15 @@ export function openSpmUrl(path: string, d?: any, id = 0, isOpen?: boolean, isTo
// TODO 新窗口打开
if(isOpen){
window.open(`${path}${spm.value}`);
window.open(`${path}${spm.value}`,'_blank');
return;
}else {
window.open(`${path}${spm.value}`, '_top');
return;
}
// 跳转页面
navigateTo(`${path}${spm.value}`)
// navigateTo(`${path}${spm.value}`)
}
// 单点登录控制台
@ -155,3 +158,19 @@ export function loginAdminByToken(): void {
const sysDomain = useSysDomain();
openSpmUrl(`https://${sysDomain.value}/token-login`,undefined, 0, true,true)
}
export function getSpmUrl(path: string, d?: any, id = 0): string {
const config = useWebsite();
const spm = ref<string>('');
const model = ref<string>('c');
const tid = config.value.tenantId || 0;
const mid = config.value.loginUser?.merchantId || 0;
const pid = d?.parentId || 0;
const cid = d?.categoryId || 0;
const uid = config.value.loginUser?.userId || 0;
const timestamp = ref(Date.now() / 1000);
let token = uuidv4();
// TODO 封装spm
spm.value = `?spm=${model.value}.${tid}.${mid}.${pid}.${cid}.${id}.${uid}.${timestamp.value}&token=${token}`;
return `${path}${spm.value}`
}

Loading…
Cancel
Save