fix: 权限列表JSON字段与角色可编辑; 前台site_id与SPA; 首页积木扩展区
Made-with: Cursor
This commit is contained in:
@@ -10,27 +10,28 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p class="tip">超级管理员(9527)拥有全部权限且不可修改。为其他角色勾选其可用的后台权限;可创建自定义角色并赋权。</p>
|
||||
<p class="tip">
|
||||
超级管理员(9527)拥有全部权限且不可改权限勾选(防误操作)。<strong>超级用户(0)、普通用户(1)</strong>可修改权限与显示名称;自定义角色可删除。
|
||||
</p>
|
||||
<el-table v-loading="loading" :data="list" border stripe>
|
||||
<el-table-column prop="role_name" label="角色" width="160">
|
||||
<el-table-column prop="role_name" label="角色" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input v-if="row.is_custom" v-model="row.role_name" size="small" placeholder="角色名" style="width: 120px" />
|
||||
<el-input v-if="row.role_id !== 9527" v-model="row.role_name" size="small" placeholder="显示名称" style="width: 160px" />
|
||||
<span v-else>{{ row.role_name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="role_id" label="role_id" width="100" />
|
||||
<el-table-column label="权限" min-width="400">
|
||||
<el-table-column label="权限" min-width="480">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.role_id === 9527" class="perm-all">(全部权限,不可修改)</span>
|
||||
<div v-else class="perm-checkboxes">
|
||||
<el-checkbox
|
||||
v-for="p in allPermissions"
|
||||
:key="p.key"
|
||||
v-model="row._checked[p.key]"
|
||||
style="margin-right: 16px; margin-bottom: 8px"
|
||||
>
|
||||
{{ p.name }}
|
||||
</el-checkbox>
|
||||
<div v-else class="perm-grid">
|
||||
<label v-for="p in allPermissions" :key="permKey(p)" class="perm-item">
|
||||
<el-checkbox v-model="row._checked[permKey(p)]" />
|
||||
<span class="perm-text">
|
||||
<span class="perm-name">{{ permLabel(p) }}</span>
|
||||
<span class="perm-key">{{ permKey(p) }}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -43,16 +44,21 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="showCreate" title="创建角色" width="500px">
|
||||
<el-dialog v-model="showCreate" title="创建角色" width="560px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="角色名称" required>
|
||||
<el-input v-model="createForm.role_name" placeholder="请输入角色名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限">
|
||||
<div class="perm-checkboxes">
|
||||
<el-checkbox v-for="p in allPermissions" :key="p.key" v-model="createForm._checked[p.key]">
|
||||
{{ p.name }}
|
||||
</el-checkbox>
|
||||
<p class="dialog-perm-hint">勾选该角色可访问的后台能力:</p>
|
||||
<div class="perm-grid dialog-perm-grid">
|
||||
<label v-for="p in allPermissions" :key="permKey(p)" class="perm-item">
|
||||
<el-checkbox v-model="createForm._checked[permKey(p)]" />
|
||||
<span class="perm-text">
|
||||
<span class="perm-name">{{ permLabel(p) }}</span>
|
||||
<span class="perm-key">{{ permKey(p) }}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -77,10 +83,20 @@ const showCreate = ref(false)
|
||||
const creating = ref(false)
|
||||
const createForm = reactive({ role_name: '', _checked: {} })
|
||||
|
||||
/** 兼容旧接口大写字段 Key/Name */
|
||||
function permKey(p) {
|
||||
return p?.key || p?.Key || ''
|
||||
}
|
||||
function permLabel(p) {
|
||||
const k = permKey(p)
|
||||
return p?.name || p?.Name || k || '权限'
|
||||
}
|
||||
|
||||
function buildChecked(permissions) {
|
||||
const o = {}
|
||||
allPermissions.value.forEach((p) => {
|
||||
o[p.key] = permissions.includes(p.key)
|
||||
const k = permKey(p)
|
||||
if (k) o[k] = (permissions || []).includes(k)
|
||||
})
|
||||
return o
|
||||
}
|
||||
@@ -106,9 +122,10 @@ const handleSave = async () => {
|
||||
try {
|
||||
for (const row of list.value) {
|
||||
if (row.role_id === 9527) continue
|
||||
const permissions = allPermissions.value.filter((p) => row._checked[p.key]).map((p) => p.key)
|
||||
const permissions = allPermissions.value.filter((p) => row._checked[permKey(p)]).map((p) => permKey(p))
|
||||
const payload = { permissions }
|
||||
if (row.is_custom && row.role_name) payload.role_name = row.role_name
|
||||
const name = (row.role_name || '').trim()
|
||||
if (name) payload.role_name = name
|
||||
await updateRolePermissions(row.role_id, payload)
|
||||
}
|
||||
ElMessage.success('保存成功')
|
||||
@@ -123,7 +140,8 @@ const resetCreateForm = () => {
|
||||
createForm.role_name = ''
|
||||
createForm._checked = {}
|
||||
allPermissions.value.forEach((p) => {
|
||||
createForm._checked[p.key] = false
|
||||
const k = permKey(p)
|
||||
if (k) createForm._checked[k] = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,7 +153,7 @@ const handleCreate = async () => {
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
const permissions = allPermissions.value.filter((p) => createForm._checked[p.key]).map((p) => p.key)
|
||||
const permissions = allPermissions.value.filter((p) => createForm._checked[permKey(p)]).map((p) => permKey(p))
|
||||
await createRole({ role_name: name, permissions })
|
||||
ElMessage.success('创建成功')
|
||||
showCreate.value = false
|
||||
@@ -176,9 +194,50 @@ onMounted(fetchList)
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.perm-checkboxes {
|
||||
.perm-all {
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
.perm-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.dialog-perm-grid {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.perm-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
max-width: 240px;
|
||||
}
|
||||
.perm-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.perm-name {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
}
|
||||
.perm-key {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
font-family: ui-monospace, monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
.dialog-perm-hint {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form v-if="siteId" ref="formRef" :model="form" label-width="120px" style="max-width: 720px">
|
||||
<el-form v-if="siteId" ref="formRef" :model="form" label-width="120px" class="homepage-form">
|
||||
<el-divider content-position="left">导航与标题</el-divider>
|
||||
<el-form-item label="Logo 文案">
|
||||
<el-input v-model="form.logo_text" placeholder="YUHENG ONE" />
|
||||
@@ -110,6 +110,14 @@
|
||||
<el-form-item label="页脚文案">
|
||||
<el-input v-model="form.footer_text" placeholder="© 2024 YUHENG ONE" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">首页下方扩展区(可视化积木,可拖拽排序)</el-divider>
|
||||
<p class="builder-tip">
|
||||
与「网页管理 → 积木」相同:从左侧手柄拖拽调整模块顺序。保存后内容显示在落地页主视觉与特性卡片<strong>之后</strong>、页脚之前。留空则不显示扩展区。
|
||||
</p>
|
||||
<el-form-item label="扩展积木" class="builder-form-item homepage-builder-wrap">
|
||||
<PageBuilderEditor v-model="form.body_builder" :site-id="siteId" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-empty v-else description="请先选择站点" />
|
||||
@@ -129,6 +137,7 @@ import { ElMessage } from 'element-plus'
|
||||
import { getSites, getOfficialSite, getHomepage, updateHomepage } from '../../api/admin'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import LinkPickerDialog from '../../components/LinkPickerDialog.vue'
|
||||
import PageBuilderEditor from '../../components/PageBuilderEditor.vue'
|
||||
|
||||
const siteId = ref('')
|
||||
const sites = ref([])
|
||||
@@ -162,7 +171,8 @@ const defaultForm = () => ({
|
||||
{ title: '量子同步', desc: '跨维度数据同步技术,您的数据在多宇宙中保持一致' },
|
||||
{ title: '星际防护', desc: '来自未来的安全加密协议,守护您的数字资产安全' }
|
||||
],
|
||||
footer_text: '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE'
|
||||
footer_text: '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE',
|
||||
body_builder: ''
|
||||
})
|
||||
|
||||
const form = reactive(defaultForm())
|
||||
@@ -284,4 +294,25 @@ onMounted(() => {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.homepage-form {
|
||||
max-width: 720px;
|
||||
}
|
||||
.builder-tip {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
margin: 0 0 12px;
|
||||
max-width: 900px;
|
||||
}
|
||||
.homepage-builder-wrap {
|
||||
max-width: 1000px;
|
||||
}
|
||||
.homepage-builder-wrap :deep(.el-form-item__content) {
|
||||
display: block;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.builder-form-item :deep(.el-form-item__content) {
|
||||
display: block;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,10 +53,18 @@
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="editId ? '编辑网页' : '新增网页'"
|
||||
:width="form.content_mode === 'builder' ? '960px' : '720px'"
|
||||
:width="form.content_mode === 'builder' ? '1080px' : '720px'"
|
||||
top="4vh"
|
||||
@close="resetForm"
|
||||
>
|
||||
<el-alert
|
||||
v-if="form.content_mode === 'builder'"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
title="积木模式:从模块左侧 ⋮⋮ 手柄拖拽排序;链接可点「选择链接」选站内页或文件。"
|
||||
/>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="Slug" prop="slug">
|
||||
<el-input v-model="form.slug" placeholder="如 about、index(index 为首页数据,一般不单独走路由)" :disabled="!!editId" />
|
||||
@@ -74,10 +82,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容模式">
|
||||
<el-select v-model="form.content_mode" placeholder="模式" style="width: 200px">
|
||||
<el-option label="HTML 富文本" value="html" />
|
||||
<el-option label="积木组装(可视化)" value="builder" />
|
||||
</el-select>
|
||||
<el-radio-group v-model="form.content_mode">
|
||||
<el-radio-button value="builder">积木(可视化拖拽)</el-radio-button>
|
||||
<el-radio-button value="html">HTML 源码</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" link style="margin-left: 12px" @click="insertBuilderTemplate">插入积木模板</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="发布到前台">
|
||||
@@ -86,7 +94,7 @@
|
||||
<el-form-item v-if="form.content_mode === 'html'" label="内容" prop="content">
|
||||
<el-input v-model="form.content" type="textarea" :rows="14" placeholder="直接编写 HTML" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="页面积木" class="builder-form-item">
|
||||
<el-form-item v-else label="页面积木" class="builder-form-item page-builder-wrap">
|
||||
<PageBuilderEditor v-model="form.content" :site-id="siteId" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -202,7 +210,7 @@ const form = reactive({
|
||||
title: '',
|
||||
type: 'page',
|
||||
content: '',
|
||||
content_mode: 'html',
|
||||
content_mode: 'builder',
|
||||
route_path: '',
|
||||
published: true
|
||||
})
|
||||
@@ -231,7 +239,7 @@ const openDialog = (row) => {
|
||||
form.title = row ? row.title : ''
|
||||
form.type = row ? row.type || 'page' : 'page'
|
||||
form.content = row ? row.content || '' : ''
|
||||
form.content_mode = row?.content_mode || 'html'
|
||||
form.content_mode = row?.content_mode || 'builder'
|
||||
form.route_path = row?.route_path || ''
|
||||
form.published = row?.published !== false
|
||||
dialogVisible.value = true
|
||||
@@ -242,7 +250,7 @@ const resetForm = () => {
|
||||
form.title = ''
|
||||
form.type = 'page'
|
||||
form.content = ''
|
||||
form.content_mode = 'html'
|
||||
form.content_mode = 'builder'
|
||||
form.route_path = ''
|
||||
form.published = true
|
||||
editId.value = ''
|
||||
@@ -303,4 +311,7 @@ onMounted(() => {
|
||||
display: block;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.page-builder-wrap {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -52,6 +52,11 @@
|
||||
1. 在 `web/src/components/blocks/BlockRenderer.vue` 增加 `type` 分支与样式。
|
||||
2. 后台仍通过 JSON 配置 `props`,无需改库表结构。
|
||||
|
||||
## 首页编辑:下方扩展积木
|
||||
|
||||
- 除原有导航、主视觉、特性等表单项外,可增加 **「首页下方扩展区」**:与网页积木相同 JSON,保存后由前台 `Home.vue` 在特性区之后、页脚之前用 **同一套 BlockRenderer** 动态渲染。
|
||||
- 下载的静态 `index.html` 目前**不包含**该积木区(仅在线 SPA 展示)。
|
||||
|
||||
## 首页编辑(导航 / 下载 / 平台链接)
|
||||
|
||||
- **管理后台 → 首页编辑与下载**:导航链接、下载按钮链接、各平台链接均可点 **「选择链接」**,与积木编辑器共用 `LinkPickerDialog`。
|
||||
|
||||
@@ -122,7 +122,8 @@ func UpdateRolePermissions(c *gin.Context) {
|
||||
coll := config.GetDB(config.DBName).Collection("role_permissions")
|
||||
filter := bson.M{"role_id": roleID}
|
||||
set := bson.M{"role_id": roleID, "permissions": input.Permissions}
|
||||
if input.RoleName != "" && roleID >= customRoleIDStart {
|
||||
// 超级管理员(9527)已拦截;其余预定义(0/1)与自定义角色均可更新显示名称
|
||||
if input.RoleName != "" {
|
||||
set["role_name"] = input.RoleName
|
||||
}
|
||||
update := bson.M{"$set": set}
|
||||
|
||||
@@ -14,21 +14,24 @@ const (
|
||||
PermRolePermission = "role:permission" // 角色权限管理
|
||||
)
|
||||
|
||||
// PermissionItem 单条权限定义(JSON 须用小写 key/name,供前端展示与勾选)
|
||||
type PermissionItem struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// AllPermissions 所有可配置权限(用于角色权限管理页)
|
||||
var AllPermissions = []struct {
|
||||
Key string
|
||||
Name string
|
||||
}{
|
||||
{PermSiteManage, "站点管理"},
|
||||
{PermHomepageEdit, "首页编辑"},
|
||||
{PermPageManage, "网页管理"},
|
||||
{PermModuleUpload, "功能模块上传"},
|
||||
{PermUserManage, "用户管理"},
|
||||
{PermWorkspaceManage, "工作空间"},
|
||||
{PermConversationManage, "对话管理"},
|
||||
{PermSMSConfig, "短信配置"},
|
||||
{PermPaymentConfig, "支付配置"},
|
||||
{PermRolePermission, "角色权限管理"},
|
||||
var AllPermissions = []PermissionItem{
|
||||
{Key: PermSiteManage, Name: "站点管理"},
|
||||
{Key: PermHomepageEdit, Name: "首页编辑"},
|
||||
{Key: PermPageManage, Name: "网页管理"},
|
||||
{Key: PermModuleUpload, Name: "功能模块上传"},
|
||||
{Key: PermUserManage, Name: "用户管理"},
|
||||
{Key: PermWorkspaceManage, Name: "工作空间"},
|
||||
{Key: PermConversationManage, Name: "对话管理"},
|
||||
{Key: PermSMSConfig, Name: "短信配置"},
|
||||
{Key: PermPaymentConfig, Name: "支付配置"},
|
||||
{Key: PermRolePermission, Name: "角色权限管理"},
|
||||
}
|
||||
|
||||
// RolePermissionsDoc MongoDB 文档:角色 ID -> 名称与权限列表(支持自定义角色)
|
||||
|
||||
@@ -40,6 +40,8 @@ type HomepageData struct {
|
||||
BadgeText string `json:"badge_text"` // FREE ACCESS
|
||||
Features []FeatureItem `json:"features"` // 星际导航等
|
||||
FooterText string `json:"footer_text"` // © 2024 YUHENG ONE
|
||||
// BodyBuilder 首页下方扩展区:与网页积木相同 JSON 字符串 {"version":1,"blocks":[...]},空则仅展示上方模板
|
||||
BodyBuilder string `json:"body_builder,omitempty"`
|
||||
}
|
||||
|
||||
type NavLink struct {
|
||||
|
||||
@@ -2,14 +2,32 @@ import { apiBase } from '../config'
|
||||
|
||||
const prefix = () => (apiBase ? `${apiBase}/api` : '/api')
|
||||
|
||||
/** 与 /web/routes 返回的 site_id 一致,拉取单页时附带,避免多站点下错站 */
|
||||
let cachedWebSiteId = ''
|
||||
|
||||
export function getCachedWebSiteId() {
|
||||
return cachedWebSiteId
|
||||
}
|
||||
|
||||
export async function fetchWebRoutes() {
|
||||
const res = await fetch(`${prefix()}/web/routes`)
|
||||
if (!res.ok) return { site_id: '', routes: [] }
|
||||
return res.json()
|
||||
let url = `${prefix()}/web/routes`
|
||||
const sid = new URLSearchParams(window.location.search).get('site_id')
|
||||
if (sid) url += `?site_id=${encodeURIComponent(sid)}`
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) {
|
||||
cachedWebSiteId = ''
|
||||
return { site_id: '', routes: [] }
|
||||
}
|
||||
const data = await res.json()
|
||||
cachedWebSiteId = data.site_id || ''
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchWebPageByPath(path) {
|
||||
const q = new URLSearchParams({ path: path || '/' })
|
||||
const fromQs = new URLSearchParams(window.location.search).get('site_id')
|
||||
const sid = cachedWebSiteId || fromQs
|
||||
if (sid) q.set('site_id', sid)
|
||||
const res = await fetch(`${prefix()}/web/page?${q}`)
|
||||
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error || '加载失败')
|
||||
return res.json()
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="bodyBuilderBlocks.length" class="home-body-builder">
|
||||
<BlockRenderer :blocks="bodyBuilderBlocks" />
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>{{ data.footer_text || '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE' }}</p>
|
||||
<p class="beian">成都宇惠达智能科技有限公司 <a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">蜀ICP备2025134957号-1</a></p>
|
||||
@@ -58,6 +62,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { apiBase } from '../config'
|
||||
import BlockRenderer from '../components/blocks/BlockRenderer.vue'
|
||||
|
||||
const starsEl = ref(null)
|
||||
let cometTimer = null
|
||||
@@ -85,7 +90,8 @@ const defaultData = () => ({
|
||||
{ title: '量子同步', desc: '跨维度数据同步技术,您的数据在多宇宙中保持一致' },
|
||||
{ title: '星际防护', desc: '来自未来的安全加密协议,守护您的数字资产安全' }
|
||||
],
|
||||
footer_text: '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE'
|
||||
footer_text: '© 2024 YUHENG ONE // STELLAR EXPLORATION INITIATIVE',
|
||||
body_builder: ''
|
||||
})
|
||||
|
||||
const data = reactive(defaultData())
|
||||
@@ -100,6 +106,18 @@ const descriptionHtml = computed(() => {
|
||||
return s.replace(/\n/g, '<br>')
|
||||
})
|
||||
|
||||
/** 首页扩展区:与后台「页面积木」相同 JSON */
|
||||
const bodyBuilderBlocks = computed(() => {
|
||||
const raw = data.body_builder
|
||||
if (!raw || typeof raw !== 'string') return []
|
||||
try {
|
||||
const j = JSON.parse(raw)
|
||||
return Array.isArray(j.blocks) ? j.blocks : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const featureIconPaths = [
|
||||
'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z',
|
||||
'M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4s1.79-4 4-4h.71C7.37 7.69 9.48 6 12 6c3.04 0 5.5 2.46 5.5 5.5v.5H19c1.66 0 3 1.34 3 3s-1.34 3-3 3z',
|
||||
@@ -182,6 +200,13 @@ onUnmounted(() => {
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.home-body-builder {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 24px 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({
|
||||
appType: 'spa',
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 3001,
|
||||
|
||||
Reference in New Issue
Block a user