新增:导航栏目设计拆分简单和高级展示方式

This commit is contained in:
2024-09-03 01:13:18 +08:00
parent bf43bdbab4
commit aa85c133e3
24 changed files with 1121 additions and 353 deletions

View File

@@ -1,11 +1,11 @@
VITE_APP_NAME=后台管理系统 VITE_APP_NAME=后台管理系统
VITE_SOCKET_URL=wss://server.gxwebsoft.com VITE_SOCKET_URL=wss://server.gxwebsoft.com
#VITE_SERVER_URL=https://server.gxwebsoft.com/api VITE_SERVER_URL=https://server.gxwebsoft.com/api
VITE_THINK_URL=https://gxtyzx-api.websoft.top/api VITE_THINK_URL=https://gxtyzx-api.websoft.top/api
#VITE_API_URL=https://modules.gxwebsoft.com/api #VITE_API_URL=https://modules.gxwebsoft.com/api
VITE_SERVER_URL=http://127.0.0.1:9090/api #VITE_SERVER_URL=http://127.0.0.1:9090/api
VITE_API_URL=http://127.0.0.1:9001/api VITE_API_URL=http://127.0.0.1:9001/api
#VITE_THINK_URL=http://127.0.0.1:9099/api #VITE_THINK_URL=http://127.0.0.1:9099/api
#/booking/bookingItem #/booking/bookingItem

View File

