From 05e04ce0213ef15b337bd76a373ea5be3441d6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Wed, 16 Sep 2026 16:53:07 +0800 Subject: [PATCH] Add configurable contact page management --- .../controller/app/AppContactController.java | 39 +++ .../web/controller/app/PublicController.java | 10 + .../com/ruoyi/app/domain/AppContactItem.java | 42 +++ .../com/ruoyi/app/domain/AppContactPage.java | 33 +++ .../ruoyi/app/mapper/AppContactMapper.java | 19 ++ .../ruoyi/app/service/IAppContactService.java | 15 + .../service/impl/AppContactServiceImpl.java | 152 ++++++++++ .../resources/mapper/app/AppContactMapper.xml | 74 +++++ ruoyi-ui/src/api/app/contact.js | 9 + ruoyi-ui/src/views/app/contact/index.vue | 270 ++++++++++++++++++ sql/contact_page.sql | 95 ++++++ 11 files changed, 758 insertions(+) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/AppContactController.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactItem.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactPage.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/app/mapper/AppContactMapper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/app/service/IAppContactService.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/app/service/impl/AppContactServiceImpl.java create mode 100644 ruoyi-system/src/main/resources/mapper/app/AppContactMapper.xml create mode 100644 ruoyi-ui/src/api/app/contact.js create mode 100644 ruoyi-ui/src/views/app/contact/index.vue create mode 100644 sql/contact_page.sql diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/AppContactController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/AppContactController.java new file mode 100644 index 0000000..5e0e69e --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/AppContactController.java @@ -0,0 +1,39 @@ +package com.ruoyi.web.controller.app; + +import com.ruoyi.app.domain.AppContactPage; +import com.ruoyi.app.service.IAppContactService; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/app/contact") +public class AppContactController extends BaseController +{ + @Autowired + private IAppContactService contactService; + + @PreAuthorize("@ss.hasPermi('app:contact:query')") + @GetMapping + public AjaxResult getInfo() + { + return success(contactService.selectAdminContactPage()); + } + + @PreAuthorize("@ss.hasPermi('app:contact:edit')") + @Log(title = "联系页面配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody AppContactPage page) + { + page.setUpdateBy(getUsername()); + return toAjax(contactService.updateContactPage(page)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/PublicController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/PublicController.java index 573efd9..8f19a6f 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/PublicController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/app/PublicController.java @@ -2,6 +2,7 @@ package com.ruoyi.web.controller.app; import com.ruoyi.app.domain.ShuiYinVo; import com.ruoyi.app.mapper.AppPunlicMapper; +import com.ruoyi.app.service.IAppContactService; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.system.service.ISysConfigService; @@ -20,6 +21,8 @@ public class PublicController extends BaseController { AppPunlicMapper appPunlicMapper; @Autowired private ISysConfigService configService; + @Autowired + private IAppContactService contactService; /** * 去水印 key 免费申请联系作者微信:yimoziyuan666 * @return @@ -76,4 +79,11 @@ public class PublicController extends BaseController { return map; } + /** 获取小程序联系页面公开配置。 */ + @GetMapping(value = "/contact-page") + public AjaxResult getContactPage() + { + return success(contactService.selectPublicContactPage()); + } + } diff --git a/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactItem.java b/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactItem.java new file mode 100644 index 0000000..d0ce4b7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactItem.java @@ -0,0 +1,42 @@ +package com.ruoyi.app.domain; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 小程序联系页面联系方式对象 app_contact_item。 + */ +public class AppContactItem extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + private Long id; + private String type; + private String label; + private String value; + private String iconUrl; + private String qrCodeUrl; + /** copy复制,qrcode查看二维码 */ + private String actionType; + /** 0启用,1停用 */ + private String status; + private Integer sort; + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getLabel() { return label; } + public void setLabel(String label) { this.label = label; } + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + public String getIconUrl() { return iconUrl; } + public void setIconUrl(String iconUrl) { this.iconUrl = iconUrl; } + public String getQrCodeUrl() { return qrCodeUrl; } + public void setQrCodeUrl(String qrCodeUrl) { this.qrCodeUrl = qrCodeUrl; } + public String getActionType() { return actionType; } + public void setActionType(String actionType) { this.actionType = actionType; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public Integer getSort() { return sort; } + public void setSort(Integer sort) { this.sort = sort; } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactPage.java b/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactPage.java new file mode 100644 index 0000000..a074ecb --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/app/domain/AppContactPage.java @@ -0,0 +1,33 @@ +package com.ruoyi.app.domain; + +import java.util.ArrayList; +import java.util.List; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 小程序联系页面配置对象 app_contact_page。 + */ +public class AppContactPage extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + private Long id; + private String avatarUrl; + private String nickname; + private String bio; + private String backgroundUrl; + private List contacts = new ArrayList<>(); + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getAvatarUrl() { return avatarUrl; } + public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; } + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } + public String getBio() { return bio; } + public void setBio(String bio) { this.bio = bio; } + public String getBackgroundUrl() { return backgroundUrl; } + public void setBackgroundUrl(String backgroundUrl) { this.backgroundUrl = backgroundUrl; } + public List getContacts() { return contacts; } + public void setContacts(List contacts) { this.contacts = contacts; } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/app/mapper/AppContactMapper.java b/ruoyi-system/src/main/java/com/ruoyi/app/mapper/AppContactMapper.java new file mode 100644 index 0000000..9019826 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/app/mapper/AppContactMapper.java @@ -0,0 +1,19 @@ +package com.ruoyi.app.mapper; + +import java.util.List; +import com.ruoyi.app.domain.AppContactItem; +import com.ruoyi.app.domain.AppContactPage; +import org.apache.ibatis.annotations.Param; + +public interface AppContactMapper +{ + AppContactPage selectAppContactPage(); + + List selectAppContactItemList(@Param("status") String status); + + int upsertAppContactPage(AppContactPage page); + + int deleteAllAppContactItems(); + + int insertAppContactItem(AppContactItem item); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/app/service/IAppContactService.java b/ruoyi-system/src/main/java/com/ruoyi/app/service/IAppContactService.java new file mode 100644 index 0000000..fdfd328 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/app/service/IAppContactService.java @@ -0,0 +1,15 @@ +package com.ruoyi.app.service; + +import com.ruoyi.app.domain.AppContactPage; + +public interface IAppContactService +{ + /** 查询后台维护用的完整配置。 */ + AppContactPage selectAdminContactPage(); + + /** 查询小程序展示用的启用配置。 */ + AppContactPage selectPublicContactPage(); + + /** 保存页面资料及完整联系方式列表。 */ + int updateContactPage(AppContactPage page); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/app/service/impl/AppContactServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/app/service/impl/AppContactServiceImpl.java new file mode 100644 index 0000000..1ee03f6 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/app/service/impl/AppContactServiceImpl.java @@ -0,0 +1,152 @@ +package com.ruoyi.app.service.impl; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import com.ruoyi.app.domain.AppContactItem; +import com.ruoyi.app.domain.AppContactPage; +import com.ruoyi.app.mapper.AppContactMapper; +import com.ruoyi.app.service.IAppContactService; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class AppContactServiceImpl implements IAppContactService +{ + private static final int MAX_CONTACTS = 30; + + @Autowired + private AppContactMapper contactMapper; + + @Override + public AppContactPage selectAdminContactPage() + { + return buildContactPage(null); + } + + @Override + public AppContactPage selectPublicContactPage() + { + return buildContactPage("0"); + } + + private AppContactPage buildContactPage(String status) + { + AppContactPage page = contactMapper.selectAppContactPage(); + if (page == null) + { + page = defaultPage(); + } + page.setContacts(contactMapper.selectAppContactItemList(status)); + return page; + } + + @Override + @Transactional + public int updateContactPage(AppContactPage page) + { + if (page == null) + { + throw new ServiceException("页面配置不能为空"); + } + normalizePage(page); + List contacts = page.getContacts(); + if (contacts == null) + { + contacts = new ArrayList<>(); + page.setContacts(contacts); + } + if (contacts.size() > MAX_CONTACTS) + { + throw new ServiceException("联系方式最多可配置" + MAX_CONTACTS + "条"); + } + + Date now = new Date(); + page.setId(1L); + page.setUpdateTime(now); + contactMapper.upsertAppContactPage(page); + contactMapper.deleteAllAppContactItems(); + + int index = 0; + for (AppContactItem item : contacts) + { + normalizeItem(item, index++); + item.setId(null); + item.setCreateBy(page.getUpdateBy()); + item.setUpdateBy(page.getUpdateBy()); + item.setCreateTime(now); + item.setUpdateTime(now); + contactMapper.insertAppContactItem(item); + } + return 1; + } + + private void normalizePage(AppContactPage page) + { + page.setAvatarUrl(trimAndCheck(page.getAvatarUrl(), 500, "头像地址")); + page.setNickname(trimAndCheck(page.getNickname(), 100, "昵称")); + page.setBio(trimAndCheck(page.getBio(), 500, "个人简介")); + page.setBackgroundUrl(trimAndCheck(page.getBackgroundUrl(), 500, "背景图地址")); + if (StringUtils.isEmpty(page.getNickname())) + { + throw new ServiceException("昵称不能为空"); + } + } + + private void normalizeItem(AppContactItem item, int index) + { + if (item == null) + { + throw new ServiceException("第" + (index + 1) + "条联系方式不能为空"); + } + item.setType(trimAndCheck(item.getType(), 30, "联系方式类型")); + item.setLabel(trimAndCheck(item.getLabel(), 50, "联系方式名称")); + item.setValue(trimAndCheck(item.getValue(), 500, "联系方式内容")); + item.setIconUrl(trimAndCheck(item.getIconUrl(), 500, "图标地址")); + item.setQrCodeUrl(trimAndCheck(item.getQrCodeUrl(), 500, "二维码地址")); + if (StringUtils.isEmpty(item.getType())) + { + item.setType("custom"); + } + if (StringUtils.isEmpty(item.getLabel())) + { + throw new ServiceException("第" + (index + 1) + "条联系方式名称不能为空"); + } + if (StringUtils.isEmpty(item.getValue())) + { + throw new ServiceException("第" + (index + 1) + "条联系方式内容不能为空"); + } + if (!"qrcode".equals(item.getActionType()) || StringUtils.isEmpty(item.getQrCodeUrl())) + { + item.setActionType("copy"); + } + item.setStatus("1".equals(item.getStatus()) ? "1" : "0"); + if (item.getSort() == null || item.getSort() < 0) + { + item.setSort((index + 1) * 10); + } + } + + private String trimAndCheck(String value, int maxLength, String fieldName) + { + String result = StringUtils.trim(value); + if (result != null && result.length() > maxLength) + { + throw new ServiceException(fieldName + "不能超过" + maxLength + "个字符"); + } + return result; + } + + private AppContactPage defaultPage() + { + AppContactPage page = new AppContactPage(); + page.setId(1L); + page.setAvatarUrl("https://img.yidaima.cn/%E5%A4%B4%E5%83%8F/9b889964f97e4d0c8f52f7b9ec0d04da.jpg"); + page.setNickname("Nan Yin"); + page.setBio("如果爱意难平,那就把它藏在心底吧!"); + return page; + } +} diff --git a/ruoyi-system/src/main/resources/mapper/app/AppContactMapper.xml b/ruoyi-system/src/main/resources/mapper/app/AppContactMapper.xml new file mode 100644 index 0000000..0439226 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/app/AppContactMapper.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into app_contact_page + (id, avatar_url, nickname, bio, background_url, create_by, create_time, update_by, update_time) + values + (1, #{avatarUrl}, #{nickname}, #{bio}, #{backgroundUrl}, + #{updateBy}, #{updateTime}, #{updateBy}, #{updateTime}) + on duplicate key update + avatar_url = values(avatar_url), nickname = values(nickname), bio = values(bio), + background_url = values(background_url), update_by = values(update_by), + update_time = values(update_time) + + + + delete from app_contact_item + + + + insert into app_contact_item + (type, label, contact_value, icon_url, qr_code_url, action_type, status, sort, + create_by, create_time, update_by, update_time) + values + (#{type}, #{label}, #{value}, #{iconUrl}, #{qrCodeUrl}, #{actionType}, #{status}, #{sort}, + #{createBy}, #{createTime}, #{updateBy}, #{updateTime}) + + diff --git a/ruoyi-ui/src/api/app/contact.js b/ruoyi-ui/src/api/app/contact.js new file mode 100644 index 0000000..93eeefa --- /dev/null +++ b/ruoyi-ui/src/api/app/contact.js @@ -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 }) +} diff --git a/ruoyi-ui/src/views/app/contact/index.vue b/ruoyi-ui/src/views/app/contact/index.vue new file mode 100644 index 0000000..4a1f05d --- /dev/null +++ b/ruoyi-ui/src/views/app/contact/index.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/sql/contact_page.sql b/sql/contact_page.sql new file mode 100644 index 0000000..ab89d71 --- /dev/null +++ b/sql/contact_page.sql @@ -0,0 +1,95 @@ +-- 小程序“关注我们”页面配置及后台菜单。 + +CREATE TABLE IF NOT EXISTS `app_contact_page` ( + `id` bigint NOT NULL COMMENT '配置主键,固定为1', + `avatar_url` varchar(500) DEFAULT NULL COMMENT '头像地址', + `nickname` varchar(100) NOT NULL COMMENT '昵称', + `bio` varchar(500) DEFAULT NULL COMMENT '个人简介', + `background_url` varchar(500) DEFAULT NULL COMMENT '背景图地址,为空使用页面渐变背景', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小程序联系页面配置'; + +CREATE TABLE IF NOT EXISTS `app_contact_item` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '联系方式主键', + `type` varchar(30) NOT NULL DEFAULT 'custom' COMMENT '类型:qq/wechat/email/blog/custom', + `label` varchar(50) NOT NULL COMMENT '显示名称', + `contact_value` varchar(500) NOT NULL COMMENT '联系方式内容', + `icon_url` varchar(500) DEFAULT NULL COMMENT '自定义图标地址', + `qr_code_url` varchar(500) DEFAULT NULL COMMENT '二维码地址', + `action_type` varchar(20) NOT NULL DEFAULT 'copy' COMMENT '操作:copy/qrcode', + `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态:0启用,1停用', + `sort` int NOT NULL DEFAULT 0 COMMENT '显示排序', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_app_contact_item_status_sort` (`status`, `sort`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小程序联系方式'; + +INSERT INTO `app_contact_page` + (`id`, `avatar_url`, `nickname`, `bio`, `background_url`, `create_by`, `create_time`, `update_by`, `update_time`) +SELECT + 1, + 'https://img.yidaima.cn/%E5%A4%B4%E5%83%8F/9b889964f97e4d0c8f52f7b9ec0d04da.jpg', + 'Nan Yin', '如果爱意难平,那就把它藏在心底吧!', NULL, 'admin', NOW(), '', NULL +WHERE NOT EXISTS (SELECT 1 FROM `app_contact_page` WHERE `id` = 1); + +SET @contact_seed_needed := (SELECT COUNT(*) = 0 FROM `app_contact_item`); + +INSERT INTO `app_contact_item` + (`type`, `label`, `contact_value`, `icon_url`, `qr_code_url`, `action_type`, `status`, `sort`, `create_by`, `create_time`) +SELECT 'qq', 'QQ', '594458910', NULL, 'https://img.yidaima.cn/feast/qq_qrcode.jpg', 'qrcode', '0', 10, 'admin', NOW() +WHERE @contact_seed_needed = 1; + +INSERT INTO `app_contact_item` + (`type`, `label`, `contact_value`, `icon_url`, `qr_code_url`, `action_type`, `status`, `sort`, `create_by`, `create_time`) +SELECT 'wechat', '微信', 'forfeastcoding', NULL, 'https://img.yidaima.cn/feast/wx_qrcode.jpg', 'qrcode', '0', 20, 'admin', NOW() +WHERE @contact_seed_needed = 1; + +INSERT INTO `app_contact_item` + (`type`, `label`, `contact_value`, `icon_url`, `qr_code_url`, `action_type`, `status`, `sort`, `create_by`, `create_time`) +SELECT 'email', '邮箱', '594458910@qq.com', NULL, NULL, 'copy', '0', 30, 'admin', NOW() +WHERE @contact_seed_needed = 1; + +INSERT INTO `app_contact_item` + (`type`, `label`, `contact_value`, `icon_url`, `qr_code_url`, `action_type`, `status`, `sort`, `create_by`, `create_time`) +SELECT 'blog', '博客', 'http://www.yidaima.cn/', NULL, NULL, 'copy', '0', 40, 'admin', NOW() +WHERE @contact_seed_needed = 1; + +SET @contact_parent_id := COALESCE( + (SELECT parent_id FROM sys_menu WHERE perms = 'app:resource:list' LIMIT 1), + (SELECT parent_id FROM sys_menu WHERE perms = 'app:article:list' LIMIT 1), + 0 +); + +SET @contact_order_num := COALESCE( + (SELECT MAX(order_num) + 1 FROM sys_menu WHERE parent_id = @contact_parent_id), + 1 +); + +INSERT INTO sys_menu + (menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT + '联系页面配置', @contact_parent_id, @contact_order_num, + 'contact', 'app/contact/index', '', 1, 0, + 'C', '0', '0', 'app:contact:query', 'phone', 'admin', NOW(), '', NULL, + '维护小程序关注页面资料和联系方式' +WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:contact:query'); + +SET @contact_menu_id := ( + SELECT menu_id FROM sys_menu WHERE perms = 'app:contact:query' LIMIT 1 +); + +INSERT INTO sys_menu + (menu_name, parent_id, order_num, path, component, query, is_frame, is_cache, + menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +SELECT '联系页面修改', @contact_menu_id, 1, '', '', '', 1, 0, + 'F', '0', '0', 'app:contact:edit', '#', 'admin', NOW(), '', NULL, '' +WHERE @contact_menu_id IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'app:contact:edit');