Lufaneng 11 months ago
parent 244b016226
commit 04747f0018

@ -10,7 +10,7 @@
text-color="#000" text-color="#000"
router router
> >
<template v-for="(item, index) in asideRouter[0].children" :key="index"> <template v-for="(item, index) in routes[1].children" :key="index">
<el-sub-menu <el-sub-menu
:index="'/' + item.path" :index="'/' + item.path"
v-if="item.children && item.children.filter((el) => !el.meta.hide).length > 0" v-if="item.children && item.children.filter((el) => !el.meta.hide).length > 0"
@ -66,24 +66,8 @@ const isAdmin =
console.log(useUserStore().userInfo) console.log(useUserStore().userInfo)
console.log(isAdmin) console.log(isAdmin)
const filterRoute = (arr) => {
const arr1 = JSON.parse(JSON.stringify(arr))
let tempArr = []
tempArr = arr1.filter((item) => !item.meta?.needAdmin)
arr1.forEach((item) => {
if (item.children && item.children.length > 0) {
item.children = filterRoute(item.children)
}
})
return tempArr
}
const asideRouter = isAdmin ? routes : filterRoute(routes)
console.log('asideRouter', asideRouter)
console.log('routes', routes)
console.log(filterRoute(routes))
const route = useRoute() const route = useRoute()
console.log(routes)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

@ -53,19 +53,19 @@ const breadcrumbList = ref([])
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const getBreadcrumbList = () => { // const getBreadcrumbList = () => {
const arr = [] // const arr = []
const obj = routes[0].children.find((item) => item.path === route.path.split('/')[1]) // const obj = userStore[0].children.find((item) => item.path === route.path.split('/')[1])
arr.push(obj) // arr.push(obj)
if (route.path.split('/').length > 2) { // if (route.path.split('/').length > 2) {
arr.push(obj.children.find((item) => item.path === route.path.split('/')[2])) // arr.push(obj.children.find((item) => item.path === route.path.split('/')[2]))
} // }
breadcrumbList.value = arr // breadcrumbList.value = arr
} // }
watch(route, () => { // watch(route, () => {
getBreadcrumbList() // getBreadcrumbList()
}) // })
getBreadcrumbList() // getBreadcrumbList()
const logout = () => { const logout = () => {
userStore.logout() userStore.logout()

@ -6,90 +6,227 @@ import { useUserStore } from '@/stores/user'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import 'nprogress/nprogress.css' import 'nprogress/nprogress.css'
export const routes = [ // 基础路由(不需要权限)
export const constantRoutes = [
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/index.vue'),
meta: { title: '登录' },
},
{ {
path: '/', path: '/',
component: Layout, component: Layout,
redirect: '/BusinessManage', redirect: `/SystemManage/UserManage`, // 重定向到第一个有权限的路由
children: [ children: [
// { // {
// path: 'WorkbenchPage', // path: 'WorkbenchPage',
// name: 'WorkbenchPage', // name: 'WorkbenchPage',
// component: () => import('@/views/WorkbenchPage/index.vue'), // component: () => import('@/views/WorkbenchPage/index.vue'),
// meta: { title: '工作台' }, // meta: { title: '工作台', permissions: ['workbench'] },
// }, // },
{ {
path: 'BusinessManage', path: 'SystemManage',
name: 'BusinessManage', meta: { title: '系统管理' },
// redirect: '/BusinessManage/StoreManage', redirect: `/SystemManage/UserManage`,
meta: { title: '门店管理' },
component: () => import('@/views/BusinessManage/index.vue'),
children: [ children: [
// { {
// path: 'StoreManage', path: 'UserManage',
// name: 'StoreManage', name: 'UserManage',
// component: () => import('@/views/BusinessManage/index.vue'), component: () => import('@/views/UserManage/index.vue'),
// meta: { title: '门店列表' }, meta: { title: '用户管理', permissions: ['RoleManage'] },
// }, },
// { {
// path: 'DataManage', path: 'RoleManage',
// name: 'DataManage', name: 'RoleManage',
// component: () => import('@/views/BusinessManage/DataManage.vue'), component: () => import('@/views/RoleManage/index.vue'),
// meta: { title: '分类管理' }, meta: { title: '角色管理', permissions: ['RoleManage'] },
// }, },
{
path: 'AuthManage',
name: 'AuthManage',
component: () => import('@/views/AuthManage/index.vue'),
meta: { title: '权限管理', permissions: ['AuthManage'] },
},
], ],
}, },
{ {
path: 'EnterpriseManage', path: 'ModelManage',
name: 'EnterpriseManage', name: 'ModelManage',
component: () => import('@/views/EnterpriseManage/index.vue'), component: () => import('@/views/ModelManage/index.vue'),
meta: { title: '企业管理' }, meta: { title: '模版管理' },
}, }
{
path: 'CustomerManage',
name: 'CustomerManage',
component: () => import('@/views/CustomerManage/index.vue'),
meta: { title: 'C端管理', needAdmin: true },
},
{
path: 'StoreReview',
name: 'StoreReview',
component: () => import('@/views/StoreReview/index.vue'),
meta: { title: '门店审核', needAdmin: true },
},
{
path: 'EnterpriseAudit',
name: 'EnterpriseAudit',
component: () => import('@/views/EnterpriseAudit/index.vue'),
meta: { title: '企业审核', needAdmin: true },
},
], ],
}, }
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/index.vue'),
meta: { title: '登录' },
},
] ]
// 动态路由配置(需要权限)
// export const asyncRoutes = [
// // {
// // path: 'WorkbenchPage',
// // name: 'WorkbenchPage',
// // component: () => import('@/views/WorkbenchPage/index.vue'),
// // meta: { title: '工作台', permissions: ['workbench'] },
// // },
// {
// path: 'BusinessManage',
// name: 'BusinessManage',
// meta: { title: '门店管理', permissions: ['business'] },
// component: () => import('@/views/BusinessManage/index.vue'),
// children: [
// // {
// // path: 'StoreManage',
// // name: 'StoreManage',
// // component: () => import('@/views/BusinessManage/index.vue'),
// // meta: { title: '门店列表', permissions: ['business:store'] },
// // },
// // {
// // path: 'DataManage',
// // name: 'DataManage',
// // component: () => import('@/views/BusinessManage/DataManage.vue'),
// // meta: { title: '分类管理', permissions: ['business:data'] },
// // },
// ],
// },
// {
// path: 'EnterpriseManage',
// name: 'EnterpriseManage',
// component: () => import('@/views/EnterpriseManage/index.vue'),
// meta: { title: '企业管理', permissions: ['enterprise'] },
// },
// {
// path: 'CustomerManage',
// name: 'CustomerManage',
// component: () => import('@/views/CustomerManage/index.vue'),
// meta: { title: 'C端管理', permissions: ['superadmin'] },
// },
// {
// path: 'StoreReview',
// name: 'StoreReview',
// component: () => import('@/views/StoreReview/index.vue'),
// meta: { title: '门店审核', permissions: ['superadmin'] },
// },
// {
// path: 'EnterpriseAudit',
// name: 'EnterpriseAudit',
// component: () => import('@/views/EnterpriseAudit/index.vue'),
// meta: { title: '企业审核', permissions: ['superadmin'] },
// },
// ]
// 检查用户权限
function hasPermission(permissions, userPermissions) {
if (!permissions || permissions.length === 0) return true
if (!userPermissions || userPermissions.length === 0) return false
return permissions.some(permission =>
userPermissions.some(userPerm => userPerm.code === permission)
)
}
// 过滤有权限的路由
function filterAsyncRoutes(routes, userPermissions) {
const filteredRoutes = []
routes.forEach(route => {
const temp = { ...route }
if (hasPermission(temp.meta?.permissions, userPermissions)) {
if (temp.children) {
temp.children = filterAsyncRoutes(temp.children, userPermissions)
}
filteredRoutes.push(temp)
}
})
return filteredRoutes
}
// 生成动态路由
export function generateRoutes(userPermissions) {
const accessedRoutes = filterAsyncRoutes(asyncRoutes, userPermissions)
// 如果有可访问的路由,创建主布局路由
if (accessedRoutes.length > 0) {
const mainRoute = {
path: '/',
component: Layout,
redirect: `/${accessedRoutes[0].path}`, // 重定向到第一个有权限的路由
children: accessedRoutes,
}
return [mainRoute, ...constantRoutes]
}
return constantRoutes
}
export const routes = constantRoutes
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes, routes,
}) })
// 白名单 // 白名单
const wihteList = ['/login'] const whiteList = ['/login']
// 标记是否已添加动态路由
let hasAddRoutes = false
// 全局前置导航守卫 // 全局前置导航守卫
router.beforeEach((to) => { router.beforeEach(async (to, from, next) => {
// 开启进度条 // 开启进度条
NProgress.start() NProgress.start()
// 如果没有 token ,并且不在白名单中,则从定向到登陆
if (!useUserStore().token && !wihteList.includes(to.path)) return '/login' const userStore = useUserStore()
if (to.meta.needAdmin && !(useUserStore().userInfo?.jurisdictions?.findIndex(item => item.code === 'superadmin') !== -1)) {
return false // 如果没有 token并且不在白名单中则重定向到登录
} // if (!userStore.token && !whiteList.includes(to.path)) {
// return next('/login')
// }
// 如果有 token 但还没有添加动态路由
// if (userStore.token && !hasAddRoutes) {
// try {
// // 获取用户权限
// const userPermissions = userStore.userInfo?.jurisdictions || []
// // 生成有权限的路由
// const accessRoutes = generateRoutes(userPermissions)
// // 清除现有路由
// router.getRoutes().forEach(route => {
// if (route.name && route.name !== 'login') {
// router.removeRoute(route.name)
// }
// })
// // 添加新的动态路由
// accessRoutes.forEach(route => {
// router.addRoute(route)
// })
// hasAddRoutes = true
// // 如果当前访问的是根路径,重定向到第一个有权限的页面
// if (to.path === '/') {
// const firstRoute = accessRoutes.find(route => route.path === '/')
// if (firstRoute && firstRoute.children && firstRoute.children.length > 0) {
// return next(`/${firstRoute.children[0].path}`)
// }
// }
// // 重新导航到目标路由
// return next({ ...to, replace: true })
// } catch (error) {
// console.error('生成路由失败:', error)
// // 清除用户信息并重定向到登录
// userStore.logout()
// return next('/login')
// }
// }
next()
}) })
// 全局后置导航守卫 // 全局后置导航守卫
@ -99,4 +236,15 @@ router.afterEach((to) => {
NProgress.done() NProgress.done()
}) })
// 重置路由(用于退出登录时)
export function resetRouter() {
hasAddRoutes = false
// 清除动态添加的路由
router.getRoutes().forEach(route => {
if (route.name && route.name !== 'login') {
router.removeRoute(route.name)
}
})
}
export default router export default router

