修复bug ,底部导航,装修优化

This commit is contained in:
Jason 2022-09-14 16:41:39 +08:00
parent a1fc2fed1c
commit cf99b35b79
28 changed files with 323 additions and 112 deletions

View File

@ -30,6 +30,7 @@
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.0",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/lodash-es": "^4.17.6",
"@types/node": "^16.11.41",
"@types/nprogress": "^0.2.0",

View File

@ -10,6 +10,11 @@ export function setDecoratePages(params: any) {
return request.post({ url: '/decorate/pages/save', params })
}
// 获取首页文章数据
export function getDecorateArticle(params?: any) {
return request.get({ url: '/decorate/data/article', params })
}
// 底部导航详情
export function getDecorateTabbar(params?: any) {
return request.get({ url: '/decorate/tabbar/detail', params })

View File

@ -1,11 +1,5 @@
<template>
<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>
<el-image :style="styles" v-bind="props"> </el-image>
</template>
<script lang="ts" setup>

View File

@ -4,7 +4,7 @@
自定义链接
<div class="ml-4 flex-1 min-w-[100px]">
<el-input
:model-value="modelValue.path"
:model-value="modelValue.query?.url"
placeholder="请输入链接地址"
@input="handleInput"
/>
@ -32,7 +32,10 @@ const emit = defineEmits<{
const handleInput = (value: string) => {
emit('update:modelValue', {
path: value,
path: '/pages/webview/webview',
query: {
url: value
},
type: LinkTypeEnum.CUSTOM_LINK
})
}

View File

@ -10,16 +10,8 @@
</el-menu-item>
</el-menu>
<div class="flex-1 pl-4">
<shop-pages
v-model="activeLink"
v-if="LinkTypeEnum.SHOP_PAGES == activeMenu"
@update:model-value="updateLink"
/>
<custom-link
v-model="activeLink"
v-if="LinkTypeEnum.CUSTOM_LINK == activeMenu"
@update:model-value="updateLink"
/>
<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>
@ -72,9 +64,10 @@ const handleSelect = (index: string) => {
activeMenu.value = index
}
const updateLink = (value: any) => {
watch(activeLink, (value) => {
if (!value.type) return
emit('update:modelValue', value)
}
})
watch(
() => props.modelValue,

View File

@ -1,11 +1,6 @@
<template>
<div class="link-picker flex-1" @click="!disabled && popupRef?.open()">
<el-input
:model-value="modelValue?.name ?? modelValue?.path"
placeholder="请选择链接"
readonly
:disabled="disabled"
>
<el-input :model-value="getLink" placeholder="请选择链接" readonly :disabled="disabled">
<template #suffix>
<icon v-if="!modelValue?.path" name="el-icon-ArrowRight" />
<icon
@ -43,6 +38,17 @@ const activeLink = ref<Link>({ path: '', type: LinkTypeEnum.SHOP_PAGES })
const handleConfirm = () => {
emit('update:modelValue', activeLink.value)
}
const getLink = computed(() => {
switch (props.modelValue?.type) {
case LinkTypeEnum.SHOP_PAGES:
return props.modelValue.name
case LinkTypeEnum.CUSTOM_LINK:
return props.modelValue.query?.url
default:
return props.modelValue?.name
}
})
watch(
() => props.modelValue,
(value) => {

View File

@ -86,8 +86,6 @@ export function findFirstValidRoute(routes: RouteRecordRaw[]): string | undefine
}
export function getRoutePath(perms: string) {
console.log(router.getRoutes())
console.log(router.getRoutes().find((item) => item.meta?.perms == perms)?.path)
return router.getRoutes().find((item) => item.meta?.perms == perms)?.path || ''
}

View File

@ -64,14 +64,13 @@ export class Axios {
return response
},
(err: AxiosError) => {
let cancelUrl = err.config?.url
if (isFunction(responseInterceptorsCatchHook)) {
responseInterceptorsCatchHook(err)
}
if (err.code == AxiosError.ERR_CANCELED) {
cancelUrl = err.message
if (err.code != AxiosError.ERR_CANCELED) {
this.removeCancelToken(err.config?.url!)
}
this.removeCancelToken(cancelUrl!)
if (err.code == AxiosError.ECONNABORTED) {
setTimeout(() => {
console.log(err)

View File

@ -149,3 +149,14 @@ export const timeFormat = (dateTime: number, fmt = 'yyyy-mm-dd') => {
}
return fmt
}
/**
* @description id
* @param length { Number } id的长度
* @return { String } id
*/
export const getNonDuplicateID = (length = 8) => {
let idStr = Date.now().toString(36)
idStr += Math.random().toString(36).substring(3, length)
return idStr
}

View File

@ -1,11 +1,21 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert type="warning" title="温馨提示:用于管理网站的分类,只可添加到一级" :closable="false" show-icon />
<el-alert
type="warning"
title="温馨提示:用于管理网站的分类,只可添加到一级"
:closable="false"
show-icon
/>
</el-card>
<el-card class="!border-none mt-4" shadow="never" v-loading="pager.loading">
<div>
<el-button class="mb-4" v-perms="['article:cate:add']" type="primary" @click="handleAdd()">
<el-button
class="mb-4"
v-perms="['article:cate:add']"
type="primary"
@click="handleAdd()"
>
<template #icon>
<icon name="el-icon-Plus" />
</template>
@ -17,17 +27,32 @@
<el-table-column label="文章数" prop="number" min-width="120" />
<el-table-column label="状态" min-width="120">
<template #default="{ row }">
<el-switch v-perms="['article:cate:change']" v-model="row.isShow" :active-value="1"
:inactive-value="0" @change="changeStatus(row.id)" />
<el-switch
v-perms="['article:cate:change']"
v-model="row.isShow"
:active-value="1"
:inactive-value="0"
@change="changeStatus(row.id)"
/>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="120" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button v-perms="['article:cate:edit']" type="primary" link @click="handleEdit(row)">
<el-button
v-perms="['article:cate:edit']"
type="primary"
link
@click="handleEdit(row)"
>
编辑
</el-button>
<el-button v-perms="['article:cate:del']" type="danger" link @click="handleDelete(row.id)">
<el-button
v-perms="['article:cate:del']"
type="danger"
link
@click="handleDelete(row.id)"
>
删除
</el-button>
</template>

View File

@ -54,6 +54,8 @@
:src="row.image"
class="w-[60px] h-[45px]"
:preview-src-list="[row.image]"
preview-teleported
fit="contain"
/>
</template>
</el-table-column>

View File

@ -0,0 +1,59 @@
<template>
<el-image :style="styles" v-bind="props" :src="getImageUrl(src)">
<template #placeholder>
<div class="image-slot"></div>
</template>
<template #error>
<div class="image-slot">
<icon name="el-icon-Picture" :size="30" />
</div>
</template>
</el-image>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { CSSProperties } from 'vue'
import { addUnit } from '@/utils/util'
import { imageProps } from 'element-plus'
import useAppStore from '@/stores/modules/app'
const props = defineProps({
width: {
type: [String, Number],
default: 'auto'
},
height: {
type: [String, Number],
default: 'auto'
},
radius: {
type: [String, Number],
default: 0
},
...imageProps
})
const { getImageUrl } = useAppStore()
const styles = computed<CSSProperties>(() => {
return {
width: addUnit(props.width),
height: addUnit(props.height),
borderRadius: addUnit(props.radius)
}
})
</script>
<style lang="scss" scoped>
.el-image {
display: block;
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #fafafa;
color: #909399;
}
}
</style>

View File

@ -1,31 +1,32 @@
<template>
<div class="shadow mx-[30px] pages-preview">
<div
v-for="(widget, index) in pageData"
:key="widget"
class="relative"
:class="{
'cursor-pointer': !widget?.disabled
}"
@click="handleClick(widget, index)"
>
<el-scrollbar>
<div
class="absolute w-full h-full z-[100] border-dashed"
v-for="(widget, index) in pageData"
:key="widget.id"
class="relative"
:class="{
select: index == modelValue,
'border-br border-2': !widget?.disabled
'cursor-pointer': !widget?.disabled
}"
></div>
<slot>
<keep-alive>
@click="handleClick(widget, index)"
>
<div
class="absolute w-full h-full z-[100] border-dashed"
:class="{
select: index == modelValue,
'border-[#dcdfe6] border-2': !widget?.disabled
}"
></div>
<slot>
<component
:is="widgets[widget?.name]?.content"
:content="widget.content"
:styles="widget.styles"
:key="widget.id"
/>
</keep-alive>
</slot>
</div>
</slot>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>

View File

@ -1,14 +1,14 @@
<template>
<div class="banner">
<div class="banner-image">
<image-contain width="100%" height="170px" :src="getImageUrl(getImage)" fit="contain" />
<decoration-img width="100%" height="170px" :src="getImage" fit="contain" />
</div>
</div>
</template>
<script lang="ts" setup>
import useAppStore from '@/stores/modules/app'
import type { PropType } from 'vue'
import type options from './options'
import DecorationImg from '../../decoration-img.vue'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
@ -20,7 +20,7 @@ const props = defineProps({
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
const getImage = computed(() => {
const { data } = props.content
if (Array.isArray(data)) {

View File

@ -1,6 +1,6 @@
<template>
<div class="customer-service">
<image-contain width="140px" height="140px" :src="getImageUrl(content.qrcode)" alt="" />
<decoration-img width="140px" height="140px" :src="content.qrcode" alt="" />
<div class="text-[15px] mt-[7px] font-medium">{{ content.title }}</div>
<div class="text-[#666] mt-[20px]">服务时间{{ content.time }}</div>
<div class="text-[#666] mt-[7px]">客服电话{{ content.mobile }}</div>
@ -12,9 +12,9 @@
</div>
</template>
<script lang="ts" setup>
import useAppStore from '@/stores/modules/app'
import type { PropType } from 'vue'
import type options from './options'
import DecorationImg from '../../decoration-img.vue'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
@ -26,7 +26,6 @@ defineProps({
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
</script>
<style lang="scss" scoped>

View File

@ -9,7 +9,7 @@
:key="index"
class="flex flex-col items-center w-1/4 mb-[15px]"
>
<image-contain width="26px" height="26px" :src="getImageUrl(item.image)" alt="" />
<decoration-img width="26px" height="26px" :src="item.image" alt="" />
<div class="mt-[7px]">{{ item.name }}</div>
</div>
</div>
@ -19,7 +19,7 @@
:key="index"
class="flex items-center border-b border-[#e5e5e5] h-[50px] px-[12px]"
>
<image-contain width="24px" height="24px" :src="getImageUrl(item.image)" alt="" />
<decoration-img width="24px" height="24px" :src="item.image" alt="" />
<div class="ml-[10px] flex-1">{{ item.name }}</div>
<div>
<icon name="el-icon-ArrowRight" />
@ -29,9 +29,9 @@
</div>
</template>
<script lang="ts" setup>
import useAppStore from '@/stores/modules/app'
import type { PropType } from 'vue'
import type options from './options'
import DecorationImg from '../../decoration-img.vue'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
@ -43,7 +43,6 @@ defineProps({
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
</script>
<style lang="scss" scoped>

View File

@ -1,21 +1,21 @@
<template>
<div class="nav pt-[15px] pb-[8px]">
<div class="nav bg-white 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="getImageUrl(item.image)" alt="" />
<decoration-img width="41px" height="41px" :src="item.image" alt="" />
<div class="mt-[7px]">{{ item.name }}</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import useAppStore from '@/stores/modules/app'
import type { PropType } from 'vue'
import type options from './options'
import DecorationImg from '../../decoration-img.vue'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
@ -27,11 +27,6 @@ const props = defineProps({
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
</script>
<style lang="scss" scoped>
.nav {
background-color: #fff;
}
</style>
<style lang="scss" scoped></style>

View File

@ -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>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,70 @@
<template>
<div class="news">
<div class="flex items-center news-title mx-[10px] my-[15px] text-[17px] font-medium">
最新资讯
</div>
<div
v-for="item in newsList"
:key="item.id"
class="news-card flex bg-white px-[10px] py-[16px] text-[#333] border-[#f2f2f2] border-b"
>
<div class="mr-[10px]" v-if="item.image">
<img :src="item.image" class="w-[120px] h-[90px]" />
</div>
<div class="flex flex-col justify-between flex-1">
<div class="text-[15px] font-medium line-clamp-2">{{ item.title }}</div>
<div class="line-clamp-1 text-sm mt-[8px]">
{{ item.intro }}
</div>
<div class="text-[#999] text-xs w-full flex justify-between mt-[8px]">
<div>{{ item.createTime }}</div>
<div class="flex items-center">
<icon name="el-icon-View" />
<div class="ml-[5px]">{{ item.visit }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import { getDecorateArticle } from '@/api/decoration'
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 newsList = ref<any[]>([])
const getData = async () => {
const data = await getDecorateArticle({
limit: 10
})
newsList.value = data
}
getData()
</script>
<style lang="scss" scoped>
.news {
.news-title {
&::before {
content: '';
width: 4px;
height: 17px;
display: block;
margin-right: 5px;
background: #4173ff;
}
}
}
</style>

View File

@ -0,0 +1,8 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -0,0 +1,7 @@
export default () => ({
title: '资讯',
name: 'news',
disabled: 1,
content: {},
styles: {}
})

View File

@ -1,14 +1,14 @@
<template>
<div class="banner mx-[10px] mt-[10px]">
<div class="banner-image">
<image-contain width="100%" height="100px" :src="getImageUrl(getImage)" fit="contain" />
<decoration-img width="100%" height="100px" :src="getImage" fit="contain" />
</div>
</div>
</template>
<script lang="ts" setup>
import useAppStore from '@/stores/modules/app'
import type { PropType } from 'vue'
import type options from './options'
import DecorationImg from '../../decoration-img.vue'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
@ -20,7 +20,6 @@ const props = defineProps({
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
const getImage = computed(() => {
const { data } = props.content
if (Array.isArray(data)) {

View File

@ -1,6 +1,6 @@
<template>
<div class="decoration-pages min-w-[1100px]">
<el-card shadow="never" class="!border-none flex-1" :body-style="{ height: '100%' }">
<el-card shadow="never" class="!border-none flex-1 flex" :body-style="{ flex: 1 }">
<div class="flex h-full items-start">
<Menu v-model="activeMenu" :menus="menus" />
<preview v-model="selectWidgetIndex" :pageData="getPageData" />
@ -19,6 +19,7 @@ import AttrSetting from '../component/pages/attr-setting.vue'
import widgets from '../component/widgets'
import { getDecoratePages, setDecoratePages } from '@/api/decoration'
import feedback from '@/utils/feedback'
import { getNonDuplicateID } from '@/utils/util'
enum pagesTypeEnum {
HOME = '1',
USER = '2',
@ -26,7 +27,13 @@ enum pagesTypeEnum {
}
const generatePageData = (widgetNames: string[]) => {
return widgetNames.map((widgetName) => widgets[widgetName]?.options() || {})
return widgetNames.map((widgetName) => {
const options = {
id: getNonDuplicateID(),
...(widgets[widgetName]?.options() || {})
}
return options
})
}
const menus: Record<
@ -41,7 +48,7 @@ const menus: Record<
id: 1,
pageType: 1,
name: '商城首页',
pageData: generatePageData(['search', 'banner', 'nav'])
pageData: generatePageData(['search', 'banner', 'nav', 'news'])
},
[pagesTypeEnum.USER]: {
id: 2,
@ -70,6 +77,7 @@ const getData = async () => {
const data = await getDecoratePages({ id: activeMenu.value })
menus[String(data.id)].pageData = JSON.parse(data.pageData)
}
const setData = async () => {
await setDecoratePages({
...menus[activeMenu.value],

View File

@ -60,5 +60,7 @@ module.exports = {
}
},
plugins: []
plugins: [
require('@tailwindcss/line-clamp') // 引入插件
]
}

View File

@ -426,6 +426,11 @@
resolved "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27"
integrity sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==
"@tailwindcss/line-clamp@^0.4.2":
version "0.4.2"
resolved "https://registry.npmmirror.com/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz#f353c5a8ab2c939c6267ac5b907f012e5ee130f9"
integrity sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==
"@transloadit/prettier-bytes@0.0.7":
version "0.0.7"
resolved "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz#cdb5399f445fdd606ed833872fa0cabdbc51686b"

View File

@ -1,21 +1,30 @@
<template>
<u-tabbar v-bind="tabbarStyle" :list="tabbarList" @change="handleChange"></u-tabbar>
<u-tabbar
v-model="current"
v-bind="tabbarStyle"
:list="tabbarList"
@change="handleChange"
:hide-tab-bar="false"
></u-tabbar>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/stores/app'
import { currentPage, navigateTo } from '@/utils/util'
import { onLoad } from '@dcloudio/uni-app'
import { computed, onMounted, ref } from 'vue'
import { navigateTo } from '@/utils/util'
import { computed, ref } from 'vue'
const current = ref()
const appStore = useAppStore()
const tabbarList = computed(() => {
return appStore.getTabbarConfig.map((item: any) => ({
iconPath: item.unselected,
selectedIconPath: item.selected,
text: item.name,
link: JSON.parse(item.link),
pagePath: JSON.parse(item.link).path
}))
return appStore.getTabbarConfig.map((item: any) => {
const link = JSON.parse(item.link)
return {
iconPath: item.unselected,
selectedIconPath: item.selected,
text: item.name,
link,
pagePath: link.path
}
})
})
const tabbarStyle = computed(() => ({
@ -26,8 +35,4 @@ const handleChange = (index: number) => {
const selectTab = tabbarList.value[index]
navigateTo(selectTab.link, 'reLaunch')
}
// onMounted(() => {
// const page = currentPage()
// console.log(page)
// })
</script>

View File

@ -238,16 +238,20 @@ export default {
},
// tab
switchTab(index) {
// v-model
this.$emit("change", index);
let pagePath = this.list[index].pagePath;
// pagePath使uni.switchTab
if (this.list[index].pagePath) {
if (pagePath) {
if(pagePath == this.pageUrl || pagePath == "/" + this.pageUrl) return
// v-model
this.$emit("change", index);
uni.switchTab({
url: this.list[index].pagePath
url: pagePath
});
} else {
// papgePathv-modelvalue
// v-modelvaluegetCurrentPages()
this.$emit("change", index);
this.$emit("input", index);
this.$emit("update:modelValue", index);
}
@ -370,4 +374,4 @@ export default {
}
}
}
</style>
</style>

View File

@ -53,15 +53,8 @@ export enum LinkTypeEnum {
}
export function navigateTo(link: Link, navigateType: 'navigateTo' | 'reLaunch' = 'navigateTo') {
let url: string
switch (link.type) {
case LinkTypeEnum.SHOP_PAGES:
url = link.query ? `${link.path}?${objectToQuery(link.query)}` : link.path
uni[navigateType]({ url })
break
case LinkTypeEnum.CUSTOM_LINK:
uni[navigateType]({ url: `/pages/webview/webview?url=${link.path}` })
}
const url = link.query ? `${link.path}?${objectToQuery(link.query)}` : link.path
uni[navigateType]({ url })
}
/**