支持工单功能(8/10)

实现管理员侧工单支持功能
*前端部分
This commit is contained in:
Chenx221 2024-04-18 17:16:33 +08:00
parent 4a5cd54936
commit 89d87fa4dc
Signed by: chenx221
GPG Key ID: D7A9EC07024C3021
2 changed files with 297 additions and 0 deletions

View File

@ -0,0 +1,127 @@
<?php
use app\assets\FontAwesomeAsset;
use app\models\Tickets;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\grid\ActionColumn;
use yii\grid\GridView;
use yii\widgets\Pjax;
/** @var yii\web\View $this */
/** @var app\models\TicketsSearch $searchModel */
/** @var yii\data\ActiveDataProvider $dataProvider */
/** @var int $pendingTickets */
$this->title = '工单支持管理';
$this->params['breadcrumbs'][] = $this->title;
FontAwesomeAsset::register($this);
?>
<div class="tickets-index">
<h1><?= Html::encode($this->title) ?></h1>
<button type="button" class="btn btn-secondary position-relative">
未处理的工单
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?= $pendingTickets > 99 ? '99+' : $pendingTickets ?>
<span class="visually-hidden">pending tickets</span>
</span>
</button>
<?php Pjax::begin(); ?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'id',
'label' => '工单ID',
],
[
'attribute' => 'user_id',
'label' => '用户',
'format' => 'html',
'value' => function (Tickets $model) {
return $model->user->username . '<br>(ID: ' . $model->user->id . ')';
}
],
[
'attribute' => 'title',
'label' => '标题',
'format' => 'raw', // 使用 raw 格式,这样 Yii2 不会对 value 的返回值进行 HTML 编码
'value' => function (Tickets $model) {
return Html::a($model->title, ['ticket-view', 'id' => $model->id]);
},
],
[
'attribute' => 'created_at',
'label' => '创建时间',
'filter' => false
],
[
'attribute' => 'updated_at',
'label' => '最近更新时间',
'value' => function (Tickets $model) {
return Yii::$app->formatter->asRelativeTime(new DateTime($model->updated_at, new DateTimeZone('GMT+8')));
},
'filter' => false
],
[
'attribute' => 'status',
'label' => '状态',
'format' => 'raw',
'value' => function (Tickets $model) {
return match ($model->status) {
Tickets::STATUS_OPEN => '<span class="badge rounded-pill bg-primary">工单已开启</span>',
Tickets::STATUS_ADMIN_REPLY => '<span class="badge rounded-pill bg-info">管理员已回复</span>',
Tickets::STATUS_USER_REPLY => '<span class="badge rounded-pill bg-secondary">用户已回复</span>',
Tickets::STATUS_CLOSED => '<span class="badge rounded-pill bg-success">工单已关闭</span>',
default => '<span class="badge rounded-pill bg-danger">未知状态</span>',
};
},
'filter' => [
Tickets::STATUS_OPEN => '工单已开启',
Tickets::STATUS_ADMIN_REPLY => '管理员已回复',
Tickets::STATUS_USER_REPLY => '用户已回复',
Tickets::STATUS_CLOSED => '工单已关闭',
]
],
[
'class' => ActionColumn::class,
'template' => '{view} {delete}',
'buttons' => [
'view' => function ($url, $model, $key) {
return Html::a('<i class="fa-solid fa-eye"></i>', ['ticket-view', 'id' => $model->id], [
'title' => '查看工单',
'data-pjax' => '0',
]);
},
'delete' => function ($url, $model, $key) {
if ($model->status !== Tickets::STATUS_CLOSED) {
return Html::a('<i class="fa-solid fa-xmark"></i>', ['ticket-delete', 'id' => $model->id, 'from' => 'index'], [
'title' => '关闭工单',
'data-pjax' => '0',
'data-confirm' => '你确定要关闭这个工单吗?',
'data-method' => 'post',
]);
} else {
return Html::tag('i', '', [
'class' => 'fa-solid fa-xmark',
'style' => 'color: gray;',
'title' => '工单已关闭',
]);
}
},
],
'urlCreator' => function ($action, Tickets $model, $key, $index, $column) {
return Url::toRoute([$action, 'id' => $model->id]);
},
],
],
]); ?>
<?php Pjax::end(); ?>
</div>

170
views/admin/ticket_view.php Normal file
View File

