优化网站导航模块

This commit is contained in:
2024-08-23 22:28:24 +08:00
parent 1d81fa9270
commit 13832d9de0
964 changed files with 90774 additions and 31362 deletions

View File

@@ -87,6 +87,29 @@
/>
</template>
</a-form-item>
<a-form-item label="所在页面">
<SelectDesign
:placeholder="`请选择页面`"
v-model:value="form.pageName"
@done="choosePageId"
/>
</a-form-item>
<a-form-item label="宽度">
<a-input
allow-clear
:maxlength="3000"
placeholder="px"
v-model:value="form.width"
/>
</a-form-item>
<a-form-item label="高度">
<a-input
allow-clear
:maxlength="2000"
placeholder="px"
v-model:value="form.height"
/>
</a-form-item>
<a-form-item label="标题" name="name">
<a-input
allow-clear
@@ -125,19 +148,16 @@
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, isChinese } from 'ele-admin-pro';
import { assignObject } from 'ele-admin-pro';
import { addAd, updateAd } from '@/api/cms/ad';
import { Ad } from '@/api/cms/ad/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { FormInstance, type Rule, RuleObject } from 'ant-design-vue/es/form';
import { FormInstance, type Rule } from 'ant-design-vue/es/form';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { uploadFile } from '@/api/system/file';
import { FileRecord } from '@/api/system/file/model';
import { checkExistence } from '@/api/system/company';
import image from '@/views/cms/photo/image.vue';
import defaultResult from 'ant-design-vue/es/_util/isMobile';
import any = defaultResult.any;
import { Design } from '@/api/cms/design/model';
// 是否是修改
const isUpdate = ref(false);
@@ -180,7 +200,9 @@
type: '',
status: 0,
comments: '',
sortNumber: 100
sortNumber: 100,
pageName: '',
pageId: undefined
});
/* 更新visible */
@@ -274,6 +296,11 @@
form.images = '';
};
const choosePageId = (data: Design) => {
form.pageName = data.name;
form.pageId = data.pageId;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
@@ -323,15 +350,14 @@
});
});
}
if (props.data.adType == '幻灯片') {
const arr = JSON.parse(props.data.path);
arr.map((d) => {
pathList.value.push(d);
});
}
// if (props.data.adType == '幻灯片') {
// const arr = JSON.parse(props.data.path);
// arr.map((d) => {
// pathList.value.push(d);
// });
// }
isUpdate.value = true;
} else {
images.value = [];
isUpdate.value = false;
}
} else {

View File

@@ -27,14 +27,9 @@
<AntDesignOutlined />
</template>
</a-avatar>
<a
@click="
openPreview(
`https://${record.tenantId}.wsdns.cn/a/${record.articleId}`
)
"
>{{ record.title }}</a
>
<a @click="openPreview(`/article/detail/${record.articleId}`)">{{
record.title
}}</a>
</a-space>
</template>
<template v-if="column.key === 'image'"> </template>

View File

@@ -66,15 +66,9 @@
style="margin-right: 10px"
v-if="record.image"
/>
<a
@click="
openPreview(
`https://${record.tenantId}.wsdns.cn/article/` +
record.categoryId
)
"
>{{ record.title }}</a
>
<a @click="openPreview(`/article/` + record.categoryId)">{{
record.title
}}</a>
</template>
<template v-if="column.key === 'showIndex'">
<a-space @click="onShowIndex(record)">

View File

@@ -1,138 +0,0 @@
<!-- 最新动态 -->
<template>
<a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }">
<template #extra>
<more-icon @remove="onRemove" @edit="onEdit" />
</template>
<div
style="height: 346px; padding: 22px 20px 0 20px"
class="ele-scrollbar-hover"
>
<a-timeline>
<a-timeline-item
v-for="item in activities"
:key="item.id"
:color="item.color"
>
<em>{{ item.time }}</em>
<em>{{ item.title }}</em>
</a-timeline-item>
</a-timeline>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MoreIcon from './more-icon.vue';
defineProps<{
title?: string;
}>();
const emit = defineEmits<{
(e: 'remove'): void;
(e: 'edit'): void;
}>();
interface Activitie {
id: number;
title: string;
time: string;
color?: string;
}
// 最新动态数据
const activities = ref<Activitie[]>([]);
/* 查询最新动态 */
const queryActivities = () => {
activities.value = [
{
id: 1,
title: 'SunSmile 解决了bug 登录提示操作失败',
time: '20:30',
color: 'gray'
},
{
id: 2,
title: 'Jasmine 解决了bug 按钮颜色与设计不符',
time: '19:30',
color: 'gray'
},
{
id: 3,
title: '项目经理 指派了任务 解决项目一的bug',
time: '18:30'
},
{
id: 4,
title: '项目经理 指派了任务 解决项目二的bug',
time: '17:30'
},
{
id: 5,
title: '项目经理 指派了任务 解决项目三的bug',
time: '16:30'
},
{
id: 6,
title: '项目经理 指派了任务 解决项目四的bug',
time: '15:30',
color: 'gray'
},
{
id: 7,
title: '项目经理 指派了任务 解决项目五的bug',
time: '14:30',
color: 'gray'
},
{
id: 8,
title: '项目经理 指派了任务 解决项目六的bug',
time: '12:30',
color: 'gray'
},
{
id: 9,
title: '项目经理 指派了任务 解决项目七的bug',
time: '11:30'
},
{
id: 10,
title: '项目经理 指派了任务 解决项目八的bug',
time: '10:30',
color: 'gray'
},
{
id: 11,
title: '项目经理 指派了任务 解决项目九的bug',
time: '09:30',
color: 'green'
},
{
id: 12,
title: '项目经理 指派了任务 解决项目十的bug',
time: '08:30',
color: 'red'
}
];
};
const onRemove = () => {
emit('remove');
};
const onEdit = () => {
emit('edit');
};
queryActivities();
</script>
<style lang="less" scoped>
.ele-scrollbar-hover
:deep(.ant-timeline-item-last > .ant-timeline-item-content) {
min-height: auto;
}
</style>

View File

@@ -1,81 +0,0 @@
<template>
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px' }">
<template #extra
><a @click="openUrl('/oa/app/index')" class="ele-text-placeholder"
>更多<RightOutlined /></a
></template>
<a-list :size="`small`" :split="false" :data-source="list">
<template #renderItem="{ item }">
<a-list-item>
<div class="app-box">
<a-image
:height="45"
:width="45"
:preview="false"
:src="item.appIcon"
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
/>
<div class="app-info">
<a
class="ele-text-heading"
>
{{ item.appName }}
</a>
<span class="ele-text-placeholder">
{{ item.appCode }}
</span>
</div>
</div>
</a-list-item>
</template>
</a-list>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Article } from '@/api/cms/article/model';
import { openUrl } from '@/utils/common';
import { pageApp } from '@/api/oa/app';
import { RightOutlined } from '@ant-design/icons-vue';
const props = defineProps<{
title: string;
}>();
const list = ref<Article[]>([]);
/**
* 加载数据
*/
const reload = () => {
const { title } = props;
// 加载文章列表
pageApp({
limit: 5,
status: 0,
appStatus: '开发中'
}).then((data) => {
if (data?.list) {
list.value = data.list;
}
});
};
reload();
</script>
<script lang="ts">
export default {
name: 'DashboardArticleList'
};
</script>
<style lang="less" scoped>
.app-box {
display: flex;
.app-info {
display: flex;
margin-left: 5px;
flex-direction: column;
}
}
</style>

View File

@@ -1,56 +0,0 @@
<template>
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px', minHeight: '252px' }">
<template #extra
><a
@click="openPreview('/article/' + categoryId)"
class="ele-text-placeholder"
>更多<RightOutlined /></a
></template>
<a-list :size="`small`" :split="false" :data-source="list">
<template #renderItem="{ item }">
<a-list-item>
<a
class="ele-text-secondary"
@click="openUrl('/cms/article/' + item.articleId)"
>
{{ item.title }}
</a>
</a-list-item>
</template>
</a-list>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageArticle } from '@/api/cms/article';
import { Article } from '@/api/cms/article/model';
import {openNew, openPreview, openUrl} from '@/utils/common';
import { RightOutlined } from '@ant-design/icons-vue';
const list = ref<Article[]>([]);
const props = defineProps<{
title: string;
categoryId: number;
}>();
/**
* 加载数据
*/
const reload = () => {
const { categoryId } = props;
// 加载文章列表
pageArticle({ categoryId, limit: 6 }).then((data) => {
if (data?.list) {
list.value = data.list;
}
});
};
reload();
</script>
<script lang="ts">
export default {
name: 'DashboardArticleList'
};
</script>

View File

@@ -1,80 +0,0 @@
<template>
<a-card :bordered="false" title="小组成员">
<template #extra>
<a-tooltip>
<template #title>邀请加入</template>
<UserAddOutlined @click="onShowQrcode" :style="{ fontSize: '18px' }" />
</a-tooltip>
</template>
<a-list
class="demo-loadmore-list"
item-layout="horizontal"
:data-source="list"
>
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
<a-popover>
<template #content> 待处理 </template>
<a class="ele-text-danger">{{ item.pending }}</a>
</a-popover>
<a-popover>
<template #content> 本月已处理 </template>
<a class="ele-text-secondary">{{ item.month }}</a>
</a-popover>
</template>
<a-skeleton avatar :title="false" :loading="!!item.loading" active>
<a-list-item-meta>
<template #title>
{{ item.nickname }}
</template>
<template #avatar>
<a-avatar :src="item.avatar" />
</template>
</a-list-item-meta>
</a-skeleton>
</a-list-item>
</template>
</a-list>
<!-- 工单二维码 -->
<Qrcode v-model:visible="showQrcode" />
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { pageTaskCount } from '@/api/oa/task-count';
import { TaskCount } from '@/api/oa/task-count/model';
import { UserAddOutlined } from '@ant-design/icons-vue';
import Qrcode from './qrcode.vue';
const showQrcode = ref(false);
const list = ref<TaskCount[]>([]);
pageTaskCount({ limit: 10, roleCode: 'commander' }).then((res) => {
if (res) {
list.value = res?.list;
}
});
const onShowQrcode = () => {
showQrcode.value = true;
};
</script>
<style lang="less" scoped>
.monitor-evaluate-text {
width: 90px;
flex-shrink: 0;
white-space: nowrap;
opacity: 0.8;
& > .anticon {
font-size: 12px;
margin: 0 6px 0 8px;
}
}
/deep/.ant-list-item {
padding: 7px 0;
}
</style>

View File

@@ -1,70 +0,0 @@
<!-- 本月目标 -->
<template>
<a-card :title="title" :bordered="false">
<template #extra>
<more-icon @remove="onRemove" @edit="onEdit" />
</template>
<div class="workplace-goal-group">
<a-progress
:width="180"
:percent="80"
type="dashboard"
:stroke-width="4"
:show-info="false"
/>
<div class="workplace-goal-content">
<ele-tag color="blue" size="large" shape="circle">
<trophy-outlined />
</ele-tag>
<div class="workplace-goal-num">285</div>
</div>
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { TrophyOutlined } from '@ant-design/icons-vue';
import MoreIcon from './more-icon.vue';
defineProps<{
title?: string;
}>();
const emit = defineEmits<{
(e: 'remove'): void;
(e: 'edit'): void;
}>();
const onRemove = () => {
emit('remove');
};
const onEdit = () => {
emit('edit');
};
</script>
<style lang="less" scoped>
.workplace-goal-group {
height: 310px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
.workplace-goal-content {
position: absolute;
top: 50%;
left: 50%;
width: 180px;
margin: -50px 0 0 -90px;
text-align: center;
}
.workplace-goal-num {
font-size: 40px;
}
}
</style>

View File

@@ -1,70 +0,0 @@
<!-- 本月目标 -->
<template>
<a-card :title="title" :bordered="false">
<template #extra>
<more-icon @remove="onRemove" @edit="onEdit" />
</template>
<div class="workplace-goal-group">
<a-progress
:width="180"
:percent="80"
type="dashboard"
:stroke-width="4"
:show-info="false"
/>
<div class="workplace-goal-content">
<ele-tag color="blue" size="large" shape="circle">
<trophy-outlined />
</ele-tag>
<div class="workplace-goal-num">285</div>
</div>
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { TrophyOutlined } from '@ant-design/icons-vue';
import MoreIcon from './more-icon.vue';
defineProps<{
title?: string;
}>();
const emit = defineEmits<{
(e: 'remove'): void;
(e: 'edit'): void;
}>();
const onRemove = () => {
emit('remove');
};
const onEdit = () => {
emit('edit');
};
</script>
<style lang="less" scoped>
.workplace-goal-group {
height: 310px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
.workplace-goal-content {
position: absolute;
top: 50%;
left: 50%;
width: 180px;
margin: -50px 0 0 -90px;
text-align: center;
}
.workplace-goal-num {
font-size: 40px;
}
}
</style>

View File

@@ -1,178 +0,0 @@
<!-- 快捷方式 -->
<template>
<a-row :gutter="16" ref="wrapRef">
<a-col v-for="item in data" :key="item.url" :lg="3" :md="6" :sm="9" :xs="8">
<a-card :bordered="false" hoverable :body-style="{ padding: 0 }">
<div class="app-link-block" @click="navTo(item)">
<component
:is="item.icon"
class="app-link-icon"
:style="{ color: item.color }"
/>
<div class="app-link-title">{{ item.title }}</div>
</div>
</a-card>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import SortableJs from 'sortablejs';
import type { Row as ARow } from 'ant-design-vue';
import { openNew, openUrl } from '@/utils/common';
import { getOriginDomain } from '@/utils/domain';
const CACHE_KEY = 'workplace-links';
// 当前开发环境
const env = import.meta.env.MODE;
interface LinkItem {
icon: string;
title: string;
url: string;
color?: string;
}
// 默认顺序
const DEFAULT: LinkItem[] = [
{
icon: 'settingOutlined',
title: '网站设置',
url: '/website/setting'
},
{
icon: 'LayoutOutlined',
title: '单页管理',
url: '/cms/design'
},
{
icon: 'FileSearchOutlined',
title: '文章管理',
url: '/cms/article'
},
{
icon: 'AntDesignOutlined',
title: '广告管理',
url: '/website/ad'
},
{
icon: 'InstagramOutlined',
title: '图片素材',
url: '/cms/photo'
},
{
icon: 'ShoppingOutlined',
title: '表单数据',
url: '/cms/form-record'
},
{
icon: 'ChromeOutlined',
title: '友情链接',
url: '/oa/link'
},
{
icon: 'DesktopOutlined',
title: '网站首页',
url: `${localStorage.getItem('TenantId')}.${localStorage.getItem(
'domain'
)}`
}
];
// 获取缓存的顺序
const cache = (() => {
const str = localStorage.getItem(CACHE_KEY);
try {
return str ? JSON.parse(str) : null;
} catch (e) {
return null;
}
})();
const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]);
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
let sortableIns: SortableJs | null = null;
/* 重置布局 */
const reset = () => {
data.value = [...DEFAULT];
cacheData();
};
/* 缓存布局 */
const cacheData = () => {
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
};
const navTo = (item) => {
if (item.icon == 'DesktopOutlined') {
// if (env == 'development') {
// return openUrl(getOriginDomain());
// }
return openNew(`${domain.value}`);
}
openUrl(item.url);
};
onMounted(() => {
const isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
return;
}
sortableIns = new SortableJs(wrapRef.value?.$el, {
animation: 300,
onUpdate: ({ oldIndex, newIndex }) => {
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
const temp = [...data.value];
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
data.value = temp;
cacheData();
}
},
setData: () => {}
});
});
onBeforeUnmount(() => {
if (sortableIns) {
sortableIns.destroy();
}
});
defineExpose({ reset });
</script>
<script lang="ts">
import * as icons from './link-icons';
import { getSiteInfo } from '@/api/layout';
import { ref } from 'vue';
const tenantId = ref<number>();
const domain = ref<string>();
getSiteInfo().then((data) => {
tenantId.value = data.tenantId;
domain.value = data.domain;
});
export default {
components: icons
};
</script>
<style lang="less" scoped>
.app-link-block {
padding: 12px;
text-align: center;
display: block;
color: inherit;
.app-link-icon {
color: #666666;
font-size: 30px;
margin: 6px 0 10px 0;
}
}
</style>

