feat: 签到功能

This commit is contained in:
kuaifan 2022-12-11 21:42:18 +08:00
parent 2c7f4837d5
commit e3cdb20579
14 changed files with 859 additions and 108 deletions

View File

@ -2,9 +2,10 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\User;
use App\Models\UserCheckin;
use App\Models\UserCheckinRecord;
use App\Module\Base;
use Carbon\Carbon;
use Request;
@ -16,127 +17,95 @@ use Request;
*/
class PublicController extends AbstractController
{
const appid = "10001";
const appkey = "TWVCVBJSiCjAFOPpFVdkpQCMWDw66EUY";
/**
* 验证签名
* @return void
* 签到 - 路由器openwrt功能安装脚本
*
* @apiParam {String} key
*
* @return string
*/
private function _sign()
public function checkin__install()
{
$query = Request::query();
$query['sign'] = Request::header('sign') ?: Request::input('sign');
// 检查必要参数
if ($query['appid'] != self::appid) {
throw new ApiException('appid is error');
$key = trim(Request::input('key'));
//
$setting = Base::setting('checkinSetting');
if ($setting['wifi'] !== 'open') {
return <<<EOF
#!/bin/sh
echo "function off"
EOF;
}
foreach (['appid', 'ver', 'ts', 'nonce'] as $key) {
if (!isset($query[$key])) {
throw new ApiException($key . ' parameter is empty');
}
}
if (intval($query['ts']) + 300 < time()) {
throw new ApiException('ts expired');
}
// 验证签名
ksort($query);
$string = "";
foreach ($query as $k => $v) {
if ($v != '' && $k != 'sign') {
$string .= $k . "=" . $v . "&";
}
}
$sign = md5($string . self::appkey);
if ($sign != $query['sign']) {
throw new ApiException('sign is error');
if ($key != $setting['key']) {
return <<<EOF
#!/bin/sh
echo "key error"
EOF;
}
//
$reportUrl = Base::fillUrl("api/public/checkin/report");
return <<<EOE
#!/bin/sh
echo 'installing...'
cat > /etc/init.d/dootask-checkin-report <<EOF
#!/bin/sh
mac=\\\$(awk 'NR!=1&&\\\$3=="0x2" {print \\\$4}' /proc/net/arp | tr "\\n" ",")
tmp='{"key":"{$setting['key']}","mac":"'\\\${mac}'","time":"'\\\$(date +%s)'"}'
curl -4 -X POST "{$reportUrl}" -H "Content-Type: application/json" -d \\\${tmp}
EOF
chmod +x /etc/init.d/dootask-checkin-report
crontab -l >/tmp/cronbak
sed -i '/\/etc\/init.d\/dootask-checkin-report/d' /tmp/cronbak
sed -i '/^$/d' /tmp/cronbak
echo "* * * * * sh /etc/init.d/dootask-checkin-report" >>/tmp/cronbak
crontab /tmp/cronbak
rm -f /tmp/cronbak
/etc/init.d/cron enable
/etc/init.d/cron restart
echo 'installed'
EOE;
}
/**
* @api {get} api/public/attendance/portraitlist 01. 【考勤】人员头像数据
* {post} 签到 - 路由器openwrt上报
*
* @apiDescription 需要签名
* @apiVersion 1.0.0
* @apiGroup public
* @apiName attendance__portraitlist
* @apiParam {String} key
* @apiParam {String} mac 使用逗号分割多个
* @apiParam {String} time
*
* @apiParam {String} last_at 最后获取时间格式示例2022-01-01 12:50:01
*
* @apiParam {String} appid 唯一身份ID跟签名appkey配合使用
* @apiParam {String} ver 版本号1.0
* @apiParam {Number} ts 10位数时间戳有效时间300秒
* @apiParam {String} nonce 随机字符串
* @apiParam {String} sign 签名字符串=md5(query_key1=query_val1&query_key2=query_val2...&appkey)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
* @return string
*/
public function attendance__portraitlist()
public function checkin__report()
{
$this->_sign();
//
$last_at = Request::input('last_at');
//
$builder = User::where('userimg', '!=', '')->whereNull('disable_at');
if (strtotime($last_at)) {
$builder->where('updated_at', '>', Carbon::parse($last_at));
}
$list = $builder->orderBy('updated_at')->take(50)->get();
//
$array = [];
foreach ($list as $item) {
$array[] = [
'userid' => $item->userid,
'userimg' => $item->userimg,
'updated_at' => $item->updated_at,
];
}
//
return Base::retSuccess('success', $array);
}
/**
* @api {get} api/public/attendance/update 02. 【考勤】上报考勤数据
*
* @apiDescription 需要签名
* @apiVersion 1.0.0
* @apiGroup public
* @apiName attendance__update
*
* @apiParam {Number} userid 会员ID
* @apiParam {Number} time 时间数据10位数时间戳
*
* @apiParam {String} appid 唯一身份ID跟签名appkey配合使用
* @apiParam {String} ver 版本号1.0
* @apiParam {Number} ts 10位数时间戳有效时间300秒
* @apiParam {String} nonce 随机字符串
* @apiParam {String} sign 签名字符串=md5(query_key1=query_val1&query_key2=query_val2...&appkey)
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function attendance__update()
{
$this->_sign();
//
$userid = intval(Request::input('userid'));
$key = trim(Request::input('key'));
$mac = trim(Request::input('mac'));
$time = intval(Request::input('time'));
//
$user = User::whereUserid($userid)->first();
if (empty($user)) {
return Base::retError('user not exist');
$setting = Base::setting('checkinSetting');
if ($setting['wifi'] !== 'open') {
return 'function off';
}
if ($key != $setting['key']) {
return 'key error';
}
// todo 保存到考勤数据库
info([
'userid' => $user->userid,
'input' => Request::input(),
'time' => $time,
'at' => Carbon::now()->toDateTimeString(),
]);
//
return Base::retSuccess('success');
$macs = explode(",", $mac);
foreach ($macs as $item) {
$item = strtoupper($item);
if (empty($item) || !preg_match("/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/", $item)) {
continue;
}
$userCheckin = UserCheckin::whereMac($item)->first();
if ($userCheckin) {
UserCheckinRecord::createInstance([
'userid' => $userCheckin->userid,
'mac' => $userCheckin->mac,
'time' => $time,
])->save();
}
}
return 'success';
}
}

View File

@ -4,11 +4,17 @@ namespace App\Http\Controllers\Api;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserCheckinRecord;
use App\Module\Base;
use App\Module\BillExport;
use App\Module\BillMultipleExport;
use Arr;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
use Madzipper;
use Request;
use Session;
/**
* @apiDefine system
@ -208,6 +214,57 @@ class SystemController extends AbstractController
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/checkin 03. 获取签到设置、保存签到设置(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__checkin
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['wifi', 'key']
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__checkin()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Request::input();
foreach ($all as $key => $value) {
if (!in_array($key, [
'wifi',
'key',
])) {
unset($all[$key]);
}
}
if ($all['wifi'] === 'close') {
$all['key'] = md5(Base::generatePassword(32));
}
$setting = Base::setting('checkinSetting', Base::newTrim($all));
} else {
$setting = Base::setting('checkinSetting');
}
//
if (empty($setting['key'])) {
$setting['key'] = md5(Base::generatePassword(32));
Base::setting('checkinSetting', $setting);
}
//
$setting['wifi'] = $setting['wifi'] ?: 'close';
$setting['cmd'] = "curl -sSL '" . Base::fillUrl("api/public/checkin/install?key={$setting['key']}") . "' | sh";
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/apppush 04. 获取APP推送设置、保存APP推送设置限管理员
*
@ -742,6 +799,171 @@ class SystemController extends AbstractController
}
}
/**
* @api {get} api/system/checkin/export 17. 导出签到数据(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName checkin__export
*
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
* @apiParam {Array} [date] 指定日期范围,如:['2020-12-12', '2020-12-30']
* @apiParam {Array} [time] 指定时间范围,如:['09:00', '18:00']
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function checkin__export()
{
User::auth('admin');
//
$userid = Base::arrayRetainInt(Request::input('userid'), true);
$date = Request::input('date');
$time = Request::input('time');
//
if (empty($userid) || empty($date) || empty($time)) {
return Base::retError('参数错误');
}
if (count($userid) > 20) {
return Base::retError('导出成员限制最多20个');
}
if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) {
return Base::retError('日期选择错误');
}
if (Carbon::parse($date[1])->timestamp - Carbon::parse($date[0])->timestamp > 35 * 86400) {
return Base::retError('日期范围限制最大35天');
}
if (!(is_array($time) && Base::isTime($time[0]) && Base::isTime($time[1]))) {
return Base::retError('时间选择错误');
}
//
$secondStart = strtotime("2000-01-01 {$time[0]}") - strtotime("2000-01-01 00:00:00");
$secondEnd = strtotime("2000-01-01 {$time[1]}") - strtotime("2000-01-01 00:00:00");
//
$headings = [];
$headings[] = '成员ID';
$headings[] = '成员名称';
$headings[] = '成员邮箱';
$headings[] = '签到日期';
$headings[] = '签到时间1';
$headings[] = '签到时间2';
//
$sheets = [];
$start = Carbon::parse($date[0])->startOfDay();
$end = Carbon::parse($date[1])->endOfDay();
$users = User::whereIn('userid', $userid)->take(20)->get();
/** @var User $user */
foreach ($users as $user) {
$records = UserCheckinRecord::whereUserid($user->userid)->whereBetween("created_at", [$start, $end])->orderBy('id')->get();
//
$datas = [];
$styles = [];
$startT = $start->timestamp;
$endT = $end->timestamp;
$index = 1;
while ($startT < $endT) {
$index++;
$first = $records->whereBetween("created_at", [Carbon::parse($startT), Carbon::parse($startT + $secondStart)])->first();
$last = $records->whereBetween("created_at", [Carbon::parse($startT + $secondEnd), Carbon::parse($startT + 86400)])->last();
$first = $first ? Carbon::parse($first->created_at)->timestamp : 0;
$last = $last ? Carbon::parse($last->created_at)->timestamp : 0;
if (empty($first) || $first > $startT + $secondStart) {
$styles["E{$index}"] = [
'font' => [
'color' => [
'rgb' => 'ff0000'
]
],
];
}
if (empty($last) || $last < $startT + $secondEnd) {
$styles["F{$index}"] = [
'font' => [
'color' => [
'rgb' => 'ff0000'
]
],
];
}
$datas[] = [
$user->userid,
$user->nickname,
$user->email,
date("Y-m-d", $startT),
$first ? date("H:i", $first) : '-',
$last ? date("H:i", $last) : '-',
];
$startT += 86400;
}
$sheets[] = BillExport::create()->setTitle($user->nickname)->setHeadings($headings)->setData($datas)->setStyles($styles);
}
if (empty($sheets)) {
return Base::retError('没有任何数据');
}
//
$fileName = $users[0]->nickname;
if (count($users) > 1) {
$fileName .= "" . count($userid) . "位成员";
}
$fileName .= '签到记录_' . Base::time() . '.xls';
$filePath = "temp/checkin/export/" . date("Ym", Base::time());
$export = new BillMultipleExport($sheets);
$res = $export->store($filePath . "/" . $fileName);
if ($res != 1) {
return Base::retError('导出失败,' . $fileName . '');
}
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
$zipPath = storage_path($zipFile);
if (file_exists($zipPath)) {
Base::deleteDirAndFile($zipPath, true);
}
try {
Madzipper::make($zipPath)->add($xlsPath)->close();
} catch (\Throwable) {
}
//
if (file_exists($zipPath)) {
$base64 = base64_encode(Base::array2string([
'file' => $zipFile,
]));
Session::put('checkin::export:userid', $user->userid);
return Base::retSuccess('success', [
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
'url' => Base::fillUrl('api/system/checkin/down?key=' . urlencode($base64)),
]);
} else {
return Base::retError('打包失败,请稍后再试...');
}
}
/**
* @api {get} api/system/checkin/down 17. 下载导出的签到数据
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName checkin__down
*
* @apiParam {String} key 通过export接口得到的下载钥匙
*
* @apiSuccess {File} data 返回数据(直接下载文件)
*/
public function checkin__down()
{
$userid = Session::get('checkin::export:userid');
if (empty($userid)) {
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
}
//
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
$file = $array['file'];
if (empty($file) || !file_exists(storage_path($file))) {
return Base::ajaxError("文件不存在!", [], 0, 502);
}
return response()->download(storage_path($file));
}
/**
* @api {get} api/system/version 18. 获取版本号
*

View File

@ -7,6 +7,7 @@ use App\Models\Meeting;
use App\Models\Project;
use App\Models\UmengAlias;
use App\Models\User;
use App\Models\UserCheckin;
use App\Models\UserDelete;
use App\Models\UserDepartment;
use App\Models\UserEmailVerification;
@ -1287,4 +1288,87 @@ class UsersController extends AbstractController
//
return Base::retSuccess('删除成功');
}
/**
* @api {get} api/users/checkin/get 22. 获取签到设置
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName checkin__get
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function checkin__get()
{
$user = User::auth();
//
$list = UserCheckin::whereUserid($user->userid)->orderBy('id')->get();
//
return Base::retSuccess('success', $list);
}
/**
* @api {post} api/users/checkin/save 22. 保存签到设置
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup users
* @apiName checkin__save
*
* @apiParam {Array} list 优先级数据,格式:[{mac,remark}]
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function checkin__save()
{
$user = User::auth();
//
if (Base::settingFind('checkinSetting', 'wifi') !== 'open') {
return Base::retError('此功能未开启,请联系管理员开启');
}
//
$list = Base::getPostValue('list');
$array = [];
if (empty($list) || !is_array($list)) {
return Base::retError('参数错误');
}
foreach ($list AS $item) {
$item = Base::newTrim($item);
if (empty($item['mac']) || !preg_match("/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/", $item['mac'])) {
continue;
}
$array[] = [
'mac' => strtoupper($item['mac']),
'remark' => substr($item['remark'], 0, 50),
];
}
if (count($array) > 3) {
return Base::retError('最多只能添加3个MAC地址');
}
//
return AbstractModel::transaction(function() use ($array, $user) {
$ids = [];
$list = [];
foreach ($array as $item) {
$row = UserCheckin::updateInsert([
'userid' => $user->userid,
'mac' => $item['mac'],
], [
'remark' => $item['remark'],
]);
if ($row) {
$ids[] = $row->id;
$list[] = $row;
}
}
UserCheckin::whereUserid($user->userid)->whereNotIn('id', $ids)->delete();
//
return Base::retSuccess('success', $list);
});
}
}

View File

@ -54,6 +54,12 @@ class VerifyCsrfToken extends Middleware
// 保存汇报
'api/report/store/',
// 签到设置
'api/users/checkin/save/',
// 签到上报
'api/public/checkin/report/',
// 发布桌面端
'desktop/publish/',
];

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\UserCheckin
*
* @property int $id
* @property int|null $userid 会员id
* @property string|null $mac MAC地址
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin query()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereMac($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereRemark($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckin whereUserid($value)
* @mixin \Eloquent
*/
class UserCheckin extends AbstractModel
{
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Module\Base;
/**
* App\Models\UserCheckinRecord
*
* @property int $id
* @property int|null $userid 会员id
* @property string|null $mac MAC地址
* @property int|null $time 上报的时间戳
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord query()
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereMac($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereTime($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUserid($value)
* @mixin \Eloquent
*/
class UserCheckinRecord extends AbstractModel
{
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCheckinsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_checkins', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员id');
$table->string('mac', 100)->nullable()->default('')->comment('MAC地址');
$table->string('remark', 100)->nullable()->default('')->comment('备注');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_checkins');
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserCheckinRecordsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_checkin_records', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('userid')->nullable()->default(0)->comment('会员id');
$table->string('mac', 100)->nullable()->default('')->comment('MAC地址');
$table->bigInteger('time')->nullable()->default(0)->comment('上报的时间戳');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_checkin_records');
}
}

View File

@ -0,0 +1,113 @@
<template>
<div class="setting-item submit">
<Form ref="formData" label-width="auto" @submit.native.prevent>
<Alert show-icon style="margin-bottom:18px">
{{$L('手机连接上指定WIFI后自动签到。')}}
</Alert>
<Row class="setting-template">
<Col span="12">{{$L('MAC地址')}}</Col>
<Col span="12">{{$L('备注')}}</Col>
</Row>
<Row v-for="(item, key) in formData" :key="key" class="setting-template">
<Col span="12">
<Input
v-model="item.mac"
:maxlength="20"
:placeholder="$L('请输入MAC地址')"
clearable
@on-clear="delDatum(key)"/>
</Col>
<Col span="12">
<Input v-model="item.remark" :maxlength="100" :placeholder="$L('备注')"/>
</Col>
</Row>
<Button type="default" icon="md-add" @click="addDatum">{{$L('添加地址')}}</Button>
</Form>
<div class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
<Button :loading="loadIng > 0" @click="resetForm" style="margin-left: 8px">{{$L('重置')}}</Button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
loadIng: 0,
formData: [],
nullDatum: {
'mac': '',
'remark': '',
},
}
},
mounted() {
this.initData();
},
methods: {
initData() {
this.loadIng++;
this.$store.dispatch("call", {
url: 'users/checkin/get',
}).then(({data}) => {
this.formData = data.length > 0 ? data : [$A.cloneJSON(this.nullDatum)];
this.formData_bak = $A.cloneJSON(this.formData);
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
},
submitForm() {
this.$refs.formData.validate((valid) => {
if (valid) {
const list = this.formData
.filter(item => /^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/.test(item.mac.trim()))
.map(item => {
return {
mac: item.mac.trim(),
remark: item.remark.trim()
}
});
//
this.loadIng++;
this.$store.dispatch("call", {
url: 'users/checkin/save',
data: {list},
method: 'post',
}).then(({data}) => {
this.formData = data;
this.formData_bak = $A.cloneJSON(this.formData);
$A.messageSuccess('修改成功');
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadIng--;
});
}
})
},
resetForm() {
this.formData = $A.cloneJSON(this.formData_bak);
},
addDatum() {
this.formData.push($A.cloneJSON(this.nullDatum));
},
delDatum(key) {
this.formData.splice(key, 1);
if (this.formData.length === 0) {
this.addDatum();
}
},
}
}
</script>

View File

@ -0,0 +1,195 @@
<template>
<div class="setting-component-item">
<Form ref="formData" :model="formData" :rules="ruleData" label-width="auto" @submit.native.prevent>
<div class="block-setting-box">
<h3>{{ $L('WIFI签到') }}</h3>
<FormItem :label="$L('功能开启')" prop="wifi">
<RadioGroup v-model="formData.wifi">
<Radio label="open">{{ $L('开启') }}</Radio>
<Radio label="close">{{ $L('关闭') }}</Radio>
</RadioGroup>
<div class="export-data" @click="exportShow=true">{{$L('导出签到数据')}}</div>
</FormItem>
<template v-if="formData.wifi === 'open'">
<FormItem :label="$L('功能说明')" prop="explain">
<p>1. {{$L('此功能仅支持手机客户端使用。')}}</p>
<p>2. {{$L('手机连接上指定路由器WIFI后自动签到。')}}{{$L('(注:理论上不限制连接方式)')}}</p>
<p>3. {{$L('签到延迟时长为±1分钟。')}}</p>
</FormItem>
<FormItem :label="$L('安装说明')" prop="install">
<p>1. {{$L('此功能仅支持Openwrt系统的路由器。')}}</p>
<p>2. {{$L('关闭签到功能再开启需要重新安装。')}}</p>
<p>3. {{$L('进入路由器终端执行以下命令即可完成安装:')}}</p>
<Input ref="cmd" @on-focus="clickCmd" style="margin-top:6px" type="textarea" readonly :value="formData.cmd"/>
</FormItem>
</template>
</div>
</Form>
<div class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{ $L('提交') }}</Button>
<Button :loading="loadIng > 0" @click="resetForm" style="margin-left: 8px">{{ $L('重置') }}</Button>
</div>
<!--导出签到数据-->
<Modal
v-model="exportShow"
:title="$L('导出签到数据')"
:mask-closable="false">
<Form ref="export" :model="exportData" label-width="auto" @submit.native.prevent>
<FormItem :label="$L('导出成员')">
<UserInput v-model="exportData.userid" :multiple-max="20" :placeholder="$L('请选择成员')"/>
</FormItem>
<FormItem :label="$L('签到日期')">
<DatePicker
v-model="exportData.date"
type="daterange"
format="yyyy/MM/dd"
style="width:100%"
:placeholder="$L('请选择签到日期')"/>
<div class="page-setting-checkin-export-common">
{{$L('快捷选择')}}:
<em @click="exportData.date=dateShortcuts('prev')">上个月</em>
<em @click="exportData.date=dateShortcuts('this')">这个月</em>
</div>
</FormItem>
<FormItem :label="$L('签到时间')">
<TimePicker
v-model="exportData.time"
type="timerange"
format="HH:mm"
style="width:100%"
:placeholder="$L('请选择签到时间')"/>
<div class="page-setting-checkin-export-common">
{{$L('快捷选择')}}:
<em @click="exportData.time=['8:30', '18:00']">8:30-18:00</em>
<em @click="exportData.time=['9:00', '18:00']">9:00-18:00</em>
<em @click="exportData.time=['9:30', '18:00']">9:30-18:30</em>
</div>
</FormItem>
</Form>
<div slot="footer" class="adaption">
<Button type="default" @click="exportShow=false">{{$L('取消')}}</Button>
<Button type="primary" :loading="exportLoadIng > 0" @click="onExport">{{$L('导出')}}</Button>
</div>
</Modal>
</div>
</template>
<script>
import UserInput from "../../../../components/UserInput";
export default {
name: "SystemCheckin",
components: {UserInput},
data() {
return {
loadIng: 0,
formData: {
wifi: '',
cmd: '',
},
ruleData: {},
dateOptions: {
shortcuts: [
{
text: this.$L('上个月'),
value() {
return [$A.getData('上个月', true), this.lastSecond($A.getData('上个月结束', true))];
}
},
{
text: this.$L('这个月'),
value() {
return [$A.getData('本周', true), this.lastSecond($A.getData('本月结束', true))];
}
}
]
},
exportShow: false,
exportLoadIng: 0,
exportData: {
userid: [],
date: [],
time: [],
},
}
},
mounted() {
this.systemSetting();
},
methods: {
submitForm() {
this.$refs.formData.validate((valid) => {
if (valid) {
this.systemSetting(true);
}
})
},
resetForm() {
this.formData = $A.cloneJSON(this.formDatum_bak);
},
systemSetting(save) {
this.loadIng++;
this.$store.dispatch("call", {
url: 'system/setting/checkin?type=' + (save ? 'save' : 'all'),
data: this.formData,
}).then(({data}) => {
if (save) {
$A.messageSuccess('修改成功');
}
this.formData = data;
this.formDatum_bak = $A.cloneJSON(this.formData);
}).catch(({msg}) => {
if (save) {
$A.modalError(msg);
}
}).finally(_ => {
this.loadIng--;
});
},
dateShortcuts(act) {
const lastSecond = (e) => {
return $A.Date($A.formatDate("Y-m-d 23:59:29", Math.round(e / 1000)))
};
if (act === 'prev') {
return [$A.getData('上个月', true), lastSecond($A.getData('上个月结束', true))];
} else if (act === 'this') {
return [$A.getData('本月', true), lastSecond($A.getData('本月结束', true))]
}
},
clickCmd() {
this.$nextTick(_ => {
this.$refs.cmd.focus({cursor:'all'});
});
},
onExport() {
if (this.exportLoadIng > 0) {
return;
}
this.exportLoadIng++;
this.$store.dispatch("call", {
url: 'system/checkin/export',
data: this.exportData,
}).then(({data}) => {
this.exportShow = false;
this.$store.dispatch('downUrl', {
url: data.url
});
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.exportLoadIng--;
});
}
}
}
</script>