@ -0,0 +1,170 @@
<?php
use app\assets\FontAwesomeAsset;
use app\assets\QuillAsset;
use app\models\Tickets;
use yii\helpers\Html;
use yii\web\YiiAsset;
use yii\widgets\DetailView;
/** @var yii\web\View $this */
/** @var app\models\Tickets $model */
/** @var string $ticketReplies */
$this->title = '工单: ' . $model->title;
$this->params['breadcrumbs'][] = ['label' => '工单支持管理', 'url' => ['ticket-manage']];
$this->params['breadcrumbs'][] = '工单ID ' . $model->id;
YiiAsset::register($this);
QuillAsset::register($this);
FontAwesomeAsset::register($this);
$this->registerCssFile('@web/css/tickets.css');
?>
<div class="tickets-view">
<h1><?= Html::encode($this->title) ?></h1>
<br>
<div class="row">
<div class="col-md-3">
<!-- DetailView -->
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
[
'attribute' => 'status',
'label' => '状态',
'format' => 'raw',
'value' => function (Tickets $model) {
return match ($model->status) {
Tickets::STATUS_OPEN => '<span class="badge rounded-pill bg-primary">工单已开启</span>',
Tickets::STATUS_ADMIN_REPLY => '<span class="badge rounded-pill bg-info">管理员已回复</span>',
Tickets::STATUS_USER_REPLY => '<span class="badge rounded-pill bg-secondary">用户已回复</span>',
Tickets::STATUS_CLOSED => '<span class="badge rounded-pill bg-success">工单已关闭</span>',
default => '<span class="badge rounded-pill bg-danger">未知状态</span>',
};
}
],
[
'attribute' => 'user_id',
'label' => '用户',
'format' => 'html',
'value' => function (Tickets $model) {
return $model->user->username . '<br>(ID: ' . $model->user->id . ')';
}
],
[
'attribute' => 'ip',
'label' => '用户IP',
],
[
'attribute' => 'created_at',
'label' => '创建时间',
'format' => 'raw', // 使用 raw 格式,这样 Yii2 不会对 value 的返回值进行 HTML 编码
'value' => function (Tickets $model) {
$dateTime = new DateTime($model->created_at, new DateTimeZone('GMT+8'));
return $model->created_at . '<br>(' . Yii::$app->formatter->asRelativeTime($dateTime) . ')';
}
],
[
'attribute' => 'updated_at',
'label' => '最近更新时间',
'format' => 'raw', // 使用 raw 格式,这样 Yii2 不会对 value 的返回值进行 HTML 编码
'value' => function (Tickets $model) {
$dateTime = new DateTime($model->updated_at, new DateTimeZone('GMT+8'));
return $model->updated_at . '<br>(' . Yii::$app->formatter->asRelativeTime($dateTime) . ')';
}
]
],
]) ?>
<p>
<?= ($model->status === Tickets::STATUS_CLOSED) ? '' : Html::a('关闭工单', ['ticket-delete', 'id' => $model->id], ['class' => 'btn btn-danger',
'data' => ['confirm' => '您确定要关闭这个工单吗?',
'method' => 'post',],]) ?>
</p>
</div>
<div class="col-md-9">
<!-- User message input and ticket content -->
<div class="form-control">
<div id="editor" class="form-control">
</div>
<?= Html::button('回复', ['class' => 'btn btn-primary', 'id' => 'send']) ?>
</div>
<div id="ticket-content">
<br>
</div>
</div>
</div>
</div>
<?php
$user_name = $model->user->username;
$core_js = <<<JS
//写的很乱
var theme = document.documentElement.getAttribute('data-bs-theme')==='dark'?'bubble':'snow'
const quill = new Quill('#editor', {
theme: theme
});
$('#send').on('click', function() {
var content = quill.getContents();
// check content not empty
if (quill.getLength()===1) {
alert('内容不能为空');
return false;
}
var ticketId = $model->id; // 你需要在这里设置正确的工单ID
$.ajax({
url: 'index.php?r=admin%2Fticket-reply',
type: 'POST',
data: {
ticketId: ticketId,
content: JSON.stringify(content)
},
success: function(response) {
// 处理服务器的响应
console.log(response);
// 如果服务器返回的状态是成功,刷新页面
if (response.status === 'success') {
location.reload();
}
},
error: function(jqXHR, textStatus, errorThrown) {
// 处理错误
console.error(textStatus, errorThrown);
}
});
});
function quillGetHTML(inputDelta,skipParse=false) {
var delta = skipParse?inputDelta:JSON.parse(inputDelta);
var tempQuill=new Quill(document.createElement("div"));
tempQuill.setContents(delta);
return tempQuill.root.innerHTML;
}
function generateReply(user, message, date,ip) {
return '<div class="ticket-reply"><div class="ticket-reply-top"><div class="user"><i class="fas fa-user-circle"></i><span class="name">'+user+'</span></div><div class="info"> <span class="type">用户</span> <div class="date">'+date+' ('+ip+')'+'</div> </div> </div> <div class="ticket-message">'+message+'</div></div>';
}
function generateAdminReply(user, message, date,ip) {
return '<div class="ticket-reply admin"> <div class="ticket-reply-top"> <div class="user"> <i class="fas fa-user-circle"></i> <span class="name">'+user+'</span> </div> <div class="info"> <span class="type">管理员</span> <div class="date">'+date+' ('+ip+')'+'</div> </div> </div> <div class="ticket-message">'+message+'</div></div>';
}
var ticketContent = $model->description;
var ticketContentElement = $('#ticket-content');
ticketContentElement.append(generateReply('$user_name', quillGetHTML(ticketContent,true), '$model->created_at','$model->ip'));
var TicketReplies = $ticketReplies;
for (let i = 0; i < TicketReplies.length; i++) {
let reply = TicketReplies[i];
let message = quillGetHTML(reply.message);
let date = reply.created_at;
let ip = reply.ip;
if (reply.is_admin !== 1) {
ticketContentElement.append(generateReply(reply.name, message, date,ip));
} else {
ticketContentElement.append(generateAdminReply(reply.name, message, date,ip));
}
}
JS;
$this->registerJs($core_js);
?>