This commit is contained in:
吴昊天 2026-09-23 19:24:55 +08:00
commit 8ce140bdb2
28 changed files with 392 additions and 238 deletions

View File

@ -206,6 +206,8 @@ class RoutineTemplateController extends AuthController
}
$data['appId'] = sys_config('routine_appId');
$data['help'] = 'https://doc.crmeb.com/web/single/crmeb_v4/978';
// 站点网址:前端 iframe H5 预览地址回退使用(H5 产物部署在 backend/public)
$data['site_url'] = sys_config('site_url');
return app('json')->success($data);
}
}

View File

@ -40,8 +40,8 @@
accept="image/png, image/jpeg, image/gif, image/jpg"
@change="selectImg($event)"
/>
<el-button type="danger" plain icon="el-icon-zoom-in" v-db-click @click="changeScale(1)">放大</el-button>
<el-button type="danger" plain icon="el-icon-zoom-out" v-db-click @click="changeScale(-1)">缩小</el-button>
<el-button type="danger" plain icon="ZoomIn" v-db-click @click="changeScale(1)">放大</el-button>
<el-button type="danger" plain icon="ZoomOut" v-db-click @click="changeScale(-1)">缩小</el-button>
<el-button type="danger" plain v-db-click @click="rotateLeft">↺ 左旋转</el-button>
<el-button type="danger" plain v-db-click @click="rotateRight">↻ 右旋转</el-button>
</div>

View File

@ -46,14 +46,14 @@
>
<el-button
type="success"
icon="el-icon-edit"
icon="Edit"
v-show="item.new[i].isDel && isShow"
v-db-click
@click="clkk(item)"
></el-button>
<el-button
type="error"
icon="el-icon-delete"
icon="Delete"
v-show="item.new[i].isDel && isShow"
v-db-click
@click="del(item, '删除图文', i)"
@ -61,7 +61,7 @@
></el-button>
<el-button
type="primary"
icon="el-icon-s-promotion"
icon="Promotion"
v-show="item.new[i].isDel && isShowSend"
v-db-click
@click="send(item, '发送', i)"

View File

@ -2,7 +2,7 @@
<el-dialog v-model="visible" width="800px" title="选择图标" append-to-body>
<div class="icon-selector">
<div class="search-box">
<el-input v-model="searchText" placeholder="搜索图标名称" prefix-icon="el-icon-search" clearable></el-input>
<el-input v-model="searchText" placeholder="搜索图标名称" prefix-icon="Search" clearable></el-input>
</div>
<div class="icon-list">
<div class="icon-item" v-for="(item, index) in filteredIcons" :key="index" @click="selectIcon(item)">

View File

