Add configurable contact page management

This commit is contained in:
王鹏
2026-09-16 16:53:07 +08:00
parent e31af58d58
commit 05e04ce021
11 changed files with 758 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getContactConfig() {
return request({ url: '/app/contact', method: 'get' })
}
export function updateContactConfig(data) {
return request({ url: '/app/contact', method: 'put', data })
}

View File

@@ -0,0 +1,270 @@
<template>
<div v-loading="loading" class="app-container contact-config">
<el-alert
title="这里保存后,小程序会动态展示启用的联系方式;页面导航标题保持现状。"
type="info"
:closable="false"
show-icon
class="page-alert"
/>
<el-form ref="form" :model="form" :rules="rules" label-width="96px">
<el-card shadow="never" class="config-card">
<div slot="header" class="card-header">
<div>
<div class="card-title">页面资料</div>
<div class="card-desc">配置头像、昵称和简介,背景图不上传时使用默认渐变背景。</div>
</div>
</div>
<el-row :gutter="28">
<el-col :xs="24" :sm="12">
<el-form-item label="头像" prop="avatarUrl">
<image-upload v-model="form.avatarUrl" :limit="1" :file-size="5" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="顶部背景图" prop="backgroundUrl">
<image-upload v-model="form.backgroundUrl" :limit="1" :file-size="8" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="28">
<el-col :xs="24" :sm="12">
<el-form-item label="昵称" prop="nickname">
<el-input v-model.trim="form.nickname" maxlength="100" show-word-limit placeholder="请输入昵称" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12">
<el-form-item label="个人简介" prop="bio">
<el-input v-model.trim="form.bio" maxlength="500" show-word-limit placeholder="请输入个人简介" />
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card shadow="never" class="config-card">
<div slot="header" class="card-header">
<div>
<div class="card-title">联系方式</div>
<div class="card-desc">可新增、删除、启停和排序;未上传图标时小程序会使用对应类型的文字图标。</div>
</div>
<el-button type="primary" plain icon="el-icon-plus" size="small" @click="addContact">新增联系方式</el-button>
</div>
<el-table :data="form.contacts" border row-key="rowKey" empty-text="暂无联系方式,请点击右上角新增">
<el-table-column label="排序" width="90" align="center">
<template slot-scope="scope">
<el-input-number v-model="scope.row.sort" :min="0" :max="9999" :controls="false" size="small" />
</template>
</el-table-column>
<el-table-column label="类型" width="130">
<template slot-scope="scope">
<el-select v-model="scope.row.type" size="small" @change="applyTypePreset(scope.row)">
<el-option v-for="item in contactTypes" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column label="显示名称" min-width="145">
<template slot-scope="scope">
<el-input v-model.trim="scope.row.label" maxlength="50" size="small" placeholder="如:微信" />
</template>
</el-table-column>
<el-table-column label="联系内容" min-width="220">
<template slot-scope="scope">
<el-input v-model.trim="scope.row.value" maxlength="500" size="small" placeholder="账号、邮箱或网址" />
</template>
</el-table-column>
<el-table-column label="点击操作" width="130">
<template slot-scope="scope">
<el-select v-model="scope.row.actionType" size="small">
<el-option label="复制内容" value="copy" />
<el-option label="查看二维码" value="qrcode" :disabled="!scope.row.qrCodeUrl" />
</el-select>
</template>
</el-table-column>
<el-table-column label="自定义图标" width="112" align="center">
<template slot-scope="scope">
<image-upload v-model="scope.row.iconUrl" class="compact-uploader" :limit="1" :file-size="2" :is-show-tip="false" />
</template>
</el-table-column>
<el-table-column label="二维码" width="112" align="center">
<template slot-scope="scope">
<image-upload v-model="scope.row.qrCodeUrl" class="compact-uploader" :limit="1" :file-size="5" :is-show-tip="false" />
</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" class="danger-button" @click="removeContact(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<div class="save-bar">
<el-button type="primary" icon="el-icon-check" :loading="saving" @click="submitForm">保存配置</el-button>
</div>
</el-form>
</div>
</template>
<script>
import { getContactConfig, updateContactConfig } from '@/api/app/contact'
export default {
name: 'ContactConfig',
data() {
return {
loading: false,
saving: false,
rowSequence: 0,
contactTypes: [
{ label: 'QQ', value: 'qq' },
{ label: '微信', value: 'wechat' },
{ label: '邮箱', value: 'email' },
{ label: '博客/网站', value: 'blog' },
{ label: '自定义', value: 'custom' }
],
form: this.emptyForm(),
rules: {
nickname: [{ required: true, message: '请输入昵称', trigger: 'blur' }]
}
}
},
created() {
this.loadConfig()
},
methods: {
emptyForm() {
return { id: 1, avatarUrl: '', nickname: '', bio: '', backgroundUrl: '', contacts: [] }
},
loadConfig() {
this.loading = true
getContactConfig().then(response => {
const data = response.data || this.emptyForm()
data.contacts = (data.contacts || []).map(item => this.withRowKey(item))
this.form = data
}).finally(() => { this.loading = false })
},
withRowKey(item) {
this.rowSequence += 1
return Object.assign({
id: undefined,
type: 'custom',
label: '',
value: '',
iconUrl: '',
qrCodeUrl: '',
actionType: 'copy',
status: '0',
sort: this.rowSequence * 10
}, item, { rowKey: `contact-${this.rowSequence}` })
},
addContact() {
if (this.form.contacts.length >= 30) {
this.$modal.msgWarning('最多可配置30条联系方式')
return
}
this.form.contacts.push(this.withRowKey({ sort: (this.form.contacts.length + 1) * 10 }))
},
removeContact(index) {
this.form.contacts.splice(index, 1)
},
applyTypePreset(row) {
const labels = { qq: 'QQ', wechat: '微信', email: '邮箱', blog: '博客' }
if (labels[row.type] && !row.label) row.label = labels[row.type]
if (row.type === 'email' || row.type === 'blog') row.actionType = 'copy'
},
validateContacts() {
for (let i = 0; i < this.form.contacts.length; i += 1) {
const item = this.form.contacts[i]
if (!item.label) return `第${i + 1}条联系方式缺少显示名称`
if (!item.value) return `第${i + 1}条联系方式缺少联系内容`
if (item.actionType === 'qrcode' && !item.qrCodeUrl) return `第${i + 1}条联系方式选择了二维码操作,但尚未上传二维码`
}
return ''
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
const message = this.validateContacts()
if (message) {
this.$modal.msgWarning(message)
return
}
this.saving = true
const payload = JSON.parse(JSON.stringify(this.form))
payload.contacts.forEach(item => { delete item.rowKey })
updateContactConfig(payload).then(() => {
this.$modal.msgSuccess('保存成功')
this.loadConfig()
}).finally(() => { this.saving = false })
})
}
}
}
</script>
<style scoped lang="scss">
.contact-config {
padding-bottom: 84px;
}
.page-alert {
margin-bottom: 18px;
}
.config-card {
margin-bottom: 18px;
border-radius: 8px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
color: #303133;
font-size: 16px;
font-weight: 600;
}
.card-desc {
margin-top: 6px;
color: #909399;
font-size: 13px;
}
.danger-button {
color: #f56c6c;
}
.save-bar {
position: sticky;
z-index: 20;
bottom: 0;
display: flex;
justify-content: flex-end;
margin: 0 -20px -84px;
padding: 14px 24px;
border-top: 1px solid #ebeef5;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 -4px 14px rgba(31, 45, 61, 0.06);
}
::v-deep .compact-uploader .el-upload--picture-card,
::v-deep .compact-uploader .el-upload-list--picture-card .el-upload-list__item {
width: 58px;
height: 58px;
line-height: 58px;
margin: 0;
}
::v-deep .compact-uploader .el-upload-list--picture-card .el-upload-list__item-actions {
font-size: 14px;
}
::v-deep .el-input-number {
width: 62px;
}
@media (max-width: 992px) {
.card-header { align-items: flex-start; gap: 16px; }
}
</style>