View File

@@ -1,14 +0,0 @@
export {
UserOutlined,
TeamOutlined,
FileSearchOutlined,
ChromeOutlined,
ShoppingOutlined,
LaptopOutlined,
AppstoreAddOutlined,
DesktopOutlined,
AntDesignOutlined,
SettingOutlined,
InstagramOutlined,
LayoutOutlined
} from '@ant-design/icons-vue';

View File

@@ -1,54 +0,0 @@
<template>
<a-card :title="linkType" :bordered="false" :body-style="{ padding: '2px' }">
<template #extra
><a @click="openNew('/oa/link')" class="ele-text-placeholder"
>更多<RightOutlined /></a
></template>
<a-list :size="`small`" :split="false" :data-source="list">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item-meta>
<template #avatar>
<a-avatar :src="item.icon" />
</template>
<template #title>
<a @click="openUrl(item.url)">{{ item.name }}</a>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { openNew, openUrl } from '@/utils/common';
import { pageLink } from '@/api/oa/link';
import { RightOutlined } from '@ant-design/icons-vue';
const list = ref<any[]>([]);
const props = defineProps<{
linkType: string;
}>();
/**
* 加载数据
*/
const reload = () => {
const { linkType } = props;
// 加载文章列表
pageLink({ linkType, limit: 5 }).then((data) => {
if (data?.list) {
list.value = data.list;
}
});
};
reload();
</script>
<script lang="ts">
export default {
name: 'DashboardArticleList'
};
</script>

View File

@@ -1,38 +0,0 @@
<template>
<a-dropdown placement="bottomRight">
<more-outlined class="ele-text-secondary" style="font-size: 18px" />
<template #overlay>
<a-menu :selectable="false" @click="onClick">
<a-menu-item key="edit">
<div class="ele-cell">
<edit-outlined />
<div class="ele-cell-content">编辑</div>
</div>
</a-menu-item>
<a-menu-item key="remove">
<div class="ele-cell ele-text-danger">
<delete-outlined />
<div class="ele-cell-content">删除</div>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<script lang="ts" setup>
import {
MoreOutlined,
EditOutlined,
DeleteOutlined
} from '@ant-design/icons-vue';
const emit = defineEmits<{
(e: 'edit'): void;
(e: 'remove'): void;
}>();
const onClick = ({ key }) => {
emit(key);
};
</script>

View File

@@ -1,121 +0,0 @@
<!-- 用户信息 -->
<template>
<a-card :bordered="false" :body-style="{ padding: '20px' }">
<div class="ele-cell workplace-user-card">
<div class="ele-cell-content ele-cell">
<a-avatar :size="68" :src="loginUser.avatar">
<template v-if="!loginUser.avatar" #icon>
<user-outlined />
</template>
</a-avatar>
<div class="ele-cell-content">
<h4 class="ele-elip">
早安, {{ loginUser.nickname }}, 开始您一天的工作吧!
</h4>
<div class="ele-elip ele-text-secondary">
<cloud-outlined />
<em>{{ elip[Math.floor(Math.random() * elip.length)] }}</em>
<!-- <em>今日多云转阴18 - 22出门记得穿外套哦~</em>-->
</div>
</div>
</div>
<div class="workplace-count-group">
<!-- <div class="workplace-count-item">-->
<!-- <div class="workplace-count-header">-->
<!-- <ele-tag color="blue" shape="circle" size="small">-->
<!-- <appstore-filled />-->
<!-- </ele-tag>-->
<!-- <span class="workplace-count-name">项目数</span>-->
<!-- </div>-->
<!-- <h2>0</h2>-->
<!-- </div>-->
<!-- <div class="workplace-count-item">-->
<!-- <div class="workplace-count-header">-->
<!-- <ele-tag color="orange" shape="circle" size="small">-->
<!-- <check-square-outlined />-->
<!-- </ele-tag>-->
<!-- <span class="workplace-count-name">待办项</span>-->
<!-- </div>-->
<!-- <h2>6 / 24</h2>-->
<!-- </div>-->
<!-- <div class="workplace-count-item">-->
<!-- <div class="workplace-count-header">-->
<!-- <ele-tag color="green" shape="circle" size="small">-->
<!-- <bell-filled />-->
<!-- </ele-tag>-->
<!-- <span class="workplace-count-name">消息</span>-->
<!-- </div>-->
<!-- <h2>0</h2>-->
<!-- </div>-->
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import {
UserOutlined,
CloudOutlined,
AppstoreFilled,
CheckSquareOutlined,
BellFilled
} from '@ant-design/icons-vue';
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();
// 当前登录用户信息
const loginUser = computed(() => userStore.info ?? {});
const elip = ref<string[]>([
'小事成就大事,细节成就完美~',
'心态决定命运,自信走向成功',
'人生能有几回博,今日不博何时博',
'成功需要成本,时间也是一种成本,对时间的珍惜就是对成本的节约',
'有志者自有千方百计,无志者只感千难万难',
'积一时之跬步,臻千里之遥程'
]);
</script>
<style lang="less" scoped>
.workplace-user-card {
.ele-cell-content {
overflow: hidden;
}
h4 {
margin-bottom: 6px;
}
}
.workplace-count-group {
white-space: nowrap;
text-align: right;
flex-shrink: 0;
}
.workplace-count-item {
display: inline-block;
margin: 0 4px 0 24px;
}
.workplace-count-name {
margin-left: 8px;
}
@media screen and (max-width: 992px) {
.workplace-count-item {
margin: 0 2px 0 12px;
}
}
@media screen and (max-width: 768px) {
.workplace-user-card {
display: block;
}
.workplace-count-group {
margin-top: 8px;
}
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<ele-modal
:width="400"
:visible="visible"
:title="`邀请新成员`"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
:footer="null"
@ok="save"
>
<div
class="qrcode-list"
style="display: flex; justify-content: space-around"
>
<div>
<img :src="qrcode" width="240" height="240" />
<div
style="
display: flex;
justify-content: center;
font-size: 26px;
padding-top: 20px;
"
>使用微信扫一扫</div
>
</div>
</div>
</ele-modal>
</template>
<script lang="ts" setup>
import { User } from '@/api/system/user/model';
import { reactive, ref } from 'vue';
import { taskJoinQRCode } from '@/api/oa/task';
defineProps<{
// 弹窗是否打开
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'done', user: User): void;
(e: 'update:visible', visible: boolean): void;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const qrcode = ref('');
// 用户信息
const form = reactive<User>({
userId: undefined,
nickname: undefined
});
const save = () => {
emit('done', form);
};
taskJoinQRCode({}).then((text) => {
qrcode.value = String(text);
});
</script>
<style scoped lang="less"></style>

View File

@@ -1,112 +0,0 @@
<template>
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px' }">
<template #extra
><a @click="openUrl('/oa/task')" class="ele-text-placeholder"
>更多<RightOutlined /></a
></template>
<a-list :size="`small`" :split="false" :data-source="list">
<template #renderItem="{ item }">
<a-list-item>
<div class="app-box">
<div class="app-info">
<a
class="ele-text-secondary"
@click="openNew('/oa/task/detail/' + item.taskId)"
>
<a-typography-paragraph
ellipsis
:content="`【${item.taskType}】${item.name}`"
/>
</a>
</div>
<a class="ele-text-placeholder">
<a-tag v-if="item.progress === TOBEARRANGED" color="red"
>待安排</a-tag
>
<a-tag v-if="item.progress === PENDING" color="orange"
>待处理</a-tag
>
<a-tag v-if="item.progress === PROCESSING" color="purple"
>处理中</a-tag
>
<a-tag v-if="item.progress === TOBECONFIRMED" color="cyan"
>待评价</a-tag
>
<a-tag v-if="item.progress === COMPLETED" color="green"
>已完成</a-tag
>
<a-tag v-if="item.progress === CLOSED">已关闭</a-tag>
<div class="ele-text-danger" v-if="item.overdueDays">
已逾期{{ item.overdueDays }}
</div>
</a>
</div>
</a-list-item>
</template>
</a-list>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { openUrl } from '@/utils/common';
import { Task } from '@/api/oa/task/model';
import { pageTask } from '@/api/oa/task';
import { useUserStore } from '@/store/modules/user';
import {
CLOSED,
COMPLETED,
PENDING,
PROCESSING,
TOBEARRANGED,
TOBECONFIRMED
} from '@/api/oa/task/model/progress';
import { RightOutlined } from '@ant-design/icons-vue';
const props = defineProps<{
title: string;
}>();
const list = ref<Task[]>([]);
/**
* 加载数据
*/
const reload = () => {
const { title } = props;
const userStore = useUserStore();
const where = {
userId: undefined,
commander: userStore.info?.userId,
limit: 6,
status: 0
};
// 加载列表
pageTask(where).then((data) => {
if (data?.list) {
list.value = data.list;
}
});
};
reload();
</script>
<script lang="ts">
export default {
name: 'DashboardArticleList'
};
</script>
<style lang="less" scoped>
.app-box {
display: flex;
width: 100%;
justify-content: space-between;
overflow: hidden;
.app-info {
display: flex;
margin-left: 5px;
flex-direction: column;
width: 400px;
}
}
</style>

View File

@@ -1,84 +0,0 @@
<!-- 小组成员 -->
<template>
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }">
<template #extra>
<more-icon @remove="onRemove" @edit="onEdit" />
</template>
<div
v-for="(item, index) in userList"
:key="index"
class="ele-cell user-list-item"
>
<div style="flex-shrink: 0">
<a-avatar :size="46" :src="item.avatar" />
</div>
<div class="ele-cell-content">
<span class="ele-cell-title ele-elip">{{ item.nickname }}</span>
<div class="ele-cell-desc ele-elip">{{ item.phone }}</div>
</div>
<div style="flex-shrink: 0">
<a-tag :color="['green', 'red'][item.status]">
{{ ['在线', '离线'][item.status] }}
</a-tag>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MoreIcon from './more-icon.vue';
import { pageUsers } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
defineProps<{
title?: string;
}>();
const emit = defineEmits<{
(e: 'remove'): void;
(e: 'edit'): void;
}>();
// 小组成员数据
const userList = ref<User[]>([]);
/* 查询小组成员 */
const queryUserList = () => {
pageUsers({ parentId: 11, limit: 5 }).then((data: any) => {
userList.value = data.list;
});
};
const onRemove = () => {
emit('remove');
};
const onEdit = () => {
emit('edit');
};
queryUserList();
</script>
<style lang="less" scoped>
.user-list-item {
padding: 12px 18px;
& + .user-list-item {
border-top: 1px solid hsla(0, 0%, 60%, 0.15);
}
.ele-cell-content {
overflow: hidden;
}
.ele-cell-desc {
margin-top: 0;
}
.ant-tag {
margin: 0;
}
}
</style>

View File