@ -6,6 +6,9 @@ export const useUserStore = defineStore('user', () => {
const userInfo = ref( const userInfo = ref(
localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}, localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {},
) )
const routerList = ref(
localStorage.getItem('routerList') ? JSON.parse(localStorage.getItem('routerList')) : [],
)
const setToken = (t) => { const setToken = (t) => {
token.value = t token.value = t
localStorage.setItem('token', t) localStorage.setItem('token', t)
@ -14,11 +17,22 @@ export const useUserStore = defineStore('user', () => {
userInfo.value = u userInfo.value = u
localStorage.setItem('userInfo', JSON.stringify(u)) localStorage.setItem('userInfo', JSON.stringify(u))
} }
const setRouterList = (u) => {
userInfo.value = u
localStorage.setItem('routerList', JSON.stringify(u))
}
const logout = () => { const logout = () => {
localStorage.removeItem('token') localStorage.removeItem('token')
localStorage.removeItem('userInfo') localStorage.removeItem('userInfo')
localStorage.removeItem('routerList')
token.value = '' token.value = ''
userInfo.value = {} userInfo.value = {}
routerList.value = []
// 重置路由
import('@/router').then(({ resetRouter }) => {
resetRouter()
})
} }
return { token, setToken, setUserInfo, userInfo, logout } return { token, setToken, setUserInfo, userInfo, logout }
}) })