@@ -42,7 +42,13 @@ export interface Design {
backgroundColor?: string; backgroundColor?: string;
// 关联网站导航ID // 关联网站导航ID
navigationId?: number; navigationId?: number;
showLayout?: boolean;
btn?: any[];
showBanner?: boolean;
buyUrl?: string; buyUrl?: string;
demoUrl?: string;
account?: string;
docUrl?: string;
} }
/** /**

View File

@@ -50,10 +50,11 @@
<!-- logo 图标 --> <!-- logo 图标 -->
<template #logo> <template #logo>
<AImage <AImage
v-if="!company?.shortName" v-if="logoPath"
:preview="false" :preview="false"
:style="{ width: '100px', height: '30px' }" :style="{ width: '100px', height: '30px' }"
:src="logoPath" :src="logoPath"
@click="openUrl(`/system/profile`)"
alt="logo" alt="logo"
/> />
</template> </template>
@@ -117,6 +118,7 @@
setHomeComponents setHomeComponents
} from '@/utils/page-tab-util'; } from '@/utils/page-tab-util';
import type { TabCtxMenuOption } from 'ele-admin-pro/es/ele-pro-layout/types'; import type { TabCtxMenuOption } from 'ele-admin-pro/es/ele-pro-layout/types';
import { openUrl } from '@/utils/common';
const { push } = useRouter(); const { push } = useRouter();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
@@ -126,7 +128,9 @@
// 租户信息 // 租户信息
const { company } = storeToRefs(tenantStore); const { company } = storeToRefs(tenantStore);
// 网站名称 // 网站名称
const projectName = company.value?.shortName; const projectName = !company.value?.companyLogo
? company.value?.shortName
: '';
// 网站LOGO // 网站LOGO
const logoPath = const logoPath =
company.value?.companyLogo || 'https://www.gxwebsoft.com/ws-logo.svg'; company.value?.companyLogo || 'https://www.gxwebsoft.com/ws-logo.svg';

View File

@@ -118,6 +118,10 @@ export function getSiteDomain(): string {
} }
}); });
} }
// 开发环境调试域名
if (localStorage.getItem('DevDomain')) {
return `${localStorage.getItem('DevDomain')}`;
}
return `${localStorage.getItem('Domain')}`; return `${localStorage.getItem('Domain')}`;
} }

View File

@@ -21,7 +21,7 @@
> >
<a-tabs type="card" v-model:active-key="active" @change="onChange"> <a-tabs type="card" v-model:active-key="active" @change="onChange">
<a-tab-pane tab="基本信息" key="base"> <a-tab-pane tab="基本信息" key="base">
<a-form-item label="文章标题" name="categoryId"> <a-form-item label="选择栏目" name="categoryId">
<a-tree-select <a-tree-select
allow-clear allow-clear
:tree-data="navigationList" :tree-data="navigationList"
@@ -42,31 +42,6 @@
v-model:value="form.title" v-model:value="form.title"
/> />
</a-form-item> </a-form-item>
<!-- <a-form-item label="单位名称" name="unitName">-->
<!-- <a-input-->
<!-- allow-clear-->
<!-- style="width: 558px"-->
<!-- placeholder="单位名称,如(个)"-->
<!-- v-model:value="form.unitName"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="文章价格" name="price">-->
<!-- <a-input-number-->
<!-- :placeholder="`文章价格`"-->
<!-- style="width: 240px"-->
<!-- v-model:value="form.price"-->
<!-- />-->
<!-- <div class="ele-text-placeholder">文章的实际购买金额最低0.01</div>-->
<!-- </a-form-item>-->
<!-- <a-form-item label="市场价" name="salePrice">-->
<!-- <a-input-number-->
<!-- :placeholder="`市场价`"-->
<!-- style="width: 240px"-->
<!-- v-model:value="form.salePrice"-->
<!-- />-->
<!-- <div class="ele-text-placeholder">划线价仅用于文章页展示</div>-->
<!-- </a-form-item>-->
<a-form-item label="文章图片" name="image"> <a-form-item label="文章图片" name="image">
<SelectFile <SelectFile
:placeholder="`请选择图片`" :placeholder="`请选择图片`"
@@ -75,9 +50,6 @@
@done="chooseImage" @done="chooseImage"
@del="onDeleteItem" @del="onDeleteItem"
/> />
<!-- <div class="ele-text-placeholder"-->
<!-- >支持上传视频mp4格式视频时长不超过60秒视频大小不超过200M</div-->
<!-- >-->
</a-form-item> </a-form-item>
<a-form-item label="上传附件" name="files"> <a-form-item label="上传附件" name="files">
<SelectFile <SelectFile
@@ -161,7 +133,6 @@
import TinymceEditor from '@/components/TinymceEditor/index.vue'; import TinymceEditor from '@/components/TinymceEditor/index.vue';
import { uploadFile, uploadOss } from "@/api/system/file"; import { uploadFile, uploadOss } from "@/api/system/file";
import { SpecValue } from "@/api/shop/specValue/model"; import { SpecValue } from "@/api/shop/specValue/model";
import { Spec } from "@/api/shop/spec/model";
import { ArticleCategory } from "@/api/cms/category/model"; import { ArticleCategory } from "@/api/cms/category/model";
import { listArticleCategory } from "@/api/cms/category"; import { listArticleCategory } from "@/api/cms/category";
import { Navigation } from "@/api/cms/navigation/model"; import { Navigation } from "@/api/cms/navigation/model";
@@ -181,6 +152,7 @@
data?: Article | null; data?: Article | null;
// 商户ID // 商户ID
merchantId?: number; merchantId?: number;
categoryId?: number;
// 栏目数据 // 栏目数据
navigationList?: Navigation[]; navigationList?: Navigation[];
}>(); }>();
@@ -204,8 +176,6 @@
const spec = ref<SpecValue[]>([]); const spec = ref<SpecValue[]>([]);
const showSpecForm = ref(false); const showSpecForm = ref(false);
const name = ref();
const value = ref();
const files = ref<ItemType[]>([]); const files = ref<ItemType[]>([]);
const category = ref<string[]>([]); const category = ref<string[]>([]);
const takeaway = ref<ArticleCategory[]>([]); const takeaway = ref<ArticleCategory[]>([]);
@@ -264,14 +234,14 @@
trigger: 'blur' trigger: 'blur'
} }
], ],
image: [ // image: [
{ // {
required: true, // required: true,
message: '请上传图片', // message: '请上传图片',
type: 'string', // type: 'string',
trigger: 'blur' // trigger: 'blur'
} // }
], // ],
// files: [ // files: [
// { // {
// required: true, // required: true,
@@ -316,27 +286,6 @@
], ],
}); });
const onType = (index: number) => {
form.type = index;
};
// /* 搜索 */
// const chooseMerchantId = (item: Merchant) => {
// form.merchantName = item.merchantName;
// form.merchantId = item.merchantId;
// };
//
// const chooseArticleCategory = (item: ArticleCategory,value: any) => {
// form.categoryId = value[1].value;
// form.categoryParent = value[0].label;
// form.categoryChildren = value[1].label;
// }
// const chooseTakeawayCategory = (item: ArticleCategory, value: any) => {
// form.categoryParent = '店铺分类';
// form.categoryChildren = value[0].label;
// form.categoryId = item[0];
// }
const chooseImage = (data: FileRecord) => { const chooseImage = (data: FileRecord) => {
images.value.push({ images.value.push({
uid: data.id, uid: data.id,
@@ -360,14 +309,6 @@
form.image = ''; form.image = '';
}; };
const onClose = (index) => {
spec.value.splice(index, 1);
};
const openSpecForm = () => {
showSpecForm.value = !showSpecForm.value;
};
const onComments = () => { const onComments = () => {
if (form.comments == undefined) { if (form.comments == undefined) {
form.comments = htmlToText(content.value) form.comments = htmlToText(content.value)
@@ -375,33 +316,6 @@
} }
} }
const onSpec = (row: Spec) => {
// form.specName = row.specName;
if(row.specValue){
spec.value = JSON.parse(row?.specValue);
}
}
// 新增规格
const addSpecValue = () => {
if (!name.value || !value.value) {
message.error(`请输入规格和规格值`);
return false;
}
const findIndex = spec.value.findIndex((d) => d.value == name.value);
if (findIndex == 0) {
message.error(`${name.value}已存在)`);
return false;
}
spec.value.push({
value: name.value,
detail: [value.value]
});
name.value = '';
value.value = '';
openSpecForm();
};
const chooseFile = (data: FileRecord) => { const chooseFile = (data: FileRecord) => {
files.value.push({ files.value.push({
uid: data.id, uid: data.id,
@@ -531,6 +445,9 @@
watch( watch(
() => props.visible, () => props.visible,
(visible) => { (visible) => {
if(props.categoryId){
form.categoryId = props.categoryId;
}
if (visible) { if (visible) {
images.value = []; images.value = [];
category.value = []; category.value = [];

View File

@@ -63,6 +63,7 @@
selection?: []; selection?: [];
merchantId?: number; merchantId?: number;
navigationList?: Navigation[]; navigationList?: Navigation[];
categoryId?: number;
model?: string; model?: string;
}>(), }>(),
{} {}
@@ -99,7 +100,6 @@
const handleSearch = (e) => { const handleSearch = (e) => {
const text = e.target.value; const text = e.target.value;
resetFields();
if (text === '已发布') { if (text === '已发布') {
where.status = 0; where.status = 0;
} }
@@ -113,6 +113,7 @@
}; };
const reload = () => { const reload = () => {
console.log(where);
getCount(where).then((data: any) => { getCount(where).then((data: any) => {
articleCount.value = data; articleCount.value = data;
}); });
@@ -141,14 +142,18 @@
watch( watch(
() => props.merchantId, () => props.merchantId,
(id) => { () => {
if (Number(id) > 0) { if (props.categoryId) {
where.merchantId = id; where.categoryId = props.categoryId;
reload();
} else {
where.merchantId = undefined;
reload();
} }
reload();
// if (Number(id) > 0) {
// where.merchantId = id;
// reload();
// } else {
// where.merchantId = undefined;
// reload();
// }
}, },
{ immediate: true } { immediate: true }
); );

View File

@@ -18,6 +18,7 @@
:selection="selection" :selection="selection"
:navigationList="navigationList" :navigationList="navigationList"
:merchantId="merchantId" :merchantId="merchantId"
:categoryId="categoryId"
:model="model" :model="model"
@add="openEdit" @add="openEdit"
@remove="removeBatch" @remove="removeBatch"
@@ -26,7 +27,8 @@
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'title'"> <template v-if="column.key === 'title'">
<a <span
class="cursor-pointer"
@click=" @click="
openSpmUrl( openSpmUrl(
`/article/detail/${record.articleId}.html`, `/article/detail/${record.articleId}.html`,
@@ -34,7 +36,20 @@
record.articleId record.articleId
) )
" "
>{{ record.title }}</a >{{ record.title }}</span
>
</template>
<template v-if="column.key === 'categoryName'">
<span
class="cursor-pointer"
@click="
openSpmUrl(
`/article/${record.categoryId}`,
record,
record.categoryId
)
"
>{{ record.categoryName }}</span
> >
</template> </template>
<template v-if="column.key === 'type'"> <template v-if="column.key === 'type'">
@@ -75,6 +90,7 @@
<ArticleEdit <ArticleEdit
v-model:visible="showEdit" v-model:visible="showEdit"
:navigationList="navigationList" :navigationList="navigationList"
:categoryId="categoryId"
:data="current" :data="current"
@done="reload" @done="reload"
/> />
@@ -103,7 +119,6 @@
import router from '@/router'; import router from '@/router';
import { toTreeData } from 'ele-admin-pro'; import { toTreeData } from 'ele-admin-pro';
import { openSpmUrl } from '@/utils/common'; import { openSpmUrl } from '@/utils/common';
import { getSiteDomain } from '@/utils/domain';
import { listNavigation } from '@/api/cms/navigation'; import { listNavigation } from '@/api/cms/navigation';
import { Navigation } from '@/api/cms/navigation/model'; import { Navigation } from '@/api/cms/navigation/model';
@@ -124,10 +139,6 @@
const categoryId = ref<number>(); const categoryId = ref<number>();
// 当前模型 // 当前模型
const model = ref<number>(); const model = ref<number>();
// 网站域名
const domain = getSiteDomain();
// 随机数
const token = ref<any>('');
// 栏目数据 // 栏目数据
const navigationList = ref<Navigation[]>(); const navigationList = ref<Navigation[]>();
@@ -153,15 +164,22 @@
align: 'center', align: 'center',
width: 90 width: 90
}, },
{
title: '封面图',
dataIndex: 'image',
key: 'image',
width: 120,
align: 'center'
},
{ {
title: '文章标题', title: '文章标题',
dataIndex: 'title', dataIndex: 'title',
key: 'title' key: 'title'
}, },
{ {
title: '封面图', title: '栏目名称',
dataIndex: 'image', dataIndex: 'categoryName',
key: 'image', key: 'categoryName',
width: 120, width: 120,
align: 'center' align: 'center'
}, },
@@ -231,6 +249,9 @@
/* 搜索 */ /* 搜索 */
const reload = (where?: ArticleParam) => { const reload = (where?: ArticleParam) => {
if (where?.categoryId) {
categoryId.value = where.categoryId;
}
selection.value = []; selection.value = [];
tableRef?.value?.reload({ where: where }); tableRef?.value?.reload({ where: where });
}; };
@@ -317,7 +338,7 @@
data: res?.map((d) => { data: res?.map((d) => {
d.value = d.navigationId; d.value = d.navigationId;
d.label = d.title; d.label = d.title;
if (d.model == 'links' || d.model == 'product' || d.path == '/') { if (d.model != 'article') {
d.disabled = true; d.disabled = true;
} }
return d; return d;
@@ -331,8 +352,9 @@
watch( watch(
() => router.currentRoute.value.query, () => router.currentRoute.value.query,
(query) => { (query) => {
console.log(query);
if (query) { if (query) {
categoryId.value = Number(query.categoryId); categoryId.value = Number(query.id);
model.value = Number(query.type); model.value = Number(query.type);
reload(); reload();
} }
@@ -346,5 +368,3 @@
name: 'ArticleV2' name: 'ArticleV2'
}; };
</script> </script>
<style lang="less" scoped></style>

View File

@@ -5,7 +5,7 @@
:visible="visible" :visible="visible"
:maskClosable="false" :maskClosable="false"
:maxable="true" :maxable="true"
:title="isUpdate ? '编辑页面' : '添加页面'" :title="isUpdate ? '编辑内容' : '添加内容'"
:body-style="{ paddingBottom: '28px' }" :body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible" @update:visible="updateVisible"
@ok="save" @ok="save"
@@ -19,30 +19,6 @@
styleResponsive ? { md: 21, sm: 19, xs: 24 } : { flex: '1' } styleResponsive ? { md: 21, sm: 19, xs: 24 } : { flex: '1' }
" "
> >
<a-form-item label="页面名称" name="name">
<a-input
allow-clear
:maxlength="100"
placeholder="关于我们"
v-model:value="form.name"
/>
</a-form-item>
<a-form-item label="路由地址" name="path" :extra="form.path ? `${form.path}` : ''">
<a-input
allow-clear
:maxlength="100"
placeholder="/about"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="组件路径" name="component" :extra="form.component ? `@/views${form.component}` : ''">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入组件路径"
v-model:value="form.component"
/>
</a-form-item>
<a-form-item label="Banner" name="photo"> <a-form-item label="Banner" name="photo">
<SelectFile <SelectFile
:placeholder="`请选择图片`" :placeholder="`请选择图片`"
@@ -51,13 +27,81 @@
@done="chooseFile" @done="chooseFile"
@del="onDeleteItem" @del="onDeleteItem"
/> />
<!-- <ele-image-upload--> </a-form-item>
<!-- v-model:value="images"--> <a-form-item label="网站SEO">
<!-- :limit="1"--> <a-space direction="vertical"
<!-- :drag="true"--> class="w-full">
<!-- :upload-handler="uploadHandler"--> <a-input
<!-- @upload="onUpload"--> allow-clear
<!-- />--> :maxlength="100"
placeholder="Title"
v-model:value="form.name"
/>
<a-textarea
:rows="4"
:maxlength="200"
placeholder="Description"
v-model:value="form.description"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="Keywords"
v-model:value="form.keywords"
/>
</a-space>
</a-form-item>
<a-form-item label="按钮">
<a-space direction="vertical"
class="w-full">
<a-input
allow-clear
:maxlength="100"
placeholder="演示地址"
v-model:value="form.demoText"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="demoUrl"
v-model:value="form.demoUrl"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="立即购买"
v-model:value="form.buyText"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="buyUrl"
v-model:value="form.buyUrl"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="产品文档"
v-model:value="form.docText"
/>
<a-input
allow-clear
:maxlength="100"
placeholder="docUrl"
v-model:value="form.docUrl"
/>
</a-space>
</a-form-item>
<a-form-item label="页面样式">
<a-space direction="vertical"
class="w-full">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="Tailwind CSS风格"
v-model:value="form.style"
/>
</a-space>
</a-form-item> </a-form-item>
<a-form-item label="页面内容" name="content"> <a-form-item label="页面内容" name="content">
<!-- 编辑器 --> <!-- 编辑器 -->
@@ -71,15 +115,6 @@
/> />
</div> </div>
</a-form-item> </a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="状态" name="status"> <a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status"> <a-radio-group v-model:value="form.status">
<a-radio :value="0">开启</a-radio> <a-radio :value="0">开启</a-radio>
@@ -89,24 +124,20 @@
</a-form> </a-form>
</ele-modal> </ele-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from 'vue'; import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue'; import { Form, message } from 'ant-design-vue';
import {assignObject, uuid} from 'ele-admin-pro'; import { uuid } from 'ele-admin-pro';
import { addDesign, updateDesign } from '@/api/cms/design'; import { addDesign, updateDesign } from '@/api/cms/design';
import { Design } from '@/api/cms/design/model'; import { Design } from '@/api/cms/design/model';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { FormInstance, RuleObject } from 'ant-design-vue/es/form'; import { FormInstance } from 'ant-design-vue/es/form';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import {uploadFile, uploadOss} from '@/api/system/file'; import {uploadFile, uploadOss} from '@/api/system/file';
import TinymceEditor from "@/components/TinymceEditor/index.vue"; import TinymceEditor from "@/components/TinymceEditor/index.vue";
import useFormData from "@/utils/use-form-data"; import useFormData from "@/utils/use-form-data";
import type {Article} from "@/api/cms/article/model";
import {ArticleCategory} from "@/api/cms/category/model";
import {removeSiteInfoCache} from "@/api/cms/website"; import {removeSiteInfoCache} from "@/api/cms/website";
import success from "@/views/result/success/index.vue";
import {FileRecord} from "@/api/system/file/model"; import {FileRecord} from "@/api/system/file/model";
// 是否是修改 // 是否是修改
@@ -137,6 +168,8 @@
const images = ref<ItemType[]>([]); const images = ref<ItemType[]>([]);
// 表格选中数据 // 表格选中数据
const formRef = ref<FormInstance | null>(null); const formRef = ref<FormInstance | null>(null);
// 布局
const layout = ref<any>();
// 表单数据 // 表单数据
const { form, resetFields, assignFields } = useFormData<Design>({ const { form, resetFields, assignFields } = useFormData<Design>({
@@ -146,6 +179,8 @@
path: '', path: '',
component: '/about/index', component: '/about/index',
content: '', content: '',
description: '',
keywords: '',
type: '', type: '',
status: 0, status: 0,
comments: '', comments: '',
@@ -303,6 +338,7 @@
}); });
}; };
const chooseFile = (data: FileRecord) => { const chooseFile = (data: FileRecord) => {
images.value.push({ images.value.push({
uid: data.id, uid: data.id,
@@ -329,7 +365,8 @@
loading.value = true; loading.value = true;
const formData = { const formData = {
...form, ...form,
content: content.value content: content.value,
layout: JSON.stringify(form)
}; };
const saveOrUpdate = isUpdate.value ? updateDesign : addDesign; const saveOrUpdate = isUpdate.value ? updateDesign : addDesign;
saveOrUpdate(formData) saveOrUpdate(formData)
@@ -367,6 +404,9 @@
status: 'done' status: 'done'
}) })
} }
if(props.data.layout){
layout.value = JSON.parse(props.data.layout);
}
isUpdate.value = true; isUpdate.value = true;
} else { } else {
isUpdate.value = false; isUpdate.value = false;

View File

@@ -24,22 +24,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue'; import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue'; import { watch } from 'vue';
import useSearch from "@/utils/use-search"; import useSearch from '@/utils/use-search';
import type { ArticleParam } from "@/api/cms/article/model"; import type { ArticleParam } from '@/api/cms/article/model';
import { DesignParam } from '@/api/cms/design/model';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
// 选中的角色 // 选中的角色
selection?: []; selection?: [];
categoryId?: number;
navigationList?: Navigator[]; navigationList?: Navigator[];
}>(), }>(),
{} {}
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'search', where?: GradeParam): void; (e: 'search', where?: DesignParam): void;
(e: 'add'): void; (e: 'add'): void;
(e: 'remove'): void; (e: 'remove'): void;
(e: 'batchMove'): void; (e: 'batchMove'): void;
@@ -69,12 +70,16 @@
/* 重置 */ /* 重置 */
const reset = () => { const reset = () => {
resetFields(); resetFields();
type.value = ''; emit('search', where);
reload();
}; };
watch( watch(
() => props.selection, () => props.categoryId,
() => {} (categoryId) => {
if (categoryId) {
where.categoryId = categoryId;
}
},
{ immediate: true }
); );
</script> </script>

View File

@@ -9,12 +9,12 @@
:datasource="datasource" :datasource="datasource"
:customRow="customRow" :customRow="customRow"
tool-class="ele-toolbar-form" tool-class="ele-toolbar-form"
class="sys-org-table"
> >
<template #toolbar> <template #toolbar>
<search <search
@search="reload" @search="reload"
:selection="selection" :selection="selection"
:categoryId="categoryId"
:navigationList="navigationList" :navigationList="navigationList"
@add="openEdit" @add="openEdit"
@remove="removeBatch" @remove="removeBatch"
@@ -42,7 +42,13 @@
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<a-space> <a-space>
<a @click="openDesign(record)">设计</a> <a-tooltip :title="`添加自定义组件及样式`">
<a class="text-fuchsia-300" @click="openRecord(record)"
>布局</a
>
</a-tooltip>
<!-- <a-divider type="vertical" />-->
<!-- <a @click="openDesign(record)">设计</a>-->
<a-divider type="vertical" /> <a-divider type="vertical" />
<a @click="openEdit(record)">修改</a> <a @click="openEdit(record)">修改</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
@@ -61,12 +67,19 @@
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<DesignEdit v-model:visible="showEdit" :data="current" @done="reload" /> <DesignEdit v-model:visible="showEdit" :data="current" @done="reload" />
<!-- 编辑弹窗 -->
<RecordEdit
v-model:visible="showRecordEdit"
:data="current"
@done="reload"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { createVNode, ref } from 'vue'; import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue'; import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro'; import type { EleProTable } from 'ele-admin-pro';
@@ -76,6 +89,7 @@
} from 'ele-admin-pro/es/ele-pro-table/types'; } from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue'; import Search from './components/search.vue';
import DesignEdit from './components/design-edit.vue'; import DesignEdit from './components/design-edit.vue';
import RecordEdit from './record/index.vue';
import { import {
pageDesign, pageDesign,
removeDesign, removeDesign,
@@ -88,6 +102,7 @@
import { getSiteDomain } from '@/utils/domain'; import { getSiteDomain } from '@/utils/domain';
import { Navigation } from '@/api/cms/navigation/model'; import { Navigation } from '@/api/cms/navigation/model';
import { listNavigation } from '@/api/cms/navigation'; import { listNavigation } from '@/api/cms/navigation';
import router from '@/router';
// 表格实例 // 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -100,10 +115,13 @@
const showEdit = ref(false); const showEdit = ref(false);
// 是否显示批量移动弹窗 // 是否显示批量移动弹窗
const showMove = ref(false); const showMove = ref(false);
// 是否显示组件库
const showRecordEdit = ref(false);
// 网站域名 // 网站域名
const domain = getSiteDomain(); const domain = getSiteDomain();
// 栏目数据 // 栏目数据
const navigationList = ref<Navigation[]>(); const navigationList = ref<Navigation[]>();
const categoryId = ref();
// 加载状态 // 加载状态
const loading = ref(true); const loading = ref(true);
@@ -120,6 +138,9 @@
if (filters) { if (filters) {
where.status = filters.status; where.status = filters.status;
} }
if (!where.categoryId && categoryId.value) {
where.categoryId = categoryId.value;
}
return pageDesign({ return pageDesign({
...where, ...where,
...orders, ...orders,
@@ -181,7 +202,7 @@
]); ]);
/* 搜索 */ /* 搜索 */
const reload = (where?: DesignParam) => { const reload = (where?: any) => {
selection.value = []; selection.value = [];
tableRef?.value?.reload({ where: where }); tableRef?.value?.reload({ where: where });
}; };
@@ -192,11 +213,14 @@
showEdit.value = true; showEdit.value = true;
}; };
const openDesign = (row?: Design) => { /* 打开组件管理 */
push({ const openRecord = (row?: Design) => {
path: '/cms/design/detail', current.value = row ?? null;
query: { id: row?.pageId } showRecordEdit.value = true;
}); // push({
// path: '/cms/design/detail',
// query: { id: row?.pageId }
// });
}; };
/* 打开批量移动弹窗 */ /* 打开批量移动弹窗 */
@@ -283,6 +307,17 @@
}; };
query(); query();
watch(
() => router.currentRoute.value.query,
(query) => {
if (query) {
categoryId.value = Number(query.id);
reload();
}
},
{ immediate: true }
);
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -290,30 +325,3 @@
name: 'Design' name: 'Design'
}; };
</script> </script>
<style lang="less" scoped>
.sys-org-table :deep(.ant-table-body) {
overflow: auto !important;
overflow: overlay !important;
}
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
padding: 0 4px;
margin-bottom: 0;
}
.design-item {
margin: auto;
display: flex;
text-align: center;
flex-direction: column;
justify-content: center;
align-items: center;
width: 70px;
height: 70px;
border-radius: 70px;
background: url('data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.024693877551020406%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%230a060d%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23660061%22%20stop-opacity%3D%221%22%20offset%3D%220.95%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E');
}
:deep(.ant-image-img) {
max-height: 80px;
}
</style>

View File

@@ -0,0 +1,250 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑组件' : '添加组件'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<!-- <a-form-item label="上级" name="parentId">-->
<!-- <a-input-->
<!-- allow-clear-->
<!-- placeholder="请输入上级id, 0是顶级"-->
<!-- v-model:value="form.parentId"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="选择组件" name="dictCode">
<SelectDictDictionary
dict-code="componentsType"
:placeholder="`选择组件`"
v-model:value="form.dictCode"
@done="chooseComponents"
/>
</a-form-item>
<!-- 公共字段 -->
<a-form-item label="标题" name="title">
<a-input allow-clear placeholder="标题" v-model:value="form.title" />
</a-form-item>
<a-form-item label="样式" name="styles">
<a-input allow-clear placeholder="样式" v-model:value="form.styles" />
</a-form-item>
<!-- 卡片 -->
<!-- <template v-if="form.dictCode == 'Card'">-->
<!-- <a-form-item label="标题" name="title">-->
<!-- <a-input allow-clear placeholder="标题" v-model:value="form.title" />-->
<!-- </a-form-item>-->
<!-- </template>-->
<!-- <a-form-item label="缩列图" name="photo">-->
<!-- <a-input-->
<!-- allow-clear-->
<!-- placeholder="请输入缩列图"-->
<!-- v-model:value="form.photo"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="用户ID" name="userId">-->
<!-- <a-input-->
<!-- allow-clear-->
<!-- placeholder="请输入用户ID"-->
<!-- v-model:value="form.userId"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="排序(数字越小越靠前)" name="sortNumber">-->
<!-- <a-input-number-->
<!-- :min="0"-->
<!-- :max="9999"-->
<!-- class="ele-fluid"-->
<!-- placeholder="请输入排序号"-->
<!-- v-model:value="form.sortNumber"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="备注" name="comments">-->
<!-- <a-textarea-->
<!-- :rows="4"-->
<!-- :maxlength="200"-->
<!-- placeholder="请输入描述"-->
<!-- v-model:value="form.comments"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addDesignRecord, updateDesignRecord } from '@/api/cms/designRecord';
import { DesignRecord } from '@/api/cms/designRecord/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
import { DictionaryData } from '@/api/system/dictionary-data/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: DesignRecord | null;
// 导航ID
categoryId?: number;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<DesignRecord>({
id: undefined,
pageId: undefined,
parentId: undefined,
title: undefined,
styles: '',
keywords: undefined,
description: undefined,
path: undefined,
photo: undefined,
userId: undefined,
sortNumber: undefined,
comments: undefined,
status: undefined,
tenantId: undefined,
createTime: undefined,
designRecordId: undefined,
designRecordName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
dictCode: [
{
required: true,
type: 'string',
message: '请选择组件',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const chooseComponents = (data: DictionaryData) => {
form.title = data.dictDataName;
form.dictCode = data.dictDataCode;
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
navigationId: props.categoryId
};
const saveOrUpdate = isUpdate.value
? updateDesignRecord
: addDesignRecord;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if (props.data.image) {
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加组件</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { watch } from 'vue';
import { DesignParam } from '@/api/cms/design/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: DesignParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,276 @@
<template>
<a-drawer
width="70%"
:visible="visible"
:title="`${data?.title}`"
placement="left"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
:confirm-loading="loading"
:footer="null"
>
<ele-pro-table
ref="tableRef"
row-key="navigationId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
:categoryId="categoryId"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'isStatus'">
<a-tag v-if="record.isStatus === 1" color="green">开启</a-tag>
<a-tag v-if="record.isStatus === 2" color="red">关闭</a-tag>
</template>
<template v-if="column.key === 'isFree'">
<a-tag v-if="record.isFree === 1" color="green">免费</a-tag>
<a-tag v-if="record.isFree === 2" color="orange">收费</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="moveUp(record)">上移<ArrowUpOutlined /></a>
<a-divider type="vertical" />
<a @click="openEdit(record)">编辑</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
<!-- 编辑弹窗 -->
<DesignRecordEdit
v-model:visible="showEdit"
:merchant="data"
:categoryId="categoryId"
:data="current"
@done="reload"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import {
ArrowUpOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import { EleProTable } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import DesignRecordEdit from './components/designRecordEdit.vue';
import {
removeDesignRecord,
removeBatchDesignRecord,
updateDesignRecord
} from '@/api/cms/designRecord';
import type { DesignRecord } from '@/api/cms/designRecord/model';
import { Navigation } from '@/api/cms/navigation/model';
import { pageDesignRecord } from '@/api/cms/designRecord';
import { DesignRecordParam } from '@/api/cms/designRecord/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
categoryId?: number | null;
// 导航信息
data?: Navigation;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<DesignRecord[]>([]);
// 当前编辑数据
const current = ref<DesignRecord | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.navigationId = props.categoryId;
return pageDesignRecord({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
key: 'navigationId',
width: 48,
align: 'center',
fixed: 'left',
hideInSetting: true,
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
},
{
title: '组件',
dataIndex: 'title',
key: 'title'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
width: 120,
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: DesignRecordParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: DesignRecord) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: DesignRecord) => {
const hide = message.loading('请求中..', 0);
removeDesignRecord(row.periodId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchDesignRecord(selection.value.map((d) => d.periodId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
// 上移
const moveUp = (row?: DesignRecord) => {
updateDesignRecord({
periodId: row?.periodId,
sortNumber: Number(row?.sortNumber) - 1
}).then((msg) => {
message.success(msg);
reload();
});
};
/* 自定义行属性 */
const customRow = (record: DesignRecord) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
watch(
() => props.categoryId,
(categoryId) => {
if (categoryId) {
reload();
}
}
);
</script>
<script lang="ts">
export default {
name: 'DesignRecord'
};
</script>
<style lang="less" scoped></style>

View File

@@ -96,6 +96,7 @@
visible: boolean; visible: boolean;
// 修改回显的数据 // 修改回显的数据
data?: Domain | null; data?: Domain | null;
websiteId?: number;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@@ -173,7 +174,8 @@
} }
} }
const formData = { const formData = {
...form ...form,
websiteId: props.websiteId
}; };
if (isUpdate.value) { if (isUpdate.value) {
setTimeout(() => { setTimeout(() => {

View File

@@ -14,6 +14,7 @@
<search <search
@search="reload" @search="reload"
:selection="selection" :selection="selection"
:websiteId="websiteId"
@add="openEdit" @add="openEdit"
@remove="removeBatch" @remove="removeBatch"
@batchMove="openMove" @batchMove="openMove"
@@ -76,7 +77,12 @@
</a-card> </a-card>
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<DomainEdit v-model:visible="showEdit" :data="current" @done="reload" /> <DomainEdit
v-model:visible="showEdit"
:websiteId="websiteId"
:data="current"
@done="reload"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -86,6 +92,8 @@
import { message, Modal } from 'ant-design-vue'; import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro'; import type { EleProTable } from 'ele-admin-pro';
import { useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import type { import type {
DatasourceFunction, DatasourceFunction,
ColumnItem ColumnItem
@@ -95,11 +103,13 @@
import { import {
pageDomain, pageDomain,
removeDomain, removeDomain,
removeBatchDomain, removeBatchDomain
updateDomain
} from '@/api/cms/domain'; } from '@/api/cms/domain';
import type { Domain, DomainParam } from '@/api/cms/domain/model'; import type { Domain, DomainParam } from '@/api/cms/domain/model';
import { openPreview, openUrl } from "@/utils/common"; import { openUrl } from '@/utils/common';
const route = useRoute();
const { push } = useRouter();
// 表格实例 // 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -112,6 +122,11 @@
const showEdit = ref(false); const showEdit = ref(false);
// 是否显示批量移动弹窗 // 是否显示批量移动弹窗
const showMove = ref(false); const showMove = ref(false);
const websiteId = ref<number>();
if (route.params.id) {
websiteId.value = route.params.id;
}
// 表格数据源 // 表格数据源
const datasource: DatasourceFunction = ({ const datasource: DatasourceFunction = ({
@@ -124,6 +139,7 @@
if (filters) { if (filters) {
where.status = filters.status; where.status = filters.status;
} }
where.websiteId = route.params.id;
return pageDomain({ return pageDomain({
...where, ...where,
...orders, ...orders,

View File

@@ -16,7 +16,16 @@
:label-col="{ md: { span: 3 }, sm: { span: 4 }, xs: { span: 24 } }" :label-col="{ md: { span: 3 }, sm: { span: 4 }, xs: { span: 24 } }"
:wrapper-col="{ md: { span: 21 }, sm: { span: 22 }, xs: { span: 24 } }" :wrapper-col="{ md: { span: 21 }, sm: { span: 22 }, xs: { span: 24 } }"
> >
<a-form-item label="参数" name="name"> <a-form-item label="参数" name="comments">
<a-input
allow-clear
:maxlength="100"
placeholder="网站名称"
v-model:value="form.comments"
@pressEnter="save"
/>
</a-form-item>
<a-form-item label="调用" name="name">
<SelectWebsiteField <SelectWebsiteField
:placeholder="`可配置参数`" :placeholder="`可配置参数`"
class="input-item" class="input-item"
@@ -32,14 +41,6 @@
v-model:value="form.value" v-model:value="form.value"
/> />
</a-form-item> </a-form-item>
<a-form-item label="描述" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="css样式" name="style"> <a-form-item label="css样式" name="style">
<a-textarea <a-textarea
:rows="4" :rows="4"

View File

@@ -38,7 +38,8 @@
<template #content> <template #content>
{{ record.comments }} {{ record.comments }}
</template> </template>
<ExclamationCircleOutlined /> {{ record.comments }}
<!-- <ExclamationCircleOutlined />-->
</a-popover> </a-popover>
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
@@ -127,32 +128,17 @@
const columns = ref<any[]>([ const columns = ref<any[]>([
{ {
title: '参数名', title: '参数名',
dataIndex: 'name',
width: 180,
key: 'name'
},
{
title: '描述',
dataIndex: 'comments', dataIndex: 'comments',
key: 'comments', key: 'comments',
width: 120, width: 250,
align: 'center' ellipsis: true
}, },
{ {
title: '配置值', title: '配置值',
dataIndex: 'value', dataIndex: 'value',
key: 'value', key: 'value',
width: 400,
ellipsis: true ellipsis: true
}, },
{
title: '默认值',
dataIndex: 'defaultValue'
},
{
title: '可设置范围',
dataIndex: 'modifyRange'
},
{ {
title: '排序', title: '排序',
dataIndex: 'sortNumber', dataIndex: 'sortNumber',

View File

@@ -1,15 +1,21 @@
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<template> <template>
<ele-modal <a-drawer
:width="1000" :width="1000"
:visible="visible" :visible="visible"
:maskClosable="false" :maskClosable="false"
:maxable="true" :maxable="true"
:title="isUpdate ? '编辑内容' : '添加内容'" :title="isUpdate ? '页面设计' : '添加页面设计'"
placement="left"
:confirm-loading="loading"
:body-style="{ paddingBottom: '28px' }" :body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible" @update:visible="updateVisible"
@ok="save" @ok="save"
> >
<template #extra>
<a-button type="primary" style="margin-right: 8px" @click="save">保存</a-button>
<a-button @click="onPreview">预览</a-button>
</template>
<a-form <a-form
ref="formRef" ref="formRef"
:model="form" :model="form"
@@ -19,15 +25,6 @@
styleResponsive ? { md: 21, sm: 19, xs: 24 } : { flex: '1' } styleResponsive ? { md: 21, sm: 19, xs: 24 } : { flex: '1' }
" "
> >
<a-form-item label="Banner" name="photo">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="网站SEO"> <a-form-item label="网站SEO">
<a-space direction="vertical" <a-space direction="vertical"
class="w-full"> class="w-full">
@@ -51,48 +48,107 @@
/> />
</a-space> </a-space>
</a-form-item> </a-form-item>
<a-form-item label="购买链接"> <a-form-item label="banner">
<a-space direction="vertical" <a-space direction="vertical" class="w-full">
class="w-full"> <div class="p-2">
<a-input <a-radio-group v-model:value="form.showBanner">
allow-clear <a-radio :value="true">开启</a-radio>
:maxlength="100" <a-radio :value="false">关闭</a-radio>
placeholder="购买链接 buyUrl" </a-radio-group>
v-model:value="form.buyUrl" </div>
/> <template v-if="form.showBanner">
<SelectFile
:placeholder="`请选择背景图`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
<a-textarea
:rows="4"
:maxlength="200"
placeholder="Tailwind CSS风格"
v-model:value="form.style"
/>
<a-input
allow-clear
:maxlength="100"
:placeholder="`演示地址 demoUrl`"
v-model:value="form.demoUrl"
/>
<a-input
allow-clear
:maxlength="100"
:placeholder="`账号密码 admin,123456`"
v-model:value="form.account"
/>
<a-input
allow-clear
:maxlength="100"
:placeholder="`立即购买 buyUrl`"
v-model:value="form.buyUrl"
/>
<a-input
allow-clear
:maxlength="100"
:placeholder="`产品文档 docUrl`"
v-model:value="form.docUrl"
/>
</template>
</a-space>
<a-divider style="margin: 30px 0" />
</a-form-item>
<a-form-item label="展示方式" name="content">
<a-space direction="vertical" class="w-full">
<div class="p-1.5">
<a-radio-group v-model:value="form.showLayout">
<a-radio :value="false">简单</a-radio>
<a-radio :value="true">高级</a-radio>
</a-radio-group>
</div>
<template v-if="!form.showLayout">
<!-- 编辑器 -->
<div class="content">
<tinymce-editor
v-model:value="content"
:disabled="disabled"
:init="config"
placeholder="图片直接粘贴自动上传"
@paste="onPaste"
/>
</div>
</template>
<template v-if="form.showLayout">
<!-- 组件名称 -->
<a-space direction="vertical" class="w-full">
<template v-for="(item,index) in components" :key="index">
<a-card :title="`${item.name}`" class="layout-item w-full bg-gray-50 my-2">
<template #title>
<a-select
ref="select"
v-model:value="form.adType"
style="width: 120px"
>
<a-select-option value="图片广告">图片广告</a-select-option>
<a-select-option value="幻灯片">幻灯片</a-select-option>
<a-select-option value="视频广告">视频广告</a-select-option>
</a-select>
</template>
</a-card>
</template>
</a-space>
<a-button @click="addComponents">添加组件</a-button>
</template>
</a-space> </a-space>
</a-form-item> </a-form-item>
<a-form-item label="页面样式"> <!-- <a-form-item label="状态" name="status">-->
<a-space direction="vertical" <!-- <a-radio-group v-model:value="form.status">-->
class="w-full"> <!-- <a-radio :value="0">开启</a-radio>-->
<a-textarea <!-- <a-radio :value="1">关闭</a-radio>-->
:rows="4" <!-- </a-radio-group>-->
:maxlength="200" <!-- </a-form-item>-->
placeholder="Tailwind CSS风格"
v-model:value="form.style"
/>
</a-space>
</a-form-item>
<a-form-item label="页面内容" name="content">
<!-- 编辑器 -->
<div class="content">
<tinymce-editor
v-model:value="content"
:disabled="disabled"
:init="config"
placeholder="图片直接粘贴自动上传"
@paste="onPaste"
/>
</div>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">开启</a-radio>
<a-radio :value="1">关闭</a-radio>
</a-radio-group>
</a-form-item>
</a-form> </a-form>
</ele-modal> </a-drawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -119,6 +175,8 @@ const { styleResponsive } = storeToRefs(themeStore);
const disabled = ref(false); const disabled = ref(false);
// 编辑器内容,双向绑定 // 编辑器内容,双向绑定
const content = ref<any>(''); const content = ref<any>('');
// 组件列表
const components = ref<any[]>();
const props = defineProps<{ const props = defineProps<{
// 弹窗是否打开 // 弹窗是否打开
@@ -151,14 +209,22 @@ const { form, resetFields, assignFields } = useFormData<Design>({
description: '', description: '',
keywords: '', keywords: '',
content: '', content: '',
buyUrl: '',
type: '', type: '',
categoryId: undefined, categoryId: undefined,
style: '', style: '',
status: 0, status: 0,
comments: '', comments: '',
sortNumber: 100, sortNumber: 100,
navigationId: undefined navigationId: undefined,
showLayout: false,
layout: '',
btn: [],
showBanner: true,
buyUrl: '',
demoUrl: '',
account: '',
docUrl: ''
}); });
const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null); const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null);
@@ -326,6 +392,41 @@ const onDeleteItem = (index: number) => {
form.photo = ''; form.photo = '';
} }
const addComponents = () => {
if(!components.value){
components.value = [];
}
components.value?.push({
type: 'card',
name: '组件名称',
css: '',
data: []
})
}
const options = [{
value: 'primary',
label: 'primary',
},{
value: 'success',
label: 'success',
},{
value: 'warning',
label: 'warning',
},{
value: 'danger',
label: 'danger',
},{
value: 'info',
label: 'info',
},{
value: 'text',
label: 'text',
}];
const delBtn = (index: number) => {
form.btn?.splice(index,1)
}
/* 保存编辑 */ /* 保存编辑 */
const save = () => { const save = () => {
@@ -336,11 +437,14 @@ const save = () => {
.validate() .validate()
.then(() => { .then(() => {
loading.value = true; loading.value = true;
console.log(form);
const formData = { const formData = {
...form, ...form,
layout: JSON.stringify(form),
categoryId: props.categoryId, categoryId: props.categoryId,
content: content.value content: content.value,
}; };
console.log(formData);
const saveOrUpdate = isUpdate.value ? updateDesign : addDesign; const saveOrUpdate = isUpdate.value ? updateDesign : addDesign;
saveOrUpdate(formData) saveOrUpdate(formData)
.then((msg) => { .then((msg) => {
@@ -367,15 +471,18 @@ watch(
if(props.categoryId){ if(props.categoryId){
content.value = ''; content.value = '';
images.value = [] images.value = []
console.log(props.categoryId);
pageDesign({categoryId: props.categoryId,limit: 1}).then(res => { pageDesign({categoryId: props.categoryId,limit: 1}).then(res => {
const design = res?.list[0]; const design = res?.list[0];
if(design){ if(design){
assignFields(design); assignFields(design);
if(design.layout){
assignFields(JSON.parse(design.layout));
}
if(design?.content){ if(design?.content){
content.value = design.content content.value = design.content
} }
if(design.photo){ if(design.photo){
form.photo = design.photo;
images.value.push({ images.value.push({
uid: uuid(), uid: uuid(),
url: design.photo, url: design.photo,
@@ -399,8 +506,3 @@ watch(
{ immediate: true } { immediate: true }
); );
</script> </script>
<style lang="less">
.sdf{
display: none;
}
</style>

View File

@@ -55,18 +55,15 @@
@pressEnter="save" @pressEnter="save"
/> />
</a-form-item> </a-form-item>
<!-- <a-form-item--> <a-form-item label="组件路径" name="component" v-if="isUpdate">
<!-- label="组件路径"--> <a-input
<!-- name="component"--> allow-clear
<!-- v-if="isUpdate"--> disabled
<!-- >--> placeholder="/pages/product/detail.vue"
<!-- <a-input--> v-model:value="form.component"
<!-- allow-clear--> @pressEnter="save"
<!-- placeholder="/pages/product/detail.vue"--> />
<!-- v-model:value="form.component"--> </a-form-item>
<!-- @pressEnter="save"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="css样式" name="style" v-if="isUpdate"> <a-form-item label="css样式" name="style" v-if="isUpdate">
<a-input <a-input
allow-clear allow-clear
@@ -202,7 +199,7 @@
navigationId: undefined, navigationId: undefined,
model: 'custom', model: 'custom',
code: undefined, code: undefined,
modelName: '通用模型', modelName: '单页模型',
type: 0, type: 0,
title: '', title: '',
parentId: 0, parentId: 0,
@@ -314,11 +311,22 @@
] ]
}); });
const chooseModel = (item: Navigation) => { const chooseModel = (item: any) => {
console.log(item); console.log(item);
form.model = `${item.value}`; form.model = `${item.value}`;
form.modelName = `${item.label}`; form.modelName = `${item.label}`;
form.componentPath = `${item.component}`; if (item.value == 'custom') {
form.path = `/custom-${form.navigationId}`;
form.component = `/pages/[${item.value}]/index.vue`;
return;
}
if (item.value == 'links') {
form.path = `https://`;
form.component = `/pages/${item.value}/index.vue`;
return;
}
form.path = `/${item.value}/${form.navigationId}`;
form.component = `/pages/${item.value}/index.vue`;
}; };
const chooseFile = (data: FileRecord) => { const chooseFile = (data: FileRecord) => {

View File

@@ -35,6 +35,7 @@
<a-radio-group v-model:value="position" @change="reload"> <a-radio-group v-model:value="position" @change="reload">
<a-radio-button :value="1">顶部</a-radio-button> <a-radio-button :value="1">顶部</a-radio-button>
<a-radio-button :value="2">底部</a-radio-button> <a-radio-button :value="2">底部</a-radio-button>
<a-radio-button :value="0">不限</a-radio-button>
</a-radio-group> </a-radio-group>
<a-divider type="vertical" /> <a-divider type="vertical" />
<!-- 搜索表单 --> <!-- 搜索表单 -->
@@ -49,7 +50,18 @@
</template> </template>
<template #bodyCell="{ column, record, index }"> <template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'path'"> <template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span> <span class="cursor-pointer" v-if="isDirectory(record)"></span>
<span
v-else
@click="openSpmUrl(record.path, record, record.navigationId)"
class="ele-text-placeholder cursor-pointer"
>{{ record.path }}</span
>
<!-- <span-->
<!-- class="ele-text-placeholder cursor-pointer"-->
<!-- @click="openSpmUrl(record.path, record, record.navigationId)"-->
<!-- >{{ record.path }}</span-->
<!-- >-->
</template> </template>
<template v-if="column.key === 'component'"> <template v-if="column.key === 'component'">
<template v-if="!isDirectory(record)"> <template v-if="!isDirectory(record)">
@@ -114,12 +126,8 @@
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'action'">
<a-space> <a-space>
<a-tooltip :title="`添加自定义组件及样式`"> <a-tooltip :title="`配置SEO及页面内容`">
<a class="text-fuchsia-300" @click="openLayout(record)">布局</a> <a class="text-fuchsia-300" @click="openDesign(record)">设计</a>
</a-tooltip>
<a-divider type="vertical" />
<a-tooltip :title="`配置SEO及页面元素`">
<a class="text-gray-400" @click="openDesign(record)">内容</a>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-tooltip :title="`添加子分类`"> <a-tooltip :title="`添加子分类`">
@@ -193,6 +201,7 @@
import { openSpmUrl } from '@/utils/common'; import { openSpmUrl } from '@/utils/common';
import { getSiteInfo } from '@/api/layout'; import { getSiteInfo } from '@/api/layout';
import { Design } from '@/api/cms/design/model'; import { Design } from '@/api/cms/design/model';
import router from '@/router';
// 表格实例 // 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -312,6 +321,10 @@
where.top = undefined; where.top = undefined;
where.bottom = 0; where.bottom = 0;
} }
if (position.value == 0) {
where.top = undefined;
where.bottom = undefined;
}
where.isMpWeixin = false; where.isMpWeixin = false;
return listNavigation({ ...where }); return listNavigation({ ...where });
}; };
@@ -344,22 +357,49 @@
showEdit.value = true; showEdit.value = true;
}; };
// 跳转模型内容列表
const openDesign = (row?: Navigation) => { const openDesign = (row?: Navigation) => {
categoryId.value = row?.navigationId; categoryId.value = row?.navigationId;
showDesignEdit.value = true; showDesignEdit.value = true;
// // TODO 通用模型 return;
// if (row?.model == 'custom') {
// if (row && isDirectory(row)) {
// categoryId.value = row?.navigationId;
// showDesignEdit.value = true; // showDesignEdit.value = true;
// return; // return;
// } // }
// // TODO 单页模型
// // TODO 文章列表 if (row?.model == 'custom') {
// if (row?.model === 'article') { router.push({
// router.push({ path: `/cms/design`,
// path: '/cms/article', query: {
// query: { categoryId: row.navigationId, type: row.type } id: row.navigationId,
// }); type: row.model
// } }
});
return;
}
// TODO 文章列表
if (row?.model === 'article') {
router.push({
path: `/cms/article`,
query: {
id: row.navigationId,
type: row.model
}
});
return;
}
// TODO 产品列表
if (row?.model === 'product') {
router.push({
path: '/goods/index',
query: { categoryId: row.navigationId, type: row.type }
});
return;
}
}; };
const openLayout = (row?: Navigation) => { const openLayout = (row?: Navigation) => {

View File

@@ -19,8 +19,8 @@
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-group`)">--> <!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-group`)">-->
<!-- <span>卡片管理</span>--> <!-- <span>卡片管理</span>-->
<!-- </a-button>--> <!-- </a-button>-->
<!-- <a-button class="ele-btn-icon" @click="openUrl(`/mp-package`)">--> <!-- <a-button class="ele-btn-icon" @click="navigator(`/website/domain`)">-->
<!-- <span>分包管理</span>--> <!-- <span>域名管理</span>-->
<!-- </a-button>--> <!-- </a-button>-->
</a-space> </a-space>
</template> </template>
@@ -28,7 +28,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue'; import { PlusOutlined } from '@ant-design/icons-vue';
import { watch } from 'vue'; import { watch } from 'vue';
import { openUrl } from '@/utils/common';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@@ -146,7 +146,7 @@
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form'; import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model'; import { FileRecord } from '@/api/system/file/model';
import { addDomain } from '@/api/cms/domain'; import { checkExistence } from '@/api/cms/domain';
// 是否是修改 // 是否是修改
const isUpdate = ref(false); const isUpdate = ref(false);
@@ -175,6 +175,7 @@
const formRef = ref<FormInstance | null>(null); const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]); const images = ref<ItemType[]>([]);
const websiteQrcode = ref<ItemType[]>([]); const websiteQrcode = ref<ItemType[]>([]);
const oldDomain = ref();
// 用户信息 // 用户信息
const form = reactive<Website>({ const form = reactive<Website>({
@@ -244,6 +245,31 @@
// trigger: 'blur' // trigger: 'blur'
// } // }
// ], // ],
websiteCode: [
{
required: true,
type: 'string',
message: '该域名已存在',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请输入二级域名');
}
checkExistence('domain', `${value}.wsdns.cn`)
.then(() => {
if (value === oldDomain.value) {
return resolve();
}
reject('已存在');
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
adminUrl: [ adminUrl: [
{ {
required: true, required: true,
@@ -342,6 +368,9 @@
status: 'done' status: 'done'
}); });
} }
if (props.data.websiteCode) {
oldDomain.value = props.data.websiteCode;
}
isUpdate.value = true; isUpdate.value = true;
} else { } else {
isUpdate.value = false; isUpdate.value = false;

View File

@@ -76,6 +76,10 @@
</template> </template>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<a-space> <a-space>
<a @click="openUrl(`/website/domain/${record.websiteId}`)"
>域名管理</a
>
<a-divider type="vertical" />
<a @click="openEdit(record)">编辑</a> <a @click="openEdit(record)">编辑</a>
</a-space> </a-space>
</template> </template>
@@ -147,6 +151,12 @@
// 表格列配置 // 表格列配置
const columns = ref<ColumnItem[]>([ const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'tenantId',
key: 'tenantId',
width: 80
},
{ {
title: '网站名称', title: '网站名称',
dataIndex: 'websiteName', dataIndex: 'websiteName',
@@ -195,7 +205,7 @@
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 120, width: 180,
fixed: 'right', fixed: 'right',
align: 'center', align: 'center',
hideInSetting: true hideInSetting: true

View File

@@ -23,13 +23,13 @@
@done="chooseFile" @done="chooseFile"
@del="onDeleteItem" @del="onDeleteItem"
/> />
<!-- <ele-image-upload--> <!-- <ele-image-upload-->
<!-- v-model:value="logo"--> <!-- v-model:value="logo"-->
<!-- :accept="'image/*'"--> <!-- :accept="'image/*'"-->
<!-- :item-style="{ width: '80px', height: '80px' }"--> <!-- :item-style="{ width: '80px', height: '80px' }"-->
<!-- :limit="1"--> <!-- :limit="1"-->
<!-- @upload="onUpload"--> <!-- @upload="onUpload"-->
<!-- />--> <!-- />-->
</a-form-item> </a-form-item>
<a-form-item label="应用名称"> <a-form-item label="应用名称">
<a-space class="justify ele-text-secondary"> <a-space class="justify ele-text-secondary">
@@ -240,8 +240,6 @@ import { useThemeStore } from "@/store/modules/theme";
import { getCompany, updateCompany, destructionTenant } from "@/api/system/company"; import { getCompany, updateCompany, destructionTenant } from "@/api/system/company";
import Field from "./components/field.vue"; import Field from "./components/field.vue";
import Version from "./components/version.vue"; import Version from "./components/version.vue";
import { FILE_SERVER } from "@/config/setting";
import { assignObject } from "ele-admin-pro"; import { assignObject } from "ele-admin-pro";
import { getFileSize, openUrl } from "@/utils/common"; import { getFileSize, openUrl } from "@/utils/common";
import { Company } from "@/api/system/company/model"; import { Company } from "@/api/system/company/model";
@@ -250,6 +248,7 @@ import { uploadFile } from "@/api/system/file";
import { logout } from "@/utils/page-tab-util"; import { logout } from "@/utils/page-tab-util";
import { ExclamationCircleOutlined } from "@ant-design/icons-vue"; import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
import { FileRecord } from "@/api/system/file/model"; import { FileRecord } from "@/api/system/file/model";
import { uuid } from 'ele-admin-pro';
const useForm = Form.useForm; const useForm = Form.useForm;
@@ -450,10 +449,9 @@ const destruction = (tenantId) => {
const query = () => { const query = () => {
logo.value = []; logo.value = [];
getCompany().then((response) => { getCompany().then((response) => {
console.log(response.companyLogo);
if (response.companyLogo) { if (response.companyLogo) {
logo.value.push({ logo.value.push({
uid: 1, uid: `${uuid()}`,
url: response.companyLogo, url: response.companyLogo,
status: "done" status: "done"
}); });