装修相关
This commit is contained in:
parent
9ec547d9f4
commit
45a31db37f
|
|
@ -34,7 +34,8 @@ module.exports = {
|
|||
'no-prototype-builtins': 'off',
|
||||
'prefer-spread': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off'
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
||||
'vue/no-mutating-props': 'off'
|
||||
},
|
||||
globals: {
|
||||
module: 'readonly'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 页面装修详情
|
||||
export function getDecoratePages(params: any) {
|
||||
return request.get({ url: '/decorate/pages/detail', params })
|
||||
}
|
||||
|
||||
// 页面装修保存
|
||||
export function setDecoratePages(params: any) {
|
||||
return request.post({ url: '/decorate/pages/save', params })
|
||||
}
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<el-image :style="styles" v-bind="props"> </el-image>
|
||||
<el-image :style="styles" v-bind="props">
|
||||
<template v-slot:error>
|
||||
<div class="text-tx-secondary flex items-center justify-center bg-white h-full">
|
||||
<icon name="el-icon-Picture" :size="30" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="custom-link mt-[60px]">
|
||||
<div class="flex flex-wrap items-center">
|
||||
自定义链接
|
||||
<div class="ml-4 flex-1 min-w-[100px]">
|
||||
<el-input
|
||||
:model-value="modelValue.path"
|
||||
placeholder="请输入链接地址"
|
||||
@input="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
请填写完整的带有“https://”或“http://”的链接地址,链接的域名必须在微信公众平台设置业务域名
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { LinkTypeEnum, type Link } from '.'
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<Link>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Link): void
|
||||
}>()
|
||||
|
||||
const handleInput = (value: string) => {
|
||||
emit('update:modelValue', {
|
||||
path: value,
|
||||
type: LinkTypeEnum.CUSTOM_LINK
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export enum LinkTypeEnum {
|
||||
'SHOP_PAGES' = 'shop',
|
||||
'CUSTOM_LINK' = 'custom'
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
path: string
|
||||
name?: string
|
||||
type: string
|
||||
params?: Record<string, any>
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="link flex">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
class="w-[160px] min-h-[400px] link-menu"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<el-menu-item v-for="(item, index) in menus" :index="item.type" :key="index">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="flex-1 pl-4">
|
||||
<shop-pages v-model="activeLink" v-if="LinkTypeEnum.SHOP_PAGES == activeMenu" />
|
||||
<custom-link v-model="activeLink" v-if="LinkTypeEnum.CUSTOM_LINK == activeMenu" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { LinkTypeEnum, type Link } from '.'
|
||||
import ShopPages from './shop-pages.vue'
|
||||
import CustomLink from './custom-link.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<Link>
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: any): void
|
||||
}>()
|
||||
|
||||
const activeLink = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
const menus = ref([
|
||||
{
|
||||
name: '商城页面',
|
||||
type: LinkTypeEnum.SHOP_PAGES
|
||||
},
|
||||
{
|
||||
name: '自定义链接',
|
||||
type: LinkTypeEnum.CUSTOM_LINK
|
||||
}
|
||||
])
|
||||
const activeMenu = ref<string>(LinkTypeEnum.SHOP_PAGES)
|
||||
|
||||
const handleSelect = (index: string) => {
|
||||
activeMenu.value = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.link-menu {
|
||||
--el-menu-item-height: 40px;
|
||||
:deep(.el-menu-item) {
|
||||
border-color: transparent;
|
||||
&.is-active {
|
||||
border-right-width: 2px;
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div class="link-picker">
|
||||
<popup width="700px" title="链接选择" @confirm="handleConfirm">
|
||||
<template v-slot:trigger>
|
||||
<div class="cursor-pointer">
|
||||
<slot>
|
||||
<el-input
|
||||
:model-value="modelValue?.name ?? modelValue?.path"
|
||||
placeholder="请选择链接"
|
||||
readonly
|
||||
>
|
||||
<template #suffix>
|
||||
<icon v-if="!modelValue?.path" name="el-icon-ArrowRight" />
|
||||
<icon
|
||||
v-else
|
||||
name="el-icon-Close"
|
||||
@click.stop="emit('update:modelValue', {})"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<link-content v-model="activeLink" />
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Link } from '.'
|
||||
import LinkContent from './index.vue'
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: any): void
|
||||
}>()
|
||||
const activeLink = ref<Link>()
|
||||
const handleConfirm = () => {
|
||||
emit('update:modelValue', activeLink.value)
|
||||
}
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
activeLink.value = value as Link
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="shop-pages">
|
||||
<div class="link-list flex flex-wrap">
|
||||
<div
|
||||
class="link-item border border-br px-5 py-[5px] rounded-[3px] cursor-pointer"
|
||||
v-for="(item, index) in linkList"
|
||||
:class="{ 'border-primary text-primary': modelValue.path == item.path }"
|
||||
:key="index"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { LinkTypeEnum, type Link } from '.'
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<Link>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Link): void
|
||||
}>()
|
||||
|
||||
const linkList = ref([
|
||||
{
|
||||
path: '/pages/index/index',
|
||||
name: '商城首页',
|
||||
type: LinkTypeEnum.SHOP_PAGES
|
||||
}
|
||||
])
|
||||
|
||||
const handleSelect = (value: Link) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
</script>
|
||||
|
|
@ -41,7 +41,8 @@
|
|||
v-show="showUpload"
|
||||
:class="{
|
||||
'is-disabled': disabled,
|
||||
'is-one': limit == 1
|
||||
'is-one': limit == 1,
|
||||
[uploadClass]: true
|
||||
}"
|
||||
>
|
||||
<slot name="upload">
|
||||
|
|
@ -123,6 +124,10 @@ export default defineComponent({
|
|||
hiddenUpload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
uploadClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -269,8 +274,8 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
.material-upload {
|
||||
.upload-btn {
|
||||
@apply box-border rounded border-br border-dashed border flex flex-col justify-center items-center;
|
||||
:deep(.upload-btn) {
|
||||
@apply text-tx-secondary box-border rounded border-br border-dashed border flex flex-col justify-center items-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div class="menu flex-1 min-h-0" :class="themeClass" :style="`--aside-width: ${width}px`">
|
||||
<div
|
||||
class="menu flex-1 min-h-0"
|
||||
:class="themeClass"
|
||||
:style="isCollapsed ? '' : `--aside-width: ${width}px`"
|
||||
>
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
v-bind="config"
|
||||
|
|
@ -48,7 +52,6 @@ const props = defineProps({
|
|||
defineEmits(['select'])
|
||||
|
||||
const route = useRoute()
|
||||
console.log(route)
|
||||
const activeMenu = computed<string>(() => route.meta?.activeMenu || route.path)
|
||||
const themeClass = computed(() => `theme-${props.theme}`)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ export function findFirstValidRoute(routes: RouteRecordRaw[]): string | undefine
|
|||
}
|
||||
|
||||
export function getRoutePath(perms: string) {
|
||||
console.log(router.getRoutes().find((item) => item.meta?.perms == perms)?.path)
|
||||
return router.getRoutes().find((item) => item.meta?.perms == perms)?.path || ''
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
--el-border-color-dark: #d4d7de;
|
||||
--el-border-color-darker: #cdd0d6;
|
||||
--el-fill-color: #f0f2f5;
|
||||
--el-fill-color-light: #f5f7fa;
|
||||
--el-fill-color-light: #f8f8f8;
|
||||
--el-fill-color-lighter: #fafafa;
|
||||
--el-fill-color-extra-light: #fafcff;
|
||||
--el-fill-color-dark: #ebedf0;
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@
|
|||
<el-input v-model="formData.intro" placeholder="请输入文章简介" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="intro">
|
||||
<el-form-item label="摘要" prop="summary">
|
||||
<div class="w-80">
|
||||
<el-input type="textarea" :rows="6" v-model="formData.intro" />
|
||||
<el-input type="textarea" :rows="6" v-model="formData.summary" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章封面" prop="image">
|
||||
|
|
@ -101,7 +101,8 @@ const formData = reactive({
|
|||
content: '',
|
||||
visit: 0,
|
||||
sort: 0,
|
||||
isShow: ''
|
||||
isShow: '',
|
||||
summary: ''
|
||||
})
|
||||
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@
|
|||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问链接">
|
||||
<div>https://b2cplus.likeshop.cn/mobile</div>
|
||||
<el-button class="ml-4">复制</el-button>
|
||||
<div>{{ formData.accessLink }}</div>
|
||||
<el-button class="ml-4" @click="copy(formData.accessLink)">复制</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
|
@ -39,13 +39,16 @@
|
|||
<script lang="ts" setup>
|
||||
import { getH5Config, setH5Config } from '@/api/channel/h5'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
|
||||
const formData = reactive({
|
||||
status: 0,
|
||||
close: 0,
|
||||
url: ''
|
||||
url: '',
|
||||
accessLink: ''
|
||||
})
|
||||
|
||||
const { copy } = useClipboard()
|
||||
const getDetail = async () => {
|
||||
const data = await getH5Config()
|
||||
for (const key in formData) {
|
||||
|
|
|
|||
|
|
@ -36,17 +36,16 @@
|
|||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getH5Config, setH5Config } from '@/api/channel/h5'
|
||||
import { getWxDevConfig, setWxDevConfig } from '@/api/channel/wx_dev'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const formData = reactive({
|
||||
status: 0,
|
||||
close: 0,
|
||||
url: ''
|
||||
appId: '',
|
||||
appSecret: ''
|
||||
})
|
||||
|
||||
const getDetail = async () => {
|
||||
const data = await getH5Config()
|
||||
const data = await getWxDevConfig()
|
||||
for (const key in formData) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
|
|
@ -54,7 +53,7 @@ const getDetail = async () => {
|
|||
}
|
||||
|
||||
const handelSave = async () => {
|
||||
await setH5Config(formData)
|
||||
await setWxDevConfig(formData)
|
||||
getDetail()
|
||||
feedback.msgSuccess('操作成功')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="pages-setting">
|
||||
<div
|
||||
class="title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2"
|
||||
>
|
||||
{{ widget?.title }}
|
||||
</div>
|
||||
<component
|
||||
class="pt-5 pr-4"
|
||||
:is="widgets[widget?.name]?.attr"
|
||||
:content="widget?.content"
|
||||
:styles="widget?.styles"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import widgets from '../widgets'
|
||||
|
||||
const props = defineProps({
|
||||
widget: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<el-menu
|
||||
:default-active="modelValue"
|
||||
class="w-[160px] min-h-[668px] pages-menu"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<el-menu-item v-for="(item, key) in menus" :index="key" :key="item.id">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
menus: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({})
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '1'
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: string): void
|
||||
}>()
|
||||
const handleSelect = (index: string) => {
|
||||
emit('update:modelValue', index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-menu {
|
||||
:deep(.el-menu-item) {
|
||||
border-color: transparent;
|
||||
&.is-active {
|
||||
border-right-width: 2px;
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<el-scrollbar class="shadow mx-[30px] pages-preview">
|
||||
<div>
|
||||
<div
|
||||
v-for="(widget, index) in pageData"
|
||||
:key="widget"
|
||||
class="relative"
|
||||
:class="{
|
||||
'cursor-pointer': !widget?.disabled
|
||||
}"
|
||||
@click="handleClick(widget, index)"
|
||||
>
|
||||
<div
|
||||
class="absolute w-full h-full z-[100] border-dashed"
|
||||
:class="{
|
||||
select: index == modelValue,
|
||||
'border-br border-2': !widget?.disabled
|
||||
}"
|
||||
></div>
|
||||
<component
|
||||
:is="widgets[widget?.name]?.content"
|
||||
:content="widget.content"
|
||||
:styles="widget.styles"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import widgets from '../widgets'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
pageData: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: number): void
|
||||
}>()
|
||||
|
||||
const handleClick = (widget: any, index: number) => {
|
||||
if (widget.disabled) return
|
||||
emit('update:modelValue', index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-preview {
|
||||
background-color: #f8f8f8;
|
||||
width: 360px;
|
||||
max-height: 734px;
|
||||
height: 100%;
|
||||
.select {
|
||||
@apply border-primary border-solid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="是否启用">
|
||||
<el-radio-group v-model="content.enabled">
|
||||
<el-radio :label="1">开启</el-radio>
|
||||
<el-radio :label="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片设置">
|
||||
<div class="flex-1">
|
||||
<del-wrap
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
@close="handleDelete(index)"
|
||||
>
|
||||
<div class="bg-fill-light flex items-center w-full p-4 mb-4">
|
||||
<material-picker v-model="item.image" upload-class="bg-body" />
|
||||
<div>
|
||||
<el-form-item label="图片名称">
|
||||
<el-input v-model="item.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item class="mt-[18px]" label="图片链接">
|
||||
<link-picker v-model="item.link" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.data?.length < limit">
|
||||
<el-button type="primary" @click="handleAdd">添加图片</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import feedback from '@/utils/feedback'
|
||||
import type { PropType } from 'vue'
|
||||
import type options from './options'
|
||||
const limit = 10
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
props.content.data.push({
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
})
|
||||
} else {
|
||||
feedback.msgError(`最多添加${limit}张图片`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return feedback.msgError('最少保留一张图片')
|
||||
}
|
||||
props.content.data.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="banner">
|
||||
<div class="banner-image">
|
||||
<image-contain width="100%" height="170px" :src="getImage" fit="contain" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import type options from './options'
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const getImage = computed(() => {
|
||||
const { data } = props.content
|
||||
if (Array.isArray(data)) {
|
||||
return data[0] ? data[0].image : ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export default () => ({
|
||||
title: '首页轮播图',
|
||||
name: 'banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
const widgets: Record<string, any> = import.meta.glob('./**/*', { eager: true })
|
||||
interface Widget {
|
||||
attr: any
|
||||
content: any
|
||||
options: any
|
||||
}
|
||||
const exportWidgets: Record<string, Widget> = {}
|
||||
Object.keys(widgets).forEach((key) => {
|
||||
const widgetName = key.replace(/^\.\/([\w-]+).*/gi, '$1')
|
||||
const widgetContent = key.replace(/(.*\/)*([^.]+).*/gi, '$2') as keyof Widget
|
||||
exportWidgets[widgetName] = exportWidgets[widgetName] ?? {}
|
||||
exportWidgets[widgetName][widgetContent] = widgets[key]?.default
|
||||
})
|
||||
|
||||
export default exportWidgets
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="是否启用">
|
||||
<el-radio-group v-model="content.enabled">
|
||||
<el-radio :label="1">开启</el-radio>
|
||||
<el-radio :label="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单图片">
|
||||
<div class="flex-1">
|
||||
<div class="form-tips">最多可添加10个,建议图片尺寸:100px*100px</div>
|
||||
<del-wrap
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
@close="handleDelete(index)"
|
||||
>
|
||||
<div class="bg-fill-light flex items-center w-full p-4 mt-4">
|
||||
<material-picker v-model="item.image" upload-class="bg-body">
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="20" />
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<div>
|
||||
<el-form-item label="图片名称">
|
||||
<el-input v-model="item.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item class="mt-[18px]" label="图片链接">
|
||||
<link-picker v-model="item.link" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.data?.length < limit">
|
||||
<el-button type="primary" @click="handleAdd">添加菜单</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import feedback from '@/utils/feedback'
|
||||
import type { PropType } from 'vue'
|
||||
import type options from './options'
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const limit = 10
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
props.content.data.push({
|
||||
image: '',
|
||||
name: '导航',
|
||||
link: {}
|
||||
})
|
||||
} else {
|
||||
feedback.msgError(`最多添加${limit}个`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
props.content.data.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<div class="nav pt-[15px] pb-[8px]">
|
||||
<div class="flex flex-wrap">
|
||||
<div
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/5 mb-[15px]"
|
||||
>
|
||||
<image-contain width="41px" height="41px" :src="item.image" alt="" />
|
||||
<div class="mt-[7px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import type options from './options'
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav {
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export default () => ({
|
||||
title: '导航菜单',
|
||||
name: 'nav',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '导航',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import type options from './options'
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="search">
|
||||
<div class="search-con flex items-center px-[15px]">
|
||||
<icon name="el-icon-Search" :size="17" />
|
||||
<span class="ml-[5px]">请输入关键词搜索</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
background-color: #fff;
|
||||
padding: 7px 12px;
|
||||
.search-con {
|
||||
height: 100%;
|
||||
height: 36px;
|
||||
border-radius: 36px;
|
||||
background: #f4f4f4;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default () => ({
|
||||
title: '搜索',
|
||||
name: 'search',
|
||||
disabled: true,
|
||||
content: {},
|
||||
styles: {}
|
||||
})
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="decoration-pages">
|
||||
<el-card shadow="never" class="!border-none flex-1" :body-style="{ height: '100%' }">
|
||||
<div class="flex h-full items-start">
|
||||
<el-scrollbar>
|
||||
<Menu v-model="activeMenu" :menus="menus" />
|
||||
</el-scrollbar>
|
||||
<preview v-model="selectWidgetIndex" :pageData="getPageData" />
|
||||
<el-scrollbar class="flex-1">
|
||||
<attr-setting :widget="getSelectWidget" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-card>
|
||||
<footer-btns>
|
||||
<el-button type="primary" @click="setData">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import Menu from '../component/pages/menu.vue'
|
||||
import Preview from '../component/pages/preview.vue'
|
||||
import AttrSetting from '../component/pages/attr-setting.vue'
|
||||
import widgets from '../component/widgets'
|
||||
import { getDecoratePages, setDecoratePages } from '@/api/decoration'
|
||||
import feedback from '@/utils/feedback'
|
||||
enum pagesTypeEnum {
|
||||
HOME = '1',
|
||||
USER = '2',
|
||||
SERVICE = '3'
|
||||
}
|
||||
|
||||
const generatePageData = (widgetNames: string[]) => {
|
||||
return widgetNames.map((widgetName) => widgets[widgetName]?.options() || {})
|
||||
}
|
||||
|
||||
const menus: Record<
|
||||
string,
|
||||
{
|
||||
id: number
|
||||
name: string
|
||||
pageData: any[]
|
||||
}
|
||||
> = reactive({
|
||||
[pagesTypeEnum.HOME]: {
|
||||
id: 1,
|
||||
pageType: 1,
|
||||
name: '商城首页',
|
||||
pageData: generatePageData(['search', 'banner', 'nav'])
|
||||
},
|
||||
[pagesTypeEnum.USER]: {
|
||||
id: 2,
|
||||
pageType: 2,
|
||||
name: '个人中心',
|
||||
pageData: generatePageData(['banner'])
|
||||
},
|
||||
[pagesTypeEnum.SERVICE]: {
|
||||
id: 3,
|
||||
pageType: 3,
|
||||
name: '客服设置',
|
||||
pageData: generatePageData(['banner'])
|
||||
}
|
||||
})
|
||||
|
||||
const activeMenu = ref('1')
|
||||
const selectWidgetIndex = ref(-1)
|
||||
const getPageData = computed(() => {
|
||||
return menus[activeMenu.value]?.pageData ?? []
|
||||
})
|
||||
const getSelectWidget = computed(() => {
|
||||
return menus[activeMenu.value]?.pageData[selectWidgetIndex.value] ?? ''
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
const data = await getDecoratePages({ id: activeMenu.value })
|
||||
}
|
||||
const setData = async () => {
|
||||
await setDecoratePages(menus[activeMenu.value])
|
||||
feedback.msg('保存成功')
|
||||
}
|
||||
watch(
|
||||
activeMenu,
|
||||
() => {
|
||||
selectWidgetIndex.value = getPageData.value.findIndex((item) => !item.disabled)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
getData()
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.decoration-pages {
|
||||
height: calc(100vh - var(--navbar-height) - 80px);
|
||||
@apply flex flex-col;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,9 +18,12 @@
|
|||
<el-form-item label="短信签名" prop="sign">
|
||||
<el-input v-model="formData.sign" placeholder="请输入短信签名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="APP_KEY" prop="appKey">
|
||||
<el-form-item label="APP_KEY" prop="appKey" v-if="formData.alias == 'aliyun'">
|
||||
<el-input v-model="formData.appKey" placeholder="请输入APP_KEY" />
|
||||
</el-form-item>
|
||||
<el-form-item label="APP_ID" prop="appId" v-if="formData.alias == 'tencent'">
|
||||
<el-input v-model="formData.appId" placeholder="请输入APP_ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SECRET_ID" prop="secretId" v-if="formData.alias == 'tencent'">
|
||||
<el-input v-model="formData.secretId" placeholder="请输入SECRET_ID" />
|
||||
</el-form-item>
|
||||
|
|
@ -50,6 +53,7 @@ const formData = reactive({
|
|||
alias: '',
|
||||
sign: '',
|
||||
appKey: '',
|
||||
appId: '',
|
||||
secretKey: '',
|
||||
secretId: '',
|
||||
status: 0
|
||||
|
|
@ -63,6 +67,13 @@ const formRules = {
|
|||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入APP_ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appKey: [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ module.exports = {
|
|||
'br-extra-light': 'var(--el-border-color-extra-light)',
|
||||
'br-dark': 'var( --el-border-color-dark)',
|
||||
fill: 'var(--el-fill-color)',
|
||||
'fill-light': 'var(--el-fill-color-light)',
|
||||
'fill-lighter': 'var(--el-fill-color-lighter)',
|
||||
mask: 'var(--el-mask-color)'
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue