25 changed files with 924 additions and 9 deletions
-
71client/dist/chatbot-sdk.js
-
2client/dist/chatbot-sdk.js.map
-
2client/dist/chatbot-sdk.min.js
-
2client/dist/chatbot-sdk.min.js.map
-
21client/src/api.ts
-
1client/src/config.ts
-
17client/src/dom.ts
-
19client/src/index.ts
-
17client/src/styles.ts
-
4client/src/types.ts
-
53src/main/java/com/wok/supportbot/config/DatabaseInitConfig.java
-
30src/main/java/com/wok/supportbot/controller/AiController.java
-
134src/main/java/com/wok/supportbot/controller/SystemConfigController.java
-
12src/main/java/com/wok/supportbot/dao/SystemConfigMapper.java
-
58src/main/java/com/wok/supportbot/entity/SystemConfig.java
-
2src/main/java/com/wok/supportbot/security/SecurityConfig.java
-
106src/main/java/com/wok/supportbot/service/SystemConfigService.java
-
273src/main/resources/static/components/SystemConfigManager.js
-
22src/main/resources/static/js/api.js
-
6src/main/resources/static/js/app.js
-
4src/main/resources/static/js/store.js
-
71src/main/resources/static/sdk/chatbot-sdk.js
-
2src/main/resources/static/sdk/chatbot-sdk.js.map
-
2src/main/resources/static/sdk/chatbot-sdk.min.js
-
2src/main/resources/static/sdk/chatbot-sdk.min.js.map
2
client/dist/chatbot-sdk.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
client/dist/chatbot-sdk.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
client/dist/chatbot-sdk.min.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,134 @@ |
|||||
|
package com.wok.supportbot.controller; |
||||
|
|
||||
|
import com.wok.supportbot.entity.SystemConfig; |
||||
|
import com.wok.supportbot.service.SystemConfigService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.http.ResponseEntity; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* 系统配置管理接口 |
||||
|
* <p> |
||||
|
* 管理后台编辑系统级配置项(SDK 保密声明等), |
||||
|
* 管理接口需要 admin 角色。 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@RestController |
||||
|
@RequestMapping("/system-config") |
||||
|
public class SystemConfigController { |
||||
|
|
||||
|
@Autowired |
||||
|
private SystemConfigService systemConfigService; |
||||
|
|
||||
|
/** |
||||
|
* 查询所有系统配置列表 |
||||
|
*/ |
||||
|
@GetMapping("/list") |
||||
|
@PreAuthorize("hasRole('admin')") |
||||
|
public ResponseEntity<Map<String, Object>> list() { |
||||
|
try { |
||||
|
List<SystemConfig> configs = systemConfigService.listAll(); |
||||
|
return ResponseEntity.ok(Map.of( |
||||
|
"success", true, |
||||
|
"data", configs |
||||
|
)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("查询系统配置列表失败", e); |
||||
|
return ResponseEntity.status(500).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "查询失败:" + e.getMessage() |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询单条系统配置 |
||||
|
* |
||||
|
* @param key 配置键(如 "disclaimer") |
||||
|
*/ |
||||
|
@GetMapping("/{key}") |
||||
|
@PreAuthorize("hasRole('admin')") |
||||
|
public ResponseEntity<Map<String, Object>> getByKey(@PathVariable String key) { |
||||
|
try { |
||||
|
SystemConfig config = systemConfigService.getByKey(key); |
||||
|
if (config == null) { |
||||
|
return ResponseEntity.status(404).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "配置不存在:" + key |
||||
|
)); |
||||
|
} |
||||
|
return ResponseEntity.ok(Map.of( |
||||
|
"success", true, |
||||
|
"data", config |
||||
|
)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("查询系统配置失败, key={}", key, e); |
||||
|
return ResponseEntity.status(500).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "查询失败:" + e.getMessage() |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新系统配置 |
||||
|
* |
||||
|
* @param key 配置键(如 "disclaimer") |
||||
|
* @param body 请求体 { "configValue": "...", "description": "..." } |
||||
|
*/ |
||||
|
@PutMapping("/{key}") |
||||
|
@PreAuthorize("hasRole('admin')") |
||||
|
public ResponseEntity<Map<String, Object>> update( |
||||
|
@PathVariable String key, |
||||
|
@RequestBody Map<String, String> body) { |
||||
|
try { |
||||
|
String configValue = body.getOrDefault("configValue", ""); |
||||
|
String description = body.getOrDefault("description", ""); |
||||
|
systemConfigService.saveOrUpdate(key, configValue, description); |
||||
|
return ResponseEntity.ok(Map.of( |
||||
|
"success", true, |
||||
|
"message", "保存成功" |
||||
|
)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("更新系统配置失败", e); |
||||
|
return ResponseEntity.status(500).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "保存失败:" + e.getMessage() |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除系统配置(逻辑删除) |
||||
|
* |
||||
|
* @param key 配置键(如 "disclaimer") |
||||
|
*/ |
||||
|
@DeleteMapping("/{key}") |
||||
|
@PreAuthorize("hasRole('admin')") |
||||
|
public ResponseEntity<Map<String, Object>> delete(@PathVariable String key) { |
||||
|
try { |
||||
|
boolean deleted = systemConfigService.deleteByKey(key); |
||||
|
if (!deleted) { |
||||
|
return ResponseEntity.status(404).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "配置不存在:" + key |
||||
|
)); |
||||
|
} |
||||
|
return ResponseEntity.ok(Map.of( |
||||
|
"success", true, |
||||
|
"message", "删除成功" |
||||
|
)); |
||||
|
} catch (Exception e) { |
||||
|
log.error("删除系统配置失败, key={}", key, e); |
||||
|
return ResponseEntity.status(500).body(Map.of( |
||||
|
"success", false, |
||||
|
"message", "删除失败:" + e.getMessage() |
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
package com.wok.supportbot.dao; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.wok.supportbot.entity.SystemConfig; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* 系统配置 Mapper |
||||
|
*/ |
||||
|
@Mapper |
||||
|
public interface SystemConfigMapper extends BaseMapper<SystemConfig> { |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
package com.wok.supportbot.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.*; |
||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Builder; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.io.Serial; |
||||
|
import java.io.Serializable; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* 系统配置实体(key-value 模式,支持灵活扩展) |
||||
|
*/ |
||||
|
@Data |
||||
|
@Builder |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
@TableName("system_config") |
||||
|
public class SystemConfig implements Serializable { |
||||
|
|
||||
|
@Serial |
||||
|
@TableField(exist = false) |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
/** 主键ID(雪花算法) */ |
||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID) |
||||
|
@JsonSerialize(using = ToStringSerializer.class) |
||||
|
private Long id; |
||||
|
|
||||
|
/** 配置键(唯一标识,如 "disclaimer") */ |
||||
|
@TableField("config_key") |
||||
|
private String configKey; |
||||
|
|
||||
|
/** 配置值 */ |
||||
|
@TableField("config_value") |
||||
|
private String configValue; |
||||
|
|
||||
|
/** 描述说明 */ |
||||
|
@TableField("description") |
||||
|
private String description; |
||||
|
|
||||
|
/** 创建时间 */ |
||||
|
@TableField(value = "create_time", fill = FieldFill.INSERT) |
||||
|
private Date createTime; |
||||
|
|
||||
|
/** 更新时间 */ |
||||
|
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) |
||||
|
private Date updateTime; |
||||
|
|
||||
|
/** 逻辑删除标识 */ |
||||
|
@TableField("is_delete") |
||||
|
@TableLogic |
||||
|
private boolean isDelete; |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
package com.wok.supportbot.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
||||
|
import com.wok.supportbot.dao.SystemConfigMapper; |
||||
|
import com.wok.supportbot.entity.SystemConfig; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 系统配置 CRUD 服务 |
||||
|
* <p> |
||||
|
* 使用 key-value 模式存储系统级配置项(如 SDK 保密声明等), |
||||
|
* 便于管理后台灵活编辑、SDK 端动态拉取。 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Service |
||||
|
public class SystemConfigService { |
||||
|
|
||||
|
@Autowired |
||||
|
private SystemConfigMapper systemConfigMapper; |
||||
|
|
||||
|
/** |
||||
|
* 根据 key 查询配置值(仅返回 configValue 字符串) |
||||
|
* |
||||
|
* @param key 配置键 |
||||
|
* @return configValue,不存在返回 null |
||||
|
*/ |
||||
|
public String getValueByKey(String key) { |
||||
|
SystemConfig config = getByKey(key); |
||||
|
return config != null ? config.getConfigValue() : null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据 key 查询完整配置实体 |
||||
|
* |
||||
|
* @param key 配置键 |
||||
|
* @return 配置实体,不存在返回 null |
||||
|
*/ |
||||
|
public SystemConfig getByKey(String key) { |
||||
|
LambdaQueryWrapper<SystemConfig> wrapper = new LambdaQueryWrapper<>(); |
||||
|
wrapper.eq(SystemConfig::getConfigKey, key); |
||||
|
return systemConfigMapper.selectOne(wrapper); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 保存或更新配置(upsert 语义) |
||||
|
* |
||||
|
* @param key 配置键 |
||||
|
* @param value 配置值 |
||||
|
* @param description 描述说明 |
||||
|
*/ |
||||
|
public void saveOrUpdate(String key, String value, String description) { |
||||
|
SystemConfig existing = getByKey(key); |
||||
|
if (existing != null) { |
||||
|
// 更新 configValue、description 和 updateTime |
||||
|
LambdaUpdateWrapper<SystemConfig> wrapper = new LambdaUpdateWrapper<>(); |
||||
|
wrapper.eq(SystemConfig::getId, existing.getId()) |
||||
|
.set(SystemConfig::getConfigValue, value) |
||||
|
.set(SystemConfig::getDescription, description) |
||||
|
.set(SystemConfig::getUpdateTime, new Date()); |
||||
|
systemConfigMapper.update(null, wrapper); |
||||
|
log.info("更新系统配置:{}", key); |
||||
|
} else { |
||||
|
// 新建 |
||||
|
SystemConfig config = SystemConfig.builder() |
||||
|
.configKey(key) |
||||
|
.configValue(value) |
||||
|
.description(description) |
||||
|
.build(); |
||||
|
systemConfigMapper.insert(config); |
||||
|
log.info("新建系统配置:{}", key); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询所有未删除的配置列表 |
||||
|
* |
||||
|
* @return 配置实体列表 |
||||
|
*/ |
||||
|
public List<SystemConfig> listAll() { |
||||
|
LambdaQueryWrapper<SystemConfig> wrapper = new LambdaQueryWrapper<>(); |
||||
|
wrapper.orderByAsc(SystemConfig::getConfigKey); |
||||
|
return systemConfigMapper.selectList(wrapper); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据 key 删除配置(逻辑删除) |
||||
|
* |
||||
|
* @param key 配置键 |
||||
|
* @return true 删除成功,false 配置不存在 |
||||
|
*/ |
||||
|
public boolean deleteByKey(String key) { |
||||
|
SystemConfig existing = getByKey(key); |
||||
|
if (existing == null) { |
||||
|
return false; |
||||
|
} |
||||
|
systemConfigMapper.deleteById(existing.getId()); |
||||
|
log.info("删除系统配置:{}", key); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,273 @@ |
|||||
|
/** |
||||
|
* 系统配置管理组件 |
||||
|
* <p> |
||||
|
* 管理后台编辑系统级配置项(SDK 保密声明等), |
||||
|
* 支持 HTML 编辑 + 实时预览。 |
||||
|
*/ |
||||
|
import { getSystemConfig, listSystemConfigs, updateSystemConfig, deleteSystemConfig } from '../js/api.js' |
||||
|
import { toast } from '../js/utils.js' |
||||
|
|
||||
|
export default { |
||||
|
template: `
|
||||
|
<div class="card"> |
||||
|
<h3>🔧 系统配置</h3> |
||||
|
<p style="color: var(--text-secondary); margin-bottom: 16px; font-size: 14px;"> |
||||
|
管理系统级配置项,修改后实时生效(SDK 窗口刷新即拉取最新内容)。 |
||||
|
</p> |
||||
|
|
||||
|
<!-- 配置列表 --> |
||||
|
<div v-if="!editing" style="display: flex; flex-direction: column; gap: 12px;"> |
||||
|
<!-- 操作栏 --> |
||||
|
<div style="display: flex; gap: 8px; margin-bottom: 4px;"> |
||||
|
<button @click="startAdd" |
||||
|
style="padding: 6px 14px; background: var(--primary); color: #fff; border: none; |
||||
|
border-radius: 6px; cursor: pointer; font-size: 13px;"> |
||||
|
+ 新建配置 |
||||
|
</button> |
||||
|
<button @click="load" style="padding: 6px 14px; |
||||
|
background: var(--bg); border: 1px solid var(--border); border-radius: 6px; |
||||
|
cursor: pointer; font-size: 13px; color: var(--text-secondary);"> |
||||
|
🔄 刷新 |
||||
|
</button> |
||||
|
</div> |
||||
|
<div v-if="loading" style="text-align: center; padding: 40px; color: var(--text-secondary);"> |
||||
|
加载中... |
||||
|
</div> |
||||
|
<div v-else-if="configs.length === 0" style="text-align: center; padding: 40px; color: var(--text-secondary);"> |
||||
|
暂无配置项,点击「新建配置」开始 |
||||
|
</div> |
||||
|
<div v-else v-for="item in configs" :key="item.configKey" |
||||
|
style="display: flex; align-items: center; justify-content: space-between; |
||||
|
padding: 14px 16px; border: 1px solid var(--border); border-radius: 8px; |
||||
|
cursor: pointer; transition: border-color 0.2s, box-shadow 0.2s;" |
||||
|
@click="edit(item)" |
||||
|
@mouseenter="e => e.currentTarget.style.borderColor = 'var(--primary)'" |
||||
|
@mouseleave="e => e.currentTarget.style.borderColor = 'var(--border)'"> |
||||
|
<div style="flex: 1; min-width: 0;"> |
||||
|
<div style="font-weight: 600; font-size: 14px; margin-bottom: 2px;"> |
||||
|
{{ item.configKey }} |
||||
|
</div> |
||||
|
<div style="font-size: 12px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> |
||||
|
{{ item.description || '无描述' }} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div style="font-size: 12px; color: var(--text-secondary); margin-left: 16px; white-space: nowrap;"> |
||||
|
{{ item.updateTime ? formatTime(item.updateTime) : '' }} |
||||
|
</div> |
||||
|
<button @click.stop="deleteItem(item)" |
||||
|
style="margin-left: 10px; padding: 4px 10px; background: #fee2e2; color: #dc2626; |
||||
|
border: none; border-radius: 6px; cursor: pointer; font-size: 12px;" |
||||
|
title="删除配置"> |
||||
|
🗑 删除 |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 编辑区 --> |
||||
|
<div v-else> |
||||
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;"> |
||||
|
<button @click="cancelEdit" |
||||
|
style="padding: 4px 10px; background: var(--bg); border: 1px solid var(--border); |
||||
|
border-radius: 6px; cursor: pointer; font-size: 13px;"> |
||||
|
← 返回列表 |
||||
|
</button> |
||||
|
<span style="font-weight: 600; font-size: 15px;">{{ isAdd ? '新建' : '编辑' }} {{ editingKey }}</span> |
||||
|
<span v-if="!isAdd && editingItem.description" style="font-size: 12px; color: var(--text-secondary);"> |
||||
|
— {{ editingItem.description }} |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 配置键和描述(新建和编辑均显示) --> |
||||
|
<div style="display: flex; gap: 12px; margin-bottom: 12px; flex-wrap: wrap;"> |
||||
|
<div style="flex: 1; min-width: 180px;"> |
||||
|
<label style="display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; color: var(--text-secondary);"> |
||||
|
配置键 <span style="color: #ef4444;">*</span> |
||||
|
</label> |
||||
|
<input v-model="form.configKey" :disabled="!isAdd" |
||||
|
:placeholder="isAdd ? '如 disclaimer、welcome_text' : ''" |
||||
|
:style="'width: 100%; padding: 6px 10px; border: 1px solid var(--border); ' + |
||||
|
'border-radius: 6px; font-size: 13px; background: ' + (isAdd ? 'var(--bg)' : 'var(--border)') + '; color: var(--text);'" /> |
||||
|
</div> |
||||
|
<div style="flex: 1; min-width: 180px;"> |
||||
|
<label style="display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px; color: var(--text-secondary);"> |
||||
|
描述说明 |
||||
|
</label> |
||||
|
<input v-model="form.description" placeholder="用于说明此配置的用途" |
||||
|
style="width: 100%; padding: 6px 10px; border: 1px solid var(--border); |
||||
|
border-radius: 6px; font-size: 13px; background: var(--bg); color: var(--text);" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div style="display: flex; gap: 16px; flex-wrap: wrap;"> |
||||
|
<!-- 编辑区 --> |
||||
|
<div style="flex: 1; min-width: 300px;"> |
||||
|
<label style="display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: var(--text-secondary);"> |
||||
|
HTML 内容 |
||||
|
</label> |
||||
|
<textarea v-model="form.configValue" rows="14" |
||||
|
style="width: 100%; padding: 10px 12px; border: 1px solid var(--border); |
||||
|
border-radius: 8px; font-family: 'Consolas', 'Monaco', monospace; |
||||
|
font-size: 13px; line-height: 1.5; resize: vertical; |
||||
|
background: var(--bg); color: var(--text);" |
||||
|
placeholder="输入 HTML 内容..."></textarea> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 预览区 --> |
||||
|
<div style="flex: 1; min-width: 300px;"> |
||||
|
<label style="display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: var(--text-secondary);"> |
||||
|
👁 实时预览 |
||||
|
</label> |
||||
|
<div style="border: 1px solid var(--border); border-radius: 8px; padding: 12px; |
||||
|
min-height: 200px; background: #f9fafb; font-size: 10px; color: #9CA3AF; |
||||
|
line-height: 1.5; overflow: auto;"> |
||||
|
<div v-html="form.configValue" style="margin: 0;"></div> |
||||
|
<div v-if="!form.configValue" style="color: #ccc; font-style: italic;"> |
||||
|
预览区域(输入内容后实时显示效果) |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<div style="display: flex; gap: 8px; margin-top: 16px;"> |
||||
|
<button @click="save" |
||||
|
:disabled="saving" |
||||
|
style="padding: 8px 20px; background: var(--primary); color: #fff; border: none; |
||||
|
border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500;"> |
||||
|
{{ saving ? '保存中...' : '💾 保存' }} |
||||
|
</button> |
||||
|
<button @click="cancelEdit" |
||||
|
style="padding: 8px 20px; background: var(--bg); border: 1px solid var(--border); |
||||
|
border-radius: 8px; cursor: pointer; font-size: 14px;"> |
||||
|
取消 |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
`,
|
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
configs: [], |
||||
|
loading: false, |
||||
|
saving: false, |
||||
|
editing: false, |
||||
|
isAdd: false, |
||||
|
editingKey: '', |
||||
|
editingItem: {}, |
||||
|
form: { configKey: '', configValue: '', description: '' } |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
mounted() { |
||||
|
this.load() |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
async load() { |
||||
|
this.loading = true |
||||
|
try { |
||||
|
const json = await listSystemConfigs() |
||||
|
if (json.success) { |
||||
|
this.configs = json.data || [] |
||||
|
} |
||||
|
} catch (e) { |
||||
|
toast(e.message || '加载失败', 'error') |
||||
|
} finally { |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
edit(item) { |
||||
|
this.isAdd = false |
||||
|
this.editingKey = item.configKey |
||||
|
this.editingItem = item |
||||
|
this.form.configKey = item.configKey |
||||
|
this.form.configValue = item.configValue || '' |
||||
|
this.form.description = item.description || '' |
||||
|
this.editing = true |
||||
|
}, |
||||
|
|
||||
|
startAdd() { |
||||
|
this.isAdd = true |
||||
|
this.editingKey = '' |
||||
|
this.editingItem = {} |
||||
|
this.form.configKey = '' |
||||
|
this.form.configValue = '' |
||||
|
this.form.description = '' |
||||
|
this.editing = true |
||||
|
}, |
||||
|
|
||||
|
cancelEdit() { |
||||
|
this.editing = false |
||||
|
this.isAdd = false |
||||
|
this.editingKey = '' |
||||
|
this.editingItem = {} |
||||
|
this.form.configKey = '' |
||||
|
this.form.configValue = '' |
||||
|
this.form.description = '' |
||||
|
}, |
||||
|
|
||||
|
async save() { |
||||
|
if (this.saving) return |
||||
|
|
||||
|
// 新建时需要校验 configKey
|
||||
|
if (this.isAdd) { |
||||
|
const key = (this.form.configKey || '').trim() |
||||
|
if (!key) { |
||||
|
toast('请输入配置键', 'error') |
||||
|
return |
||||
|
} |
||||
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(key)) { |
||||
|
toast('配置键只能包含字母、数字、下划线和连字符,且以字母开头', 'error') |
||||
|
return |
||||
|
} |
||||
|
this.editingKey = key |
||||
|
} |
||||
|
|
||||
|
this.saving = true |
||||
|
try { |
||||
|
const json = await updateSystemConfig( |
||||
|
this.editingKey, |
||||
|
this.form.configValue, |
||||
|
this.form.description || this.editingItem.description || '' |
||||
|
) |
||||
|
if (json.success) { |
||||
|
toast('保存成功', 'success') |
||||
|
this.cancelEdit() |
||||
|
this.load() |
||||
|
} else { |
||||
|
toast(json.message || '保存失败', 'error') |
||||
|
} |
||||
|
} catch (e) { |
||||
|
toast(e.message || '保存失败', 'error') |
||||
|
} finally { |
||||
|
this.saving = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
async deleteItem(item) { |
||||
|
if (!confirm(`确定删除配置「${item.configKey}」?`)) return |
||||
|
try { |
||||
|
const json = await deleteSystemConfig(item.configKey) |
||||
|
if (json.success) { |
||||
|
toast('删除成功', 'success') |
||||
|
this.load() |
||||
|
} else { |
||||
|
toast(json.message || '删除失败', 'error') |
||||
|
} |
||||
|
} catch (e) { |
||||
|
toast(e.message || '删除失败', 'error') |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** 简单时间格式化 */ |
||||
|
formatTime(dateStr) { |
||||
|
if (!dateStr) return '' |
||||
|
const d = new Date(dateStr) |
||||
|
const pad = n => String(n).padStart(2, '0') |
||||
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + |
||||
|
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
2
src/main/resources/static/sdk/chatbot-sdk.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/main/resources/static/sdk/chatbot-sdk.min.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2
src/main/resources/static/sdk/chatbot-sdk.min.js.map
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue