添加:消息中心

This commit is contained in:
wuhui_zzw 2023-10-27 17:21:29 +08:00
parent 2fd7085b3c
commit 3e95f923ef
17 changed files with 836 additions and 2 deletions

View File

@ -0,0 +1 @@
## 消息中心

View File

@ -0,0 +1,14 @@
<?php
return [
app\common\events\PluginWasEnabled::class => function ($plugins) {
\Artisan::call('migrate',['--path'=>'plugins/message-center/migrations','--force'=>true]);
},
app\common\events\PluginWasDisabled::class => function ($plugin) {
},
app\common\events\PluginWasDeleted::class => function () {
\Artisan::call('migrate:rollback',['--path'=>'plugins/message-center/migrations']);
}
];

View File

@ -0,0 +1,8 @@
"use strict";
$.extend($.locales['en'], {
'welfare': {
test: "JavaScript i18n test: English"
}
});

View File

@ -0,0 +1,5 @@
<?php
return [
'title'=>'this is test title'
];

View File

@ -0,0 +1,6 @@
$.extend($.locales['zh-CN'], {
'welfare': {
test: "JavaScript i18n test: 简体中文"
}
});

View File

@ -0,0 +1,5 @@
<?php
return [
'title'=>'测试标题'
];

View File

@ -0,0 +1,11 @@
{
"name": "message-center",
"terminal": "wechat|min|wap",
"version": "1.0.1",
"title": "消息中心",
"description": "消息中心",
"author": "zzw",
"url": "",
"namespace": "Yunshop\\MessageCenter",
"config": "config.tpl"
}

View File

