新增微信管理模块和用户资料路由
- 集成微信管理路由与配置、模板和日志页面 - 新增个人信息用户资料路由 - 更新了 AdminLayout,添加了新的微信相关图标和菜单映射 - 删除了默认style.css内容
This commit is contained in:
parent
14b73bcf87
commit
6fb1f781d4
1
src/api/system/profile.js
Normal file
1
src/api/system/profile.js
Normal file
@ -0,0 +1 @@
|
||||
|
||||
114
src/api/wechat/index.js
Normal file
114
src/api/wechat/index.js
Normal file
@ -0,0 +1,114 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取微信公众号配置
|
||||
* @returns {Promise} 返回配置信息
|
||||
*/
|
||||
export function getWechatConfig() {
|
||||
return request.get('/api/admin/wechat/config')
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新微信公众号配置
|
||||
* @param {Object} data - 配置数据
|
||||
* @param {string} data.app_id - 公众号AppID
|
||||
* @param {string} data.app_secret - 公众号AppSecret
|
||||
* @param {string} data.token - 公众号Token
|
||||
* @param {string} data.encoding_aes_key - 消息加密密钥
|
||||
* @param {number} data.status - 状态:0-禁用 1-启用
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateWechatConfig(data) {
|
||||
return request.put('/api/admin/wechat/config', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建消息模板
|
||||
* @param {Object} data - 模板数据
|
||||
* @param {string} data.template_id - 模板ID
|
||||
* @param {string} data.title - 模板标题
|
||||
* @param {string} data.content - 模板内容
|
||||
* @param {string} data.example - 模板示例
|
||||
* @param {string} data.type - 模板类型:activity-活动通知 system-系统通知
|
||||
* @param {number} data.status - 状态:0-禁用 1-启用
|
||||
* @returns {Promise} 返回创建结果
|
||||
*/
|
||||
export function createTemplate(data) {
|
||||
return request.post('/api/admin/wechat/templates', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.page_size=10] - 每页数量
|
||||
* @param {string} [params.type] - 模板类型
|
||||
* @param {number} [params.status] - 状态
|
||||
* @returns {Promise} 返回模板列表数据
|
||||
*/
|
||||
export function getTemplateList(params = {}) {
|
||||
return request.get('/api/admin/wechat/templates', {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
page_size: params.page_size || 10,
|
||||
type: params.type,
|
||||
status: params.status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板详情
|
||||
* @param {string|number} id - 模板ID
|
||||
* @returns {Promise} 返回模板详情
|
||||
*/
|
||||
export function getTemplateDetail(id) {
|
||||
return request.get(`/api/admin/wechat/templates/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模板
|
||||
* @param {string|number} id - 模板ID
|
||||
* @param {Object} data - 更新数据
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateTemplate(id, data) {
|
||||
return request.put(`/api/admin/wechat/templates/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模板
|
||||
* @param {string|number} id - 模板ID
|
||||
* @returns {Promise} 返回删除结果
|
||||
*/
|
||||
export function deleteTemplate(id) {
|
||||
return request.delete(`/api/admin/wechat/templates/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息发送记录
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.page_size=10] - 每页数量
|
||||
* @param {number} [params.status] - 发送状态:0-失败 1-成功
|
||||
* @param {string} [params.start_date] - 开始日期
|
||||
* @param {string} [params.end_date] - 结束日期
|
||||
* @returns {Promise} 返回消息发送记录列表
|
||||
*/
|
||||
export function getMessageLogs(params = {}) {
|
||||
// 移除所有 undefined 和空字符串的参数
|
||||
const validParams = {}
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] !== undefined && params[key] !== '') {
|
||||
validParams[key] = params[key]
|
||||
}
|
||||
})
|
||||
|
||||
return request.get('/api/admin/wechat/message-logs', {
|
||||
params: {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
...validParams
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -21,7 +21,8 @@ import {
|
||||
VideoCamera,
|
||||
ChatLineRound,
|
||||
InfoFilled,
|
||||
Grid
|
||||
Grid,
|
||||
ChatDotRound
|
||||
} from "@element-plus/icons-vue";
|
||||
import logo from '../assets/images/logo.png';
|
||||
|
||||
@ -49,7 +50,8 @@ const icons = {
|
||||
VideoCamera: markRaw(VideoCamera),
|
||||
ChatLineRound: markRaw(ChatLineRound),
|
||||
InfoFilled: markRaw(InfoFilled),
|
||||
Grid: markRaw(Grid)
|
||||
Grid: markRaw(Grid),
|
||||
ChatDotRound: markRaw(ChatDotRound)
|
||||
};
|
||||
|
||||
// 图标映射关系
|
||||
@ -64,7 +66,8 @@ const iconMapping = {
|
||||
'activity': 'Collection',
|
||||
'course': 'DataLine',
|
||||
'feedback': 'ChatLineRound',
|
||||
'about': 'InfoFilled'
|
||||
'about': 'InfoFilled',
|
||||
'wechat': 'ChatDotRound'
|
||||
};
|
||||
|
||||
// 获取路由菜单
|
||||
|
||||
@ -40,6 +40,39 @@ const router = createRouter({
|
||||
component: () => import('../views/dashboard/index.vue'),
|
||||
meta: { title: '控制台', icon: 'HomeFilled' }
|
||||
},
|
||||
{
|
||||
path: 'system/profile',
|
||||
name: 'UserProfile',
|
||||
component: () => import('../views/system/profile/index.vue'),
|
||||
meta: { title: '个人信息', hideInMenu: true }
|
||||
},
|
||||
// 微信管理
|
||||
{
|
||||
path: 'wechat',
|
||||
name: 'Wechat',
|
||||
meta: { title: '消息推送管理', icon: 'ChatDotRound' },
|
||||
redirect: '/wechat/config',
|
||||
children: [
|
||||
{
|
||||
path: 'config',
|
||||
name: 'WechatConfig',
|
||||
component: () => import('../views/wechat/config/index.vue'),
|
||||
meta: { title: '公众号配置' }
|
||||
},
|
||||
{
|
||||
path: 'templates',
|
||||
name: 'WechatTemplates',
|
||||
component: () => import('../views/wechat/templates/index.vue'),
|
||||
meta: { title: '消息模板' }
|
||||
},
|
||||
{
|
||||
path: 'logs',
|
||||
name: 'WechatLogs',
|
||||
component: () => import('../views/wechat/logs/index.vue'),
|
||||
meta: { title: '发送记录' }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 系统管理
|
||||
{
|
||||
path: 'system',
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
364
src/views/system/profile/index.vue
Normal file
364
src/views/system/profile/index.vue
Normal file
@ -0,0 +1,364 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { User, Lock, Message, Phone } from '@element-plus/icons-vue'
|
||||
|
||||
// 用户信息
|
||||
const userInfo = reactive({
|
||||
username: 'admin',
|
||||
nickname: '管理员',
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
avatar: '',
|
||||
role: '超级管理员',
|
||||
createTime: '2023-01-01',
|
||||
lastLoginTime: '2024-03-20 10:00:00',
|
||||
lastLoginIp: '192.168.1.100'
|
||||
})
|
||||
|
||||
// 修改密码表单
|
||||
const passwordForm = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
// 修改密码表单规则
|
||||
const passwordRules = {
|
||||
oldPassword: [
|
||||
{ required: true, message: '请输入原密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== passwordForm.newPassword) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 修改个人信息表单
|
||||
const profileForm = reactive({
|
||||
nickname: userInfo.nickname,
|
||||
email: userInfo.email,
|
||||
phone: userInfo.phone
|
||||
})
|
||||
|
||||
// 个人信息表单规则
|
||||
const profileRules = {
|
||||
nickname: [
|
||||
{ required: true, message: '请输入昵称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const passwordFormRef = ref()
|
||||
const profileFormRef = ref()
|
||||
|
||||
// 处理修改密码
|
||||
const handleChangePassword = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
|
||||
await passwordFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success('密码修改成功(这是模拟消息)')
|
||||
// 重置表单
|
||||
passwordForm.oldPassword = ''
|
||||
passwordForm.newPassword = ''
|
||||
passwordForm.confirmPassword = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理更新个人信息
|
||||
const handleUpdateProfile = async () => {
|
||||
if (!profileFormRef.value) return
|
||||
|
||||
await profileFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success('个人信息更新成功(这是模拟消息)')
|
||||
// 更新用户信息
|
||||
userInfo.nickname = profileForm.nickname
|
||||
userInfo.email = profileForm.email
|
||||
userInfo.phone = profileForm.phone
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理头像上传
|
||||
const handleAvatarSuccess = (response) => {
|
||||
userInfo.avatar = response.url
|
||||
ElMessage.success('头像上传成功(这是模拟消息)')
|
||||
}
|
||||
|
||||
// 上传之前的处理
|
||||
const beforeAvatarUpload = (file) => {
|
||||
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
|
||||
const isLt2M = file.size / 1024 / 1024 < 2
|
||||
|
||||
if (!isJPG) {
|
||||
ElMessage.error('上传头像图片只能是 JPG/PNG 格式!')
|
||||
}
|
||||
if (!isLt2M) {
|
||||
ElMessage.error('上传头像图片大小不能超过 2MB!')
|
||||
}
|
||||
return isJPG && isLt2M
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="profile-container">
|
||||
<!-- 基本信息卡片 -->
|
||||
<el-card class="profile-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="profile-content">
|
||||
<!-- 左侧头像区域 -->
|
||||
<div class="avatar-container">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
action="#"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:on-success="handleAvatarSuccess"
|
||||
>
|
||||
<img
|
||||
v-if="userInfo.avatar"
|
||||
:src="userInfo.avatar"
|
||||
class="avatar"
|
||||
/>
|
||||
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
||||
</el-upload>
|
||||
<div class="upload-tip">点击上传头像</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧信息区域 -->
|
||||
<div class="info-container">
|
||||
<el-form
|
||||
ref="profileFormRef"
|
||||
:model="profileForm"
|
||||
:rules="profileRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="userInfo.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="profileForm.nickname" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="profileForm.email">
|
||||
<template #prefix>
|
||||
<el-icon><Message /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="profileForm.phone">
|
||||
<template #prefix>
|
||||
<el-icon><Phone /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<el-input v-model="userInfo.role" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleUpdateProfile">保存修改</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 安全设置卡片 -->
|
||||
<el-card class="profile-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>安全设置</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="passwordFormRef"
|
||||
:model="passwordForm"
|
||||
:rules="passwordRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="原密码" prop="oldPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.oldPassword"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入原密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.newPassword"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入新密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="passwordForm.confirmPassword"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请确认新密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleChangePassword">修改密码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 登录信息 -->
|
||||
<div class="login-info">
|
||||
<div class="info-item">
|
||||
<span class="label">上次登录时间:</span>
|
||||
<span class="value">{{ userInfo.lastLoginTime }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">上次登录IP:</span>
|
||||
<span class="value">{{ userInfo.lastLoginIp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.profile-container {
|
||||
padding: 20px;
|
||||
|
||||
.profile-card {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-content {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
|
||||
.avatar-container {
|
||||
text-align: center;
|
||||
|
||||
.avatar-uploader {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
|
||||
&:hover {
|
||||
border-color: #409EFF;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
line-height: 178px;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-container {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-info {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
.label {
|
||||
color: #606266;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
}
|
||||
</style>
|
||||
367
src/views/wechat/config/index.vue
Normal file
367
src/views/wechat/config/index.vue
Normal file
@ -0,0 +1,367 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Setting, Key, Lock, Warning } from '@element-plus/icons-vue'
|
||||
import { getWechatConfig, updateWechatConfig } from '@/api/wechat'
|
||||
|
||||
// 配置表单数据
|
||||
const formData = reactive({
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
token: '',
|
||||
encoding_aes_key: '',
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 获取配置信息
|
||||
const getConfig = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getWechatConfig()
|
||||
if (res.success) {
|
||||
const { app_id, app_secret, token, encoding_aes_key, status } = res.data
|
||||
Object.assign(formData, {
|
||||
app_id,
|
||||
app_secret: app_secret || formData.app_secret,
|
||||
token,
|
||||
encoding_aes_key,
|
||||
status: status ?? 1
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取配置失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取配置失败:', error)
|
||||
ElMessage.error('获取配置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单规则
|
||||
const rules = {
|
||||
app_id: [
|
||||
{ required: true, message: '请输入AppID', trigger: 'blur' },
|
||||
{ min: 10, message: 'AppID长度不能小于10个字符', trigger: 'blur' }
|
||||
],
|
||||
app_secret: [
|
||||
{ required: true, message: '请输入AppSecret', trigger: 'blur' },
|
||||
{ min: 10, message: 'AppSecret长度不能小于10个字符', trigger: 'blur' }
|
||||
],
|
||||
token: [
|
||||
{ required: true, message: '请输入Token', trigger: 'blur' },
|
||||
{ min: 3, message: 'Token长度不能小于3个字符', trigger: 'blur' }
|
||||
],
|
||||
encoding_aes_key: [
|
||||
{ min: 10, message: '加密密钥长度不能小于10个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref()
|
||||
|
||||
// 保存配置
|
||||
const handleSave = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const res = await updateWechatConfig(formData)
|
||||
if (res.success) {
|
||||
ElMessage.success('保存成功')
|
||||
getConfig() // 重新获取配置
|
||||
} else {
|
||||
ElMessage.error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
ElMessage.error('保存失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="header-title">
|
||||
<el-icon class="icon"><Setting /></el-icon>
|
||||
<span>公众号配置</span>
|
||||
</div>
|
||||
<div class="header-desc">配置微信公众号的基本信息,用于消息推送和用户交互</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="main-content">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="140px"
|
||||
class="config-form"
|
||||
>
|
||||
<div class="form-section">
|
||||
<div class="section-title">基本配置</div>
|
||||
<el-form-item label="AppID" prop="app_id">
|
||||
<el-input
|
||||
v-model="formData.app_id"
|
||||
placeholder="请输入公众号AppID"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Key /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="AppSecret" prop="app_secret">
|
||||
<el-input
|
||||
v-model="formData.app_secret"
|
||||
type="password"
|
||||
placeholder="请输入公众号AppSecret"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Token" prop="token">
|
||||
<el-input
|
||||
v-model="formData.token"
|
||||
placeholder="请输入公众号Token"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Key /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="消息加密密钥" prop="encoding_aes_key">
|
||||
<el-input
|
||||
v-model="formData.encoding_aes_key"
|
||||
placeholder="请输入消息加密密钥(选填)"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSave" class="save-button">
|
||||
保存配置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 配置说明 -->
|
||||
<div class="config-tips">
|
||||
<div class="tips-header">
|
||||
<el-icon class="warning-icon"><Warning /></el-icon>
|
||||
<span>配置说明</span>
|
||||
</div>
|
||||
<div class="tips-content">
|
||||
<div class="tip-item">
|
||||
<div class="tip-title">AppID 和 AppSecret</div>
|
||||
<div class="tip-desc">可在微信公众平台 > 开发 > 基本配置中获取,是进行消息交互的重要凭证</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-title">Token</div>
|
||||
<div class="tip-desc">用于验证消息的确来自微信服务器,建议使用16位以上的随机字符串</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-title">消息加密密钥</div>
|
||||
<div class="tip-desc">用于加强消息安全性,可选填,建议在正式环境中启用</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-title">安全提示</div>
|
||||
<div class="tip-desc">所有配置信息请妥善保管,不要泄露给他人,建议定期更新Token和加密密钥</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: calc(100vh - 120px);
|
||||
|
||||
.box-card {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
|
||||
.config-form {
|
||||
.form-section {
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 22px;
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
.el-input__wrapper {
|
||||
box-shadow: 0 0 0 1px #dcdfe6 inset;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px #c0c4cc inset;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
box-shadow: 0 0 0 1px #409EFF inset;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__prefix {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.save-button {
|
||||
width: 160px;
|
||||
height: 40px;
|
||||
font-size: 15px;
|
||||
margin-top: 10px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-tips {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ebeef5;
|
||||
height: fit-content;
|
||||
|
||||
.tips-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.warning-icon {
|
||||
font-size: 18px;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
padding: 20px;
|
||||
|
||||
.tip-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tip-desc {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
262
src/views/wechat/logs/index.vue
Normal file
262
src/views/wechat/logs/index.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Timer, Message } from '@element-plus/icons-vue'
|
||||
import { getMessageLogs } from '@/api/wechat'
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
status: undefined,
|
||||
start_date: '',
|
||||
end_date: ''
|
||||
})
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref([])
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false)
|
||||
const logsList = ref([])
|
||||
|
||||
// 分页参数
|
||||
const pagination = reactive({
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建请求参数,移除空值
|
||||
const params = {
|
||||
page: queryParams.page,
|
||||
page_size: queryParams.page_size
|
||||
}
|
||||
|
||||
// 只有当状态不是 undefined 时才添加
|
||||
if (queryParams.status !== undefined) {
|
||||
params.status = queryParams.status
|
||||
}
|
||||
|
||||
// 只有当日期不为空时才添加
|
||||
if (queryParams.start_date) {
|
||||
params.start_date = queryParams.start_date
|
||||
}
|
||||
if (queryParams.end_date) {
|
||||
params.end_date = queryParams.end_date
|
||||
}
|
||||
|
||||
const res = await getMessageLogs(params)
|
||||
if (res.success) {
|
||||
logsList.value = res.data.list || []
|
||||
if (res.data.pagination) {
|
||||
pagination.total = res.data.pagination.total || 0
|
||||
pagination.page = res.data.pagination.current || 1
|
||||
pagination.pageSize = res.data.pagination.page_size || 10
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取发送记录失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取发送记录失败:', error)
|
||||
ElMessage.error('获取发送记录失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
queryParams.status = undefined
|
||||
queryParams.start_date = ''
|
||||
queryParams.end_date = ''
|
||||
queryParams.page = 1
|
||||
dateRange.value = [] // 重置日期选择器
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理页码改变
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.page = val
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理每页条数改变
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.page_size = val
|
||||
queryParams.page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 格式化发送状态
|
||||
const formatStatus = (status) => {
|
||||
const statusMap = {
|
||||
0: { type: 'danger', text: '发送失败' },
|
||||
1: { type: 'success', text: '发送成功' }
|
||||
}
|
||||
return statusMap[status] || { type: 'info', text: '未知状态' }
|
||||
}
|
||||
|
||||
// 日期范围选择器快捷选项
|
||||
const pickerOptions = {
|
||||
shortcuts: [
|
||||
{
|
||||
text: '最近一周',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '最近一个月',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '最近三个月',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
|
||||
return [start, end]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 处理日期范围变化
|
||||
const handleDateRangeChange = (dates) => {
|
||||
if (dates) {
|
||||
queryParams.start_date = dates[0]
|
||||
queryParams.end_date = dates[1]
|
||||
} else {
|
||||
queryParams.start_date = ''
|
||||
queryParams.end_date = ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
<el-form-item label="发送状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择发送状态"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option label="发送成功" :value="1" />
|
||||
<el-option label="发送失败" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送时间" prop="date_range">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:shortcuts="pickerOptions.shortcuts"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleDateRangeChange"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="logsList"
|
||||
border
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="template_id" label="模板ID" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="template_title" label="模板标题" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="content" label="发送内容" min-width="300" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="发送状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="formatStatus(row.status).type" size="small">
|
||||
{{ formatStatus(row.status).text }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="error_message" label="错误信息" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.status === 0" class="error-message">{{ row.error_message }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="发送时间" width="180" align="center" />
|
||||
<el-table-column prop="sender_name" label="发送人" width="120" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
v-if="pagination.total > 10"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
377
src/views/wechat/templates/index.vue
Normal file
377
src/views/wechat/templates/index.vue
Normal file
@ -0,0 +1,377 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import { getTemplateList, createTemplate, updateTemplate, deleteTemplate } from '@/api/wechat'
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
title: '',
|
||||
type: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
// 模板类型选项
|
||||
const templateTypes = [
|
||||
{ label: '活动通知', value: 'activity' },
|
||||
{ label: '系统通知', value: 'system' }
|
||||
]
|
||||
|
||||
// 表格数据
|
||||
const loading = ref(false)
|
||||
const templateList = ref([])
|
||||
|
||||
// 分页参数
|
||||
const pagination = reactive({
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...queryParams,
|
||||
type: queryParams.type || undefined,
|
||||
status: queryParams.status === '' ? undefined : queryParams.status
|
||||
}
|
||||
|
||||
const res = await getTemplateList(params)
|
||||
if (res.success) {
|
||||
templateList.value = res.data.list || []
|
||||
if (res.data.pagination) {
|
||||
pagination.total = res.data.pagination.total || 0
|
||||
pagination.page = res.data.pagination.current || 1
|
||||
pagination.pageSize = res.data.pagination.page_size || 10
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取模板列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取模板列表失败:', error)
|
||||
ElMessage.error('获取模板列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
queryParams.title = ''
|
||||
queryParams.type = undefined
|
||||
queryParams.status = undefined
|
||||
queryParams.page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 弹窗控制
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const dialogTitle = computed(() => dialogType.value === 'add' ? '添加模板' : '编辑模板')
|
||||
|
||||
// 表单数据
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
template_id: '',
|
||||
title: '',
|
||||
content: '',
|
||||
example: '',
|
||||
type: '',
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
template_id: [
|
||||
{ required: true, message: '请输入模板ID', trigger: 'blur' }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: '请输入模板标题', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入模板内容', trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择模板类型', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
// 处理添加/编辑
|
||||
const handleAddOrEdit = (type, row) => {
|
||||
dialogType.value = type
|
||||
dialogVisible.value = true
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
formData.value = { ...row }
|
||||
} else {
|
||||
formData.value = {
|
||||
template_id: '',
|
||||
title: '',
|
||||
content: '',
|
||||
example: '',
|
||||
type: '',
|
||||
status: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认删除该模板吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
const res = await deleteTemplate(row.id)
|
||||
if (res.success) {
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const api = dialogType.value === 'add' ? createTemplate : updateTemplate
|
||||
const res = await api(
|
||||
dialogType.value === 'add' ? formData.value : formData.value.id,
|
||||
formData.value
|
||||
)
|
||||
|
||||
if (res.success) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '添加成功' : '修改成功')
|
||||
dialogVisible.value = false
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error('提交失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理页码改变
|
||||
const handleCurrentChange = (val) => {
|
||||
queryParams.page = val
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理每页条数改变
|
||||
const handleSizeChange = (val) => {
|
||||
queryParams.page_size = val
|
||||
queryParams.page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<el-card class="search-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
<el-form-item label="模板标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入模板标题"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择模板类型"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in templateTypes"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="table-container">
|
||||
<template #header>
|
||||
<el-button type="primary" :icon="Plus" @click="handleAddOrEdit('add')">新增模板</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="templateList"
|
||||
border
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="template_id" label="模板ID" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="title" label="模板标题" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="content" label="模板内容" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="example" label="模板示例" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="type" label="模板类型" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ templateTypes.find(item => item.value === row.type)?.label || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link :icon="Edit" @click="handleAddOrEdit('edit', row)">编辑</el-button>
|
||||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pagination.total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加/编辑弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="700px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="模板ID" prop="template_id">
|
||||
<el-input v-model="formData.template_id" placeholder="请输入模板ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入模板标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入模板内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板示例" prop="example">
|
||||
<el-input
|
||||
v-model="formData.example"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入模板示例"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="模板类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择模板类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in templateTypes"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: 20px;
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user