@@ -1,365 +0,0 @@
<template>
<div class="ele-body ele-body-card">
<a-row :gutter="16" ref="wrapRef">
<a-col :md="6">
<a-card hoverable>
<div class="flex justify-between leading-5">
<div class="title font-bold">文章</div>
<div class="count font-bold">20</div>
</div>
</a-card>
</a-col>
<a-col :md="6">
<a-card hoverable>
<div class="flex justify-between leading-5">
<div class="title font-bold">商品</div>
<div class="count font-bold">1</div>
</div>
</a-card>
</a-col>
<a-col :md="6">
<a-card hoverable>
<div class="flex justify-between leading-5">
<div class="title font-bold">用户</div>
<div class="count font-bold">658</div>
</div>
</a-card>
</a-col>
<a-col :md="6">
<a-card hoverable>
<div class="flex justify-between leading-5">
<div class="title font-bold">订单</div>
<div class="count font-bold">1820</div>
</div>
</a-card>
</a-col>
</a-row>
<!-- <a-row :gutter="16" ref="wrapRef">-->
<!-- <a-col-->
<!-- v-for="(item, index) in data"-->
<!-- :key="item.name"-->
<!-- :lg="item.lg"-->
<!-- :md="item.md"-->
<!-- :sm="item.sm"-->
<!-- :xs="item.xs"-->
<!-- >-->
<!-- <component-->
<!-- :is="item.name"-->
<!-- :title="item.title"-->
<!-- @remove="onRemove(index)"-->
<!-- @edit="onEdit(index)"-->
<!-- />-->
<!-- </a-col>-->
<!-- </a-row>-->
<!-- <a-card :bordered="false" :body-style="{ padding: 0 }">-->
<!-- <div class="ele-cell" style="line-height: 42px">-->
<!-- <div-->
<!-- class="ele-cell-content ele-text-primary workplace-bottom-btn"-->
<!-- @click="add"-->
<!-- >-->
<!-- <PlusCircleOutlined /> 添加视图-->
<!-- </div>-->
<!-- <a-divider type="vertical" />-->
<!-- <div-->
<!-- class="ele-cell-content ele-text-primary workplace-bottom-btn"-->
<!-- @click="reset"-->
<!-- >-->
<!-- <UndoOutlined /> 重置布局-->
<!-- </div>-->
<!-- </div>-->
<!-- </a-card>-->
<ele-modal
:width="680"
v-model:visible="visible"
title="未添加的视图"
:footer="null"
>
<a-row :gutter="16">
<a-col
v-for="item in notAddedData"
:key="item.name"
:md="8"
:sm="12"
:xs="24"
>
<div
class="workplace-card-item ele-border-split"
@click="addView(item)"
>
<div class="workplace-card-header ele-border-split">
{{ item.title }}
</div>
<div class="workplace-card-body ele-text-placeholder">
<plus-circle-outlined />
</div>
</div>
</a-col>
</a-row>
<a-empty v-if="!notAddedData.length" description="已添加所有视图" />
</ele-modal>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import SortableJs from 'sortablejs';
import type { Row as ARow } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import Article from './components/article-list.vue';
import Link from './components/link-list.vue';
import App from './components/app-list.vue';
import Task from './components/task-card.vue';
import {
PlusCircleOutlined,
SoundOutlined,
RightOutlined
} from '@ant-design/icons-vue';
import { openNew } from '@/utils/common';
const CACHE_KEY = 'workplace-layout';
const domain = ref('');
import { getSiteInfo } from '@/api/layout';
getSiteInfo().then((data) => {
if (data?.domain) {
domain.value = data.domain;
if (data.domain.indexOf('http') == 0) {
localStorage.setItem('Domain', domain.value);
} else {
localStorage.setItem('Domain', `http://${domain.value}`);
}
}
});
interface ViewItem {
name: string;
title: string;
lg: number;
md: number;
sm: number;
xs: number;
}
// 默认布局
const DEFAULT: ViewItem[] = [
{
name: 'link',
title: '网址导航',
lg: 24,
md: 24,
sm: 24,
xs: 24
},
{
name: 'task-card',
title: '我的工单',
lg: 18,
md: 24,
sm: 24,
xs: 24
},
// {
// name: 'project-card',
// title: '项目管理',
// lg: 16,
// md: 24,
// sm: 24,
// xs: 24
// },
{
name: 'user-list',
title: '小组成员',
lg: 6,
md: 24,
sm: 24,
xs: 24
}
// {
// name: 'activities-card',
// title: '最新动态',
// lg: 6,
// md: 24,
// sm: 24,
// xs: 24
// },
// {
// name: 'goal-card',
// title: '本月目标',
// lg: 8,
// md: 24,
// sm: 24,
// xs: 24
// },
// {
// name: 'docs',
// title: '知识库',
// lg: 8,
// md: 24,
// sm: 24,
// xs: 24
// }
];
// 获取缓存的顺序
const cache = (() => {
const str = localStorage.getItem(CACHE_KEY);
try {
return str ? JSON.parse(str) : null;
} catch (e) {
return null;
}
})();
const data = ref<ViewItem[]>([...(cache ?? DEFAULT)]);
const visible = ref(false);
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
let sortableIns: SortableJs | null = null;
// 未添加的数据
const notAddedData = computed(() => {
return DEFAULT.filter((d) => !data.value.some((t) => t.name === d.name));
});
/* 添加 */
const add = () => {
visible.value = true;
};
/* 重置布局 */
const reset = () => {
data.value = [...DEFAULT];
cacheData();
message.success('已重置');
};
/* 缓存布局 */
const cacheData = () => {
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
};
/* 删除视图 */
const onRemove = (index: number) => {
data.value = data.value.filter((_d, i) => i !== index);
cacheData();
};
/* 编辑视图 */
const onEdit = (index: number) => {
data.value.map((d) => {
if (d.name == 'user-list') {
}
});
// message.info('点击了编辑');
};
/* 添加视图 */
const addView = (item) => {
data.value.push(item);
cacheData();
message.success('已添加');
};
onMounted(() => {
const isTouchDevice = 'ontouchstart' in document.documentElement;
if (isTouchDevice) {
return;
}
sortableIns = new SortableJs(wrapRef.value?.$el, {
handle: '.ant-card-head',
animation: 300,
onUpdate: ({ oldIndex, newIndex }) => {
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
const temp = [...data.value];
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
data.value = temp;
cacheData();
}
},
setData: () => {}
});
});
onBeforeUnmount(() => {
if (sortableIns) {
sortableIns.destroy();
}
});
</script>
<script lang="ts">
import ActivitiesCard from './components/activities-card.vue';
import TaskCard from './components/task-card.vue';
import GoalCard from './components/goal-card.vue';
import UserList from './components/count-user.vue';
import Docs from './components/docs.vue';
import LinkCard from './components/link-card.vue';
import ProfileCard from './components/profile-card.vue';
export default {
name: 'DashboardWorkplace',
components: {
LinkCard,
UserList,
ActivitiesCard,
TaskCard,
GoalCard,
Docs,
ProfileCard
}
};
</script>
<style lang="less" scoped>
.ele-body :deep(.ant-card-head) {
cursor: move;
position: relative;
}
.ele-body :deep(.ant-row > .ant-col.sortable-chosen > .ant-card) {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
}
.workplace-bottom-btn {
text-align: center;
cursor: pointer;
transition: background-color 0.2s;
}
.workplace-bottom-btn:hover {
background: hsla(0, 0%, 60%, 0.05);
}
/* 添加弹窗 */
.workplace-card-item {
margin-bottom: 15px;
border-width: 1px;
border-style: solid;
border-radius: 4px;
position: relative;
cursor: pointer;
transition: box-shadow 0.2s, background-color 0.2s;
}
.workplace-card-item:hover {
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
background: hsla(0, 0%, 60%, 0.05);
}
.workplace-card-item .workplace-card-header {
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 8px;
}
.gg-title {
padding: 0 5px;
margin-right: 20px;
}
.workplace-card-body {
font-size: 26px;
padding: 24px 10px;
text-align: center;
}
</style>

View File

