feat(通讯录): 在群权限面板内嵌进群欢迎表单配置

在通讯录-群详情-群功能权限区域新增群级欢迎配置表单,支持文本与卡片模板的直观维护、变量提示与实时预览;打开群详情自动加载当前群欢迎配置,保存时直接调用群级配置接口写入MySQL并刷新Redis,避免跳转到独立页面查找配置。
This commit is contained in:
liuwei
2026-04-20 11:01:21 +08:00
parent 7d45fdd61d
commit 492711ea0c

View File

@@ -430,6 +430,53 @@
</template>
</el-table-column>
</el-table>
<el-card class="detail-card welcome-config-card" shadow="never" v-loading="groupWelcomeConfigLoading">
<div slot="header" class="detail-card-header">
<span>进群欢迎配置(群级差异化)</span>
<span class="detail-card-sub">当前群:{% raw %}{{ currentGroup.name || currentGroup.wxid || '-' }}{% endraw %}</span>
</div>
<el-form label-width="110px" size="mini">
<el-form-item label="文本欢迎开关">
<el-switch v-model="groupWelcomeConfig.welcome_text_enabled"></el-switch>
</el-form-item>
<el-form-item label="文本模板">
<el-input v-model="groupWelcomeConfig.welcome_text_template"></el-input>
</el-form-item>
<el-form-item label="卡片开关">
<el-switch v-model="groupWelcomeConfig.welcome_card_enabled"></el-switch>
</el-form-item>
<el-form-item label="卡片标题">
<el-input v-model="groupWelcomeConfig.card_title_template"></el-input>
</el-form-item>
<el-form-item label="卡片描述">
<el-input v-model="groupWelcomeConfig.card_desc_template"></el-input>
</el-form-item>
<el-form-item label="卡片URL">
<el-input v-model="groupWelcomeConfig.card_url"></el-input>
</el-form-item>
<el-form-item label="缩略图URL">
<el-input v-model="groupWelcomeConfig.card_thumb_url"></el-input>
</el-form-item>
<el-form-item label="变量提示">
<div class="form-tip">
<code>{nickname}</code><code>{wxid}</code><code>{group_id}</code><code>{now}</code><code>{head_url}</code>
</div>
</el-form-item>
<el-form-item label="预览">
<div class="preview-box">
<p><strong>文本:</strong>{% raw %}{{ previewGroupWelcomeText }}{% endraw %}</p>
<p><strong>标题:</strong>{% raw %}{{ previewGroupWelcomeTitle }}{% endraw %}</p>
<p><strong>描述:</strong>{% raw %}{{ previewGroupWelcomeDesc }}{% endraw %}</p>
<p><strong>URL</strong>{% raw %}{{ previewGroupWelcomeUrl }}{% endraw %}</p>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="saveGroupWelcomeConfig">保存欢迎配置</el-button>
<el-button size="mini" @click="resetGroupWelcomeConfig">恢复默认</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<div class="group-members-section">
@@ -786,6 +833,16 @@
managedGroupMap: {},
groupPermissions: [],
groupPermissionsLoading: false,
groupWelcomeConfigLoading: false,
groupWelcomeConfig: {
welcome_text_enabled: true,
welcome_text_template: '👏欢迎 {nickname} 加入群聊!🎉',
welcome_card_enabled: true,
card_title_template: '👏欢迎 {nickname} 加入群聊!🎉',
card_desc_template: '⌚时间:{now}',
card_url: 'https://newsnow.busiyi.world/',
card_thumb_url: '{head_url}'
},
// 当前群基础资料:用于展示群主、群公告、管理员、成员数等信息。
currentGroupProfile: { owner_wxid: '', owner_name: '', announcement: '', member_count: 0, admin_count: 0, admins: [] },
// 群公告手动同步按钮的加载态,避免重复点击触发多次请求。
@@ -849,6 +906,18 @@
const keyword = (this.emojiKeyword || '').trim().toLowerCase();
if (!keyword) return this.emojiLibrary;
return this.emojiLibrary.filter(item => (item.md5 || '').toLowerCase().includes(keyword));
},
previewGroupWelcomeText() {
return this.renderWelcomeTemplate(this.groupWelcomeConfig.welcome_text_template);
},
previewGroupWelcomeTitle() {
return this.renderWelcomeTemplate(this.groupWelcomeConfig.card_title_template);
},
previewGroupWelcomeDesc() {
return this.renderWelcomeTemplate(this.groupWelcomeConfig.card_desc_template);
},
previewGroupWelcomeUrl() {
return this.renderWelcomeTemplate(this.groupWelcomeConfig.card_url);
}
},
mounted() {
@@ -910,10 +979,100 @@
this.loadGroupMembers(group.wxid);
this.loadGroupPermissions(group.wxid);
this.loadGroupInsights(group.wxid);
this.loadGroupWelcomeConfig(group.wxid);
},
viewUserDetails(user) { this.currentUser = user; this.userDetailDialogVisible = true; },
viewOfficialDetails(official) { this.currentOfficial = official; this.officialDetailDialogVisible = true; },
viewPublicDetails(publicFriend) { this.currentPublic = publicFriend; this.publicDetailDialogVisible = true; },
getDefaultWelcomeConfig() {
return {
welcome_text_enabled: true,
welcome_text_template: '👏欢迎 {nickname} 加入群聊!🎉',
welcome_card_enabled: true,
card_title_template: '👏欢迎 {nickname} 加入群聊!🎉',
card_desc_template: '⌚时间:{now}',
card_url: 'https://newsnow.busiyi.world/',
card_thumb_url: '{head_url}'
};
},
normalizeWelcomeConfig(raw) {
const base = this.getDefaultWelcomeConfig();
const cfg = raw && typeof raw === 'object' ? raw : {};
return {
welcome_text_enabled: cfg.welcome_text_enabled !== undefined ? !!cfg.welcome_text_enabled : base.welcome_text_enabled,
welcome_text_template: String(cfg.welcome_text_template || base.welcome_text_template),
welcome_card_enabled: cfg.welcome_card_enabled !== undefined ? !!cfg.welcome_card_enabled : base.welcome_card_enabled,
card_title_template: String(cfg.card_title_template || base.card_title_template),
card_desc_template: String(cfg.card_desc_template || base.card_desc_template),
card_url: String(cfg.card_url || base.card_url),
card_thumb_url: String(cfg.card_thumb_url || base.card_thumb_url)
};
},
renderWelcomeTemplate(template) {
const vars = {
nickname: '张三',
wxid: 'wxid_demo_123',
group_id: (this.currentGroup && this.currentGroup.wxid) || '123456@chatroom',
now: '2026-04-20 12:00:00',
head_url: 'https://example.com/avatar.png'
};
let text = String(template || '');
Object.entries(vars).forEach(([k, v]) => {
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
});
return text;
},
loadGroupWelcomeConfig(groupId) {
if (!groupId) return;
this.groupWelcomeConfigLoading = true;
this.groupWelcomeConfig = this.getDefaultWelcomeConfig();
axios.get('/group_plugin_config/api/list', {
params: { group_id: groupId, plugin_name: '群成员变更监控' }
}).then(response => {
if (!response.data || !response.data.success) {
this.$message.warning('加载欢迎配置失败,已使用默认值');
return;
}
const rows = response.data.data || [];
const row = rows.find(item => String(item.config_key || '') === 'welcome');
if (row && row.config_json) {
this.groupWelcomeConfig = this.normalizeWelcomeConfig(row.config_json);
}
}).catch(error => {
console.error('加载群欢迎配置失败:', error);
this.$message.warning('加载欢迎配置失败,已使用默认值');
}).finally(() => { this.groupWelcomeConfigLoading = false; });
},
resetGroupWelcomeConfig() {
this.groupWelcomeConfig = this.getDefaultWelcomeConfig();
this.$message.success('已恢复默认欢迎配置');
},
saveGroupWelcomeConfig() {
if (!this.currentGroup || !this.currentGroup.wxid) return;
const renderedUrl = this.renderWelcomeTemplate(this.groupWelcomeConfig.card_url);
if (!/^https?:\/\//i.test(renderedUrl)) {
this.$message.error('卡片URL必须是 http 或 https 开头');
return;
}
this.groupWelcomeConfigLoading = true;
axios.post('/group_plugin_config/api/upsert', {
group_id: this.currentGroup.wxid,
plugin_name: '群成员变更监控',
config_key: 'welcome',
enabled: true,
config_json: this.groupWelcomeConfig,
updated_by: 'dashboard'
}).then(response => {
if (response.data && response.data.success) {
this.$message.success('群欢迎配置保存成功');
} else {
this.$message.error((response.data && response.data.message) || '群欢迎配置保存失败');
}
}).catch(error => {
console.error('保存群欢迎配置失败:', error);
this.$message.error('保存群欢迎配置失败');
}).finally(() => { this.groupWelcomeConfigLoading = false; });
},
loadGroupPermissions(groupId) {
this.groupPermissionsLoading = true;
this.groupPermissions = [];
@@ -1537,6 +1696,7 @@
.pagination-container { margin-top: 20px; text-align: right; }
.group-insight-section { margin-top: 20px; }
.group-permission-section { margin-top: 20px; }
.welcome-config-card { margin-top: 14px; }
.group-members-section { margin-top: 20px; }
.section-title {
margin: 20px 0 15px 0; border-bottom: 1px solid rgba(148,163,184,0.12); padding-bottom: 10px;
@@ -1677,6 +1837,16 @@
border: 1px solid rgba(148,163,184,0.16); border-radius: 12px; padding: 8px;
display: flex; flex-direction: column; gap: 8px; align-items: center; background: #fff;
}
.form-tip{
padding:10px 12px;border-radius:10px;background:#f8fbff;border:1px solid #d9e8f8;color:#4a6179
}
.form-tip code{
display:inline-block;margin-right:6px;background:#eef6ff;border:1px solid #d2e6ff;color:#12539a;padding:1px 6px;border-radius:6px;font-size:12px
}
.preview-box{
padding:12px;border:1px dashed #c7d8ea;background:#f8fbff;border-radius:10px;color:#3f5c77;line-height:1.7
}
.preview-box p{margin:0 0 4px 0}
.emoji-thumb { width: 72px; height: 72px; object-fit: contain; border-radius: 8px; background: rgba(148,163,184,0.08); }
.emoji-md5 { font-size: 11px; color: #64748b; word-break: break-all; text-align: center; min-height: 30px; }
.emoji-actions { width: 100%; display: flex; justify-content: center; }