@ -3,7 +3,7 @@
v-model="dialogVisible"
width="1188px"
top="5vh"
custom-class="theme-dialog"
class="theme-dialog"
:show-close="false"
append-to-body
:modal="true"
@ -43,21 +43,26 @@
<el-input
v-model="searchKeyword"
placeholder="请输入主题名称"
suffix-icon="el-icon-search"
suffix-icon="Search"
clearable
@clear="searchTheme"
@change="searchTheme"
></el-input>
</div>
</div>
<div class="theme-grid">
<div class="theme-grid" v-loading="loading">
<!-- 新建空白主题 -->
<div class="theme-item create-new" v-if="activeTab === 'my'" @click="createNewTheme">
<div class="theme-item create-new" v-if="activeTab === 'my'" v-db-click @click="createNewTheme">
<div class="create-icon">
<div class="iconfont iconic_brush"></div>
</div>
<div class="create-text">新建空白主题</div>
</div>
<!-- 空状态 -->
<div class="theme-empty" v-if="!loading && themes.length === 0">未找到相关主题,换个关键词试试</div>
<!-- 主题列表 -->
<div class="theme-item" v-for="(theme, index) in themes" :key="index">
<div class="theme-cover">
@ -85,8 +90,8 @@
<div class="theme-time">修改时间:{{ theme.up_time }}</div>
<div class="theme-hover-actions">
<div class="button default" @click="viewThemeDetail(theme)">查看详情</div>
<div class="button primary" @click="selectTheme(theme)">使用主题</div>
<div class="button default" v-db-click @click="viewThemeDetail(theme)">查看详情</div>
<div class="button primary" v-db-click @click="selectTheme(theme)">使用主题</div>
</div>
</div>
</div>
@ -134,7 +139,7 @@
</div>
</div>
<div class="info-btn">
<el-button type="primary" @click="selectTheme(currentTheme)">使用主题</el-button>
<el-button type="primary" v-db-click @click="selectTheme(currentTheme)">使用主题</el-button>
</div>
</div>
</div>
@ -143,6 +148,7 @@
<div class="section-title">主题图片</div>
<div class="images-list">
<div class="image-item" v-for="(opt, index) in filterOptions" :key="index">
<div class="image-label">{{ opt.label }}</div>
<div class="phone-mockup">
<img
v-if="currentTheme[opt.value]"
@ -228,6 +234,7 @@ const filterOptions = [
{ label: '个人中心', value: 'user_image' },
];
const themes = ref([]);
const loading = ref(false);
const page = ref(1);
const limit = ref(9);
const total = ref(0);
@ -261,10 +268,15 @@ function getList() {
if (props.type == 'mall' && props.currentType == 'home') {
reqData.page_type = 'all';
}
getThemeList(reqData).then((res) => {
themes.value = res.data.list;
total.value = res.data.count;
});
loading.value = true;
getThemeList(reqData)
.then((res) => {
themes.value = res.data.list;
total.value = res.data.count;
})
.finally(() => {
loading.value = false;
});
}
function handlePageChange(val) {
@ -352,23 +364,8 @@ function searchTheme() {
</script>
<style lang="scss" scoped>
// 弹窗样式
:deep(.theme-dialog) {
border-radius: 8px;
overflow: hidden;
.el-dialog__header {
border-bottom: 1px solid #f0f0f0;
padding: 0px !important;
}
.el-dialog__body {
padding: 0 !important;
}
.el-dialog__body::-webkit-scrollbar {
width: 0%;
}
}
// 弹窗壳层(header/body/圆角)覆盖在本文件末尾的非 scoped 块中:
// append-to-body 弹窗 teleport 到 body 后脱离组件样式作用域,scoped :deep 无法命中
.dialog-header {
display: flex;
@ -377,7 +374,7 @@ function searchTheme() {
width: 100%;
padding: 15px 24px;
.dialog-header-tabs {
background: #fff;
background: var(--prev-bg-white);
.tabs {
display: flex;
@ -387,7 +384,7 @@ function searchTheme() {
font-size: 14px;
line-height: 22px;
font-weight: 500;
color: #333;
color: var(--prev-color-text-regular);
cursor: pointer;
position: relative;
}
@ -395,21 +392,30 @@ function searchTheme() {
height: 12px;
margin-left: 20px;
cursor: pointer;
transition: opacity 0.3s;
&:hover {
opacity: 0.75;
}
}
}
}
}
.el-icon-close {
font-size: 16px;
color: #909399;
color: var(--prev-color-text-secondary);
cursor: pointer;
transition: color 0.3s;
&:hover {
color: var(--prev-color-primary);
}
}
.dialog-content {
display: flex;
flex-direction: column;
height: calc(100% - 100px);
padding: 1px 24px 1px 24px !important;
padding: 0 24px 24px !important;
.content-wrapper {
flex: 1;
@ -417,12 +423,11 @@ function searchTheme() {
overflow: hidden;
.main-content {
flex: 1;
min-height: 756px;
padding: 20px 0;
display: flex;
flex-direction: column;
overflow: hidden;
background: #fff;
background: var(--prev-bg-white);
.filters-header {
display: flex;
align-items: center;
@ -441,42 +446,57 @@ function searchTheme() {
gap: 30px;
overflow-y: auto;
flex: 1;
max-height: calc(100vh - 300px);
.theme-empty {
width: 100%;
text-align: center;
padding: 60px 0;
font-size: 13px;
color: var(--prev-color-text-secondary);
}
.theme-item {
width: 204px;
height: 295px;
background: #fff;
border-radius: 8px;
background: var(--prev-bg-white);
border-radius: 6px;
overflow: hidden;
border: 1px solid #f0f0f0;
border: 1px solid var(--prev-border-color-lighter);
display: flex;
flex-direction: column;
transition: box-shadow 0.3s, border-color 0.3s;
&:hover {
border-color: var(--prev-color-primary-light-5);
box-shadow: 0 6px 16px rgba(0, 21, 41, 0.08);
}
&.create-new {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #fff;
cursor: pointer;
border: 1px solid #eee;
background: #fafafa;
border: 1px dashed var(--prev-border-color-base);
background: var(--prev-bg-color);
height: 295px; // Fixed height to match theme items
transition: all 0.3s;
.iconic_brush {
font-size: 40px;
color: #ccc;
color: var(--prev-color-text-placeholder);
padding-bottom: 15px;
}
.create-text {
color: #333;
color: var(--prev-color-text-regular);
font-size: 14px;
}
&:hover {
border-color: var(--prev-color-primary);
background: var(--prev-color-primary-light-9);
.iconic_brush,
.create-text {
color: var(--prev-color-primary);
@ -488,7 +508,7 @@ function searchTheme() {
position: relative;
height: 215px;
overflow: hidden;
background: #f0f2f5;
background: var(--prev-bg-main-color);
transition: height 0.3s;
.blur-bg {
@ -508,10 +528,10 @@ function searchTheme() {
z-index: 2;
height: 95%;
margin: 25px 24px 0 24px;
background: #fff;
background: var(--prev-bg-white);
border-radius: 10px 10px 0 0;
overflow: hidden;
border: 2px solid #fff;
border: 2px solid var(--prev-bg-white);
img {
width: 100%;
@ -523,13 +543,13 @@ function searchTheme() {
align-items: center;
height: 100%;
font-size: 12px;
color: #999;
color: var(--prev-color-text-secondary);
}
}
}
.theme-info {
background: #fff;
background: var(--prev-bg-white);
flex: 1;
display: flex;
flex-direction: column;
@ -539,7 +559,7 @@ function searchTheme() {
.theme-title {
padding: 12px 0px 0 12px;
font-size: 14px;
color: #333;
color: var(--prev-color-text-regular);
margin-bottom: 12px;
font-weight: 500;
white-space: nowrap;
@ -550,9 +570,9 @@ function searchTheme() {
justify-content: space-between;
.tag {
font-weight: 400;
color: rgba(2, 86, 255, 1);
color: var(--prev-color-primary);
font-size: 12px;
background: rgba(2, 86, 255, 0.06);
background: var(--prev-color-primary-light-9);
padding: 4px 6px 4px 8px;
border-radius: 10px 0 0 10px;
}
@ -562,7 +582,7 @@ function searchTheme() {
padding: 0 12px 0 12px;
font-size: 12px;
color: #999;
color: var(--prev-color-text-secondary);
margin-bottom: 10px;
}
@ -586,7 +606,6 @@ function searchTheme() {
font-size: 12px;
cursor: pointer;
transition: all 0.3s;
cursor: pointer;
}
.primary {
background: var(--prev-color-primary);
@ -596,10 +615,10 @@ function searchTheme() {
}
}
.default {
background: #f5f5f5;
color: #606266;
background: var(--prev-bg-color);
color: var(--prev-color-text-primary);
&:hover {
background: #e4e7ed;
background: var(--prev-border-color-base);
}
}
}
@ -624,33 +643,31 @@ function searchTheme() {
}
}
}
:deep(.el-dialog__body) {
max-height: calc(100vh - 100px) !important;
}
.detail-content {
display: flex;
flex-direction: column;
height: calc(100vh - 100px);
background: #f5f7fa;
background: var(--prev-bg-color);
.detail-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px 16px;
background: #fff;
border-bottom: 1px solid #eee;
background: var(--prev-bg-white);
border-bottom: 1px solid var(--prev-border-color-lighter);
.back-btn {
cursor: pointer;
color: #666;
color: var(--prev-color-text-primary);
font-size: 14px;
margin-right: 10px;
display: flex;
align-items: center;
transition: color 0.3s;
&:hover {
color: var(--prev-color-primary-light-1);
color: var(--prev-color-primary);
}
i {
@ -661,32 +678,33 @@ function searchTheme() {
.detail-title {
font-size: 14px;
font-weight: 500;
color: #333;
border-left: 1px solid #eee;
color: var(--prev-color-text-regular);
border-left: 1px solid var(--prev-border-color-lighter);
padding-left: 10px;
}
}
.detail-body {
height: calc(100vh - 100px);
flex: 1;
min-height: 0;
overflow-y: auto;
background: #fff;
padding: 0 20px 0 24px !important;
background: var(--prev-bg-white);
padding: 0 20px 24px 24px;
.detail-top {
display: flex;
background: #fff;
background: var(--prev-bg-white);
padding: 24px 0 20px 0;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--prev-border-color-lighter);
.detail-preview-main {
width: 180px;
height: 210px;
border-radius: 8px;
border-radius: 6px;
overflow: hidden;
margin-right: 24px;
border: 1px solid #eeeeee;
border: 1px solid var(--prev-border-color-lighter);
img {
width: 100%;
object-fit: cover;
@ -702,16 +720,15 @@ function searchTheme() {
.info-title {
font-size: 16px;
font-weight: 500;
color: #333;
color: var(--prev-color-text-regular);
margin-bottom: 20px;
}
.info-desc {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 30px;
color: #888;
color: var(--prev-color-text-secondary);
}
}
}
@ -720,7 +737,7 @@ function searchTheme() {
.section-title {
font-size: 16px;
font-weight: 500;
color: #333;
color: var(--prev-color-text-regular);
margin-bottom: 15px;
}
@ -733,14 +750,21 @@ function searchTheme() {
margin-right: 29px;
flex-shrink: 0;
.image-label {
font-size: 12px;
color: var(--prev-color-text-secondary);
text-align: center;
margin-bottom: 10px;
}
.phone-mockup {
width: 214px;
height: 380px;
background: #fff;
border-radius: 8px;
background: var(--prev-bg-white);
border-radius: 6px;
overflow: hidden;
position: relative;
border: 1px solid #eeeeee;
border: 1px solid var(--prev-border-color-lighter);
.preview-image {
width: 100%;
object-fit: cover;
@ -752,7 +776,7 @@ function searchTheme() {
align-items: center;
padding: 10px;
margin-top: 70px;
color: rgba(153, 153, 153, 1);
color: var(--prev-color-text-secondary);
font-size: 13px;
}
}
@ -762,8 +786,8 @@ function searchTheme() {
width: max-content;
height: max-content;
text-align: center;
background: #f9f9f9;
border-radius: 8px 8px 8px 8px;
background: var(--prev-bg-color);
border-radius: 6px;
display: flex;
flex-direction: column;
justify-content: center;
@ -783,10 +807,36 @@ function searchTheme() {
.qrcode-text {
font-size: 12px;
color: #999;
color: var(--prev-color-text-secondary);
}
}
}
}
}
</style>
<style lang="scss">
/* 弹窗壳层覆盖(非 scoped):append-to-body 弹窗 teleport 到 body 后
脱离组件样式作用域,scoped :deep 无法命中,必须用业务前缀全局类 */
.theme-dialog {
border-radius: 6px;
overflow: hidden;
/* header 内边距归零,留白由内部 .dialog-header(15px 24px)提供,
避免与全局 .el-dialog__header 的 15px 叠加成 30px 双重边距 */
.el-dialog__header {
padding: 0 !important;
border-bottom: 1px solid var(--prev-border-color-lighter);
}
.el-dialog__body {
padding: 0 !important;
/* 放开全局 720px 上限,与 top:5vh 匹配,滚动交给内部网格/详情区 */
max-height: calc(100vh - 100px);
&::-webkit-scrollbar {
width: 0;
}
}
}
</style>

View File

@ -8,7 +8,7 @@
</div>
<div class="header-right">
<el-button icon="el-icon-view" @click="$emit('preview')">预览</el-button>
<el-button icon="View" @click="$emit('preview')">预览</el-button>
<el-button v-if="!isMicroPage" @click="$emit('save-template')">另存主题</el-button>
<el-button @click="$emit('save')">保存</el-button>
<el-button type="primary" @click="$emit('save-close')">保存并关闭</el-button>

View File

@ -106,11 +106,7 @@
<div class="main-content">
<div class="filters-header">
<div class="search-box">
<el-input
v-model="searchKeyword"
placeholder="请输入主题名称"
prefix-icon="el-icon-search"
></el-input>
<el-input v-model="searchKeyword" placeholder="请输入主题名称" prefix-icon="Search"></el-input>
</div>
</div>

View File

@ -185,8 +185,11 @@ function setColumnsMenuHighlight(path) {
watch(
() => themeConfigStore.themeConfig.columnsAsideStyle,
(val) => {
val === 'columnsRound' ? (difference.value = 3) : (difference.value = 0);
// 圆角滑块高 44px(项高 50px),top 下移 3px 使滑块在项内垂直居中;卡片滑块顶满 50px 项高,无需偏移。
// 设置面板 radio 下发 kebab-case(columns-round/columns-card),原驼峰比较 columnsRound 永不匹配,一并修正
val === 'columns-round' ? (difference.value = 3) : (difference.value = 0);
},
{ immediate: true },
);
// 监听路由的变化
@ -222,6 +225,8 @@ watch(
color: var(--prev-bg-columnsMenuBarColor);
width: 100%;
height: 50px;
/* 一级菜单图标统一 16px:iconfont 菜单图标组件默认 16px,EP 图标无 .el-menu 上下文会回落 14px */
font-size: 16px;
text-align: center;
display: flex;
cursor: pointer;
@ -260,11 +265,11 @@ watch(
text-decoration: none;
color: var(--prev-bg-columnsMenuBarColor);
}
/* 悬停反馈:以菜单文字色衍生 8% 半透明底,深/浅主题自动适配;不占用选中态主题蓝 */
&:hover {
background: color-mix(in srgb, var(--prev-bg-columnsMenuBarColor) 8%, transparent);
}
}
// li:hover {
// background: var(--prev-color-primary);
// color: var(--prev-bg-columnsMenuBarColor);
// }
.layout-columns {
transition: 0.3s ease-in-out;
}
@ -274,18 +279,19 @@ watch(
transition: 0.3s ease-in-out;
}
/* 圆角选中滑块:项高 50px 内上下各留 3px、左右各留 5px,圆角 6px 与全站控件圆角一致;卡片风格由 .columns-card 覆盖回全宽直角 */
.columns-round {
background: var(--prev-color-primary);
// color: var(--prev-color-text-white);
position: absolute;
left: 50%;
top: 2px;
height: 50px;
width: 65px;
top: 3px;
height: 44px;
width: calc(100% - 10px);
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;
border-radius: 5px;
border-radius: 6px;
}
.columns-card {
@extend .columns-round;

View File

@ -12,7 +12,7 @@
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
prefix-icon="el-icon-search"
prefix-icon="Search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"

View File

@ -526,7 +526,7 @@ function onSetLayout(layout) {
}
themeConfigStore.themeConfig.layout = layout;
themeConfigStore.themeConfig.isDrawer = false;
themeConfigStore.themeConfig.columnsAsideStyle = 'columns-card';
// 高亮风格(columnsAsideStyle)不随布局切换重置,保留用户选择;默认值唯一真源在 store/module/themeConfig.js
setLocalTheme(themeConfigStore.themeConfig.themeStyle);
}

View File

@ -24,7 +24,7 @@ import * as tools from '@/libs/tools';
import timeOptions from '@/libs/timeOptions';
// UI 框架
import ElementPlus from 'element-plus';
import ElementPlus, { ElMessage } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import 'element-plus/dist/index.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
@ -75,6 +75,20 @@ const app = createApp(App);
window.Vue = Vue;
window.ElementPlus = ElementPlus;
// ---- ElMessage 防刷屏:登录过期等场景下多个并发请求同时失败,各页面 catch 各弹一条相同提示。
// 为类型方法统一注入 grouping,由 Element Plus 把相同内容合并为一条并显示重复计数;
// 内容为 VNode 时不支持合并,原样透传。 ----
['success', 'warning', 'info', 'error'].forEach((type) => {
const original = ElMessage[type].bind(ElMessage);
ElMessage[type] = (options) => {
const message = typeof options === 'string' ? options : options?.message;
if (typeof message === 'string' && message) {
return original(typeof options === 'string' ? { message, grouping: true } : { ...options, grouping: true });
}
return original(options);
};
});
// ---- 过渡兼容层:Vue2 时代的 this.bus($on/$off/$emit 转发到 mitt 总线) ----
app.config.globalProperties.bus = {
$on: (type, fn) => bus.on(type, fn),

View File

@ -5,7 +5,7 @@
<el-card :bordered="false" shadow="never">
<div class="card-title">
<el-avatar
icon="el-icon-user-solid"
icon="UserFilled"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">用户</h4>
@ -23,7 +23,7 @@
<el-card :bordered="false" shadow="never" class="dashboard-console-visit">
<div class="card-title">
<el-avatar
icon="el-icon-s-marketing"
icon="DataAnalysis"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">购买用户统计</h4>

View File

@ -7,7 +7,7 @@
<el-row justify="center" align="middle">
<el-col :span="8" class="card-title">
<el-avatar
icon="el-icon-s-marketing"
icon="DataAnalysis"
style="color: var(--prev-color-primary); background-color: #e6f7ff"
></el-avatar>
<h4 class="ivu-pl-8">订单</h4>

View File

@ -7,11 +7,12 @@
<div class="mcp-hero-title">MCP 服务管理</div>
<div class="mcp-hero-sub">安装 MCP 服务,为 AI Agent 扩展更多工具能力</div>
</div>
<el-button type="primary" icon="el-icon-setting" v-db-click @click="openEditor()">配置 MCP</el-button>
<!-- icon prop 需传全局注册的 EP 图标名(PascalCase);el-icon-* 字符串不会解析成组件 -->
<el-button type="primary" icon="Setting" v-db-click @click="openEditor()">配置 MCP</el-button>
</div>
<div class="mcp-toolbar">
<el-input v-model="keyword" placeholder="搜索 MCP" prefix-icon="el-icon-search" clearable class="mcp-search" />
<el-input v-model="keyword" placeholder="搜索 MCP" prefix-icon="Search" clearable class="mcp-search" />
</div>
<div class="mcp-section">
@ -75,7 +76,13 @@
尚未同步工具,点击 <i class="el-icon-refresh" /> 拉取远端工具清单。
</div>
<div v-else class="mcp-tools">
<el-tooltip v-for="tool in row.tools" :key="tool.name" :content="toolTip(tool)" placement="top">
<el-tooltip
v-for="tool in row.tools"
:key="tool.name"
:content="toolTip(tool)"
placement="top"
popper-class="ai-mcp-tool-tip"
>
<span class="tool-chip mono">{{ tool.label || tool.name }}</span>
</el-tooltip>
</div>
@ -609,3 +616,11 @@ function removeServer(row) {
}
}
</style>
<style lang="scss">
/* 工具提示 popper 挂载在 body 下,scoped 样式无法命中,用专用类全局限定宽度 */
.ai-mcp-tool-tip {
max-width: 420px;
line-height: 1.6;
}
</style>

View File

@ -8,7 +8,7 @@
Skill 以 Markdown 文件存储,Agent 对话时按选择注入上下文(Markdown 指南、流程说明等)。
</div>
</div>
<el-button type="primary" v-db-click @click="openEditor()">新建 Skill</el-button>
<el-button type="primary" icon="Plus" v-db-click @click="openEditor()">新建 Skill</el-button>
</div>
<el-table :data="list" v-loading="loading" row-key="key">
@ -41,6 +41,7 @@
<span class="table-link danger" @click="removeSkill(scope.row)">删除</span>
</template>
</el-table-column>
<template #empty>暂无 Skill,点击右上角「新建 Skill」创建</template>
</el-table>
</el-card>
@ -51,6 +52,7 @@
direction="rtl"
size="60%"
append-to-body
class="ai-skill-drawer"
:close-on-click-modal="false"
>
<div class="drawer-body">
@ -122,17 +124,17 @@
direction="rtl"
size="60%"
append-to-body
class="ai-skill-drawer"
:close-on-click-modal="false"
>
<div class="drawer-body">
<div class="toolbar">
<div class="toolbar-tip" style="margin-top: 0">
附属资料不随对话注入,Agent 判断需要更完整资料时按需加载;文件为 .md 格式(SKILL.md 与 EXAMPLES.md
为保留名)。
</div>
<el-button type="primary" size="small" icon="el-icon-plus" v-db-click @click="openAttachmentEditor()"
>新建附件</el-button
>
<div class="attach-tip">
附属资料不随对话注入,Agent 判断需要更完整资料时按需加载;文件为 .md 格式(SKILL.md 与 EXAMPLES.md
为保留名)。
</div>
<div class="attach-toolbar">
<span class="attach-count">共 {{ attachmentList.length }} 个文件</span>
<el-button type="primary" icon="Plus" v-db-click @click="openAttachmentEditor()">新建附件</el-button>
</div>
<el-table :data="attachmentList" v-loading="attachmentLoading" row-key="file">
<el-table-column label="文件名" min-width="200">
@ -155,6 +157,7 @@
<span class="table-link danger" @click="removeAttachment(scope.row)">删除</span>
</template>
</el-table-column>
<template #empty>暂无附属资料,点击右上角「新建附件」添加</template>
</el-table>
</div>
<template #footer>
@ -171,6 +174,7 @@
direction="rtl"
size="60%"
append-to-body
class="ai-skill-drawer"
:close-on-click-modal="false"
>
<div class="drawer-body">
@ -567,18 +571,18 @@ onMounted(() => {
margin-bottom: 20px;
}
.toolbar-title {
color: #303133;
color: var(--prev-color-text-regular);
font-size: 18px;
font-weight: 600;
}
.toolbar-tip,
.muted {
margin-top: 6px;
color: #909399;
color: var(--prev-color-text-secondary);
font-size: 13px;
}
.skill-name {
color: #303133;
color: var(--prev-color-text-regular);
font-weight: 600;
}
.mono {
@ -599,11 +603,11 @@ onMounted(() => {
opacity: 0.8;
}
&.danger {
color: #f56c6c;
color: var(--prev-color-danger);
}
}
.danger {
color: #f56c6c;
color: var(--prev-color-danger);
}
.content-editor :deep(.el-textarea__inner) {
height: max(calc(100vh - 310px), 520px);
@ -613,11 +617,37 @@ onMounted(() => {
line-height: 1.7;
}
.drawer-body {
padding: 0 24px 10px;
/* 左右留白由 .ai-skill-drawer 全局规则(16px)提供,此处只补底部间距,避免双重内边距 */
padding: 0 0 10px;
}
.ai-inline-tip {
margin-left: 10px;
color: #909399;
color: var(--prev-color-text-secondary);
font-size: 12px;
}
/* 附属资料抽屉:说明独立成行,操作行(计数 + 新建按钮)右对齐,与卡片页工具栏同构 */
.attach-tip {
margin-bottom: 14px;
color: var(--prev-color-text-secondary);
font-size: 12px;
line-height: 18px;
}
.attach-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
}
.attach-count {
color: var(--prev-color-text-secondary);
font-size: 12px;
}
</style>
<style lang="scss">
/* 三个抽屉均 append-to-body 挂载在 body 下,scoped 样式无法命中,
用业务前缀类收窄 body 水平内边距(全局 35px 对 60% 宽抽屉偏大) */
.ai-skill-drawer .el-drawer__body {
padding: 16px;
}
</style>

View File

@ -9,7 +9,7 @@
<div class="task-hero-title">自动化任务</div>
<div class="task-hero-sub">让 AI 按计划无人值守执行指令并推送结果(如每日经营日报)</div>
</div>
<el-button type="primary" icon="el-icon-plus" v-db-click @click="openEditor()">新建任务</el-button>
<el-button type="primary" icon="Plus" v-db-click @click="openEditor()">新建任务</el-button>
</div>
<div class="task-section">

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.query.lottery_id ? '编辑' : '新增' }}抽奖活动</span>
</div>

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>

View File

@ -271,4 +271,11 @@ function brokerageSetUp() {
</script>
<style lang="scss" scoped>
@use '../productAdd.scss' as *;
.tip {
width: 100%;
color: #999;
font-size: 12px;
line-height: 20px;
}
</style>

View File

@ -11,7 +11,7 @@
></WangEditor>
</el-form-item>
</el-col>
<el-col :span="6" style="width: 33%">
<el-col :span="8">
<div class="ifam">
<div class="content" v-html="content"></div>
</div>
@ -57,4 +57,16 @@ function getEditorContent(val) {
</script>
<style lang="scss" scoped>
@use '../productAdd.scss' as *;
/* 编辑器整体与右侧手机预览(644px)等高:工具栏自适应行数,编辑区占满剩余高度,底部对齐 */
:deep(.wang-editor) {
display: flex;
flex-direction: column;
height: 644px;
}
:deep(.editor-content) {
flex: 1;
height: auto !important;
}
</style>

View File

@ -1,16 +1,20 @@
<template>
<div>
<!-- <div class="i-layout-page-header header-title">
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div> -->
<pages-header ref="pageHeader" :title="$route.meta.title"></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-card shadow="never" class="ivu-mt mt16">
<div class="flex-wrapper">
<!-- :src="iframeUrl" -->
<div>
<iframe class="iframe-box" :src="iframeUrl" frameborder="0" ref="iframe"></iframe>
<div v-if="iframeUrl" class="preview-box">
<iframe class="iframe-box" :src="iframeUrl" frameborder="0"></iframe>
<div class="mask"></div>
</div>
<div v-else class="preview-box preview-empty">
<el-empty description="未获取到 H5 商城地址,无法预览">
<div class="empty-tips">
请在 系统设置 → 系统配置 中正确配置站点网址(site_url); 如需指向独立 H5 调试地址,可在
frontend/admin/.env.dev 中配置 VITE_APP_H5_URL
</div>
</el-empty>
</div>
<div class="right">
<div class="content">
@ -66,27 +70,18 @@
</template>
<script setup>
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { routineDownload, routineInfo } from '../../../api';
import Setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'routineTemplate' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = ref({
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
});
const iframeUrl = ref(`${Setting.h5BaseURL || location.origin}/pages/index/index?mdType=iframeWindow`);
// H5 预览地址:接口返回站点配置后按优先级组装
// 1. 前端环境变量 VITE_APP_H5_URL(显式指定,如独立 uni-app dev server)
// 2. 后端系统配置站点网址 site_url(默认形态,H5 部署在 backend/public)
// 3. 当前域名(兜底)
const iframeUrl = ref('');
const is_live = ref(1);
const pageData = ref({
code: '',
@ -98,6 +93,9 @@ const pageData = ref({
routineInfo().then((res) => {
pageData.value = res.data;
iframeUrl.value = `${
Setting.h5BaseURL || res.data.site_url || location.origin
}/pages/index/index?mdType=iframeWindow`;
});
function downLoad() {
@ -126,7 +124,7 @@ function downLoadCode(url) {
var url = canvas.toDataURL(); //得到图片的base64编码数据
var a = document.createElement('a'); // 生成一个a元素
var event = new MouseEvent('click'); // 创建一个单击事件
a.download = name || 'photo'; // 设置图片名称
a.download = '小程序码'; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
@ -147,6 +145,23 @@ function downLoadCode(url) {
display: flex;
border-radius: 10px;
}
.preview-box {
position: relative;
width: 312px;
}
.preview-empty {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.empty-tips {
max-width: 260px;
font-size: 12px;
color: var(--prev-color-text-secondary);
line-height: 1.8;
text-align: center;
}
.iframe-box {
width: 312px;
height: 550px;

View File

@ -25,7 +25,7 @@
backgroundSize: '100% 100%',
}"
>
<el-button type="error" icon="el-icon-delete" v-db-click @click="del(i)" v-show="isDel"></el-button>
<el-button type="error" icon="Delete" v-db-click @click="del(i)" v-show="isDel"></el-button>
</div>
<span class="news_sp">{{ item.title }}</span>
</div>
@ -34,7 +34,7 @@
<div class="news_cent_img ivu-mr-8">
<img :src="item.image_input ? item.image_input : baseImg" />
</div>
<el-button type="error" icon="el-icon-delete" v-db-click @click="del(i)"></el-button>
<el-button type="error" icon="Delete" v-db-click @click="del(i)"></el-button>
</div>
</div>
<!-- <div class="acea-row row-center-wrapper">

View File

@ -185,13 +185,7 @@
</div>
</div>
</div>
<el-button
v-else
class="upload-select"
type="dashed"
icon="el-icon-plus"
@click="modalPicTap(1)"
></el-button>
<el-button v-else class="upload-select" type="dashed" icon="Plus" @click="modalPicTap(1)"></el-button>
<p class="tip">建议尺寸:80px*30px,若未上传则为空白</p>
</el-form-item>
<el-form-item label="排序:">

View File

@ -91,7 +91,7 @@
<el-form ref="modelForm" :model="form" :rules="rules" label-position="top">
<el-form-item prop="base_url">
<div class="base-url-head">
<span class="base-url-label">自定义请求地址<i class="required-star">*</i></span>
<span class="base-url-label"><i class="required-star">*</i>自定义请求地址</span>
<span class="base-url-switch">
<i class="el-icon-link" />
<span>完整 URL</span>
@ -463,14 +463,16 @@ onMounted(() => {
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 4px;
/* 标签(0) + 提示(6px) 合计 8px,对齐 EP top 标签与内容的默认间距 */
margin-bottom: 2px;
}
.base-url-label {
color: var(--prev-color-text-primary);
font-size: 14px;
font-weight: 600;
/* 与全局 .el-form-item__label(12px / text-regular)对齐,避免比其他标签大一圈 */
color: var(--prev-color-text-regular);
font-size: 12px;
font-weight: 400;
.required-star {
margin-left: 4px;
margin-right: 4px;
color: var(--prev-color-danger);
font-style: normal;
}
@ -480,7 +482,7 @@ onMounted(() => {
align-items: center;
gap: 6px;
color: var(--prev-color-text-primary);
font-size: 13px;
font-size: 12px;
i {
color: var(--prev-color-text-secondary);
}

View File

@ -2,9 +2,7 @@
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<el-button class="btn-back" icon="el-icon-arrow-left" type="text" v-db-click @click="$router.go(-1)"
>返回</el-button
>
<el-button class="btn-back" icon="ArrowLeft" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>

View File

@ -27,61 +27,66 @@
</el-form-item>
</el-col>
<el-col :span="24" v-for="(item, index) in formValidate.typelist" :key="index">
<el-col v-bind="grid">
<el-form-item
:label="'字段' + (index + 1) + ':'"
label-width="90px"
:prop="'typelist.' + index + '.name.value'"
:rules="{ required: true, message: '请输入字段名称:姓名', trigger: 'blur' }"
<el-row :gutter="20">
<el-col v-bind="grid">
<el-form-item
:label="'字段' + (index + 1) + ':'"
label-width="90px"
:prop="'typelist.' + index + '.name.value'"
:rules="{ required: true, message: '请输入字段名称:姓名', trigger: 'blur' }"
>
<el-input v-model="item.name.value" placeholder="字段名称:姓名"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.title.value'"
:rules="{ required: true, message: '请输入字段配置名', trigger: 'blur' }"
>
<el-input v-model="item.title.value" placeholder="字段配置名:name"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.type.value'"
:rules="{ required: true, message: '请选择字段类型', trigger: 'change' }"
>
<el-select placeholder="字段类型" v-model="item.type.value">
<el-option value="input">文本框</el-option>
<el-option value="textarea">多行文本框</el-option>
<el-option value="radio">单选框</el-option>
<el-option value="checkbox">多选框</el-option>
<el-option value="select">下拉选择</el-option>
<el-option value="upload">单图</el-option>
<el-option value="uploads">多图</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col v-bind="gridBtn">
<el-form-item label-width="0">
<el-button icon="Delete" v-db-click @click="delGroup(index)"></el-button>
</el-form-item>
</el-col>
<el-col
v-if="item.type.value === 'radio' || item.type.value === 'checkbox' || item.type.value === 'select'"
:span="24"
>
<el-input v-model="item.name.value" placeholder="字段名称:姓名"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid" class="goupBox">
<el-form-item
label-width="0"
:prop="'typelist.' + index + '.title.value'"
:rules="{ required: true, message: '请输入字段配置名', trigger: 'blur' }"
>
<el-input v-model="item.title.value" placeholder="字段配置名:name"></el-input>
</el-form-item>
</el-col>
<el-col v-bind="grid" prop="type" class="goupBox">
<el-form-item
:prop="'typelist.' + index + '.type.value'"
:rules="{ required: true, message: '请选择字段类型', trigger: 'change' }"
label-width="0"
>
<el-select placeholder="字段类型" v-model="item.type.value">
<el-option value="input">文本框</el-option>
<el-option value="textarea">多行文本框</el-option>
<el-option value="radio">单选框</el-option>
<el-option value="checkbox">多选框</el-option>
<el-option value="select">下拉选择</el-option>
<el-option value="upload">单图</el-option>
<el-option value="uploads">多图</el-option>
</el-select>
</el-form-item>
</el-col>
<el-button icon="el-icon-delete" v-db-click @click="delGroup(index)"></el-button>
<!-- <el-col span="1"> </el-col> -->
<el-col
:span="24"
v-if="item.type.value === 'radio' || item.type.value === 'checkbox' || item.type.value === 'select'"
>
<el-form-item
:prop="'typelist.' + index + '.param.value'"
:rules="{ required: true, message: '请输入参数方式', trigger: 'blur' }"
>
<el-input
type="textarea"
:rows="4"
:placeholder="item.param.placeholder"
v-model="item.param.value"
style="width: 90%"
></el-input>
</el-form-item>
</el-col>
<el-form-item
:prop="'typelist.' + index + '.param.value'"
:rules="{ required: true, message: '请输入参数方式', trigger: 'blur' }"
>
<el-input
type="textarea"
:rows="4"
:placeholder="item.param.placeholder"
v-model="item.param.value"
style="width: 90%"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col>
<el-form-item>
@ -134,6 +139,13 @@ const grid = {
sm: 24,
xs: 24,
};
const gridBtn = {
xl: 3,
lg: 3,
md: 12,
sm: 24,
xs: 24,
};
const modals = ref(false);
const modal12 = ref(false);
const ruleValidate = {
@ -235,7 +247,4 @@ defineExpose({ modals, fromData });
.cur {
cursor: pointer;
}
.goupBox :deep(.ivu-form-item-content) {
margin-left: 43px !important;
}
</style>

View File

@ -80,7 +80,7 @@ export const useThemeConfigStore = defineStore(
// 主页面切换动画:可选值"<slide-right|slide-left|opacitys>",默认 slide-right
animation: 'opacitys',
// 分栏高亮风格:可选值"<columns-round|columns-card>",默认 columns-round
columnsAsideStyle: 'columns-card',
columnsAsideStyle: 'columns-round',
// 分栏布局风格:可ƒ选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
columnsAsideLayout: 'columns-vertical',

View File

@ -394,6 +394,14 @@
text-decoration: none;
}
// 二级菜单展开抖动修复:EP 的 ElCollapseTransition 动画在 overflow visible/hidden 间切换,
// .columns-round 给菜单项加的 margin 在 visible 时向外塌陷、hidden 时被包含,导致 scrollHeight
// 测量值与实际内容高度不一致,动画起止瞬间高度突变(肉眼可见的上跳)。
// flow-root 让内联子菜单始终成为 BFC,margin 全程被包含,三者高度一致;间隙视觉不变。
.layout-aside .el-menu .el-menu--inline {
display: flow-root;
}
// 菜单收起时鼠标经过背景颜色/字体颜色
.el-menu--vertical {
background: var(--prev-bg-menuBar) !important;