Compare commits

...

7 Commits

Author SHA1 Message Date
07e8151736 【优化】修改密码接口 2025-11-17 15:10:29 +08:00
ad5ca8c545 【新增】mongoDB支持 2025-11-17 15:10:09 +08:00
95e7b752e3 【改进】修改密码接口 2025-11-17 15:09:47 +08:00
3ea2777c94 【改进】隐私政策 2025-11-17 15:09:26 +08:00
1db7b3f6a1 【新增】排除咨询接口 2025-11-17 15:09:00 +08:00
03c5226a6a 【新增】咨询接口 2025-11-17 15:08:52 +08:00
7bd3d54d97 【新增】系统用户 2025-11-17 15:08:06 +08:00
17 changed files with 464 additions and 65 deletions

View File

@@ -36,13 +36,14 @@ dependencies {
implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0' // 权限认证
implementation 'com.alibaba:druid-spring-boot-starter:1.2.27' // 数据库连接池
implementation 'org.lionsoul:ip2region:2.7.0' // IP 归属地
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' // mongodb
compileOnly 'org.projectlombok:lombok' // Lombok
developmentOnly 'org.springframework.boot:spring-boot-devtools' // 热重载
runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 驱动
annotationProcessor 'org.projectlombok:lombok' // Lombok 注解处理
testImplementation 'org.springframework.boot:spring-boot-starter-test' // 测试框架
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // thymeleaf模版引擎
implementation 'com.aliyun.oss:aliyun-sdk-oss:3.15.1' // OSS SDK
implementation 'com.aliyun.oss:aliyun-sdk-oss:3.17.4' // OSS SDK
}
tasks.named('test') {

View File

@@ -45,6 +45,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
// 排除 Druid 监控
.excludePathPatterns("/druid/**")
// 排除错误页面
.excludePathPatterns("/error", "/error/**");
.excludePathPatterns("/error", "/error/**")
// 排除咨询接口
.excludePathPatterns("/contactMsg", "/contactMsg/**");
}
}

View File

@@ -0,0 +1,9 @@
package com.corewing.app.dto.sys;
import lombok.Data;
@Data
public class SysResetPasswordRequest {
private Long userId;
private String password;
}

View File

@@ -0,0 +1,8 @@
package com.corewing.app.dto.sys;
import lombok.Data;
@Data
public class SysUserIdRequest {
private Long id;
}

View File

@@ -0,0 +1,52 @@
package com.corewing.app.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.corewing.app.common.base.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.Id;
/**
* 联系消息
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("app_contact_msg")
public class ContactMsg extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 咨询人
*/
private String name;
/**
* 咨询类型
*/
private int category;
/**
* 联系方式
*/
private String contact;
/**
* 咨询内容
*/
private String msg;
/**
* 咨询状态
*/
private int status;
}

View File

@@ -0,0 +1,10 @@
package com.corewing.app.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.corewing.app.entity.ContactMsg;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ContactMsgMapper extends BaseMapper<ContactMsg> {
}

View File

@@ -4,6 +4,8 @@ import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.corewing.app.common.Result;
import com.corewing.app.dto.api.SysLoginRequest;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.dto.sys.SysUserIdRequest;
import com.corewing.app.entity.SysUser;
import com.corewing.app.service.SysUserService;
import com.corewing.app.util.I18nUtil;
@@ -40,6 +42,30 @@ public class SysUserController {
return Result.success(sysUserPage);
}
@PostMapping("/update")
@ResponseBody
public Result<String> update(@RequestBody SysUser sysUser) {
return Result.isBool(sysUserService.update(sysUser));
}
@PostMapping("/save")
@ResponseBody
public Result<String> save(@RequestBody SysUser sysUser) {
return Result.isBool(sysUserService.save(sysUser));
}
@PutMapping("/resetPassword")
@ResponseBody
public Result<String> resetPassword(@RequestBody SysResetPasswordRequest sysResetPasswordRequest) {
return Result.isBool(sysUserService.resetPassword(sysResetPasswordRequest));
}
@DeleteMapping("/delete")
@ResponseBody
public Result<String> delete(@RequestBody SysUserIdRequest sysUserIdRequest) {
return Result.isBool(sysUserService.removeById(sysUserIdRequest.getId()));
}
/**
* 后台管理登录
*/

