Browse Source

重构客服角色管理页面排版

TDesign-Vue-Next-1.20.6
wanghanlin 2 weeks ago
parent
commit
7ce8028d73
  1. BIN
      client/assets/launcher-logo.png
  2. 6
      client/dist/chatbot-sdk.js
  3. 2
      client/dist/chatbot-sdk.min.js
  4. 6
      client/src/styles.ts
  5. 572
      frontend/src/views/RoleManager.vue
  6. 5
      src/main/java/com/wok/supportbot/controller/CustomerServiceRoleController.java
  7. 30
      src/main/java/com/wok/supportbot/service/CustomerServiceRoleService.java
  8. 6
      src/main/resources/static/sdk/chatbot-sdk.js
  9. 2
      src/main/resources/static/sdk/chatbot-sdk.min.js
  10. BIN
      src/main/resources/static/sdk/launcher-logo.png

BIN
client/assets/launcher-logo.png

Before

Width: 1024  |  Height: 1024  |  Size: 87 KiB

After

Width: 1254  |  Height: 1254  |  Size: 178 KiB

6
client/dist/chatbot-sdk.js

@ -1095,9 +1095,9 @@ var ChatbotSDK = (function () {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15)); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15));
} }
.csk-launcher img { .csk-launcher img {
width: 36px;
height: 36px;
object-fit: contain;
width: 100%;
height: 100%;
object-fit: cover;
position: relative; position: relative;
z-index: 1; z-index: 1;
-webkit-user-drag: none; -webkit-user-drag: none;

2
client/dist/chatbot-sdk.min.js
File diff suppressed because it is too large
View File

6
client/src/styles.ts

@ -118,9 +118,9 @@ function getStyles(config: ResolvedConfig): string {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15)); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15));
} }
.csk-launcher img { .csk-launcher img {
width: 36px;
height: 36px;
object-fit: contain;
width: 100%;
height: 100%;
object-fit: cover;
position: relative; position: relative;
z-index: 1; z-index: 1;
-webkit-user-drag: none; -webkit-user-drag: none;

572
frontend/src/views/RoleManager.vue

@ -2,88 +2,538 @@
<t-card title="🎭 客服角色管理" :bordered="false"> <t-card title="🎭 客服角色管理" :bordered="false">
<p class="desc-text">为角色设定人设并授权可检索的知识库分类对话页只能选择这里配置好的角色检索范围由服务端按角色强制限定</p> <p class="desc-text">为角色设定人设并授权可检索的知识库分类对话页只能选择这里配置好的角色检索范围由服务端按角色强制限定</p>
<!-- 新增/编辑表单 -->
<div class="card-box form-bar">
<t-row :gutter="12">
<t-col :span="4"><t-input v-model="form.name" placeholder="角色名称 *" size="small" /></t-col>
<t-col :span="4"><t-input v-model="form.description" placeholder="职责描述(可选)" size="small" /></t-col>
<t-col :span="2"><t-checkbox v-model="form.enabled">启用</t-checkbox></t-col>
</t-row>
<t-textarea v-model="form.prompt" :autosize="{minRows:2,maxRows:3}" placeholder="角色人设/风格提示词" style="margin-top:8px;" />
<t-space style="margin-top:8px;">
<t-button theme="success" size="small" @click="submit">{{ editingId?'保存修改':'新增角色' }}</t-button>
<t-button v-show="editingId" variant="outline" size="small" @click="resetForm">取消编辑</t-button>
<t-button variant="outline" size="small" @click="reload">刷新</t-button>
</t-space>
</div>
<!-- 角色列表 -->
<t-empty v-if="!roles.length" description="暂无角色" />
<div v-else>
<div v-for="role in roles" :key="role.id" class="role-card" :style="{opacity:role.enabled?'1':'.6'}">
<div class="role-header">
<div>
<strong class="role-name">{{ role.name }}</strong>
<t-tag v-if="!role.enabled" size="small" theme="danger" variant="light" style="margin-left:6px;">已停用</t-tag>
<div class="role-desc">{{ role.description || '—' }}</div>
</div>
<t-space :size="4">
<t-button size="small" variant="text" @click="startEdit(role)">编辑</t-button>
<t-button size="small" variant="text" theme="danger" @click="remove(role)">删除</t-button>
<div class="role-shell">
<!-- ===== 左侧角色列表 ===== -->
<aside class="role-sidebar">
<div class="sidebar-header">
<span class="sidebar-title">角色列表</span>
<t-space :size="6">
<t-button theme="primary" size="small" @click="startCreate"> 新增</t-button>
<t-button variant="outline" size="small" @click="handleRefresh">刷新</t-button>
</t-space> </t-space>
</div> </div>
<div v-if="role.prompt" class="role-prompt">人设{{ role.prompt }}</div>
<div class="role-categories">
<span class="cat-label">可检索知识库</span>
<t-checkbox v-for="c in categoryStore.categories" :key="c.id"
:checked="role.categoryIds.includes(String(c.id))"
@change="toggleCategory(role, String(c.id), ($event as any))">
{{ c.name }}
</t-checkbox>
<span v-if="!categoryStore.categories.length" class="cat-hint">暂无知识库分类</span>
</div>
</div>
<t-loading size="small" :loading="loading" class="sidebar-body">
<t-empty v-if="!roles.length" description="暂无角色" />
<div v-else class="role-items">
<div
v-for="role in roles"
:key="role.id"
:class="['role-item', selectedId === role.id && 'active']"
@click="selectRole(role)"
tabindex="0"
role="button"
:aria-label="'选择角色 ' + role.name"
@keydown.enter="selectRole(role)"
@keydown.space.prevent="selectRole(role)"
>
<div class="role-item-main">
<strong class="role-item-name">{{ role.name }}</strong>
<t-tag size="small" :theme="role.enabled ? 'success' : 'default'" variant="light">
{{ role.enabled ? '启用' : '停用' }}
</t-tag>
</div>
<div class="role-item-desc" :title="role.description || ''">{{ role.description || '—' }}</div>
</div>
</div>
</t-loading>
</aside>
<!-- ===== 右侧编辑区 ===== -->
<section class="role-detail">
<!-- 角色已被删除或不存在 -->
<t-empty v-if="mode === 'edit' && !selectedRole" description="该角色已被删除,请选择其他角色" />
<!-- 未选中且非新建 -->
<t-empty v-else-if="mode === 'view'" description="请选择左侧角色,或点击「+ 新增」创建角色" />
<!-- 新建 / 编辑 -->
<template v-else>
<div class="detail-header">
<div class="detail-title-row">
<h3 class="detail-title">{{ mode === 'create' ? '新增角色' : selectedRole?.name }}</h3>
<t-tag
v-if="mode === 'edit' && selectedRole"
size="small"
:theme="selectedRole.enabled ? 'success' : 'default'"
variant="light"
>
{{ selectedRole.enabled ? '启用' : '停用' }}
</t-tag>
</div>
<t-space :size="6">
<t-button
v-if="mode === 'edit' && selectedRole"
variant="outline"
theme="danger"
size="small"
:loading="deleting"
@click="handleDelete"
>
删除角色
</t-button>
</t-space>
</div>
<t-form label-align="top" class="detail-form">
<t-row :gutter="16">
<t-col :span="8">
<t-form-item label="角色名称 *">
<t-input v-model="form.name" :disabled="saving" placeholder="请输入角色名称" />
</t-form-item>
</t-col>
<t-col :span="4">
<t-form-item label="状态">
<t-switch v-model="form.enabled" :disabled="saving" :label="['启用', '停用']" />
</t-form-item>
</t-col>
</t-row>
<t-form-item label="职责描述">
<t-input v-model="form.description" :disabled="saving" placeholder="职责描述(可选)" />
</t-form-item>
<t-form-item label="人设 / 风格提示词">
<t-textarea
v-model="form.prompt"
:disabled="saving"
:autosize="{ minRows: 4, maxRows: 8 }"
placeholder="为角色设定人设、风格与回答约束,该提示词将注入到 AI 系统 Prompt 中"
/>
</t-form-item>
<t-form-item label="可检索知识库授权">
<t-checkbox-group v-if="categoryStore.categories.length" v-model="form.categoryIds">
<t-checkbox
v-for="c in categoryStore.categories"
:key="c.id"
:value="String(c.id)"
:disabled="saving"
>
{{ c.name }}
</t-checkbox>
</t-checkbox-group>
<span v-else class="desc-text">暂无知识库分类请先在知识库管理中创建分类</span>
</t-form-item>
</t-form>
<div class="detail-footer">
<t-button variant="outline" size="small" :disabled="saving" @click="handleCancel">取消</t-button>
<t-button theme="primary" size="small" :loading="saving" @click="handleSubmit">保存角色</t-button>
</div>
</template>
</section>
</div> </div>
</t-card> </t-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useCategoryStore } from '@/stores/category' import { useCategoryStore } from '@/stores/category'
import { getAllRoles, createRole, updateRole, deleteRole, updateRoleCategories } from '@/api/role' import { getAllRoles, createRole, updateRole, deleteRole, updateRoleCategories } from '@/api/role'
import { toast } from '@/utils/toast' import { toast } from '@/utils/toast'
import { useConfirm } from '@/composables/useConfirm' import { useConfirm } from '@/composables/useConfirm'
const { confirm } = useConfirm() const { confirm } = useConfirm()
const categoryStore = useCategoryStore() const categoryStore = useCategoryStore()
// ---- ----
const roles = ref<any[]>([]) const roles = ref<any[]>([])
const editingId = ref('')
const form = reactive({name:'',description:'',prompt:'',enabled:true})
const loading = ref(false)
const saving = ref(false)
const deleting = ref(false)
const mode = ref<'view' | 'create' | 'edit'>('view')
const selectedId = ref<string | null>(null)
const form = reactive({
name: '',
description: '',
prompt: '',
enabled: true,
categoryIds: [] as string[],
})
// ---- ref ----
const snapshot = ref(takeSnapshot())
function takeSnapshot(): string {
return JSON.stringify({
name: form.name,
description: form.description,
prompt: form.prompt,
enabled: form.enabled,
categoryIds: [...form.categoryIds].sort(),
})
}
function resetForm(): void {
form.name = ''
form.description = ''
form.prompt = ''
form.enabled = true
form.categoryIds = []
snapshot.value = takeSnapshot()
}
function setFormFromRole(role: any): void {
form.name = role.name || ''
form.description = role.description || ''
form.prompt = role.prompt || ''
form.enabled = role.enabled !== false
form.categoryIds = [...(role.categoryIds || [])]
snapshot.value = takeSnapshot()
}
const isDirty = computed(() => takeSnapshot() !== snapshot.value)
// ---- ----
const selectedRole = computed(() => roles.value.find(r => r.id === selectedId.value) ?? null)
// ---- ----
onMounted(() => {
reload()
categoryStore.loadCategories()
})
// ---- selectedRole 退 ----
watch(selectedRole, (role) => {
if (!role && mode.value === 'edit') {
mode.value = 'view'
selectedId.value = null
resetForm()
}
})
// ---- ----
async function reload(): Promise<void> {
const currentSelectedId = selectedId.value
loading.value = true
try {
const r = await getAllRoles()
if (r.success) {
roles.value = (r.data || []).map((ro: any) => ({
...ro,
categoryIds: (ro.categoryIds || []).map(String),
}))
//
if (currentSelectedId && selectedId.value === currentSelectedId && mode.value === 'edit') {
const fresh = roles.value.find(x => x.id === currentSelectedId)
if (fresh) {
setFormFromRole(fresh)
}
}
} else {
toast('加载角色列表失败:' + (r.message || '未知错误'), 'error')
}
} catch (e: any) {
toast('加载失败:' + e.message, 'error')
} finally {
loading.value = false
}
}
onMounted(()=>{reload();categoryStore.loadCategories()})
// ---- ----
async function handleRefresh(): Promise<void> {
if (!(await guardDirty('刷新'))) return
await reload()
}
async function reload(){try{const r=await getAllRoles();if(r.success)roles.value=(r.data||[]).map((ro:any)=>({...ro,categoryIds:(ro.categoryIds||[]).map(String)}))}catch(e:any){toast('加载失败:'+e.message,'error')}}
// ---- ----
async function guardDirty(action: string = '切换'): Promise<boolean> {
if (!isDirty.value) return true
return await confirm(`${action}会丢失未保存的修改`, '确定放弃当前修改吗?')
}
function startEdit(role:any){editingId.value=role.id;form.name=role.name;form.description=role.description||'';form.prompt=role.prompt||'';form.enabled=role.enabled}
function resetForm(){editingId.value='';form.name='';form.description='';form.prompt='';form.enabled=true}
// ---- / ----
async function startCreate(): Promise<void> {
if (!(await guardDirty('新建角色'))) return
mode.value = 'create'
selectedId.value = null
resetForm()
}
async function submit(){
if(!form.name.trim()){toast('请输入角色名称','error');return}
const d:any={name:form.name.trim(),description:form.description.trim(),prompt:form.prompt,enabled:form.enabled}
try{let r;if(editingId.value)r=await updateRole(editingId.value,d);else r=await createRole(d)
if(r.success){toast(editingId.value?'修改成功':'新增成功','success');resetForm();reload()}else toast(r.message||'操作失败','error')}catch(e:any){toast('操作失败:'+e.message,'error')}}
async function selectRole(role: any): Promise<void> {
if (selectedId.value === role.id) return
if (!(await guardDirty('切换角色'))) return
mode.value = 'edit'
selectedId.value = role.id
setFormFromRole(role)
}
async function remove(role:any){if(!await confirm(`确定删除「${role.name}」?`))return
try{const r=await deleteRole(role.id);if(r.success){toast('删除成功','success');reload()}else toast(r.message||'删除失败','error')}catch(e:any){toast('删除失败:'+e.message,'error')}}
// ---- ----
function handleCancel(): void {
if (mode.value === 'create') {
mode.value = 'view'
selectedId.value = null
resetForm()
} else if (mode.value === 'edit') {
if (selectedRole.value) {
setFormFromRole(selectedRole.value)
} else {
//
mode.value = 'view'
selectedId.value = null
resetForm()
}
}
}
async function toggleCategory(role:any, catId:string, checked:boolean){
const ids=new Set(role.categoryIds);if(checked)ids.add(catId);else ids.delete(catId)
try{const r=await updateRoleCategories(role.id,Array.from(ids));if(r.success){role.categoryIds=Array.from(ids)}else toast(r.message||'授权失败','error')}catch(e:any){toast('授权失败:'+e.message,'error')}}
// ---- ----
async function handleSubmit(): Promise<void> {
const name = form.name.trim()
if (!name) {
toast('请输入角色名称', 'error')
return
}
saving.value = true
try {
const payload = {
name,
description: form.description.trim(),
prompt: form.prompt.trim(),
enabled: form.enabled,
}
let roleId: string | null = selectedId.value
if (mode.value === 'create') {
const r = await createRole(payload)
if (!r.success) { toast(r.message || '保存失败', 'error'); return }
roleId = r.data?.id || null
if (!roleId) {
// success id
toast('新增成功', 'success')
await reload()
const matched = roles.value.find(x => x.name === name)
if (matched) {
roleId = matched.id
}
}
} else {
if (!roleId) {
toast('系统错误:未找到当前编辑的角色', 'error')
return
}
const r = await updateRole(roleId, payload)
if (!r.success) { toast(r.message || '保存失败', 'error'); return }
}
//
if (roleId) {
const catRes = await updateRoleCategories(roleId, [...form.categoryIds])
if (!catRes.success) {
toast(catRes.message || '保存成功,但知识库授权失败', 'error')
}
} else {
toast('保存成功,但无法获取角色 ID,知识库授权未提交', 'warning')
}
if (mode.value === 'create') {
toast('新增成功', 'success')
} else {
toast('保存成功', 'success')
}
await reload()
//
if (roleId) {
mode.value = 'edit'
selectedId.value = roleId
const updated = roles.value.find(x => x.id === roleId)
if (updated) setFormFromRole(updated)
} else {
mode.value = 'view'
selectedId.value = null
resetForm()
}
} catch (e: any) {
toast('操作失败:' + e.message, 'error')
} finally {
saving.value = false
}
}
// ---- ----
async function handleDelete(): Promise<void> {
const role = selectedRole.value
if (!role) return
if (!(await confirm(`确定删除角色「${role.name}」?`, '删除后不可恢复,已绑定此角色的 API Key 将无法使用。'))) return
deleting.value = true
try {
const r = await deleteRole(role.id)
if (r.success) {
toast('删除成功', 'success')
selectedId.value = null
mode.value = 'view'
resetForm()
await reload()
} else {
toast(r.message || '删除失败', 'error')
}
} catch (e: any) {
toast('删除失败:' + e.message, 'error')
} finally {
deleting.value = false
}
}
</script> </script>
<style scoped> <style scoped>
.role-card{padding:12px;border:1px solid #e7e7e7;border-radius:8px;margin-bottom:10px;}
.role-header{display:flex;justify-content:space-between;align-items:flex-start;gap:8px;}.role-name{font-size:14px;}.role-desc{font-size:12px;color:#999;margin-top:2px;}
.role-prompt{font-size:12px;color:#333;margin-top:6px;background:#f9fafb;border-radius:6px;padding:6px 8px;}
.role-categories{margin-top:8px;font-size:12px;display:flex;flex-wrap:wrap;gap:4px 12px;align-items:center;}.cat-label{color:#999;}.cat-hint{color:#999;}
/* ===== 整体分栏 ===== */
.role-shell {
display: flex;
gap: 16px;
min-height: 520px;
}
/* ===== 左侧列表 ===== */
.role-sidebar {
width: 260px;
flex-shrink: 0;
display: flex;
flex-direction: column;
background: var(--td-bg-color-page);
border: 1px solid var(--td-border-level-1-color);
border-radius: 10px;
overflow: hidden;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px 12px;
border-bottom: 1px solid var(--td-border-level-1-color);
}
.sidebar-title {
font-size: 13px;
font-weight: 600;
color: var(--td-text-color-secondary);
}
.sidebar-body {
flex: 1;
overflow-y: auto;
padding: 10px 12px;
}
.role-items {
display: flex;
flex-direction: column;
gap: 6px;
}
.role-item {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid transparent;
cursor: pointer;
background: var(--td-bg-color-container);
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.role-item:hover {
background: var(--td-bg-color-component);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.role-item:focus-visible {
outline: 2px solid var(--td-brand-color);
outline-offset: -2px;
}
.role-item.active {
background: var(--td-brand-color-light);
border-color: var(--td-brand-color);
}
.role-item-main {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 3px;
}
.role-item-name {
font-size: 13px;
color: var(--td-text-color-primary);
}
.role-item-desc {
font-size: 12px;
color: var(--td-text-color-placeholder);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ===== 右侧编辑区 ===== */
.role-detail {
flex: 1;
display: flex;
flex-direction: column;
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-level-1-color);
border-radius: 10px;
padding: 20px 24px;
overflow-y: auto;
}
.detail-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20px;
padding-bottom: 14px;
border-bottom: 1px solid var(--td-border-level-1-color);
}
.detail-title-row {
display: flex;
align-items: center;
gap: 8px;
}
.detail-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: var(--td-text-color-primary);
}
.detail-form {
flex: 1;
}
.detail-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 24px;
padding-top: 14px;
border-top: 1px solid var(--td-border-level-1-color);
}
/* ===== 响应式 ===== */
@media (max-width: 1024px) {
.role-sidebar {
width: 220px;
}
.role-detail {
padding: 16px 20px;
}
}
@media (max-width: 768px) {
.role-shell {
flex-direction: column;
}
.role-sidebar {
width: auto;
max-height: 260px;
}
}
</style> </style>

5
src/main/java/com/wok/supportbot/controller/CustomerServiceRoleController.java

@ -51,10 +51,11 @@ public class CustomerServiceRoleController {
@PreAuthorize("hasRole('admin')") @PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> createRole(@RequestBody Map<String, Object> body) { public ResponseEntity<Map<String, Object>> createRole(@RequestBody Map<String, Object> body) {
try { try {
customerServiceRoleService.createRole(
Long newId = customerServiceRoleService.createRole(
str(body.get("roleKey")), str(body.get("name")), str(body.get("description")), str(body.get("roleKey")), str(body.get("name")), str(body.get("description")),
str(body.get("prompt")), boolOrNull(body.get("enabled"))); str(body.get("prompt")), boolOrNull(body.get("enabled")));
return ResponseEntity.ok(Map.of("success", true, "message", "角色创建成功"));
return ResponseEntity.ok(Map.of("success", true, "message", "角色创建成功",
"data", Map.of("id", newId.toString())));
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.status(500).body(Map.of("success", false, "message", "创建角色失败:" + e.getMessage())); return ResponseEntity.status(500).body(Map.of("success", false, "message", "创建角色失败:" + e.getMessage()));
} }

30
src/main/java/com/wok/supportbot/service/CustomerServiceRoleService.java

@ -5,9 +5,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -97,21 +101,33 @@ public class CustomerServiceRoleService {
/** /**
* 新增角色role_key 缺省时自动生成 * 新增角色role_key 缺省时自动生成
* @return 新建角色的 ID
*/ */
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void createRole(String roleKey, String name, String description, String prompt, Boolean enabled) {
public Long createRole(String roleKey, String name, String description, String prompt, Boolean enabled) {
if (name == null || name.trim().isEmpty()) { if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("角色名称不能为空"); throw new IllegalArgumentException("角色名称不能为空");
} }
String key = (roleKey == null || roleKey.trim().isEmpty()) String key = (roleKey == null || roleKey.trim().isEmpty())
? "role_" + System.currentTimeMillis() ? "role_" + System.currentTimeMillis()
: roleKey.trim(); : roleKey.trim();
jdbcTemplate.update("""
INSERT INTO customer_service_role (role_key, name, description, prompt, enabled)
VALUES (?, ?, ?, ?, ?)
""",
key, name.trim(), description, prompt,
enabled != null ? enabled : true);
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(con -> {
PreparedStatement ps = con.prepareStatement(
"INSERT INTO customer_service_role (role_key, name, description, prompt, enabled) VALUES (?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, key);
ps.setString(2, name.trim());
ps.setString(3, description);
ps.setString(4, prompt);
ps.setBoolean(5, enabled != null ? enabled : true);
return ps;
}, keyHolder);
Number generatedId = keyHolder.getKey();
if (generatedId == null) {
throw new RuntimeException("创建角色失败:无法获取自动生成的 ID");
}
return generatedId.longValue();
} }
/** /**

6
src/main/resources/static/sdk/chatbot-sdk.js

@ -1095,9 +1095,9 @@ var ChatbotSDK = (function () {
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15)); filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15));
} }
.csk-launcher img { .csk-launcher img {
width: 36px;
height: 36px;
object-fit: contain;
width: 100%;
height: 100%;
object-fit: cover;
position: relative; position: relative;
z-index: 1; z-index: 1;
-webkit-user-drag: none; -webkit-user-drag: none;

2
src/main/resources/static/sdk/chatbot-sdk.min.js
File diff suppressed because it is too large
View File

BIN
src/main/resources/static/sdk/launcher-logo.png

Before

Width: 1024  |  Height: 1024  |  Size: 87 KiB

After

Width: 1254  |  Height: 1254  |  Size: 178 KiB

Loading…
Cancel
Save