@ -0,0 +1,90 @@
<?php
namespace Yunshop\MessageCenter;
class PluginApplication extends \app\common\services\PluginApplication{
protected function setMenuConfig(){
\app\backend\modules\menu\Menu::current()->setPluginMenu('message-center', [
'name' => '消息中心',
'type' => 'marketing',
'url' => 'plugin.message-center.admin.index.index',// url 可以填写http 也可以直接写路由
'url_params' => '',//如果是url填写的是路由则启用参数否则不启用
'permit' => 1,//如果不设置则不会做权限检测
'menu' => 1,//如果不设置则不显示菜单,子菜单也将不显示
'icon' => '',//菜单图标
'list_icon' => 'message-center',
'parents' => [],
'top_show' => 0,
'left_first_show' => 0,
'left_second_show' => 1,
'child' => [
'plugin_message_center_index' => [
'name' => '消息管理',
'permit' => 1,
'menu' => 1,
'icon' => '',
'url' => 'plugin.message-center.admin.index.index',
'url_params' => '',
'item' => 'plugin_message_center_index',
'parents' => ['message-center'],
'child' => [
// 权限补充
'plugin_message_center_index_index' => [
'name' => '消息管理',
'url' => 'plugin.message-center.admin.index.index',
'url_params' => '',
'permit' => 1,
'menu' => 0,
'icon' => '',
'item' => 'plugin_message_center_index_index',
'parents' => ['message-center','plugin_message_center_index']
],
'plugin_message_center_index_edit_info' => [
'name' => '消息编辑',
'url' => 'plugin.message-center.admin.index.edit-info',
'url_params' => '',
'permit' => 1,
'menu' => 0,
'icon' => '',
'item' => 'plugin_message_center_index_edit_info',
'parents' => ['message-center','plugin_message_center_index']
],
]
],
'plugin_message_center_read_record' => [
'name' => '阅读记录',
'permit' => 1,
'menu' => 1,
'icon' => '',
'url' => 'plugin.message-center.admin.read-record.index',
'url_params' => '',
'parents' => ['message-center'],
'child' => [
// 权限补充
'plugin_message_center_read_record_index' => [
'name' => '转账明细',
'url' => 'plugin.message-center.admin.read-record.index',
'url_params' => '',
'permit' => 1,
'menu' => 0,
'icon' => '',
'item' => 'plugin_message_center_read_record_index',
'parents' => ['message-center','plugin_message_center_read_record']
],
]
],
]
]);
}
public function boot(){
$events = app('events');
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace Yunshop\MessageCenter\admin;
use app\common\components\BaseController;
use app\common\helpers\PaginationHelper;
use Illuminate\Support\Facades\DB;
use Yunshop\MessageCenter\models\MessageCenter;
use Yunshop\MessageCenter\models\MessageCenterRead;
class IndexController extends BaseController{
/**
* Common: 消息列表获取
* Author: wu-hui
* Time: 2023/10/27 16:42
* @return array|string
* @throws \Throwable
*/
public function index(){
//参数获取
$search = request()->input('search');
// 获取列表信息
$result = MessageCenter::getList($search);
$data = [
'list' => $result['data'],
'pager' => PaginationHelper::show($result['total'],$result['current_page'],$result['per_page']),
'search' => $search
];
return view('Yunshop\MessageCenter::index.index',$data)->render();
}
/**
* Common: 消息发布
* Author: wu-hui
* Time: 2023/10/27 16:35
* @return array|\Illuminate\Http\JsonResponse|string
* @throws \Throwable
*/
public function editInfo(){
// 参数获取
if(request()->isMethod('post')){
DB::beginTransaction();
try{
// 参数处理
$info = request()->input('info');
$info['user_ids'] = trim(implode(',', array_unique($info['user_ids'])),',');
// 消息发布
$id = MessageCenter::insertGetId([
'uniacid' => \YunShop::app()->uniacid,
'notice_type' => (int)$info['notice_type'],
'message_title' => $info['message_title'],
'message_content' => $info['message_content'],
'created_at' => time(),
'updated_at' => time(),
]);
MessageCenterRead::sendMessage($info,$id);
DB::commit();
return $this->successJson('发布成功');
}catch(\Exception $e){
DB::rollBack();
return $this->errorJson($e->getMessage());
}
}
return view('Yunshop\MessageCenter::index.edit')->render();
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Yunshop\MessageCenter\admin;
use app\common\components\BaseController;
use app\common\helpers\PaginationHelper;
use Yunshop\MessageCenter\models\MessageCenterRead;
class ReadRecordController extends BaseController{
/**
* Common: 阅读记录
* Author: wu-hui
* Time: 2023/10/27 17:13
* @return array|string
* @throws \Throwable
*/
public function index(){
//参数获取
$search = request()->input('search');
// 获取列表信息
$result = MessageCenterRead::getList($search);
$data = [
'list' => $result['data'],
'pager' => PaginationHelper::show($result['total'],$result['current_page'],$result['per_page']),
'search' => $search
];
return view('Yunshop\MessageCenter::read_record.index',$data)->render();
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2020/3/18
* Time: 14:27
*/
namespace Yunshop\MessageCenter\models;
use app\common\models\BaseModel;
class MessageCenter extends BaseModel{
public $table = 'yz_message_center';
public $casts = [
'created_at' => 'datetime:Y-m-d H:i:s'
];
protected $fillable = [
'uniacid',
'type',
'message_title',
'message_content',
'created_at',
'updated_at',
'deleted_at',
];
/**
* Common: 列表信息获取
* Author: wu-hui
* Time: 2023/10/27 16:42
* @param $search
* @return mixed
*/
public function getList($search){
// 条件生成
$where = [];
if($search['message_title'] > 0) $where[] = ['message_title','like',"%{$search['member_id']}%"];
// 列表获取
return self::uniacid()
->select(['id','message_title','notice_type','created_at'])
->where($where)
->withCount(['read as total_user',
'read as total_unread' => function($query){
$query->where('is_see',0);
},
'read as total_read' => function($query){
$query->where('is_see',1);
}
])
->orderBy('created_at','DESC')
->orderBy('id','DESC')
->paginate(10)
->toArray();
}
/**
* Common: 关联阅读信息表(一对多)
* Author: wu-hui
* Time: 2023/10/27 13:47
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function read(){
return $this->hasMany(MessageCenterRead::class,'message_center_id','id');
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2020/3/18
* Time: 14:27
*/
namespace Yunshop\MessageCenter\models;
use app\common\models\BaseModel;
use app\common\models\Member;
class MessageCenterRead extends BaseModel{
public $table = 'yz_message_center_read';
public $casts = [
'created_at' => 'datetime:Y-m-d H:i:s',
'updated_at' => 'datetime:Y-m-d H:i:s',
'read_time' => 'datetime:Y-m-d H:i:s'
];
protected $fillable = [
'uniacid',
'member_id',
'is_see',
'message_center_id',
'created_at',
'updated_at',
'deleted_at',
];
/**
* Common: 阅读记录
* Author: wu-hui
* Time: 2023/10/27 17:12
* @param $search
* @return mixed
*/
public function getList($search){
// 条件生成
$where = [
['yz_message_center_read.is_see','=',1]
];
if($search['message_title'] > 0) $where[] = ['yz_message_center.message_title','like',"%{$search['member_id']}%"];
// 列表获取
return self::uniacid()
->leftjoin('yz_message_center','yz_message_center.id','yz_message_center_read.message_center_id')
->select([
'yz_message_center_read.id',
'yz_message_center.message_title',
'yz_message_center_read.member_id',
'yz_message_center_read.read_time'
])
->with(['member'=>function($query){
$query->select('uid', 'nickname', 'realname', 'avatar');
}])
->where($where)
->orderBy('yz_message_center_read.updated_at','DESC')
->orderBy('yz_message_center_read.id','DESC')
->paginate(10)
->toArray();
}
/**
* Common: 发布消息
* Author: wu-hui
* Time: 2023/10/27 16:35
* @param $info
* @param $messageCenterId
* @return BaseModel
*/
public static function sendMessage($info,$messageCenterId){
// 用户信息获取
$userIds = self::getNoticeUserList($info);
// 生成记录 达到发送消息的结果
$uniacid = \YunShop::app()->uniacid;
$insertData = [];
foreach($userIds as $memberId){
$insertData[] = [
'uniacid' => $uniacid,
'member_id' => $memberId,
'message_center_id' => $messageCenterId,
'created_at' => time(),
'updated_at' => time(),
];
}
return self::insert($insertData);
}
/**
* Common: 获取通知用户列表
* Author: wu-hui
* Time: 2023/10/27 16:26
* @param $info
* @return \app\framework\Database\Eloquent\Collection|string[]
*/
private static function getNoticeUserList($info){
$ids = explode(',',$info['user_ids']);// 指定用户
if((int)$info['notice_type'] == 0) $ids = Member::uniacid()->pluck('uid')->toArray();// 全部用户
return $ids;
}
/**
* Common: 关联用户表 一对一
* Author: wu-hui
* Time: 2023/10/27 17:10
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function member(){
return $this->hasOne(Member::class,'uid','member_id');
}
}

View File

View File

@ -0,0 +1,218 @@
@extends('layouts.base')
@section('title', '编辑文章')
@section('content')
<style>
.user-list{
--user-block-margin-right-: 15px;
width: 100%;
display: inline-flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
}
.user-list .user-block{
width: calc((100% - (2 * var(--user-block-margin-right-))) / 3);
margin-right: var(--user-block-margin-right-)!important;
margin-bottom: 15px!important;
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
background: #fff;
border-radius: 5px;
padding: 10px 15px;
box-shadow: 0 0 5px 0 #14141433;
position: relative;
}
.user-list .user-block:nth-child(3n){
margin-right: 0!important;
}
.user-list .user-block .avatar{
width: 65px!important;
height: 65px!important;
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
}
.user-list .user-block .avatar .avatar-image{
width: 100%;
height: 100%;
}
.user-list .user-block .user-info{
width: calc(100% - 75px) !important;
height: 65px!important;
}
.user-list .user-block .user-info .user-title{
height: 35px;
line-height: 35px;
font-size: 14px;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 800;
}
.user-list .user-block .user-info .user-id{
height: 30px;
line-height: 30px;
font-size: 14px;
font-weight: 500;
}
.user-list .user-block .del-btn{
position: absolute;
right: 0;
top: 0;
border-top-right-radius: 5px;
background: #f56c6c;
color: #FFF;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
cursor: pointer;
}
</style>
<div class="all">
<div id="app" v-cloak>
<el-form ref="form" :model="info" :rules="rules" label-width="15%">
<div class="vue-head" style="width: 70%!important;margin-top: 20px;">
<el-form-item label="消息标题" prop="message_title">
<el-input v-model.trim="info.message_title" placeholder="请输入消息标题"></el-input>
</el-form-item>
<el-form-item label="通知类型" prop="notice_type">
<div style="line-height:40px">
<el-radio v-model.number="info.notice_type" :label="0">全部用户</el-radio>
<el-radio v-model.number="info.notice_type" :label="1">指定用户</el-radio>
</div>
</el-form-item>
<el-form-item label="通知用户" v-if="info.notice_type == 1">
<div class="user-list" v-if="Object.values(user_list).length > 0">
<div class="user-block" v-for="(item,index) in user_list">
<div class="avatar">
<img class="avatar-image" :src="item.member_avatar" />
</div>
<div class="user-info">
<div class="user-title">[[ item.member_name ]]</div>
<div class="user-id">ID[[ item.uid ]]</div>
</div>
<div class="del-btn" @click="delUser(item.uid,index)">
<i class='fa fa-remove'></i>
</div>
</div>
</div>
<el-button type="button" size="small" @click="addUser">添加用户</el-button>
</el-form-item>
<el-form-item label="消息内容">
<tinymceee v-model.trim="info.message_content" ref="tinymceee"></tinymceee>
</el-form-item>
<el-form-item label="">
<el-button type="primary" @click="submitForm()">发布</el-button>
</el-form-item>
</div>
</el-form>
</div>
</div>
<script src="{{resource_get('static/yunshop/tinymce4.7.5/tinymce.min.js')}}"></script>
@include('public.admin.tinymceee')
<script>
new Vue({
el:"#app",
delimiters: ['[[', ']]'],
name: 'send_message',
data() {
return{
// 消息内容
info:{
message_title: '',// 消息标题
message_content: '',// 消息内容
notice_type: 0,// 通知类型0=全部1=指定用户
user_ids: [],// 用户ID列表仅指定用户存在
},
user_list: [],
rules:{
message_title:{ required: true, message: '请输入消息标题'}
},
}
},
created() {},
mounted() {},
methods: {
// 添加用户
addUser(){
let _this = this;
let popup = util.selectMember('{!! yzWebUrl('member.query.selected') !!}',{},{
confirm:function (userInfo) {
if(_this.info.user_ids[userInfo.uid] === 'undefined' || _this.info.user_ids[userInfo.uid] === undefined){
_this.user_list = _this.user_list.concat([{
member_name: userInfo.nickname,
member_avatar: userInfo.avatar_image,
uid: userInfo.uid,
}]);
_this.info.user_ids[userInfo.uid + ''] = userInfo.uid;
_this.$forceUpdate();
}
// 关闭弹框
popup.modal('hide');
}
});
},
// 删除用户
delUser(uid,index){
let _this = this;
let userList = Object.assign({},_this.user_list);
let userIds = Object.assign({},_this.info.user_ids);
delete userList[index];
delete userIds[uid];
_this.user_list = Object.values(userList);
_this.info.user_ids = userIds;
_this.$forceUpdate();
},
// 点击发布
submitForm() {
let _this = this;
let info = Object.assign({},_this.info);
// 内容校验
if(info.notice_type === 1 && Object.values(info.user_ids).length <= 0){
_this.$message({type: 'error',message: '请至少选择一个用户!'});
return false;
}
if(info.message_content.length <= 0){
_this.$message({type: 'error',message: '请输入消息内容!'});
return false;
}
// 提交内容
_this.$refs['form'].validate((valid) => {
if (valid) {
let loading = this.$loading({target:document.querySelector(".content"),background: 'rgba(0, 0, 0, 0)'});
this.$http.post("{{yzWebUrl('plugin.message-center.admin.index.edit-info')}}", { info: info }).then(response => {
if (response.data.result) {
this.$message({type: 'success',message: '操作成功!'});
window.location.href = "{{yzWebUrl('plugin.message-center.admin.index.index')}}";
} else {
this.$message({message: response.data.msg,type: 'error'});
}
loading.close();
},response => {
loading.close();
});
}
else {
console.log('error submit!!');
return false;
}
});
},
},
})
</script>
@endsection

View File

@ -0,0 +1,80 @@
<script src="{!!resource_absolute('static/js/xlsx.full.min.js')!!}"></script>
@extends('layouts.base')
<style>
.panel-body .label{
font-size: 14px!important;
}
</style>
@section('content')
<div class="w1200 m0a" id="storeManagerIndex">
{{--顶部搜索--}}
<div class="panel panel-info">
<div class="panel-body">
<form action="" method="post" class="form-horizontal" role="form" id="form1">
<div class="form-group">
<div class="col-sm-11 col-xs-12">
<div class="row row-fix tpl-category-container" >
<div class="col-xs-12 col-sm-8 col-lg-3">
<input class="form-control" name="search[message_title]" id="" type="text" value="{{ $search['message_title'] }}" placeholder="标题">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-6 col-lg-6">
<input name="page" value="1" class="hide">
<a href="{{yzWebUrl('plugin.message-center.admin.index.edit-info')}}">
<button type="button" class="btn btn-info"><i class="fa fa-plus"></i> 添加</button>
</a>
<button class="btn btn-success" id="search"><i class="fa fa-search"></i> 搜索</button>
</div>
</div>
</form>
</div>
</div>
{{--信息列表--}}
<div class="panel panel-default">
<div class="panel-body" style="padding-top: 0;margin-bottom: 30px;overflow: auto;padding-right: 30px;">
<table class="table" style="min-width:1500px;overflow: auto;">
<thead>
<tr>
<th style="text-align:center;">ID</th>
<th style="text-align:center;">标题</th>
<th style="text-align:center;">通知类型</th>
<th style="text-align:center;">
<span class="label label-default">发送人数()</span>
<span class="label label-info">未读人数</span>
<span class="label label-warning">已读人数</span>
</th>
<th style="text-align:center;">发布时间</th>
</tr>
</thead>
<tbody>
@foreach ($list as $item)
<tr style="height: 50px;">
<td style="text-align:center;">{{ $item['id'] }}</td>
<td style="text-align:center;">{{ $item['message_title'] }}</td>
<td style="text-align:center;">
@if((int)$item['notice_type'] == 1 )
<span class="label label-warning">指定用户</span>
@else
<span class="label label-info">全部</span>
@endif
</td>
<td style="text-align:center;">
<span class="label label-default">{{ $item['total_user'] }}</span>
<span class="label label-info">{{ $item['total_unread'] }}</span>
<span class="label label-warning">{{ $item['total_read'] }}</span>
</td>
<td style="text-align:center;">{{ $item['created_at'] }}</td>
</tr>
@endforeach
</tbody>
</table>
{!! $pager !!}
</div>
</div>
</div>
<script type="text/javascript"></script>
@endsection

View File

@ -0,0 +1,105 @@
<script src="{!!resource_absolute('static/js/xlsx.full.min.js')!!}"></script>
@extends('layouts.base')
<style>
.user{
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
overflow: hidden;
height: 80px;
}
.user .user-avatar{
height: 50px;
width: 50px;
margin-right: 5px;
border-radius: 50%;
overflow: hidden;
}
.user .user-avatar .avatar-image{
width: 100% !important;
height: 100% !important;
}
.user .user-info{
height: 50px;
text-align: left;
}
.user .user-info .user-nickname{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user .user-info .user-status{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.panel-body .label{
font-size: 14px!important;
}
</style>
@section('content')
<div class="w1200 m0a" id="storeManagerIndex">
{{--顶部搜索--}}
<div class="panel panel-info">
<div class="panel-body">
<form action="" method="post" class="form-horizontal" role="form" id="form1">
<div class="form-group">
<div class="col-sm-11 col-xs-12">
<div class="row row-fix tpl-category-container" >
<div class="col-xs-12 col-sm-8 col-lg-3">
<input class="form-control" name="search[message_title]" id="" type="text" value="{{ $search['message_title'] }}" placeholder="标题">
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-6 col-lg-6">
<input name="page" value="1" class="hide">
<button class="btn btn-success" id="search"><i class="fa fa-search"></i> 搜索</button>
</div>
</div>
</form>
</div>
</div>
{{--信息列表--}}
<div class="panel panel-default">
<div class="panel-body" style="padding-top: 0;margin-bottom: 30px;overflow: auto;padding-right: 30px;">
<table class="table" style="min-width:1500px;overflow: auto;">
<thead>
<tr>
<th style="text-align:center;">ID</th>
<th style="text-align:left;">用户信息</th>
<th style="text-align:center;">标题</th>
<th style="text-align:center;">查看时间</th>
</tr>
</thead>
<tbody>
@foreach ($list as $item)
<tr style="height: 50px;">
<td style="text-align:center;">{{ $item['id'] }}</td>
<td style="text-align:left;" >
<div class="user">
<div class="user-avatar">
<img class="avatar-image" src="{{$item['member']['avatar_image']}}" />
</div>
<div class="user-info">
<div class="user-nickname">昵称:{{ $item['member']['nickname'] }}</div>
<div class="user-status">ID{{ $item['member']['uid'] }}</div>
</div>
</div>
</td>
<td style="text-align:center;">{{ $item['message_title'] }}</td>
<td style="text-align:center;">{{ $item['read_time'] }}</td>
</tr>
@endforeach
</tbody>
</table>
{!! $pager !!}
</div>
</div>
</div>
<script type="text/javascript"></script>
@endsection

View File

@ -185,8 +185,8 @@
<span class="label label-warning">占比(%)</span>
</template>
<template slot-scope="scope">
<span class="label label-default">[[scope.row.total_quantity || '0.00' ]]{{ $item['total_quantity'] }}</span>
<span class="label label-info">[[scope.row.quantity || '0.00' ]]{{ $item['quantity'] }}</span>
<span class="label label-default">[[scope.row.total_quantity || '0.00' ]]</span>
<span class="label label-info">[[scope.row.quantity || '0.00' ]]</span>
<span class="label label-warning">[[scope.row.ratio ? scope.row.ratio + '%' : '0.00' ]]</span>
</template>
</el-table-column>