View File

@@ -0,0 +1,25 @@
package com.corewing.app.modules.app;
import com.corewing.app.common.Result;
import com.corewing.app.entity.ContactMsg;
import com.corewing.app.service.ContactMsgService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/contactMsg")
public class AppContactMsgController {
@Resource
private ContactMsgService contactMsgService;
@PostMapping("/save")
public Result<String> save(@RequestBody ContactMsg contactMsg) {
return Result.isBool(contactMsgService.save(contactMsg));
}
}

View File

@@ -132,11 +132,12 @@ public class AppUserController {
/**
* 更新用户信息
*/
@PutMapping
@PutMapping("/update")
public Result<String> update(@RequestBody User user) {
Long userId = StpUtil.getLoginIdAsLong();
user.setId(userId);
// 不允许通过此接口修改密码
user.setPassword(null);
boolean success = userService.updateById(user);
if (success) {
return Result.success(I18nUtil.getMessage("user.update.success"));
@@ -145,7 +146,7 @@ public class AppUserController {
}
/**
* 修改密码@
* 修改密码
*/
@PutMapping("/password")
public Result<String> updatePassword(@RequestBody UpdatePasswordRequest request) {

View File

@@ -0,0 +1,7 @@
package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.entity.ContactMsg;
public interface ContactMsgService extends IService<ContactMsg> {
}

View File

@@ -2,6 +2,7 @@ package com.corewing.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.entity.SysUser;
/**
@@ -11,6 +12,10 @@ public interface SysUserService extends IService<SysUser> {
Page<SysUser> page(SysUser sysUser);
boolean save(SysUser sysUser);
boolean update(SysUser sysUser);
/**
* 根据用户名查询用户
*
@@ -36,4 +41,11 @@ public interface SysUserService extends IService<SysUser> {
* @param loginIp 登录IP
*/
void updateLoginInfo(Long userId, String loginIp);
/**
* 修改密码
* @param sysResetPasswordRequest
* @return
*/
boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest);
}

View File

@@ -0,0 +1,12 @@
package com.corewing.app.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.entity.ContactMsg;
import com.corewing.app.mapper.ContactMsgMapper;
import com.corewing.app.service.ContactMsgService;
import org.springframework.stereotype.Service;
@Service
public class ContactMsgServiceImpl extends ServiceImpl<ContactMsgMapper, ContactMsg> implements ContactMsgService {
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.corewing.app.common.page.PageContext;
import com.corewing.app.dto.sys.SysResetPasswordRequest;
import com.corewing.app.entity.SysUser;
import com.corewing.app.mapper.SysUserMapper;
import com.corewing.app.service.SysUserService;
@@ -25,9 +26,20 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
public Page<SysUser> page(SysUser sysUser) {
Page<SysUser> page = PageContext.getPage(SysUser.class);
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(sysUser.getStatus() != null, SysUser::getStatus, sysUser.getStatus());
queryWrapper.like(StringUtils.hasText(sysUser.getUsername()), SysUser::getUsername, sysUser.getUsername());
return page(page, queryWrapper);
}
public boolean save(SysUser sysUser) {
String encryptPassword = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes(StandardCharsets.UTF_8));
sysUser.setPassword(encryptPassword);
return super.save(sysUser);
}
public boolean update(SysUser sysUser) {
return updateById(sysUser);
}
@Override
public SysUser getByUsername(String username) {
@@ -75,4 +87,16 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
user.setLoginTime(LocalDateTime.now());
this.updateById(user);
}
@Override
public boolean resetPassword(SysResetPasswordRequest sysResetPasswordRequest) {
SysUser sysUser = getById(sysResetPasswordRequest.getUserId());
if(sysUser == null) {
throw new RuntimeException(I18nUtil.getMessage("error.user.not.found"));
}
String encryptPassword = DigestUtils.md5DigestAsHex(sysResetPasswordRequest.getPassword().getBytes(StandardCharsets.UTF_8));
sysUser.setPassword(encryptPassword);
return updateById(sysUser);
}
}

View File

@@ -45,6 +45,11 @@ spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms
# MongoDB 配置
spring.data.mongodb.host=120.24.204.180
spring.data.mongodb.database=app
spring.data.mongodb.port=27017
# Sa-Token 配置
sa-token.token-name=Authorization
sa-token.timeout=2592000

View File

@@ -577,7 +577,7 @@
this.modalInstances['resetPwdModal'].show();
},
resetPwd() {
request.put("/biz/user/resetPasswordRequest", this.resetPasswordDto)
request.put("/biz/user/resetPassword", this.resetPasswordDto)
.then((res) => {
if (res.code === 200) {
$message.success('修改成功!', 500);

View File

@@ -14,7 +14,7 @@
<div class="main-container">
<div class="table-container">
<!-- 页面标题 -->
<h3 class="mb-4">用户管理</h3>
<h3 class="mb-4">系统用户</h3>
<!-- 多参数搜索栏 -->
<div class="search-bar">
@@ -25,10 +25,10 @@
用户名
</span>
<input
v-model="searchParams.keyword"
v-model="searchParams.username"
type="text"
class="form-control"
placeholder="关键词搜索(名称/ID"
placeholder="关键词搜索"
@keyup.enter="fetchData()"
>
</div>
@@ -88,7 +88,6 @@
<th>名称</th>
<th>状态</th>
<th>用户类型</th>
<th>所属部门</th>
<th>创建时间</th>
<th style="width: 120px;">操作</th>
</tr>
@@ -118,7 +117,8 @@
</tr>
<!-- 数据列表 -->
<tr v-else v-for="(item, index) in tableData" :key="item.id" :class="{ selected: selectedIds.includes(item.id) }">
<tr v-else v-for="(item, index) in tableData" :key="item.id"
:class="{ selected: selectedIds.includes(item.id) }">
<td>
<input
type="checkbox"
@@ -129,7 +129,7 @@
>
</td>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.username }}</td>
<td>
<span class="badge" :class="item.status === 1 ? 'bg-success' : 'bg-danger'">
{{ item.status === 1 ? '启用' : '禁用' }}
@@ -140,14 +140,13 @@
{{ item.userType === 'admin' ? '管理员' : item.userType === 'editor' ? '编辑' : '查看者' }}
</span>
</td>
<td>
<span class="badge bg-secondary">
{{ item.deptId === '1' ? '技术部' : item.deptId === '2' ? '运营部' : item.deptId === '3' ? '市场部' : '人事部' }}
</span>
</td>
<td>{{ formatTime(item.createTime) }}</td>
<td>{{ item.createTime }}</td>
<td>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-warning" @click="openPwdModal(item)">
<i class="bi bi-lock"></i>
</button>
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
<i class="bi bi-pencil"></i>
</button>
@@ -161,34 +160,32 @@
</table>
</div>
<!-- 批量操作(表格下方左侧) -->
<div class="batch-actions mt-3">
<div class="d-flex align-items-center gap-2">
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
<!-- <div class="batch-actions mt-3">-->
<!-- <div class="d-flex align-items-center gap-2">-->
<!-- <span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>-->
<!-- 使用select标签实现批量操作选择 -->
<select
class="form-select"
v-model="batchAction"
:disabled="selectedIds.length === 0"
@change="handleBatchOperation"
style="width: auto; min-width: 160px;"
>
<option value="">-- 批量操作 --</option>
<option value="delete">批量删除</option>
<option value="enable">批量启用</option>
<option value="disable">批量禁用</option>
</select>
<!-- <select-->
<!-- class="form-select"-->
<!-- v-model="batchAction"-->
<!-- :disabled="selectedIds.length === 0"-->
<!-- @change="handleBatchOperation"-->
<!-- style="width: auto; min-width: 160px;"-->
<!-- >-->
<!-- <option value="">&#45;&#45; 批量操作 &#45;&#45;</option>-->
<!-- <option value="delete">批量删除</option>-->
<!-- <option value="enable">批量启用</option>-->
<!-- <option value="disable">批量禁用</option>-->
<!-- </select>-->
<button
class="btn btn-secondary"
@click="clearSelected()"
:disabled="selectedIds.length === 0"
>
<i class="bi bi-x-circle me-1"></i> 取消选择
</button>
</div>
</div>
<!-- <button-->
<!-- class="btn btn-secondary"-->
<!-- @click="clearSelected()"-->
<!-- :disabled="selectedIds.length === 0"-->
<!-- >-->
<!-- <i class="bi bi-x-circle me-1"></i> 取消选择-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
<!-- 分页控件 -->
<div class="d-flex justify-content-between align-items-center mt-3">
@@ -218,12 +215,128 @@
</div>
</div>
<!-- 修改密码 -->
<div class="modal fade" id="resetPwdModal" tabindex="-1" aria-labelledby="resetPwdModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="resetPwdModalLabel">修改密码</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="resetPasswordDto.username" disabled>
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">密码</label>
<div class="col-sm-10 position-relative">
<!-- 密码输入框 -->
<input :type="isPasswordVisible ? 'text' : 'password'" class="form-control pe-10"
id="togglePassword"
placeholder="请输入密码" v-model="resetPasswordDto.password">
<!-- 切换按钮(绝对定位在输入框右侧) -->
<button type="button"
@click="togglePasswordVisibility"
class="btn btn-transparent position-absolute end-0 top-0 h-100 px-3 border-0 bg-transparent"
id="toggleBtn">
<i :class="isPasswordVisible ? 'bi bi-eye-slash text-secondary' : 'bi bi-eye text-secondary'"
id="toggleIcon"></i>
</button>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" @click.prevent="resetPwd">修改</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="addOrEditModel" tabindex="-1" aria-labelledby="addOrEditModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="addOrEditModalLabel">{{ addOrEditTitle }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">姓名</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="addOrEditDto.realName"
placeholder="请输入真实姓名">
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="addOrEditDto.username"
placeholder="请输入用户名">
</div>
</div>
<div class="mb-3 row" v-if="addOrEditDto.id == null">
<label class="col-sm-2 col-form-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" v-model="addOrEditDto.password"
placeholder="请输入密码">
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">号码</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="addOrEditDto.telephone"
placeholder="请输入号码">
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">邮箱</label>
<div class="col-sm-10">
<input type="text" class="form-control" v-model="addOrEditDto.email"
placeholder="请输入邮箱">
</div>
</div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">状态</label>
<div class="col-sm-10">
<div class="form-check form-check-inline" >
<input class="form-check-input" type="radio" name="status" id="enableRadio" value="1" v-model="addOrEditDto.status">
<label class="form-check-label" for="enableRadio">启用</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="status" id="disableRadio" value="0" v-model="addOrEditDto.status">
<label class="form-check-label" for="disableRadio">禁用</label>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" @click.prevent="saveUser">保存</button>
</div>
</div>
</div>
</div>
<!-- 外部引入库文件 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- 外部引入模拟请求文件 -->
<script src="/assets/js/axiosRequest.js"></script>
<script src="/assets/js/message.js"></script>
<script src="/assets/js/confirmModal.js"></script>
<!-- 页面核心逻辑 -->
<script>
@@ -236,7 +349,8 @@
loading: false, // 加载状态
// 多搜索参数绑定(与搜索栏对应)
searchParams: {
userName: '',
username: '',
status: '',
},
// 分页参数
pageNum: 1, // 当前页码
@@ -248,7 +362,25 @@
selectAll: false, // 全选状态
batchAction: '', // 批量操作选择
// 分页显示范围最多显示5个页码
pageRange: 5
pageRange: 5,
modalInstances: {},
resetPasswordDto: {
userId: null,
nickName: null,
username: null,
password: null,
},
isPasswordVisible: false,
addOrEditTitle: '',
addOrEditDto: {
id: null,
username: null,
realName: '',
password: null,
status: 1,
email: '',
telephone: ''
}
}
},
computed: {
@@ -310,9 +442,9 @@
});
if (response.code === 200) {
this.tableData = response.data; // 列表数据
this.total = response.data.total; // 总条数
this.totalPages = Math.ceil(this.total / this.pageSize); // 总页数
this.tableData = response.data.records;
this.total = response.data.total;
this.totalPages = response.data.pages;
}
} catch (error) {
console.error('获取数据失败:', error);
@@ -431,35 +563,108 @@
// 单个删除
async handleDelete(id) {
if (!confirm('确定要删除这条数据吗?')) return;
showConfirmModal({
title: '删除确认',
content: '确定要删除这条数据吗?',
onConfirm: async () => {
try {
const response = await request.post('/sys/data/delete', {id});
const response = await request.post('/sys/user/delete', {id});
if (response.code === 200) {
alert('删除成功');
$message.success("删除成功");
this.fetchData(); // 重新加载数据
}
} catch (error) {
console.error('删除失败:', error);
alert('删除失败,请重试!');
$message.error('删除失败,请重试!');
}
}
});
},
// 打开重置密码墨台框
openPwdModal(item) {
console.log(item)
this.resetPasswordDto.userId = item.id;
this.resetPasswordDto.username = item.username;
this.resetPasswordDto.password = '';
this.modalInstances['resetPwdModal'].show();
},
resetPwd() {
request.put("/sys/user/resetPassword", this.resetPasswordDto)
.then((res) => {
if (res.code === 200) {
$message.success('修改成功!', 500);
this.modalInstances['resetPwdModal'].hide();
} else {
$message.error('修改失败,请重试!');
}
this.resetPasswordDto = {};
}).catch((error) => {
$message.error('修改失败,请重试!');
})
},
// 打开新增模态框
openAddModal() {
alert('打开新增模态框');
// 实际项目中添加模态框显示逻辑
this.addOrEditTitle = '新增';
this.clearForm();
this.modalInstances['addOrEditModel'].show();
},
// 打开编辑模态框
openEditModal(item) {
alert(`打开编辑模态框编辑ID: ${item.id}`);
// 实际项目中添加模态框显示和数据回显逻辑
this.clearForm();
this.addOrEditTitle = '编辑';
this.addOrEditDto = item;
this.modalInstances['addOrEditModel'].show();
},
saveUser() {
let url = (this.addOrEditDto === null || this.addOrEditDto.id === null) ? '/sys/user/save' : '/sys/user/update';
request.post(url, this.addOrEditDto)
.then((res) => {
if (res.code === 200) {
$message.success('保存成功!', 500);
this.fetchData();
this.modalInstances['addOrEditModel'].hide();
} else {
$message.error('保存失败!', 500);
}
this.clearForm();
}).catch(() => {
})
},
togglePasswordVisibility() {
this.isPasswordVisible = !this.isPasswordVisible;
},
// 清空表单数据
clearForm() {
this.addOrEditDto = {
id: null,
username: '',
realName: '',
password: null,
status: 1,
email: '',
telephone: ''
};
}
},
mounted() {
// 页面加载完成后初始化数据
this.fetchData();
const modalIds = ['resetPwdModal', 'addOrEditModel'];
modalIds.forEach(id => {
console.log(id)
const modalElement = document.getElementById(id);
if (modalElement) {
this.modalInstances[id] = new bootstrap.Modal(modalElement, {
backdrop: 'static',
keyboard: true
});
}
});
}
}).mount('#app');
</script>

View File

@@ -110,7 +110,7 @@
<!-- 底部提示 -->
<div class="mt-8 text-center text-xs text-secondary">
<p>如有任何疑问,请联系我们的客服团队</p>
<p class="mt-1">service@example.com</p>
<p class="mt-1">support@worewing.com</p>
</div>
</main>