From d48ed1810243032f86647871500b81c2f3f693d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9F=A6=E8=8D=A3=E8=B6=85?= <302645122@qq.com>
Date: Fri, 14 Jan 2022 15:55:26 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E3=80=90=E5=B7=A5=E4=BD=9C=E6=8A=A5?=
=?UTF-8?q?=E5=91=8A=E3=80=91=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/Http/Controllers/Api/ReportController.php | 379 ++++++++++++++++++
app/Http/Middleware/VerifyCsrfToken.php | 3 +
app/Models/Report.php | 156 +++++++
app/Models/ReportReceive.php | 39 ++
...2021_12_30_155338_create_reports_table.php | 39 ++
...30_162019_create_report_receives_table.php | 38 ++
.../2022_01_04_111739_add_report_sign.php | 32 ++
resources/assets/js/pages/manage.vue | 40 +-
.../js/pages/manage/components/Report.vue | 65 +++
.../pages/manage/components/ReportDetail.vue | 87 ++++
.../js/pages/manage/components/ReportEdit.vue | 236 +++++++++++
.../js/pages/manage/components/ReportMy.vue | 172 ++++++++
.../pages/manage/components/ReportReceive.vue | 194 +++++++++
resources/assets/sass/_.scss | 14 +
resources/assets/sass/app-down.scss | 74 ++++
resources/assets/sass/auto-tip.scss | 6 +
resources/assets/sass/circle.scss | 14 +
resources/assets/sass/components/_.scss | 1 +
resources/assets/sass/components/report.scss | 218 ++++++++++
resources/assets/sass/drawer-overlay.scss | 136 +++++++
resources/assets/sass/img-update.scss | 255 ++++++++++++
.../sass/pages/components/dialog-wrapper.scss | 11 +
resources/assets/sass/pages/page-login.scss | 11 +
resources/assets/sass/quick-edit.scss | 58 +++
resources/assets/sass/report.scss | 218 ++++++++++
resources/assets/sass/scroller-y.scss | 19 +
resources/assets/sass/spinner.scss | 20 +
resources/assets/sass/t-editor.scss | 118 ++++++
resources/assets/sass/tag-input.scss | 93 +++++
resources/assets/sass/user-avatar.scss | 73 ++++
resources/assets/sass/user-input.scss | 70 ++++
routes/web.php | 4 +
32 files changed, 2890 insertions(+), 3 deletions(-)
create mode 100755 app/Http/Controllers/Api/ReportController.php
create mode 100644 app/Models/Report.php
create mode 100644 app/Models/ReportReceive.php
create mode 100644 database/migrations/2021_12_30_155338_create_reports_table.php
create mode 100644 database/migrations/2021_12_30_162019_create_report_receives_table.php
create mode 100644 database/migrations/2022_01_04_111739_add_report_sign.php
create mode 100644 resources/assets/js/pages/manage/components/Report.vue
create mode 100644 resources/assets/js/pages/manage/components/ReportDetail.vue
create mode 100644 resources/assets/js/pages/manage/components/ReportEdit.vue
create mode 100644 resources/assets/js/pages/manage/components/ReportMy.vue
create mode 100644 resources/assets/js/pages/manage/components/ReportReceive.vue
create mode 100644 resources/assets/sass/_.scss
create mode 100644 resources/assets/sass/app-down.scss
create mode 100644 resources/assets/sass/auto-tip.scss
create mode 100644 resources/assets/sass/circle.scss
create mode 100644 resources/assets/sass/components/report.scss
create mode 100644 resources/assets/sass/drawer-overlay.scss
create mode 100644 resources/assets/sass/img-update.scss
create mode 100755 resources/assets/sass/quick-edit.scss
create mode 100644 resources/assets/sass/report.scss
create mode 100755 resources/assets/sass/scroller-y.scss
create mode 100644 resources/assets/sass/spinner.scss
create mode 100755 resources/assets/sass/t-editor.scss
create mode 100755 resources/assets/sass/tag-input.scss
create mode 100755 resources/assets/sass/user-avatar.scss
create mode 100755 resources/assets/sass/user-input.scss
diff --git a/app/Http/Controllers/Api/ReportController.php b/app/Http/Controllers/Api/ReportController.php
new file mode 100755
index 00000000..472defcb
--- /dev/null
+++ b/app/Http/Controllers/Api/ReportController.php
@@ -0,0 +1,379 @@
+whereUserid($user->userid);
+ $type = trim(Request::input('type'));
+ $createAt = Request::input('created_at');
+ in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
+ $whereArray = [];
+ if (is_array($createAt)) {
+ if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
+ if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
+ }
+ $list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
+ if ($list->items()) {
+ foreach ($list->items() as $item) {
+ $item->receivesUser;
+ $item->receives = empty($item->receivesUser) ? [] : array_column($item->receivesUser->toArray(), "userid");
+ }
+ }
+
+ return Base::retSuccess('success', $list);
+ }
+
+ /**
+ * 我接收的汇报
+ * @return array
+ */
+ public function receive(): array
+ {
+ $user = User::auth();
+ $builder = Report::query();
+ $builder->whereHas("receivesUser", function ($query) use ($user) {
+ $query->where("report_receives.userid", $user->userid);
+ });
+ $type = trim(Request::input('type'));
+ $createAt = Request::input('created_at');
+ $username = trim(Request::input('username', ''));
+ $builder->whereHas('sendUser', function ($query) use ($username) {
+ if (!empty($username)) {
+ $query->where('users.email', 'LIKE', '%' . $username . '%');
+ }
+ });
+ in_array($type, [Report::WEEKLY, Report::DAILY]) && $builder->whereType($type);
+ $whereArray = [];
+ if (is_array($createAt)) {
+ if ($createAt[0] > 0) $whereArray[] = ['created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($createAt[0]))];
+ if ($createAt[1] > 0) $whereArray[] = ['created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($createAt[1]))];
+ }
+ $list = $builder->where($whereArray)->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
+ if ($list->items()) {
+ foreach ($list->items() as $item) {
+ $item["receive_time"] = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
+ $item->receivesUser;
+ $item->receives = empty($item->receivesUser) ? [] : array_column($item->receivesUser->toArray(), "userid");
+ }
+ }
+ return Base::retSuccess('success', $list);
+ }
+
+ /**
+ * 保存并发送工作汇报
+ * @return array
+ */
+ public function store(): array
+ {
+ $input = [
+ "id" => Base::getPostValue("id", 0),
+ "title" => Base::getPostValue("title"),
+ "type" => Base::getPostValue("type"),
+ "content" => Base::getPostValue("content"),
+ "receive" => Base::getPostValue("receive"),
+ // 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1,上一天同理。
+ "offset" => Base::getPostValue("offset", 0),
+ ];
+ $validator = Validator::make($input, [
+ 'id' => 'numeric',
+ 'title' => 'required',
+ 'type' => ['required', Rule::in([Report::WEEKLY, Report::DAILY])],
+ 'content' => 'required',
+ 'receive' => 'required',
+ 'offset' => ['numeric', 'max:0'],
+ ], [
+ 'id.numeric' => 'ID只能是数字',
+ 'title.required' => '请填写标题',
+ 'type.required' => '请选择汇报类型',
+ 'type.in' => '汇报类型错误',
+ 'content.required' => '请填写汇报内容',
+ 'receive.required' => '请选择接收人',
+ 'offset.numeric' => '工作汇报周期格式错误,只能是数字',
+ 'offset.max' => '只能提交当天/本周或者之前的的工作汇报',
+ ]);
+ if ($validator->fails())
+ return Base::retError($validator->errors()->first());
+
+ $user = User::auth();
+ // 接收人
+ if ( is_array($input["receive"]) ) {
+ // 删除当前登录人
+ $input["receive"] = array_diff($input["receive"], [$user->userid]);
+
+ // 查询用户是否存在
+ if ( count($input["receive"]) !== User::whereIn("userid", $input["receive"])->count() )
+ return Base::retError("用户不存在");
+
+ foreach ($input["receive"] as $userid) {
+ $input["receive_content"][] = [
+ "receive_time" => Carbon::now()->toDateTimeString(),
+ "userid" => $userid,
+ "read" => 0,
+ ];
+ }
+ }
+
+ // 在事务中运行
+ Report::transaction( function () use ($input, $user) {
+ $id = $input["id"];
+ if ($id) {
+ // 编辑
+ $report = Report::getOne($id);
+ $report->updateInstance([
+ "title" => $input["title"],
+ "type" => $input["type"],
+ "content" => htmlspecialchars($input["content"]),
+ ]);
+ } else {
+ // 生成唯一标识
+ $sign = Report::generateSign($input["type"], $input["offset"]);
+ // 检查唯一标识是否存在
+ if (empty($input["id"])) {
+ if (Report::query()->whereSign($sign)->count() > 0)
+ throw new ApiException("请勿重复提交工作汇报");
+ }
+ $report = Report::createInstance([
+ "title" => $input["title"],
+ "type" => $input["type"],
+ "content" => htmlspecialchars($input["content"]),
+ "userid" => $user->userid,
+ "sign" => $sign,
+ ]);
+ }
+
+ $report->save();
+ if (!empty($input["receive_content"])) {
+ // 删除关联
+ $report->Receives()->delete();
+ // 保存接收人
+ $report->Receives()->createMany($input["receive_content"]);
+ }
+
+ } );
+ return Base::retSuccess('success');
+ }
+
+ /**
+ * 生成汇报模板
+ * @return array
+ */
+ public function template(): array
+ {
+ $user = User::auth();
+ $type = trim( Request::input("type") );
+ $offset = abs( intval( Request::input("offset", 0) ) );
+ $now_dt = trim( Request::input("date") ) ? Carbon::parse( Request::input("date") ) : Carbon::now();
+ // 获取开始时间
+ if ($type === Report::DAILY) {
+ $start_time = Carbon::today();
+ if ( $offset > 0 ) {
+ // 将当前时间调整为偏移量当天结束
+ $now_dt->subDays( $offset )->endOfDay();
+ // 开始时间偏移量计算
+ $start_time->subDays( $offset );
+ }
+ $end_time = Carbon::instance($start_time)->endOfDay();
+ } else {
+ $start_time = Carbon::now();
+ if ( $offset > 0 ) {
+ // 将当前时间调整为偏移量当周结束
+ $now_dt->subWeeks( $offset )->endOfDay();
+ // 开始时间偏移量计算
+ $start_time->subWeeks( $offset );
+ }
+ $start_time->startOfWeek();
+ $end_time = Carbon::instance($start_time)->endOfWeek();
+ }
+ // 生成唯一标识
+ $sign = Report::generateSign($type, 0, Carbon::instance($start_time));
+ $one = Report::query()->whereSign($sign)->first();
+ // 如果已经提交了相关汇报
+ if ($one) {
+ return Base::retSuccess('success', [
+ "content" => $one->content,
+ "title" => $one->title,
+ "id" => $one->id,
+ ]);
+ }
+
+
+ // 已完成的任务
+ $completeContent = "";
+ $complete_task = ProjectTask::query()
+ ->whereNotNull("complete_at")
+ ->whereBetween("complete_at", [$start_time->toDateTimeString(), $end_time->toDateTimeString()])
+ ->whereHas("taskUser", function ($query) use ($user) {
+ $query->where("userid", $user->userid);
+ })
+ ->orderByDesc("id")
+ ->get();
+ if ($complete_task->isNotEmpty()) {
+ foreach ($complete_task as $task) {
+ $complete_at = Carbon::parse($task->complete_at);
+ $pre = $type == Report::WEEKLY ? ('[' . Base::Lang('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . '] ') : '';
+ $completeContent .= '
' . $pre . $task->name . '';
+ }
+ } else {
+ $completeContent = ' ';
+ }
+
+ // 未完成的任务
+ $unfinishedContent = "";
+ $unfinished_task = ProjectTask::query()
+ ->whereNull("complete_at")
+ ->whereNotNull("start_at")
+ ->where("end_at", "<", $end_time->toDateTimeString())
+ ->whereHas("taskUser", function ($query) use ($user) {
+ $query->where("userid", $user->userid);
+ })
+ ->orderByDesc("id")
+ ->get();
+ if ($unfinished_task->isNotEmpty()) {
+ foreach ($unfinished_task as $task) {
+ empty($task->end_at) || $end_at = Carbon::parse($task->end_at);
+ $pre = ( !empty( $end_at ) && $end_at->lt($now_dt) ) ? '[' . Base::Lang('超期') . '] ' : '';
+ $unfinishedContent .= '' . $pre . $task->name . '';
+ }
+ } else {
+ $unfinishedContent = ' ';
+ }
+ // 生成标题
+ if ( $type === Report::WEEKLY ) {
+ $title = $user->nickname . "的周报[" . $start_time->format("m/d") . "-" . $end_time->format("m/d") . "]";
+ $title .= "[" . $start_time->month . "月第" . $start_time->weekOfMonth . "周]";
+ } else {
+ $title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
+ }
+ return Base::retSuccess('success', [
+ "time" => $start_time->toDateTimeString(),
+ "complete_task" => $complete_task,
+ "unfinished_task" => $unfinished_task,
+ "content" => '' . Base::Lang('已完成工作') . '
' .
+ $completeContent . '
' .
+ Base::Lang('未完成的工作') . '
' .
+ $unfinishedContent . '
',
+ "title" => $title,
+ ]);
+ }
+
+ /**
+ * @return array
+ */
+ public function detail(): array
+ {
+ $id = intval( trim( Request::input("id") ) );
+ if (empty( $id ))
+ return Base::retError("缺少ID参数");
+
+ $one = Report::getOne($id);
+ $one["type_val"] = $one->getRawOriginal("type");
+
+ $user = User::auth();
+ // 标记为已读
+ if ( !empty( $one->receivesUser ) ) {
+ foreach ($one->receivesUser as $item) {
+ if ($item->userid === $user->userid && $item->pivot->read === 0) {
+ $one->receivesUser()->updateExistingPivot($user->userid, [
+ "read" => 1,
+ ]);
+ }
+ }
+ }
+
+ return Base::retSuccess("success", $one);
+ }
+
+ /**
+ * 获取最后一次提交的接收人
+ * @return array
+ */
+ public function last_submitter(): array
+ {
+ $one = Report::getLastOne();
+ return Base::retSuccess("success", empty( $one["receives"] ) ? [] : $one["receives"]);
+ }
+
+ /**
+ * 获取未读
+ * @return array
+ */
+ public function unread(): array
+ {
+ $userid = intval( trim( Request::input("userid") ) );
+ $user = empty($userid) ? User::auth() : User::find($userid);
+
+ $data = Report::whereHas("Receives", function (Builder $query) use ($user) {
+ $query->where("userid", $user->userid)->where("read", 0);
+ })->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
+ return Base::retSuccess("success", $data);
+ }
+
+ /**
+ * 标记汇报已读,可批量
+ * @return array
+ */
+ public function read(): array
+ {
+ $user = User::auth();
+ $ids = Request::input("ids");
+ if ( !is_array($ids) && !is_string($ids) ) {
+ return Base::retError("请传入正确的工作汇报Id");
+ }
+
+ if ( is_string($ids) ) {
+ $ids = explode(",", $ids);
+ }
+
+ $data = Report::with(["receivesUser" => function (BelongsToMany $query) use ($user) {
+ $query->where("report_receives.userid", $user->userid)->where("read", 0);
+ }])->whereIn("id", $ids)->get();
+
+ if ( $data->isNotEmpty() ) {
+ foreach ($data as $item) {
+ (!empty($item->receivesUser) && $item->receivesUser->isNotEmpty()) && $item->receivesUser()->updateExistingPivot($user->userid, [
+ "read" => 1,
+ ]);
+ }
+ }
+ return Base::retSuccess("success", $data);
+ }
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
index d2ec036e..9775ee86 100644
--- a/app/Http/Middleware/VerifyCsrfToken.php
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -44,5 +44,8 @@ class VerifyCsrfToken extends Middleware
// 保存文件内容(上传)
'api/file/content/upload/',
+
+ // 保存汇报
+ 'api/report/store/',
];
}
diff --git a/app/Models/Report.php b/app/Models/Report.php
new file mode 100644
index 00000000..5b04efe8
--- /dev/null
+++ b/app/Models/Report.php
@@ -0,0 +1,156 @@
+hasMany(ReportReceive::class, "rid");
+ }
+
+ public function receivesUser(): BelongsToMany
+ {
+ return $this->belongsToMany(User::class, ReportReceive::class, "rid", "userid")
+ ->withPivot("receive_time", "read");
+ }
+
+ public function sendUser()
+ {
+ return $this->hasOne(User::class, "userid", "userid");
+ }
+
+ public function getTypeAttribute($value): string
+ {
+ return match ($value) {
+ Report::WEEKLY => "周报",
+ Report::DAILY => "日报",
+ default => "",
+ };
+ }
+
+ public function getContentAttribute($value): string
+ {
+ return htmlspecialchars_decode($value);
+ }
+
+ public function getReceivesAttribute()
+ {
+ if (!isset($this->appendattrs['receives'])) {
+ $this->appendattrs['receives'] = empty( $this->receivesUser ) ? [] : array_column($this->receivesUser->toArray(), "userid");
+ }
+ return $this->appendattrs['receives'];
+ }
+
+ /**
+ * 获取单条记录
+ * @param $id
+ * @param User|null $user
+ * @return Report|Builder|Model|object|null
+ * @throw ApiException
+ */
+ public static function getOne($id, User $user = null)
+ {
+ $user === null && $user = User::auth();
+ $one = self::whereUserid($user->userid)->whereId($id)->first();
+ if ( empty($one) )
+ throw new ApiException("记录不存在");
+ return $one;
+ }
+
+ /**
+ * 获取最后一条提交记录
+ * @param User|null $user
+ * @return Builder|Model|\Illuminate\Database\Query\Builder|object
+ */
+ public static function getLastOne(User $user = null)
+ {
+ $user === null && $user = User::auth();
+ $one = self::whereUserid($user->userid)->orderByDesc("created_at")->first();
+ if ( empty($one) )
+ throw new ApiException("记录不存在");
+ return $one;
+ }
+
+ /**
+ * 生成唯一标识
+ * @param $type
+ * @param $offset
+ * @param Carbon|null $time
+ * @return string
+ */
+ public static function generateSign($type, $offset, Carbon $time = null): string
+ {
+ $user = User::auth();
+ $now_dt = $time === null ? Carbon::now() : $time;
+ $time_s = match ($type) {
+ Report::WEEKLY => function() use ($now_dt, $offset) {
+ // 如果设置了周期偏移量
+ empty( $offset ) || $now_dt->subWeeks( abs( $offset ) );
+ $now_dt->startOfWeek(); // 设置为当周第一天
+ return $now_dt->year . $now_dt->weekOfYear . $now_dt->month . $now_dt->weekOfMonth;
+ },
+ Report::DAILY => function() use ($now_dt, $offset) {
+ // 如果设置了周期偏移量
+ empty( $offset ) || $now_dt->subDays( abs( $offset ) );
+ return $now_dt->year . $now_dt->dayOfYear . $now_dt->month . $now_dt->daysInMonth;
+ },
+ default => "",
+ };
+ return md5( $user->userid . ( is_callable($time_s) ? $time_s() : "" ) . $type );
+ }
+}
diff --git a/app/Models/ReportReceive.php b/app/Models/ReportReceive.php
new file mode 100644
index 00000000..43232e39
--- /dev/null
+++ b/app/Models/ReportReceive.php
@@ -0,0 +1,39 @@
+id();
+ $table->timestamps();
+ $table->string("title")->default("")->comment("标题");
+ $table->enum("type", ["weekly", "daily"])->default("daily")->comment("汇报类型");
+ $table->unsignedBigInteger("userid")->default(0);
+ $table->longText("content")->nullable();
+ $table->index(["userid", "created_at"], "default");
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('reports');
+ }
+}
diff --git a/database/migrations/2021_12_30_162019_create_report_receives_table.php b/database/migrations/2021_12_30_162019_create_report_receives_table.php
new file mode 100644
index 00000000..7231e9d3
--- /dev/null
+++ b/database/migrations/2021_12_30_162019_create_report_receives_table.php
@@ -0,0 +1,38 @@
+bigIncrements("id");
+ $table->unsignedInteger("rid")->default(0);
+ $table->timestamp("receive_time")->nullable()->comment("接收时间");
+ $table->unsignedBigInteger("userid")->default(0)->comment("接收人");
+ $table->unsignedTinyInteger("read")->default(0)->comment("是否已读");
+ $table->index(["userid", "receive_time"], "default");
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('report_receives');
+ }
+}
diff --git a/database/migrations/2022_01_04_111739_add_report_sign.php b/database/migrations/2022_01_04_111739_add_report_sign.php
new file mode 100644
index 00000000..137d2497
--- /dev/null
+++ b/database/migrations/2022_01_04_111739_add_report_sign.php
@@ -0,0 +1,32 @@
+string("sign")->default("")->comment("汇报唯一标识");
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('reports', function (Blueprint $table) {
+ $table->dropColumn("sign");
+ });
+ }
+}
diff --git a/resources/assets/js/pages/manage.vue b/resources/assets/js/pages/manage.vue
index f323825f..a54be022 100644
--- a/resources/assets/js/pages/manage.vue
+++ b/resources/assets/js/pages/manage.vue
@@ -21,7 +21,10 @@
v-for="(item, key) in menu"
:key="key"
:divided="!!item.divided"
- :name="item.path">{{$L(item.name)}}
+ :name="item.path">
+ {{$L(item.name)}}
+
+
+
+
+
+
+
{
$A.setStorage("clearCache", $A.randomString(6))
@@ -683,6 +706,17 @@ export default {
this.natificationHidden = !!document[hiddenProperty]
}
document.addEventListener(visibilityChangeEvent, visibilityChangeListener);
+ },
+
+ reportUnread() {
+ this.$store.dispatch("call", {
+ url: 'report/unread',
+ method: 'get',
+ }).then(({data, msg}) => {
+ // data 结果数据
+ this.reportUnreadNumber = data.total ? data.total : 0;
+ // msg 结果描述
+ });
}
}
}
diff --git a/resources/assets/js/pages/manage/components/Report.vue b/resources/assets/js/pages/manage/components/Report.vue
new file mode 100644
index 00000000..b3e12e92
--- /dev/null
+++ b/resources/assets/js/pages/manage/components/Report.vue
@@ -0,0 +1,65 @@
+
+
+
+
+ 填写
+
+
+ 我的汇报
+
+
+ 收到的汇报
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/assets/js/pages/manage/components/ReportDetail.vue b/resources/assets/js/pages/manage/components/ReportDetail.vue
new file mode 100644
index 00000000..3ac28cb5
--- /dev/null
+++ b/resources/assets/js/pages/manage/components/ReportDetail.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
{{ data.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ data.created_at }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/assets/js/pages/manage/components/ReportEdit.vue b/resources/assets/js/pages/manage/components/ReportEdit.vue
new file mode 100644
index 00000000..b742b2da
--- /dev/null
+++ b/resources/assets/js/pages/manage/components/ReportEdit.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
diff --git a/resources/assets/js/pages/manage/components/ReportMy.vue b/resources/assets/js/pages/manage/components/ReportMy.vue
new file mode 100644
index 00000000..d50faf09
--- /dev/null
+++ b/resources/assets/js/pages/manage/components/ReportMy.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
diff --git a/resources/assets/js/pages/manage/components/ReportReceive.vue b/resources/assets/js/pages/manage/components/ReportReceive.vue
new file mode 100644
index 00000000..c0dde4c9
--- /dev/null
+++ b/resources/assets/js/pages/manage/components/ReportReceive.vue
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
diff --git a/resources/assets/sass/_.scss b/resources/assets/sass/_.scss
new file mode 100644
index 00000000..7620c4ea
--- /dev/null
+++ b/resources/assets/sass/_.scss
@@ -0,0 +1,14 @@
+@import "app-down";
+@import "auto-tip";
+@import "circle";
+@import "drawer-overlay";
+@import "img-update";
+@import "loading";
+@import "scroller-y";
+@import "spinner";
+@import "t-editor";
+@import "quick-edit";
+@import "tag-input";
+@import "user-avatar";
+@import "user-input";
+@import "report";
diff --git a/resources/assets/sass/app-down.scss b/resources/assets/sass/app-down.scss
new file mode 100644
index 00000000..81e12c1f
--- /dev/null
+++ b/resources/assets/sass/app-down.scss
@@ -0,0 +1,74 @@
+.common-app-down {
+ position: absolute;
+ bottom: 26px;
+ right: 26px;
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ transition: bottom 0.3s;
+ &.on-client {
+ &[data-route=login] {
+ bottom: 75px;
+ }
+ }
+}
+
+.common-app-down-notification {
+ .notification-head {
+ display: flex;
+ align-items: center;
+ .notification-title {
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 18px;
+ color: #17233d;
+ font-weight: 500;
+ margin-right: 6px;
+ }
+ }
+ .notification-body {
+ max-height: 210px;
+ overflow: auto;
+ margin: 18px 0;
+ .markdown-preview {
+ margin: -20px -12px;
+ h2 {
+ font-size: 18px !important;
+ padding-top: 2px !important;
+ }
+ ul {
+ li {
+ padding: 2px 0 2px 2px !important;
+ &:after {
+ top: 10px !important;
+ width: 6px !important;
+ height: 6px !important;
+ }
+ }
+ }
+ }
+ }
+ .notification-link {
+ margin-top: 20px;
+ text-align: right;
+ > button + button {
+ margin-left: 6px;
+ }
+ }
+}
+.common-app-down-link {
+ display: inline-block;
+ cursor: pointer;
+ line-height: 32px;
+ height: 32px;
+ padding: 0 15px;
+ font-size: 14px;
+ border-radius: 4px;
+ color: #fff;
+ background-color: #8bcf70;
+ border-color: #8bcf70;
+ &:hover {
+ color: #fff;
+ opacity: 0.9;
+ }
+}
diff --git a/resources/assets/sass/auto-tip.scss b/resources/assets/sass/auto-tip.scss
new file mode 100644
index 00000000..935fab46
--- /dev/null
+++ b/resources/assets/sass/auto-tip.scss
@@ -0,0 +1,6 @@
+.common-auto-tip {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap
+}
diff --git a/resources/assets/sass/circle.scss b/resources/assets/sass/circle.scss
new file mode 100644
index 00000000..53a000c3
--- /dev/null
+++ b/resources/assets/sass/circle.scss
@@ -0,0 +1,14 @@
+.common-circle {
+ border-radius: 50%;
+ .common-circle-path {
+ fill: transparent;
+ }
+ .common-circle-g-path-ring {
+ stroke: $primary-color;
+ }
+ .common-circle-g-path-core {
+ fill: $primary-color;
+ transform: scale(0.56);
+ transform-origin: 50%;
+ }
+}
diff --git a/resources/assets/sass/components/_.scss b/resources/assets/sass/components/_.scss
index 28cf8881..7620c4ea 100644
--- a/resources/assets/sass/components/_.scss
+++ b/resources/assets/sass/components/_.scss
@@ -11,3 +11,4 @@
@import "tag-input";
@import "user-avatar";
@import "user-input";
+@import "report";
diff --git a/resources/assets/sass/components/report.scss b/resources/assets/sass/components/report.scss
new file mode 100644
index 00000000..99a28cca
--- /dev/null
+++ b/resources/assets/sass/components/report.scss
@@ -0,0 +1,218 @@
+.report {
+ height: 100%;
+ padding: 10px 20px;
+ .report-list-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ top: 0;
+ padding-top: 53px;
+ flex-direction: column;
+ position: absolute;
+ .report-row-header,.report-row-foot{
+ flex: 0 0 auto;
+ }
+ .report-row-content{
+ flex:1 1 auto;
+ .ivu-table{
+ .ivu-table-body{
+ height: 100%;
+ overflow-y: auto;
+ padding-bottom: 50px;
+ }
+ }
+ }
+ }
+
+ .page-box {
+ text-align: center;
+ margin-top: 15px;
+ }
+ .ivu-tabs{
+ height: 100%;
+ position: relative;
+ .ivu-tabs-bar{
+ position: relative;
+ z-index: 2;
+ background: #fff;
+ }
+ .ivu-tabs-content{
+ height: 100%;
+ width: 100%;
+ margin-top: -53px;
+ padding-top: 53px;
+ }
+ .ivu-tabs-tabpane{
+ overflow-y: auto;
+ }
+ }
+}
+
+.report-detail {
+ .report-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 12px;
+ padding-top: 20px;
+ .report-title-icon{
+ font-size: 22px;
+ cursor: pointer;
+ margin-right: 20px;
+ }
+ }
+
+ .report-profile {
+ font-size: 14px;
+ }
+
+ .report-submitter {
+ display: flex;
+ flex-wrap: wrap;
+ height: 28px;
+ line-height: 28px;
+ p{
+ display: block;
+ width: 100%;
+ text-align: justify;
+ padding-right: 12px;
+ &:after{
+ content: '';
+ display: inline-block;
+ width: 100%;
+ }
+ }
+ & > div {
+ margin-right: 8px;
+ }
+ }
+
+ .ivu-col{
+ margin-bottom: 12px;
+ }
+
+ .report-content {
+ margin-top: 12px;
+ width: 100%;
+ h2{
+ margin-bottom: 10px;
+ }
+ ol{
+ margin-bottom: 20px;
+ padding-left: 18px;
+ li{
+ font-size: 14px;
+ line-height: 24px;
+ }
+ }
+ }
+}
+.report-box{
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ .report-row-header,.report-row-foot{
+ flex: 0 0 auto;
+ }
+ .report-row-content{
+ flex:1 1 auto;
+ }
+ .report-row-foot{
+ margin-bottom: 0;
+ }
+}
+.report-row{
+ margin-bottom: 20px;
+ .report-row-a{
+ float: right;
+ line-height: 32px;
+ .report-row-a-icon{
+ transform: rotate(-90deg);
+ font-size: 16px;
+ margin-right: 2px;
+ }
+ }
+ .report-bottom{
+ width: 120px;
+ }
+ .report-bottom-save{
+ width: 120px;
+ background: #F4F5F7 ;
+ color: #515A6E;
+ border-color: #F4F5F7 ;
+ }
+}
+.report-titles{
+ line-height: 32px;
+}
+.report-radiogroup{
+ background: #F4F5F7 !important;
+ padding: 2px !important;
+ border-radius: 4px!important;
+ .ivu-radio-wrapper{
+ padding: 0 30px !important;
+ background: #F4F5F7 !important;
+ color: #515A6E !important;
+ box-shadow: none !important;
+ border: none!important;
+ &:before{
+ width: 0!important;
+ }
+ &:after{
+ width: 0!important;
+ }
+ }
+ .ivu-radio-focus{
+ box-shadow: none !important;
+ border: none!important;
+ &:after{
+ background: none!important;
+ }
+ }
+ .ivu-radio-wrapper-checked:not(.ivu-radio-wrapper-disabled){
+ background: #fff !important;
+ color: #8BCF70 !important;
+ box-shadow: none !important;
+ border: none!important;
+ border-radius: 4px!important;
+ }
+
+}
+.report-buttongroup{
+ margin-top: 2px;
+ background: #F4F5F7!important;
+ border-radius: 4px;
+ .report-buttongroup-shu{
+ position: absolute;
+ left: 47px;
+ width: 1px;
+ height: 23px;
+ background-color: #E5E5E5;
+ top: 5px;
+ }
+ .ivu-btn-primary{
+ background: #F4F5F7!important;
+ box-shadow: none !important;
+ border: none!important;
+ color: #8BCF70 !important;
+ &[disabled]{
+ color: #515A6E !important;
+ }
+ }
+}
+.report-poptip{
+ .ivu-tooltip-inner{
+ min-width: 60px !important;
+ font-size: 12px !important;
+ }
+}
+.reportmy-row{
+ margin-bottom: 20px;
+ .reportmy-titles{
+ line-height: 32px;
+ }
+}
+.report-main{
+ .report-submitter{
+ padding-top: 13px;
+ }
+}
diff --git a/resources/assets/sass/drawer-overlay.scss b/resources/assets/sass/drawer-overlay.scss
new file mode 100644
index 00000000..943c9ab2
--- /dev/null
+++ b/resources/assets/sass/drawer-overlay.scss
@@ -0,0 +1,136 @@
+.drawer-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 0;
+ height: 0;
+ z-index: 1000;
+ box-sizing: border-box;
+ pointer-events: none;
+ background: rgba(0, 0, 0, 0.76);
+ outline: none;
+ opacity: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+
+ .overlay-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 1;
+ }
+
+ .overlay-body {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ z-index: 2;
+
+ .overlay-close {
+ flex-shrink: 0;
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ > a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ color: #dbdbde;
+ &:hover {
+ color: #fff
+ }
+ .icon {
+ width: 24px;
+ height: 24px
+ }
+ }
+ }
+
+ .overlay-resize {
+ width: 100%;
+ height: 5px;
+ margin-bottom: -5px;
+ z-index: 1;
+ }
+
+ .overlay-content {
+ flex: 1;
+ position: relative;
+ background: #fff;
+ border-radius: 18px 18px 0 0;
+ transform: translate(0, 15%) scale(0.98);
+ cursor: default;
+ opacity: 0;
+ }
+ }
+
+ &.overlay-visible {
+ pointer-events: auto;
+ opacity: 1;
+ width: 100%;
+ height: 100%;
+ transition: opacity 0.2s ease;
+ .overlay-body {
+ .overlay-content {
+ opacity: 1;
+ transform: translate(0, 0) scale(1);
+ transition: opacity 0.2s ease, transform 0.3s ease;
+ }
+ }
+ }
+
+ &.overlay-hide {
+ width: 100%;
+ height: 100%;
+ transition: opacity 0.2s ease;
+ .overlay-body {
+ .overlay-content {
+ transform: translate(0, 15%) scale(0.98);
+ transition: opacity 0.2s ease, transform 0.2s ease
+ }
+ }
+ }
+
+ &.right {
+ flex-direction: row;
+ justify-content: flex-end;
+ .overlay-body {
+ flex-direction: row;
+ .overlay-close {
+ align-items: flex-start;
+ }
+ .overlay-resize {
+ width: 5px;
+ height: 100%;
+ margin-right: -5px;
+ z-index: 1;
+ }
+ .overlay-content {
+ transform: translate(15%, 0) scale(0.98);
+ border-radius: 18px 0 0 18px;
+ }
+ }
+ &.overlay-visible {
+ .overlay-body {
+ .overlay-content {
+ transform: translate(0, 0) scale(1);
+ }
+ }
+ }
+ &.overlay-hide {
+ .overlay-body {
+ .overlay-content {
+ transform: translate(15%, 0) scale(0.98);
+ }
+ }
+ }
+ }
+}
diff --git a/resources/assets/sass/img-update.scss b/resources/assets/sass/img-update.scss
new file mode 100644
index 00000000..3c0cf6dd
--- /dev/null
+++ b/resources/assets/sass/img-update.scss
@@ -0,0 +1,255 @@
+.img-upload-modal {
+ .ivu-modal-mask {
+ z-index: 1001;
+ }
+
+ .ivu-modal-no-mask {
+ background-color: rgba(55, 55, 55, .2);
+ }
+
+ .ivu-modal-wrap {
+ z-index: 1001;
+ }
+}
+
+.imgcomp-upload-list {
+ display: inline-block;
+ width: 60px;
+ height: 60px;
+ text-align: center;
+ line-height: 60px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ overflow: hidden;
+ background: #fff;
+ position: relative;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
+ margin-right: 4px;
+ vertical-align: top;
+
+ .imgcomp-upload-img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-position: center;
+ background-size: cover;
+ }
+
+ .imgcomp-upload-list-cover {
+ display: none;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, .6);
+ }
+
+ .imgcomp-upload-list-cover i {
+ color: #fff;
+ font-size: 24px;
+ cursor: pointer;
+ vertical-align: middle;
+ margin: 0;
+ transition: all .2s;
+ }
+
+ .imgcomp-upload-list-cover i:hover {
+ font-size: 28px;
+ }
+
+ .ivu-progress {
+ height: 100%;
+ .ivu-progress-outer {
+ background-color: rgba(0, 0, 0, 0.68);
+ height: 100%;
+ .ivu-progress-inner {
+ width: 88%;
+ margin: 0 auto;
+ }
+ }
+ }
+}
+
+.imgcomp-upload-list:hover .imgcomp-upload-list-cover {
+ display: block;
+}
+
+.img-upload-foot {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+ .img-upload-foot-input {
+ flex: 1;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+ .img-upload-foot-httptitle {
+ cursor: pointer;
+ padding-left: 3px;
+ margin-right: 22px;
+ }
+ }
+}
+
+.add-box {
+ width: 60px;
+ height: 60px;
+ line-height: 60px;
+ display: inline-block;
+ background: #fff;
+ border: 1px dashed #dddee1;
+ border-radius: 4px;
+ text-align: center;
+ position: relative;
+ overflow: hidden;
+ vertical-align: top;
+
+ .add-box-icon {
+ i {
+ vertical-align: middle;
+ padding-bottom: 2px;
+ }
+ }
+
+ .add-box-upload {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ color: #ffffff;
+ padding-top: 9px;
+ background: rgba(0, 0, 0, 0.6);
+
+ .add-box-item {
+ height: 22px;
+ line-height: 22px;
+ cursor: pointer;
+
+ .ivu-upload-drag, .ivu-upload-drag:hover {
+ background: transparent;
+ border: 0;
+ border-radius: 0;
+ }
+
+ span {
+ transition: all .2s;
+ font-size: 12px;
+ }
+ }
+
+ .add-box-item:hover {
+ span {
+ font-size: 14px;
+ }
+ }
+ }
+
+ em {
+ font-style: normal;
+ }
+}
+
+.add-box:hover {
+ border-color: rgba(0, 0, 0, .6);
+
+ .add-box-upload {
+ display: block;
+ }
+}
+
+.callback-add-box {
+ display: block;
+ width: auto;
+ height: 25px;
+ line-height: 25px;
+ border: 0;
+ background: transparent;
+
+ .add-box-icon {
+ display: none;
+ }
+
+ .add-box-upload {
+ display: block;
+ width: auto;
+ background: transparent;
+ color: #333;
+ padding: 0;
+
+ > div {
+ display: inline-block;
+ padding-right: 10px;
+ }
+ }
+}
+
+.browse-load {
+ margin: 20px;
+ text-align: center;
+}
+
+.browse-list {
+ max-height: 540px;
+ overflow: auto;
+
+ .browse-item {
+ margin: 10px 15px;
+ display: inline-block;
+ text-align: center;
+ cursor: pointer;
+ position: relative;
+
+ .browse-img {
+ width: 64px;
+ height: 64px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKoAAABxCAAAAABg5GeyAAACW0lEQVR4Ae3XVRLjMBAE0L3/rSwKM3OcSNPyLVYOLvM6UD0Bd03LVe9XH+RlhlRSSSWVVFJJJZVUUkkllVRSSSWVVFJJJZVUUkl9WyqppJJKKqmkkgpURP17xngOAR5NxW5wlJ9MaLQh83F4NHWmd/gZtdVBaOldfDB1bq5UpJFbFOC6LKnYrkRO209PAw+hIuzWB8Ep5es8HvYo4z4tE1X8UeRwlMM2D5Bzkc7kj6Bi3VTKDDwEeUcrMxrUvGDXTnHa6kK69SDN9sgq1clxKSbNHqqnYmdri81Q9QHf1JPt1Frncaib2XbiTKL2GkHaurnY9LOulMV0O7G6Kw+g9sw2ohhm62KezVJaaufjWC1TnOkr1exilJ7Ji0vxCCqO9V4UwV4PYr9+apouhGYLKfnahdpqegjmeoXOpXgANe70pKT6Zhu19qkY2nC0PZS527lQOyInqr8Uvc5jqfUb1X+PGh5IhW90S2quh3FQC2XRcF66TUkTXPcLKm5FtdR9RJq+2hWII7UpFtmsQLEyzsdJtkxxpr6gLotbUSlV9yeT0Trmzk2XPdUThLYarUbWOY9j04xXQ2u+pMZLYSumGmNUH3HbM9qOAwSHodN2Pks25F2j3aI7+IxqNsB+YLWb16ukSjiW4xNB0gMoMfApBS/XZQgi3p9/5RsiKNKZEOwYFVIF5VyTyD19sbyjIJiNJRZxpNbx2S8sGKvGZNHJBniBu9Wy5WxjGuQFqIAcBHiRGyt4ua5gSCWVVFJJJZVUUkkllVRSSSWVVFJJJZVUUkkllVRSSSWVVFI/AgO0SXIVYHeGAAAAAElFTkSuQmCC);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ }
+
+ .browse-title {
+ display: block;
+ width: 64px;
+ margin-top: 5px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .browse-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 64px;
+ font-size: 36px;
+ padding-top: 15px;
+ color: #ffffff;
+ background-color: rgba(0, 0, 0, 0.5);
+ }
+ }
+}
+
+.browse-list-disabled {
+ position: relative;
+}
+
+.browse-list-disabled:after {
+ position: absolute;
+ content: '';
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(255, 255, 255, 0.9);
+ z-index: 1;
+}
diff --git a/resources/assets/sass/pages/components/dialog-wrapper.scss b/resources/assets/sass/pages/components/dialog-wrapper.scss
index bf9dfbb6..c27e80b1 100644
--- a/resources/assets/sass/pages/components/dialog-wrapper.scss
+++ b/resources/assets/sass/pages/components/dialog-wrapper.scss
@@ -180,6 +180,17 @@
}
}
}
+ .dialog-action {
+ align-self: flex-start;
+ display: flex;
+ align-items: flex-start;
+ height: 100%;
+
+ > * {
+ margin: 0 5px;
+ }
+
+ }
&.history {
cursor: pointer;
justify-content: center;
diff --git a/resources/assets/sass/pages/page-login.scss b/resources/assets/sass/pages/page-login.scss
index dab531e1..ce92e1ff 100644
--- a/resources/assets/sass/pages/page-login.scss
+++ b/resources/assets/sass/pages/page-login.scss
@@ -79,6 +79,17 @@
.login-switch {
color: #aaaaaa;
}
+ .login-input-tips-box{
+ position: relative;
+ .login-input-tips{
+ font-size: 12px;
+ position: absolute;
+ left: 0;
+ bottom: -20px;
+ color: #c7c7c7;
+ }
+ }
+
}
}
.login-bottom {
diff --git a/resources/assets/sass/quick-edit.scss b/resources/assets/sass/quick-edit.scss
new file mode 100755
index 00000000..f9f59f15
--- /dev/null
+++ b/resources/assets/sass/quick-edit.scss
@@ -0,0 +1,58 @@
+.quick-edit {
+ display: flex;
+ align-items: center;
+ max-width: 100%;
+ .quick-input {
+ flex: 1;
+ max-width: 100%;
+ position: relative;
+ .quick-loading {
+ position: absolute;
+ top: 0;
+ right: 8px;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ .common-loading {
+ margin: 0;
+ width: 14px;
+ height: 14px;
+ }
+ }
+ }
+ .quick-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ align-items: center;
+ white-space: nowrap;
+ height: 20px;
+ line-height: 20px;
+ margin-right: 6px;
+ }
+ .quick-icon {
+ display: none;
+ font-size: 16px;
+ cursor: pointer;
+ }
+ &.quick-always {
+ .quick-icon {
+ display: inline-block;
+ opacity: 0.3;
+ transition: opacity 0.2s;
+ }
+ }
+ &:hover {
+ .quick-icon {
+ display: inline-block;
+ opacity: 1;
+ }
+ }
+}
+.ivu-table-row-hover {
+ .quick-edit {
+ .quick-icon {
+ display: inline-block;
+ opacity: 1;
+ }
+ }
+}
diff --git a/resources/assets/sass/report.scss b/resources/assets/sass/report.scss
new file mode 100644
index 00000000..99a28cca
--- /dev/null
+++ b/resources/assets/sass/report.scss
@@ -0,0 +1,218 @@
+.report {
+ height: 100%;
+ padding: 10px 20px;
+ .report-list-wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ top: 0;
+ padding-top: 53px;
+ flex-direction: column;
+ position: absolute;
+ .report-row-header,.report-row-foot{
+ flex: 0 0 auto;
+ }
+ .report-row-content{
+ flex:1 1 auto;
+ .ivu-table{
+ .ivu-table-body{
+ height: 100%;
+ overflow-y: auto;
+ padding-bottom: 50px;
+ }
+ }
+ }
+ }
+
+ .page-box {
+ text-align: center;
+ margin-top: 15px;
+ }
+ .ivu-tabs{
+ height: 100%;
+ position: relative;
+ .ivu-tabs-bar{
+ position: relative;
+ z-index: 2;
+ background: #fff;
+ }
+ .ivu-tabs-content{
+ height: 100%;
+ width: 100%;
+ margin-top: -53px;
+ padding-top: 53px;
+ }
+ .ivu-tabs-tabpane{
+ overflow-y: auto;
+ }
+ }
+}
+
+.report-detail {
+ .report-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 12px;
+ padding-top: 20px;
+ .report-title-icon{
+ font-size: 22px;
+ cursor: pointer;
+ margin-right: 20px;
+ }
+ }
+
+ .report-profile {
+ font-size: 14px;
+ }
+
+ .report-submitter {
+ display: flex;
+ flex-wrap: wrap;
+ height: 28px;
+ line-height: 28px;
+ p{
+ display: block;
+ width: 100%;
+ text-align: justify;
+ padding-right: 12px;
+ &:after{
+ content: '';
+ display: inline-block;
+ width: 100%;
+ }
+ }
+ & > div {
+ margin-right: 8px;
+ }
+ }
+
+ .ivu-col{
+ margin-bottom: 12px;
+ }
+
+ .report-content {
+ margin-top: 12px;
+ width: 100%;
+ h2{
+ margin-bottom: 10px;
+ }
+ ol{
+ margin-bottom: 20px;
+ padding-left: 18px;
+ li{
+ font-size: 14px;
+ line-height: 24px;
+ }
+ }
+ }
+}
+.report-box{
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ .report-row-header,.report-row-foot{
+ flex: 0 0 auto;
+ }
+ .report-row-content{
+ flex:1 1 auto;
+ }
+ .report-row-foot{
+ margin-bottom: 0;
+ }
+}
+.report-row{
+ margin-bottom: 20px;
+ .report-row-a{
+ float: right;
+ line-height: 32px;
+ .report-row-a-icon{
+ transform: rotate(-90deg);
+ font-size: 16px;
+ margin-right: 2px;
+ }
+ }
+ .report-bottom{
+ width: 120px;
+ }
+ .report-bottom-save{
+ width: 120px;
+ background: #F4F5F7 ;
+ color: #515A6E;
+ border-color: #F4F5F7 ;
+ }
+}
+.report-titles{
+ line-height: 32px;
+}
+.report-radiogroup{
+ background: #F4F5F7 !important;
+ padding: 2px !important;
+ border-radius: 4px!important;
+ .ivu-radio-wrapper{
+ padding: 0 30px !important;
+ background: #F4F5F7 !important;
+ color: #515A6E !important;
+ box-shadow: none !important;
+ border: none!important;
+ &:before{
+ width: 0!important;
+ }
+ &:after{
+ width: 0!important;
+ }
+ }
+ .ivu-radio-focus{
+ box-shadow: none !important;
+ border: none!important;
+ &:after{
+ background: none!important;
+ }
+ }
+ .ivu-radio-wrapper-checked:not(.ivu-radio-wrapper-disabled){
+ background: #fff !important;
+ color: #8BCF70 !important;
+ box-shadow: none !important;
+ border: none!important;
+ border-radius: 4px!important;
+ }
+
+}
+.report-buttongroup{
+ margin-top: 2px;
+ background: #F4F5F7!important;
+ border-radius: 4px;
+ .report-buttongroup-shu{
+ position: absolute;
+ left: 47px;
+ width: 1px;
+ height: 23px;
+ background-color: #E5E5E5;
+ top: 5px;
+ }
+ .ivu-btn-primary{
+ background: #F4F5F7!important;
+ box-shadow: none !important;
+ border: none!important;
+ color: #8BCF70 !important;
+ &[disabled]{
+ color: #515A6E !important;
+ }
+ }
+}
+.report-poptip{
+ .ivu-tooltip-inner{
+ min-width: 60px !important;
+ font-size: 12px !important;
+ }
+}
+.reportmy-row{
+ margin-bottom: 20px;
+ .reportmy-titles{
+ line-height: 32px;
+ }
+}
+.report-main{
+ .report-submitter{
+ padding-top: 13px;
+ }
+}
diff --git a/resources/assets/sass/scroller-y.scss b/resources/assets/sass/scroller-y.scss
new file mode 100755
index 00000000..7f9f816d
--- /dev/null
+++ b/resources/assets/sass/scroller-y.scss
@@ -0,0 +1,19 @@
+.app-scroller-y {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ .app-scroller-bottom {
+ height: 0;
+ margin: 0;
+ padding: 0;
+ }
+ &.static {
+ position: static;
+ flex: 1;
+ }
+}
diff --git a/resources/assets/sass/spinner.scss b/resources/assets/sass/spinner.scss
new file mode 100644
index 00000000..8238a0ed
--- /dev/null
+++ b/resources/assets/sass/spinner.scss
@@ -0,0 +1,20 @@
+.common-spinner {
+ display: none;
+ position: fixed;
+ z-index: 9999;
+ bottom: 20px;
+ right: 20px;
+ margin: 0 auto;
+ width: 30px;
+ height: 30px;
+
+ .common-circular {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ }
+}
diff --git a/resources/assets/sass/t-editor.scss b/resources/assets/sass/t-editor.scss
new file mode 100755
index 00000000..30d5a48f
--- /dev/null
+++ b/resources/assets/sass/t-editor.scss
@@ -0,0 +1,118 @@
+.teditor-box,
+.teditor-transfer {
+ .tox {
+ &.tox-silver-sink {
+ z-index: 13000;
+ }
+ }
+}
+
+.teditor-box {
+ position: relative;
+ min-height: 22px;
+
+ .icon-inline {
+ color: #bbbbbb;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+
+ textarea {
+ opacity: 0;
+ }
+
+ .tox-tinymce {
+ box-shadow: none;
+ box-sizing: border-box;
+ border-color: #dddee1;
+ border-radius: 4px;
+ overflow: hidden;
+
+ .tox-statusbar {
+ span.tox-statusbar__branding {
+ a {
+ display: none;
+ }
+ }
+ }
+
+ .tox-tbtn--bespoke {
+ .tox-tbtn__select-label {
+ width: auto;
+ }
+ }
+ }
+}
+
+.teditor-transfer {
+ background-color: #ffffff;
+
+ .tox-toolbar {
+ > div:last-child {
+ > button:last-child {
+ margin-right: 64px;
+ }
+ }
+ }
+
+ .ivu-modal-header {
+ display: none;
+ }
+
+ .ivu-modal-close {
+ top: 7px;
+ z-index: 2;
+ }
+
+ .teditor-transfer-body {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+
+ textarea {
+ opacity: 0;
+ }
+
+ .tox-tinymce {
+ border: 0;
+
+ .tox-statusbar {
+ span.tox-statusbar__branding {
+ a {
+ display: none;
+ }
+ }
+ }
+ }
+ }
+}
+
+.teditor-loadstyle {
+ width: 100%;
+ height: 180px;
+ overflow: hidden;
+ position: relative;
+}
+
+.teditor-loadedstyle {
+ width: 100%;
+ max-height: inherit;
+ overflow: inherit;
+ position: relative;
+}
+
+.upload-control {
+ display: none;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+}
+
+.tox-tinymce-inline {
+ z-index: 100000;
+}
diff --git a/resources/assets/sass/tag-input.scss b/resources/assets/sass/tag-input.scss
new file mode 100755
index 00000000..a377ea07
--- /dev/null
+++ b/resources/assets/sass/tag-input.scss
@@ -0,0 +1,93 @@
+.common-tag-input {
+ display: inline-block;
+ width: 100%;
+ min-height: 32px;
+ padding: 2px 7px;
+ border: 1px solid #dddee1;
+ border-radius: 4px;
+ color: #495060;
+ background: #fff;
+ position: relative;
+ cursor: text;
+ vertical-align: middle;
+ line-height: normal;
+ transition: all .2s;
+
+ &:hover {
+ border-color: #a2d98d;
+ }
+
+ &.focus {
+ border-color: #a2d98d;
+ box-shadow: 0 0 0 2px rgba(139,207,112,.2)
+ }
+
+ .tags-item, .tags-input {
+ position: relative;
+ float: left;
+ color: #495060;
+ background-color: #f1f8ff;
+ border-radius: 3px;
+ line-height: 22px;
+ margin: 2px 6px 2px 0;
+ padding: 0 20px 0 6px;
+
+ .tags-content {
+ line-height: 22px;
+ }
+
+ .tags-del {
+ width: 20px;
+ height: 22px;
+ text-align: center;
+ cursor: pointer;
+ position: absolute;
+ top: -1px;
+ right: 0;
+ }
+ }
+
+ .tags-input {
+ max-width: 80%;
+ padding: 0;
+ background-color: inherit;
+ border: none;
+ color: inherit;
+ height: 22px;
+ line-height: 22px;
+ -webkit-appearance: none;
+ outline: none;
+ resize: none;
+ overflow: hidden;
+ }
+
+ .tags-input::placeholder {
+ color: #bbbbbb;
+ }
+
+ .tags-placeholder {
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: -1;
+ color: rgba(255, 255, 255, 0);
+ }
+}
+.common-tag-input::after {
+ content: "";
+ display: block;
+ height: 0;
+ clear: both;
+}
+.ivu-form-item-error {
+ .common-tag-input {
+ border-color: #ed4014;
+ &:hover {
+ border-color: #ed4014;
+ }
+ &.focus {
+ border-color: #ed4014;
+ box-shadow: 0 0 0 2px rgba(237,64,20,.2)
+ }
+ }
+}
diff --git a/resources/assets/sass/user-avatar.scss b/resources/assets/sass/user-avatar.scss
new file mode 100755
index 00000000..93d872a4
--- /dev/null
+++ b/resources/assets/sass/user-avatar.scss
@@ -0,0 +1,73 @@
+.common-avatar {
+ position: relative;
+ &.avatar-wrapper {
+ display: flex;
+ align-items: center;
+ .avatar-box {
+ position: relative;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ .avatar-default {
+ background-color: transparent;
+ }
+ .avatar-text {
+ background-color: $primary-color;
+ > span {
+ display: inline-block;
+ font-size: 15px;
+ line-height: 1;
+ }
+ }
+ > em {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: #ff9900;
+ border: 1px solid #ffffff;
+ transform-origin: right bottom;
+ z-index: 1;
+ }
+ &.online {
+ > em {
+ background-color: $primary-color;
+ }
+ }
+ }
+ .avatar-name {
+ padding-left: 6px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+}
+.common-avatar-transfer {
+ padding: 4px 2px;
+ line-height: 1.5;
+ > p {
+ padding: 1px 2px;
+ }
+ .avatar-icons {
+ margin-top: 12px;
+ border-top: 1px solid rgba(244, 244, 245, 0.5);
+ padding: 8px 0 2px;
+ display: flex;
+ align-items: center;
+ > i {
+ cursor: pointer;
+ font-size: 22px;
+ margin-right: 12px;
+ color: #F4F4F5;
+ &:last-child {
+ margin-right: 0;
+ }
+ &:hover {
+ color: #ffffff;
+ }
+ }
+ }
+}
diff --git a/resources/assets/sass/user-input.scss b/resources/assets/sass/user-input.scss
new file mode 100755
index 00000000..f316f691
--- /dev/null
+++ b/resources/assets/sass/user-input.scss
@@ -0,0 +1,70 @@
+.common-user {
+ position: relative;
+ white-space: normal;
+ .common-user-loading {
+ position: absolute;
+ top: 2px;
+ bottom: 0;
+ right: 10px;
+ display: flex;
+ align-items: center;
+ .common-loading {
+ width: 14px;
+ height: 14px;
+ }
+ }
+ &.hidden-input {
+ .ivu-select-selection {
+ padding: 0 4px;
+ .ivu-select-input {
+ display: none;
+ }
+ }
+ }
+}
+.common-user-transfer {
+ .user-input-option {
+ display: flex;
+ align-items: center;
+ .user-input-avatar {
+ display: flex;
+ align-items: center;
+ .avatar {
+ width: 26px;
+ height: 26px;
+ line-height: 26px;
+ }
+ }
+ .user-input-nickname {
+ margin-left: 10px;
+ flex: 1;
+ }
+ .user-input-userid {
+ margin-left: 10px;
+ font-size: 12px;
+ color: #cccccc;
+ transition: margin 0.1s;
+ }
+ }
+ .ivu-select-item {
+ &.ivu-select-item-selected {
+ &:after {
+ top: 8px;
+ }
+ .user-input-option {
+ .user-input-userid {
+ margin-right: 16px;
+ }
+ }
+ }
+ }
+ .user-drop-prepend {
+ text-align: center;
+ color: #c5c8ce;
+ line-height: 20px;
+ padding-bottom: 5px;
+ font-size: 12px;
+ border-bottom: 1px solid #f1f1f1;
+ margin-bottom: 5px;
+ }
+}
diff --git a/routes/web.php b/routes/web.php
index 4f3fc9eb..bc307054 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\Api\DialogController;
use App\Http\Controllers\Api\FileController;
use App\Http\Controllers\Api\ProjectController;
+use App\Http\Controllers\Api\ReportController;
use App\Http\Controllers\Api\SystemController;
use App\Http\Controllers\Api\UsersController;
use App\Http\Controllers\IndexController;
@@ -39,6 +40,9 @@ Route::prefix('api')->middleware(['webapi'])->group(function () {
// 文件
Route::any('file/{method}', FileController::class);
Route::any('file/{method}/{action}', FileController::class);
+ // 报告
+ Route::any('report/{method}', ReportController::class);
+ Route::any('report/{method}/{action}', ReportController::class);
});
/**