View File

@ -71,6 +71,7 @@ export default {
menu() {
const menu = [
{path: 'personal', name: '个人设置'},
{path: 'checkin', name: '签到设置', desc: ' (Beta)'},
{path: 'language', name: '语言设置'},
{path: 'theme', name: '主题设置'},
{path: 'password', name: '密码设置'},
@ -114,7 +115,7 @@ export default {
let name = '';
menu.some((item) => {
if (routeName === `manage-setting-${item.path}`) {
name = item.name;
name = `${item.name}${item.desc||''}`;
return true;
}
})

View File

@ -13,6 +13,9 @@
<TabPane :label="$L('会议功能')" name="meeting">
<SystemMeeting/>
</TabPane>
<TabPane :label="$L('签到功能')" name="checkin">
<SystemCheckin/>
</TabPane>
<TabPane :label="$L('邮件设置')" name="emailSetting">
<SystemEmailSetting/>
</TabPane>
@ -30,9 +33,11 @@ import SystemColumnTemplate from "./components/SystemColumnTemplate";
import SystemEmailSetting from "./components/SystemEmailSetting";
import SystemAppPush from "./components/SystemAppPush";
import SystemMeeting from "./components/SystemMeeting";
import SystemCheckin from "./components/SystemCheckin";
export default {
components: {
SystemCheckin,
SystemMeeting,
SystemAppPush, SystemColumnTemplate, SystemTaskPriority, SystemSetting, SystemEmailSetting},
data() {

View File

@ -39,6 +39,11 @@ export default [
path: 'personal',
component: () => import('./pages/manage/setting/personal.vue'),
},
{
name: 'manage-setting-checkin',
path: 'checkin',
component: () => import('./pages/manage/setting/checkin.vue'),
},
{
name: 'manage-setting-language',
path: 'language',

View File

@ -243,6 +243,13 @@
.block-setting-placeholder {
height: 8px;
}
.export-data {
cursor: pointer;
color: #2b85e4;
&:hover {
text-decoration: underline;
}
}
}
}
}
@ -297,6 +304,20 @@
}
}
.page-setting-checkin-export-common {
display: flex;
align-items: center;
> em {
cursor: pointer;
color: #2b85e4;
margin-left: 8px;
font-style: normal;
&:hover {
text-decoration: underline;
}
}
}
@media (max-width: 768px) {
.page-setting {
.setting-head {