@@ -22,17 +22,12 @@
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<a
@click="
openPreview(
`https://${record.tenantId}.wsdns.cn${record.path}`
)
"
>{{ record.name }}</a
>
<a @click="openUrl(`${domain}${record.path}`)">{{
record.name
}}</a>
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
<span class="ele-text-placeholder">{{ `${record.path}` }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
@@ -70,7 +65,7 @@
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { createVNode, ref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
@@ -87,7 +82,8 @@
} from '@/api/cms/design';
import type { Design, DesignParam } from '@/api/cms/design/model';
import { useRouter } from 'vue-router';
import { openPreview } from '@/utils/common';
import { openPreview, openUrl } from "@/utils/common";
import { getSiteDomain } from '@/utils/domain';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -100,7 +96,8 @@
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
const domain = localStorage.getItem('Domain');
// 网站域名
const domain = getSiteDomain();
// 加载状态
const loading = ref(true);

View File

@@ -20,7 +20,6 @@
<a-input
allow-clear
placeholder="site_name"
:disabled="isUpdate"
v-model:value="form.name"
/>
</a-form-item>

View File

@@ -0,0 +1,261 @@
<!-- 链接编辑弹窗 -->
<template>
<ele-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '修改链接' : '添加链接'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="名称" name="name">
<a-input
allow-clear
:maxlength="50"
placeholder="链接名称"
v-model:value="form.name"
/>
</a-form-item>
<a-form-item label="网址" name="url">
<a-input
allow-clear
:maxlength="200"
placeholder="https://www.baidu.com"
v-model:value="form.url"
/>
</a-form-item>
<a-form-item label="类型" name="linkType">
<a-select
optionFilterProp="label"
placeholder="请选择链接类型"
:options="linkType"
v-model:value="form.linkType"
/>
</a-form-item>
<a-form-item label="图标" name="icon">
<SelectFile
:placeholder="`请选择视频文件`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
<!-- <ele-image-upload-->
<!-- v-model:value="images"-->
<!-- :limit="1"-->
<!-- :item-style="{ width: '40px', height: '40px' }"-->
<!-- :upload-handler="uploadHandler"-->
<!-- @upload="onUpload"-->
<!-- />-->
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="备注">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { addLink, updateLink } from '@/api/cms/link';
import type { Link } from '@/api/cms/link/model';
import { urlReg } from 'ele-admin-pro';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { uploadFile } from '@/api/system/file';
import { getDictionaryOptions } from '@/utils/common';
import { FileRecord } from '@/api/system/file/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Link | null;
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 链接类型
const linkType = getDictionaryOptions('linkType');
// 已上传数据
const images = ref<ItemType[]>([]);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Link>({
id: undefined,
name: '',
url: '',
sortNumber: 100,
linkType: undefined,
comments: undefined
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
name: [
{
required: true,
type: 'string',
trigger: 'blur'
}
],
linkType: [
{
required: true,
type: 'string',
trigger: 'blur'
}
],
url: [
{
required: true,
message: '请输入正确的URL',
type: 'string',
trigger: 'blur'
}
]
});
// 上传文件
const onUpload = (item: any) => {
const { file } = item;
uploadFile(file)
.then((data) => {
images.value.push({
uid: data.id,
url: data.url,
status: 'done'
});
form.icon = data.url;
message.success('上传成功');
})
.catch((e) => {
message.error(e.message);
});
};
/* 上传事件 */
const uploadHandler = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (file.size / 1024 / 1024 > 1) {
message.error('大小不能超过 1MB');
return;
}
onUpload(item);
};
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.icon = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateLink : addLink;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignFields(props.data);
images.value = [];
if (props.data.icon) {
images.value.push({
uid: 1,
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
images.value = [];
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>

View File

@@ -0,0 +1,106 @@
<!-- 搜索表单 -->
<template>
<div style="display: flex; justify-content: space-between">
<a-space style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加网址</span>
</a-button>
<DictSelect
dict-code="linkType"
v-model:value="where.linkType"
:placeholder="`选择分类`"
style="width: 200px"
@change="search"
/>
<a-input-search
allow-clear
placeholder="搜索关键词"
v-model:value="where.keywords"
@pressEnter="search"
@search="search"
/>
<a-button @click="reset">重置</a-button>
<a-button
v-if="hasRole('superAdmin')"
@click="openUrl(`/website/link/dict`)"
>设置分类</a-button
>
</a-space>
<a-space :size="10" style="flex-wrap: wrap; margin-right: 20px">
<a-button
danger
v-if="selection?.length"
class="ele-btn-icon"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
</a-space>
</div>
</template>
<script lang="ts" setup>
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import useSearch from '@/utils/use-search';
import { watch } from 'vue';
import DictSelect from '@/components/DictSelect/index.vue';
import { LinkParam } from '@/api/cms/link/model';
import { openUrl } from '@/utils/common';
import { hasRole } from '@/utils/permission';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: LinkParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'advanced'): void;
}>();
// 表单数据
const { where, resetFields } = useSearch<LinkParam>({
linkType: undefined,
name: undefined,
keywords: ''
});
/* 搜索 */
const search = () => {
emit('search', {
...where
});
};
// 发布应用
const add = () => {
emit('add');
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -4,7 +4,7 @@
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '修改位置' : '添加位置'"
:title="isUpdate ? '修改分类' : '添加分类'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
@@ -20,7 +20,6 @@
>
<a-form-item label="分类标识" name="dictCode">
<a-input
allow-clear
:maxlength="20"
disabled
placeholder="请输入分类标识"
@@ -65,7 +64,7 @@
import useFormData from '@/utils/use-form-data';
import { addDictData, updateDictData } from '@/api/system/dict-data';
import { DictData } from '@/api/system/dict-data/model';
import { removeSiteInfoCache } from "@/api/cms/website";
import { removeSiteInfoCache } from '@/api/cms/website';
//
const themeStore = useThemeStore();
@@ -97,9 +96,10 @@
//
const { form, resetFields, assignFields } = useFormData<DictData>({
dictId: undefined,
dictName: '',
dictDataId: undefined,
dictDataName: '',
dictCode: 'mpPosition',
dictCode: 'linkType',
dictDataCode: '',
sortNumber: 100,
comments: ''
@@ -137,11 +137,12 @@
const saveOrUpdate = isUpdate.value ? updateDictData : addDictData;
form.dictDataCode = form.dictDataName;
form.dictId = props.dictId;
form.dictName = '链接分类';
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
//
//
removeSiteInfoCache(form.dictCode + ':' + form.tenantId);
updateVisible(false);
emit('done');

View File

@@ -85,7 +85,7 @@
width: 80
},
{
title: '名称',
title: '分类名称',
dataIndex: 'dictDataName',
showSorterTooltip: false
},
@@ -120,7 +120,7 @@
//
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where.dictCode = 'mpPosition';
where.dictCode = 'linkType';
return pageDictData({ ...where, ...orders, page, limit });
};
@@ -180,11 +180,11 @@
//
const loadDict = () => {
listDictionaries({ dictCode: 'mpPosition' }).then(async (data) => {
listDictionaries({ dictCode: 'linkType' }).then(async (data) => {
if (data?.length == 0) {
await addDict({ dictCode: 'mpPosition', dictName: '菜单位置' });
await addDict({ dictCode: 'linkType', dictName: '链接分类' });
}
await listDictionaries({ dictCode: 'mpPosition' }).then((list) => {
await listDictionaries({ dictCode: 'linkType' }).then((list) => {
list?.map((d) => {
dictId.value = Number(d.dictId);
});
@@ -206,6 +206,6 @@
<script lang="ts">
export default {
name: 'GroupIdDict'
name: 'TaskDictData'
};
</script>

View File

@@ -0,0 +1,164 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:scroll="{ x: 800 }"
cache-key="proSystemLinkTable"
>
<template #toolbar>
<LinkSearch @add="openEdit" @search="reload" />
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'link'">
<a-avatar
shape="square"
:width="40"
:src="record.icon"
style="margin-right: 5px"
/>
<a @click="openUrl(record.url)">{{ record.name }}</a>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click.stop="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
placement="topRight"
title="确定要删除此模块吗?"
@confirm.stop="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<link-edit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue/es';
import { AntDesignOutlined, PlusOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro/es';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import { messageLoading } from 'ele-admin-pro/es';
import LinkEdit from './components/link-edit.vue';
import { pageLink, removeLink } from '@/api/cms/link';
import type { Link, LinkParam } from '@/api/cms/link/model';
import { Menu } from '@/api/system/menu/model';
import { openNew, openUrl } from '@/utils/common';
import LinkSearch from './components/link-search.vue';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
// {
// key: 'index',
// width: 48,
// align: 'center',
// fixed: 'left',
// hideInSetting: true,
// customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
// },
{
title: '名称',
dataIndex: 'link',
key: 'link'
},
{
title: '备注',
dataIndex: 'comments'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 120,
showSorterTooltip: false,
customRender: ({ text }) => ['显示', '隐藏'][text]
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center'
}
]);
// 表格选中数据
const selection = ref<Link[]>([]);
// 当前编辑数据
const current = ref<Link | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
return pageLink({ ...where, ...orders, limit, page });
};
/* 搜索 */
const reload = (where?: LinkParam) => {
selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: Link) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 自定义行属性 */
const customRow = (record: Link) => {
return {
// 行点击事件
onClick: () => {
// openUrl(`${record.url}`);
},
// 行双击事件
onDblclick: () => {
openUrl(`${record.url}`);
}
};
};
/* 删除单个 */
const remove = (row: Link) => {
const hide = messageLoading('请求中..', 0);
removeLink(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
</script>
<script lang="ts">
export default {
name: 'SystemLink'
};
</script>

View File

@@ -68,7 +68,7 @@
</a-form-item>
</template>
<a-form-item label="位置" name="pageId">
<SelectMpAd
<SelectMpPages
:placeholder="`请选择页面`"
v-model:value="form.pageName"
@done="choosePageId"

View File

@@ -14,7 +14,6 @@
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:need-page="false"
tool-class="ele-toolbar-form"
class="sys-org-table"
>

View File

@@ -20,7 +20,6 @@
<a-input
allow-clear
placeholder="site_name"
:disabled="isUpdate"
v-model:value="form.name"
/>
</a-form-item>
@@ -33,7 +32,7 @@
v-if="!pageId"
extra="页面参数,留空视为全局参数"
>
<SelectMpAd
<SelectMpPages
:placeholder="`请选择页面`"
v-model:value="form.pageName"
@done="choosePageId"

View File

@@ -1,279 +0,0 @@
<!-- 编辑弹窗 -->
<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="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"
/>
</a-form-item>
<a-form-item label="路由地址" name="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</a-form-item>
<a-form-item label="打开方式" name="target">
<DictSelect
dict-code="navType"
class="form-item"
placeholder="请选择链接方式"
v-model:value="form.target"
/>
</a-form-item>
<a-form-item label="所在行" name="rows">
<a-input-number
:min="0"
:max="3"
class="ele-fluid"
placeholder="请输入所在行"
v-model:value="form.rows"
/>
</a-form-item>
<a-form-item label="管理人员可见" name="adminShow">
<a-switch
checked-children=""
un-checked-children=""
:checked="form.adminShow === 1"
@update:checked="updateAdminShow"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
position: undefined,
rows: undefined,
active: undefined,
userId: 0,
adminShow: undefined,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
const updateAdminShow = (value: boolean) => {
form.adminShow = value ? 1 : 0;
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,52 +0,0 @@
<!-- 搜索表单 -->
<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 useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 0
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,550 +0,0 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid">
<div class="title-bar">
<span class="back"></span>
<span>{{ form.pageName || '个人中心' }}</span>
<a class="share" @click="onShare"></a>
</div>
</div>
</div>
<!-- 会员信息卡片 -->
<template v-if="form.showUserCard">
<a-popover>
<template #content> 点击更换背景 </template>
<div
class="user-card"
:style="{
backgroundImage: 'url(' + param.mp_user_top + ')'
}"
@click="
openUserCard({
name: 'mp_user_top',
value: param.mp_user_top,
comments: '小程序我的顶部背景图片',
sortNumber: 100
})
"
>
<div class="user-avatar" @click.stop="onAvatar">
<a-avatar :src="param.site_logo" :size="60" />
<div class="user-info">
<div class="nickname">昵称</div>
<div class="phone">手机号码</div>
</div>
</div>
</div>
</a-popover>
<UserCardEdit
v-model:visible="showUserCardEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 订单卡片 -->
<template v-if="form.showOrderCard">
<div class="order-card ele-cell">
<div
v-for="(item, index) in order"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</div>
</template>
<template v-if="form.showToolsCard">
<div class="tools-card">
<div
v-for="(item, index) in server"
:key="index"
class="ele-cell"
@click="openMpMenuEdit(item)"
>
<a-avatar :src="item.icon" :size="24" />
<div
class="title ele-cell-content"
:style="{ color: item.color || '#333333' }"
>{{ item.title }}</div
>
<RightOutlined class="ele-text-secondary" />
</div>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<template v-if="form.showMenuCard">
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<!-- 幻灯片轮播 -->
<template v-if="form.showCarousel">
<a-carousel arrows autoplay :dots="true">
<template v-if="adImageList">
<template v-for="(img, index) in adImageList" :key="index">
<div class="ad-item">
<a-image
:preview="false"
:src="img.url"
width="100%"
height="200px"
/>
</div>
</template>
</template>
</a-carousel>
</template>
<!-- 导航菜单 -->
<template v-if="form.showMenuCard">
<div class="menu-card ele-cell">
<div
v-for="(item, index) in scrollList"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 商户列表 -->
<template v-if="form.showShopCard">
<div class="merchant-card-title">场地预定</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
<!-- <div class="">-->
<!-- <a-button>我要去</a-button>-->
<!-- </div>-->
</div>
</template>
<!-- 培训课程 -->
<template v-if="form.showTtrainCard">
<div class="merchant-card-title">培训课程</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
</div>
</template>
</div>
</template>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<a
class="home-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/home`)"
>
<HomeOutlined class="icon" />
<span>首页</span>
</a>
<a
class="shop-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/shop`)"
>
<ShopOutlined class="icon" />
<span>商城</span>
</a>
<a
class="order-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/order`)"
>
<ProfileOutlined class="icon" />
<span>订单</span>
</a>
<a
class="user-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/user`)"
>
<UserOutlined class="icon ele-text-danger" />
<span class="ele-text-danger">我的</span>
</a>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import {
ProfileOutlined,
ShopOutlined,
HomeOutlined,
UserOutlined,
RightOutlined
} from '@ant-design/icons-vue';
import { ref, unref, watch } from 'vue';
import { MpWeixinParam, WebsiteField } from '@/api/cms/website/field/model';
import { listWebsiteField } from '@/api/system/website/field';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { listMpMenu } from '@/api/cms/mp-menu';
import { listAd } from '@/api/cms/ad';
import { listMerchant } from '@/api/shop/merchant';
import { Merchant } from '@/api/shop/merchant/model';
import { openUrl } from '@/utils/common';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
const { query } = unref(currentRoute);
import MpMenuEdit from './mpMenuEdit.vue';
import UserCardEdit from '@/views/cms/field/components/website-field-edit.vue';
const prpos = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
type?: number;
list?: any[] | null;
refresh?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const param = ref<MpWeixinParam>({});
const showUserCardEdit = ref(false);
const showMpMenuEdit = ref(false);
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 幻灯片广告
const adImageList = ref<any[]>();
// 首页导航图标
const scrollList = ref<any[]>();
// 订单图标
const order = ref<any[]>();
// 服务图标
const server = ref<any[]>();
// 商户列表
const shopList = ref<Merchant[]>();
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
/* 打开编辑弹窗 */
const openUserCard = (row?: WebsiteField) => {
current.value = row ?? null;
showUserCardEdit.value = true;
};
/* 打开编辑弹窗 */
const openMpMenuEdit = (row?: MpMenu) => {
current.value = row ?? null;
showMpMenuEdit.value = true;
};
const onShare = () => {};
const onAvatar = () => {};
const reload = () => {
listWebsiteField({}).then((list) => {
list.map((d) => {
const key = String(d.name);
param.value[key] = d.value;
});
});
listMpMenu({ type: 4 }).then((list) => {
server.value = list.filter((d) => d.type == 0);
order.value = list.filter((d) => d.rows == 0);
scrollList.value = list.filter((d) => d.type == 2);
});
listAd({ adType: '幻灯片' }).then((res) => {
const carouselImages = res[0].images;
if (carouselImages) {
adImageList.value = JSON.parse(carouselImages);
}
});
listMerchant({}).then((list) => {
shopList.value = list;
});
emit('done');
};
reload();
watch(
() => prpos.refresh,
(refresh) => {
if (refresh) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 16px;
width: 390px;
height: 844px;
background: url('@/assets/img/app-ui.png');
background-repeat: no-repeat;
background-position: top;
background-size: 100%;
//position: relative;
padding: 0 16px;
.phone-header-black {
height: 99px;
border-radius: 20px 20px 0 0;
background-size: 100%;
.title {
height: 99px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: end;
padding-bottom: 13px;
.title-bar {
width: 100%;
display: flex;
justify-content: space-between;
.back {
display: block;
width: 50px;
margin-left: 3px;
background-color: #ffffff;
}
.share {
display: block;
width: 50px;
cursor: pointer;
}
}
}
}
.phone-body-bg {
padding: 0 16px;
height: 680px;
border-radius: 0 0 44px 44px;
overflow: hidden;
}
.phone-body {
width: 356px;
margin-left: 17px;
height: 630px;
padding: 0;
position: absolute;
top: 98px;
left: 0;
z-index: 999;
.comments {
padding: 20px;
word-break: break-all;
}
.form-data {
padding: 10px 20px;
.submit-btn {
padding: 30px 0;
}
}
}
.goods {
.price {
font-size: 18px;
}
.ele-cell-title {
font-weight: 600;
font-size: 16px;
}
.goods-attr {
}
}
.goods-divider {
height: 6px;
}
.buy-bar {
position: fixed;
width: 356px;
bottom: 70px;
//top: 881px;
background-color: #ffffff;
border-radius: 0 0 44px 44px;
border-top: 1px solid var(--grey-8);
.ele-cell-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.shop-btn,
.kefu-btn,
.star-btn {
display: flex;
flex-direction: column;
font-size: 13px;
padding: 0 9px;
cursor: pointer;
white-space: nowrap;
}
.icon {
font-size: 19px;
}
.buy-btn {
display: flex;
.add-cart {
border-radius: 100px 0 0 100px;
border: none;
background-color: var(--orange-5);
color: #ffffff;
height: 40px;
width: 95px;
}
.buy-now {
border-radius: 0 100px 100px 0;
border: none;
background-color: var(--red-6);
color: #ffffff;
height: 40px;
width: 95px;
}
}
}
}
:deep(.slick-slide) {
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
font-size: 38px;
}
:deep(.slick-arrow.custom-slick-arrow) {
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide h3) {
color: #fff;
}
.user-card {
height: 170px;
margin: 0 1px;
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
display: flex;
.user-avatar {
margin-left: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.user-info {
margin-left: 10px;
.nickname {
color: var(--grey-3);
font-size: 18px;
font-weight: 500;
}
.phone {
color: var(--grey-5);
}
}
}
}
.order-card {
width: 340px;
height: 80px;
margin: 0 1px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 230px;
left: 24px;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.tools-card {
width: 340px;
margin: 0 1px;
padding: 6px 16px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 324px;
left: 24px;
.ele-cell {
padding: 4px 0;
border-bottom: 1px solid var(--grey-9);
cursor: pointer;
}
.btn-center {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -1,296 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white" style="display: ">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>修改完后需要<a @click="clearSiteInfoCache">更新缓存</a
>才会生效</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" :refresh="refresh" @done="reload" />
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import Simulator from './components/simulator.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle, openUrl } from '@/utils/common';
import { removeSiteInfoCache } from '@/api/cms/website';
import success from '@/views/result/success/index.vue';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 页面标题
const title = getPageTitle();
const type = ref<number>(2);
const refresh = ref(false);
// 模拟器的字段
const form = ref<any>({
pageName: '管理中心',
showUserCard: true,
showOrderCard: true,
showToolsCard: true
});
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.type = 4;
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path'
},
// {
// title: '分类ID',
// dataIndex: 'type',
// key: 'type',
// align: 'center'
// },
{
title: '所在行',
dataIndex: 'rows',
key: 'rows',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
if (where?.type) {
type.value = Number(where?.type);
} else {
type.value = 0;
}
refresh.value = !refresh.value;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success('缓存已更新');
}
}
);
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
</script>
<script lang="ts">
export default {
name: 'MpMenuUser'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,279 +0,0 @@
<!-- 编辑弹窗 -->
<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="type">
<a-select
v-model:value="form.type"
placeholder="页面"
:style="`width: 200px`"
>
<a-select-option :value="2">首页</a-select-option>
<a-select-option :value="3">商城</a-select-option>
<a-select-option :value="1">订单</a-select-option>
<a-select-option :value="0">我的</a-select-option>
<a-select-option :value="4">管理</a-select-option>
</a-select>
</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="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</a-form-item>
<a-form-item label="所在行" name="rows">
<a-input-number
:min="0"
:max="3"
class="ele-fluid"
placeholder="请输入所在行"
v-model:value="form.rows"
/>
</a-form-item>
<a-form-item label="打开方式" name="target">
<DictSelect
dict-code="navType"
class="form-item"
placeholder="请选择链接方式"
v-model:value="form.target"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
rows: 0,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
position: undefined,
active: undefined,
userId: 0,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,58 +0,0 @@
<!-- 搜索表单 -->
<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 { ref, watch } from 'vue';
import useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 2
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
const menuType = ref(0);
const onChange = () => {
console.log(menuType.value);
}
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,573 +0,0 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid">
<div class="title-bar">
<span class="back"></span>
<span>{{ form.pageName || '首页' }}</span>
<a class="share" @click="onShare"></a>
</div>
</div>
</div>
<template v-if="form.showMenuCard">
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<!-- 幻灯片轮播 -->
<template v-if="form.showCarousel">
<div
class="ad-bar"
:style="{
backgroundImage: 'url(' + param.mp_user_top + ')'
}"
@click="onCarousel"
>
<a-carousel arrows autoplay :dots="true">
<template v-if="adImageList">
<template v-for="(img, index) in adImageList" :key="index">
<div class="ad-item">
<a-image
:preview="false"
:src="img.url"
width="100%"
height="200px"
/>
</div>
</template>
</template>
</a-carousel>
</div>
</template>
<!-- 导航菜单 -->
<template v-if="form.showMenuCard">
<!-- 单排 -->
<div class="order-card ele-cell">
<div
v-for="(item, index) in navigation"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="40" :preview="false" />
<span style="white-space: nowrap">{{ item.title }}</span>
</div>
</div>
<!-- 双排 -->
<div class="menu-card ele-cell">
<template v-for="(item, index) in navigation1" :key="index">
<div
v-if="index < 4"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-avatar :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</template>
</div>
<div class="menu-card ele-cell">
<template v-for="(item, index) in navigation2" :key="index">
<div
v-if="index < 4"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-avatar :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</template>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 商户列表 -->
<template v-if="form.showShopCard">
<div class="merchant-card-title">场地预定</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
<!-- <div class="">-->
<!-- <a-button>我要去</a-button>-->
<!-- </div>-->
</div>
</template>
<!-- 培训课程 -->
<template v-if="form.showTtrainCard">
<div class="merchant-card-title">培训课程</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
</div>
</template>
</div>
</template>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<a
class="home-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/home`)"
>
<HomeOutlined class="icon ele-text-danger" />
<span class="ele-text-danger">首页</span>
</a>
<a
class="shop-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/shop`)"
>
<ShopOutlined class="icon" />
商城
</a>
<a
class="order-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/order`)"
>
<ProfileOutlined class="icon" />
订单
</a>
<a
class="user-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/user`)"
>
<UserOutlined class="icon" />
我的
</a>
</div>
</a-card>
<AdEdit v-model:visible="showAdEdit" :data="ad" @done="reload" />
</div>
</template>
<script lang="ts" setup>
import {
ProfileOutlined,
ShopOutlined,
HomeOutlined,
UserOutlined
} from '@ant-design/icons-vue';
import { ref, unref, watch } from 'vue';
import { MpWeixinParam, WebsiteField } from '@/api/cms/website/field/model';
import { listWebsiteField } from '@/api/system/website/field';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { listMpMenu } from '@/api/cms/mp-menu';
import { listAd } from '@/api/cms/ad';
import { listMerchant } from '@/api/shop/merchant';
import { Merchant } from '@/api/shop/merchant/model';
import { openUrl } from '@/utils/common';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
const { query } = unref(currentRoute);
import MpMenuEdit from './mpMenuEdit.vue';
import { Ad } from '@/api/cms/ad/model';
import AdEdit from '@/views/cms/ad/components/ad-edit.vue';
const prpos = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
type?: number;
list?: any[] | null;
refresh?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const param = ref<MpWeixinParam>({});
const showUserCardEdit = ref(false);
const showMpMenuEdit = ref(false);
const showAdEdit = ref(false);
const ad = ref<Ad>();
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 幻灯片广告
const adImageList = ref<any[]>();
// 首页导航图标
const scrollList = ref<any>();
// 单排
const navigation = ref<MpMenu[]>();
// 第一排
const navigation1 = ref<any[]>();
// 第二排
const navigation2 = ref<any[]>();
// 订单图标
const order = ref<any[]>();
// 服务图标
const server = ref<any[]>();
// 商户列表
const shopList = ref<Merchant[]>();
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
/* 打开编辑弹窗 */
const openUserCard = (row?: WebsiteField) => {
current.value = row ?? null;
showUserCardEdit.value = true;
};
/* 打开编辑弹窗 */
const openMpMenuEdit = (row?: MpMenu) => {
current.value = row ?? null;
showMpMenuEdit.value = true;
};
const onShare = () => {};
const onCarousel = (row?: Ad) => {
showAdEdit.value = true;
};
const reload = () => {
listWebsiteField({}).then((list) => {
list.map((d) => {
const key = String(d.name);
param.value[key] = d.value;
});
});
listMpMenu({}).then((list) => {
server.value = list.filter((d) => d.type == 0);
order.value = list.filter((d) => d.type == 1);
navigation.value = list.filter((d) => d.type == 2 && d.rows == 0);
navigation1.value = list.filter((d) => d.type == 2 && d.rows == 1);
navigation2.value = list.filter((d) => d.type == 2 && d.rows == 2);
});
listAd({ adType: '幻灯片' }).then((res) => {
const carouselImages = res[0].images;
if (carouselImages) {
adImageList.value = JSON.parse(carouselImages);
}
});
listMerchant({}).then((list) => {
shopList.value = list;
});
emit('done');
};
reload();
watch(
() => prpos.refresh,
(refresh) => {
if (refresh) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 26px;
width: 390px;
height: 844px;
background: url('@/assets/img/app-ui.png');
background-repeat: no-repeat;
background-position: top;
background-size: 100%;
//position: relative;
padding: 0 16px;
.phone-header-black {
height: 99px;
border-radius: 20px 20px 0 0;
background-size: 100%;
.title {
height: 99px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: end;
padding-bottom: 13px;
.title-bar {
width: 100%;
display: flex;
justify-content: space-between;
.back {
display: block;
width: 50px;
margin-left: 3px;
background-color: #ffffff;
}
.share {
display: block;
width: 50px;
cursor: pointer;
}
}
}
}
.ad-bar {
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
}
.phone-body-bg {
padding: 0 16px;
height: 680px;
border-radius: 0 0 44px 44px;
overflow: hidden;
}
.phone-body {
width: 356px;
margin-left: 17px;
height: 630px;
padding: 0;
position: absolute;
top: 98px;
left: 0;
z-index: 999;
.comments {
padding: 20px;
word-break: break-all;
}
.form-data {
padding: 10px 20px;
.submit-btn {
padding: 30px 0;
}
}
}
.goods {
.price {
font-size: 18px;
}
.ele-cell-title {
font-weight: 600;
font-size: 16px;
}
.goods-attr {
}
}
.goods-divider {
height: 6px;
}
.buy-bar {
position: fixed;
width: 356px;
bottom: 70px;
//top: 881px;
background-color: #ffffff;
border-radius: 0 0 44px 44px;
border-top: 1px solid var(--grey-8);
.ele-cell-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.home-btn,
.shop-btn,
.order-btn,
.user-btn {
display: flex;
flex-direction: column;
font-size: 13px;
padding: 0 9px;
cursor: pointer;
white-space: nowrap;
}
.icon {
font-size: 19px;
}
.buy-btn {
display: flex;
.add-cart {
border-radius: 100px 0 0 100px;
border: none;
background-color: var(--orange-5);
color: #ffffff;
height: 40px;
width: 95px;
}
.buy-now {
border-radius: 0 100px 100px 0;
border: none;
background-color: var(--red-6);
color: #ffffff;
height: 40px;
width: 95px;
}
}
}
}
:deep(.slick-slide) {
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
font-size: 38px;
}
:deep(.slick-arrow.custom-slick-arrow) {
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide h3) {
color: #fff;
}
.user-card {
height: 170px;
margin: 0 1px;
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
display: flex;
.user-avatar {
margin-left: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.user-info {
margin-left: 10px;
.nickname {
color: var(--grey-3);
font-size: 18px;
font-weight: 500;
}
.phone {
color: var(--grey-5);
}
}
}
}
.menu-card {
width: 330px;
margin: 6px auto;
border-radius: 5px;
border-color: slategrey;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.merchant-card-title {
width: 340px;
margin: 6px auto;
font-size: 20px;
font-weight: 500;
margin-top: 16px;
}
.merchant-card {
width: 340px;
max-height: 80px;
margin: 6px auto;
margin-bottom: 16px;
padding: 8px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
.merchant-name {
font-weight: 500;
font-size: 15px;
}
.merchant-desc {
width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.tools-card {
width: 340px;
margin: 0 1px;
padding: 6px 16px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 324px;
left: 24px;
.ele-cell {
padding: 4px 0;
border-bottom: 1px solid var(--grey-9);
}
.btn-center {
display: flex;
align-items: center;
justify-content: center;
}
}
.order-card {
width: 330px;
height: 80px;
margin: 0 auto;
padding: 8px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
//position: absolute;
//top: 230px;
//left: 24px;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
</style>

View File

@@ -1,291 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white" style="display: ">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>修改完后需要<a @click="clearSiteInfoCache">清除缓存</a
>才会生效</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" :refresh="refresh" @done="reload" />
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, unref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import Simulator from './components/simulator.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle, openUrl } from '@/utils/common';
import { removeSiteInfoCache } from '@/api/cms/website';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 页面标题
const title = getPageTitle();
const type = ref<number>(2);
const refresh = ref(false);
// 模拟器的字段
const form = ref<any>({
pageName: '首页',
showOrderCard: true,
showCarousel: true,
showMenuCard: true,
showShopCard: true,
showTtrainCard: false
});
// 菜单列表
const list = ref<any[]>();
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.type = 2;
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path'
},
{
title: '所在行',
dataIndex: 'rows',
key: 'rows',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
type.value = Number(where?.type) || 0;
refresh.value = !refresh.value;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success('缓存已更新');
}
}
);
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
</script>
<script lang="ts">
export default {
name: 'MpMenuHome'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,261 +0,0 @@
<!-- 编辑弹窗 -->
<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="type">
<a-select
v-model:value="form.type"
placeholder="页面"
:style="`width: 200px`"
>
<a-select-option :value="2">首页</a-select-option>
<a-select-option :value="3">商城</a-select-option>
<a-select-option :value="1">订单</a-select-option>
<a-select-option :value="0">我的</a-select-option>
<a-select-option :value="4">管理</a-select-option>
</a-select>
</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="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 0,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
position: undefined,
active: undefined,
userId: 0,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,61 +0,0 @@
<!-- 搜索表单 -->
<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>
<span class="ele-text-placeholder" style="margin-left: 20px"
>菜单类型</span
>
<a-radio-group v-model:value="where.type" @change="handleSearch">
<a-radio-button :value="1">订单图标</a-radio-button>
<a-radio-button :value="0">功能图标</a-radio-button>
</a-radio-group>
</a-space>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { watch } from 'vue';
import useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 0
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,284 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>(https://www.iconfont.cn)</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" />
</div>
</a-page-header>
<div class="page">
<div class="ele-body">
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle } from '@/utils/common';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 页面标题
const title = getPageTitle();
const type = ref<number>(0);
// 模拟器的字段
const form = ref<any>({
pageName: '个人中心',
showUserCard: true,
showOrderCard: true,
showToolsCard: true
});
// 菜单列表
const list = ref<any[]>();
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path',
align: 'center'
},
{
title: '组件地址',
dataIndex: 'component',
key: 'component',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
type.value = Number(where?.type) || 0;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'MpMenuIndex'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,261 +0,0 @@
<!-- 编辑弹窗 -->
<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="type">
<a-select
v-model:value="form.type"
placeholder="页面"
:style="`width: 200px`"
>
<a-select-option :value="2">首页</a-select-option>
<a-select-option :value="3">商城</a-select-option>
<a-select-option :value="1">订单</a-select-option>
<a-select-option :value="0">我的</a-select-option>
<a-select-option :value="4">管理</a-select-option>
</a-select>
</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="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
position: undefined,
active: undefined,
userId: 0,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,52 +0,0 @@
<!-- 搜索表单 -->
<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 useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 2
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,533 +0,0 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid">
<div class="title-bar">
<span class="back"></span>
<span>{{ form.pageName || '订单' }}</span>
<a class="share" @click="onShare"></a>
</div>
</div>
</div>
<template v-if="form.showOrderCard">
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<div class="tabs-card">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="全部订单" />
<a-tab-pane key="2" tab="未支付" force-render />
<a-tab-pane key="3" tab="已支付" />
<a-tab-pane key="4" tab="已完成" />
<a-tab-pane key="5" tab="押金" />
</a-tabs>
</div>
<!-- 订单列表 -->
<template v-for="(item, index) in orderList" :key="index">
<a-card
size="small"
class="order-item"
:title="item.merchantName"
:body-style="{ padding: '8px' }"
>
<template #title>
{{ item.name }}
</template>
<template #extra
><span class="ele-text-success">已支付</span></template
>
<template #actions>
<div class="ele-cell card-actions">
<div class="card-actions-item" @click="actionsClick">
<a-button class="mini-btn" size="mini">申请开票</a-button>
</div>
</div>
</template>
<div class="order-body ele-cell ele-cell-align-top">
<!-- <uv-avatar :src="item.merchantAvatar" shape="square" size="90"></uv-avatar> -->
<div class="ele-cell-content">
<block
v-for="(sub, subIndex) in item.orderInfoList"
:key="subIndex"
>
<div class="ele-cell">
<div class="ele-text-secondary">时间</div>
<div class="ele-text-heading">{{ sub.dateTime }}</div>
</div>
<div class="ele-cell">
<div class="ele-text-secondary">场地</div>
<div class="ele-text-heading">{{ sub.fieldName }}</div>
</div>
<div class="ele-cell">
<div class="ele-text-secondary">金额</div>
<div class="ele-text-heading">{{ sub.price }}</div>
</div>
<div style="padding-bottom: 8px"></div>
</block>
<div class="ele-cell">
<div class="ele-text-placeholder" style="flex: 1;"
>共2件</div
>
<div class="ele-cell" style="font-weight: bold">
<div class="ele-text-heading"
>实付</div
>
<div class="ele-text-heading">{{ item.payPrice }}</div>
</div>
</div>
</div>
</div>
</a-card>
</template>
</div>
</template>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<a
class="home-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/home`)"
>
<HomeOutlined class="icon" />
<span>首页</span>
</a>
<a
class="shop-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/shop`)"
>
<ShopOutlined class="icon" />
<span>商城</span>
</a>
<a
class="order-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/order`)"
>
<ProfileOutlined class="icon ele-text-danger" />
<span class="ele-text-danger">订单</span>
</a>
<a
class="user-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/user`)"
>
<UserOutlined class="icon" />
我的
</a>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import {
ProfileOutlined,
ShopOutlined,
HomeOutlined,
UserOutlined
} from '@ant-design/icons-vue';
import { ref, unref, watch } from 'vue';
import { MpWeixinParam, WebsiteField } from '@/api/cms/website/field/model';
import { listWebsiteField } from '@/api/system/website/field';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { listMpMenu } from '@/api/cms/mp-menu';
import { listAd } from '@/api/cms/ad';
import { listMerchant } from '@/api/shop/merchant';
import { Merchant } from '@/api/shop/merchant/model';
import { openUrl } from '@/utils/common';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
const { query } = unref(currentRoute);
import MpMenuEdit from '@/views/cms/mpDesign/components/mpMenuEdit.vue';
import { listOrder, pageOrder } from '@/api/shop/order';
import { Order } from '@/api/shop/order/model';
const prpos = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
type?: number;
list?: any[] | null;
refresh?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const param = ref<MpWeixinParam>({});
const showUserCardEdit = ref(false);
const showMpMenuEdit = ref(false);
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 幻灯片广告
const adImageList = ref<any[]>();
// 首页导航图标
const scrollList = ref<any[]>();
// 订单图标
const order = ref<any[]>();
// 服务图标
const server = ref<any[]>();
// 商户列表
const shopList = ref<Merchant[]>();
// 订单列表
const orderList = ref<Order[]>();
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
/* 打开编辑弹窗 */
const openUserCard = (row?: WebsiteField) => {
current.value = row ?? null;
showUserCardEdit.value = true;
};
/* 打开编辑弹窗 */
const openMpMenuEdit = (row?: MpMenu) => {
current.value = row ?? null;
showMpMenuEdit.value = true;
};
const onShare = () => {};
const activeKey = ref('1');
const reload = () => {
// listWebsiteField({}).then((list) => {
// list.map((d) => {
// const key = String(d.name);
// param.value[key] = d.value;
// });
// });
// listMpMenu({}).then((list) => {
// server.value = list.filter((d) => d.type == 0);
// order.value = list.filter((d) => d.type == 1);
// scrollList.value = list.filter((d) => d.type == 2);
// });
//
// listAd({ adType: '幻灯片' }).then((res) => {
// const carouselImages = res[0].images;
// if (carouselImages) {
// adImageList.value = JSON.parse(carouselImages);
// }
// });
// listMerchant({}).then((list) => {
// shopList.value = list;
// });
pageOrder({ userId: 13448 }).then((res) => {
orderList.value = res?.list;
});
emit('done');
};
const actionsClick = () => {};
reload();
watch(
() => prpos.refresh,
(refresh) => {
if (refresh) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 26px;
width: 390px;
height: 844px;
background: url('@/assets/img/app-ui.png');
background-repeat: no-repeat;
background-position: top;
background-size: 100%;
//position: relative;
padding: 0 16px;
.phone-header-black {
height: 99px;
border-radius: 20px 20px 0 0;
background-size: 100%;
.title {
height: 99px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: end;
padding-bottom: 13px;
.title-bar {
width: 100%;
display: flex;
justify-content: space-between;
.back {
display: block;
width: 50px;
margin-left: 3px;
background-color: #ffffff;
}
.share {
display: block;
width: 50px;
cursor: pointer;
}
}
}
}
.phone-body-bg {
padding: 0 16px;
height: 680px;
border-radius: 0 0 44px 44px;
overflow: hidden;
}
.phone-body {
width: 356px;
margin-left: 17px;
height: 630px;
padding: 0;
position: absolute;
top: 98px;
left: 0;
z-index: 999;
.comments {
padding: 20px;
word-break: break-all;
}
.form-data {
padding: 10px 20px;
.submit-btn {
padding: 30px 0;
}
}
}
.goods {
.price {
font-size: 18px;
}
.ele-cell-title {
font-weight: 600;
font-size: 16px;
}
.goods-attr {
}
}
.goods-divider {
height: 6px;
}
.buy-bar {
position: fixed;
width: 356px;
bottom: 70px;
//top: 881px;
background-color: #ffffff;
border-radius: 0 0 44px 44px;
border-top: 1px solid var(--grey-8);
.ele-cell-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.home-btn,
.shop-btn,
.order-btn,
.user-btn {
display: flex;
flex-direction: column;
font-size: 13px;
padding: 0 9px;
cursor: pointer;
white-space: nowrap;
}
.icon {
font-size: 19px;
}
.buy-btn {
display: flex;
.add-cart {
border-radius: 100px 0 0 100px;
border: none;
background-color: var(--orange-5);
color: #ffffff;
height: 40px;
width: 95px;
}
.buy-now {
border-radius: 0 100px 100px 0;
border: none;
background-color: var(--red-6);
color: #ffffff;
height: 40px;
width: 95px;
}
}
}
}
:deep(.slick-slide) {
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
font-size: 38px;
}
:deep(.slick-arrow.custom-slick-arrow) {
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide h3) {
color: #fff;
}
.user-card {
height: 170px;
margin: 0 1px;
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
display: flex;
.user-avatar {
margin-left: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.user-info {
margin-left: 10px;
.nickname {
color: var(--grey-3);
font-size: 18px;
font-weight: 500;
}
.phone {
color: var(--grey-5);
}
}
}
}
.menu-card {
width: 340px;
height: 80px;
margin: 6px auto;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.merchant-card-title {
width: 340px;
margin: 6px auto;
font-size: 20px;
font-weight: 500;
margin-top: 16px;
}
.merchant-card {
width: 340px;
max-height: 80px;
margin: 6px auto;
margin-bottom: 16px;
padding: 8px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
.merchant-name {
font-weight: 500;
font-size: 15px;
}
.merchant-desc {
width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.tools-card {
width: 340px;
margin: 0 1px;
padding: 6px 16px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 324px;
left: 24px;
.ele-cell {
padding: 4px 0;
border-bottom: 1px solid var(--grey-9);
}
.btn-center {
display: flex;
align-items: center;
justify-content: center;
}
}
.tabs-card {
padding: 0 8px;
//.order-item {
// background-color: #ffffff;
// padding: 10px;
// min-height: 200px;
//}
}
.order-item {
margin-bottom: 10px;
.order-title {
padding: 20rpx 0;
border-bottom: 1px #eee solid;
.merchant-name {
padding-left: 12rpx;
}
}
.order-body {
}
.card-actions {
display: flex;
justify-content: flex-end;
padding-right: 16px;
}
}
</style>

View File

@@ -1,288 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white" style="display: ">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>修改完后需要<a @click="clearSiteInfoCache"
>清除缓存</a
>才会生效</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" :refresh="refresh" @done="reload" />
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, unref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import Simulator from './components/simulator.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle, openUrl } from '@/utils/common';
import { removeSiteInfoCache } from "@/api/cms/website";
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 页面标题
const title = getPageTitle();
const type = ref<number>(2);
const refresh = ref(false);
// 模拟器的字段
const form = ref<any>({
pageName: '场馆预定订单',
showOrderCard: true
});
// 菜单列表
const list = ref<any[]>();
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.type = 5;
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path'
},
{
title: '分类ID',
dataIndex: 'type',
key: 'type',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
type.value = Number(where?.type) || 0;
refresh.value = !refresh.value;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success('缓存已更新');
}
}
);
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
</script>
<script lang="ts">
export default {
name: 'MpMenuOrder'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,261 +0,0 @@
<!-- 编辑弹窗 -->
<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="type">
<a-select
v-model:value="form.type"
placeholder="页面"
:style="`width: 200px`"
>
<a-select-option :value="2">首页</a-select-option>
<a-select-option :value="3">商城</a-select-option>
<a-select-option :value="1">订单</a-select-option>
<a-select-option :value="0">我的</a-select-option>
<a-select-option :value="4">管理</a-select-option>
</a-select>
</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="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
position: undefined,
active: undefined,
userId: 0,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,52 +0,0 @@
<!-- 搜索表单 -->
<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 useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 2
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,480 +0,0 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid">
<div class="title-bar">
<span class="back"></span>
<span>{{ form.pageName || '商城' }}</span>
<a class="share" @click="onShare"></a>
</div>
</div>
</div>
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<!-- 导航菜单 -->
<template v-if="form.showMenuCard">
<div class="menu-card ele-cell" style="margin-top: 10px">
<div
v-for="(item, index) in shopIcon"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 商品列表 -->
<template v-if="form.showGoodsCard">
<div class="goods-card-title">商品列表</div>
<div
class="goods-card ele-cell"
v-for="(item, index) in goodsList"
:key="index"
>
<a-image
:src="item.image ? JSON.parse(item.image)[0] : []"
:width="60"
:preview="false"
/>
<div class="goods-info ele-cell-content">
<div class="goods-name">{{ item.title }}</div>
<div class="goods-price ele-text-danger ele-cell">
<div class="ele-cell-content">{{ item.price }}</div>
<a-button size="mini">去购买</a-button>
</div>
</div>
</div>
</template>
</div>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<a
class="home-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/home`)"
>
<HomeOutlined class="icon" />
<span>首页</span>
</a>
<a
class="shop-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/shop`)"
>
<ShopOutlined class="icon ele-text-danger" />
<span class="ele-text-danger">商城</span>
</a>
<a
class="order-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/order`)"
>
<ProfileOutlined class="icon" />
订单
</a>
<a
class="user-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/user`)"
>
<UserOutlined class="icon" />
我的
</a>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import {
ProfileOutlined,
ShopOutlined,
HomeOutlined,
UserOutlined
} from '@ant-design/icons-vue';
import { ref, unref, watch } from 'vue';
import { MpWeixinParam, WebsiteField } from '@/api/cms/website/field/model';
import { listWebsiteField } from '@/api/system/website/field';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { listMpMenu } from '@/api/cms/mp-menu';
import { listAd } from '@/api/cms/ad';
import { listMerchant } from '@/api/shop/merchant';
import { Merchant } from '@/api/shop/merchant/model';
import { openUrl } from '@/utils/common';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
const { query } = unref(currentRoute);
import MpMenuEdit from '@/views/cms/mpDesign/components/mpMenuEdit.vue';
import { pageGoods } from '@/api/shop/goods';
import { Goods } from '@/api/shop/goods/model';
const prpos = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
type?: number;
list?: any[] | null;
refresh?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const param = ref<MpWeixinParam>({});
const showUserCardEdit = ref(false);
const showMpMenuEdit = ref(false);
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 幻灯片广告
const adImageList = ref<any[]>();
// 首页导航图标
const scrollList = ref<any[]>();
// 订单图标
const order = ref<any[]>();
// 服务图标
const server = ref<any[]>();
// 商城导航图标
const shopIcon = ref<any[]>();
// 商户列表
const shopList = ref<Merchant[]>();
// 商品列表
const goodsList = ref<Goods[]>();
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
/* 打开编辑弹窗 */
const openUserCard = (row?: WebsiteField) => {
current.value = row ?? null;
showUserCardEdit.value = true;
};
/* 打开编辑弹窗 */
const openMpMenuEdit = (row?: MpMenu) => {
current.value = row ?? null;
showMpMenuEdit.value = true;
};
const onShare = () => {};
const reload = () => {
listWebsiteField({}).then((list) => {
list.map((d) => {
const key = String(d.name);
param.value[key] = d.value;
});
});
listMpMenu({}).then((list) => {
server.value = list.filter((d) => d.type == 0);
order.value = list.filter((d) => d.type == 1);
scrollList.value = list.filter((d) => d.type == 2);
shopIcon.value = list.filter((d) => d.type == 3);
});
listAd({ adType: '幻灯片' }).then((res) => {
const carouselImages = res[0].images;
if (carouselImages) {
adImageList.value = JSON.parse(carouselImages);
}
});
listMerchant({}).then((list) => {
shopList.value = list;
});
pageGoods({}).then((res) => {
goodsList.value = res?.list;
});
emit('done');
};
reload();
watch(
() => prpos.refresh,
(refresh) => {
if (refresh) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 26px;
width: 390px;
height: 844px;
background: url('@/assets/img/app-ui.png');
background-repeat: no-repeat;
background-position: top;
background-size: 100%;
//position: relative;
padding: 0 16px;
.phone-header-black {
height: 99px;
border-radius: 20px 20px 0 0;
background-size: 100%;
.title {
height: 99px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: end;
padding-bottom: 13px;
.title-bar {
width: 100%;
display: flex;
justify-content: space-between;
.back {
display: block;
width: 50px;
margin-left: 3px;
background-color: #ffffff;
}
.share {
display: block;
width: 50px;
cursor: pointer;
}
}
}
}
.phone-body-bg {
padding: 0 16px;
height: 680px;
border-radius: 0 0 44px 44px;
overflow: hidden;
}
.phone-body {
width: 356px;
margin-left: 17px;
height: 630px;
padding: 0;
position: absolute;
top: 98px;
left: 0;
z-index: 999;
.comments {
padding: 20px;
word-break: break-all;
}
.form-data {
padding: 10px 20px;
.submit-btn {
padding: 30px 0;
}
}
}
.goods {
.price {
font-size: 18px;
}
.ele-cell-title {
font-weight: 600;
font-size: 16px;
}
.goods-attr {
}
}
.goods-divider {
height: 6px;
}
.buy-bar {
position: fixed;
width: 356px;
bottom: 70px;
//top: 881px;
background-color: #ffffff;
border-radius: 0 0 44px 44px;
border-top: 1px solid var(--grey-8);
.ele-cell-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.home-btn,
.shop-btn,
.order-btn,
.user-btn {
display: flex;
flex-direction: column;
font-size: 13px;
padding: 0 9px;
cursor: pointer;
white-space: nowrap;
}
.icon {
font-size: 19px;
}
.buy-btn {
display: flex;
.add-cart {
border-radius: 100px 0 0 100px;
border: none;
background-color: var(--orange-5);
color: #ffffff;
height: 40px;
width: 95px;
}
.buy-now {
border-radius: 0 100px 100px 0;
border: none;
background-color: var(--red-6);
color: #ffffff;
height: 40px;
width: 95px;
}
}
}
}
:deep(.slick-slide) {
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
font-size: 38px;
}
:deep(.slick-arrow.custom-slick-arrow) {
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide h3) {
color: #fff;
}
.user-card {
height: 170px;
margin: 0 1px;
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
display: flex;
.user-avatar {
margin-left: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.user-info {
margin-left: 10px;
.nickname {
color: var(--grey-3);
font-size: 18px;
font-weight: 500;
}
.phone {
color: var(--grey-5);
}
}
}
}
.menu-card {
width: 340px;
height: 80px;
margin: 6px auto;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.goods-card-title {
width: 340px;
margin: 6px auto;
font-size: 20px;
font-weight: 500;
margin-top: 16px;
}
.goods-card {
width: 340px;
max-height: 80px;
margin: 6px auto;
margin-bottom: 16px;
padding: 8px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
.goods-name {
font-weight: 500;
font-size: 15px;
}
.goods-desc {
width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-price {
font-size: 18px;
font-weight: 500;
}
}
.tools-card {
width: 340px;
margin: 0 1px;
padding: 6px 16px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 324px;
left: 24px;
.ele-cell {
padding: 4px 0;
border-bottom: 1px solid var(--grey-9);
}
.btn-center {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -1,290 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white" style="display: ">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>修改完后需要<a @click="clearSiteInfoCache"
>清除缓存</a
>才会生效</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" :refresh="refresh" @done="reload" />
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, unref, watch } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import Simulator from './components/simulator.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle, openUrl } from '@/utils/common';
import { removeSiteInfoCache } from "@/api/cms/website";
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 页面标题
const title = getPageTitle();
const type = ref<number>(2);
const refresh = ref(false);
// 模拟器的字段
const form = ref<any>({
pageName: '商城',
showCarousel: true,
showMenuCard: true,
showGoodsCard: true
});
// 菜单列表
const list = ref<any[]>();
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.type = 3;
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path'
},
{
title: '分类ID',
dataIndex: 'type',
key: 'type',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
type.value = Number(where?.type) || 0;
refresh.value = !refresh.value;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success('缓存已更新');
}
}
);
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
</script>
<script lang="ts">
export default {
name: 'MpMenuShop'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,300 +0,0 @@
<!-- 编辑弹窗 -->
<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="type">
<a-select
v-model:value="form.type"
placeholder="页面"
:style="`width: 200px`"
>
<a-select-option :value="2">首页</a-select-option>
<a-select-option :value="3">商城</a-select-option>
<a-select-option :value="1">订单</a-select-option>
<a-select-option :value="0">我的</a-select-option>
<a-select-option :value="4">管理</a-select-option>
</a-select>
</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="path">
<a-input
allow-clear
placeholder="请输入路由地址"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="图标颜色" name="color">
<ele-color-picker
:show-alpha="true"
v-model:value="form.color"
:predefine="predefineColors"
/>
</a-form-item>
<a-form-item label="打开方式" name="target">
<DictSelect
dict-code="navType"
class="form-item"
placeholder="请选择链接方式"
v-model:value="form.target"
/>
</a-form-item>
<a-form-item label="所在行" name="rows">
<a-input-number
:min="0"
:max="3"
class="ele-fluid"
placeholder="请输入所在行"
v-model:value="form.rows"
/>
</a-form-item>
<a-form-item label="管理人员可见" name="adminShow">
<a-switch
checked-children=""
un-checked-children=""
:checked="form.adminShow === 1"
@update:checked="updateAdminShow"
/>
</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>
</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 { addMpMenu, updateMpMenu } from '@/api/cms/mp-menu';
import { MpMenu } from '@/api/cms/mp-menu/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';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 类型 0服务 1订单
type?: number;
// 修改回显的数据
data?: MpMenu | null;
}>();
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<MpMenu>({
menuId: undefined,
parentId: 0,
title: '',
type: 2,
isMpWeixin: true,
path: undefined,
component: undefined,
target: undefined,
icon: '',
color: undefined,
hide: undefined,
rows: undefined,
position: undefined,
active: undefined,
userId: 0,
adminShow: undefined,
home: undefined,
sortNumber: 100,
comments: '',
status: 0
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
title: [
{
required: true,
type: 'string',
message: '请填写菜单名称',
trigger: 'blur'
}
],
type: [
{
required: true,
type: 'string',
message: '请选择所在页面',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
]
});
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.thumbnail,
status: 'done'
});
form.icon = data.thumbnail;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.icon = '';
};
const updateAdminShow = (value: boolean) => {
form.adminShow = value ? 1 : 0;
};
// 预设颜色
const predefineColors = ref([
'#40a9ff',
'#9254de',
'#36cfc9',
'#73d13d',
'#f759ab',
'#cf1313',
'#ff4d4f',
'#ffa940',
'#ffc53d',
'#f3d3d3',
'#1b1b1b',
'#363636',
'#4d4d4d',
'#737373',
'#a6a6a6',
'#d9d9d9',
'#e6e6e6',
'#f2f2f2',
'#f7f7f7',
'#fafafa'
]);
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateMpMenu : addMpMenu;
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.type) {
form.type = props.type;
}
if (props.data) {
assignObject(form, props.data);
if (props.data.icon) {
images.value.push({
uid: uuid(),
url: props.data.icon,
status: 'done'
});
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -1,61 +0,0 @@
<!-- 搜索表单 -->
<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>-->
<!-- <span class="ele-text-placeholder" style="margin-left: 20px"-->
<!-- >菜单类型</span-->
<!-- >-->
<!-- <a-radio-group v-model:value="where.type" @change="handleSearch">-->
<!-- <a-radio-button :value="0">会员功能</a-radio-button>-->
<!-- <a-radio-button :value="1">订单图标</a-radio-button>-->
<!-- </a-radio-group>-->
<!-- </a-space>-->
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { watch } from 'vue';
import useSearch from '@/utils/use-search';
import { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: MpMenuParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 表单数据
const { where } = useSearch<MpMenu>({
type: 0
});
const handleSearch = () => {
emit('search', where);
};
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -1,550 +0,0 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid">
<div class="title-bar">
<span class="back"></span>
<span>{{ form.pageName || '个人中心' }}</span>
<a class="share" @click="onShare"></a>
</div>
</div>
</div>
<!-- 会员信息卡片 -->
<template v-if="form.showUserCard">
<a-popover>
<template #content> 点击更换背景 </template>
<div
class="user-card"
:style="{
backgroundImage: 'url(' + param.mp_user_top + ')'
}"
@click="
openUserCard({
name: 'mp_user_top',
value: param.mp_user_top,
comments: '小程序我的顶部背景图片',
sortNumber: 100
})
"
>
<div class="user-avatar" @click.stop="onAvatar">
<a-avatar :src="param.site_logo" :size="60" />
<div class="user-info">
<div class="nickname">昵称</div>
<div class="phone">手机号码</div>
</div>
</div>
</div>
</a-popover>
<UserCardEdit
v-model:visible="showUserCardEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 订单卡片 -->
<template v-if="form.showOrderCard">
<div class="order-card ele-cell">
<div
v-for="(item, index) in order"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</div>
</template>
<template v-if="form.showToolsCard">
<div class="tools-card">
<div
v-for="(item, index) in server"
:key="index"
class="ele-cell"
@click="openMpMenuEdit(item)"
>
<a-avatar :src="item.icon" :size="24" />
<div
class="title ele-cell-content"
:style="{ color: item.color || '#333333' }"
>{{ item.title }}</div
>
<RightOutlined class="ele-text-secondary" />
</div>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<template v-if="form.showMenuCard">
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<!-- 幻灯片轮播 -->
<template v-if="form.showCarousel">
<a-carousel arrows autoplay :dots="true">
<template v-if="adImageList">
<template v-for="(img, index) in adImageList" :key="index">
<div class="ad-item">
<a-image
:preview="false"
:src="img.url"
width="100%"
height="200px"
/>
</div>
</template>
</template>
</a-carousel>
</template>
<!-- 导航菜单 -->
<template v-if="form.showMenuCard">
<div class="menu-card ele-cell">
<div
v-for="(item, index) in scrollList"
:key="index"
class="ele-cell-content ele-text-center btn-center"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.icon" :width="30" :preview="false" />
<span>{{ item.title }}</span>
</div>
</div>
<MpMenuEdit
v-model:visible="showMpMenuEdit"
:data="current"
@done="reload"
/>
</template>
<!-- 商户列表 -->
<template v-if="form.showShopCard">
<div class="merchant-card-title">场地预定</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
<!-- <div class="">-->
<!-- <a-button>我要去</a-button>-->
<!-- </div>-->
</div>
</template>
<!-- 培训课程 -->
<template v-if="form.showTtrainCard">
<div class="merchant-card-title">培训课程</div>
<div
class="merchant-card ele-cell"
v-for="(item, index) in shopList"
:key="index"
@click="openMpMenuEdit(item)"
>
<a-image :src="item.image" :width="96" :preview="false" />
<div class="merchant-info ele-cell-content">
<div class="merchant-name">{{ item.merchantName }}</div>
<div class="merchant-desc ele-cell-desc">
{{ item.comments }}
</div>
</div>
</div>
</template>
</div>
</template>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<a
class="home-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/home`)"
>
<HomeOutlined class="icon" />
<span>首页</span>
</a>
<a
class="shop-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/shop`)"
>
<ShopOutlined class="icon" />
<span>商城</span>
</a>
<a
class="order-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/order`)"
>
<ProfileOutlined class="icon" />
<span>订单</span>
</a>
<a
class="user-btn ele-cell-content ele-text-secondary"
@click="openUrl(`/mp-weixin/user`)"
>
<UserOutlined class="icon ele-text-danger" />
<span class="ele-text-danger">我的</span>
</a>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import {
ProfileOutlined,
ShopOutlined,
HomeOutlined,
UserOutlined,
RightOutlined
} from '@ant-design/icons-vue';
import { ref, unref, watch } from 'vue';
import { MpWeixinParam, WebsiteField } from '@/api/cms/website/field/model';
import { listWebsiteField } from '@/api/system/website/field';
import { MpMenu } from '@/api/cms/mp-menu/model';
import { listMpMenu } from '@/api/cms/mp-menu';
import { listAd } from '@/api/cms/ad';
import { listMerchant } from '@/api/shop/merchant';
import { Merchant } from '@/api/shop/merchant/model';
import { openUrl } from '@/utils/common';
import { useRouter } from 'vue-router';
const { currentRoute } = useRouter();
const { query } = unref(currentRoute);
import MpMenuEdit from './mpMenuEdit.vue';
import UserCardEdit from '@/views/cms/field/components/website-field-edit.vue';
const prpos = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
type?: number;
list?: any[] | null;
refresh?: boolean;
}>(),
{
placeholder: undefined
}
);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const param = ref<MpWeixinParam>({});
const showUserCardEdit = ref(false);
const showMpMenuEdit = ref(false);
// 当前编辑数据
const current = ref<WebsiteField | null>(null);
// 幻灯片广告
const adImageList = ref<any[]>();
// 首页导航图标
const scrollList = ref<any[]>();
// 订单图标
const order = ref<any[]>();
// 服务图标
const server = ref<any[]>();
// 商户列表
const shopList = ref<Merchant[]>();
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
/* 打开编辑弹窗 */
const openUserCard = (row?: WebsiteField) => {
current.value = row ?? null;
showUserCardEdit.value = true;
};
/* 打开编辑弹窗 */
const openMpMenuEdit = (row?: MpMenu) => {
current.value = row ?? null;
showMpMenuEdit.value = true;
};
const onShare = () => {};
const onAvatar = () => {};
const reload = () => {
listWebsiteField({}).then((list) => {
list.map((d) => {
const key = String(d.name);
param.value[key] = d.value;
});
});
listMpMenu({ type: 0 }).then((list) => {
server.value = list.filter((d) => d.rows == 1);
order.value = list.filter((d) => d.rows == 0);
scrollList.value = list.filter((d) => d.type == 2);
});
listAd({ adType: '幻灯片' }).then((res) => {
const carouselImages = res[0].images;
if (carouselImages) {
adImageList.value = JSON.parse(carouselImages);
}
});
listMerchant({}).then((list) => {
shopList.value = list;
});
emit('done');
};
reload();
watch(
() => prpos.refresh,
(refresh) => {
if (refresh) {
reload();
}
},
{ immediate: true }
);
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 16px;
width: 390px;
height: 844px;
background: url('@/assets/img/app-ui.png');
background-repeat: no-repeat;
background-position: top;
background-size: 100%;
//position: relative;
padding: 0 16px;
.phone-header-black {
height: 99px;
border-radius: 20px 20px 0 0;
background-size: 100%;
.title {
height: 99px;
font-size: 16px;
display: flex;
justify-content: center;
align-items: end;
padding-bottom: 13px;
.title-bar {
width: 100%;
display: flex;
justify-content: space-between;
.back {
display: block;
width: 50px;
margin-left: 3px;
background-color: #ffffff;
}
.share {
display: block;
width: 50px;
cursor: pointer;
}
}
}
}
.phone-body-bg {
padding: 0 16px;
height: 680px;
border-radius: 0 0 44px 44px;
overflow: hidden;
}
.phone-body {
width: 356px;
margin-left: 17px;
height: 630px;
padding: 0;
position: absolute;
top: 98px;
left: 0;
z-index: 999;
.comments {
padding: 20px;
word-break: break-all;
}
.form-data {
padding: 10px 20px;
.submit-btn {
padding: 30px 0;
}
}
}
.goods {
.price {
font-size: 18px;
}
.ele-cell-title {
font-weight: 600;
font-size: 16px;
}
.goods-attr {
}
}
.goods-divider {
height: 6px;
}
.buy-bar {
position: fixed;
width: 356px;
bottom: 70px;
//top: 881px;
background-color: #ffffff;
border-radius: 0 0 44px 44px;
border-top: 1px solid var(--grey-8);
.ele-cell-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.shop-btn,
.kefu-btn,
.star-btn {
display: flex;
flex-direction: column;
font-size: 13px;
padding: 0 9px;
cursor: pointer;
white-space: nowrap;
}
.icon {
font-size: 19px;
}
.buy-btn {
display: flex;
.add-cart {
border-radius: 100px 0 0 100px;
border: none;
background-color: var(--orange-5);
color: #ffffff;
height: 40px;
width: 95px;
}
.buy-now {
border-radius: 0 100px 100px 0;
border: none;
background-color: var(--red-6);
color: #ffffff;
height: 40px;
width: 95px;
}
}
}
}
:deep(.slick-slide) {
overflow: hidden;
}
:deep(.slick-arrow.custom-slick-arrow) {
font-size: 38px;
}
:deep(.slick-arrow.custom-slick-arrow) {
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
transition: ease all 0.3s;
opacity: 0.3;
z-index: 1;
}
:deep(.slick-arrow.custom-slick-arrow:before) {
display: none;
}
:deep(.slick-arrow.custom-slick-arrow:hover) {
color: #fff;
opacity: 0.5;
}
:deep(.slick-slide h3) {
color: #fff;
}
.user-card {
height: 170px;
margin: 0 1px;
background-color: var(--grey-10);
background-repeat: no-repeat;
background-size: cover;
display: flex;
.user-avatar {
margin-left: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.user-info {
margin-left: 10px;
.nickname {
color: var(--grey-3);
font-size: 18px;
font-weight: 500;
}
.phone {
color: var(--grey-5);
}
}
}
}
.order-card {
width: 340px;
height: 80px;
margin: 0 1px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 230px;
left: 24px;
.btn-center {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
}
.tools-card {
width: 340px;
margin: 0 1px;
padding: 6px 16px;
background: #ffffff;
border-radius: 5px;
border-color: slategrey;
position: absolute;
top: 324px;
left: 24px;
.ele-cell {
padding: 4px 0;
border-bottom: 1px solid var(--grey-9);
cursor: pointer;
}
.btn-center {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -1,296 +0,0 @@
<template>
<a-page-header :title="title" @back="() => $router.go(-1)">
<div class="ele-cell ele-cell-align-top">
<!-- 设计画布 -->
<div class="body ele-cell-content ele-bg-white" style="display: ">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="menuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #footer>
<div class="ele-text-secondary"
>温馨提示选图标可以上<a
href="https://www.iconfont.cn"
target="_blank"
>阿里巴巴矢量图标库</a
>修改完后需要<a @click="clearSiteInfoCache"
>清除缓存</a
>才会生效</div
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'icon'">
<a-avatar :src="record.icon" :width="50" />
</template>
<template v-if="column.key === 'path'">
<span class="ele-text-placeholder">{{ record.path }}</span>
</template>
<template v-if="column.key === 'component'">
<span class="ele-text-placeholder">{{ record.component }}</span>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<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>
</a-card>
</div>
<!-- 载入模拟器 -->
<Simulator :form="form" :type="type" :refresh="refresh" @done="reload" />
<!-- 中间间隙 -->
<div style="width: 500px"></div>
<!-- 编辑弹窗 -->
<MpMenuEdit
v-model:visible="showEdit"
:type="type"
:data="current"
@done="reload"
/>
</div>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { 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 MpMenuEdit from './components/mpMenuEdit.vue';
import Simulator from './components/simulator.vue';
import {
pageMpMenu,
removeMpMenu,
removeBatchMpMenu
} from '@/api/cms/mp-menu';
import type { MpMenu, MpMenuParam } from '@/api/cms/mp-menu/model';
import { getPageTitle, openUrl } from '@/utils/common';
import { removeSiteInfoCache } from "@/api/cms/website";
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<MpMenu[]>([]);
// 当前编辑数据
const current = ref<MpMenu | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 页面标题
const title = getPageTitle();
const type = ref<number>(2);
const refresh = ref(false);
// 模拟器的字段
const form = ref<any>({
pageName: '个人中心',
showUserCard: true,
showOrderCard: true,
showToolsCard: true
});
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
where.type = 0;
return pageMpMenu({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'menuId',
key: 'menuId',
align: 'center',
width: 90
},
{
title: '菜单图标',
dataIndex: 'icon',
key: 'icon',
align: 'center',
width: 90
},
{
title: '菜单名称',
dataIndex: 'title',
key: 'title',
align: 'center'
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path'
},
// {
// title: '分类ID',
// dataIndex: 'type',
// key: 'type',
// align: 'center'
// },
{
title: '所在行',
dataIndex: 'rows',
key: 'rows',
align: 'center'
},
{
title: '排序',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: MpMenuParam) => {
if (where?.type) {
type.value = Number(where?.type);
} else {
type.value = 0;
}
refresh.value = !refresh.value;
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: MpMenu) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success('缓存已更新');
}
}
);
};
/* 删除单个 */
const remove = (row: MpMenu) => {
const hide = message.loading('请求中..', 0);
removeMpMenu(row.menuId)
.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);
removeBatchMpMenu(selection.value.map((d) => d.menuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 自定义行属性 */
const customRow = (record: MpMenu) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
</script>
<script lang="ts">
export default {
name: 'MpMenuUser'
};
</script>
<style lang="less" scoped></style>

View File

@@ -8,7 +8,6 @@
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:need-page="false"
tool-class="ele-toolbar-form"
class="sys-org-table"
>

View File

@@ -0,0 +1,385 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="1000"
:visible="visible"
:maskClosable="false"
:maxable="true"
:title="isUpdate ? '编辑内容' : '添加内容'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 2, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
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">
<a-input
allow-clear
:maxlength="100"
placeholder="/about"
v-model:value="form.path"
/>
</a-form-item>
<a-form-item label="组件路径" name="component">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入组件路径"
v-model:value="form.component"
/>
</a-form-item>
<a-form-item label="Banner" name="photo">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseFile"
@del="onDeleteItem"
/>
<!-- <ele-image-upload-->
<!-- v-model:value="images"-->
<!-- :limit="1"-->
<!-- :drag="true"-->
<!-- :upload-handler="uploadHandler"-->
<!-- @upload="onUpload"-->
<!-- />-->
</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="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-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 { message } from 'ant-design-vue';
import { uuid} from 'ele-admin-pro';
import { addDesign, updateDesign } from '@/api/cms/design';
import { Design } from '@/api/cms/design/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { FormInstance, Rule } from 'ant-design-vue/es/form';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import {uploadFile, uploadOss} from '@/api/system/file';
import TinymceEditor from "@/components/TinymceEditor/index.vue";
import useFormData from "@/utils/use-form-data";
import {removeSiteInfoCache} from "@/api/cms/website";
import {FileRecord} from "@/api/system/file/model";
// 是否是修改
const isUpdate = ref(false);
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const disabled = ref(false);
// 编辑器内容,双向绑定
const content = ref<any>('');
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: Design | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 已上传数据
const images = ref<ItemType[]>([]);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
// 表单数据
const { form, resetFields, assignFields } = useFormData<Design>({
pageId: undefined,
name: '',
images: '',
path: '',
component: '/custom/index',
content: '',
type: '',
status: 0,
comments: '',
sortNumber: 100,
navigationId: undefined
});
const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null);
const config = ref({
height: 500,
images_upload_handler: (blobInfo, success, error) => {
const file = blobInfo.blob();
const formData = new FormData();
formData.append('file', file, file.name);
uploadOss(file).then(res => {
success(res.url)
}).catch((msg) => {
error(msg);
})
return false;
},
// 自定义文件上传(这里使用把选择的文件转成 blob 演示)
file_picker_callback: (callback: any, _value: any, meta: any) => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
// 设定文件可选类型
if (meta.filetype === 'image') {
input.setAttribute('accept', 'image/*');
} else if (meta.filetype === 'media') {
input.setAttribute('accept', 'video/*,.pdf');
}
input.onchange = () => {
const file = input.files?.[0];
if (!file) {
return;
}
if (meta.filetype === 'media') {
if (file.size / 1024 / 1024 > 200) {
editorRef.value?.alert({ content: '大小不能超过 200MB' });
return;
}
if (!file.type.startsWith('video/')) {
editorRef.value?.alert({ content: '只能选择视频文件' });
return;
}
uploadOss(file).then(res => {
callback(res.downloadUrl);
})
}
};
input.click();
}
});
/* 粘贴图片上传服务器并插入编辑器 */
const onPaste = (e) => {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
let hasFile = false;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
let file = items[i].getAsFile();
const item: ItemType = {
file,
uid: (file as any).lastModified,
name: file.name
};
uploadFile(<File>item.file)
.then((result) => {
const addPath = `<p><img class="content-img" src="${result.url}"></p>`;
content.value = content.value + addPath
})
.catch((e) => {
message.error(e.message);
});
hasFile = true;
}
}
if (hasFile) {
e.preventDefault();
}
}
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
name: [
{
required: true,
type: 'string',
message: '请填写页面名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
],
component: [
{
required: true,
type: 'string',
message: '请填写组件路径',
trigger: 'blur'
}
]
});
/* 上传事件 */
const uploadHandler = (file: File) => {
const item: ItemType = {
file,
uid: (file as any).uid,
name: file.name
};
if (file.type.startsWith('video')) {
if (file.size / 1024 / 1024 > 200) {
message.error('大小不能超过 200MB');
return;
}
}
if (file.type.startsWith('image')) {
if (file.size / 1024 / 1024 > 5) {
message.error('大小不能超过 5MB');
return;
}
}
onUpload(item);
};
// 上传文件
const onUpload = (item: any) => {
const { file } = item;
uploadFile(file)
.then((data) => {
form.photo = data.path;
images.value.push({
uid: data.id,
url:
file.type == 'video/mp4'
? 'https://oss.wsdns.cn/20240301/6e4e32cb808245d4be336b9486961145.png'
: data.path,
status: 'done'
});
})
.catch((e) => {
message.error(e.message);
});
};
const chooseFile = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.downloadUrl,
status: 'done'
});
form.photo = data.downloadUrl;
}
const onDeleteItem = (index: number) => {
images.value.splice(index,1)
form.photo = '';
}
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form,
content: content.value
};
const saveOrUpdate = isUpdate.value ? updateDesign : addDesign;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
// 清除缓存
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId'));
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
content.value = ''
images.value = []
if (props.data) {
assignFields(props.data);
if(props.data.content){
content.value = props.data.content
}
if(props.data.photo){
images.value.push({
uid: uuid(),
url: props.data.photo,
status: 'done'
})
}
isUpdate.value = !!props.data.pageId;
} else {
isUpdate.value = false;
content.value = '';
resetFields();
}
} else {
resetFields();
formRef.value?.clearValidate();
}
},
{ immediate: true }
);
</script>
<style lang="less">
.sdf{
display: none;
}
</style>

View File

@@ -33,23 +33,13 @@
@update:value="(value?: number) => (form.parentId = value)"
/>
</a-form-item>
<a-form-item label="模型" name="type">
<a-select
ref="select"
v-model:value="form.type"
style="width: 253px"
@change="onType"
>
<a-select-option :value="1">单页内容</a-select-option>
<a-select-option :value="2">新闻分类</a-select-option>
<a-select-option :value="3">新闻详情</a-select-option>
<a-select-option :value="4">表单设计</a-select-option>
<a-select-option :value="5">知识文档</a-select-option>
<a-select-option :value="6">商品分类</a-select-option>
<a-select-option :value="7">商品详情</a-select-option>
<a-select-option :value="9">外部链接</a-select-option>
<a-select-option :value="0">通用模型</a-select-option>
</a-select>
<a-form-item label="菜单名称" name="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"
@pressEnter="save"
/>
</a-form-item>
<a-form-item label="选择页面" v-if="form.type == 1">
<SelectDesign
@@ -101,52 +91,55 @@
/>
</a-form-item>
<a-form-item
v-if="form.type == 9 || form.type == 0"
:label="form.type == 9 ? '链接地址' : '路由地址'"
name="path"
:extra="form.path ? `${form.path}` : ''"
>
<a-input
allow-clear
:placeholder="form.type == 9 ? '请输入链接地址' : '/about'"
:placeholder="form.type == 9 ? '请输入链接地址' : '/about.html'"
v-model:value="form.path"
@pressEnter="save"
/>
</a-form-item>
<a-form-item
label="组件路径"
name="component"
v-if="form.type == 0"
:extra="form.component ? `@/views${form.component}` : ''"
>
<a-form-item label="组件路径" name="component" v-if="form.type == 0">
<a-input
allow-clear
placeholder="/about/index"
placeholder="/custom/index"
v-model:value="form.component"
@pressEnter="save"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
:width="40"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="菜单名称" name="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"
@pressEnter="save"
/>
<a-form-item label="位置" name="position">
<a-select
ref="select"
v-model:value="form.position"
style="width: 253px"
>
<a-select-option :value="1">顶部</a-select-option>
<a-select-option :value="2">底部</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="模型" name="type">
<a-select
ref="select"
v-model:value="form.type"
style="width: 253px"
@change="onType"
>
<a-select-option :value="0">通用模型</a-select-option>
<a-select-option :value="1">单页内容</a-select-option>
<a-select-option :value="2">新闻分类</a-select-option>
<a-select-option :value="3">新闻详情</a-select-option>
<a-select-option :value="4">表单设计</a-select-option>
<a-select-option :value="5">知识文档</a-select-option>
<a-select-option :value="6">商品分类</a-select-option>
<a-select-option :value="7">商品详情</a-select-option>
<a-select-option :value="9">外部链接</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
@@ -158,16 +151,6 @@
@pressEnter="save"
/>
</a-form-item>
<a-form-item label="位置" name="position">
<a-select
ref="select"
v-model:value="form.position"
style="width: 253px"
>
<a-select-option :value="1">顶部</a-select-option>
<a-select-option :value="2">底部</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="是否展示" name="hide">
<a-switch
checked-children=""
@@ -176,6 +159,16 @@
@update:checked="updateHideValue"
/>
</a-form-item>
<a-form-item label="菜单图标" name="icon">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
:width="40"
@done="chooseFile"
@del="onDeleteItem"
/>
</a-form-item>
</a-col>
</a-row>
<div style="margin-bottom: 22px">
@@ -210,22 +203,17 @@
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { Navigation } from '@/api/cms/navigation/model';
import {
addNavigation,
updateNavigation,
checkExistence
} from '@/api/cms/navigation';
import { addNavigation, updateNavigation } from '@/api/cms/navigation';
import { Design } from '@/api/cms/design/model';
import { removeSiteInfoCache } from '@/api/cms/website';
import { isChinese, isUrl } from 'ele-admin-pro';
import { ArticleCategory } from '@/api/cms/category/model';
import { Article } from '@/api/cms/article/model';
import { Form } from '@/api/cms/form/model';
import { DocsBook } from '@/api/cms/docs-book/model';
import { Goods } from '@/api/shop/goods/model';
import {GoodsCategory} from "@/api/shop/goodsCategory/model";
import {ItemType} from "ele-admin-pro/es/ele-image-upload/types";
import {FileRecord} from "@/api/system/file/model";
import { GoodsCategory } from '@/api/shop/goodsCategory/model';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FileRecord } from '@/api/system/file/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
@@ -261,7 +249,7 @@
// 表单数据
const { form, resetFields, assignFields } = useFormData<Navigation>({
navigationId: undefined,
type: 1,
type: 0,
title: '',
parentId: 0,
path: '',
@@ -279,14 +267,6 @@
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
type: [
{
required: true,
message: '请选择导航类型',
type: 'number',
trigger: 'blur'
}
],
title: [
{
required: true,
@@ -295,56 +275,66 @@
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
trigger: 'blur',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请填写路由地址');
}
if (form.type == 9) {
if (!isUrl(value)) {
return reject('请输入正确的网址');
}
resolve();
}
if (form.type == 1) {
if (form.pageId == 0) {
return reject('请选择页面');
}
}
if (value.charAt(0) != '/') {
return reject('请填写路由地址,必须是以"/"开头的英文字母+数字');
}
if (isChinese(value)) {
return reject('不支持中文');
}
// let zg = /^[0-9a-zA-Z]*$/;
// if (!zg.test(value)) {
// return reject('仅支持字母和数字');
// }
checkExistence('path', value, form.navigationId)
.then((msg) => {
return reject(msg);
})
.catch(() => {
resolve();
});
});
}
}
],
sortNumber: [
{
required: true,
message: '请输入排序号',
type: 'number',
trigger: 'blur'
}
],
// component: [
// {
// required: true,
// type: 'string',
// trigger: 'blur',
// validator: (_rule: Rule, value: string) => {
// return new Promise<void>((resolve, reject) => {
// if (!value) {
// return reject('请填写组件路径');
// }
// if (value.charAt(0) != '/') {
// return reject('请填写路由地址,必须是以"/"开头的英文字母+数字');
// }
// if (isChinese(value)) {
// return reject('不支持中文');
// }
// resolve();
// });
// }
// }
// ],
// path: [
// {
// required: true,
// type: 'string',
// trigger: 'blur',
// validator: (_rule: Rule, value: string) => {
// return new Promise<void>((resolve, reject) => {
// if (!value) {
// return reject('请填写路由地址');
// }
// if (form.type == 9) {
// if (!isUrl(value)) {
// return reject('请输入正确的网址');
// }
// resolve();
// }
// if (form.type == 1) {
// if (form.pageId == 0) {
// return reject('请选择页面');
// }
// }
// if (value.charAt(0) != '/') {
// return reject('请填写路由地址,必须是以"/"开头的英文字母+数字');
// }
// if (isChinese(value)) {
// return reject('不支持中文');
// }
// resolve();
// // checkExistence('path', value, form.navigationId)
// // .then((msg) => {
// // return reject(msg);
// // })
// // .catch(() => {
// // resolve();
// // });
// });
// }
// }
// ],
status: [
{
required: true,
@@ -424,8 +414,6 @@
const chooseGoods = (data: Goods) => {
form.goodsId = data.goodsId;
form.title = data.title;
form.pageName = data.title;
form.path = '/goods/detail/' + data.goodsId;
form.component = '/goods/search';
};
@@ -437,13 +425,12 @@
status: 'done'
});
form.icon = data.path;
}
};
const onDeleteItem = (index: number) => {
images.value.splice(index,1)
images.value.splice(index, 1);
form.icon = '';
}
};
/* 保存编辑 */
const save = () => {
@@ -456,6 +443,10 @@
const navigationForm = {
...form
};
if (form.path != '' && form.path?.charAt(0) != '/') {
message.error('路由必须以"/"开头');
return false;
}
const saveOrUpdate = isUpdate.value ? updateNavigation : addNavigation;
saveOrUpdate(navigationForm)
.then((msg) => {

View File

@@ -110,12 +110,13 @@
<a-space>
<a @click="openEdit(null, record.navigationId)">添加</a>
<a-divider type="vertical" />
<!-- <a @click="moveUp(record)">上移<ArrowUpOutlined /></a>-->
<!-- <a-divider type="vertical" />-->
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a @click="openDesign(record)">内容</a>
<a-divider type="vertical" />
<a-popconfirm
placement="topRight"
:disabled="record.home == 1"
title="确定要删除此菜单吗?"
@confirm="remove(record)"
>
@@ -135,6 +136,12 @@
:position="position"
@done="reload"
/>
<!-- 页面编辑弹窗 -->
<DesignEdit
v-model:visible="showDesignEdit"
:data="design"
@done="reload"
/>
</div>
</template>
@@ -156,6 +163,7 @@
} from 'ele-admin-pro/es';
import type { EleProTable } from 'ele-admin-pro/es';
import NavigationEdit from './components/navigation-edit.vue';
import DesignEdit from './components/design-edit.vue';
import {
listNavigation,
removeNavigation,
@@ -164,6 +172,8 @@
import type { Navigation, NavigationParam } from '@/api/cms/navigation/model';
import { openPreview } from '@/utils/common';
import { getSiteInfo } from '@/api/layout';
import { getDesign } from '@/api/cms/design';
import { Design } from '@/api/cms/design/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -190,6 +200,7 @@
{
title: '组件路径',
dataIndex: 'component',
hideInTable: true,
key: 'component'
},
{
@@ -240,8 +251,12 @@
// 当前编辑数据
const current = ref<Navigation | null>(null);
// 当前选中页面
const design = ref<Design>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 编辑内容
const showDesignEdit = ref(false);
// 上级分类id
const parentId = ref<number>();
// 分类数据
@@ -306,11 +321,26 @@
showEdit.value = true;
};
const openDesign = (row?: Navigation) => {
// 设置默认值
design.value = {
navigationId: row?.navigationId,
name: row?.title,
path: `/page-${row?.navigationId}.html`,
component: `/pages/custom/index`
};
getDesign(Number(row?.pageId))
.then((res) => {
design.value = res;
})
.catch(() => {})
.finally(() => {
showDesignEdit.value = true;
});
};
/* 删除单个 */
const remove = (row: Navigation) => {
if (row.home == 1) {
return;
}
if (row.children?.length) {
message.error('请先删除子节点');
return;

View File

@@ -91,21 +91,13 @@
<span v-if="form.version === 20">授权版</span>
<span v-if="form.version === 30">永久授权</span>
</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 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 :value="2">已关闭</a-radio>
</a-radio-group>
</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>
@@ -287,6 +279,7 @@
loading.value = false;
message.success(msg);
updateVisible(false);
localStorage.setItem('Domain',`${form.prefix}${form.domain}`);
emit('done');
})
.catch((e) => {

View File

@@ -52,7 +52,9 @@
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" ghost @click="openUrl(`${record.prefix}${record.adminUrl}`)">进入后台</a-button>
<a @click="openUrl(`${record.prefix}${record.domain}`)">首页</a>
<a-divider type="vertical" />
<a @click="openUrl(record.adminUrl)">后台</a>
</a-space>
</template>
</template>
@@ -84,7 +86,8 @@
updateWebsite
} from '@/api/cms/website';
import type { Website, WebsiteParam } from '@/api/cms/website/model';
import { openUrl } from '@/utils/common';
import { openPreview, openUrl } from '@/utils/common';
import { getSiteDomain } from '@/utils/domain';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -99,6 +102,7 @@
const showMove = ref(false);
// 加载状态
const loading = ref(true);
const domain = getSiteDomain();
// 表格数据源
const datasource: DatasourceFunction = ({