新增
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="login flex flex-col">
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<div class="login-card flex rounded-md overflow-hidden">
|
||||
<div class="flex-1 h-full hidden md:inline-block">
|
||||
<image-contain :src="config.login_image" :width="400" height="100%" />
|
||||
</div>
|
||||
<div
|
||||
class="login-form bg-body flex flex-col justify-center px-10 py-10 md:w-[400px] w-[375px] flex-none mx-auto"
|
||||
>
|
||||
<div class="text-center text-3xl font-medium mb-8">{{ config.web_name }}</div>
|
||||
<el-form ref="formRef" :model="formData" size="large" :rules="rules">
|
||||
<el-form-item prop="account">
|
||||
<el-input
|
||||
v-model="formData.account"
|
||||
placeholder="请输入账号"
|
||||
@keyup.enter="handleEnter"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-User" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
ref="passwordRef"
|
||||
v-model="formData.password"
|
||||
show-password
|
||||
placeholder="请输入密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-Lock" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mb-5">
|
||||
<el-checkbox v-model="remAccount" label="记住账号"></el-checkbox>
|
||||
</div>
|
||||
<el-button type="primary" size="large" :loading="isLock" @click="lockLogin">
|
||||
登录
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<layout-footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, InputInstance } from 'element-plus'
|
||||
import { computed, onMounted, reactive, ref, shallowRef } from 'vue'
|
||||
|
||||
import { ACCOUNT_KEY } from '@/enums/cacheEnums'
|
||||
import { PageEnum } from '@/enums/pageEnum'
|
||||
import { useLockFn } from '@/hooks/useLockFn'
|
||||
import LayoutFooter from '@/layout/components/footer.vue'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import cache from '@/utils/cache'
|
||||
|
||||
const passwordRef = shallowRef<InputInstance>()
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const remAccount = ref(false)
|
||||
const config = computed(() => appStore.config)
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
password: ''
|
||||
})
|
||||
const rules = {
|
||||
account: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入账号',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
// 回车按键监听
|
||||
const handleEnter = () => {
|
||||
if (!formData.password) {
|
||||
return passwordRef.value?.focus()
|
||||
}
|
||||
handleLogin()
|
||||
}
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
await formRef.value?.validate()
|
||||
// 记住账号,缓存
|
||||
cache.set(ACCOUNT_KEY, {
|
||||
remember: remAccount.value,
|
||||
account: remAccount.value ? formData.account : ''
|
||||
})
|
||||
await userStore.login(formData)
|
||||
const {
|
||||
query: { redirect }
|
||||
} = route
|
||||
const path = typeof redirect === 'string' ? redirect : PageEnum.INDEX
|
||||
router.push(path)
|
||||
}
|
||||
const { isLock, lockFn: lockLogin } = useLockFn(handleLogin)
|
||||
|
||||
onMounted(() => {
|
||||
const value = cache.get(ACCOUNT_KEY)
|
||||
if (value?.remember) {
|
||||
remAccount.value = value.remember
|
||||
formData.account = value.account
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
background-image: url('./images/login_bg.png');
|
||||
@apply min-h-screen bg-no-repeat bg-center bg-cover;
|
||||
.login-card {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card shadow="never" class="!border-none">
|
||||
<template #header>
|
||||
<span class="font-extrabold text-lg">充值设置</span>
|
||||
</template>
|
||||
<el-form :model="formData" label-width="120px">
|
||||
<el-form-item label="状态">
|
||||
<div>
|
||||
<el-radio-group v-model="formData.status" class="ml-4">
|
||||
<el-radio :value="1">开启</el-radio>
|
||||
<el-radio :value="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">关闭或开启充值功能,关闭后将不显示充值入口</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最低充值金额">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="formData.min_amount"
|
||||
placeholder="请输入最低充值金额"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tips">
|
||||
最低充值金额要求,不填或填0表示不限制最低充值金额
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<footer-btns v-perms="['recharge.recharge/setConfig']">
|
||||
<el-button type="primary" @click="handleSubmit">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="rechargeConfig">
|
||||
import { getRechargeConfig, setRechargeConfig } from '@/api/app/recharge'
|
||||
|
||||
const formData = reactive({
|
||||
status: 1, //功能状态 1-开启 0-关闭
|
||||
min_amount: '' //最低充值金额
|
||||
})
|
||||
|
||||
const getConfig = async () => {
|
||||
const data = await getRechargeConfig()
|
||||
Object.assign(formData, data)
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
await setRechargeConfig(formData)
|
||||
getConfig()
|
||||
}
|
||||
getConfig()
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="edit-popup">
|
||||
<popup
|
||||
ref="popupRef"
|
||||
:title="popupTitle"
|
||||
:async="true"
|
||||
width="550px"
|
||||
@confirm="handleSubmit"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" label-width="84px" :rules="formRules">
|
||||
<el-form-item label="栏目名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入栏目名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<div>
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
|
||||
<div class="form-tips">默认为0, 数值越大越排前</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="is_show">
|
||||
<el-switch v-model="formData.is_show" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="articleColumnEdit">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { articleCateAdd, articleCateDetail, articleCateEdit } from '@/api/article'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
|
||||
const emit = defineEmits(['success', 'close'])
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
const mode = ref('add')
|
||||
const popupTitle = computed(() => {
|
||||
return mode.value == 'edit' ? '编辑栏目' : '新增栏目'
|
||||
})
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
sort: 0,
|
||||
is_show: 1
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入栏目名称',
|
||||
trigger: ['blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate()
|
||||
mode.value == 'edit' ? await articleCateEdit(formData) : await articleCateAdd(formData)
|
||||
popupRef.value?.close()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
const open = (type = 'add') => {
|
||||
mode.value = type
|
||||
popupRef.value?.open()
|
||||
}
|
||||
|
||||
const setFormData = (data: Record<any, any>) => {
|
||||
for (const key in formData) {
|
||||
if (data[key] != null && data[key] != undefined) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDetail = async (row: Record<string, any>) => {
|
||||
const data = await articleCateDetail({
|
||||
id: row.id
|
||||
})
|
||||
setFormData(data)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
setFormData,
|
||||
getDetail
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<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.articleCate/add']"
|
||||
type="primary"
|
||||
@click="handleAdd()"
|
||||
>
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table size="large" :data="pager.lists">
|
||||
<el-table-column label="栏目名称" prop="name" min-width="120" />
|
||||
<el-table-column label="文章数" prop="article_count" min-width="120" />
|
||||
<el-table-column label="状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-perms="['article.articleCate/updateStatus']"
|
||||
v-model="row.is_show"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="changeStatus($event, 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.articleCate/edit']"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['article.articleCate/delete']"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="articleColumn">
|
||||
import { articleCateDelete, articleCateLists, articleCateStatus } from '@/api/article'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
const showEdit = ref(false)
|
||||
|
||||
const { pager, getLists } = usePaging({
|
||||
fetchFun: articleCateLists
|
||||
})
|
||||
const handleAdd = async () => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('add')
|
||||
}
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('edit')
|
||||
editRef.value?.getDetail(data)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await articleCateDelete({ id })
|
||||
getLists()
|
||||
}
|
||||
|
||||
const changeStatus = async (is_show: any, id: number) => {
|
||||
try {
|
||||
await articleCateStatus({ id, is_show })
|
||||
getLists()
|
||||
} catch (error) {
|
||||
getLists()
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div class="article-edit">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-page-header :content="$route.meta.title" @back="$router.back()" />
|
||||
</el-card>
|
||||
<el-card class="mt-4 !border-none" shadow="never">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
class="ls-form"
|
||||
:model="formData"
|
||||
label-width="85px"
|
||||
:rules="rules"
|
||||
>
|
||||
<div class="xl:flex">
|
||||
<div>
|
||||
<el-form-item label="文章标题" prop="title">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.title"
|
||||
placeholder="请输入文章标题"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 3 }"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章栏目" prop="cid">
|
||||
<el-select
|
||||
class="w-80"
|
||||
v-model="formData.cid"
|
||||
placeholder="请选择文章栏目"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in optionsData.article_cate"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章简介" prop="desc">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.desc"
|
||||
placeholder="请输入文章简介"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
:maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="abstract">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 6, maxRows: 6 }"
|
||||
v-model="formData.abstract"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章封面" prop="image">
|
||||
<div>
|
||||
<div>
|
||||
<material-picker v-model="formData.image" :limit="1" />
|
||||
</div>
|
||||
<div class="form-tips">建议尺寸:240*180px</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.author" placeholder="请输入作者名称" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<div>
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
|
||||
<div class="form-tips">默认为0, 数值越大越排前</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="初始浏览量" prop="click_virtual">
|
||||
<div>
|
||||
<el-input-number v-model="formData.click_virtual" :min="0" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文章状态" required prop="is_show">
|
||||
<el-radio-group v-model="formData.is_show">
|
||||
<el-radio :value="1">显示</el-radio>
|
||||
<el-radio :value="0">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="xl:ml-20">
|
||||
<el-form-item label="文章内容" prop="content">
|
||||
<editor v-model="formData.content" :height="667" :width="375" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<footer-btns>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="articleListsEdit">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { articleAdd, articleCateAll, articleDetail, articleEdit } from '@/api/article'
|
||||
import { useDictOptions } from '@/hooks/useDictOptions'
|
||||
import useMultipleTabs from '@/hooks/useMultipleTabs'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
title: '',
|
||||
image: '',
|
||||
cid: '',
|
||||
desc: '',
|
||||
author: '',
|
||||
content: '',
|
||||
click_virtual: 0,
|
||||
sort: 0,
|
||||
is_show: 1,
|
||||
abstract: ''
|
||||
})
|
||||
|
||||
const { removeTab } = useMultipleTabs()
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const rules = reactive({
|
||||
title: [{ required: true, message: '请输入文章标题', trigger: 'blur' }],
|
||||
cid: [{ required: true, message: '请选择文章栏目', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const getDetails = async () => {
|
||||
const data = await articleDetail({
|
||||
id: route.query.id
|
||||
})
|
||||
Object.keys(formData).forEach((key) => {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
})
|
||||
}
|
||||
|
||||
const { optionsData } = useDictOptions<{
|
||||
article_cate: any[]
|
||||
}>({
|
||||
article_cate: {
|
||||
api: articleCateAll
|
||||
}
|
||||
})
|
||||
|
||||
const handleSave = async () => {
|
||||
await formRef.value?.validate()
|
||||
if (route.query.id) {
|
||||
await articleEdit(formData)
|
||||
} else {
|
||||
await articleAdd(formData)
|
||||
}
|
||||
removeTab()
|
||||
router.back()
|
||||
}
|
||||
|
||||
route.query.id && getDetails()
|
||||
</script>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="article-lists">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="文章标题">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="输入文章标题"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="栏目名称">
|
||||
<el-select v-model="queryParams.cid">
|
||||
<el-option label="全部" value />
|
||||
<el-option
|
||||
v-for="item in optionsData.article_cate"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="文章状态">
|
||||
<el-select v-model="queryParams.is_show">
|
||||
<el-option label="全部" value />
|
||||
<el-option label="显示" :value="1" />
|
||||
<el-option label="隐藏" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
<router-link
|
||||
v-perms="['article.article/add', 'article.article/add:edit']"
|
||||
:to="{
|
||||
path: getRoutePath('article.article/add:edit')
|
||||
}"
|
||||
>
|
||||
<el-button type="primary" class="mb-4">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
发布文章
|
||||
</el-button>
|
||||
</router-link>
|
||||
</div>
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
<el-table-column label="ID" prop="id" min-width="80" />
|
||||
<el-table-column label="封面" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<image-contain
|
||||
v-if="row.image"
|
||||
:src="row.image"
|
||||
:width="60"
|
||||
:height="45"
|
||||
:preview-src-list="[row.image]"
|
||||
preview-teleported
|
||||
fit="contain"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="标题"
|
||||
prop="title"
|
||||
min-width="160"
|
||||
show-tooltip-when-overflow
|
||||
/>
|
||||
<el-table-column label="栏目" prop="cate_name" min-width="100" />
|
||||
<el-table-column label="作者" prop="author" min-width="120" />
|
||||
<el-table-column label="浏览量" prop="click" min-width="100" />
|
||||
<el-table-column label="状态" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-perms="['article.article/updateStatus']"
|
||||
v-model="row.is_show"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="changeStatus($event, row.id)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sort" min-width="100" />
|
||||
<el-table-column label="发布时间" prop="create_time" min-width="120" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-perms="['article.article/edit', 'article.article/add:edit']"
|
||||
type="primary"
|
||||
link
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
path: getRoutePath('article.article/add:edit'),
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
}"
|
||||
>
|
||||
编辑
|
||||
</router-link>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['article.article/delete']"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="articleLists">
|
||||
import { articleCateAll, articleDelete, articleLists, articleStatus } from '@/api/article'
|
||||
import { useDictOptions } from '@/hooks/useDictOptions'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { getRoutePath } from '@/router'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const queryParams = reactive({
|
||||
title: '',
|
||||
cid: '',
|
||||
is_show: ''
|
||||
})
|
||||
|
||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||
fetchFun: articleLists,
|
||||
params: queryParams
|
||||
})
|
||||
|
||||
const { optionsData } = useDictOptions<{
|
||||
article_cate: any[]
|
||||
}>({
|
||||
article_cate: {
|
||||
api: articleCateAll
|
||||
}
|
||||
})
|
||||
|
||||
const changeStatus = async (is_show: any, id: number) => {
|
||||
try {
|
||||
await articleStatus({ id, is_show })
|
||||
getLists()
|
||||
} catch (error) {
|
||||
getLists()
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await articleDelete({ id })
|
||||
getLists()
|
||||
}
|
||||
|
||||
onActivated(() => {
|
||||
getLists()
|
||||
})
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert type="warning" title="温馨提示:H5设置" :closable="false" show-icon />
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<el-form ref="formRef" :model="formData" label-width="120px">
|
||||
<el-form-item label="渠道状态" required prop="status">
|
||||
<div>
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">开启</el-radio>
|
||||
<el-radio :value="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">状态为关闭时,将不对外提供服务,请谨慎操作</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="关闭后访问页面" prop="page_status">
|
||||
<el-radio-group v-model="formData.page_status">
|
||||
<el-radio :value="0">空页面</el-radio>
|
||||
<el-radio :value="1">自定义链接</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="page_url" v-if="formData.page_status == 1">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.page_url" placeholder="请输入完整的url" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="访问链接">
|
||||
<div class="flex-1 min-w-0 break-words">
|
||||
{{ formData.url }}
|
||||
<el-button v-copy="formData.url">复制</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<footer-btns v-perms="['channel.web_page_setting/setConfig']">
|
||||
<el-button type="primary" @click="handelSave">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="h5Config">
|
||||
import { getH5Config, setH5Config } from '@/api/channel/h5'
|
||||
|
||||
const formData = reactive({
|
||||
status: 0,
|
||||
page_status: 0,
|
||||
page_url: '',
|
||||
url: ''
|
||||
})
|
||||
|
||||
const getDetail = async () => {
|
||||
const data = await getH5Config()
|
||||
for (const key in formData) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
const handelSave = async () => {
|
||||
await setH5Config(formData)
|
||||
getDetail()
|
||||
}
|
||||
|
||||
getDetail()
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:填写微信开放平台开发配置,请前往微信开放平台创建应用并完成认证;网站应用配置主要用于网站微信登录和微信支付"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="160px">
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">网站应用</div>
|
||||
<el-form-item label="AppID" prop="app_id">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret" prop="app_secret">
|
||||
<div>
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
<footer-btns v-perms="['channel.open_setting/setConfig']">
|
||||
<el-button type="primary" @click="handelSave">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="wxDevConfig">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { getOpenSettingConfig, setOpenSettingConfig } from '@/api/channel/open_setting'
|
||||
|
||||
const formData = reactive({
|
||||
app_id: '',
|
||||
app_secret: ''
|
||||
})
|
||||
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const formRules = {
|
||||
app_id: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppID',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
app_secret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppSecret',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getDetail = async () => {
|
||||
const data = await getOpenSettingConfig()
|
||||
for (const key in formData) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
const handelSave = async () => {
|
||||
await formRef.value?.validate()
|
||||
await setOpenSettingConfig(formData)
|
||||
getDetail()
|
||||
}
|
||||
|
||||
getDetail()
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:填写微信小程序开发配置,请前往微信公众平台申请小程序并完成认证"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
:label-width="appStore.isMobile ? '80px' : '160px'"
|
||||
>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">微信小程序</div>
|
||||
<el-form-item label="小程序名称" prop="name">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.name" placeholder="请输入小程序名称" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="原始ID" prop="original_id">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.original_id" placeholder="请输入原始ID" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序码" prop="qr_code">
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<material-picker v-model="formData.qr_code" :limit="1" />
|
||||
</div>
|
||||
<div class="form-tips">建议尺寸:宽400px*高400px。jpg,jpeg,png格式</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">开发者ID</div>
|
||||
<el-form-item label="AppID" prop="app_id">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret" prop="app_secret">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->开发者ID,设置AppID和AppSecret
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">服务器域名</div>
|
||||
<el-form-item label="request合法域名" prop="appId">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.request_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.request_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="socket合法域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.socket_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.socket_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写wss协议域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="uploadFile合法域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.upload_file_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.upload_file_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="downloadFile合法域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.download_file_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.download_file_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="udp合法域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.udp_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.udp_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写udp协议域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">业务域名</div>
|
||||
<el-form-item label="业务域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.business_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.business_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->业务域名,填写业务域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
<footer-btns v-perms="['channel.mnp_settings/setConfig']">
|
||||
<el-button type="primary" @click="handelSave">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="weappConfig">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { getWeappConfig, setWeappConfig } from '@/api/channel/weapp'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
original_id: '',
|
||||
qr_code: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
business_domain: '',
|
||||
download_file_domain: '',
|
||||
request_domain: '',
|
||||
socket_domain: '',
|
||||
tcpDomain: '',
|
||||
udp_domain: '',
|
||||
upload_file_domain: ''
|
||||
})
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const formRules = {
|
||||
app_id: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppID',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
app_secret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppSecret',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
const getDetail = async () => {
|
||||
const data = await getWeappConfig()
|
||||
for (const key in formData) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
const handelSave = async () => {
|
||||
await formRef.value?.validate()
|
||||
await setWeappConfig(formData)
|
||||
getDetail()
|
||||
}
|
||||
|
||||
getDetail()
|
||||
</script>
|
||||
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:填写微信公众号开发配置,请前往微信公众平台申请服务号并完成认证"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
:label-width="appStore.isMobile ? '80px' : '160px'"
|
||||
>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">微信公众号</div>
|
||||
<el-form-item label="公众号名称" prop="name">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.name" placeholder="请输入公众号名称" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="原始ID" prop="original_id">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.original_id" placeholder="请输入原始ID" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="公众号二维码" prop="qr_code">
|
||||
<div>
|
||||
<div>
|
||||
<material-picker v-model="formData.qr_code" :limit="1" />
|
||||
</div>
|
||||
<div class="form-tips">建议尺寸:宽400px*高400px。jpg,jpeg,png格式</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">公众号开发者信息</div>
|
||||
<el-form-item label="AppID" prop="app_id">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret" prop="app_secret">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div class="form-tips">
|
||||
小程序账号登录微信公众平台,点击开发>开发设置->开发者ID,设置AppID和AppSecret
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">服务器配置</div>
|
||||
<el-form-item label="URL">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.url" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.url">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
登录微信公众平台,点击开发>基本配置>服务器配置,填写服务器地址(URL)
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="Token" prop="Token">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.token" placeholder="请输入Token" />
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
登录微信公众平台,点击开发>基本配置>服务器配置,设置令牌Token。不填默认为“likeshop”
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="EncodingAESKey" prop="encoding_aes_key">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.encoding_aes_key"
|
||||
placeholder="请输入EncodingAESKey"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
消息加密密钥由43位字符组成,字符范围为A-Z,a-z,0-9
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="消息加密方式" required prop="encryption_type">
|
||||
<div class="flex-1 min-w-0">
|
||||
<el-radio-group
|
||||
class="flex-col !items-start min-w-0"
|
||||
v-model="formData.encryption_type"
|
||||
>
|
||||
<el-radio :value="1">
|
||||
明文模式 (不使用消息体加解密功能,安全系数较低)
|
||||
</el-radio>
|
||||
|
||||
<el-radio :value="2">
|
||||
兼容模式 (明文、密文将共存,方便开发者调试和维护)
|
||||
</el-radio>
|
||||
<el-radio :value="3">
|
||||
安全模式(推荐) (消息包为纯密文,需要开发者加密和解密,安全系数高)
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="font-medium mb-7">功能设置</div>
|
||||
<el-form-item label="业务域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.business_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.business_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
登录微信公众平台,点击设置>公众号设置>功能设置,填写业务域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="JS接口安全域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.js_secure_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.js_secure_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
登录微信公众平台,点击设置>公众号设置>功能设置,填写JS接口安全域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="网页授权域名">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="sm:flex">
|
||||
<div class="mr-4 sm:w-80 flex">
|
||||
<el-input v-model="formData.web_auth_domain" disabled />
|
||||
</div>
|
||||
<el-button v-copy="formData.web_auth_domain">复制</el-button>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
登录微信公众平台,点击设置>公众号设置>功能设置,填写网页授权域名
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
<footer-btns v-perms="['channel.official_account_setting/setConfig']">
|
||||
<el-button type="primary" @click="handelSave">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="wxOaConfig">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { getOaConfig, setOaConfig } from '@/api/channel/wx_oa'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
original_id: ' ',
|
||||
qr_code: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
url: '',
|
||||
token: '',
|
||||
encoding_aes_key: '',
|
||||
encryption_type: 1,
|
||||
business_domain: '',
|
||||
js_secure_domain: '',
|
||||
web_auth_domain: ''
|
||||
})
|
||||
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const formRules = {
|
||||
app_id: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppID',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
app_secret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入AppSecret',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getDetail = async () => {
|
||||
const data = await getOaConfig()
|
||||
for (const key in formData) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
|
||||
const handelSave = async () => {
|
||||
await formRef.value?.validate()
|
||||
await setOaConfig(formData)
|
||||
getDetail()
|
||||
}
|
||||
|
||||
getDetail()
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts" name="wxOaMenu">
|
||||
import OaAttr from './menu_com/oa-attr.vue'
|
||||
import OaPhone from './menu_com/oa-phone.vue'
|
||||
import { useMenuOa } from './menu_com/useMenuOa'
|
||||
|
||||
const { getOaMenuFunc, handleSave, handlePublish } = useMenuOa(undefined)
|
||||
getOaMenuFunc()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="menu-oa">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="配置微信公众号菜单,点击确认,保存菜单并发布至微信公众号"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div class="lg:flex flex-1">
|
||||
<!-- Phone -->
|
||||
<oa-phone></oa-phone>
|
||||
|
||||
<!-- Attr -->
|
||||
<div class="mt-4 lg:mt-0 max-w-[400px]">
|
||||
<oa-attr></oa-attr>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<footer-btns>
|
||||
<el-button type="primary" @click="handleSave" v-perms="['channel:oaMenu:save']">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handlePublish" v-perms="['channel:oaMenu:publish']">
|
||||
发布
|
||||
</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-oa {
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script lang="ts" setup>
|
||||
import oaMenuForm from './oa-menu-form.vue'
|
||||
import oaMenuFormEdit from './oa-menu-form-edit.vue'
|
||||
import { useMenuOa } from './useMenuOa'
|
||||
|
||||
const menuRef = shallowRef()
|
||||
|
||||
const {
|
||||
menuList,
|
||||
menuIndex,
|
||||
handleAddSubMenu,
|
||||
handleEditSubMenu,
|
||||
handleDelMenu,
|
||||
handleDelSubMenu
|
||||
} = useMenuOa(menuRef)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Attr -->
|
||||
<template v-for="(attrItem, attrIndex) in menuList" :key="attrIndex">
|
||||
<div class="flex-1 oa-attr min-w-0" v-show="attrIndex === menuIndex">
|
||||
<div class="text-base oa-attr-title">菜单配置</div>
|
||||
|
||||
<del-wrap @close="handleDelMenu(menuIndex)">
|
||||
<div class="flex items-center w-full p-4 mt-4 rounded bg-fill-light">
|
||||
<oa-menu-form
|
||||
ref="menuRef"
|
||||
modular="master"
|
||||
v-model:name="attrItem.name"
|
||||
v-model:menuType="attrItem.has_menu"
|
||||
v-model:visitType="attrItem.type"
|
||||
v-model:url="attrItem.url"
|
||||
v-model:appId="attrItem.appid"
|
||||
v-model:pagePath="attrItem.pagepath"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<!-- 编辑子菜单 -->
|
||||
<ul>
|
||||
<li
|
||||
class="flex"
|
||||
v-for="(subItem, subIndex) in attrItem.sub_button"
|
||||
:key="subIndex"
|
||||
style="padding: 8px"
|
||||
>
|
||||
<span class="mr-auto">{{ subItem.name }}</span>
|
||||
<!-- 编辑子菜单 -->
|
||||
<oa-menu-form-edit
|
||||
modular="edit"
|
||||
:subItem="subItem"
|
||||
@edit="handleEditSubMenu($event, subIndex)"
|
||||
>
|
||||
<el-button link>
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
</oa-menu-form-edit>
|
||||
|
||||
<!-- 删除子菜单 -->
|
||||
<popup @confirm="handleDelSubMenu(menuIndex, subIndex)">
|
||||
是否删除当前子菜单?
|
||||
<template #trigger>
|
||||
<el-button link>
|
||||
<el-icon class="ml-5"><Delete /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</popup>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- 新增子菜单 -->
|
||||
<oa-menu-form-edit modular="add" @add="handleAddSubMenu">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="attrItem.sub_button.length >= 5"
|
||||
>
|
||||
添加子菜单({{ attrItem.sub_button.length }}/5)
|
||||
</el-button>
|
||||
</oa-menu-form-edit>
|
||||
</div>
|
||||
</oa-menu-form>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.oa-attr {
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script lang="ts" setup>
|
||||
import oaMenuForm from './oa-menu-form.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'add', value: any): void
|
||||
(event: 'edit', value: any): void
|
||||
}>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modular: string
|
||||
subItem?: any
|
||||
}>(),
|
||||
{
|
||||
modular: 'edit',
|
||||
subItem: {}
|
||||
}
|
||||
)
|
||||
|
||||
const menuFormEditRef = shallowRef()
|
||||
const menuFromPopupRef = shallowRef()
|
||||
|
||||
const form = {
|
||||
name: '',
|
||||
type: 'view',
|
||||
url: '',
|
||||
appid: '',
|
||||
pagepath: ''
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (Object.keys(props.subItem).length != 0) {
|
||||
for (const key in form) {
|
||||
//@ts-ignore
|
||||
form[key] = props.subItem[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleRules = async () => {
|
||||
await menuFormEditRef.value.menuFormRef.validate()
|
||||
if (props.modular === 'edit') {
|
||||
emit('edit', { ...form })
|
||||
} else {
|
||||
emit('add', { ...form })
|
||||
}
|
||||
menuFromPopupRef.value.close()
|
||||
menuFormEditRef.value.menuFormRef.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<popup
|
||||
ref="menuFromPopupRef"
|
||||
async
|
||||
:clickModalClose="false"
|
||||
:title="`${modular === 'add' ? '新增' : '编辑'}子菜单`"
|
||||
@confirm="handleRules"
|
||||
>
|
||||
<oa-menu-form
|
||||
ref="menuFormEditRef"
|
||||
modular="secondary"
|
||||
v-model:name="form.name"
|
||||
v-model:visitType="form.type"
|
||||
v-model:url="form.url"
|
||||
v-model:appId="form.appid"
|
||||
v-model:pagePath="form.pagepath"
|
||||
></oa-menu-form>
|
||||
<template #trigger>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</popup>
|
||||
</template>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<el-form ref="menuFormRef" :rules="rules" :model="menuForm" label-width="100px">
|
||||
<!-- 菜单名称 -->
|
||||
<el-form-item :label="modular === 'master' ? '主菜单名称' : '子菜单名称'" prop="name">
|
||||
<el-input v-model="menuForm.name" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 菜单类型 -->
|
||||
<el-form-item label="主菜单类型" prop="menuType" v-if="modular === 'master'">
|
||||
<el-radio-group v-model="menuForm.menuType">
|
||||
<el-radio :value="false">不配置子菜单</el-radio>
|
||||
<el-radio :value="true">配置子菜单</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="" v-if="menuForm.menuType && modular === 'master'">
|
||||
<slot></slot>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="!menuForm.menuType">
|
||||
<!-- 跳转链接 -->
|
||||
<el-form-item label="跳转链接" prop="visitType">
|
||||
<el-radio-group v-model="menuForm.visitType">
|
||||
<el-radio value="view">网页</el-radio>
|
||||
<el-radio value="miniprogram">小程序</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 网址 -->
|
||||
<el-form-item label="网址" prop="url">
|
||||
<el-input v-model="menuForm.url" />
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="menuForm.visitType == 'miniprogram'">
|
||||
<!-- AppId -->
|
||||
<el-form-item label="AppId" prop="appId">
|
||||
<el-input v-model="menuForm.appId" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 路径 -->
|
||||
<el-form-item label="路径" prop="pagePath">
|
||||
<el-input v-model="menuForm.pagePath" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { rules } from './useMenuOa'
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:name',
|
||||
'update:menuType',
|
||||
'update:visitType',
|
||||
'update:url',
|
||||
'update:appId',
|
||||
'update:pagePath'
|
||||
])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modular?: string
|
||||
name?: string
|
||||
menuType?: boolean
|
||||
visitType?: string
|
||||
url?: string
|
||||
appId?: string
|
||||
pagePath?: string
|
||||
}>(),
|
||||
{
|
||||
modular: 'master',
|
||||
name: '',
|
||||
menuType: false,
|
||||
visitType: 'view',
|
||||
url: '',
|
||||
appId: '',
|
||||
pagePath: ''
|
||||
}
|
||||
)
|
||||
|
||||
const menuFormRef = shallowRef<FormInstance>()
|
||||
// 表单数据
|
||||
const menuForm = ref({ ...props })
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
(value) => {
|
||||
menuForm.value = value
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.modular === 'master') {
|
||||
emit('update:menuType', menuForm.value.menuType)
|
||||
}
|
||||
emit('update:name', menuForm.value.name)
|
||||
emit('update:visitType', menuForm.value.visitType)
|
||||
emit('update:url', menuForm.value.url)
|
||||
emit('update:appId', menuForm.value.appId)
|
||||
emit('update:pagePath', menuForm.value.pagePath)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
menuFormRef
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,122 @@
|
||||
<script lang="ts" setup>
|
||||
import useSettingStore from '@/stores/modules/setting'
|
||||
|
||||
import { useMenuOa } from './useMenuOa'
|
||||
|
||||
// 菜单颜色(这里采用全局颜色)
|
||||
const settingStore = useSettingStore()
|
||||
const themeColor = computed(() => settingStore.theme || '#4A5DFF')
|
||||
|
||||
const { menuList, menuIndex, handleAddMenu } = useMenuOa(useMenuOa)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Phone -->
|
||||
<div class="oa-phone mr-[35px]">
|
||||
<div class="oa-phone-content"></div>
|
||||
|
||||
<div class="flex oa-phone-menu">
|
||||
<div class="flex items-center justify-center oa-phone-menu-switch">
|
||||
<el-icon>
|
||||
<Grid />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<template v-for="(menuItem, i) in menuList" :key="i">
|
||||
<div class="relative flex-1" @click="menuIndex = i">
|
||||
<!-- 一级菜单 -->
|
||||
<div
|
||||
class="flex items-center justify-center flex-1 text-sm oa-phone-menu-item"
|
||||
:class="{ 'active-menu': menuIndex === i }"
|
||||
>
|
||||
{{ menuItem.name }}
|
||||
</div>
|
||||
|
||||
<!-- 二级菜单 -->
|
||||
<div
|
||||
class="oa-phone-menu-subitem"
|
||||
v-show="menuItem.sub_button.length && menuItem.has_menu"
|
||||
>
|
||||
<template v-for="(subItem, index2) in menuItem.sub_button" :key="index2">
|
||||
<div class="oa-phone-menu-subitem-title">
|
||||
{{ subItem.name }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 新增菜单 -->
|
||||
<template v-if="menuList.length <= 2">
|
||||
<div class="flex items-center justify-center flex-1 h-full" @click="handleAddMenu">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.oa-phone {
|
||||
width: 260px;
|
||||
height: 461px;
|
||||
border: 1px solid #e5e5ea;
|
||||
flex: none;
|
||||
&-content {
|
||||
height: 420px;
|
||||
border-bottom: 1px solid #e5e5ea;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
|
||||
&-switch {
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #e5e5ea;
|
||||
}
|
||||
|
||||
// 一级菜单
|
||||
&-item {
|
||||
height: 100%;
|
||||
border-right: 1px solid #e5e5ea;
|
||||
}
|
||||
|
||||
&-item:nth-child(4) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.active-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.active-menu::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 41px;
|
||||
top: -1px;
|
||||
position: absolute;
|
||||
border: 1px solid v-bind(themeColor);
|
||||
}
|
||||
|
||||
// 二级菜单
|
||||
&-subitem {
|
||||
width: 98%;
|
||||
left: 2px;
|
||||
bottom: calc(100% + 10px);
|
||||
position: absolute;
|
||||
border: 1px solid #e5e5ea;
|
||||
border-top: none;
|
||||
|
||||
&-title {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e5e5ea;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { Menu } from '@/api/channel/wx_oa'
|
||||
import { getOaMenu, setOaMenuPublish, setOaMenuSave } from '@/api/channel/wx_oa'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
// 菜单实例
|
||||
export const menuRef = shallowRef()
|
||||
// 菜单数据
|
||||
const menuList = ref<Menu[]>([])
|
||||
const menuIndex = ref<number>(0)
|
||||
|
||||
// 校验
|
||||
export const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
{
|
||||
min: 1,
|
||||
max: 12,
|
||||
message: '长度限制12个字符',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
menuType: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
visitType: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
url: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
{
|
||||
pattern:
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
/(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/,
|
||||
message: '请输入合法的网址链接',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
pagePath: [
|
||||
{
|
||||
required: true,
|
||||
message: '必填项不能为空',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export const useMenuOa = (ref: any) => {
|
||||
if (ref) menuRef.value = ref
|
||||
|
||||
// 添加主菜单
|
||||
const handleAddMenu = () => {
|
||||
menuList.value.push({
|
||||
name: '菜单名称',
|
||||
has_menu: false,
|
||||
type: 'view',
|
||||
url: '',
|
||||
appid: '',
|
||||
pagepath: '',
|
||||
sub_button: []
|
||||
})
|
||||
}
|
||||
|
||||
// 添加子菜单
|
||||
const handleAddSubMenu = (event?: Menu) => {
|
||||
const index = menuIndex.value
|
||||
if (menuList.value[index].sub_button.length >= 5) {
|
||||
feedback.msgError('已添加上限~')
|
||||
return
|
||||
}
|
||||
menuList.value[index].sub_button.push(event)
|
||||
}
|
||||
|
||||
// 编辑子菜单
|
||||
const handleEditSubMenu = (event: Menu, subIndex: number) => {
|
||||
const index = menuIndex.value
|
||||
menuList.value[index].sub_button[subIndex] = event
|
||||
}
|
||||
|
||||
// 删除主菜单
|
||||
const handleDelMenu = (index: number) => {
|
||||
if (index != 0) {
|
||||
menuIndex.value--
|
||||
}
|
||||
menuList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 删除子菜单
|
||||
const handleDelSubMenu = (index: number, subIndex: number) => {
|
||||
menuList.value[index].sub_button.splice(subIndex, 1)
|
||||
}
|
||||
|
||||
// 获取菜单
|
||||
const getOaMenuFunc = async () => {
|
||||
try {
|
||||
menuList.value = await getOaMenu()
|
||||
} catch (error) {
|
||||
console.log('获取菜单=>', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存菜单
|
||||
const handleSave = async () => {
|
||||
const refs = menuRef.value.value
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
try {
|
||||
await refs[i].menuFormRef.validate()
|
||||
} catch (error) {
|
||||
menuIndex.value = i
|
||||
return
|
||||
}
|
||||
}
|
||||
await setOaMenuSave(menuList.value)
|
||||
}
|
||||
|
||||
// 保存菜单
|
||||
const handlePublish = async () => {
|
||||
const refs = menuRef.value.value
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
try {
|
||||
await refs[i].menuFormRef.validate()
|
||||
} catch (error) {
|
||||
menuIndex.value = i
|
||||
return
|
||||
}
|
||||
}
|
||||
await setOaMenuPublish(menuList.value)
|
||||
}
|
||||
|
||||
return {
|
||||
menuList,
|
||||
menuIndex,
|
||||
handleAddMenu,
|
||||
handleAddSubMenu,
|
||||
handleEditSubMenu,
|
||||
handleDelMenu,
|
||||
handleDelSubMenu,
|
||||
getOaMenuFunc,
|
||||
handleSave,
|
||||
handlePublish
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:1.粉丝在公众号发送内容时,系统无法匹配情况下发送启用的默认文本回复;2.同时只能启用一个默认回复。"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
<el-button class="mb-4" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
|
||||
<el-table-column label="规则名称" prop="name" min-width="120" />
|
||||
<el-table-column label="回复类型" min-width="120">
|
||||
<template #default="{ row }">
|
||||
{{ getContentType(row.content_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复内容" prop="content" min-width="120" />
|
||||
<el-table-column label="状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
: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 type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
const showEdit = ref(false)
|
||||
|
||||
const getContentType = computed(() => {
|
||||
return (val: number) => {
|
||||
switch (val) {
|
||||
case 1:
|
||||
return '文本'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { pager, getLists } = usePaging({
|
||||
fetchFun: getOaReplyList,
|
||||
params: {
|
||||
reply_type: 3
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = async () => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('add', 3)
|
||||
}
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('edit', 3)
|
||||
editRef.value?.getDetail(data)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await oaReplyDel({ id })
|
||||
feedback.msgSuccess('删除成功')
|
||||
getLists()
|
||||
}
|
||||
|
||||
const changeStatus = async (id: number) => {
|
||||
try {
|
||||
await changeOaReplyStatus({ id })
|
||||
getLists()
|
||||
} catch (error) {
|
||||
getLists()
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div class="edit-popup">
|
||||
<popup
|
||||
ref="popupRef"
|
||||
:title="popupTitle"
|
||||
:async="true"
|
||||
width="500px"
|
||||
@confirm="handleSubmit"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
label-width="84px"
|
||||
:rules="formRules"
|
||||
class="pr-10"
|
||||
>
|
||||
<el-form-item label="规则名称" prop="name">
|
||||
<div class="flex-1">
|
||||
<el-input v-model="formData.name" placeholder="请输入规则名称" />
|
||||
<div class="form-tips">方便通过名称管理关注回复内容</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="keyword" v-if="formData.reply_type == 2">
|
||||
<div class="flex-1">
|
||||
<el-input v-model="formData.keyword" placeholder="请输入关键词" />
|
||||
<div class="form-tips">方便通过名称管理关注回复内容</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="匹配方式" prop="matching_type" v-if="formData.reply_type == 2">
|
||||
<div class="flex-1">
|
||||
<el-radio-group v-model="formData.matching_type">
|
||||
<el-radio :value="1">全匹配</el-radio>
|
||||
<el-radio :value="2">模糊匹配</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">模糊匹配时,关键词部分匹配用户输入的内容即可</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="回复类型" prop="content_type" :min="0">
|
||||
<div class="flex-1">
|
||||
<el-radio-group v-model="formData.content_type">
|
||||
<el-radio :value="1">文本</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">暂时只支持文本类型</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="回复内容" prop="content">
|
||||
<div class="flex-1">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
:autosize="{ minRows: 4, maxRows: 4 }"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入回复内容"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<div class="flex-1">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="回复数量"
|
||||
prop="reply_num"
|
||||
required
|
||||
v-if="formData.reply_type == 2"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<el-radio-group v-model="formData.reply_num">
|
||||
<el-radio :value="1">回复匹配首词条</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">
|
||||
设置关键词匹配多条时回复的数量,暂时支持回复一条内容
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
|
||||
import { getOaReplyDetail, oaReplyAdd, oaReplyEdit } from '@/api/channel/wx_oa'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
|
||||
const emit = defineEmits(['success', 'close'])
|
||||
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
const mode = ref('add')
|
||||
const popupTitle = computed(() => {
|
||||
return mode.value == 'edit' ? '编辑' : '新增'
|
||||
})
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
reply_type: 0,
|
||||
content_type: 1,
|
||||
keyword: '',
|
||||
content: '',
|
||||
matching_type: 1,
|
||||
status: 1,
|
||||
sort: 0,
|
||||
reply_num: 1
|
||||
})
|
||||
|
||||
const formRules: FormRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入规则名称',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
keyword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入关键词',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
matching_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择匹配方式',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
content_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择回复类型',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入回复内容',
|
||||
trigger: ['blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate()
|
||||
mode.value == 'edit' ? await oaReplyEdit(formData) : await oaReplyAdd(formData)
|
||||
popupRef.value?.close()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
const open = (modes = 'add', type = 0) => {
|
||||
mode.value = modes
|
||||
formData.reply_type = type
|
||||
popupRef.value?.open()
|
||||
}
|
||||
|
||||
const setFormData = (data: Record<any, any>) => {
|
||||
for (const key in formData) {
|
||||
if (data[key] != null && data[key] != undefined) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDetail = async (row: Record<string, any>) => {
|
||||
const data = await getOaReplyDetail({
|
||||
id: row.id
|
||||
})
|
||||
setFormData(data)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
setFormData,
|
||||
getDetail
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:1.粉丝关注公众号时,会自动发送启用的关注回复;2.同时只能启用一个关注回复。"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
<el-button class="mb-4" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
|
||||
<el-table-column label="规则名称" prop="name" min-width="120" />
|
||||
<el-table-column label="回复类型" min-width="120">
|
||||
<template #default="{ row }">
|
||||
{{ getContentType(row.content_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复内容" prop="content" min-width="120" />
|
||||
<el-table-column label="状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
: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 type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
const showEdit = ref(false)
|
||||
|
||||
const getContentType = computed(() => {
|
||||
return (val: number) => {
|
||||
switch (val) {
|
||||
case 1:
|
||||
return '文本'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { pager, getLists } = usePaging({
|
||||
fetchFun: getOaReplyList,
|
||||
params: {
|
||||
reply_type: 1
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = async () => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('add', 1)
|
||||
}
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('edit', 1)
|
||||
editRef.value?.getDetail(data)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await oaReplyDel({ id })
|
||||
getLists()
|
||||
}
|
||||
|
||||
const changeStatus = async (id: number) => {
|
||||
try {
|
||||
await changeOaReplyStatus({ id })
|
||||
getLists()
|
||||
} catch (error) {
|
||||
getLists()
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:1.粉丝在公众号发送内容时,通过关键词可触发关键词回复;2.同时可启用多个关键词回复,有多条关键词匹配时优选选择排序靠前的一条"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<div>
|
||||
<el-button class="mb-4" type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
|
||||
<el-table-column label="规则名称" prop="name" min-width="120" />
|
||||
|
||||
<el-table-column label="关键词" prop="keyword" min-width="120" />
|
||||
<el-table-column label="匹配方式" min-width="120">
|
||||
<template #default="{ row }">
|
||||
{{ getMatchingType(row.matching_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复类型" min-width="120">
|
||||
<template #default="{ row }">
|
||||
{{ getContentType(row.content_type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
: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 type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import EditPopup from './edit.vue'
|
||||
|
||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||
const showEdit = ref(false)
|
||||
|
||||
const getMatchingType = computed(() => {
|
||||
return (val: number) => {
|
||||
switch (val) {
|
||||
case 1:
|
||||
return '全匹配'
|
||||
case 2:
|
||||
return '模糊匹配'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getContentType = computed(() => {
|
||||
return (val: number) => {
|
||||
switch (val) {
|
||||
case 1:
|
||||
return '文本'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { pager, getLists } = usePaging({
|
||||
fetchFun: getOaReplyList,
|
||||
params: {
|
||||
reply_type: 2
|
||||
}
|
||||
})
|
||||
const handleAdd = async () => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('add', 2)
|
||||
}
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
editRef.value?.open('edit', 2)
|
||||
editRef.value?.getDetail(data)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await oaReplyDel({ id })
|
||||
getLists()
|
||||
}
|
||||
|
||||
const changeStatus = async (id: number) => {
|
||||
try {
|
||||
await changeOaReplyStatus({ id })
|
||||
getLists()
|
||||
} catch (error) {
|
||||
getLists()
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<popup
|
||||
ref="popupRef"
|
||||
title="余额调整"
|
||||
width="500px"
|
||||
@confirm="handleConfirm"
|
||||
:async="true"
|
||||
@close="popupClose"
|
||||
>
|
||||
<div class="pr-8">
|
||||
<el-form ref="formRef" :model="formData" label-width="120px" :rules="formRules">
|
||||
<el-form-item label="当前余额">¥ {{ value }} </el-form-item>
|
||||
<el-form-item label="余额增减" required prop="action">
|
||||
<el-radio-group v-model="formData.action">
|
||||
<el-radio :value="1">增加余额</el-radio>
|
||||
<el-radio :value="2">扣减余额</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整余额" prop="num">
|
||||
<el-input
|
||||
:model-value="formData.num"
|
||||
placeholder="请输入调整的金额"
|
||||
type="number"
|
||||
@input="numberValidate"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整后余额"> ¥ {{ adjustmentMoney }} </el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="4" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</popup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:show', value: boolean): void
|
||||
(event: 'confirm', value: any): void
|
||||
}>()
|
||||
const formData = reactive({
|
||||
action: 1, //变动类型 1-增加 2-减少
|
||||
num: '',
|
||||
remark: ''
|
||||
})
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
|
||||
const adjustmentMoney = computed(() => {
|
||||
return Number(props.value) + Number(formData.num) * (formData.action == 1 ? 1 : -1)
|
||||
})
|
||||
|
||||
const formRules: FormRules = {
|
||||
num: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入调整的金额'
|
||||
}
|
||||
]
|
||||
}
|
||||
const numberValidate = (value: string) => {
|
||||
if (value.includes('-')) {
|
||||
return feedback.msgError('请输入正整数')
|
||||
}
|
||||
formData.num = value
|
||||
}
|
||||
const handleConfirm = async () => {
|
||||
await formRef.value?.validate()
|
||||
emit('confirm', formData)
|
||||
}
|
||||
|
||||
const popupClose = () => {
|
||||
emit('update:show', false)
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
watch(
|
||||
() => props.show,
|
||||
(val) => {
|
||||
if (val) {
|
||||
popupRef.value?.open()
|
||||
} else {
|
||||
popupRef.value?.close()
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(adjustmentMoney, (val) => {
|
||||
if (val < 0) {
|
||||
feedback.msgError('调整后余额需大于0')
|
||||
formData.num = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-page-header content="用户详情" @back="$router.back()" />
|
||||
</el-card>
|
||||
<el-card class="mt-4 !border-none" header="基本资料" shadow="never">
|
||||
<el-form ref="formRef" class="ls-form" :model="formData" label-width="120px">
|
||||
<div class="bg-page flex py-5 mb-10 items-center">
|
||||
<div class="basis-40 flex flex-col justify-center items-center">
|
||||
<div class="mb-2 text-tx-regular">用户头像</div>
|
||||
<el-avatar :src="formData.avatar" :size="58" />
|
||||
</div>
|
||||
<div class="basis-40 flex flex-col justify-center items-center">
|
||||
<div class="text-tx-regular">账户余额</div>
|
||||
<div class="mt-2 flex items-center">
|
||||
¥{{ formData.user_money }}
|
||||
<el-button
|
||||
v-perms="['user.user/adjustMoney']"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleAdjust(formData.user_money)"
|
||||
>
|
||||
调整
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item label="用户昵称:">
|
||||
{{ formData.nickname }}
|
||||
</el-form-item>
|
||||
<el-form-item label="账号:">
|
||||
{{ formData.account }}
|
||||
<popover-input
|
||||
class="ml-[10px]"
|
||||
@confirm="handleEdit($event, 'account')"
|
||||
:limit="32"
|
||||
v-perms="['user.user/edit']"
|
||||
>
|
||||
<el-button type="primary" link>
|
||||
<icon name="el-icon-EditPen" />
|
||||
</el-button>
|
||||
</popover-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名:">
|
||||
{{ formData.real_name || '-' }}
|
||||
<popover-input
|
||||
class="ml-[10px]"
|
||||
@confirm="handleEdit($event, 'real_name')"
|
||||
:limit="32"
|
||||
v-perms="['user.user/edit']"
|
||||
>
|
||||
<el-button type="primary" link>
|
||||
<icon name="el-icon-EditPen" />
|
||||
</el-button>
|
||||
</popover-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别:">
|
||||
{{ formData.sex }}
|
||||
<popover-input
|
||||
class="ml-[10px]"
|
||||
type="select"
|
||||
:options="[
|
||||
{
|
||||
label: '未知',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: '男',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '女',
|
||||
value: 2
|
||||
}
|
||||
]"
|
||||
@confirm="handleEdit($event, 'sex')"
|
||||
v-perms="['user.user/edit']"
|
||||
>
|
||||
<el-button type="primary" link>
|
||||
<icon name="el-icon-EditPen" />
|
||||
</el-button>
|
||||
</popover-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话:">
|
||||
{{ formData.mobile || '-' }}
|
||||
<popover-input
|
||||
class="ml-[10px]"
|
||||
type="number"
|
||||
@confirm="handleEdit($event, 'mobile')"
|
||||
v-perms="['user.user/edit']"
|
||||
>
|
||||
<el-button type="primary" link>
|
||||
<icon name="el-icon-EditPen" />
|
||||
</el-button>
|
||||
</popover-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册来源:"> {{ formData.channel }} </el-form-item>
|
||||
<el-form-item label="注册时间:"> {{ formData.create_time }} </el-form-item>
|
||||
<el-form-item label="最近登录时间:"> {{ formData.login_time }} </el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<account-adjust
|
||||
v-model:show="adjustState.show"
|
||||
:value="adjustState.value"
|
||||
@confirm="handleConfirmAdjust"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="consumerDetail">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
import { adjustMoney, getUserDetail, userEdit } from '@/api/consumer'
|
||||
import { isEmpty } from '@/utils/util'
|
||||
|
||||
import AccountAdjust from '../components/account-adjust.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const formData = reactive({
|
||||
avatar: '',
|
||||
channel: '',
|
||||
create_time: '',
|
||||
login_time: '',
|
||||
mobile: '',
|
||||
nickname: '',
|
||||
real_name: 0,
|
||||
sex: 0,
|
||||
sn: '',
|
||||
account: '',
|
||||
user_money: ''
|
||||
})
|
||||
|
||||
const adjustState = reactive({
|
||||
show: false,
|
||||
value: ''
|
||||
})
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
|
||||
const getDetails = async () => {
|
||||
const data = await getUserDetail({
|
||||
id: route.query.id
|
||||
})
|
||||
Object.keys(formData).forEach((key) => {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
})
|
||||
}
|
||||
|
||||
const handleEdit = async (value: string, field: string) => {
|
||||
if (isEmpty(value)) return
|
||||
await userEdit({
|
||||
id: route.query.id,
|
||||
field,
|
||||
value
|
||||
})
|
||||
getDetails()
|
||||
}
|
||||
|
||||
const handleAdjust = (value: string) => {
|
||||
adjustState.show = true
|
||||
adjustState.value = value
|
||||
}
|
||||
const handleConfirmAdjust = async (value: any) => {
|
||||
await adjustMoney({ user_id: route.query.id, ...value })
|
||||
adjustState.show = false
|
||||
getDetails()
|
||||
}
|
||||
getDetails()
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-form ref="formRef" class="mb-[-16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="用户信息">
|
||||
<el-input
|
||||
v-model="queryParams.keyword"
|
||||
placeholder="账号/昵称/手机号码"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册时间">
|
||||
<daterange-picker
|
||||
v-model:startTime="queryParams.create_time_start"
|
||||
v-model:endTime="queryParams.create_time_end"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="注册来源">
|
||||
<el-select v-model="queryParams.channel">
|
||||
<el-option
|
||||
v-for="(item, key) in ClientMap"
|
||||
:key="key"
|
||||
:label="item"
|
||||
:value="key"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
<export-data
|
||||
class="ml-2.5"
|
||||
:fetch-fun="getUserList"
|
||||
:params="queryParams"
|
||||
:page-size="pager.size"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
<el-table-column label="头像" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-avatar :src="row.avatar" :size="50" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="昵称" prop="nickname" min-width="100" />
|
||||
<el-table-column label="账号" prop="account" min-width="120" />
|
||||
<el-table-column label="手机号码" prop="mobile" min-width="100" />
|
||||
<el-table-column label="注册来源" prop="channel" min-width="100" />
|
||||
<el-table-column label="注册时间" prop="create_time" min-width="120" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-perms="['user.user/detail']" type="primary" link>
|
||||
<router-link
|
||||
:to="{
|
||||
path: getRoutePath('user.user/detail'),
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
}"
|
||||
>
|
||||
详情
|
||||
</router-link>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="consumerLists">
|
||||
import { getUserList } from '@/api/consumer'
|
||||
import { ClientMap } from '@/enums/appEnums'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { getRoutePath } from '@/router'
|
||||
|
||||
const queryParams = reactive({
|
||||
keyword: '',
|
||||
channel: '',
|
||||
create_time_start: '',
|
||||
create_time_end: ''
|
||||
})
|
||||
|
||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||
fetchFun: getUserList,
|
||||
params: queryParams
|
||||
})
|
||||
onActivated(() => {
|
||||
getLists()
|
||||
})
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<draggable
|
||||
class="draggable"
|
||||
v-model="navLists"
|
||||
animation="300"
|
||||
handle=".drag-move"
|
||||
item-key="index"
|
||||
>
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap class="w-[467px]" :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"
|
||||
size="60px"
|
||||
exclude-domain
|
||||
>
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="20" />
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<div class="ml-3 flex-1">
|
||||
<div class="flex items-center">
|
||||
<span class="text-tx-regular flex-none mr-3">名称</span>
|
||||
<el-input v-model="item.name" placeholder="请输入名称" />
|
||||
</div>
|
||||
<div class="flex items-center mt-[18px]">
|
||||
<span class="text-tx-regular flex-none mr-3">链接</span>
|
||||
<link-picker v-model="item.link" />
|
||||
</div>
|
||||
<el-form-item label="是否显示" class="mt-[18px]">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
v-model="item.is_show"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleAdd">添加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const navLists = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.modelValue?.length < props.max) {
|
||||
navLists.value.push({
|
||||
image: '',
|
||||
name: '导航名称',
|
||||
link: {},
|
||||
is_show: '1'
|
||||
})
|
||||
} else {
|
||||
feedback.msgError(`最多添加${props.max}个`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.modelValue?.length <= props.min) {
|
||||
return feedback.msgError(`最少保留${props.min}个`)
|
||||
}
|
||||
navLists.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<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 { imageProps } from 'element-plus'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import { addUnit } from '@/utils/util'
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="pages-setting">
|
||||
<el-card shadow="never" class="!border-none flex">
|
||||
<div
|
||||
class="title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"
|
||||
>
|
||||
{{ widget?.title }}
|
||||
</div>
|
||||
</el-card>
|
||||
<el-scrollbar class="w-full" style="height: calc(100% - 60px)">
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="widgets[widget?.name]?.attr"
|
||||
:content="widget?.content"
|
||||
:styles="widget?.styles"
|
||||
:type="type"
|
||||
@update:content="handleUpdateContent"
|
||||
/>
|
||||
</keep-alive>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import widgets from '../widgets'
|
||||
|
||||
const emits = defineEmits(['update:content'])
|
||||
const handleUpdateContent = (data: any) => {
|
||||
emits('update:content', data)
|
||||
}
|
||||
|
||||
defineProps({
|
||||
widget: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'mobile' | 'pc'>,
|
||||
default: 'mobile'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="pages-menu">
|
||||
<el-menu
|
||||
:default-active="modelValue"
|
||||
class="w-[160px] min-h-[668px]"
|
||||
@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>
|
||||
</div>
|
||||
</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) {
|
||||
border-right: none;
|
||||
}
|
||||
: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,167 @@
|
||||
<template>
|
||||
<div class="pages-preview">
|
||||
<div class="relative flex justify-center h-full mt-5 mx-10">
|
||||
<!-- iframe预览 -->
|
||||
<iframe
|
||||
v-if="$route.query.url"
|
||||
ref="previewIframeRef"
|
||||
class="flex-1 h-full"
|
||||
width="100%"
|
||||
height="100%"
|
||||
scrolling="no"
|
||||
:src="$route.query.url as string"
|
||||
></iframe>
|
||||
<div class="max-w-[1200px] w-full absolute">
|
||||
<div
|
||||
v-for="(widget, index) in pageData"
|
||||
:key="widget.id"
|
||||
class="absolute left-0 top-0"
|
||||
: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-[#dcdfe6] border-2': !widget?.disabled,
|
||||
hide: canShowCom(widget.content)
|
||||
}"
|
||||
:style="widget.styles"
|
||||
></div>
|
||||
<slot>
|
||||
<component
|
||||
:is="widgets[widget?.name]?.content"
|
||||
:content="widget.content"
|
||||
:styles="widget.styles"
|
||||
:key="widget.id"
|
||||
ref="commonComponentRef"
|
||||
/>
|
||||
</slot>
|
||||
<!-- 部件操作按钮组 -->
|
||||
<div
|
||||
class="widget-btns py-[5px]"
|
||||
v-if="index == modelValue"
|
||||
:style="{
|
||||
top: widget.styles.top,
|
||||
left: widget.styles.width
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<el-tooltip effect="dark" content="编辑组件内容" placement="right">
|
||||
<el-button
|
||||
class="py-[5px]"
|
||||
type="primary"
|
||||
:icon="Setting"
|
||||
@click="handleClickSetting(index)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="canShowCom(widget.content) ? '显示' : '隐藏'"
|
||||
placement="right"
|
||||
>
|
||||
<el-button
|
||||
class="py-[5px]"
|
||||
type="primary"
|
||||
:icon="canShowCom(widget.content) ? View : Hide"
|
||||
@click="changeShowCom(widget.content)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Hide, Setting, View } from '@element-plus/icons-vue'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import widgets from '../widgets'
|
||||
|
||||
const commonComponentRef = shallowRef<any>()
|
||||
|
||||
defineProps({
|
||||
pageData: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: number): void
|
||||
}>()
|
||||
|
||||
// 是否显示组件
|
||||
const canShowCom = computed(() => {
|
||||
return (data: any) => {
|
||||
return data?.enabled == 0
|
||||
}
|
||||
})
|
||||
|
||||
// 点击了组件设置
|
||||
const handleClickSetting = (index: number) => {
|
||||
commonComponentRef.value[index]?.open()
|
||||
}
|
||||
|
||||
// 修改组件显示/隐藏
|
||||
const changeShowCom = (data: any) => {
|
||||
if (data.enabled === undefined) return
|
||||
data.enabled = data.enabled ? 0 : 1
|
||||
}
|
||||
|
||||
const handleClick = (widget: any, index: number) => {
|
||||
if (widget.disabled) return
|
||||
emit('update:modelValue', index)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-preview {
|
||||
@apply w-full h-full relative;
|
||||
height: 85vh;
|
||||
|
||||
.select {
|
||||
@apply border-primary border-solid;
|
||||
}
|
||||
|
||||
.hide::before {
|
||||
content: '已隐藏';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.widget-btns {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
|
||||
width: 46px;
|
||||
border-radius: 8px;
|
||||
@apply bg-primary;
|
||||
margin-left: 10px;
|
||||
|
||||
:deep(.el-button) {
|
||||
width: 46px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<el-scrollbar class="pages-preview-container">
|
||||
<div v-if="pageMeta !== null" class="absolute right-4 top-4" @click="handleClickPageMeta">
|
||||
<el-button>页面设置</el-button>
|
||||
</div>
|
||||
<div class="shadow mx-[30px] pages-preview">
|
||||
<div
|
||||
v-for="(widget, index) in pageData"
|
||||
:key="widget.id"
|
||||
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,
|
||||
hide: canShowCom(widget.content),
|
||||
'border-[#dcdfe6] border-2': !widget?.disabled
|
||||
}"
|
||||
></div>
|
||||
<!-- 选中的组件 -->
|
||||
<slot>
|
||||
<component
|
||||
:is="widgets[widget?.name]?.content"
|
||||
:content="widget.content"
|
||||
:styles="widget.styles"
|
||||
:key="widget.id"
|
||||
/>
|
||||
</slot>
|
||||
<!-- 部件操作按钮组 -->
|
||||
<div class="widget-btns py-[5px]" v-if="index == modelValue">
|
||||
<div>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="canShowCom(widget.content) ? '显示' : '隐藏'"
|
||||
placement="right"
|
||||
>
|
||||
<el-button
|
||||
class="py-[5px]"
|
||||
type="primary"
|
||||
:icon="canShowCom(widget.content) ? View : Hide"
|
||||
@click="changeShowCom(widget.content)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip effect="dark" content="上移" placement="right">
|
||||
<el-button
|
||||
class="py-[5px]"
|
||||
type="primary"
|
||||
:icon="ArrowUpBold"
|
||||
:disabled="canMoveUpCom(index)"
|
||||
@click.stop="rearrangeArray(index, index - 1)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<el-tooltip effect="dark" content="下移" placement="right">
|
||||
<el-button
|
||||
class="py-[5px]"
|
||||
type="primary"
|
||||
:icon="ArrowDownBold"
|
||||
:disabled="canMoveDownCom(index)"
|
||||
@click.stop="rearrangeArray(index, index + 1)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ArrowDownBold, ArrowUpBold, Hide, View } from '@element-plus/icons-vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { PropType } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import widgets from '../widgets'
|
||||
|
||||
const props = defineProps({
|
||||
pageMeta: {
|
||||
type: Object as any,
|
||||
default: () => null
|
||||
},
|
||||
pageData: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: number): void
|
||||
(event: 'updatePageData', value: any[]): void
|
||||
}>()
|
||||
|
||||
const oldModelValue = ref<number>(-1)
|
||||
|
||||
const handleClickPageMeta = () => {
|
||||
if (props.modelValue === -1) {
|
||||
emit('update:modelValue', oldModelValue.value)
|
||||
} else {
|
||||
oldModelValue.value = props.modelValue
|
||||
emit('update:modelValue', -1)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (widget: any, index: number) => {
|
||||
if (widget.disabled) return
|
||||
emit('update:modelValue', index)
|
||||
}
|
||||
|
||||
// 是否可以移动组件
|
||||
const canMoveUpCom = computed(() => {
|
||||
return (index: number) => {
|
||||
return index === 0
|
||||
}
|
||||
})
|
||||
|
||||
// 是否可以移动组件
|
||||
const canMoveDownCom = computed(() => {
|
||||
return (index: number) => {
|
||||
return props.pageData?.length === index + 1
|
||||
}
|
||||
})
|
||||
|
||||
// 是否显示组件
|
||||
const canShowCom = computed(() => {
|
||||
return (data: any) => {
|
||||
return data?.enabled == 0
|
||||
}
|
||||
})
|
||||
|
||||
// 修改组件显示/隐藏
|
||||
const changeShowCom = (data: any) => {
|
||||
if (data.enabled === undefined) return
|
||||
data.enabled = data.enabled ? 0 : 1
|
||||
}
|
||||
|
||||
const rearrangeArray = (currentIdx: number, targetIdx: number) => {
|
||||
if (
|
||||
currentIdx < 0 ||
|
||||
currentIdx >= props.pageData.length ||
|
||||
targetIdx < 0 ||
|
||||
targetIdx >= props.pageData.length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// const element = props.pageData.splice(currentIdx, 1)[0]
|
||||
// props.pageData.splice(targetIdx, 0, element)
|
||||
const newPageData = cloneDeep(props.pageData)
|
||||
const element = newPageData.splice(currentIdx, 1)[0]
|
||||
newPageData.splice(targetIdx, 0, element)
|
||||
|
||||
emit('updatePageData', newPageData)
|
||||
emit('update:modelValue', targetIdx)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-preview-container {
|
||||
position: relative;
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pages-preview {
|
||||
background-color: #f8f8f8;
|
||||
width: 360px;
|
||||
min-height: 615px;
|
||||
color: #333;
|
||||
|
||||
.select {
|
||||
@apply border-primary border-solid;
|
||||
}
|
||||
|
||||
.hide::before {
|
||||
content: '已隐藏';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.widget-btns {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -60px;
|
||||
overflow: hidden;
|
||||
|
||||
width: 46px;
|
||||
border-radius: 8px;
|
||||
@apply bg-primary;
|
||||
|
||||
:deep(.el-button) {
|
||||
width: 46px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<el-card shadow="never" class="!border-none flex">
|
||||
<div
|
||||
class="title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2 text-xl font-medium"
|
||||
>
|
||||
底部导航设置
|
||||
<span class="form-tips ml-[10px] !mt-0"> 至少添加2个导航,最多添加5个导航 </span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">展示样式</div>
|
||||
</div>
|
||||
<el-form-item label="默认颜色">
|
||||
<color-picker
|
||||
class="max-w-[400px]"
|
||||
v-model="data.style.default_color"
|
||||
default-color="#999999"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="选中颜色" style="margin-bottom: 0">
|
||||
<color-picker
|
||||
class="max-w-[400px]"
|
||||
v-model="data.style.selected_color"
|
||||
default-color="#4173ff"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">菜单设置</div>
|
||||
<div class="text-xs text-tx-secondary ml-2">建议图片尺寸:100px*100px</div>
|
||||
</div>
|
||||
<div class="mb-[18px] max-w-[400px]">
|
||||
<Draggable
|
||||
class="draggable"
|
||||
v-model="data.list"
|
||||
animation="300"
|
||||
draggable=".draggable"
|
||||
handle=".drag-move"
|
||||
:move="onMove"
|
||||
item-key="index"
|
||||
>
|
||||
<template v-slot:item="{ element, index }">
|
||||
<del-wrap
|
||||
@close="handleDelete(index)"
|
||||
class="max-w-[400px]"
|
||||
:show-close="index !== 0"
|
||||
:class="{ draggable: index != 0 }"
|
||||
>
|
||||
<div class="bg-fill-light w-full p-4 mt-4">
|
||||
<el-form-item label="导航图标">
|
||||
<material-picker
|
||||
v-model="element.unselected"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
size="60px"
|
||||
>
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="16" />
|
||||
<span class="text-xs leading-5"> 未选中 </span>
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<material-picker
|
||||
v-model="element.selected"
|
||||
exclude-domain
|
||||
upload-class="bg-body"
|
||||
size="60px"
|
||||
>
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="16" />
|
||||
<span class="text-xs leading-5"> 选中 </span>
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="导航名称">
|
||||
<el-input v-model="element.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="链接地址">
|
||||
<link-picker
|
||||
:is-tab="true"
|
||||
:disabled="index === 0"
|
||||
v-model="element.link"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
:disabled="index == 0"
|
||||
v-model="element.is_show"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="handleShowChange(element)"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
|
||||
<div v-if="data.list?.length < max" class="mt-4">
|
||||
<el-button class="w-full" type="primary" @click="handleAdd">
|
||||
添加导航 {{ data.list?.length }} / {{ max }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
list: [],
|
||||
style: {}
|
||||
})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: any): void
|
||||
}>()
|
||||
const data = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const max = 5
|
||||
const min = 2
|
||||
const showTabbarList = computed(() => {
|
||||
return data.value.list?.filter((tab: any) => tab.is_show == '1') || []
|
||||
})
|
||||
const handleAdd = () => {
|
||||
if (data.value.list?.length < max) {
|
||||
data.value.list.push({
|
||||
name: '',
|
||||
selected: '',
|
||||
unselected: '',
|
||||
is_show: 1,
|
||||
link: {}
|
||||
})
|
||||
} else {
|
||||
feedback.msgError(`最多添加${max}个`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (data.value.list?.length <= min) {
|
||||
return feedback.msgError(`最少保留${min}个`)
|
||||
}
|
||||
data.value.list.splice(index, 1)
|
||||
}
|
||||
|
||||
const onMove = (e: any) => {
|
||||
if (e.relatedContext.index == 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const handleShowChange = (item: any) => {
|
||||
if (showTabbarList.value.length < min) {
|
||||
item.is_show = 1
|
||||
return feedback.msgError(`最少显示${min}个`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="tabbar flex">
|
||||
<template v-for="(item, index) in showTabbarList" :key="index">
|
||||
<div
|
||||
class="tabbar-item flex flex-col justify-center items-center flex-1"
|
||||
:style="{ color: style.default_color }"
|
||||
>
|
||||
<decoration-img width="22px" height="22px" :src="item.unselected" fit="cover" />
|
||||
<div class="leading-3 text-[12px] mt-[4px]">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.vue'
|
||||
|
||||
const props = defineProps({
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
list: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const showTabbarList = computed(() => {
|
||||
return props.list?.filter((tab: any) => tab.is_show == 1) || []
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tabbar {
|
||||
position: absolute;
|
||||
height: 50px;
|
||||
background-color: #fff;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border: 2px solid var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div
|
||||
class="title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2"
|
||||
>
|
||||
pc导航设置
|
||||
</div>
|
||||
<el-form class="mt-4" label-width="70px">
|
||||
<el-tabs model-value="nav">
|
||||
<el-tab-pane label="主导航设置" name="nav">
|
||||
<menu-set v-model="data.nav" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="菜单设置" name="menu">
|
||||
<menu-set v-model="data.menu" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import MenuSet from './menu-set.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
nav: [],
|
||||
menu: {}
|
||||
})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: any): void
|
||||
}>()
|
||||
const data = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="pc-aside absolute top-0 bottom-0 left-0 w-[150px]">
|
||||
<ElScrollbar>
|
||||
<div class="h-full px-[10px] flex flex-col">
|
||||
<div class="mb-auto">
|
||||
<div class="nav">
|
||||
<template v-for="(item, index) in nav" :key="index">
|
||||
<div
|
||||
v-if="item.is_show == '1'"
|
||||
:to="item.link.path"
|
||||
class="nav-item"
|
||||
:class="{
|
||||
active: index == 0
|
||||
}"
|
||||
>
|
||||
<decoration-img
|
||||
width="18px"
|
||||
height="18px"
|
||||
:src="index == 0 ? item.selected : item.unselected"
|
||||
fit="cover"
|
||||
>
|
||||
<template #error>
|
||||
<span></span>
|
||||
</template>
|
||||
</decoration-img>
|
||||
|
||||
<div class="ml-[10px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="menu">
|
||||
<div class="menu-item" v-for="(item, index) in menu" :key="index">
|
||||
<decoration-img
|
||||
width="16px"
|
||||
height="16px"
|
||||
:src="item.unselected"
|
||||
fit="cover"
|
||||
>
|
||||
<template #error>
|
||||
<span></span>
|
||||
</template>
|
||||
</decoration-img>
|
||||
|
||||
<span class="ml-[6px] line-clamp-1">{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.vue'
|
||||
|
||||
defineProps({
|
||||
nav: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
menu: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pc-aside {
|
||||
border: 2px solid var(--el-color-primary);
|
||||
:deep(.el-scrollbar__view) {
|
||||
height: 100%;
|
||||
}
|
||||
.nav {
|
||||
.nav-item {
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
margin: 10px 0;
|
||||
padding: 0 12px;
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #19e8b7 0%, #00abff 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
padding: 0 12px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin: 3px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="mb-[18px] max-w-[400px]">
|
||||
<Draggable
|
||||
class="draggable"
|
||||
v-model="menu"
|
||||
animation="300"
|
||||
handle=".drag-move"
|
||||
item-key="index"
|
||||
>
|
||||
<template v-slot:item="{ element, index }">
|
||||
<del-wrap @close="handleDelete(index)" class="max-w-[400px]">
|
||||
<div class="bg-fill-light w-full p-4 mt-4">
|
||||
<el-form-item
|
||||
label="导航图标"
|
||||
v-if="
|
||||
element.unselected !== undefined || element.selected !== undefined
|
||||
"
|
||||
>
|
||||
<material-picker
|
||||
v-if="element.unselected !== undefined"
|
||||
v-model="element.unselected"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
size="60px"
|
||||
>
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="16" />
|
||||
<span class="text-xs leading-5"> 未选中 </span>
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<material-picker
|
||||
v-model="element.selected"
|
||||
v-if="element.selected !== undefined"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
size="60px"
|
||||
>
|
||||
<template #upload>
|
||||
<div class="upload-btn w-[60px] h-[60px]">
|
||||
<icon name="el-icon-Plus" :size="16" />
|
||||
<span class="text-xs leading-5"> 选中 </span>
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="导航名称" v-if="element.name !== undefined">
|
||||
<el-input v-model="element.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="链接地址" v-if="element.link !== undefined">
|
||||
<link-picker v-model="element.link" type="pc" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示" v-if="element.is_show !== undefined">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
v-model="element.is_show"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</Draggable>
|
||||
</div>
|
||||
|
||||
<el-form-item v-if="menu.length < max" label-width="0">
|
||||
<el-button type="primary" @click="handleAdd"> 添加导航 </el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: any[]
|
||||
max?: number
|
||||
min?: number
|
||||
itemData: any
|
||||
}>(),
|
||||
{
|
||||
max: 9999,
|
||||
min: -1,
|
||||
itemData: () => ({
|
||||
name: '',
|
||||
selected: '',
|
||||
unselected: '',
|
||||
is_show: '1',
|
||||
link: {}
|
||||
})
|
||||
}
|
||||
)
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: any): void
|
||||
}>()
|
||||
const menu = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (menu.value?.length < props.max) {
|
||||
menu.value.push(cloneDeep(props.itemData))
|
||||
} else {
|
||||
feedback.msgError(`最多添加${props.max}个`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (menu.value.length <= props.min && props.min > -1) {
|
||||
return feedback.msgError(`最少保留${props.min}个`)
|
||||
}
|
||||
menu.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base dark:text-[#ffffff] text-[#101010] font-medium">展示样式</div>
|
||||
</div>
|
||||
<el-radio-group v-model="contentData.style">
|
||||
<el-radio :value="1">常规</el-radio>
|
||||
<el-radio :value="2">大屏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex mt-2" v-if="content.style == 1">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] dark:text-[#ffffff] font-medium">背景联动</div>
|
||||
</div>
|
||||
<el-radio-group v-model="contentData.bg_style">
|
||||
<el-radio :value="1">开启</el-radio>
|
||||
<el-radio :value="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="p-[15px] rounded-[8px] bg-[#f3f8ff] text-[#136bdf] mt-2">
|
||||
开启背景联动后,需为轮播图设置背景图,轮播图切换时,背景图也跟随切换,此时该页面自身的“页面背景“设置将失效。
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex-1 mt-2">
|
||||
<div class="flex items-end">
|
||||
<div class="text-base text-[#101010] dark:text-[#ffffff] font-medium">轮播图片</div>
|
||||
<div v-if="content.style == 1" class="text-xs text-tx-secondary ml-2">
|
||||
最多添加5张,建议图片尺寸:750px*340px
|
||||
</div>
|
||||
<div v-else class="text-xs text-tx-secondary ml-2">
|
||||
最多添加5张,建议图片尺寸:750px*1100px
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<draggable
|
||||
class="draggable"
|
||||
v-model="contentData.data"
|
||||
animation="300"
|
||||
handle=".drag-move"
|
||||
item-key="index"
|
||||
>
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap :key="index" @close="handleDelete(index)" class="w-full">
|
||||
<div class="bg-fill-light w-full p-4 mt-4">
|
||||
<div class="flex justify-center w-[467px]">
|
||||
<template v-if="content.style == 1">
|
||||
<material-picker
|
||||
size="122px"
|
||||
v-model="item.image"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
>
|
||||
<template #upload>
|
||||
<div class="w-[122px] h-[122px] banner-upload-btn">
|
||||
轮播图
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<material-picker
|
||||
v-if="content.style == 1 || content.bg_style == 1"
|
||||
class="ml-[40px]"
|
||||
size="122px"
|
||||
v-model="item.bg"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
>
|
||||
<template #upload>
|
||||
<div class="w-[122px] h-[122px] banner-upload-btn">
|
||||
背景图
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
</template>
|
||||
<template v-else>
|
||||
<material-picker
|
||||
width="396px"
|
||||
height="196px"
|
||||
v-model="item.image"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<el-form-item class="mt-[18px]" label="图片链接">
|
||||
<link-picker v-if="type == 'mobile'" v-model="item.link" />
|
||||
<el-input
|
||||
v-if="type == 'pc'"
|
||||
placeholder="请输入链接"
|
||||
v-model="item.link.path"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示" class="mt-[18px] !mb-0">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
v-model="item.is_show"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="mt-4" v-if="content.data?.length < limit">
|
||||
<el-button class="w-full" type="primary" @click="handleAdd">添加图片</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { PropType } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const limit = 5
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'mobile' | 'pc'>,
|
||||
default: 'mobile'
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.push({
|
||||
is_show: '1',
|
||||
image: '',
|
||||
bg: '',
|
||||
name: '',
|
||||
link: {}
|
||||
})
|
||||
emits('update:content', content)
|
||||
} else {
|
||||
feedback.msgError(`最多添加${limit}张图片`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return feedback.msgError('最少保留一张图片')
|
||||
}
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.splice(index, 1)
|
||||
emits('update:content', content)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner-upload-btn {
|
||||
@apply text-tx-secondary box-border rounded border-br border-dashed border flex flex-col justify-center items-center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="banner" :style="styles">
|
||||
<div class="banner-image w-full h-full">
|
||||
<decoration-img
|
||||
width="100%"
|
||||
:height="content.style == 1 ? height : '550px'"
|
||||
:src="getImage"
|
||||
fit="contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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: () => ({})
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '170px'
|
||||
}
|
||||
})
|
||||
|
||||
const showList = computed(() => {
|
||||
return props.content.data?.filter((item: any) => item.is_show == '1') || []
|
||||
})
|
||||
|
||||
const getImage = computed(() => {
|
||||
if (Array.isArray(showList.value)) {
|
||||
return showList.value[0] ? showList.value[0].image : ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default () => ({
|
||||
title: '首页轮播图',
|
||||
name: 'banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
style: 1, // 展示样式---1=常规,2=大屏
|
||||
bg_style: 0, // 背景联动---0=关闭,1=开启
|
||||
data: [
|
||||
{
|
||||
is_show: '1',
|
||||
image: '',
|
||||
bg: '',
|
||||
name: '',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="90px" size="large" label-position="top">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<el-form-item label="平台名称">
|
||||
<el-input
|
||||
class="w-[400px]"
|
||||
show-word-limit
|
||||
maxlength="20"
|
||||
v-model="contentData.title"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="客服二维码">
|
||||
<div>
|
||||
<material-picker v-model="contentData.qrcode" exclude-domain />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
class="w-[400px]"
|
||||
show-word-limit
|
||||
maxlength="20"
|
||||
v-model="contentData.remark"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input class="w-[400px]" v-model="contentData.mobile" />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务时间">
|
||||
<el-input
|
||||
class="w-[400px]"
|
||||
show-word-limit
|
||||
maxlength="20"
|
||||
v-model="contentData.time"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<view class="bg-white p-[15px] flex text-[#101010] font-medium text-lg"> 联系我们 </view>
|
||||
<view
|
||||
class="customer-service bg-white flex flex-col justify-center items-center mx-[18px] mt-[15px] rounded-[10px] px-[10px] pb-[50px]"
|
||||
>
|
||||
<view
|
||||
class="w-full border-solid border-0 border-b border-[#f5f5f5] p-[15px] text-center text-[#101010] text-base font-medium"
|
||||
>
|
||||
{{ content.title }}
|
||||
</view>
|
||||
|
||||
<view class="mt-[30px]">
|
||||
<decoration-img width="100px" height="100px" :src="content.qrcode" alt="" />
|
||||
</view>
|
||||
<view v-if="content.remark" class="text-sm mt-[20px] font-medium">{{
|
||||
content.remark
|
||||
}}</view>
|
||||
<view v-if="content.mobile" class="text-sm mt-[12px] flex flex-wrap">
|
||||
<a class="ml-[5px] phone text-primary underline" :href="'tel:' + content.mobile">
|
||||
{{ content.mobile }}
|
||||
</a>
|
||||
</view>
|
||||
<view v-if="content.time" class="text-muted text-sm mt-[15px]">
|
||||
服务时间:{{ content.time }}
|
||||
</view>
|
||||
</view>
|
||||
Î
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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>
|
||||
.customer-service {
|
||||
background: #fff;
|
||||
@apply flex flex-col justify-center items-center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export default () => ({
|
||||
title: '客服设置',
|
||||
name: 'customer-service',
|
||||
content: {
|
||||
title: '添加客服二维码',
|
||||
time: '',
|
||||
mobile: '',
|
||||
qrcode: '',
|
||||
remark: ''
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
const widgets: Record<string, any> = import.meta.glob('./**/index.ts', { 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')
|
||||
exportWidgets[widgetName] = widgets[key]?.default
|
||||
})
|
||||
|
||||
export default exportWidgets
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end">
|
||||
<div class="text-base text-[#101010] font-medium">图片设置</div>
|
||||
<div class="text-xs text-tx-secondary ml-2">
|
||||
最多添加5张,建议图片尺寸:750px*200px
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<draggable
|
||||
class="draggable"
|
||||
v-model="contentData.data"
|
||||
animation="300"
|
||||
handle=".drag-move"
|
||||
>
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap :key="index" @close="handleDelete(index)" class="w-full">
|
||||
<div class="bg-fill-light w-full p-4 mt-4">
|
||||
<material-picker
|
||||
width="396px"
|
||||
height="196px"
|
||||
v-model="item.image"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<el-form-item class="mt-[18px]" label="图片链接">
|
||||
<link-picker
|
||||
v-if="type == 'mobile'"
|
||||
v-model="item.link"
|
||||
/>
|
||||
<el-input
|
||||
v-if="type == 'pc'"
|
||||
placeholder="请输入链接"
|
||||
v-model="item.link.path"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示" class="mt-[18px] !mb-0">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
v-model="item.is_show"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="mt-4" v-if="content.data?.length < limit">
|
||||
<el-button class="w-full" type="primary" @click="handleAdd">添加图片</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { PropType } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
const limit = 5
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'mobile' | 'pc'>,
|
||||
default: 'mobile'
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.push({
|
||||
is_show: '1',
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
})
|
||||
emits('update:content', content)
|
||||
} else {
|
||||
feedback.msgError(`最多添加${limit}张图片`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return feedback.msgError('最少保留一张图片')
|
||||
}
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.splice(index, 1)
|
||||
emits('update:content', content)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="banner" :style="styles">
|
||||
<div class="banner-image w-full h-full">
|
||||
<decoration-img
|
||||
width="100%"
|
||||
:height="styles.height || height"
|
||||
:src="getImage"
|
||||
fit="contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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: () => ({})
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '96px'
|
||||
}
|
||||
})
|
||||
|
||||
const showList = computed(() => {
|
||||
return props.content.data?.filter((item: any) => item.is_show == '1') || []
|
||||
})
|
||||
|
||||
const getImage = computed(() => {
|
||||
if (Array.isArray(showList.value)) {
|
||||
return showList.value[0] ? showList.value[0].image : ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default () => ({
|
||||
title: '首页中部轮播图',
|
||||
name: 'middle-banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
is_show: '1',
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<el-form-item label="标题">
|
||||
<el-input class="w-[396px]" v-model="contentData.title" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">展示样式</div>
|
||||
</div>
|
||||
<el-radio-group v-model="contentData.style">
|
||||
<el-radio :value="1">横排</el-radio>
|
||||
<el-radio :value="2">竖排</el-radio>
|
||||
</el-radio-group>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">菜单</div>
|
||||
<div class="text-xs text-tx-secondary ml-2">建议图片尺寸:100px*100px</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<AddNav v-model="contentData.data" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import AddNav from '../../add-nav.vue'
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="my-service">
|
||||
<div v-if="content.title" class="title px-[15px] py-[10px]">
|
||||
<div>{{ content.title }}</div>
|
||||
</div>
|
||||
<div v-if="content.style == 1" class="flex flex-wrap pt-[20px] pb-[10px]">
|
||||
<div
|
||||
v-for="(item, index) in showList"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/4 mb-[15px]"
|
||||
>
|
||||
<decoration-img width="26px" height="26px" :src="item.image" alt="" />
|
||||
<div class="mt-[7px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="content.style == 2">
|
||||
<div
|
||||
v-for="(item, index) in showList"
|
||||
:key="index"
|
||||
class="flex items-center border-b border-[#e5e5e5] h-[50px] px-[12px]"
|
||||
>
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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 showList = computed(() => {
|
||||
return props.content.data?.filter((item: any) => item.is_show == '1') || []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-service {
|
||||
margin: 10px 10px 0;
|
||||
background-color: #fff;
|
||||
border-radius: 7px;
|
||||
.title {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default () => ({
|
||||
title: '我的服务',
|
||||
name: 'my-service',
|
||||
content: {
|
||||
style: 1,
|
||||
title: '我的服务',
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '导航名称',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">展示样式</div>
|
||||
</div>
|
||||
<el-radio-group v-model="contentData.style">
|
||||
<el-radio :value="1">固定显示</el-radio>
|
||||
<el-radio :value="2">分页滑动</el-radio>
|
||||
</el-radio-group>
|
||||
<el-form-item label="每行数量" class="mt-4">
|
||||
<el-select v-model="contentData.per_line" style="width: 300px">
|
||||
<el-option
|
||||
v-for="item in 5"
|
||||
:key="item"
|
||||
:label="item + '个'"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示行数">
|
||||
<el-select v-model="contentData.show_line" style="width: 300px">
|
||||
<el-option
|
||||
v-for="item in 2"
|
||||
:key="item"
|
||||
:label="item + '行'"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end">
|
||||
<div class="text-base text-[#101010] font-medium">菜单设置</div>
|
||||
<div class="text-xs text-tx-secondary ml-2">建议图片尺寸:100px*100px</div>
|
||||
</div>
|
||||
<div class="flex-1 mt-4">
|
||||
<AddNav v-model="contentData.data" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import AddNav from '../../add-nav.vue'
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="nav bg-white pt-[15px] pb-[8px]">
|
||||
<div
|
||||
class="grid grid-rows-auto gap-y-3 w-full"
|
||||
:style="{ 'grid-template-columns': `repeat(${content.per_line}, 1fr)` }"
|
||||
>
|
||||
<div v-for="(item, index) in showList" :key="index" class="flex flex-col items-center">
|
||||
<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 type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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 showList = computed(() => {
|
||||
const data = props.content.data?.filter((item: any) => item.is_show == '1') || []
|
||||
return data.slice(0, props.content.show_line * props.content.per_line)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default () => ({
|
||||
title: '导航菜单',
|
||||
name: 'nav',
|
||||
content: {
|
||||
enabled: 1,
|
||||
style: 1, // 展示样式1=固定显示,2=分页显示
|
||||
per_line: 5, // 每行显示数量
|
||||
show_line: 2, // 显示行数
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '导航名称',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
@@ -0,0 +1,73 @@
|
||||
<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] object-contain" />
|
||||
</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.desc }}
|
||||
</div>
|
||||
|
||||
<div class="text-[#999] text-xs w-full flex justify-between mt-[8px]">
|
||||
<div>{{ item.create_time }}</div>
|
||||
<div class="flex items-center">
|
||||
<icon name="el-icon-View" />
|
||||
<div class="ml-[5px]">{{ item.click }}</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>
|
||||
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>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default () => ({
|
||||
title: '资讯',
|
||||
name: 'news',
|
||||
disabled: 1,
|
||||
content: {},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<el-form ref="form" label-width="80px" size="large">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<el-form-item label="页面标题">
|
||||
<el-radio-group v-model="contentData.title_type">
|
||||
<el-radio value="1">文字</el-radio>
|
||||
<el-radio value="2">图片</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.title_type == 1">
|
||||
<el-input
|
||||
v-model="contentData.title"
|
||||
maxlength="8"
|
||||
show-word-limit
|
||||
class="w-[300px]"
|
||||
placeholder="请输入页面标题"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.title_type == 2">
|
||||
<material-picker v-model="contentData.title_img" :limit="1" size="100px" />
|
||||
<div class="form-tips">建议图片尺寸:300px*40px</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="文字颜色" v-if="content.title_type == 1">
|
||||
<el-radio-group v-model="contentData.text_color">
|
||||
<el-radio value="1">白色</el-radio>
|
||||
<el-radio value="2">黑色</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="页面背景">
|
||||
<el-radio-group v-model="contentData.bg_type">
|
||||
<el-radio value="1">背景颜色</el-radio>
|
||||
<el-radio value="2">背景图片</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.bg_type == 1">
|
||||
<color-picker v-model="contentData.bg_color" reset-color="#F5F5F5" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.bg_type == 2">
|
||||
<material-picker v-model="contentData.bg_image" :limit="1" size="100px" />
|
||||
<div class="form-tips">建议图片尺寸:750px*高度不限</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div class="page-mate"></div>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export default () => ({
|
||||
title: '页面设置',
|
||||
name: 'page-meta',
|
||||
content: {
|
||||
title_type: 1,
|
||||
title: '',
|
||||
title_img: '',
|
||||
bg_type: 1,
|
||||
bg_color: '',
|
||||
bg_image: '',
|
||||
text_color: ''
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<popup ref="popupRef" title="轮播图设置" :async="true" width="980px" @confirm="handleSubmit">
|
||||
<el-alert title="最多可添加10张,建议图片尺寸750px*440px" type="warning" />
|
||||
|
||||
<el-scrollbar height="400px" class="mt-4">
|
||||
<div class="flex flex-wrap p-4">
|
||||
<div v-for="(item, index) in content.data" :key="index" class="w-[400px] mr-4 mb-4">
|
||||
<del-wrap :key="index" @close="handleDelete(index)" class="w-full">
|
||||
<div class="bg-fill-light w-full p-4">
|
||||
<div class="flex items-center">
|
||||
<material-picker
|
||||
width="122px"
|
||||
height="122px"
|
||||
v-model="item.image"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
>
|
||||
<template #upload>
|
||||
<div
|
||||
class="w-[122px] h-[122px] flex justify-center items-center"
|
||||
>
|
||||
轮播图
|
||||
</div>
|
||||
</template>
|
||||
</material-picker>
|
||||
<link-picker v-model="item.link" />
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 ml-4">
|
||||
<el-button link type="primary" @click="handleAdd">+ 添加轮播图</el-button>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</popup>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '170px'
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
popupRef.value?.open()
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
popupRef.value?.close()
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < 10) {
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.push({
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
})
|
||||
emits('update:content', content)
|
||||
} else {
|
||||
feedback.msgError(`最多添加${10}张图片`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return feedback.msgError('最少保留一个轮播图')
|
||||
}
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.splice(index, 1)
|
||||
emits('update:content', content)
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export default () => ({
|
||||
title: '首页轮播图',
|
||||
name: 'pc-banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
@@ -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,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default () => ({
|
||||
title: '搜索',
|
||||
name: 'search',
|
||||
disabled: 1,
|
||||
content: {},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="70px">
|
||||
<el-card shadow="never" class="!border-none flex mt-2">
|
||||
<div class="flex items-end mb-4">
|
||||
<div class="text-base text-[#101010] font-medium">菜单</div>
|
||||
<div class="text-xs text-tx-secondary ml-2">
|
||||
最多添加5张,建议图片尺寸:750px*200px
|
||||
</div>
|
||||
</div>
|
||||
<draggable
|
||||
class="draggable"
|
||||
v-model="contentData.data"
|
||||
animation="300"
|
||||
handle=".drag-move"
|
||||
item-key="index"
|
||||
>
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap :key="index" @close="handleDelete(index)" class="w-[467px]">
|
||||
<div class="bg-fill-light flex items-center w-full p-4 mt-4">
|
||||
<material-picker
|
||||
v-model="item.image"
|
||||
upload-class="bg-body"
|
||||
exclude-domain
|
||||
/>
|
||||
<div class="ml-3 flex-1">
|
||||
<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>
|
||||
<el-form-item label="是否显示" class="mt-[18px]">
|
||||
<div class="flex-1 flex items-center">
|
||||
<el-switch
|
||||
v-model="item.is_show"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
<div class="drag-move cursor-move ml-auto">
|
||||
<icon name="el-icon-Rank" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
<div class="mt-4" v-if="content.data?.length < limit">
|
||||
<el-button class="w-full" type="primary" @click="handleAdd">添加图片</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { PropType } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import type options from './options'
|
||||
|
||||
type OptionsType = ReturnType<typeof options>
|
||||
const emits = defineEmits<(event: 'update:content', data: OptionsType['content']) => void>()
|
||||
|
||||
const limit = 5
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const contentData = computed({
|
||||
get: () => props.content,
|
||||
set: (newValue) => {
|
||||
emits('update:content', newValue)
|
||||
}
|
||||
})
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.push({
|
||||
is_show: '1',
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
})
|
||||
emits('update:content', content)
|
||||
} else {
|
||||
feedback.msgError(`最多添加${limit}张图片`)
|
||||
}
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return feedback.msgError('最少保留一张图片')
|
||||
}
|
||||
const content = cloneDeep(props.content)
|
||||
content.data.splice(index, 1)
|
||||
emits('update:content', content)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="banner mx-[10px] mt-[10px]">
|
||||
<div class="banner-image">
|
||||
<decoration-img width="100%" height="100px" :src="getImage" fit="contain" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import DecorationImg from '../../decoration-img.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 showList = computed(() => {
|
||||
return props.content.data?.filter((item: any) => item.is_show == '1') || []
|
||||
})
|
||||
const getImage = computed(() => {
|
||||
if (Array.isArray(showList.value)) {
|
||||
return showList.value[0] ? showList.value[0].image : ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export default () => ({
|
||||
title: '个人中心广告图',
|
||||
name: 'user-banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
is_show: '1',
|
||||
image: '',
|
||||
name: '',
|
||||
link: {}
|
||||
}
|
||||
]
|
||||
},
|
||||
styles: {}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
<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>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="user-info flex items-center px-[25px]">
|
||||
<img src="./images/default_avatar.png" class="w-[60px] h-[60px]" alt="" />
|
||||
<div class="text-white text-[18px] ml-[10px]">未登录</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-info {
|
||||
background: url(./images/my_topbg.png);
|
||||
height: 115px;
|
||||
background-position: bottom;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
@@ -0,0 +1,9 @@
|
||||
import attr from './attr.vue'
|
||||
import content from './content.vue'
|
||||
import options from './options'
|
||||
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default () => ({
|
||||
title: '用户信息',
|
||||
name: 'user-info',
|
||||
disabled: 1,
|
||||
content: {},
|
||||
styles: {}
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 516 KiB |
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="decoration-pages min-w-[1100px]">
|
||||
<div class="flex flex-1 h-full justify-between">
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="!border-none flex scroll-view-content"
|
||||
:body-style="{ 'padding-right': 0 }"
|
||||
>
|
||||
<Menu v-model="activeMenu" :menus="menus" />
|
||||
</el-card>
|
||||
|
||||
<preview
|
||||
class="flex-1 scroll-view-content"
|
||||
v-model="selectWidgetIndex"
|
||||
@updatePageData="updatePageData"
|
||||
:pageData="getPageData"
|
||||
:pageMeta="getPageMeta"
|
||||
/>
|
||||
|
||||
<attr-setting
|
||||
class="w-[560px] scroll-view-content"
|
||||
:widget="getSelectWidget"
|
||||
@update:content="updateContent"
|
||||
/>
|
||||
</div>
|
||||
<footer-btns class="mt-4" :fixed="false" v-perms="['decorate:pages:save']">
|
||||
<el-button type="primary" @click="setData">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="decorationPages">
|
||||
import { getDecoratePages, setDecoratePages } from '@/api/decoration'
|
||||
import { getNonDuplicateID } from '@/utils/util'
|
||||
|
||||
import AttrSetting from '../component/pages/attr-setting.vue'
|
||||
import Menu from '../component/pages/menu.vue'
|
||||
import Preview from '../component/pages/preview.vue'
|
||||
import widgets from '../component/widgets'
|
||||
|
||||
enum pagesTypeEnum {
|
||||
HOME = '1',
|
||||
USER = '2',
|
||||
SERVICE = '3'
|
||||
}
|
||||
|
||||
const updatePageData = (value: any) => {
|
||||
menus[activeMenu.value].pageData = [...value]
|
||||
}
|
||||
|
||||
const generatePageData = (widgetNames: string[]) => {
|
||||
return widgetNames.map((widgetName) => {
|
||||
const options = {
|
||||
id: getNonDuplicateID(),
|
||||
...(widgets[widgetName]?.options() || {})
|
||||
}
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
const updateContent = (content: any) => {
|
||||
if (menus[activeMenu.value]?.pageData) {
|
||||
menus[activeMenu.value].pageData[selectWidgetIndex.value].content = content
|
||||
}
|
||||
}
|
||||
|
||||
const menus: Record<
|
||||
string,
|
||||
{
|
||||
id: number
|
||||
name: string
|
||||
pageMeta?: any
|
||||
pageData: any[]
|
||||
}
|
||||
> = reactive({
|
||||
[pagesTypeEnum.HOME]: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
name: '首页装修',
|
||||
pageMeta: generatePageData(['page-meta']),
|
||||
pageData: generatePageData(['search', 'banner', 'nav', 'news'])
|
||||
},
|
||||
[pagesTypeEnum.USER]: {
|
||||
id: 2,
|
||||
type: 2,
|
||||
name: '个人中心',
|
||||
pageMeta: generatePageData(['page-meta']),
|
||||
pageData: generatePageData(['user-info', 'my-service', 'user-banner'])
|
||||
},
|
||||
[pagesTypeEnum.SERVICE]: {
|
||||
id: 3,
|
||||
type: 3,
|
||||
name: '客服设置',
|
||||
pageMeta: null,
|
||||
pageData: generatePageData(['customer-service'])
|
||||
}
|
||||
})
|
||||
|
||||
const activeMenu = ref<string>('1')
|
||||
const selectWidgetIndex = ref<number>(-1)
|
||||
const getPageData = computed(() => {
|
||||
return menus[activeMenu.value]?.pageData ?? []
|
||||
})
|
||||
const getPageMeta = computed(() => {
|
||||
return menus[activeMenu.value]?.pageMeta ?? null
|
||||
})
|
||||
const getSelectWidget = computed(() => {
|
||||
if (selectWidgetIndex.value === -1) {
|
||||
return menus[activeMenu.value]?.pageMeta[0] ?? ''
|
||||
} else {
|
||||
return menus[activeMenu.value]?.pageData[selectWidgetIndex.value] ?? ''
|
||||
}
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
const data = await getDecoratePages({ id: activeMenu.value })
|
||||
menus[String(data.id)].pageData = JSON.parse(data.data)
|
||||
menus[String(data.id)].pageMeta = data?.meta ? JSON.parse(data?.meta) : null
|
||||
}
|
||||
|
||||
const setData = async () => {
|
||||
const data = menus[activeMenu.value]
|
||||
await setDecoratePages({
|
||||
...data,
|
||||
data: JSON.stringify(data.pageData),
|
||||
meta: data?.pageMeta ? JSON.stringify(data?.pageMeta) : null
|
||||
})
|
||||
getData()
|
||||
}
|
||||
watch(
|
||||
activeMenu,
|
||||
() => {
|
||||
selectWidgetIndex.value = getPageData.value.findIndex((item) => !item.disabled)
|
||||
getData()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$scroll-height: calc(100vh - var(--navbar-height) - 74px);
|
||||
.decoration-pages {
|
||||
height: $scroll-height;
|
||||
@apply flex flex-col;
|
||||
.scroll-view-content {
|
||||
height: calc($scroll-height - 60px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="decoration-pc min-w-[1100px]">
|
||||
<el-card shadow="never" class="!border-none flex-1 flex">
|
||||
<div class="text-xl font-medium">首页装修</div>
|
||||
<router-link
|
||||
:to="{
|
||||
path: '/decoration/pc_details',
|
||||
query: {
|
||||
url: state.pc_url
|
||||
}
|
||||
}"
|
||||
>
|
||||
<el-button class="m-5" type="primary" size="large">去装修</el-button>
|
||||
</router-link>
|
||||
<el-form>
|
||||
<el-form-item label="最近更新">{{ state.update_time }}</el-form-item>
|
||||
<el-form-item label="PC端链接">
|
||||
<el-input style="width: 350px" v-model="state.pc_url" disabled></el-input>
|
||||
<el-button type="primary" v-copy="state.pc_url">复制</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="decorationPc">
|
||||
import { getDecoratePc } from '@/api/decoration'
|
||||
|
||||
const state = ref({
|
||||
update_time: '',
|
||||
pc_url: ''
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
state.value = await getDecoratePc()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
getData()
|
||||
</script>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="decoration-pages min-w-[1100px]">
|
||||
<el-card shadow="never" class="!border-none !rounded-none !bg-primary">
|
||||
<div class="flex justify-between w-full">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:icon="ArrowLeft"
|
||||
style="color: #fff"
|
||||
@click="handleBack"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
<el-button v-perms="['decorate:pages:save']" @click="setData"> 保存 </el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="flex-1 h-full">
|
||||
<preview-pc v-model="selectWidgetIndex" :pageData="getPageData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="decorationPc">
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
|
||||
import { getDecoratePages, setDecoratePages } from '@/api/decoration'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import PreviewPc from './component/pages/preview-pc.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
enum pagesTypeEnum {
|
||||
HOME = '4'
|
||||
}
|
||||
|
||||
const menus: Record<
|
||||
string,
|
||||
{
|
||||
id: number
|
||||
name: string
|
||||
pageData: any[]
|
||||
}
|
||||
> = reactive({
|
||||
[pagesTypeEnum.HOME]: {
|
||||
id: 4,
|
||||
type: 4,
|
||||
name: '首页装修',
|
||||
pageData: []
|
||||
}
|
||||
})
|
||||
|
||||
const activeMenu = ref('4')
|
||||
const selectWidgetIndex = ref(0)
|
||||
const getPageData = computed(() => {
|
||||
return menus[activeMenu.value]?.pageData ?? []
|
||||
})
|
||||
|
||||
const handleBack = async () => {
|
||||
await feedback.confirm('确定离开此页面?系统可能不会保存您所做的更改。')
|
||||
router.back()
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = await getDecoratePages({ id: activeMenu.value })
|
||||
menus[String(data.id)].pageData = JSON.parse(data.data)
|
||||
selectWidgetIndex.value = getPageData.value.findIndex((item) => !item.disabled)
|
||||
}
|
||||
|
||||
const setData = async () => {
|
||||
await setDecoratePages({
|
||||
...menus[activeMenu.value],
|
||||
data: JSON.stringify(menus[activeMenu.value].pageData)
|
||||
})
|
||||
getData()
|
||||
}
|
||||
watch(
|
||||
activeMenu,
|
||||
() => {
|
||||
selectWidgetIndex.value = getPageData.value.findIndex((item) => !item.disabled)
|
||||
getData()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.decoration-pages {
|
||||
min-height: calc(100vh);
|
||||
@apply flex flex-col;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<el-form label-width="140px">
|
||||
<div>
|
||||
<div class="text-xl font-medium mb-[20px]">系统主题色</div>
|
||||
<el-form-item label-width="50">
|
||||
<ThemePicker
|
||||
v-model="formData.themeColorId"
|
||||
:theme-colors="themeColors"
|
||||
@change="selectThemeColor"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xl font-medium mt-[40px] mb-[20px]">样式设置</div>
|
||||
|
||||
<el-form-item label="导航顶部文字颜色">
|
||||
<div>
|
||||
<el-radio-group v-model="formData.topTextColor">
|
||||
<el-radio value="white" size="large">白色</el-radio>
|
||||
<el-radio value="black" size="large">黑色</el-radio>
|
||||
</el-radio-group>
|
||||
<div>
|
||||
<span class="form-tips">页面导航栏文字的颜色</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="导航顶部背景颜色">
|
||||
<div>
|
||||
<color-picker
|
||||
:resetColor="formData.themeColor1"
|
||||
v-model="formData.navigationBarColor"
|
||||
></color-picker>
|
||||
<div>
|
||||
<span class="form-tips"> 页面顶部导航栏背景颜色,不设置则跟随主题色 </span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义主题颜色" v-if="formData.themeColorId == 7">
|
||||
<div>
|
||||
<color-picker
|
||||
v-model="formData.themeColor1"
|
||||
default-color="#F8F8F8"
|
||||
></color-picker>
|
||||
<color-picker
|
||||
v-model="formData.themeColor2"
|
||||
default-color="#F8F8F8"
|
||||
class="mt-2"
|
||||
></color-picker>
|
||||
<div>
|
||||
<span class="form-tips">自定义主题渐变色,用于按钮类和主要文字</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮文字颜色">
|
||||
<div>
|
||||
<el-radio-group v-model="formData.buttonColor">
|
||||
<el-radio value="white" size="large">白色</el-radio>
|
||||
<el-radio value="black" size="large">黑色</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex" v-if="formData.themeColorId == 1">
|
||||
<img class="w-[200px]" src="@/assets/images/blue1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/blue2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/blue3.png" />
|
||||
</div>
|
||||
<div class="flex" v-if="formData.themeColorId == 2">
|
||||
<img class="w-[200px]" src="@/assets/images/green1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/green2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/green3.png" />
|
||||
</div>
|
||||
<div class="flex" v-if="formData.themeColorId == 3">
|
||||
<img class="w-[200px]" src="@/assets/images/purple1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/purple2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/purple3.png" />
|
||||
</div>
|
||||
<div class="flex" v-if="formData.themeColorId == 4">
|
||||
<img class="w-[200px]" src="@/assets/images/yellow1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/yellow2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/yellow3.png" />
|
||||
</div>
|
||||
<div class="flex" v-if="formData.themeColorId == 5">
|
||||
<img class="w-[200px]" src="@/assets/images/red1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/red2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/red3.png" />
|
||||
</div>
|
||||
<div class="flex" v-if="formData.themeColorId == 6">
|
||||
<img class="w-[200px]" src="@/assets/images/pink1.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/pink2.png" />
|
||||
<img class="w-[200px] ml-[30px]" src="@/assets/images/pink3.png" />
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
import ThemePicker from './theme-picker.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
}>()
|
||||
const themeColors = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '科技蓝',
|
||||
color1: '#2F80ED',
|
||||
color2: '#56CCF2',
|
||||
btnColor: 'white'
|
||||
},
|
||||
{ id: 2, name: '偏绿蓝', color1: '#2EC840', color2: '#3DE650', btnColor: 'white' },
|
||||
{ id: 3, name: '商务紫', color1: '#A74BFD', color2: '#CB60FF', btnColor: 'white' },
|
||||
{ id: 4, name: '活力橙', color1: '#F7971E', color2: '#FFD200', btnColor: 'black' },
|
||||
{ id: 5, name: '经典红', color1: '#FF2C3C', color2: '#EF1D2D', btnColor: 'white' },
|
||||
{ id: 6, name: '美妆色', color1: '#FD498F', color2: '#FA444D', btnColor: 'white' },
|
||||
{ id: 7, name: '自定义', color1: '#F8F8F8', color2: '#F5F5F5', btnColor: 'white' }
|
||||
])
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Record<string, any>): void
|
||||
}>()
|
||||
|
||||
//选择主题颜色
|
||||
const selectThemeColor = (item: any) => {
|
||||
if (item.id != 7) {
|
||||
formData.value.themeColor1 = item.color1
|
||||
formData.value.themeColor2 = item.color2
|
||||
formData.value.navigationBarColor = item.color1
|
||||
formData.value.buttonColor = item.btnColor
|
||||
formData.value.topTextColor = item.btnColor
|
||||
}
|
||||
}
|
||||
//表单数据
|
||||
const formData = useVModel(props, 'modelValue', emit)
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<div
|
||||
class="py-[18px] px-[20px] flex items-center text-[14px] shadow rounded-lg mr-[20px] mb-[20px] cursor-pointer"
|
||||
:class="{ 'is-select': selectId == item.id, unselect: selectId != item.id }"
|
||||
v-for="(item, index) in themeColors"
|
||||
:key="index"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<div
|
||||
class="rounded-full h-[34px] w-[34px]"
|
||||
:style="`background: linear-gradient(to right, ${item.color1}, ${item.color2})`"
|
||||
></div>
|
||||
<div class="ml-[14px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
|
||||
interface IThemeColors {
|
||||
id: number
|
||||
name: string
|
||||
color1: string
|
||||
color2: string
|
||||
btnColor: string
|
||||
}
|
||||
const props = defineProps<{
|
||||
themeColors: IThemeColors[]
|
||||
modelValue: number | string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', id: number | string): void
|
||||
(event: 'change', item: IThemeColors): void
|
||||
}>()
|
||||
//主题色
|
||||
|
||||
const selectId = useVModel(props, 'modelValue', emit)
|
||||
const handleSelect = (item: IThemeColors) => {
|
||||
selectId.value = item.id
|
||||
emit('change', item)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.is-select {
|
||||
border: 1px solid #4153ff;
|
||||
}
|
||||
|
||||
.unselect {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-card shadow="never" class="!border-none">
|
||||
<div class="mt-[-10px]">
|
||||
<el-tabs v-model="currentTabIndex">
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in tabsList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:name="index"
|
||||
>
|
||||
<component :is="item.component" v-model="item.data" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-card>
|
||||
<footer-btns class="mt-4" :fixed="true">
|
||||
<el-button type="primary" @click="setData">保存</el-button>
|
||||
</footer-btns>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { markRaw } from 'vue'
|
||||
|
||||
import { getDecoratePages, setDecoratePages } from '@/api/decoration'
|
||||
|
||||
import MobileStyle from './components/mobile-style.vue'
|
||||
|
||||
const currentTabIndex = ref(0)
|
||||
const tabsList = ref([
|
||||
{
|
||||
name: '移动端',
|
||||
id: 5,
|
||||
component: markRaw(MobileStyle),
|
||||
data: {
|
||||
themeColorId: 1,
|
||||
topTextColor: 'white',
|
||||
navigationBarColor: '',
|
||||
themeColor1: '',
|
||||
themeColor2: '',
|
||||
buttonColor: 'white'
|
||||
}
|
||||
}
|
||||
])
|
||||
const currentTab = computed(() => tabsList.value[currentTabIndex.value] || {})
|
||||
//获取数据
|
||||
const getData = async () => {
|
||||
const res = await getDecoratePages({ id: currentTab.value.id })
|
||||
currentTab.value.data = JSON.parse(res.data)
|
||||
}
|
||||
|
||||
//保存数据
|
||||
const setData = async () => {
|
||||
await setDecoratePages({
|
||||
id: currentTab.value.id,
|
||||
type: currentTab.value.id,
|
||||
data: JSON.stringify(currentTab.value.data)
|
||||
})
|
||||
getData()
|
||||
}
|
||||
|
||||
//初始化数据
|
||||
onMounted(async () => {
|
||||
await getData()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="decoration-tabbar">
|
||||
<div class="flex-1 min-h-0 w-full">
|
||||
<div class="h-full">
|
||||
<ElScrollbar>
|
||||
<div class="flex min-w-[900px] scroll-view-content">
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="!border-none flex"
|
||||
:body-style="{ 'padding-right': 0 }"
|
||||
>
|
||||
<Menu class="!h-full flex-none" v-model="activeMenu" :menus="menus" />
|
||||
</el-card>
|
||||
<div class="flex-1 pages-preview-box">
|
||||
<div class="pages-preview mx-[30px]">
|
||||
<el-scrollbar height="100%">
|
||||
<component
|
||||
:is="currentTab.component?.content"
|
||||
v-bind="currentTab.data"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar class="attr-setting flex-none">
|
||||
<div class="px-5 pb-5">
|
||||
<component
|
||||
:is="currentTab.component?.attr"
|
||||
v-model="currentTab.data"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer-btns :fixed="true" v-perms="['decorate:tabbar:save']">
|
||||
<el-button type="primary" @click="setData">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="decorationTabbar">
|
||||
import { markRaw } from 'vue'
|
||||
|
||||
import { getDecorateTabbar, setDecorateTabbar } from '@/api/decoration'
|
||||
|
||||
import Menu from './component/pages/menu.vue'
|
||||
import mobileTab from './component/tabbar/mobile'
|
||||
// import pcTab from './component/tabbar/pc'
|
||||
|
||||
enum ClientTypeEnum {
|
||||
MOBILE = '1',
|
||||
PC = '2'
|
||||
}
|
||||
|
||||
const activeMenu = ref(ClientTypeEnum.MOBILE)
|
||||
const menus: Record<
|
||||
string,
|
||||
{
|
||||
id: number
|
||||
name: string
|
||||
type: number
|
||||
data: any
|
||||
component: any
|
||||
}
|
||||
> = reactive({
|
||||
[ClientTypeEnum.MOBILE]: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
name: '移动端导航栏',
|
||||
data: {
|
||||
style: {
|
||||
default_color: '',
|
||||
selected_color: ''
|
||||
},
|
||||
list: []
|
||||
},
|
||||
component: markRaw(mobileTab)
|
||||
}
|
||||
})
|
||||
const currentTab = computed(() => {
|
||||
return menus[activeMenu.value]
|
||||
})
|
||||
const getData = async () => {
|
||||
const data = await getDecorateTabbar({
|
||||
type: activeMenu.value
|
||||
})
|
||||
menus['1'].data = data
|
||||
}
|
||||
const setData = async () => {
|
||||
const { data } = currentTab.value
|
||||
|
||||
await setDecorateTabbar({
|
||||
...data
|
||||
})
|
||||
getData()
|
||||
}
|
||||
|
||||
watch(
|
||||
activeMenu,
|
||||
() => {
|
||||
getData()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$scroll-height: calc(100vh - var(--navbar-height) - 74px);
|
||||
.decoration-tabbar {
|
||||
height: $scroll-height;
|
||||
width: 100%;
|
||||
@apply flex flex-col;
|
||||
.scroll-view-content {
|
||||
height: calc($scroll-height - 60px);
|
||||
}
|
||||
|
||||
.pages-preview-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
.pages-preview {
|
||||
background-color: #ffffff;
|
||||
width: 360px;
|
||||
height: 615px;
|
||||
color: #333;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
&:hover {
|
||||
.drag-move {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attr-setting {
|
||||
width: 450px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,623 @@
|
||||
<template>
|
||||
<div class="code-edit">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-page-header content="编辑数据表" @back="$router.back()" />
|
||||
</el-card>
|
||||
<el-card class="mt-4 !border-none" shadow="never">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
class="ls-form"
|
||||
:model="formData"
|
||||
label-width="100px"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基础信息" name="base">
|
||||
<el-form-item label="表名称" prop="table_name">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.table_name"
|
||||
placeholder="请输入表名称"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="table_comment">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.table_comment"
|
||||
placeholder="请输入表描述"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="作者">
|
||||
<div class="w-80">
|
||||
<el-input v-model="formData.author" clearable />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
class="w-full"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 4, maxRows: 4 }"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段管理" name="column">
|
||||
<el-table :data="formData.table_column">
|
||||
<el-table-column label="字段列名" prop="column_name" />
|
||||
<el-table-column label="字段描述" prop="column_comment" min-width="120">
|
||||
<template v-slot="{ row }">
|
||||
<el-input v-model="row.column_comment" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物理类型" prop="column_type" />
|
||||
<el-table-column label="必填" width="80">
|
||||
<template #header>
|
||||
<div class="header-checkbox">
|
||||
<el-checkbox
|
||||
v-model="isAllRequired"
|
||||
@change="toggleAllRequired"
|
||||
/>
|
||||
<span>必填</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row.is_required"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="插入" width="80">
|
||||
<template #header>
|
||||
<div class="header-checkbox">
|
||||
<el-checkbox
|
||||
v-model="isAllInsert"
|
||||
@change="toggleAllInsert"
|
||||
/>
|
||||
<span>插入</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row.is_insert"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" width="80">
|
||||
<template #header>
|
||||
<div class="header-checkbox">
|
||||
<el-checkbox
|
||||
v-model="isAllUpdate"
|
||||
@change="toggleAllUpdate"
|
||||
/>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row.is_update"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" width="80">
|
||||
<template #header>
|
||||
<div class="header-checkbox">
|
||||
<el-checkbox
|
||||
v-model="isAllLists"
|
||||
@change="toggleAllLists"
|
||||
/>
|
||||
<span>列表</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row.is_lists"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" width="80">
|
||||
<template #header>
|
||||
<div class="header-checkbox">
|
||||
<el-checkbox
|
||||
v-model="isAllQuery"
|
||||
@change="toggleAllQuery"
|
||||
/>
|
||||
<span>查询</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ row }">
|
||||
<el-checkbox
|
||||
v-model="row.is_query"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询方式">
|
||||
<template v-slot="{ row }">
|
||||
<el-select v-model="row.query_type">
|
||||
<el-option label="=" value="=" />
|
||||
<el-option label="!=" value="!=" />
|
||||
<el-option label=">" value=">" />
|
||||
<el-option label=">=" value=">=" />
|
||||
<el-option label="<" value="<" />
|
||||
<el-option label="<=" value="<=" />
|
||||
<el-option label="LIKE" value="like" />
|
||||
<el-option label="BETWEEN" value="between" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示类型" min-width="120">
|
||||
<template v-slot="{ row }">
|
||||
<el-select v-model="row.view_type">
|
||||
<el-option label="文本框" value="input" />
|
||||
<el-option label="文本域" value="textarea" />
|
||||
<el-option label="下拉框" value="select" />
|
||||
<el-option label="单选框" value="radio" />
|
||||
<el-option label="复选框" value="checkbox" />
|
||||
<el-option label="日期控件" value="datetime" />
|
||||
<el-option label="图片选择控件" value="imageSelect" />
|
||||
<el-option label="富文本控件" value="editor" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典类型" min-width="120">
|
||||
<template v-slot="{ row }">
|
||||
<el-select
|
||||
v-model="row.dict_type"
|
||||
clearable
|
||||
:disabled="
|
||||
!(
|
||||
row.view_type == 'select' ||
|
||||
row.view_type == 'radio' ||
|
||||
row.view_type == 'checkbox'
|
||||
)
|
||||
"
|
||||
placeholder="字典类型"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in optionsData.dict_type"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.type"
|
||||
:disabled="!item.status"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成配置" name="config">
|
||||
<el-form-item label="模板类型" prop="template_type">
|
||||
<el-radio-group v-model="formData.template_type">
|
||||
<el-radio :value="0">单表(curd)</el-radio>
|
||||
<el-radio :value="1">树表(curd)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="删除类型" prop="delete.type">
|
||||
<el-radio-group v-model="formData.delete.type">
|
||||
<el-radio :value="0">物理删除</el-radio>
|
||||
<el-radio :value="1">软删除</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="删除字段"
|
||||
prop="delete.name"
|
||||
v-if="formData.delete.type == 1"
|
||||
>
|
||||
<el-select class="w-80" v-model="formData.delete.name" clearable>
|
||||
<el-option
|
||||
v-for="item in formData.table_column"
|
||||
:key="item.id"
|
||||
:value="item.column_name"
|
||||
:label="`${item.column_name}:${item.column_comment}`"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="formData.template_type == 1">
|
||||
<el-form-item label="树表ID" prop="treePrimary">
|
||||
<div>
|
||||
<el-select
|
||||
class="w-80"
|
||||
v-model="formData.tree.tree_id"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in formData.table_column"
|
||||
:key="item.id"
|
||||
:value="item.column_name"
|
||||
:label="`${item.column_name}:${item.column_comment}`"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="form-tips">指定树表的主要ID,一般为主键</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="树表父ID" prop="treeParent">
|
||||
<div>
|
||||
<el-select
|
||||
class="w-80"
|
||||
v-model="formData.tree.tree_pid"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in formData.table_column"
|
||||
:key="item.id"
|
||||
:value="item.column_name"
|
||||
:label="`${item.column_name}:${item.column_comment}`"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="form-tips">指定树表的父ID,比如:parent_id</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="树名称" prop="treeName">
|
||||
<el-select class="w-80" v-model="formData.tree.tree_name" clearable>
|
||||
<el-option
|
||||
v-for="item in formData.table_column"
|
||||
:key="item.id"
|
||||
:value="item.column_name"
|
||||
:label="`${item.column_name}:${item.column_comment}`"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="类描述">
|
||||
<div class="w-80">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="formData.class_comment"
|
||||
placeholder="请输入文件描述"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<div>
|
||||
例:填写test,生成文件描述为test控制器(test逻辑/test模型)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="生成方式" prop="generate_type">
|
||||
<el-radio-group v-model="formData.generate_type">
|
||||
<el-radio :value="0">压缩包下载</el-radio>
|
||||
<el-radio :value="1">生成到模块</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="模块名" prop="module_name">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.module_name"
|
||||
placeholder="请输入模块名"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tips">生成文件所在模块</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="类目录">
|
||||
<div class="w-80">
|
||||
<div>
|
||||
<el-input
|
||||
v-model="formData.class_dir"
|
||||
placeholder="请输入文件所在目录"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<div>
|
||||
例:填写test,则在app/模块名/controller/test下生成控制器
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="父级菜单" prop="menu.pid">
|
||||
<el-tree-select
|
||||
class="w-80"
|
||||
v-model="formData.menu.pid"
|
||||
:data="optionsData.menu"
|
||||
clearable
|
||||
node-key="id"
|
||||
:props="{
|
||||
label: 'name'
|
||||
}"
|
||||
default-expand-all
|
||||
placeholder="请选择父级菜单"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单名称" prop="menu.name">
|
||||
<div class="w-80">
|
||||
<el-input
|
||||
v-model="formData.menu.name"
|
||||
placeholder="请输入菜单名称"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单构建" prop="menu.type" required>
|
||||
<div>
|
||||
<el-radio-group v-model="formData.menu.type">
|
||||
<el-radio :value="1">自动构建</el-radio>
|
||||
<el-radio :value="0">手动添加</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">
|
||||
自动构建:自动执行生成菜单sql。手动添加:自行添加菜单。
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="关联配置" name="relations">
|
||||
<el-button type="primary" @click="showEditPopup('add')">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
新增关联
|
||||
</el-button>
|
||||
<div class="mt-4">
|
||||
<el-table :data="formData.relations" size="small">
|
||||
<el-table-column prop="type" label="关联类型">
|
||||
<template #default="{ row }">
|
||||
<dict-value :value="row.type" :options="relationTypes" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="关联名称" />
|
||||
<el-table-column prop="model" label="关联模型" />
|
||||
<el-table-column prop="local_key" label="关联键">
|
||||
<template #default="{ row }">
|
||||
<dict-value
|
||||
:value="row.local_key"
|
||||
:options="formData.table_column"
|
||||
:config="{
|
||||
label: 'column_comment',
|
||||
value: 'column_name'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="foreign_key" label="外键" />
|
||||
<el-table-column label="操作">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="showEditPopup('edit', row, $index)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="handelDelete($index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<relations-add
|
||||
:column="formData.table_column"
|
||||
:types="relationTypes"
|
||||
v-if="showEdit"
|
||||
ref="editRef"
|
||||
@add="handleAdd"
|
||||
@edit="handleEdit"
|
||||
@close="showEdit = false"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<footer-btns>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</footer-btns>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.header-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px; /* 这个属性可以设置子元素之间的间距 */
|
||||
}
|
||||
.header-checkbox .el-checkbox {
|
||||
margin-right: 2px;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts" setup name="tableEdit">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import { menuAll } from '@/api/perms/menu'
|
||||
import { dictTypeAll } from '@/api/setting/dict'
|
||||
import { generateEdit, tableDetail } from '@/api/tools/code'
|
||||
import { useDictOptions } from '@/hooks/useDictOptions'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import RelationsAdd from '../components/relations-add.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const activeName = ref('column')
|
||||
const showEdit = ref(false)
|
||||
const relationTypes = [
|
||||
{
|
||||
name: '一对一',
|
||||
value: 'has_one'
|
||||
},
|
||||
{
|
||||
name: '一对多',
|
||||
value: 'has_many'
|
||||
}
|
||||
]
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
table_name: '',
|
||||
table_comment: '',
|
||||
author: '',
|
||||
remark: '',
|
||||
template_type: 0,
|
||||
generate_type: 0,
|
||||
module_name: '',
|
||||
class_dir: '',
|
||||
class_comment: '',
|
||||
table_column: [] as any[],
|
||||
menu: {
|
||||
pid: 0,
|
||||
name: '',
|
||||
type: 0
|
||||
},
|
||||
tree: {
|
||||
tree_id: 0,
|
||||
tree_pid: 0,
|
||||
tree_name: 0
|
||||
},
|
||||
delete: {
|
||||
name: '',
|
||||
type: 0
|
||||
},
|
||||
relations: [] as any[]
|
||||
})
|
||||
|
||||
|
||||
const isAllRequired = ref(false)
|
||||
const isAllInsert = ref(false)
|
||||
const isAllUpdate = ref(false)
|
||||
const isAllLists = ref(false)
|
||||
const isAllQuery = ref(false)
|
||||
// 全选功能
|
||||
const toggleAllRequired = () => {
|
||||
formData.table_column.forEach(item => {
|
||||
item.is_required = isAllRequired.value ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAllInsert = () => {
|
||||
formData.table_column.forEach(item => {
|
||||
item.is_insert = isAllInsert.value ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAllUpdate = () => {
|
||||
formData.table_column.forEach(item => {
|
||||
item.is_update = isAllUpdate.value ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAllLists = () => {
|
||||
formData.table_column.forEach(item => {
|
||||
item.is_lists = isAllLists.value ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAllQuery = () => {
|
||||
formData.table_column.forEach(item => {
|
||||
item.is_query = isAllQuery.value ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
let editIndex = 0
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const editRef = shallowRef<InstanceType<typeof RelationsAdd>>()
|
||||
const rules = reactive({
|
||||
table_name: [{ required: true, message: '请输入表名称' }],
|
||||
table_comment: [{ required: true, message: '请输入表描述' }],
|
||||
module_name: [{ required: true, message: '请输入模块名' }],
|
||||
generate_type: [{ required: true, trigger: 'change' }],
|
||||
template_type: [{ required: true, trigger: 'change' }],
|
||||
['menu.pid']: [{ required: true, message: '请选择父级菜单' }],
|
||||
['menu.name']: [{ required: true, message: '请输入菜单名称' }],
|
||||
['delete.type']: [{ required: true, trigger: 'change' }],
|
||||
['delete.name']: [{ required: true, message: '请选择删除字段' }]
|
||||
})
|
||||
|
||||
const showEditPopup = async (type: string, data?: any, index?: number) => {
|
||||
showEdit.value = true
|
||||
await nextTick()
|
||||
if (data && index !== undefined) {
|
||||
editRef.value?.setFormData(data)
|
||||
editIndex = index
|
||||
}
|
||||
editRef.value?.open(type)
|
||||
}
|
||||
|
||||
const handleAdd = (data: any) => {
|
||||
const newData = cloneDeep(toRaw(data))
|
||||
formData.relations.push(newData)
|
||||
}
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
const newData = cloneDeep(toRaw(data))
|
||||
console.log(editIndex)
|
||||
formData.relations.splice(editIndex, 1, newData)
|
||||
}
|
||||
|
||||
const handelDelete = (index: number) => {
|
||||
formData.relations.splice(index, 1)
|
||||
}
|
||||
const getDetails = async () => {
|
||||
const data = await tableDetail({
|
||||
id: route.query.id
|
||||
})
|
||||
Object.keys(formData).forEach((key) => {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
})
|
||||
|
||||
watch(
|
||||
() => formData.generate_type,
|
||||
(value) => {
|
||||
if (value == 1) {
|
||||
feedback
|
||||
.confirm('生成到模块方式如遇同名文件会覆盖旧文件,确定要选择此方式吗?')
|
||||
.catch(() => {
|
||||
formData.generate_type = 0
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const { optionsData } = useDictOptions<{
|
||||
dict_type: any[]
|
||||
menu: any[]
|
||||
}>({
|
||||
dict_type: {
|
||||
api: dictTypeAll
|
||||
},
|
||||
menu: {
|
||||
api: menuAll,
|
||||
transformData(data) {
|
||||
const menu = { id: 0, name: '顶级', children: [] }
|
||||
menu.children = data
|
||||
return [menu]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
await generateEdit(formData)
|
||||
router.back()
|
||||
} catch (error: any) {
|
||||
for (const err in error) {
|
||||
const isInRules = Object.keys(rules).includes(err)
|
||||
isInRules && feedback.msgError(error[err][0]?.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getDetails()
|
||||
</script>
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="code-generation">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-form class="mb-[-16px]" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="表名称">
|
||||
<el-input v-model="formData.table_name" clearable @keyup.enter="resetPage" />
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="表描述">
|
||||
<el-input v-model="formData.table_comment" clearable @keyup.enter="resetPage" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never" v-loading="pager.loading">
|
||||
<div class="flex">
|
||||
<data-table
|
||||
v-perms="['tools.generator/selectTable']"
|
||||
class="inline-block mr-[10px]"
|
||||
@success="getLists"
|
||||
>
|
||||
<el-button type="primary">
|
||||
<template #icon>
|
||||
<icon name="el-icon-Plus" />
|
||||
</template>
|
||||
导入数据表
|
||||
</el-button>
|
||||
</data-table>
|
||||
<el-button
|
||||
v-perms="['tools.generator/delete']"
|
||||
:disabled="!selectData.length"
|
||||
@click="handleDelete(selectData)"
|
||||
type="danger"
|
||||
>
|
||||
<template #icon>
|
||||
<icon name="el-icon-Delete" />
|
||||
</template>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="['tools.generator/generate']"
|
||||
:disabled="!selectData.length"
|
||||
@click="handleGenerate(selectData)"
|
||||
>
|
||||
生成代码
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<el-table
|
||||
:data="pager.lists"
|
||||
size="large"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="表名称" prop="table_name" min-width="180" />
|
||||
<el-table-column label="表描述" prop="table_comment" min-width="180" />
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
<el-table-column label="更新时间" prop="update_time" min-width="180" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<el-button
|
||||
v-perms="['tools.generator/preview']"
|
||||
type="primary"
|
||||
link
|
||||
@click="handlePreview(row.id)"
|
||||
>
|
||||
预览
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" link>
|
||||
<router-link
|
||||
v-perms="['tools.generator/edit']"
|
||||
:to="{
|
||||
path: getRoutePath('tools.generator/edit'),
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
}"
|
||||
>
|
||||
编辑
|
||||
</router-link>
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
class="ml-2"
|
||||
@command="handleCommand($event, row)"
|
||||
v-perms="[
|
||||
'tools.generator/generate',
|
||||
'tools.generator/syncColumn',
|
||||
'tools.generator/delete'
|
||||
]"
|
||||
>
|
||||
<el-button type="primary" link>
|
||||
更多
|
||||
<icon name="el-icon-ArrowDown" :size="14" />
|
||||
</el-button>
|
||||
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<div v-perms="['tools.generator/generate']">
|
||||
<el-dropdown-item command="generate">
|
||||
<el-button type="primary" link>
|
||||
生成代码
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
<div v-perms="['tools.generator/syncColumn']">
|
||||
<el-dropdown-item command="sync">
|
||||
<el-button type="primary" link>
|
||||
同步
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
<div v-perms="['tools.generator/delete']">
|
||||
<el-dropdown-item command="delete">
|
||||
<el-button type="danger" link> 删除 </el-button>
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
<code-preview
|
||||
v-if="previewState.show"
|
||||
v-model="previewState.show"
|
||||
:code="previewState.code"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="codeGenerate">
|
||||
import {
|
||||
generateCode,
|
||||
generateDelete,
|
||||
generatePreview,
|
||||
generateTable,
|
||||
syncColumn
|
||||
} from '@/api/tools/code'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import { getRoutePath } from '@/router'
|
||||
import { isProdMode } from '@/utils/env'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
import CodePreview from '../components/code-preview.vue'
|
||||
import DataTable from '../components/data-table.vue'
|
||||
|
||||
const formData = reactive({
|
||||
table_name: '',
|
||||
table_comment: ''
|
||||
})
|
||||
|
||||
const previewState = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
code: []
|
||||
})
|
||||
|
||||
const { pager, getLists, resetParams, resetPage } = usePaging({
|
||||
fetchFun: generateTable,
|
||||
params: formData
|
||||
})
|
||||
|
||||
const selectData = ref<any[]>([])
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
selectData.value = val.map(({ id }) => id)
|
||||
}
|
||||
|
||||
const handleSync = async (id: number) => {
|
||||
await feedback.confirm('确定要同步表结构?')
|
||||
await syncColumn({ id })
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number | any[]) => {
|
||||
await feedback.confirm('确定要删除?')
|
||||
await generateDelete({ id })
|
||||
getLists()
|
||||
}
|
||||
|
||||
const handlePreview = async (id: number) => {
|
||||
const data: any = await generatePreview({ id })
|
||||
previewState.code = data
|
||||
previewState.show = true
|
||||
}
|
||||
|
||||
const hasGenerateTypeInModule = (data: any[]) => {
|
||||
return data.some((item) => item.generate_type == 1)
|
||||
}
|
||||
|
||||
const handleGenerate = async (selectData: any[]) => {
|
||||
if (isProdMode() && hasGenerateTypeInModule(selectData)) {
|
||||
return feedback.msgError('生成方式为生成到模块,请在前端开发模式下使用,详细参考文档')
|
||||
}
|
||||
const data: any = await generateCode({ id: selectData })
|
||||
if (data.file) {
|
||||
window.open(data.file, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCommand = (command: any, row: any) => {
|
||||
switch (command) {
|
||||
case 'generate':
|
||||
handleGenerate([row.id])
|
||||
break
|
||||
case 'sync':
|
||||
handleSync(row.id)
|
||||
break
|
||||
case 'delete':
|
||||
handleDelete(row.id)
|
||||
}
|
||||
}
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="code-preview">
|
||||
<el-dialog v-model="show" width="900px" title="代码预览">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane
|
||||
v-for="(item, index) in code"
|
||||
:label="item.name"
|
||||
:name="`index${index}`"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex" style="height: 50vh">
|
||||
<el-scrollbar class="flex-1">
|
||||
<highlightjs autodetect :code="item.content" />
|
||||
</el-scrollbar>
|
||||
<div>
|
||||
<el-button @click="handleCopy(item.content)" type="primary" link>
|
||||
<template #icon>
|
||||
<icon name="el-icon-CopyDocument" />
|
||||
</template>
|
||||
复制
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
code: any[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
const { toClipboard } = useClipboard()
|
||||
|
||||
const activeTab = ref('index0')
|
||||
|
||||
const handleCopy = async (text: string) => {
|
||||
try {
|
||||
await toClipboard(text)
|
||||
feedback.msgSuccess('复制成功')
|
||||
} catch (e) {
|
||||
feedback.msgError('复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
const show = computed<boolean>({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div class="data-table">
|
||||
<popup
|
||||
ref="popupRef"
|
||||
:clickModalClose="false"
|
||||
title="选择表"
|
||||
width="900px"
|
||||
:async="true"
|
||||
@confirm="handleConfirm"
|
||||
>
|
||||
<template #trigger>
|
||||
<slot></slot>
|
||||
</template>
|
||||
<el-form class="ls-form" :model="formData" inline>
|
||||
<el-form-item class="w-[280px]" label="表名称">
|
||||
<el-input v-model="formData.name" clearable @keyup.enter="resetPage" />
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="表描述">
|
||||
<el-input v-model="formData.comment" clearable @keyup.enter="resetPage" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="m-4" v-loading="pager.loading">
|
||||
<el-table
|
||||
height="400"
|
||||
size="large"
|
||||
:data="pager.lists"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="表名称" prop="name" min-width="150" />
|
||||
<el-table-column label="表描述" prop="comment" min-width="160" />
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" />
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dataTable, selectTable } from '@/api/tools/code'
|
||||
import Pagination from '@/components/pagination/index.vue'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'success'): void
|
||||
}>()
|
||||
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
|
||||
const formData = reactive({
|
||||
name: '', // 表名称
|
||||
comment: '' // 表描述
|
||||
})
|
||||
|
||||
const { pager, getLists, resetParams, resetPage } = usePaging({
|
||||
fetchFun: dataTable,
|
||||
params: formData,
|
||||
size: 10
|
||||
})
|
||||
|
||||
const selectData = ref<any[]>([])
|
||||
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
selectData.value = val.map(({ name, comment }) => ({
|
||||
name,
|
||||
comment
|
||||
}))
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!selectData.value.length) return feedback.msgError('请选择数据表')
|
||||
await selectTable({
|
||||
table: selectData.value
|
||||
})
|
||||
popupRef.value?.close()
|
||||
emit('success')
|
||||
}
|
||||
|
||||
watch(
|
||||
() => popupRef.value?.visible,
|
||||
(value) => {
|
||||
if (value) getLists()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="edit-popup">
|
||||
<popup
|
||||
ref="popupRef"
|
||||
:title="popupTitle"
|
||||
:async="true"
|
||||
width="550px"
|
||||
@confirm="handleSubmit"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="formData" label-width="84px" :rules="formRules">
|
||||
<el-form-item label="关联类型" prop="type">
|
||||
<el-select class="flex-1" v-model="formData.type" placeholder="请选择关联类型">
|
||||
<el-option
|
||||
v-for="(item, index) in types"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入关联名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联模型" prop="model">
|
||||
<el-select class="flex-1" v-model="formData.model" placeholder="请选择关联模型">
|
||||
<el-option
|
||||
v-for="item in optionsData.models"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:key="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联健" prop="local_key">
|
||||
<el-select
|
||||
class="flex-1"
|
||||
v-model="formData.local_key"
|
||||
clearable
|
||||
placeholder="请选择关联健"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in column"
|
||||
:key="item.id"
|
||||
:value="item.column_name"
|
||||
:label="`${item.column_name}:${item.column_comment}`"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="外键" prop="foreign_key">
|
||||
<el-input
|
||||
v-model="formData.foreign_key"
|
||||
placeholder="关联表外键或中间表的外键"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import { getModels } from '@/api/tools/code'
|
||||
import Popup from '@/components/popup/index.vue'
|
||||
import { useDictOptions } from '@/hooks/useDictOptions'
|
||||
|
||||
defineProps({
|
||||
column: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
},
|
||||
types: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['add', 'close', 'edit'])
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
||||
const mode = ref<'add' | 'edit'>('add')
|
||||
const popupTitle = computed(() => {
|
||||
return mode.value == 'edit' ? '编辑关联' : '新增关联'
|
||||
})
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
model: '',
|
||||
type: '',
|
||||
local_key: '',
|
||||
foreign_key: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入关联名称'
|
||||
}
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择关联类型'
|
||||
}
|
||||
],
|
||||
model: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择关联模型'
|
||||
}
|
||||
],
|
||||
local_key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择关联健'
|
||||
}
|
||||
],
|
||||
foreign_key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入外键'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const { optionsData } = useDictOptions<{
|
||||
models: any[]
|
||||
}>({
|
||||
models: {
|
||||
api: getModels
|
||||
}
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate()
|
||||
popupRef.value?.close()
|
||||
emit(mode.value, formData)
|
||||
}
|
||||
|
||||
const open = (type = 'add') => {
|
||||
mode.value = type as 'add' | 'edit'
|
||||
popupRef.value?.open()
|
||||
}
|
||||
|
||||
const setFormData = (data: Record<any, any>) => {
|
||||
for (const key in formData) {
|
||||
if (data[key] != null && data[key] != undefined) {
|
||||
//@ts-ignore
|
||||
formData[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="error404">
|
||||
<error code="403" title="您的账号权限不足,请联系管理员添加权限!" :show-btn="false">
|
||||
<template #content>
|
||||
<div class="flex justify-center">
|
||||
<img class="w-[150px] h-[150px]" src="@/assets/images/no_perms.png" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
</error>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Error from './components/error.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="error404">
|
||||
<error code="404" title="哎呀,出错了!您访问的页面不存在…"></error>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Error from './components/error.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="error">
|
||||
<div>
|
||||
<slot name="content">
|
||||
<div class="error-code">{{ code }}</div>
|
||||
</slot>
|
||||
<div class="text-lg text-tx-secondary mt-7 mb-7">{{ title }}</div>
|
||||
<el-button v-if="showBtn" type="primary" @click="router.go(-1)">
|
||||
{{ second }} 秒后返回上一页
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
code: String,
|
||||
title: String,
|
||||
showBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
let timer: any = null
|
||||
const second = ref(5)
|
||||
const router = useRouter()
|
||||
props.showBtn &&
|
||||
(timer = setInterval(() => {
|
||||
if (second.value === 0) {
|
||||
clearInterval(timer)
|
||||
router.go(-1)
|
||||
} else {
|
||||
second.value--
|
||||
}
|
||||
}, 1000))
|
||||
onUnmounted(() => {
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.error {
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.error-code {
|
||||
@apply text-primary;
|
||||
font-size: 150px;
|
||||
}
|
||||
.el-button {
|
||||
width: 176px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-alert
|
||||
type="warning"
|
||||
title="温馨提示:用户账户变动记录"
|
||||
:closable="false"
|
||||
show-icon
|
||||
></el-alert>
|
||||
<el-form ref="formRef" class="mb-[-16px] mt-[16px]" :model="queryParams" :inline="true">
|
||||
<el-form-item class="w-[280px]" label="用户信息">
|
||||
<el-input
|
||||
v-model="queryParams.user_info"
|
||||
placeholder="请输入用户账号/昵称/手机号"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="w-[280px]" label="变动类型">
|
||||
<el-select v-model="queryParams.change_type">
|
||||
<el-option label="全部" value />
|
||||
<el-option
|
||||
v-for="(value, key) in optionsData.change_type"
|
||||
:key="key"
|
||||
:label="value"
|
||||
:value="key"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="记录时间">
|
||||
<daterange-picker
|
||||
v-model:startTime="queryParams.start_time"
|
||||
v-model:endTime="queryParams.end_time"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="resetParams">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<el-card class="!border-none mt-4" shadow="never">
|
||||
<el-table size="large" v-loading="pager.loading" :data="pager.lists">
|
||||
<el-table-column label="用户账号" prop="account" min-width="100" />
|
||||
<el-table-column label="用户昵称" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<image-contain
|
||||
class="flex-none mr-2"
|
||||
:src="row.avatar"
|
||||
:width="40"
|
||||
:height="40"
|
||||
preview-teleported
|
||||
fit="contain"
|
||||
/>
|
||||
{{ row.nickname }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手机号码" prop="mobile" min-width="100" />
|
||||
<el-table-column label="变动金额" prop="change_amount" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ 'text-error': row.action == 2 }">
|
||||
{{ row.change_amount }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余金额" prop="left_amount" min-width="100" />
|
||||
<el-table-column label="变动类型" prop="change_type_desc" min-width="120" />
|
||||
|
||||
<el-table-column label="来源单号" prop="source_sn" min-width="100" />
|
||||
<el-table-column label="记录时间" prop="create_time" min-width="120" />
|
||||
</el-table>
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="balanceDetail">
|
||||
import { accountLog, getUmChangeType } from '@/api/finance'
|
||||
import { useDictOptions } from '@/hooks/useDictOptions'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
|
||||
const queryParams = reactive({
|
||||
user_info: '',
|
||||
change_type: '',
|
||||
start_time: '',
|
||||
end_time: ''
|
||||
})
|
||||
|
||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||
fetchFun: accountLog,
|
||||
params: queryParams
|
||||
})
|
||||
|
||||
const { optionsData } = useDictOptions<{
|
||||
change_type: Record<number, string>
|
||||
}>({
|
||||
change_type: {
|
||||
api: getUmChangeType
|
||||
}
|
||||
})
|
||||
|
||||
getLists()
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user