@ -0,0 +1,107 @@
<script setup>
import RPC from '@/utils/rpc'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
name: [{ required: true, message: '不可为空', trigger: 'change' }],
code: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const api = isAdd.value ? '/permission/create' : '/permission/update'
const params = {
...formField.value,
id: isAdd.value ? null : props.row.id,
}
RPC.post(api, params)
.then((res) => {
ElMessage.success('操作成功')
emit('onSuccess')
emit('closed')
})
.finally(() => {
loading.value = false
})
}
if (!isAdd.value) {
formField.value = {
...props.row,
}
}
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="name" label="名称">
<el-input placeholder="请输入" v-model="formField.name" />
</el-form-item>
<el-form-item prop="code" label="字段">
<el-input placeholder="请输入" v-model="formField.code" />
</el-form-item>
<el-form-item prop="type" label="类型">
<el-select placeholder="请输入" v-model="formField.type">
<el-option :value="1" label="目录"></el-option>
<el-option :value="2" label="菜单"></el-option>
<el-option :value="3" label="按钮"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="description" label="描述">
<el-input placeholder="请输入" v-model="formField.description" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped></style>

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="title">C端管理</div> <div class="title">权限管理</div>
<el-form <el-form
ref="searchFormRef" ref="searchFormRef"
class="form" class="form"
@ -8,7 +8,7 @@
label-width="98px" label-width="98px"
:model="queryParams" :model="queryParams"
> >
<el-form-item label="名" prop="name"> <el-form-item label="" prop="name">
<el-input <el-input
clearable clearable
placeholder="请输入" placeholder="请输入"
@ -34,31 +34,27 @@
v-loading="loading" v-loading="loading"
header-row-class-name="table-header" header-row-class-name="table-header"
> >
<el-table-column align="center" label="姓名" prop="name" /> <el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
<el-table-column align="center" label="手机号码" prop="phone" /> <el-table-column align="center" label="字段" prop="code" show-overflow-tooltip />
<el-table-column align="center" label="邮箱" prop="email" /> <el-table-column align="center" label="描述" prop="description" show-overflow-tooltip />
<el-table-column align="center" label="地址" prop="address" /> <el-table-column align="center" label="类型" prop="type">
<el-table-column align="center" label="咨询类型" prop="appointment_type" />
<el-table-column align="center" label="需求补充" prop="remark" />
<el-table-column align="center" label="日期" prop="appointment_time">
<template #default="{ row }"> <template #default="{ row }">
<span>{{ <span v-if="row.type == 1"></span>
row.appointment_time && <span v-if="row.type == 2"></span>
dayjs(row.appointment_time * 1000).format('YYYY-MM-DD HH:mm:ss') <span v-if="row.type == 3"></span>
}}</span>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column align="center" label="操作"> <el-table-column align="center" label="操作" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" @click="edit(row)"> <el-button v-if="row.status != 1" link type="primary" @click="edit(row)">
修改 编辑
</el-button> </el-button>
<el-button link type="danger" @click="handleDelete(row)"> <el-button v-if="row.status != 1" link type="danger" @click="handleDelete(row)">
删除 删除
</el-button> </el-button>
</template> </template>
</el-table-column> --> </el-table-column>
</el-table> </el-table>
</div> </div>
@ -74,6 +70,7 @@
v-if="isShowDialog" v-if="isShowDialog"
:type="rowType" :type="rowType"
:row="currentRow" :row="currentRow"
:categoryList="categoryList"
@closed="isShowDialog = false" @closed="isShowDialog = false"
@on-success="getList" @on-success="getList"
/> />
@ -83,8 +80,6 @@
<script setup> <script setup>
import { useTableRPC } from '@/utils/hook' import { useTableRPC } from '@/utils/hook'
import HandleDialog from './HandleDialog.vue' import HandleDialog from './HandleDialog.vue'
import dayjs from 'dayjs'
import RPC from '@/utils/rpc'
const rowType = ref('') const rowType = ref('')
const currentRow = ref(null) const currentRow = ref(null)
@ -115,9 +110,9 @@ const {
tableContainerEl, tableContainerEl,
} = useTableRPC({ } = useTableRPC({
queryParams: {}, queryParams: {},
remoteDataFn: '/appointment/list', remoteDataFn: '/permission/list',
deleteConfig: { deleteConfig: {
remote: '/appointment/delete', remote: '/permission/delete',
key: 'id', key: 'id',
message(row) { message(row) {
return `确认删除 “${row.name}` return `确认删除 “${row.name}`

@ -1,179 +0,0 @@
<script setup>
import RPC from '@/utils/rpc'
import MapSelector from '@/components/MapSelector.vue'
import UploadFile from '@/components/UploadFile.vue'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
})
import { VueDraggable } from 'vue-draggable-plus'
const images = ref([])
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
name: [{ required: true, message: '不可为空', trigger: 'change' }],
sort: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
const showMapSelector = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const api = isAdd.value ? '/content/type/create' : '/content/type/update'
const params = {
...formField.value,
images: images.value ? JSON.stringify(images.value) : '[]',
id: isAdd.value ? null : props.row.id,
}
loading.value = true
RPC.post(api, params)
.then((res) => {
ElMessage.success('操作成功')
emit('onSuccess')
emit('closed')
})
.finally(() => {
loading.value = false
})
}
if (!isAdd.value) {
formField.value = props.row
images.value = props.row.images ? JSON.parse(props.row.images) : []
}
const set_video = (val) => {
console.log('val', val)
formField.value.video = val.fullUrl
}
const set_image = (val) => {
images.value.push(val.fullUrl)
}
const delImage = (item) => {
images.value = images.value.filter((ele) => ele !== item)
}
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="name" label="分类名称">
<el-input placeholder="请输入" v-model="formField.name" />
</el-form-item>
<el-form-item prop="sort" label="分类排序">
<el-input-number placeholder="请输入" v-model="formField.sort" />
</el-form-item>
<el-form-item prop="main_image" label="视频">
<video
controls
muted
:src="formField.video"
alt=""
class="video_box"
v-if="formField.video"
/>
<UploadFile v-else accept="video/*" @on-success="set_video"></UploadFile>
</el-form-item>
<el-form-item prop="images" label="图片">
<VueDraggable ghost-class="ghost" target=".sort-target" v-model="images">
<TransitionGroup class="sort-target" name="list" tag="div">
<div class="imgBox" v-for="item in images" :key="item">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="item"
show-progress
:preview-src-list="[item]"
fit="cover"
/>
<div class="closeBox" @click="delImage(item)">
<el-icon size="20"><CircleClose /></el-icon>
</div>
</div>
</TransitionGroup>
</VueDraggable>
<UploadFile @on-success="set_image"></UploadFile>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.video_box {
width: 370px;
height: 130px;
}
.sort-target {
display: flex;
flex-wrap: wrap;
}
.imgBox {
width: 200px;
height: 200px;
margin-right: 10px;
margin-bottom: 10px;
position: relative;
.closeBox {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
}
.list-enter-active,
.list-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: scale(0);
}
.ghost {
opacity: 0.5;
}
</style>

@ -1,420 +0,0 @@
<script setup>
import RPC from '@/utils/rpc'
import MapSelector from '@/components/MapSelector.vue'
import UploadFile from '@/components/UploadFile.vue'
import dayjs from 'dayjs'
import { VueDraggable } from 'vue-draggable-plus'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const product_images_list = ref([])
const qualification_images_list = ref([])
const product_pdfs_list = ref([])
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
store_name: [{ required: true, message: '不可为空', trigger: 'change' }],
latitude: [{ required: true, message: '不可为空', trigger: 'change' }],
longitude: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
const showMapSelector = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const api = isAdd.value ? '/store/create' : '/store/update'
const params = {
store: {
...formField.value,
product_images: product_images_list.value.join(','),
qualification_images: qualification_images_list.value.join(','),
product_pdfs: JSON.stringify(product_pdfs_list.value),
},
// category_ids: formField.value.category_ids,
id: isAdd.value ? null : props.row.id,
}
RPC.post(api, params)
.then((res) => {
ElMessage.success('操作成功')
emit('onSuccess')
emit('closed')
})
.finally(() => {
loading.value = false
})
}
if (!isAdd.value) {
formField.value = {
...props.row,
}
product_images_list.value = props.row.product_images ? props.row.product_images.split(',') : []
qualification_images_list.value = props.row.qualification_images
? props.row.qualification_images.split(',')
: []
product_pdfs_list.value = props.row.product_pdfs ? JSON.parse(props.row.product_pdfs) : []
}
const chooseCoordinates = () => {
showMapSelector.value = true
}
//
const handleCoordinateConfirm = (coordinates) => {
console.log('接收到坐标确认事件:', coordinates)
formField.value.longitude = coordinates.longitude
formField.value.latitude = coordinates.latitude
}
const set_main_image = (val) => {
console.log('val', val)
formField.value.main_image = val.fullUrl
}
const set_video = (val) => {
formField.value.video_intro = val.fullUrl
}
const set_product_images = (val) => {
product_images_list.value.push(val.fullUrl)
}
const set_qualification_images = (val) => {
qualification_images_list.value.push(val.fullUrl)
}
const set_product_pdfs = (val, file) => {
console.log(val, file)
product_pdfs_list.value.push({
url: val.fullUrl,
name: file.name,
size: (file.size / 1024 / 1024).toFixed(2),
date: dayjs().format('YYYY-MM-DD HH:mm'),
})
}
const delImage = (arr, index) => {
arr.splice(index, 1)
}
const openPdf = (item) => {
if (item.url) {
window.open(item.url)
}
}
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="store_name" label="门店名称">
<el-input placeholder="请输入" v-model="formField.store_name" />
</el-form-item>
<el-form-item prop="location" label="地区">
<el-input placeholder="请输入" v-model="formField.location" />
</el-form-item>
<el-form-item prop="address" label="地址详情">
<el-input placeholder="请输入" v-model="formField.address" />
</el-form-item>
<el-form-item prop="business_hours" label="工作时间">
<el-input placeholder="请输入" v-model="formField.business_hours" />
</el-form-item>
<el-form-item prop="contact_phone" label="联系电话">
<el-input placeholder="请输入" v-model="formField.contact_phone" />
</el-form-item>
<el-form-item prop="tags" label="分类标签">
<el-input placeholder="请输入,多个以,分隔" v-model="formField.tags" />
</el-form-item>
<!-- <el-form-item prop="category_ids" label="分类">
<el-select
clearable
default-first-option
filterable
multiple
placeholder="请选择"
v-model="formField.category_ids"
>
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item> -->
<el-form-item prop="main_image" label="门店主图">
<div class="main_image_box" v-if="formField.main_image">
<!-- <img :src="formField.main_image" alt="" /> -->
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="formField.main_image"
show-progress
:preview-src-list="[formField.main_image]"
fit="cover"
/>
<div class="closeBox" @click="formField.main_image = ''">
<el-icon size="20"><CircleClose /></el-icon>
</div>
</div>
<UploadFile v-else @on-success="set_main_image"></UploadFile>
</el-form-item>
<el-form-item prop="longitude" label="经度">
<el-input-number
style="width: 200px"
:controls="false"
placeholder="请输入"
v-model="formField.longitude"
/>
</el-form-item>
<el-form-item prop="latitude" label="纬度">
<el-input-number
style="width: 200px"
:controls="false"
placeholder="请输入"
v-model="formField.latitude"
/>
</el-form-item>
<el-form-item prop="" label="">
<el-button type="primary" @click="chooseCoordinates"></el-button>
</el-form-item>
<el-form-item prop="video_intro" label="视频">
<video
controls
muted
:src="formField.video_intro"
alt=""
class="video_box"
v-if="formField.video_intro"
/>
<UploadFile v-else accept="video/*" @on-success="set_video"></UploadFile>
</el-form-item>
<el-form-item prop="product_images" label="产品图片">
<VueDraggable ghost-class="ghost" target=".sort-target" v-model="product_images_list">
<TransitionGroup class="sort-target" name="list" tag="div">
<div class="imgBox" v-for="(item, index) in product_images_list" :key="item">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="item"
show-progress
:preview-src-list="[item]"
fit="cover"
/>
<div class="closeBox" @click="delImage(product_images_list, index)">
<el-icon size="20"><CircleClose /></el-icon>
</div>
</div>
</TransitionGroup>
</VueDraggable>
<UploadFile @on-success="set_product_images"></UploadFile>
</el-form-item>
<el-form-item prop="qualification_images" label="企业资质">
<VueDraggable
ghost-class="ghost"
target=".sort-target2"
v-model="qualification_images_list"
>
<TransitionGroup class="sort-target2" name="list" tag="div">
<div class="imgBox" v-for="(item, index) in qualification_images_list" :key="item">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="item"
show-progress
:preview-src-list="[item]"
fit="cover"
/>
<div class="closeBox" @click="delImage(qualification_images_list, index)">
<el-icon size="20"><CircleClose /></el-icon>
</div>
</div>
</TransitionGroup>
</VueDraggable>
<UploadFile @on-success="set_qualification_images"></UploadFile>
</el-form-item>
<el-form-item prop="product_pdfs" label="产品资料PDF">
<VueDraggable ghost-class="ghost" target=".sort-target-pdf" v-model="product_pdfs_list">
<TransitionGroup class="sort-target-pdf" name="list" tag="div">
<div
class="pdfBox"
v-for="item in product_pdfs_list"
@click="openPdf(item)"
:key="item"
>
<div class="pdf_item">
<el-icon size="40"><Document /></el-icon>
<div class="name">{{ item.name }}</div>
<div class="info">
<div class="size">{{ item.size }}MB</div>
<div class="date">{{ item.date }}</div>
</div>
</div>
<div class="closeBox">
<el-icon
size="20"
@click="
() =>
(product_pdfs_list = product_pdfs_list.filter((ele) => ele.url !== item.url))
"
><CircleClose
/></el-icon>
</div>
</div>
</TransitionGroup>
</VueDraggable>
<div class="pdfBox">
<UploadFile accept="pdf" @on-success="set_product_pdfs"></UploadFile>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
<!-- 地图坐标选择器 -->
<MapSelector
v-model="showMapSelector"
:longitude="formField.longitude"
:latitude="formField.latitude"
@confirm="handleCoordinateConfirm"
/>
</template>
<style lang="scss" scoped>
.main_image_box {
width: 370px;
height: 130px;
position: relative;
img {
width: 100%;
height: 100%;
}
.closeBox {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
}
.video_box {
width: 370px;
height: 130px;
}
.sort-target,
.sort-target2,
.sort-target-pdf {
display: flex;
flex-wrap: wrap;
}
.imgBox {
width: 200px;
height: 200px;
margin-right: 10px;
margin-bottom: 10px;
position: relative;
.closeBox {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
}
.list-enter-active,
.list-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: scale(0);
}
.ghost {
opacity: 0.5;
}
.pdfBox {
width: 200px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
.closeBox {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
}
.pdf_item {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 99%;
border-radius: 10px;
padding: 0 10px;
box-sizing: border-box;
border: 1px solid #ccc;
cursor: pointer;
.name {
font-weight: 700;
}
.info {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: #999;
font-size: 12px;
}
}
</style>

@ -1,74 +0,0 @@
<script setup>
import RPC from '@/utils/rpc';
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({
color_component: []
})
const formRules = reactive({
})
const loading = ref(false)
function closed() {
emit('closed')
}
const submitForm = () => {
}
const detailData = ref([])
if (!isAdd.value) {
// formField.value = props.row
}
const add = () => {
formField.value.color_component.push({ num: calcId(formField.value.color_component), color_paste_id: '', remark: '', ratio: 0.01 })
}
const handleDelete = row => {
formField.value.color_component = formField.value.color_component.filter(item => item.num !== row.num)
}
</script>
<template>
<el-dialog align-center :title="isAdd ? '新增' : '修改'" width="774px" v-model="dialogVisible" @closed="closed" :close-on-click-modal="false">
<el-form ref="formRef" label-position="right" label-suffix=":" :model="formField" :rules="formRules" v-loading="loading">
111
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed">
</el-button>
<el-button type="primary" :loading="loading" @click="submitForm">
</el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped></style>

@ -1,163 +0,0 @@
<script setup>
import RPC from '@/utils/rpc'
import MapSelector from '@/components/MapSelector.vue'
import UploadFile from '@/components/UploadFile.vue'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
store_name: [{ required: true, message: '不可为空', trigger: 'change' }],
latitude: [{ required: true, message: '不可为空', trigger: 'change' }],
longitude: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
const showMapSelector = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const params = { store: { ...formField.value }, category_ids: formField.value.category_ids }
RPC.post('/store/create', params)
}
if (!isAdd.value) {
formField.value = props.row
}
const chooseCoordinates = () => {
showMapSelector.value = true
}
RPC.post('/store/detail', { id: props.row.id })
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="store_name" label="门店名称">
<el-input placeholder="请输入" v-model="formField.store_name" />
</el-form-item>
<el-form-item prop="address" label="详细地址">
<el-input placeholder="请输入" v-model="formField.address" />
</el-form-item>
<el-form-item prop="location" label="地址">
<el-input placeholder="请输入" v-model="formField.location" />
</el-form-item>
<el-form-item prop="business_hours" label="工作时间">
<el-input placeholder="请输入" v-model="formField.business_hours" />
</el-form-item>
<el-form-item prop="contact_phone" label="联系电话">
<el-input placeholder="请输入" v-model="formField.contact_phone" />
</el-form-item>
<el-form-item prop="tags" label="分类标签">
<el-input placeholder="请输入,多个以,分隔" v-model="formField.tags" />
</el-form-item>
<el-form-item prop="category_ids" label="分类">
<el-select
clearable
default-first-option
filterable
multiple
placeholder="请选择"
v-model="formField.category_ids"
>
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item prop="main_image" label="门店主图">
<img
:src="formField.main_image"
alt=""
class="main_image_box"
v-if="formField.main_image"
/>
<UploadFile v-else @on-success="set_main_image"></UploadFile>
</el-form-item>
<el-form-item prop="longitude" label="经度">
<el-input-number
style="width: 200px"
:controls="false"
placeholder="请输入"
v-model="formField.longitude"
/>
</el-form-item>
<el-form-item prop="latitude" label="纬度">
<el-input-number
style="width: 200px"
:controls="false"
placeholder="请输入"
v-model="formField.latitude"
/>
</el-form-item>
<el-form-item prop="" label="">
<el-button type="primary" @click="chooseCoordinates"></el-button>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
</div>
</template>
</el-dialog>
<!-- 地图坐标选择器 -->
<MapSelector
v-model="showMapSelector"
:longitude="formField.longitude"
:latitude="formField.latitude"
@confirm="handleCoordinateConfirm"
/>
</template>
<style lang="scss" scoped>
.main_image_box {
width: 370px;
height: 130px;
}
</style>

@ -1,169 +0,0 @@
<template>
<div>
<div class="title">企业审核</div>
<el-form
ref="searchFormRef"
class="form"
:inline="true"
label-width="98px"
:model="queryParams"
>
<el-form-item label="企业名称" prop="store_name">
<el-input
clearable
placeholder="请输入"
style="width: 220px"
v-model="queryParams.store_name"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label-width="100px">
<el-button icon="Search" type="primary" @click="handleQuery"> </el-button>
<el-button icon="RefreshRight" @click="resetQuery"> </el-button>
</el-form-item>
</el-form>
<div style="margin-bottom: 20px">
<!-- <el-button icon="Plus" type="primary" @click="add"> </el-button> -->
</div>
<div ref="tableContainerEl" class="table_border table">
<el-table
ref="tableRef"
:data="tableData"
v-loading="loading"
header-row-class-name="table-header"
>
<el-table-column align="center" label="企业名称" prop="company_name" />
<el-table-column align="center" label="姓名" prop="contact_person" />
<el-table-column align="center" label="手机号码" prop="contact_phone" />
<el-table-column align="center" label="邮箱" prop="contact_email" />
<el-table-column align="center" label="地址" prop="address" />
<el-table-column
align="center"
width="180"
label="更新日期"
prop="updated_at"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.updated_at ? dayjs(row.updated_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '' }}
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="150">
<template #default="{ row }">
<!-- <el-button link type="primary" @click="goDetail(row)"> </el-button> -->
<el-button link type="primary" @click="review(row.id, 2)"> 通过 </el-button>
<el-button link type="danger" @click="review(row.id, 3)"> 驳回 </el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total > 0"
:total="total"
v-model:limit="queryParams.limit"
v-model:page="queryParams.page"
@pagination="getList"
/>
<HandleDialog
v-if="isShowDialog"
:type="rowType"
:row="currentRow"
:categoryList="categoryList"
@closed="isShowDialog = false"
@on-success="getList"
/>
</div>
</template>
<script setup>
import { useTableRPC } from '@/utils/hook'
import dayjs from 'dayjs'
import RPC from '@/utils/rpc'
import HandleDialog from './HandleDialog.vue'
const rowType = ref('')
const currentRow = ref(null)
const add = () => {
isShowDialog.value = true
currentRow.value = null
rowType.value = 'add'
}
const goDetail = (row) => {
isShowDialog.value = true
rowType.value = 'edit'
currentRow.value = row
}
const review = (id, status) => {
if (status === 2) {
ElMessageBox.confirm('确认通过?', '通过', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
RPC.post('/enterprise/review', { id, status }).then(() => {
ElMessage.success('操作成功')
getList()
})
})
.catch(() => {})
} else {
ElMessageBox.prompt('请输入驳回原因', '驳回', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputErrorMessage: '请输入驳回原因',
inputValidator: (value) => {
return value.trim() !== ''
},
})
.then(({ value }) => {
RPC.post('/enterprise/review', { id, status, note: value }).then(() => {
ElMessage.success('操作成功')
getList()
})
})
.catch(() => {})
}
}
const {
loading,
tableData,
tableRef,
searchFormRef,
total,
queryParams,
getList,
handleQuery,
resetQuery,
handleDelete,
isShowDialog,
maxHeight,
tableContainerEl,
} = useTableRPC({
queryParams: {},
remoteDataFn: '/enterprise/pending',
// deleteConfig: {
// remote: '/store/pending_list',
// key: 'id',
// message(row) {
// return ` ${row.underpainting_name}`
// },
// },
})
getList()
</script>
<style lang="scss" scoped>
.title {
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
}
</style>

@ -86,8 +86,8 @@ const handleLogin = () => {
}) })
.then((res) => { .then((res) => {
useUserStore().setUserInfo(res) useUserStore().setUserInfo(res)
// ElMessage.success('') ElMessage.success('登录成功')
// router.push('/') router.push('/')
}) })
.finally(() => { .finally(() => {
loading.value = false loading.value = false

@ -1,7 +1,5 @@
<script setup> <script setup>
import RPC from '@/utils/rpc' import RPC from '@/utils/rpc'
import MapSelector from '@/components/MapSelector.vue'
import UploadFile from '@/components/UploadFile.vue'
const props = defineProps({ const props = defineProps({
type: { type: {
type: String, type: String,
@ -27,15 +25,11 @@ onMounted(() => {
const formRef = ref(null) const formRef = ref(null)
const formField = ref({}) const formField = ref({})
const formRules = reactive({ const formRules = reactive({
company_name: [{ required: true, message: '不可为空', trigger: 'change' }], username: [{ required: true, message: '不可为空', trigger: 'change' }],
contact_person: [{ required: true, message: '不可为空', trigger: 'change' }], password: [{ required: true, message: '不可为空', trigger: 'change' }],
contact_phone: [{ required: true, message: '不可为空', trigger: 'change' }],
contact_email: [{ required: true, message: '不可为空', trigger: 'change' }],
address: [{ required: true, message: '不可为空', trigger: 'change' }],
}) })
const loading = ref(false) const loading = ref(false)
const showMapSelector = ref(false)
function closed() { function closed() {
emit('closed') emit('closed')
@ -43,7 +37,7 @@ function closed() {
const submitForm = async () => { const submitForm = async () => {
await formRef.value.validate() await formRef.value.validate()
const api = isAdd.value ? '/enterprise/apply' : '/enterprise/update' const api = isAdd.value ? '/user/create' : '/user/update'
const params = { const params = {
...formField.value, ...formField.value,
id: isAdd.value ? null : props.row.id, id: isAdd.value ? null : props.row.id,
@ -60,7 +54,9 @@ const submitForm = async () => {
} }
if (!isAdd.value) { if (!isAdd.value) {
formField.value = props.row formField.value = {
...props.row,
}
} }
</script> </script>
@ -82,23 +78,19 @@ if (!isAdd.value) {
v-loading="loading" v-loading="loading"
label-width="100" label-width="100"
> >
<el-form-item prop="company_name" label="企业名称"> <el-form-item prop="username" label="用户名">
<el-input placeholder="请输入" v-model="formField.company_name" /> <el-input placeholder="请输入" v-model="formField.username" />
</el-form-item>
<el-form-item prop="contact_person" label="姓名">
<el-input placeholder="请输入" v-model="formField.contact_person" />
</el-form-item> </el-form-item>
<el-form-item prop="contact_phone" label="手机号码"> <el-form-item v-if="isAdd" prop="password" label="密码">
<el-input placeholder="请输入" v-model="formField.contact_phone" /> <el-input placeholder="请输入" v-model="formField.password" />
</el-form-item> </el-form-item>
<el-form-item prop="contact_email" label="邮箱"> <el-form-item prop="phone" label="电话">
<el-input placeholder="请输入" v-model="formField.contact_email" /> <el-input placeholder="请输入" v-model="formField.phone" />
</el-form-item> </el-form-item>
<el-form-item prop="address" label="地址"> <el-form-item prop="email" label="邮箱">
<el-input placeholder="请输入" v-model="formField.address" /> <el-input placeholder="请输入" v-model="formField.email" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="closed"> </el-button> <el-button @click="closed"> </el-button>
@ -106,19 +98,6 @@ if (!isAdd.value) {
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<!-- 地图坐标选择器 -->
<MapSelector
v-model="showMapSelector"
:longitude="formField.longitude"
:latitude="formField.latitude"
@confirm="handleCoordinateConfirm"
/>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped></style>
.main_image_box {
width: 370px;
height: 130px;
}
</style>

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="title">分类管理</div> <div class="title">角色管理</div>
<el-form <el-form
ref="searchFormRef" ref="searchFormRef"
class="form" class="form"
@ -8,7 +8,7 @@
label-width="98px" label-width="98px"
:model="queryParams" :model="queryParams"
> >
<el-form-item label="分类名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input <el-input
clearable clearable
placeholder="请输入" placeholder="请输入"
@ -34,13 +34,18 @@
v-loading="loading" v-loading="loading"
header-row-class-name="table-header" header-row-class-name="table-header"
> >
<el-table-column align="center" label="分类名称" prop="name" /> <el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
<el-table-column align="center" label="排序" prop="sort" /> <el-table-column align="center" label="字段" prop="code" show-overflow-tooltip />
<el-table-column align="center" label="操作"> <el-table-column align="center" label="描述" prop="description" show-overflow-tooltip />
<el-table-column align="center" label="操作" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button link type="primary" @click="edit(row)"> </el-button> <el-button v-if="row.status != 1" link type="primary" @click="edit(row)">
编辑
</el-button>
<el-button link type="danger" @click="handleDelete(row)"> </el-button> <el-button v-if="row.status != 1" link type="danger" @click="handleDelete(row)">
删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -58,6 +63,7 @@
v-if="isShowDialog" v-if="isShowDialog"
:type="rowType" :type="rowType"
:row="currentRow" :row="currentRow"
:categoryList="categoryList"
@closed="isShowDialog = false" @closed="isShowDialog = false"
@on-success="getList" @on-success="getList"
/> />
@ -66,9 +72,7 @@
<script setup> <script setup>
import { useTableRPC } from '@/utils/hook' import { useTableRPC } from '@/utils/hook'
import dayjs from 'dayjs' import HandleDialog from './HandleDialog.vue'
import RPC from '@/utils/rpc'
import HandleDialog from './DataManageDialog.vue'
const rowType = ref('') const rowType = ref('')
const currentRow = ref(null) const currentRow = ref(null)
@ -99,9 +103,9 @@ const {
tableContainerEl, tableContainerEl,
} = useTableRPC({ } = useTableRPC({
queryParams: {}, queryParams: {},
remoteDataFn: '/content/type/list', remoteDataFn: '/role/list',
deleteConfig: { deleteConfig: {
remote: '/content/type/delete', remote: '/role/delete',
key: 'id', key: 'id',
message(row) { message(row) {
return `确认删除 “${row.name}` return `确认删除 “${row.name}`

@ -0,0 +1,100 @@
<script setup>
import RPC from '@/utils/rpc'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
name: [{ required: true, message: '不可为空', trigger: 'change' }],
code: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const api = isAdd.value ? '/role/create' : '/role/update'
const params = {
...formField.value,
id: isAdd.value ? null : props.row.id,
}
RPC.post(api, params)
.then((res) => {
ElMessage.success('操作成功')
emit('onSuccess')
emit('closed')
})
.finally(() => {
loading.value = false
})
}
if (!isAdd.value) {
formField.value = {
...props.row,
}
}
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="name" label="名称">
<el-input placeholder="请输入" v-model="formField.name" />
</el-form-item>
<el-form-item prop="code" label="字段">
<el-input placeholder="请输入" v-model="formField.code" />
</el-form-item>
<el-form-item prop="description" label="描述">
<el-input placeholder="请输入" v-model="formField.description" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped></style>

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="title">企业管理</div> <div class="title">角色管理</div>
<el-form <el-form
ref="searchFormRef" ref="searchFormRef"
class="form" class="form"
@ -8,12 +8,12 @@
label-width="98px" label-width="98px"
:model="queryParams" :model="queryParams"
> >
<el-form-item label="企业名称" prop="company_name"> <el-form-item label="名称" prop="name">
<el-input <el-input
clearable clearable
placeholder="请输入" placeholder="请输入"
style="width: 220px" style="width: 220px"
v-model="queryParams.company_name" v-model="queryParams.name"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
@ -25,7 +25,7 @@
</el-form> </el-form>
<div style="margin-bottom: 20px"> <div style="margin-bottom: 20px">
<!-- <el-button icon="Plus" type="primary" @click="add"> </el-button> --> <el-button icon="Plus" type="primary" @click="add"> </el-button>
</div> </div>
<div ref="tableContainerEl" class="table_border table"> <div ref="tableContainerEl" class="table_border table">
<el-table <el-table
@ -34,33 +34,13 @@
v-loading="loading" v-loading="loading"
header-row-class-name="table-header" header-row-class-name="table-header"
> >
<el-table-column align="center" label="企业名称" prop="company_name" /> <el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
<el-table-column align="center" label="姓名" prop="contact_person" /> <el-table-column align="center" label="字段" prop="code" show-overflow-tooltip />
<el-table-column align="center" label="手机号码" prop="contact_phone" /> <el-table-column align="center" label="描述" prop="description" show-overflow-tooltip />
<el-table-column align="center" label="邮箱" prop="contact_email" /> <el-table-column align="center" label="操作" width="120">
<el-table-column align="center" label="地址" prop="address" />
<el-table-column align="center" label="状态" prop="status" width="70">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.status == 1"></span>
<span v-if="row.status == 2"></span>
<span v-if="row.status == 3"></span>
</template>
</el-table-column>
<el-table-column align="center" label="更新日期" prop="updated_at">
<template #default="{ row }">
<span>{{
row.updated_at && dayjs(row.updated_at * 1000).format('YYYY-MM-DD HH:mm:ss')
}}</span>
</template>
</el-table-column>
<el-table-column align="center" label="驳回原因" prop="review_note" show-overflow-tooltip />
<el-table-column align="center" label="操作">
<template #default="{ row }">
<el-button v-if="isAdmin && row.status == 2" link type="primary" @click="setAdmin(row)">
设为管理员
</el-button>
<el-button v-if="row.status != 1" link type="primary" @click="edit(row)"> <el-button v-if="row.status != 1" link type="primary" @click="edit(row)">
修改 编辑
</el-button> </el-button>
<el-button v-if="row.status != 1" link type="danger" @click="handleDelete(row)"> <el-button v-if="row.status != 1" link type="danger" @click="handleDelete(row)">
@ -83,6 +63,7 @@
v-if="isShowDialog" v-if="isShowDialog"
:type="rowType" :type="rowType"
:row="currentRow" :row="currentRow"
:categoryList="categoryList"
@closed="isShowDialog = false" @closed="isShowDialog = false"
@on-success="getList" @on-success="getList"
/> />
@ -92,21 +73,6 @@
<script setup> <script setup>
import { useTableRPC } from '@/utils/hook' import { useTableRPC } from '@/utils/hook'
import HandleDialog from './HandleDialog.vue' import HandleDialog from './HandleDialog.vue'
import dayjs from 'dayjs'
import RPC from '@/utils/rpc'
import { useUserStore } from '@/stores/user'
const isAdmin =
useUserStore().userInfo?.jurisdictions &&
useUserStore().userInfo?.jurisdictions?.findIndex((item) => item.code === 'superadmin') !== -1
const setAdmin = (row) => {
RPC.post('/enterprise/set_admin', { id: row.id }).then(() => {
ElMessage.success('操作成功')
getList()
})
}
const rowType = ref('') const rowType = ref('')
const currentRow = ref(null) const currentRow = ref(null)
@ -137,12 +103,12 @@ const {
tableContainerEl, tableContainerEl,
} = useTableRPC({ } = useTableRPC({
queryParams: {}, queryParams: {},
remoteDataFn: '/enterprise/list', remoteDataFn: '/role/list',
deleteConfig: { deleteConfig: {
remote: '/enterprise/delete', remote: '/role/delete',
key: 'id', key: 'id',
message(row) { message(row) {
return `确认删除 “${row.company_name}` return `确认删除 “${row.name}`
}, },
}, },
}) })

@ -1,320 +0,0 @@
<script setup>
import RPC from '@/utils/rpc'
import MapSelector from '@/components/MapSelector.vue'
import UploadFile from '@/components/UploadFile.vue'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
onMounted(() => {
dialogVisible.value = true
})
const loading = ref(true)
function closed() {
emit('closed')
}
const store = ref({})
const categories = ref([])
const product_images_list = ref([])
const qualification_images_list = ref([])
const product_pdfs_list = ref([])
RPC.post('/store/detail', { id: props.row.id })
.then((res) => {
console.log(res)
store.value = res.store || {}
product_images_list.value = res?.store?.product_images
? res?.store?.product_images.split(',')
: []
qualification_images_list.value = res?.store?.qualification_images
? res?.store?.qualification_images.split(',')
: []
product_pdfs_list.value = res?.store?.product_pdfs ? JSON.parse(res?.store?.product_pdfs) : []
// categories.value =
// res.categories?.map((item) => {
// return {
// ...item,
// images: item.images ? JSON.parse(item.images) : [],
// }
// }) || []
})
.finally(() => {
loading.value = false
})
const openPdf = (item) => {
if (item.url) {
window.open(item.url)
}
}
const activeTab = ref(0)
</script>
<template>
<el-dialog
align-center
title="详情"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<div v-loading="loading">
<div class="title">基本信息</div>
<div class="storeBox">
<div class="item">
<div class="key">门店名称</div>
<div class="value">{{ store.store_name }}</div>
</div>
<div class="item">
<div class="key">地址详情</div>
<div class="value">{{ store.address }}</div>
</div>
<div class="item">
<div class="key">地区</div>
<div class="value">{{ store.location }}</div>
</div>
<div class="item">
<div class="key">工作时间</div>
<div class="value">{{ store.business_hours }}</div>
</div>
<div class="item">
<div class="key">联系电话</div>
<div class="value">{{ store.contact_phone }}</div>
</div>
<div class="item">
<div class="key">分类标签</div>
<div class="value">{{ store.tags }}</div>
</div>
<div class="item">
<div class="key">门店主图</div>
<div class="value">
<div class="main_image_box" v-if="store.main_image">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="store.main_image"
show-progress
:preview-src-list="[store.main_image]"
fit="cover"
/>
<!-- <img :src="store.main_image" alt="" /> -->
</div>
</div>
</div>
<div class="item">
<div class="key">经度</div>
<div class="value">{{ store.longitude }}</div>
</div>
<div class="item">
<div class="key">纬度</div>
<div class="value">{{ store.latitude }}</div>
</div>
</div>
<div class="categoriesBox">
<div class="line" v-if="store.video_intro">
<div class="key">视频</div>
<div class="value">
<video
controls
muted
:src="store.video_intro"
alt=""
class="video_box"
v-if="store.video_intro"
/>
</div>
</div>
<div class="line">
<div class="key">产品图片</div>
<div class="value">
<div class="imgBox" v-for="item in product_images_list" :key="item">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="item"
show-progress
:preview-src-list="[item]"
fit="cover"
/>
</div>
</div>
</div>
<div class="line">
<div class="key">企业资质</div>
<div class="value">
<div class="imgBox" v-for="item in qualification_images_list" :key="item">
<el-image
ref="imageRef"
style="width: 100%; height: 100%"
:src="item"
show-progress
:preview-src-list="[item]"
fit="cover"
/>
</div>
</div>
</div>
<div class="line">
<div class="key">产品资料</div>
<div class="value">
<div
class="pdfBox"
v-for="item in product_pdfs_list"
@click="openPdf(item)"
:key="item"
>
<div class="pdf_item">
<el-icon size="40"><Document /></el-icon>
<div class="name">{{ item.name }}</div>
<div class="info">
<div class="size">{{ item.size }}MB</div>
<div class="date">{{ item.date }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.main_image_box {
width: 100%;
height: 100px;
img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
}
}
.title {
font-size: 16px;
font-weight: 700;
margin-bottom: 10px;
}
.storeBox {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item {
width: 32%;
display: flex;
align-items: center;
margin-bottom: 10px;
.key {
width: 70px;
flex-shrink: 0;
margin-right: 10px;
font-weight: 700;
}
}
}
.categoriesBox {
.tabBox {
display: flex;
margin-bottom: 10px;
align-items: center;
.tabItem {
font-weight: 600;
padding: 5px 10px;
margin-right: 10px;
cursor: pointer;
&.selected {
color: #006eff;
}
}
}
.line {
display: flex;
align-items: center;
margin-bottom: 10px;
.key {
font-weight: 700;
width: 60px;
margin-right: 10px;
flex-shrink: 0;
}
.value {
flex: 1;
display: flex;
flex-wrap: wrap;
.video_box {
width: 370px;
height: 130px;
}
.imgBox {
width: 100px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
}
}
}
}
.pdfBox {
width: 200px;
height: 100px;
margin-right: 10px;
margin-bottom: 10px;
overflow: hidden;
position: relative;
.closeBox {
position: absolute;
right: 0;
top: 0;
cursor: pointer;
}
}
.pdf_item {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
height: 99%;
border-radius: 10px;
padding: 0 10px;
box-sizing: border-box;
border: 1px solid #ccc;
cursor: pointer;
.name {
font-weight: 700;
}
.info {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: #999;
font-size: 12px;
}
}
</style>

@ -1,175 +0,0 @@
<template>
<div>
<div class="title">门店审核</div>
<el-form
ref="searchFormRef"
class="form"
:inline="true"
label-width="98px"
:model="queryParams"
>
<el-form-item label="门店名称" prop="store_name">
<el-input
clearable
placeholder="请输入"
style="width: 220px"
v-model="queryParams.store_name"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label-width="100px">
<el-button icon="Search" type="primary" @click="handleQuery"> </el-button>
<el-button icon="RefreshRight" @click="resetQuery"> </el-button>
</el-form-item>
</el-form>
<div style="margin-bottom: 20px">
<el-button icon="Plus" type="primary" @click="add"> </el-button>
</div>
<div ref="tableContainerEl" class="table_border table">
<el-table
ref="tableRef"
:data="tableData"
v-loading="loading"
header-row-class-name="table-header"
>
<el-table-column align="center" label="门店名称" prop="store_name" show-overflow-tooltip />
<el-table-column align="center" label="地区" prop="location" show-overflow-tooltip />
<el-table-column align="center" label="地址详情" prop="address" show-overflow-tooltip />
<el-table-column
align="center"
label="工作时间"
prop="business_hours"
show-overflow-tooltip
/>
<el-table-column align="center" label="电话" prop="contact_phone" show-overflow-tooltip />
<el-table-column align="center" label="业务类型" prop="tags" show-overflow-tooltip />
<el-table-column
align="center"
width="180"
label="注册日期"
prop="created_at"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.created_at ? dayjs(row.created_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '' }}
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="150">
<template #default="{ row }">
<el-button link type="primary" @click="goDetail(row)"> </el-button>
<el-button link type="primary" @click="review(row.id, 2)"> 通过 </el-button>
<el-button link type="danger" @click="review(row.id, 3)"> 驳回 </el-button>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total > 0"
:total="total"
v-model:limit="queryParams.limit"
v-model:page="queryParams.page"
@pagination="getList"
/>
<HandleDialog
v-if="isShowDialog"
:type="rowType"
:row="currentRow"
:categoryList="categoryList"
@closed="isShowDialog = false"
@on-success="getList"
/>
</div>
</template>
<script setup>
import { useTableRPC } from '@/utils/hook'
import dayjs from 'dayjs'
import RPC from '@/utils/rpc'
import HandleDialog from './HandleDialog.vue'
const rowType = ref('')
const currentRow = ref(null)
const add = () => {
isShowDialog.value = true
currentRow.value = null
rowType.value = 'add'
}
const goDetail = (row) => {
isShowDialog.value = true
rowType.value = 'edit'
currentRow.value = row
}
const review = (id, status) => {
if (status === 2) {
ElMessageBox.confirm('确认通过?', '通过', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
RPC.post('/store/review', { id, status }).then(() => {
ElMessage.success('操作成功')
getList()
})
})
.catch(() => {})
} else {
ElMessageBox.prompt('请输入驳回原因', '驳回', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputErrorMessage: '请输入驳回原因',
inputValidator: (value) => {
return value.trim() !== ''
},
})
.then(({ value }) => {
RPC.post('/store/review', { id, status, note: value }).then(() => {
ElMessage.success('操作成功')
getList()
})
})
.catch(() => {})
}
}
const {
loading,
tableData,
tableRef,
searchFormRef,
total,
queryParams,
getList,
handleQuery,
resetQuery,
handleDelete,
isShowDialog,
maxHeight,
tableContainerEl,
} = useTableRPC({
queryParams: {},
remoteDataFn: '/store/pending_list',
// deleteConfig: {
// remote: '/store/pending_list',
// key: 'id',
// message(row) {
// return ` ${row.underpainting_name}`
// },
// },
})
getList()
</script>
<style lang="scss" scoped>
.title {
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
}
</style>

@ -0,0 +1,117 @@
<script setup>
import RPC from '@/utils/rpc'
const props = defineProps({
type: {
type: String,
required: true,
},
row: [Object],
categoryList: {
type: Array,
default: [],
},
})
const emit = defineEmits(['closed', 'onSuccess'])
const dialogVisible = ref(false)
const isAdd = computed(() => props.type === 'add')
onMounted(() => {
dialogVisible.value = true
})
const formRef = ref(null)
const formField = ref({})
const formRules = reactive({
username: [{ required: true, message: '不可为空', trigger: 'change' }],
password: [{ required: true, message: '不可为空', trigger: 'change' }],
})
const loading = ref(false)
function closed() {
emit('closed')
}
const submitForm = async () => {
await formRef.value.validate()
const api = isAdd.value ? '/user/create' : '/user/update'
const params = {
...formField.value,
id: isAdd.value ? null : props.row.id,
}
RPC.post(api, params)
.then((res) => {
ElMessage.success('操作成功')
emit('onSuccess')
emit('closed')
})
.finally(() => {
loading.value = false
})
}
if (!isAdd.value) {
formField.value = {
...props.row,
}
RPC.post('/user_permissions', {
user_id: props.row.id,
type: 2,
as_tree: false,
}).then((res) => {
console.log(2, res)
})
RPC.post('/user_permissions', {
user_id: props.row.id,
type: 3,
as_tree: false,
}).then((res) => {
console.log(3, res)
})
}
</script>
<template>
<el-dialog
align-center
:title="isAdd ? '新增' : '修改'"
width="80%"
v-model="dialogVisible"
@closed="closed"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
label-position="right"
label-suffix=":"
:model="formField"
:rules="formRules"
v-loading="loading"
label-width="100"
>
<el-form-item prop="username" label="用户名">
<el-input placeholder="请输入" v-model="formField.username" />
</el-form-item>
<el-form-item v-if="isAdd" prop="password" label="密码">
<el-input placeholder="请输入" v-model="formField.password" />
</el-form-item>
<el-form-item prop="phone" label="电话">
<el-input placeholder="请输入" v-model="formField.phone" />
</el-form-item>
<el-form-item prop="email" label="邮箱">
<el-input placeholder="请输入" v-model="formField.email" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closed"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</template>
</el-dialog>
</template>
<style lang="scss" scoped></style>

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="title">门店管理</div> <div class="title">用户管理</div>
<el-form <el-form
ref="searchFormRef" ref="searchFormRef"
class="form" class="form"
@ -8,12 +8,12 @@
label-width="98px" label-width="98px"
:model="queryParams" :model="queryParams"
> >
<el-form-item label="门店名称" prop="store_name"> <el-form-item label="用户名称" prop="username">
<el-input <el-input
clearable clearable
placeholder="请输入" placeholder="请输入"
style="width: 220px" style="width: 220px"
v-model="queryParams.store_name" v-model="queryParams.username"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
@ -34,36 +34,9 @@
v-loading="loading" v-loading="loading"
header-row-class-name="table-header" header-row-class-name="table-header"
> >
<el-table-column align="center" label="门店名称" prop="store_name" show-overflow-tooltip /> <el-table-column align="center" label="用户名" prop="username" show-overflow-tooltip />
<el-table-column align="center" label="地区" prop="location" show-overflow-tooltip /> <el-table-column align="center" label="电话" prop="phone" show-overflow-tooltip />
<el-table-column align="center" label="地址详情" prop="address" show-overflow-tooltip /> <el-table-column align="center" label="邮件" prop="email" show-overflow-tooltip />
<el-table-column
align="center"
label="工作时间"
prop="business_hours"
show-overflow-tooltip
/>
<el-table-column align="center" label="电话" prop="contact_phone" show-overflow-tooltip />
<el-table-column align="center" label="业务类型" prop="tags" show-overflow-tooltip />
<el-table-column
align="center"
width="180"
label="注册日期"
prop="created_at"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.created_at ? dayjs(row.created_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '' }}
</template>
</el-table-column>
<el-table-column align="center" label="状态" prop="status" width="70">
<template #default="{ row }">
<span v-if="row.status == 1"></span>
<span v-if="row.status == 2"></span>
<span v-if="row.status == 3"></span>
</template>
</el-table-column>
<el-table-column align="center" label="驳回原因" prop="review_note" show-overflow-tooltip />
<el-table-column align="center" label="操作" width="120"> <el-table-column align="center" label="操作" width="120">
<template #default="{ row }"> <template #default="{ row }">
<el-button v-if="row.status != 1" link type="primary" @click="edit(row)"> <el-button v-if="row.status != 1" link type="primary" @click="edit(row)">
@ -116,11 +89,6 @@ const edit = (row) => {
currentRow.value = row currentRow.value = row
} }
const categoryList = ref([])
RPC.post('/content/type/list', { page: 1, limit: -1 }).then((res) => {
categoryList.value = res.rows || []
})
const { const {
loading, loading,
tableData, tableData,
@ -137,12 +105,12 @@ const {
tableContainerEl, tableContainerEl,
} = useTableRPC({ } = useTableRPC({
queryParams: {}, queryParams: {},
remoteDataFn: '/store/list', remoteDataFn: '/user/list',
deleteConfig: { deleteConfig: {
remote: '/store/delete', remote: '/user/delete',
key: 'id', key: 'id',
message(row) { message(row) {
return `确认删除 “${row.store_name}` return `确认删除 “${row.username}`
}, },
}, },
}) })

@ -1,418 +0,0 @@
<template>
<div class="workbench-page">
<div class="title">总体情况</div>
<div class="content">
<!-- 统计卡片区域 -->
<div class="stats-cards">
<div class="stat-card">
<div class="stat-icon blue">
<el-icon><House /></el-icon>
</div>
<div class="stat-info">
<div class="stat-label">企业总数量</div>
<div class="stat-value">100</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon purple">
<el-icon><User /></el-icon>
</div>
<div class="stat-info">
<div class="stat-label">注册总数量</div>
<div class="stat-value">1050</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon orange">
<el-icon><Star /></el-icon>
</div>
<div class="stat-info">
<div class="stat-label">游客访问量</div>
<div class="stat-value">1136</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green">
<el-icon><Document /></el-icon>
</div>
<div class="stat-info">
<div class="stat-label">最近3个月加盟</div>
<div class="stat-value">8</div>
</div>
</div>
</div>
<!-- 图表和公告区域 -->
<div class="chart-section">
<div class="chart-container">
<div class="chart-title">近3个月加盟量趋势</div>
<v-chart :style="{ height: '300px' }" class="chart" :option="chartOption" />
</div>
<div class="notice-section">
<div class="notice-item">
<div class="notice-title">公告企业端</div>
<div
v-if="!announcementEditing"
class="notice-content"
@dblclick="handleAnnouncementEdit"
>
{{ announcement || '暂无公告内容,双击编辑' }}
</div>
<el-input
v-else
v-model="announcement"
placeholder="请输入公告内容"
type="textarea"
:rows="3"
class="notice-input"
@blur="handleAnnouncementSave"
@keyup.enter.ctrl="handleAnnouncementSave"
ref="announcementInputRef"
/>
</div>
<div class="notice-item">
<div class="notice-title">备忘录</div>
<div v-if="!memoEditing" class="notice-content" @dblclick="handleMemoEdit">
{{ memo || '暂无备忘录内容,双击编辑' }}
</div>
<el-input
v-else
v-model="memo"
placeholder="请输入备忘录内容"
type="textarea"
:rows="3"
class="notice-input"
@blur="handleMemoSave"
@keyup.enter.ctrl="handleMemoSave"
ref="memoInputRef"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { LineChart } from 'echarts/charts'
import {
GridComponent,
TitleComponent,
LegendComponent,
TooltipComponent,
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import { House, User, Star, Document } from '@element-plus/icons-vue'
use([GridComponent, TitleComponent, LegendComponent, TooltipComponent, LineChart, CanvasRenderer])
const announcement = ref('这是一条企业端公告示例内容')
const memo = ref('这是一条备忘录示例内容')
//
const announcementEditing = ref(false)
const memoEditing = ref(false)
//
const announcementInputRef = ref(null)
const memoInputRef = ref(null)
//
const isAdmin = ref(true) //
//
const handleAnnouncementEdit = () => {
if (!isAdmin.value) {
ElMessage.warning('您没有编辑权限')
return
}
announcementEditing.value = true
nextTick(() => {
announcementInputRef.value?.focus()
})
}
const handleAnnouncementSave = () => {
announcementEditing.value = false
//
ElMessage.success('公告保存成功')
}
//
const handleMemoEdit = () => {
if (!isAdmin.value) {
ElMessage.warning('您没有编辑权限')
return
}
memoEditing.value = true
nextTick(() => {
memoInputRef.value?.focus()
})
}
const handleMemoSave = () => {
memoEditing.value = false
//
ElMessage.success('备忘录保存成功')
}
//
const chartData = [
{ name: '07-01', value: 12 },
{ name: '07-02', value: 48 },
{ name: '07-03', value: 46 },
{ name: '07-04', value: 28 },
{ name: '07-05', value: 42 },
{ name: '07-06', value: 43 },
{ name: '07-07', value: 27 },
]
const chartOption = computed(() => {
return {
tooltip: {
trigger: 'axis',
// formatter: '{b}: {c}',
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: chartData.map((item) => item.name),
axisLine: {
show: false,
},
axisTick: {
show: false,
},
},
yAxis: {
type: 'value',
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: '#f0f0f0',
},
},
},
series: [
{
data: chartData.map((item) => item.value),
type: 'line',
name: '加盟量',
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: {
color: '#409EFF',
width: 2,
},
itemStyle: {
color: '#409EFF',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(64, 158, 255, 0.3)',
},
{
offset: 1,
color: 'rgba(64, 158, 255, 0.1)',
},
],
},
},
},
],
}
})
</script>
<style lang="scss" scoped>
.workbench-page {
padding: 20px;
}
.title {
font-size: 20px;
font-weight: 700;
margin-bottom: 20px;
}
.content {
.stats-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
.el-icon {
font-size: 24px;
color: white;
}
&.blue {
background: #409eff;
}
&.purple {
background: #9c27b0;
}
&.orange {
background: #ff9800;
}
&.green {
background: #4caf50;
}
}
.stat-info {
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #333;
}
}
}
}
.chart-section {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
.chart-container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.chart-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
color: #333;
}
.chart {
width: 100%;
height: 300px;
}
}
.notice-section {
display: flex;
flex-direction: column;
gap: 20px;
.notice-item {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.notice-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
color: #333;
}
.notice-content {
min-height: 80px;
padding: 10px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fafafa;
color: #666;
line-height: 1.5;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
border-color: #409eff;
background-color: #f0f8ff;
}
&:empty::before {
content: attr(placeholder);
color: #c0c4cc;
}
}
.notice-input {
:deep(.el-textarea__inner) {
border: 1px solid #e4e7ed;
border-radius: 4px;
resize: none;
}
}
}
}
}
}
@media (max-width: 1200px) {
.content {
.stats-cards {
grid-template-columns: repeat(2, 1fr);
}
.chart-section {
grid-template-columns: 1fr;
}
}
}
@media (max-width: 768px) {
.content {
.stats-cards {
grid-template-columns: 1fr;
}
}
}
</style>
Loading…
Cancel
Save