javacodeadmin/ruoyi-ui/src/views/system/SiteConfig/index.vue

2873 lines
97 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="sysseting-container">
<el-tabs v-model="activeTab">
<el-tab-pane label="基本信息" name="base">
<el-form :model="baseForm" label-width="120px" class="tab-form">
<el-form-item label="投诉电话">
<el-input v-model="baseForm.phone" placeholder="请输入投诉电话" style="width: 300px" />
</el-form-item>
<el-form-item label="抢单开始时间">
<el-time-picker v-model="baseForm.startTime" placeholder="选择时间" format="HH:mm" value-format="HH:mm" style="width: 150px" />
</el-form-item>
<el-form-item label="抢单结束时间">
<el-time-picker v-model="baseForm.endTime" placeholder="选择时间" format="HH:mm" value-format="HH:mm" style="width: 150px" />
</el-form-item>
<el-form-item label="秒杀结束时间">
<el-date-picker
v-model="baseForm.mstime"
type="datetime"
placeholder="选择秒杀结束时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 300px"
/>
</el-form-item>
<el-form-item label="质保金扣除比例">
<el-input-number v-model="baseForm.marginRate" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
</el-form-item>
<el-form-item label="消费金">
<el-input-number v-model="baseForm.consumption" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="用户支付服务费用后根据用户的实际支付换算得到的消费金比例" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="服务金">
<el-input-number v-model="baseForm.servicefee" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="用户支付商品费用后根据用户的实际支付换算得到的服务金比例" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="消费金抵扣比例">
<el-input-number v-model="baseForm.consumption_deduction" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="用户支付订单时可用消费金抵扣的最大比例" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="服务金抵扣比例">
<el-input-number v-model="baseForm.service_deduction" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="用户支付订单时可用服务金抵扣的最大比例" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="物料分佣">
<el-input-number v-model="baseForm.material_commissions" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="平台统一设置,师傅使用物料后师傅所得物料分佣的比例" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="会员优惠">
<el-input-number v-model="baseForm.member_discount" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="包年会员在平台消费进行的折扣" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="充值附送比例">
<el-input-number v-model="baseForm.recharge_discount" :min="0" :max="100" />
<span style="margin-left: 8px">%</span>
<el-tooltip content="用户充值时按照用户充值金额的比例进行赠送" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="下单送积分">
<el-input v-model="baseForm.orderScore" style="width: 120px" />
<el-tooltip content="下单支付金额多少元赠送1积分不填则不赠送" placement="right">
<i class="el-icon-question" style="margin-left: 8px; color: #999" />
</el-tooltip>
</el-form-item>
<el-form-item label="搜索热词">
<el-select v-model="baseForm.hotwords" multiple filterable allow-create default-first-option placeholder="请输入热词" style="width: 600px">
<el-option v-for="item in baseForm.hotwords" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="客服二维码">
<el-upload
class="avatar-uploader"
action="#"
:show-file-list="false"
:on-change="handleQrChange"
:before-upload="beforeQrUpload"
>
<img v-if="baseForm.qrUrl" :src="baseForm.qrUrl" class="qr-img" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('1')">提交</el-button>
<el-button @click="resetBase">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="文本配置" name="text">
<el-form :model="textForm" label-width="120px" class="tab-form">
<el-form-item label="公司名称">
<el-input v-model="textForm.company" placeholder="请输入公司名称" />
</el-form-item>
<el-form-item label="简介">
<el-input type="textarea" v-model="textForm.intro" :rows="4" placeholder="请输入公司简介" />
</el-form-item>
<el-form-item label="质保金说明">
<Editor v-model="textForm.marginDesc" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('2')">提交</el-button>
<el-button @click="resetText">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="下单时间配置" name="orderTime">
<el-form label-width="120px" class="tab-form">
<el-form-item label="下单时间">
<div v-for="(item, idx) in orderTimes" :key="idx" style="display: flex; align-items: center; margin-bottom: 8px;">
<el-time-picker
v-model="item.start"
placeholder="开始时间"
format="HH:mm"
value-format="HH:mm"
style="width: 130px; margin-right: 8px;"
@change="updateTimeRange(idx)"
/>
<span style="margin: 0 4px;">-</span>
<el-time-picker
v-model="item.end"
placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
style="width: 130px; margin-right: 12px;"
@change="updateTimeRange(idx)"
/>
<el-input-number v-model="item.count" :min="0" style="width:25%; margin-right: 12px;" />
<el-button icon="el-icon-delete" type="danger" @click="removeOrderTime(idx)" circle />
</div>
<el-button type="primary" icon="el-icon-plus" @click="addOrderTime">新增</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('3')">提交</el-button>
<el-button @click="resetOrderTime">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="时间配置" name="time">
<el-form :model="timeForm" label-width="120px" class="tab-form">
<el-form-item label="每月提现时间">
<el-select v-model="timeForm.withdrawDays" multiple placeholder="请选择日期" style="width: 300px">
<el-option v-for="d in 31" :key="d" :label="d + '号'" :value="d + '号'" />
</el-select>
</el-form-item>
<el-form-item label="定时接单时长">
<el-input-number v-model="timeForm.autoOrderMinutes" :min="1" style="width: 120px" />
<span style="margin-left: 8px">分钟</span>
<div class="el-form-item__tip">订单无人接单时多少分钟后,自动重新派单。单位(分钟)</div>
</el-form-item>
<el-form-item label="取消订单时长">
<el-input-number v-model="timeForm.cancelOrderDays" :min="1" style="width: 120px" />
<span style="margin-left: 8px">天</span>
<div class="el-form-item__tip">师傅到达之后,多久之后没有报工则取消订单。单位(天)</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('4')">提交</el-button>
<el-button @click="resetTime">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="会员配置" name="config_five">
<el-form :model="memberForm" label-width="120px" class="tab-form">
<el-form-item label="会员权益说明">
<Editor v-model="memberForm.member" />
</el-form-item>
<el-form-item label="会员规则说明">
<Editor v-model="memberForm.memberRule" />
</el-form-item>
<el-form-item label="充值规则说明">
<Editor v-model="memberForm.recharge" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('5')">提交</el-button>
<el-button @click="resetMember">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="评价标签" name="config_six">
<el-form :model="ratingForm" label-width="120px" class="tab-form">
<el-form-item label="服务评价标签">
<el-select v-model="ratingForm.service" multiple filterable allow-create default-first-option placeholder="请输入服务评价标签" style="width: 600px">
<el-option v-for="item in ratingForm.service" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="商品评价标签">
<el-select v-model="ratingForm.goods" multiple filterable allow-create default-first-option placeholder="请输入商品评价标签" style="width: 600px">
<el-option v-for="item in ratingForm.goods" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('6')">提交</el-button>
<el-button @click="resetRating">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="通知公告" name="config_banner">
<div class="notice-container">
<!-- 页面头部操作区 -->
<div class="notice-header">
<div class="notice-title">
<h3>通知公告管理</h3>
<p class="notice-desc">管理首页展示的通知公告,支持富文本编辑和状态控制</p>
</div>
<div class="notice-header-actions">
<div class="notice-filter">
<el-select v-model="noticeFilter" placeholder="筛选分类" clearable size="small" style="width: 150px; margin-right: 12px;">
<el-option label="全部公告" value=""></el-option>
<el-option label="首页通知公告" value="home"></el-option>
<el-option label="活动通知公告" value="activity"></el-option>
</el-select>
</div>
<el-button type="primary" icon="el-icon-plus" @click="addNotice">添加公告</el-button>
<el-button type="success" icon="el-icon-check" @click="saveAllConfig('notice')">保存全部</el-button>
<el-button icon="el-icon-refresh" @click="resetNotice">重置</el-button>
</div>
</div>
<!-- 公告列表 -->
<div class="notice-list">
<div v-if="filteredNoticeList.length === 0" class="notice-empty">
<el-empty description="暂无公告数据">
<el-button type="primary" @click="addNotice">添加第一条公告</el-button>
</el-empty>
</div>
<div v-for="(item, index) in filteredNoticeList" :key="index" class="notice-item">
<el-card shadow="hover" :class="['notice-card', { 'notice-offline': item.status === 0, 'notice-expanded': item.expanded }]">
<!-- 简洁行显示 -->
<div class="notice-simple-row" @click="toggleNoticeExpand(item.originalIndex)">
<div class="notice-simple-left">
<span class="notice-index">{{ item.originalIndex + 1 }}</span>
<el-tag
:type="getCategoryTagType(item.category)"
size="mini"
class="notice-category-tag-left">
{{ getCategoryName(item.category) }}
</el-tag>
<div class="notice-simple-info">
<span class="notice-simple-title">{{ item.title || '未设置标题' }}</span>
<span class="notice-simple-meta">排序: {{ item.sort || 0 }}</span>
</div>
</div>
<div class="notice-simple-right">
<el-tag :type="noticeForm.notice[item.originalIndex].status === 1 ? 'success' : 'danger'" size="small">
{{ noticeForm.notice[item.originalIndex].status === 1 ? '已上线' : '已下线' }}
</el-tag>
<el-switch
v-model="noticeForm.notice[item.originalIndex].status"
:active-value="1"
:inactive-value="0"
size="mini"
active-color="#67C23A"
inactive-color="#F56C6C"
@change="handleStatusChange(item.originalIndex)"
@click.native.stop
style="margin: 0 12px;">
</el-switch>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
circle
@click.stop="removeNotice(item.originalIndex)">
</el-button>
<i
:class="item.expanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
class="notice-expand-icon">
</i>
</div>
</div>
<!-- 展开的编辑内容 -->
<div v-show="item.expanded" class="notice-card-content">
<el-divider style="margin: 16px 0;"></el-divider>
<el-form label-width="80px" label-position="top">
<div class="notice-form-grid">
<!-- 第一行:标题 -->
<div class="notice-form-row full-width">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="公告标题" class="notice-title-item">
<el-input
v-model="item.title"
placeholder="请输入公告标题"
maxlength="100"
show-word-limit
clearable>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="公告分类" class="notice-category-item">
<el-select
v-model="item.category"
placeholder="请选择公告分类"
@change="val => handleCategoryChange(val, item, item.originalIndex)">
<el-option label="首页通知公告" value="home">
<span style="display: flex; align-items: center;">
<i class="el-icon-house" style="color: #409EFF; margin-right: 8px;"></i>
首页通知公告
</span>
</el-option>
<el-option label="活动通知公告" value="activity">
<span style="display: flex; align-items: center;">
<i class="el-icon-star-on" style="color: #E6A23C; margin-right: 8px;"></i>
活动通知公告
</span>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="排序" class="notice-sort-item">
<el-input-number
v-model="item.sort"
:min="0"
:max="999"
controls-position="right"
placeholder="排序值">
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="跳转链接" class="notice-link-item">
<el-select
v-model="item.link"
placeholder="请选择站内地址"
filterable
clearable
style="width: 100%;">
<el-option
v-for="(link, idx) in siteLinks"
:key="idx"
:label="link.name + (link.remark ? '' + link.remark + '' : '')"
:value="link.url"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 第二行:内容 -->
<div class="notice-form-row full-width">
<el-form-item label="公告内容" class="notice-content-item">
<Editor v-model="item.content" />
</el-form-item>
</div>
</div>
</el-form>
<!-- 编辑区域底部操作按钮 -->
<div class="notice-edit-actions">
<el-button type="primary" size="small" @click="saveNoticeItem(item.originalIndex)">
<i class="el-icon-check"></i> 保存
</el-button>
<el-button size="small" @click="toggleNoticeExpand(item.originalIndex)">
<i class="el-icon-close"></i> 收起
</el-button>
</div>
</div>
</el-card>
</div>
</div>
<!-- 底部操作区 -->
<div class="notice-footer" v-if="noticeForm.notice.length > 0">
<el-divider></el-divider>
<div class="notice-footer-actions">
<el-button type="primary" icon="el-icon-plus" @click="addNotice">添加公告</el-button>
<el-button type="success" icon="el-icon-check" @click="saveAllConfig('notice')">保存全部</el-button>
<el-button icon="el-icon-refresh" @click="resetNotice">重置</el-button>
</div>
<div class="notice-footer-tips">
<el-alert
title="温馨提示"
type="info"
:closable="false"
show-icon>
<div slot="description">
<p>• 只有状态为"上线"的公告才会在前端显示</p>
<p>• 公告按排序值升序排列,数值越小越靠前</p>
<p>• 保存后需要前端重新请求接口才能看到最新内容</p>
</div>
</el-alert>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="活动专区维护" name="activity_zone">
<div class="activity-zone-container">
<div class="activity-header">
<el-button type="primary" icon="el-icon-plus" @click="addActivity">添加活动</el-button>
<el-button type="success" icon="el-icon-check" @click="saveAllConfig('activity')">保存全部</el-button>
<el-button icon="el-icon-refresh" @click="resetActivity">重置</el-button>
</div>
<div v-if="activityForm.activities.length === 0" class="activity-empty">
<el-empty description="暂无活动数据">
<el-button type="primary" @click="addActivity">添加第一条活动</el-button>
</el-empty>
</div>
<div v-for="(item, index) in activityForm.activities" :key="index" class="activity-item">
<el-card shadow="hover" :class="['activity-card', { 'activity-offline': item.status === 0, 'activity-expanded': item.expanded }]">
<!-- 简洁行显示 -->
<div class="activity-simple-row" @click="toggleActivityExpand(index)">
<div class="activity-simple-left">
<span class="activity-index">{{ index + 1 }}</span>
<div class="activity-simple-info">
<span class="activity-simple-title">{{ item.title || '未设置标题' }}</span>
<span class="activity-simple-content">{{ item.content ? (item.content.length > 20 ? item.content.slice(0, 20) + '...' : item.content) : '无内容' }}</span>
<span v-if="item.link" class="activity-simple-link">链接: {{ item.link.length > 30 ? item.link.slice(0, 30) + '...' : item.link }}</span>
<span class="activity-simple-position">显示位置: {{ item.position || 0 }}</span>
</div>
<img v-if="item.imgUrl" :src="item.imgUrl" class="activity-thumb" />
</div>
<div class="activity-simple-right">
<el-tag :type="item.status === 1 ? 'success' : 'danger'" size="small">
{{ item.status === 1 ? '已上线' : '已下线' }}
</el-tag>
<el-switch
v-model="item.status"
:active-value="1"
:inactive-value="0"
size="mini"
active-color="#67C23A"
inactive-color="#F56C6C"
@change="handleActivityStatusChange(index)"
@click.native.stop
style="margin: 0 12px;">
</el-switch>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
circle
@click.stop="removeActivity(index)">
</el-button>
<i
:class="item.expanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
class="activity-expand-icon">
</i>
</div>
</div>
<!-- 展开编辑内容 -->
<div v-show="item.expanded" class="activity-card-content">
<el-divider style="margin: 16px 0;"></el-divider>
<el-form label-width="80px" label-position="top">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="活动标题">
<el-input v-model="item.title" placeholder="请输入活动标题" maxlength="100" show-word-limit clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="活动链接">
<selecturl v-model="item.link"></selecturl>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="显示位置">
<el-input-number
v-model="item.position"
:min="0"
:max="999"
controls-position="right"
placeholder="显示位置(数字越小越靠前)"
style="width: 100%;">
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态">
<el-switch v-model="item.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动图片">
<el-upload
:ref="'activityUpload' + index"
:action="uploadFileUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res, file) => handleActivityImgSuccess(res, file, index)"
:on-error="(err, file) => handleActivityImgError(err, file, index)"
:before-upload="file => beforeActivityImgUpload(file, index)"
:key="'upload_' + index + '_' + (item.imgUrl ? 'has' : 'no')"
class="activity-img-uploader"
name="file">
<div style="position:relative;display:inline-block;">
<img v-if="item.imgUrl" :src="item.imgUrl" class="activity-img" style="cursor:pointer" title="点击替换图片" />
<el-button v-if="item.imgUrl" class="activity-img-delete-btn" type="danger" icon="el-icon-close" circle size="mini" @click.stop="removeActivityImg(index)" style="position:absolute;top:2px;right:2px;padding:0;width:22px;height:22px;" title="删除图片"></el-button>
<i v-else class="el-icon-plus activity-uploader-icon" style="cursor:pointer" title="点击上传图片" />
</div>
</el-upload>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动内容">
<el-input v-model="item.content" type="textarea" :rows="4" placeholder="请输入活动内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="activity-edit-actions">
<el-button type="primary" size="small" @click="saveActivityItem(index)">
<i class="el-icon-check"></i> 保存
</el-button>
<el-button size="small" @click="toggleActivityExpand(index)">
<i class="el-icon-close"></i> 收起
</el-button>
</div>
</div>
</el-card>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="站内地址" name="siteLinks">
<el-form label-width="100px" class="tab-form">
<div v-for="(item, idx) in siteLinks" :key="idx" style="display: flex; align-items: center; margin-bottom: 8px;">
<el-input v-model="item.name" placeholder="名称" style="width: 120px; margin-right: 8px;" />
<el-input v-model="item.url" placeholder="地址" style="width: 220px; margin-right: 8px;" />
<!-- <el-select v-model="item.params" placeholder="选择类型">-->
<!-- <el-option value="1" label="普通链接"></el-option>-->
<!-- <el-option value="2" label="服务链接"></el-option>-->
<!-- <el-option value="3" label="商品链接"></el-option>-->
<!-- </el-select>-->
<!-- <el-input v-model="item.params" placeholder="参数" style="width: 160px; margin-right: 8px;" />-->
<el-input v-model="item.remark" placeholder="备注" style="width: 160px; margin-right: 8px;" />
<el-button icon="el-icon-delete" type="danger" @click="removeSiteLink(idx)" circle />
</div>
<el-button type="primary" icon="el-icon-plus" @click="addSiteLink">新增</el-button>
<el-button type="success" @click="saveSiteLinks" style="margin-left: 12px;">保存</el-button>
</el-form>
</el-tab-pane>
<el-tab-pane label="上门标准" name="serviceStandard">
<el-form :model="serviceStandardForm" label-width="120px" class="tab-form">
<el-form-item label="上门标准说明">
<Editor v-model="serviceStandardForm.serviceStandard" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveAllConfig('serviceStandard')">提交</el-button>
<el-button @click="resetServiceStandard">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="首页弹窗维护" name="popupConfig">
<div class="popup-container">
<div class="popup-header">
<el-button type="primary" icon="el-icon-plus" @click="addPopup">添加弹窗</el-button>
<el-button type="success" icon="el-icon-check" @click="saveAllConfig('popup')">保存全部</el-button>
<el-button icon="el-icon-refresh" @click="resetPopup">重置</el-button>
</div>
<div v-if="popupForm.popups.length === 0" class="popup-empty">
<el-empty description="暂无弹窗数据">
<el-button type="primary" @click="addPopup">添加第一条弹窗</el-button>
</el-empty>
</div>
<div v-for="(item, index) in popupForm.popups" :key="index" class="popup-item">
<el-card shadow="hover" :class="['popup-card', { 'popup-offline': item.status === 0, 'popup-expanded': item.expanded }]">
<!-- 简洁行显示 -->
<div class="popup-simple-row" @click="togglePopupExpand(index)">
<div class="popup-simple-left">
<span class="popup-index">{{ index + 1 }}</span>
<div class="popup-simple-info">
<span v-if="item.link" class="popup-simple-link">链接: {{ item.link.length > 30 ? item.link.slice(0, 30) + '...' : item.link }}</span>
<span class="popup-simple-position">排序: {{ item.sort || 0 }}</span>
</div>
<img v-if="item.imgUrl" :src="item.imgUrl" class="popup-thumb" />
</div>
<div class="popup-simple-right">
<el-tag :type="item.status === 1 ? 'success' : 'danger'" size="small">
{{ item.status === 1 ? '已上线' : '已下线' }}
</el-tag>
<el-switch
v-model="item.status"
:active-value="1"
:inactive-value="0"
size="mini"
active-color="#67C23A"
inactive-color="#F56C6C"
@change="handlePopupStatusChange(index)"
@click.native.stop
style="margin: 0 12px;">
</el-switch>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
circle
@click.stop="removePopup(index)">
</el-button>
<i
:class="item.expanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
class="popup-expand-icon">
</i>
</div>
</div>
<!-- 展开编辑内容 -->
<div v-show="item.expanded" class="popup-card-content">
<el-divider style="margin: 16px 0;"></el-divider>
<el-form label-width="80px" label-position="top">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="跳转链接">
<selecturl v-model="item.link"></selecturl>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="排序">
<el-input-number
v-model="item.sort"
:min="0"
:max="999"
controls-position="right"
placeholder="排序值(数字越小越靠前)"
style="width: 100%;">
</el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态">
<el-switch v-model="item.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="弹窗图片">
<el-upload
:ref="'popupUpload' + index"
:action="uploadFileUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res, file) => handlePopupImgSuccess(res, file, index)"
:on-error="(err, file) => handlePopupImgError(err, file, index)"
:before-upload="file => beforePopupImgUpload(file, index)"
:key="'upload_' + index + '_' + (item.imgUrl ? 'has' : 'no')"
class="popup-img-uploader"
name="file">
<div style="position:relative;display:inline-block;">
<img v-if="item.imgUrl" :src="item.imgUrl" class="popup-img" style="cursor:pointer" title="点击替换图片" />
<el-button v-if="item.imgUrl" class="popup-img-delete-btn" type="danger" icon="el-icon-close" circle size="mini" @click.stop="removePopupImg(index)" style="position:absolute;top:2px;right:2px;padding:0;width:22px;height:22px;" title="删除图片"></el-button>
<i v-else class="el-icon-plus popup-uploader-icon" style="cursor:pointer" title="点击上传图片" />
</div>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="popup-edit-actions">
<el-button type="primary" size="small" @click="savePopupItem(index)">
<i class="el-icon-check"></i> 保存
</el-button>
<el-button size="small" @click="togglePopupExpand(index)">
<i class="el-icon-close"></i> 收起
</el-button>
</div>
</div>
</el-card>
</div>
</div>
</el-tab-pane>
</el-tabs>
<el-button type="primary" style="margin-top: 24px;" @click="saveAllConfig">保存全部配置</el-button>
</div>
</template>
<script>
import { listSiteConfig, getSiteConfig, delSiteConfig, addSiteConfig, updateSiteConfig } from "@/api/system/SiteConfig"
import { getToken } from "@/utils/auth"
import selecturl from "./selecturl.vue"
export default {
name: 'SysSeting',
components: {
selecturl
},
data() {
return {
// 遮罩层
loading: true,
activeTab: 'base',
SiteConfigList: [],
config_one: {},
config_two: {},
config_three: {},
config_four: {},
config_five: {},
config_six: {},
total: 0, // 系统配置表格数据
token: getToken(), // 获取token用于上传
// 上传配置
uploadFileUrl:process.env.NODE_ENV === 'development'?"/dev-api/common/upload":"/prod-api/common/upload",
uploadHeaders: {
Authorization: "Bearer " + getToken(),
},
queryParams: {
pageNum: 1,
pageSize: 30,
name: null,
status: null
},
// 基本信息
baseForm: {
phone: '',
startTime: '',
endTime: '',
mstime: '',
marginRate: 10,
consumption: 0,
servicefee: 0,
material_commissions: 10,
member_discount: 80,
recharge_discount: 0,
consumption_deduction: 0,
service_deduction: 0,
orderScore: 100,
hotwords: ['水电维修', '家电清洗', '灯具维修', '墙面翻新', '门窗家具', '疏通维修', '防水维修'],
qrUrl: ''
},
// 文本配置
textForm: {
company: '',
intro: '',
marginDesc: ''
},
memberForm:{
member: '',
memberRule: '',
recharge: '' // 新增充值规则说明
},
// 评价标签配置
ratingForm: {
service: ['服务态度好', '技术专业', '准时守信', '认真负责', '价格合理'],
goods: ['商品质量好', '包装完整', '物流快速', '性价比高', '描述相符']
},
// 下单时间配置
orderTimes: [
],
// 时间配置
timeForm: {
withdrawDays: ['8号', '18号', '28号'],
autoOrderMinutes: 10,
cancelOrderDays: 7
},
config_seven: {},
noticeForm: {
notice: []
},
// 通知公告筛选
noticeFilter: '',
activityForm: {
activities: []
},
config_eight: {},
config_nine: {}, // 新增站内地址配置
siteLinks: [], // 站内地址数据
config_ten: {}, // 上门标准配置
serviceStandardForm: {
serviceStandard: ''
},
config_eleven: {}, // 首页弹窗配置
popupForm: {
popups: []
},
}
},
computed: {
// 过滤后的公告列表
filteredNoticeList() {
let filtered = this.noticeForm.notice;
// 根据分类筛选
if (this.noticeFilter) {
filtered = filtered.filter(item => item.category === this.noticeFilter);
}
// 添加原始索引,用于在过滤后仍能正确操作原数组
return filtered.map((item, index) => {
const originalIndex = this.noticeForm.notice.findIndex(notice => notice === item);
return {
...item,
originalIndex: originalIndex
};
});
}
},
created() {
this.getList()
},
methods: {
/** 查询系统配置列表 */
getList() {
this.loading = true
listSiteConfig(this.queryParams).then(response => {
this.SiteConfigList = response.rows
// config_one 基本信息
this.config_one = response.rows.find(item => item.name === 'config_one')
if (this.config_one && this.config_one.value) {
const configOneObj = JSON.parse(this.config_one.value)
this.baseForm = {
phone: configOneObj.phone || '',
startTime: configOneObj.loot_start || '',
endTime: configOneObj.loot_end || '',
mstime: configOneObj.mstime || '',
marginRate: configOneObj.margin || 10,
consumption: configOneObj.consumption || 0,
servicefee: configOneObj.servicefee || 0,
material_commissions: configOneObj.material_commissions || 10,
member_discount: configOneObj.member_discount || 80,
recharge_discount: configOneObj.recharge_discount || 0,
consumption_deduction: configOneObj.consumption_deduction || 0,
service_deduction: configOneObj.service_deduction || 0,
orderScore: configOneObj.orderScore || 100,
hotwords: configOneObj.hot || [],
qrUrl: configOneObj.kf || '',
}
}
// config_two 文本配置
this.config_two = response.rows.find(item => item.name === 'config_two')
if (this.config_two && this.config_two.value) {
const configTwoObj = JSON.parse(this.config_two.value)
this.textForm = {
company: configTwoObj.name || '',
intro: configTwoObj.brief || '',
marginDesc: configTwoObj.money_explain || ''
}
}
// config_five 会员配置
this.config_five = response.rows.find(item => item.name === 'config_five')
if (this.config_five && this.config_five.value) {
const configfiveObj = JSON.parse(this.config_five.value)
this.memberForm = {
member: configfiveObj.member || '',
memberRule: configfiveObj.memberRule || '',
recharge: configfiveObj.recharge || ''
}
}
// config_six 评价标签配置
this.config_six = response.rows.find(item => item.name === 'config_six')
if (this.config_six && this.config_six.value) {
const configSixObj = JSON.parse(this.config_six.value)
this.ratingForm = {
service: configSixObj.service || ['服务态度好', '技术专业', '准时守信', '认真负责', '价格合理'],
goods: configSixObj.goods || ['商品质量好', '包装完整', '物流快速', '性价比高', '描述相符']
}
}
// config_three 下单时间配置
this.config_three = response.rows.find(item => item.name === 'config_three')
this.orderTimes = []
if (this.config_three && this.config_three.value) {
let _data = JSON.parse(this.config_three.value)
if (_data && _data.time) {
for (let key in _data.time) {
const [start, end] = _data.time[key].key.split('-');
this.orderTimes.push({
start: start || '',
end: end || '',
count: _data.time[key].num || 99
})
}
}
}
// config_four 时间配置
this.config_four = response.rows.find(item => item.name === 'config_four')
if (this.config_four && this.config_four.value) {
const configFourObj = JSON.parse(this.config_four.value)
this.timeForm = {
withdrawDays: configFourObj.time || [],
autoOrderMinutes: configFourObj.order_time || 10,
cancelOrderDays: configFourObj.remove_time || 7
}
}
// config_seven 通知公告配置
this.config_seven = response.rows.find(item => item.name === 'config_seven')
if (this.config_seven && this.config_seven.value) {
const configNoticeObj = JSON.parse(this.config_seven.value)
this.noticeForm.notice = (configNoticeObj.notice || []).map(item => ({
...item,
category: item.category || 'home', // 兼容旧数据,默认为首页通知公告
expanded: false // 从后端加载的数据默认不展开
}))
}
// config_eight 活动专区配置
this.config_eight = response.rows.find(item => item.name === 'config_eight')
if (this.config_eight && this.config_eight.value) {
const configEightObj = JSON.parse(this.config_eight.value)
this.activityForm.activities = (configEightObj.activities || []).map(item => ({
...item,
status: typeof item.status === 'undefined' ? 1 : item.status,
link: item.link || '', // 兼容旧数据,默认为空字符串
position: typeof item.position === 'undefined' ? 0 : item.position, // 兼容旧数据默认显示位置为0
expanded: false // 从后端加载的数据默认不展开
}))
}
// config_nine 站内地址配置
this.config_nine = response.rows.find(item => item.name === 'config_nine')
if (this.config_nine && this.config_nine.value) {
try {
this.siteLinks = JSON.parse(this.config_nine.value).siteLinks || [];
} catch (e) {
this.siteLinks = [];
}
} else {
this.siteLinks = [];
}
// config_ten 上门标准配置
this.config_ten = response.rows.find(item => item.name === 'config_ten')
if (this.config_ten && this.config_ten.value) {
try {
const configTenObj = JSON.parse(this.config_ten.value)
this.serviceStandardForm = {
serviceStandard: configTenObj.serviceStandard || ''
}
} catch (e) {
this.serviceStandardForm = { serviceStandard: '' }
}
} else {
this.serviceStandardForm = { serviceStandard: '' }
}
// config_eleven 首页弹窗配置
this.config_eleven = response.rows.find(item => item.name === 'config_shiyi')
if (this.config_eleven && this.config_eleven.value) {
try {
const configElevenObj = JSON.parse(this.config_eleven.value)
this.popupForm.popups = (configElevenObj.popups || []).map(item => ({
...item,
status: typeof item.status === 'undefined' ? 1 : item.status,
link: item.link || '',
sort: typeof item.sort === 'undefined' ? 0 : item.sort,
expanded: false
}))
} catch (e) {
console.error('解析弹窗配置失败:', e);
this.popupForm.popups = []
}
} else {
this.popupForm.popups = []
}
this.total = response.total
this.loading = false
})
},
// 基本信息
submitBase() {
this.$message.success('提交成功(模拟)')
},
resetBase() {
this.$refs.baseForm && this.$refs.baseForm.resetFields && this.$refs.baseForm.resetFields()
// 手动重置mstime字段
this.baseForm.mstime = ''
},
handleQrChange(file) {
// 模拟上传二维码
const reader = new FileReader()
reader.onload = e => {
this.baseForm.qrUrl = e.target.result
}
reader.readAsDataURL(file.raw)
},
beforeQrUpload(file) {
const isImg = file.type.startsWith('image/')
if (!isImg) {
this.$message.error('只能上传图片格式')
}
return isImg
},
// 文本配置
submitText() {
this.$message.success('提交成功(模拟)')
},
resetText() {
this.textForm = { company: '', intro: '', marginDesc: '' }
},
// 下单时间配置
updateTimeRange(idx) {
const item = this.orderTimes[idx];
if (item.start && item.end && item.start >= item.end) {
this.$message.error('结束时间必须大于开始时间');
item.end = '';
}
},
addOrderTime() {
this.orderTimes.push({ start: '', end: '', count: 99 });
},
removeOrderTime(idx) {
this.orderTimes.splice(idx, 1)
},
submitOrderTime() {
// 提交时拼接时间段
const timeList = this.orderTimes.map(item => ({
time: `${item.start}-${item.end}`,
count: item.count
}));
// 这里可以将 timeList 发送给后端
this.$message.success('提交成功(模拟)')
},
resetOrderTime() {
this.orderTimes = [
]
},
// 时间配置
submitTime() {
this.$message.success('提交成功(模拟)')
},
resetTime() {
this.timeForm = { withdrawDays: ['8号', '18号', '28号'], autoOrderMinutes: 10, cancelOrderDays: 7 }
},
resetMember() {
this.memberForm = { member: '', memberRule: '', recharge: '' }
},
resetRating() {
this.ratingForm = {
service: ['服务态度好', '技术专业', '准时守信', '认真负责', '价格合理'],
goods: ['商品质量好', '包装完整', '物流快速', '性价比高', '描述相符']
}
},
resetServiceStandard() {
this.serviceStandardForm = { serviceStandard: '' }
},
resetPopup() {
this.popupForm.popups = []
},
async saveAllConfig(e) {
// 1. 组装 config_one
const config_one = {
phone: this.baseForm.phone,
loot_start: this.baseForm.startTime,
loot_end: this.baseForm.endTime,
mstime: this.baseForm.mstime,
margin: this.baseForm.marginRate,
consumption: this.baseForm.consumption,
servicefee: this.baseForm.servicefee,
material_commissions: this.baseForm.material_commissions,
member_discount: this.baseForm.member_discount,
recharge_discount: this.baseForm.recharge_discount,
consumption_deduction: this.baseForm.consumption_deduction,
service_deduction: this.baseForm.service_deduction,
orderScore: this.baseForm.orderScore,
hot: this.baseForm.hotwords,
kf: this.baseForm.qrUrl
};
// 2. 组装 config_two
const config_two = {
name: this.textForm.company,
brief: this.textForm.intro,
money_explain: this.textForm.marginDesc
};
// 3. 组装 config_three
const config_three = {
time: this.orderTimes.map(item => ({
key: `${item.start}-${item.end}`,
num: item.count
}))
};
// 4. 组装 config_four
const config_four = {
time: this.timeForm.withdrawDays,
order_time: this.timeForm.autoOrderMinutes,
remove_time: this.timeForm.cancelOrderDays
};
// 5. 组装 config_five
const config_five = {
member: this.memberForm.member,
memberRule: this.memberForm.memberRule,
recharge: this.memberForm.recharge
};
// 6. 组装 config_six
const config_six = {
service: this.ratingForm.service,
goods: this.ratingForm.goods
};
// 组装 config_seven
const config_seven = {
notice: this.noticeForm.notice.map(item => ({
title: item.title || '',
content: item.content || '',
link: item.link || '',
sort: parseInt(item.sort) || 0,
status: parseInt(item.status) || 1,
category: item.category || 'home'
})).sort((a, b) => a.sort - b.sort)
};
try {
if(e=='1'){
await updateSiteConfig({ name: 'config_one',id:this.config_one.id, value: JSON.stringify(config_one) });
}else if(e=='2'){
await updateSiteConfig({ name: 'config_two',id:this.config_two.id, value: JSON.stringify(config_two) });
}else if(e=='3'){
await updateSiteConfig({ name: 'config_three',id:this.config_three.id, value: JSON.stringify(config_three) });
}else if(e=='4'){
await updateSiteConfig({ name: 'config_four',id:this.config_four.id, value: JSON.stringify(config_four) });
}else if(e=='5'){
await updateSiteConfig({ name: 'config_five',id:this.config_five.id, value: JSON.stringify(config_five) });
}else if(e=='6'){
if (!this.config_six || !this.config_six.id) {
await addSiteConfig({ name: 'config_six', value: JSON.stringify(config_six), status: 1 });
} else {
await updateSiteConfig({ name: 'config_six', id: this.config_six.id, value: JSON.stringify(config_six) });
}
}else if(e=='notice'){
console.log('保存通知公告配置');
console.log('config_seven数据:', config_seven);
console.log('当前config_seven配置:', this.config_seven);
if (!this.config_seven || !this.config_seven.id) {
console.log('新增通知公告配置');
const response = await addSiteConfig({
name: 'config_seven',
value: JSON.stringify(config_seven),
status: 1
});
console.log('新增响应:', response);
} else {
console.log('更新通知公告配置ID:', this.config_seven.id);
const response = await updateSiteConfig({
name: 'config_seven',
id: this.config_seven.id,
value: JSON.stringify(config_seven)
});
console.log('更新响应:', response);
}
}else if(e=='activity'){
const config_eight = {
activities: this.activityForm.activities.map(item => ({
title: item.title || '',
content: item.content || '',
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
position: parseInt(item.position) || 0
}))
};
if (!this.config_eight || !this.config_eight.id) {
await addSiteConfig({ name: 'config_eight', value: JSON.stringify(config_eight), status: 1 });
} else {
await updateSiteConfig({ name: 'config_eight', id: this.config_eight.id, value: JSON.stringify(config_eight) });
}
}else if(e=='siteLinks'){
const config_nine = { siteLinks: this.siteLinks };
try {
if (!this.config_nine || !this.config_nine.id) {
await addSiteConfig({ name: 'config_nine', value: JSON.stringify(config_nine), status: 1 });
} else {
await updateSiteConfig({ name: 'config_nine', id: this.config_nine.id, value: JSON.stringify(config_nine) });
}
this.$message.success('站内地址已保存!');
await this.getList();
} catch (error) {
this.$message.error('保存失败,请重试');
}
}else if(e=='serviceStandard'){
const config_ten = {
serviceStandard: this.serviceStandardForm.serviceStandard
};
try {
if (!this.config_ten || !this.config_ten.id) {
await addSiteConfig({ name: 'config_ten', value: JSON.stringify(config_ten), status: 1 });
} else {
await updateSiteConfig({ name: 'config_ten', id: this.config_ten.id, value: JSON.stringify(config_ten) });
}
this.$message.success('上门标准已保存!');
await this.getList();
} catch (error) {
this.$message.error('保存失败,请重试');
}
return; // 单独保存时直接返回,不执行后面的通用保存逻辑
}else if(e=='popup'){
const config_eleven = {
popups: this.popupForm.popups.map(item => ({
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
sort: parseInt(item.sort) || 0
})).sort((a, b) => a.sort - b.sort)
};
try {
if (!this.config_eleven || !this.config_eleven.id) {
const response = await addSiteConfig({
name: 'config_shiyi',
value: JSON.stringify(config_eleven),
status: 1
});
this.config_eleven = response.data;
} else {
await updateSiteConfig({
name: 'config_shiyi',
id: this.config_eleven.id,
value: JSON.stringify(config_eleven)
});
}
this.$message.success('首页弹窗已保存!');
await this.getList();
} catch (error) {
console.error('保存弹窗失败:', error);
this.$message.error('保存失败,请重试');
}
return; // 单独保存时直接返回,不执行后面的通用保存逻辑
}
// 如果没有指定具体的配置类型,则保存所有配置
if (!e) {
this.$message.success('保存成功!');
await this.getList();
}
} catch (error) {
console.error('保存失败:', error);
console.error('错误详情:', error.response || error.message || error);
let errorMessage = '保存失败,请重试';
if (error.response && error.response.data && error.response.data.msg) {
errorMessage = `保存失败:${error.response.data.msg}`;
} else if (error.message) {
errorMessage = `保存失败:${error.message}`;
}
this.$message.error(errorMessage);
}
},
addNotice() {
// 找到当前最大的排序值
const maxSort = this.noticeForm.notice.length > 0
? Math.max(...this.noticeForm.notice.map(item => item.sort || 0))
: -1;
this.noticeForm.notice.push({
title: '',
content: '',
link: '',
sort: maxSort + 1,
status: 1,
category: 'home', // 默认为首页通知公告
expanded: true // 新添加的公告默认展开以便编辑
})
},
// 切换公告展开/收起状态
toggleNoticeExpand(index) {
this.$set(this.noticeForm.notice[index], 'expanded', !this.noticeForm.notice[index].expanded);
},
// 保存单个公告项
async saveNoticeItem(index) {
const item = this.noticeForm.notice[index];
console.log('开始保存公告,索引:', index);
console.log('保存的公告数据:', item);
// 验证必填项
if (!item.title || !item.title.trim()) {
this.$message.error('请输入公告标题');
return;
}
if (!item.content || !item.content.trim()) {
this.$message.error('请输入公告内容');
return;
}
// 验证分类
if (!item.category) {
this.$set(this.noticeForm.notice[index], 'category', 'home');
}
try {
// 构造保存数据
const config_seven = {
notice: this.noticeForm.notice.map(noticeItem => ({
title: noticeItem.title || '',
content: noticeItem.content || '',
link: noticeItem.link || '',
sort: parseInt(noticeItem.sort) || 0,
status: parseInt(noticeItem.status) || 1,
category: noticeItem.category || 'home'
})).sort((a, b) => a.sort - b.sort)
};
console.log('准备保存的配置数据:', config_seven);
console.log('config_seven信息:', this.config_seven);
let response;
if (!this.config_seven || !this.config_seven.id) {
console.log('新增配置数据');
response = await addSiteConfig({
name: 'config_seven',
value: JSON.stringify(config_seven),
status: 1
});
console.log('新增响应:', response);
} else {
console.log('更新配置数据ID:', this.config_seven.id);
response = await updateSiteConfig({
name: 'config_seven',
id: this.config_seven.id,
value: JSON.stringify(config_seven)
});
console.log('更新响应:', response);
}
this.$message.success('公告保存成功!');
// 重新获取数据
await this.getList();
// 保存成功后收起编辑区域
this.$set(this.noticeForm.notice[index], 'expanded', false);
} catch (error) {
console.error('保存公告失败:', error);
console.error('错误详情:', error.response || error.message || error);
this.$message.error(`保存失败:${error.message || '请重试'}`);
}
},
async removeNotice(index) {
const item = this.noticeForm.notice[index];
const title = item ? (item.title || '未设置标题') : '公告';
try {
await this.$confirm(`确定删除公告"${title}"吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 删除公告
this.noticeForm.notice.splice(index, 1);
// 重新排序
this.noticeForm.notice.forEach((item, idx) => {
item.sort = idx;
});
// 保存到后端
try {
const config_seven = {
notice: this.noticeForm.notice.map(item => ({
title: item.title || '',
content: item.content || '',
link: item.link || '',
sort: parseInt(item.sort) || 0,
status: parseInt(item.status) || 1,
category: item.category || 'home'
})).sort((a, b) => a.sort - b.sort)
};
if (!this.config_seven || !this.config_seven.id) {
await addSiteConfig({
name: 'config_seven',
value: JSON.stringify(config_seven),
status: 1
});
} else {
await updateSiteConfig({
name: 'config_seven',
id: this.config_seven.id,
value: JSON.stringify(config_seven)
});
}
this.$message.success('删除成功');
await this.getList();
} catch (error) {
console.error('删除保存失败:', error);
this.$message.error('删除失败,请重试');
// 回滚删除操作
this.getList();
}
} catch (error) {
this.$message.info('已取消删除');
}
},
resetNotice() {
this.noticeForm.notice = []
},
// 获取分类名称
getCategoryName(category) {
const categoryMap = {
'home': '首页通知公告',
'activity': '活动通知公告'
};
return categoryMap[category] || '首页通知公告';
},
// 获取分类标签类型
getCategoryTagType(category) {
const typeMap = {
'home': 'primary',
'activity': 'warning'
};
return typeMap[category] || 'primary';
},
// 处理状态变化
async handleStatusChange(index) {
const item = this.noticeForm.notice[index];
if (!item) return;
const newStatus = item.status;
const oldStatus = newStatus === 1 ? 0 : 1;
const statusText = newStatus === 1 ? '上线' : '下线';
const currentTitle = item.title || '未设置标题';
// 添加确认对话框
try {
await this.$confirm(`确定要${statusText}公告"${currentTitle}"吗?`, '状态变更确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: newStatus === 1 ? 'success' : 'warning'
});
} catch (error) {
// 用户取消了操作,回滚状态
this.$set(this.noticeForm.notice[index], 'status', oldStatus);
return;
}
// 用户确认,保存到后端
try {
const config_seven = {
notice: this.noticeForm.notice.map(item => ({
title: item.title,
content: item.content,
link: item.link,
sort: item.sort,
status: item.status,
category: item.category || 'home'
})).sort((a, b) => a.sort - b.sort)
};
if (!this.config_seven || !this.config_seven.id) {
await addSiteConfig({ name: 'config_seven', value: JSON.stringify(config_seven), status: 1 });
} else {
await updateSiteConfig({ name: 'config_seven', id: this.config_seven.id, value: JSON.stringify(config_seven) });
}
this.$message.success(`公告${statusText}成功!`);
// 重新获取数据以确保同步
this.getList();
} catch (error) {
console.error('状态更新失败:', error);
// 保存失败,回滚状态
this.$set(this.noticeForm.notice[index], 'status', oldStatus);
this.$message.error('状态更新失败,请重试');
}
},
handleCategoryChange(val, item, index) {
// 强制响应式防止item.category不是响应式
this.$set(this.noticeForm.notice[index], 'category', val)
},
addActivity() {
// 找到当前最大的显示位置值
const maxPosition = this.activityForm.activities.length > 0
? Math.max(...this.activityForm.activities.map(item => item.position || 0))
: -1;
this.activityForm.activities.push({
title: '',
content: '',
link: '',
imgUrl: '',
status: 1,
position: maxPosition + 1, // 新活动的显示位置自动设为最大值+1
expanded: false // 新添加的活动默认不展开
});
},
removeActivity(index) {
this.activityForm.activities.splice(index, 1);
},
resetActivity() {
this.activityForm.activities = [];
},
beforeActivityImgUpload(file, index) {
console.log('准备上传图片:', file.name, file.type, file.size);
// 检查文件类型
const isImg = file.type.startsWith('image/');
if (!isImg) {
this.$message.error('只能上传图片格式');
return false;
}
// 检查文件大小限制10MB
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('图片大小不能超过10MB');
return false;
}
console.log('图片检查通过,开始上传...');
this.$message.info('图片上传中...');
return true;
},
handleActivityImgSuccess(res, file, index) {
console.log('图片上传成功回调:', res, file, index);
console.log('完整响应数据:', JSON.stringify(res, null, 2));
// 处理上传成功的响应
let url = '';
if (res && res.code === 200) {
// 标准的RuoYi响应格式
if (res.url) {
url = res.url;
} else if (res.fileName) {
url = res.fileName;
} else if (res.data && res.data.url) {
url = res.data.url;
} else if (res.data && res.data.fileName) {
url = res.data.fileName;
}
} else if (res) {
// 兼容其他格式
if (typeof res === 'string') {
url = res;
} else if (res.url) {
url = res.url;
} else if (res.fileName) {
url = res.fileName;
} else if (res.data) {
if (typeof res.data === 'string') {
url = res.data;
} else if (res.data.url) {
url = res.data.url;
} else if (res.data.fileName) {
url = res.data.fileName;
}
}
}
console.log('解析出的图片URL:', url);
// 如果是相对路径自动拼接完整URL
if (url && !/^https?:\/\//.test(url)) {
// 使用VUE_APP_BASE_API或当前域名
const base = process.env.VUE_APP_BASE_API || window.location.origin;
url = base.replace(/\/$/, '') + (url.startsWith('/') ? url : '/' + url);
}
console.log('最终处理后的图片URL:', url);
if (url) {
// 确保索引有效
if (this.activityForm.activities[index]) {
this.$set(this.activityForm.activities[index], 'imgUrl', url);
this.$message.success('图片上传成功');
console.log('图片URL已设置到活动数据:', this.activityForm.activities[index]);
// 强制刷新上传组件
this.$forceUpdate();
} else {
console.error('活动索引无效:', index);
this.$message.error('活动索引无效');
}
} else {
console.error('无法从响应中获取图片URL');
console.error('响应数据结构:', res);
this.$message.error('图片上传失败:无法获取图片地址');
}
},
handleActivityImgError(err, file, index) {
console.error('图片上传失败:', err, file, index);
console.error('错误详情:', JSON.stringify(err, null, 2));
let errorMessage = '图片上传失败';
if (err.message) {
errorMessage += '' + err.message;
} else if (err.response && err.response.data && err.response.data.msg) {
errorMessage += '' + err.response.data.msg;
} else if (err.response && err.response.statusText) {
errorMessage += '' + err.response.statusText;
} else {
errorMessage += ':网络错误';
}
this.$message.error(errorMessage);
},
// 删除图片
removeActivityImg(index) {
this.$confirm('确定要删除这张图片吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$set(this.activityForm.activities[index], 'imgUrl', '');
this.$message.success('图片删除成功');
}).catch(() => {
this.$message.info('已取消删除');
});
},
toggleActivityExpand(index) {
this.$set(this.activityForm.activities[index], 'expanded', !this.activityForm.activities[index].expanded);
},
async saveActivityItem(index) {
const item = this.activityForm.activities[index];
// 验证必填项
if (!item.title || !item.title.trim()) {
this.$message.error('请输入活动标题');
return;
}
try {
// 构造保存数据
const config_eight = {
activities: this.activityForm.activities.map(activityItem => ({
title: activityItem.title || '',
content: activityItem.content || '',
link: activityItem.link || '',
imgUrl: activityItem.imgUrl || '',
status: parseInt(activityItem.status) || 0,
position: parseInt(activityItem.position) || 0
}))
};
if (!this.config_eight || !this.config_eight.id) {
await addSiteConfig({
name: 'config_eight',
value: JSON.stringify(config_eight),
status: 1
});
} else {
await updateSiteConfig({
name: 'config_eight',
id: this.config_eight.id,
value: JSON.stringify(config_eight)
});
}
this.$message.success('活动保存成功!');
// 重新获取数据
await this.getList();
// 保存成功后收起编辑区域
this.$set(this.activityForm.activities[index], 'expanded', false);
} catch (error) {
console.error('保存活动失败:', error);
this.$message.error(`保存失败:${error.message || '请重试'}`);
}
},
async handleActivityStatusChange(index) {
const item = this.activityForm.activities[index];
if (!item) return;
const newStatus = item.status;
const oldStatus = newStatus === 1 ? 0 : 1;
const statusText = newStatus === 1 ? '上线' : '下线';
const currentTitle = item.title || '未设置标题';
// 添加确认对话框
try {
await this.$confirm(`确定要${statusText}活动"${currentTitle}"吗?`, '状态变更确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: newStatus === 1 ? 'success' : 'warning'
});
} catch (error) {
// 用户取消了操作,回滚状态
this.$set(this.activityForm.activities[index], 'status', oldStatus);
return;
}
// 用户确认,保存到后端
try {
const config_eight = {
activities: this.activityForm.activities.map(item => ({
title: item.title || '',
content: item.content || '',
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
position: parseInt(item.position) || 0
}))
};
if (!this.config_eight || !this.config_eight.id) {
await addSiteConfig({ name: 'config_eight', value: JSON.stringify(config_eight), status: 1 });
} else {
await updateSiteConfig({ name: 'config_eight', id: this.config_eight.id, value: JSON.stringify(config_eight) });
}
this.$message.success(`活动${statusText}成功!`);
// 重新获取数据以确保同步
this.getList();
} catch (error) {
console.error('状态更新失败:', error);
// 保存失败,回滚状态
this.$set(this.activityForm.activities[index], 'status', oldStatus);
this.$message.error('状态更新失败,请重试');
}
},
async removeActivity(index) {
const item = this.activityForm.activities[index];
const title = item ? (item.title || '未设置标题') : '活动';
try {
await this.$confirm(`确定删除活动"${title}"`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 删除活动
this.activityForm.activities.splice(index, 1);
// 保存到后端
try {
const config_eight = {
activities: this.activityForm.activities.map(item => ({
title: item.title || '',
content: item.content || '',
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
position: parseInt(item.position) || 0
}))
};
if (!this.config_eight || !this.config_eight.id) {
await addSiteConfig({
name: 'config_eight',
value: JSON.stringify(config_eight),
status: 1
});
} else {
await updateSiteConfig({
name: 'config_eight',
id: this.config_eight.id,
value: JSON.stringify(config_eight)
});
}
this.$message.success('删除成功');
await this.getList();
} catch (error) {
console.error('删除保存失败:', error);
this.$message.error('删除失败,请重试');
// 回滚删除操作
this.getList();
}
} catch (error) {
this.$message.info('已取消删除');
}
},
addSiteLink() {
this.siteLinks.push({ name: '', url: '', remark: '', params: '' });
},
removeSiteLink(idx) {
this.siteLinks.splice(idx, 1);
},
async saveSiteLinks() {
const config_nine = { siteLinks: this.siteLinks };
try {
if (!this.config_nine || !this.config_nine.id) {
await addSiteConfig({ name: 'config_nine', value: JSON.stringify(config_nine), status: 1 });
} else {
await updateSiteConfig({ name: 'config_nine', id: this.config_nine.id, value: JSON.stringify(config_nine) });
}
this.$message.success('站内地址已保存!');
await this.getList();
} catch (error) {
this.$message.error('保存失败,请重试');
}
},
// 首页弹窗相关方法
addPopup() {
// 找到当前最大的排序值
const maxSort = this.popupForm.popups.length > 0
? Math.max(...this.popupForm.popups.map(item => item.sort || 0))
: -1;
const newPopup = {
link: '',
imgUrl: '',
sort: maxSort + 1,
status: 1,
expanded: true // 新添加的弹窗默认展开以便编辑
};
this.popupForm.popups.push(newPopup);
this.$message.success('弹窗添加成功!');
},
// 切换弹窗展开/收起状态
togglePopupExpand(index) {
this.$set(this.popupForm.popups[index], 'expanded', !this.popupForm.popups[index].expanded);
},
// 保存单个弹窗项
async savePopupItem(index) {
const item = this.popupForm.popups[index];
try {
// 构造保存数据
const config_eleven = {
popups: this.popupForm.popups.map(popupItem => ({
link: popupItem.link || '',
imgUrl: popupItem.imgUrl || '',
status: parseInt(popupItem.status) || 0,
sort: parseInt(popupItem.sort) || 0
})).sort((a, b) => a.sort - b.sort)
};
if (!this.config_eleven || !this.config_eleven.id) {
const response = await addSiteConfig({
name: 'config_shiyi',
value: JSON.stringify(config_eleven),
status: 1
});
// 更新config_eleven的id
this.config_eleven = response.data;
} else {
await updateSiteConfig({
name: 'config_shiyi',
id: this.config_eleven.id,
value: JSON.stringify(config_eleven)
});
}
this.$message.success('弹窗保存成功!');
// 重新获取数据
await this.getList();
// 保存成功后收起编辑区域
this.$set(this.popupForm.popups[index], 'expanded', false);
} catch (error) {
console.error('保存弹窗失败:', error);
this.$message.error(`保存失败${error.message || '请重试'}`);
}
},
// 删除弹窗
async removePopup(index) {
const item = this.popupForm.popups[index];
try {
await this.$confirm(`确定删除弹窗吗`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
// 删除弹窗
this.popupForm.popups.splice(index, 1);
// 重新排序
this.popupForm.popups.forEach((item, idx) => {
item.sort = idx;
});
// 保存到后端
try {
const config_eleven = {
popups: this.popupForm.popups.map(item => ({
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
sort: parseInt(item.sort) || 0
})).sort((a, b) => a.sort - b.sort)
};
if (!this.config_eleven || !this.config_eleven.id) {
if (this.popupForm.popups.length > 0) {
// 如果还有弹窗,创建新配置
const response = await addSiteConfig({
name: 'config_shiyi',
value: JSON.stringify(config_eleven),
status: 1
});
this.config_eleven = response.data;
}
} else {
await updateSiteConfig({
name: 'config_shiyi',
id: this.config_eleven.id,
value: JSON.stringify(config_eleven)
});
}
this.$message.success('删除成功');
await this.getList();
} catch (error) {
console.error('删除保存失败:', error);
this.$message.error('删除失败,请重试');
// 回滚删除操作
this.getList();
}
} catch (error) {
this.$message.info('已取消删除');
}
},
// 重置弹窗
resetPopup() {
this.popupForm.popups = [];
},
// 处理弹窗状态变化
async handlePopupStatusChange(index) {
const item = this.popupForm.popups[index];
if (!item) return;
const newStatus = item.status;
const oldStatus = newStatus === 1 ? 0 : 1;
const statusText = newStatus === 1 ? '上线' : '下线';
// 添加确认对话框
try {
await this.$confirm(`确定要${statusText}弹窗吗`, '状态变更确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: newStatus === 1 ? 'success' : 'warning'
});
} catch (error) {
// 用户取消了操作,回滚状态
this.$set(this.popupForm.popups[index], 'status', oldStatus);
return;
}
// 用户确认,保存到后端
try {
const config_eleven = {
popups: this.popupForm.popups.map(item => ({
link: item.link || '',
imgUrl: item.imgUrl || '',
status: parseInt(item.status) || 0,
sort: parseInt(item.sort) || 0
}))
};
if (!this.config_eleven || !this.config_eleven.id) {
const response = await addSiteConfig({
name: 'config_shiyi',
value: JSON.stringify(config_eleven),
status: 1
});
this.config_eleven = response.data;
} else {
await updateSiteConfig({
name: 'config_shiyi',
id: this.config_eleven.id,
value: JSON.stringify(config_eleven)
});
}
this.$message.success(`弹窗${statusText}成功`);
// 重新获取数据以确保同步
this.getList();
} catch (error) {
console.error('状态更新失败:', error);
// 保存失败,回滚状态
this.$set(this.popupForm.popups[index], 'status', oldStatus);
this.$message.error('状态更新失败,请重试');
}
},
// 弹窗图片上传前检查
beforePopupImgUpload(file, index) {
console.log('准备上传弹窗图片:', file.name, file.type, file.size);
// 检查文件类型
const isImg = file.type.startsWith('image/');
if (!isImg) {
this.$message.error('只能上传图片格式');
return false;
}
// 检查文件大小限制10MB
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('图片大小不能超过10MB');
return false;
}
console.log('弹窗图片检查通过,开始上传...');
this.$message.info('图片上传中...');
return true;
},
// 弹窗图片上传成功
handlePopupImgSuccess(res, file, index) {
console.log('弹窗图片上传成功回调:', res, file, index);
console.log('完整响应数据:', JSON.stringify(res, null, 2));
// 处理上传成功的响应
let url = '';
if (res && res.code === 200) {
// 标准的RuoYi响应格式
if (res.url) {
url = res.url;
} else if (res.fileName) {
url = res.fileName;
} else if (res.data && res.data.url) {
url = res.data.url;
} else if (res.data && res.data.fileName) {
url = res.data.fileName;
}
} else if (res) {
// 兼容其他格式
if (typeof res === 'string') {
url = res;
} else if (res.url) {
url = res.url;
} else if (res.fileName) {
url = res.fileName;
} else if (res.data) {
if (typeof res.data === 'string') {
url = res.data;
} else if (res.data.url) {
url = res.data.url;
} else if (res.data.fileName) {
url = res.data.fileName;
}
}
}
console.log('解析出的弹窗图片URL:', url);
// 如果是相对路径自动拼接完整URL
if (url && !/^https?:\/\//.test(url)) {
// 使用VUE_APP_BASE_API或当前域名
const base = process.env.VUE_APP_BASE_API || window.location.origin;
url = base.replace(/\/$/, '') + (url.startsWith('/') ? url : '/' + url);
}
console.log('最终处理后的弹窗图片URL:', url);
if (url) {
// 确保索引有效
if (this.popupForm.popups[index]) {
this.$set(this.popupForm.popups[index], 'imgUrl', url);
this.$message.success('弹窗图片上传成功');
console.log('弹窗图片URL已设置到弹窗数据:', this.popupForm.popups[index]);
// 强制刷新上传组件
this.$forceUpdate();
} else {
console.error('弹窗索引无效:', index);
this.$message.error('弹窗索引无效');
}
} else {
console.error('无法从响应中获取弹窗图片URL');
console.error('响应数据结构:', res);
this.$message.error('弹窗图片上传失败:无法获取图片地址');
}
},
// 弹窗图片上传失败
handlePopupImgError(err, file, index) {
console.error('弹窗图片上传失败:', err, file, index);
console.error('错误详情:', JSON.stringify(err, null, 2));
let errorMessage = '弹窗图片上传失败';
if (err.message) {
errorMessage += '' + err.message;
} else if (err.response && err.response.data && err.response.data.msg) {
errorMessage += '' + err.response.data.msg;
} else if (err.response && err.response.statusText) {
errorMessage += '' + err.response.statusText;
} else {
errorMessage += ':网络错误';
}
this.$message.error(errorMessage);
},
// 删除弹窗图片
removePopupImg(index) {
this.$confirm('确定要删除这张弹窗图片吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$set(this.popupForm.popups[index], 'imgUrl', '');
this.$message.success('弹窗图片删除成功');
}).catch(() => {
this.$message.info('已取消删除');
});
}
}
}
</script>
<style scoped>
.sysseting-container {
background: #fff;
padding: 24px;
min-height: 600px;
}
.tab-form {
max-width: 900px;
}
.qr-img {
width: 120px;
height: 120px;
border: 1px solid #eee;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-uploader-icon {
font-size: 32px;
color: #8c939d;
}
/* 通知公告容器样式 */
.notice-container {
background: #f8f9fa;
min-height: 600px;
padding: 0;
}
/* 页面头部样式 */
.notice-header {
background: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
display: flex;
justify-content: space-between;
align-items: center;
}
.notice-title h3 {
margin: 0 0 8px 0;
color: #303133;
font-size: 20px;
font-weight: 600;
}
.notice-desc {
margin: 0;
color: #909399;
font-size: 14px;
line-height: 1.5;
}
.notice-header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.notice-filter {
display: flex;
align-items: center;
}
/* 空状态样式 */
.notice-empty {
background: #fff;
border-radius: 8px;
padding: 40px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
/* 公告列表样式 */
.notice-list {
margin-bottom: 20px;
}
.notice-item {
margin-bottom: 20px;
}
/* 公告卡片样式 */
.notice-card {
border-radius: 8px;
border: 1px solid #e4e7ed;
transition: all 0.3s ease;
overflow: hidden;
}
.notice-card:hover {
border-color: #409EFF;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
}
.notice-card.notice-offline {
background: #fafafa;
border-color: #f0f0f0;
}
.notice-card.notice-offline:hover {
border-color: #d9d9d9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.notice-card.notice-expanded {
border-color: #409EFF;
}
/* 简洁行显示样式 */
.notice-simple-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
cursor: pointer;
transition: background-color 0.2s ease;
min-height: 60px;
}
.notice-simple-row:hover {
background-color: #f8f9fa;
}
.notice-simple-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.notice-simple-info {
margin-left: 12px;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.notice-simple-title {
font-size: 14px;
font-weight: 500;
color: #303133;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-category-tag-left {
margin-left: 12px;
margin-right: 8px;
flex-shrink: 0;
align-self: flex-start;
}
.notice-simple-meta {
display: block;
font-size: 12px;
color: #909399;
line-height: 1.2;
}
.notice-simple-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.notice-expand-icon {
font-size: 14px;
color: #909399;
transition: transform 0.3s ease;
margin-left: 8px;
}
.notice-card.notice-expanded .notice-expand-icon {
transform: rotate(180deg);
}
/* 序号样式 */
.notice-index {
background: #409EFF;
color: #fff;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
/* 卡片内容样式 */
.notice-card-content {
padding: 0 20px 20px 20px;
background: #fafbfc;
}
/* 编辑区域底部操作按钮 */
.notice-edit-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
.notice-form-grid {
display: flex;
flex-direction: column;
gap: 20px;
}
.notice-form-row.full-width {
width: 100%;
}
.notice-form-row.two-columns {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
align-items: start;
}
.notice-title-item .el-form-item__content {
width: 100%;
}
.notice-content-item .el-form-item__content {
width: 100%;
}
.notice-link-item .el-form-item__content {
width: 100%;
}
.notice-sort-item .el-form-item__content {
width: 100%;
}
/* 富文本编辑器样式 */
.notice-editor {
border-radius: 8px;
overflow: hidden;
}
.notice-editor .ql-toolbar {
border-top: 1px solid #e4e7ed;
border-left: 1px solid #e4e7ed;
border-right: 1px solid #e4e7ed;
background: #fafbfc;
}
.notice-editor .ql-container {
border-bottom: 1px solid #e4e7ed;
border-left: 1px solid #e4e7ed;
border-right: 1px solid #e4e7ed;
min-height: 200px;
font-size: 14px;
}
.notice-editor .ql-editor {
padding: 16px;
line-height: 1.6;
}
/* 底部操作区样式 */
.notice-footer {
background: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.notice-footer-actions {
display: flex;
justify-content: center;
gap: 12px;
margin-bottom: 20px;
}
.notice-footer-tips {
margin-top: 16px;
}
.notice-footer-tips .el-alert {
border-radius: 8px;
}
.notice-footer-tips .el-alert__description p {
margin: 4px 0;
color: #606266;
font-size: 13px;
line-height: 1.5;
}
/* 活动专区维护容器样式 */
.activity-zone-container { background: #f8f9fa; min-height: 400px; padding: 0; }
.activity-header { background: #fff; padding: 24px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 12px; }
.activity-card { margin-bottom: 20px; }
.activity-img { width: 120px; height: 120px; border: 1px solid #eee; display: block; }
.activity-uploader-icon { font-size: 32px; color: #8c939d; }
.activity-actions { display: flex; justify-content: flex-end; margin-top: 12px; }
/* 响应式设计 */
@media (max-width: 768px) {
.notice-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.notice-header-actions {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
gap: 8px;
}
.notice-filter {
order: -1;
width: 100%;
margin-bottom: 8px;
}
.notice-simple-row {
padding: 12px 16px;
min-height: 50px;
}
.notice-simple-left {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.notice-simple-info {
margin-left: 0;
margin-top: 0;
}
.notice-category-tag-left {
margin-left: 0;
margin-right: 0;
margin-top: 4px;
}
.notice-simple-title {
font-size: 13px;
}
.notice-simple-right {
flex-direction: column;
gap: 4px;
align-items: flex-end;
}
.notice-card-content {
padding: 0 16px 16px 16px;
}
.notice-form-row.two-columns {
grid-template-columns: 1fr;
gap: 16px;
}
.notice-edit-actions {
flex-direction: column;
align-items: stretch;
}
.notice-footer-actions {
flex-direction: column;
align-items: stretch;
}
}
.activity-simple-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
cursor: pointer;
transition: background-color 0.2s ease;
min-height: 60px;
}
.activity-simple-row:hover {
background-color: #f8f9fa;
}
.activity-simple-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.activity-simple-info {
margin-left: 12px;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.activity-simple-title {
font-size: 14px;
font-weight: 500;
color: #303133;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.activity-simple-content {
font-size: 12px;
color: #909399;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.activity-thumb {
width: 48px;
height: 48px;
object-fit: cover;
border-radius: 4px;
margin-left: 16px;
border: 1px solid #eee;
}
.activity-simple-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.activity-expand-icon {
font-size: 14px;
color: #909399;
transition: transform 0.3s ease;
margin-left: 8px;
}
.activity-card.activity-expanded .activity-expand-icon {
transform: rotate(180deg);
}
.activity-index {
background: #409EFF;
color: #fff;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.activity-card-content {
padding: 0 20px 20px 20px;
background: #fafbfc;
}
.activity-edit-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
.activity-img-delete-btn {
position: absolute;
top: 2px;
right: 2px;
z-index: 10;
padding: 0;
width: 22px;
height: 22px;
min-width: 0;
min-height: 0;
line-height: 22px;
display: flex;
align-items: center;
justify-content: center;
}
.activity-simple-link {
font-size: 11px;
color: #409EFF;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.activity-simple-position {
font-size: 11px;
color: #67C23A;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
/* 首页弹窗维护容器样式 */
.popup-container {
background: #f8f9fa;
min-height: 400px;
padding: 0;
}
.popup-header {
background: #fff;
padding: 24px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
gap: 12px;
}
.popup-empty {
background: #fff;
border-radius: 8px;
padding: 40px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.popup-item {
margin-bottom: 20px;
}
.popup-card {
border-radius: 8px;
border: 1px solid #e4e7ed;
transition: all 0.3s ease;
overflow: hidden;
}
.popup-card:hover {
border-color: #409EFF;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
}
.popup-card.popup-offline {
background: #fafafa;
border-color: #f0f0f0;
}
.popup-card.popup-offline:hover {
border-color: #d9d9d9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.popup-card.popup-expanded {
border-color: #409EFF;
}
.popup-simple-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
cursor: pointer;
transition: background-color 0.2s ease;
min-height: 60px;
}
.popup-simple-row:hover {
background-color: #f8f9fa;
}
.popup-simple-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.popup-simple-info {
margin-left: 12px;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.popup-simple-title {
font-size: 14px;
font-weight: 500;
color: #303133;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.popup-simple-link {
font-size: 11px;
color: #409EFF;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.popup-simple-position {
font-size: 11px;
color: #67C23A;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.popup-thumb {
width: 48px;
height: 48px;
object-fit: cover;
border-radius: 4px;
margin-left: 16px;
border: 1px solid #eee;
}
.popup-simple-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.popup-expand-icon {
font-size: 14px;
color: #909399;
transition: transform 0.3s ease;
margin-left: 8px;
}
.popup-card.popup-expanded .popup-expand-icon {
transform: rotate(180deg);
}
.popup-index {
background: #409EFF;
color: #fff;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.popup-card-content {
padding: 0 20px 20px 20px;
background: #fafbfc;
}
.popup-edit-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
.popup-img {
width: 120px;
height: 120px;
border: 1px solid #eee;
display: block;
object-fit: cover;
}
.popup-uploader-icon {
font-size: 32px;
color: #8c939d;
}
.popup-img-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.popup-img-delete-btn {
position: absolute;
top: 2px;
right: 2px;
z-index: 10;
padding: 0;
width: 22px;
height: 22px;
min-width: 0;
min-height: 0;
line-height: 22px;
display: flex;
align-items: center;
justify-content: center;
}
</style>