Merge branch 'master' of github.com:kuaifan/dootask into develop

# Conflicts:
#	app/Http/Controllers/Api/DialogController.php
#	app/Http/Controllers/Api/ProjectController.php
#	electron/package.json
#	package.json
#	public/css/app.css
#	public/docs/assets/main.bundle.js
#	public/js/app.js
#	public/js/build/146.js.LICENSE.txt
#	public/js/build/161.js
#	public/js/build/199.js
#	public/js/build/199.js.LICENSE.txt
#	public/js/build/218.js
#	public/js/build/218.js.LICENSE.txt
#	public/js/build/244.js
#	public/js/build/244.js.LICENSE.txt
#	public/js/build/309.js
#	public/js/build/423.js
#	public/js/build/43.js
#	public/js/build/693.js
#	public/js/build/717.js
#	public/js/build/717.js.LICENSE.txt
#	public/js/build/79.js.LICENSE.txt
#	resources/assets/js/pages/manage/components/TaskAdd.vue
This commit is contained in:
kuaifan 2022-01-28 14:39:30 +08:00
commit 35bd038802
59 changed files with 1083 additions and 406 deletions

View File

@ -331,7 +331,61 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/download 09. 文件下载
* @api {get} api/dialog/msg/detail 09. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup dialog
* @apiName msg__detail
*
* @apiParam {Number} msg_id 消息ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function msg__detail()
{
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
//
$dialogMsg = WebSocketDialogMsg::whereId($msg_id)->first();
if (empty($dialogMsg)) {
return Base::retError("文件不存在");
}
$data = $dialogMsg->toArray();
//
if ($data['type'] == 'file') {
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$msg = Base::json2array($dialogMsg->getRawOriginal('msg'));
$filePath = public_path($msg['path']);
if (in_array($msg['ext'], $codeExt) && $msg['size'] < 2 * 1024 * 1024) {
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($msg['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($msg['ext'], $localExt)) {
$url = Base::fillUrl($msg['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $msg['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
}
//
return Base::retSuccess("success", $data);
}
/**
* @api {get} api/dialog/msg/download 10. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\Api;
use App\Exceptions\ApiException;
use App\Models\AbstractModel;
use App\Models\File;
@ -10,10 +9,12 @@ use App\Models\FileContent;
use App\Models\FileLink;
use App\Models\FileUser;
use App\Models\User;
use App\Models\WebSocketDialogMsg;
use App\Module\Base;
use App\Module\Ihttp;
use Illuminate\Support\Facades\DB;
use Request;
use Response;
/**
* @apiDefine file
@ -380,11 +381,11 @@ class FileController extends AbstractController
* @apiName content
*
* @apiParam {Number|String} id
* - Number 文件ID需要登录
* - String 链接码(不需要登录,用于预览)
* - Number: 文件ID需要登录
* - String: 链接码(不需要登录,用于预览)
* @apiParam {String} down 直接下载
* - no: 浏览(默认)
* - yes: 下载
* - yes: 下载office文件直接下载
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)

View File

@ -1084,7 +1084,61 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/filedown 23. 下载任务文件
* @api {get} api/project/task/filedetail 23. 获取任务文件详情
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
* @apiGroup project
* @apiName task__filedetail
*
* @apiParam {Number} file_id 文件ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function task__filedetail()
{
User::auth();
//
$file_id = intval(Request::input('file_id'));
//
$file = ProjectTaskFile::find($file_id);
if (empty($file)) {
return Base::retError("文件不存在");
}
$data = $file->toArray();
$data['path'] = $file->getRawOriginal('path');
//
ProjectTask::userTask($file->task_id, true, true);
//
$codeExt = ['txt'];
$officeExt = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
$localExt = ['jpg', 'jpeg', 'png', 'gif'];
$filePath = public_path($data['path']);
if (in_array($data['ext'], $codeExt) && $data['size'] < 2 * 1024 * 1024) {
// 文本预览限制2M内的文件
$data['content'] = file_get_contents($filePath);
$data['file_mode'] = 1;
} elseif (in_array($data['ext'], $officeExt)) {
// office预览
$data['file_mode'] = 2;
} else {
// 其他预览
if (in_array($data['ext'], $localExt)) {
$url = Base::fillUrl($data['path']);
} else {
$url = 'http://' . env('APP_IPPR') . '.3/' . $data['path'];
}
$data['url'] = base64_encode($url);
$data['file_mode'] = 3;
}
//
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/project/task/filedown 24. 下载任务文件
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1118,7 +1172,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/add 24. 添加任务
* @api {post} api/project/task/add 25. 添加任务
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1189,7 +1243,7 @@ class ProjectController extends AbstractController
}
/**
* @api {get} api/project/task/addsub 25. 添加子任务
* @api {get} api/project/task/addsub 26. 添加子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1229,7 +1283,7 @@ class ProjectController extends AbstractController
}
/**
* @api {post} api/project/task/update 26. 修改任务、子任务
* @api {post} api/project/task/update 27. 修改任务、子任务
*
* @apiDescription 需要token身份项目、任务负责人
* @apiVersion 1.0.0
@ -1679,6 +1733,9 @@ class ProjectController extends AbstractController
if ($usertype == 'replace' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
}
if ($usertype == 'merge' && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
}
if ($userlimit && empty($userids)) {
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
}

View File

@ -30,9 +30,6 @@ class VerifyCsrfToken extends Middleware
// 修改任务
'api/project/task/update/',
// 上传任务问题
'api/project/task/upload/',
// 聊天发文件
'api/dialog/msg/sendfile/',

View File

@ -342,11 +342,13 @@ class ProjectTask extends AbstractModel
$content = $data['content'];
$times = $data['times'];
$owner = $data['owner'];
$add_assist = intval($data['add_assist']);
$subtasks = $data['subtasks'];
$p_level = intval($data['p_level']);
$p_name = $data['p_name'];
$p_color = $data['p_color'];
$top = intval($data['top']);
$userid = User::userid();
//
if (ProjectTask::whereProjectId($project_id)
->whereNull('project_tasks.complete_at')
@ -411,8 +413,13 @@ class ProjectTask extends AbstractModel
$tmpArray[] = $uid;
}
$owner = $tmpArray;
// 协助人员
$assist = [];
if (!in_array($userid, $owner) && $add_assist) {
$assist = [$userid];
}
// 创建人
$task->userid = User::userid();
$task->userid = $userid;
// 排序位置
if ($top) {
$task->sort = intval(self::whereColumnId($task->column_id)->orderBy('sort')->value('sort')) - 1;
@ -434,7 +441,7 @@ class ProjectTask extends AbstractModel
}
}
//
return AbstractModel::transaction(function() use ($times, $subtasks, $content, $owner, $task) {
return AbstractModel::transaction(function() use ($assist, $times, $subtasks, $content, $owner, $task) {
$task->save();
$owner = array_values(array_unique($owner));
foreach ($owner as $uid) {
@ -446,6 +453,16 @@ class ProjectTask extends AbstractModel
'owner' => 1,
])->save();
}
$assist = array_values(array_unique(array_diff($assist, $owner)));
foreach ($assist as $uid) {
ProjectTaskUser::createInstance([
'project_id' => $task->project_id,
'task_id' => $task->id,
'task_pid' => $task->parent_id ?: $task->id,
'userid' => $uid,
'owner' => 0,
])->save();
}
if ($content) {
ProjectTaskContent::createInstance([
'project_id' => $task->project_id,
@ -541,13 +558,17 @@ class ProjectTask extends AbstractModel
if ($newFlowItem->userids) {
// 判断自动添加负责人
$flowData['owner'] = $data['owner'] = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
if ($newFlowItem->usertype == "replace") {
// 流转模式
if (in_array($newFlowItem->usertype, ["replace", "merge"])) {
// 流转模式、剔除模式
if ($this->parent_id === 0) {
$flowData['assist'] = $data['assist'] = $this->taskUser->where('owner', 0)->pluck('userid')->toArray();
$data['assist'] = array_merge($data['assist'], $data['owner']);
}
$data['owner'] = $newFlowItem->userids;
// 判断剔除模式:保留操作状态的人员
if ($newFlowItem->usertype == "merge") {
$data['owner'][] = User::userid();
}
} else {
// 添加模式
$data['owner'] = array_merge($data['owner'], $newFlowItem->userids);

View File

@ -204,7 +204,19 @@ class WebSocketService implements WebSocketHandlerInterface
*/
private function deleteUser($fd)
{
WebSocket::whereFd($fd)->delete();
$array = [];
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
/** @var WebSocket $item */
foreach ($list as $item) {
$item->delete();
if ($item->path && str_starts_with($item->path, "file/content/")) {
$array[$item->path] = $item->path;
}
}
});
foreach ($array as $path) {
$this->pushPath($path);
}
}
/**

View File

@ -96,7 +96,7 @@ services:
fileview:
container_name: "dootask-fileview-${APP_ID}"
image: "kuaifan/fileview:4.1.0-SNAPSHOT"
image: "kuaifan/fileview:4.1.0-SNAPSHOT-RC3"
environment:
TZ: "Asia/Shanghai"
KK_CONTEXT_PATH: "/fileview"

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.7.41",
"version": "0.7.66",
"description": "DooTask is task management system.",
"main": "main.js",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.7.41",
"version": "0.7.66",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",
@ -64,7 +64,7 @@
"stylus-loader": "^6.2.0",
"tinymce": "^5.10.2",
"tui-calendar-hi": "^1.15.1-5",
"view-design-hi": "^4.7.0-8",
"view-design-hi": "^4.7.0-11",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.3",
"vue-emoji-picker": "^1.0.3",

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

1
public/js/build/159.js vendored Normal file
View File

@ -0,0 +1 @@
"use strict";(self.webpackChunkDooTask=self.webpackChunkDooTask||[]).push([[159],{26071:(e,t,i)=>{i.d(t,{Z:()=>r});var n=i(1519),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,".component-only-office[data-v-c9bf06c2]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.component-only-office .placeholder[data-v-c9bf06c2]{flex:1;height:100%;width:100%}.component-only-office .office-loading[data-v-c9bf06c2]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:2}",""]);const r=o},36159:(e,t,i)=>{i.r(t),i.d(t,{default:()=>u});var n=i(20629);function o(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function r(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?o(Object(i),!0).forEach((function(t){s(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function s(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const l={name:"OnlyOffice",props:{id:{type:String,default:function(){return"office_"+Math.round(1e4*Math.random())}},code:{type:String,default:""},value:{type:[Object,Array],default:function(){return{}}},readOnly:{type:Boolean,default:!1}},data:function(){return{loadIng:0,docEditor:null}},mounted:function(){},beforeDestroy:function(){null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null)},computed:r(r({},(0,n.rn)(["userToken","userInfo","themeIsDark"])),{},{fileType:function(){return this.getType(this.value.type)},fileName:function(){return this.value.name}}),watch:{"value.id":{handler:function(e){var t=this;e&&(this.loadIng++,$A.loadScript($A.apiUrl("../office/web-apps/apps/api/documents/api.js"),(function(e){t.loadIng--,null!==e?$A.modalAlert("组件加载失败!"):t.loadFile()})))},immediate:!0}},methods:{getType:function(e){switch(e){case"word":return"docx";case"excel":return"xlsx";case"ppt":return"pptx"}return e},loadFile:function(){var e=this;null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null);var t="zh";switch(this.getLanguage()){case"CN":case"TC":t="zh";break;default:t="en"}var i=this.code||this.value.id,n=$A.strExists(this.fileName,".")?this.fileName:this.fileName+"."+this.fileType,o={document:{fileType:this.fileType,key:this.fileType+"-"+i,title:n,url:"http://nginx/api/file/content/?id="+i+"&token="+this.userToken},editorConfig:{mode:"edit",lang:t,user:{id:this.userInfo.userid,name:this.userInfo.nickname},customization:{uiTheme:this.themeIsDark?"theme-dark":"theme-classic-light"},callbackUrl:"http://nginx/api/file/content/office?id="+i+"&token="+this.userToken}};if(/\/hideenOfficeTitle\//.test(window.navigator.userAgent)&&(o.document.title=" "),$A.leftExists(i,"msgFile_")?o.document.url="http://nginx/api/dialog/msg/download/?msg_id="+$A.leftDelete(i,"msgFile_")+"&token="+this.userToken:$A.leftExists(i,"taskFile_")&&(o.document.url="http://nginx/api/project/task/filedown/?file_id="+$A.leftDelete(i,"taskFile_")+"&token="+this.userToken),this.readOnly&&(o.editorConfig.mode="view",o.editorConfig.callbackUrl=null,!o.editorConfig.user.id)){var r=$A.getStorageInt("viewer");r||(r=$A.randNum(1e3,99999),$A.setStorage("viewer",r)),o.editorConfig.user.id="viewer_"+r,o.editorConfig.user.name="Viewer_"+r}this.$nextTick((function(){e.docEditor=new DocsAPI.DocEditor(e.id,o)}))}}};var a=i(93379),c=i.n(a),d=i(26071),f={insert:"head",singleton:!1};c()(d.Z,f);d.Z.locals;const u=(0,i(51900).Z)(l,(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{staticClass:"component-only-office"},[i("div",{staticClass:"placeholder",attrs:{id:this.id}}),e._v(" "),e.loadIng>0?i("div",{staticClass:"office-loading"},[i("Loading")],1):e._e()])}),[],!1,null,"c9bf06c2",null).exports}}]);

2
public/js/build/196.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
"use strict";(self.webpackChunkDooTask=self.webpackChunkDooTask||[]).push([[214],{96099:(e,t,i)=>{i.d(t,{Z:()=>r});var n=i(1519),o=i.n(n)()((function(e){return e[1]}));o.push([e.id,".component-only-office[data-v-ba382ddc]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.component-only-office .placeholder[data-v-ba382ddc]{flex:1;height:100%;width:100%}.component-only-office .office-loading[data-v-ba382ddc]{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:2}",""]);const r=o},29214:(e,t,i)=>{i.r(t),i.d(t,{default:()=>f});var n=i(20629);function o(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function r(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?o(Object(i),!0).forEach((function(t){a(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):o(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}const c={name:"OnlyOffice",props:{id:{type:String,default:function(){return"office_"+Math.round(1e4*Math.random())}},code:{type:String,default:""},value:{type:[Object,Array],default:function(){return{}}},readOnly:{type:Boolean,default:!1}},data:function(){return{loadIng:0,docEditor:null}},mounted:function(){},beforeDestroy:function(){null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null)},computed:r(r({},(0,n.rn)(["userToken","userInfo","themeIsDark"])),{},{fileType:function(){return this.getType(this.value.type)},fileName:function(){return this.value.name}}),watch:{"value.id":{handler:function(e){var t=this;e&&(this.loadIng++,$A.loadScript($A.apiUrl("../office/web-apps/apps/api/documents/api.js"),(function(e){t.loadIng--,null!==e?$A.modalAlert("组件加载失败!"):t.loadFile()})))},immediate:!0}},methods:{getType:function(e){switch(e){case"word":return"docx";case"excel":return"xlsx";case"ppt":return"pptx"}return""},loadFile:function(){var e=this;null!==this.docEditor&&(this.docEditor.destroyEditor(),this.docEditor=null);var t="zh";switch(this.getLanguage()){case"CN":case"TC":t="zh";break;default:t="en"}var i=this.code||this.value.id,n={document:{fileType:this.fileType,key:this.fileType+"-"+i,title:this.fileName+"."+this.fileType,url:"http://nginx/api/file/content/?id="+i+"&token="+this.userToken},editorConfig:{mode:"edit",lang:t,user:{id:this.userInfo.userid,name:this.userInfo.nickname},customization:{uiTheme:this.themeIsDark?"theme-dark":"theme-classic-light"},callbackUrl:"http://nginx/api/file/content/office?id="+i+"&token="+this.userToken}};if(this.readOnly&&(n.editorConfig.mode="view",n.editorConfig.callbackUrl=null,!n.editorConfig.user.id)){var o=$A.getStorageInt("viewer");o||(o=$A.randNum(1e3,99999),$A.setStorage("viewer",o)),n.editorConfig.user.id="viewer_"+o,n.editorConfig.user.name="Viewer_"+o}this.$nextTick((function(){e.docEditor=new DocsAPI.DocEditor(e.id,n)}))}}};var l=i(93379),s=i.n(l),d=i(96099),u={insert:"head",singleton:!1};s()(d.Z,u);d.Z.locals;const f=(0,i(51900).Z)(c,(function(){var e=this,t=e.$createElement,i=e._self._c||t;return i("div",{staticClass:"component-only-office"},[i("div",{staticClass:"placeholder",attrs:{id:this.id}}),e._v(" "),e.loadIng>0?i("div",{staticClass:"office-loading"},[i("Loading")],1):e._e()])}),[],!1,null,"ba382ddc",null).exports}}]);

1
public/js/build/264.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/js/build/428.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/build/714.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,11 @@ import Language from './language/index'
import store from './store/index'
Vue.use(Vuex);
Vue.use(ViewUI);
Vue.use(ViewUI, {
modal: {
checkEscClose: true
}
});
Vue.use(VueRouter);
Vue.use(Language);

View File

@ -63,6 +63,7 @@
type: Boolean,
default: false
},
beforeClose: Function
},
data() {
@ -136,12 +137,25 @@
},
methods: {
mask () {
mask() {
if (this.maskClosable) {
this.close()
}
},
close() {
if (!this.beforeClose) {
return this.handleClose();
}
const before = this.beforeClose();
if (before && before.then) {
before.then(this.handleClose);
} else {
this.handleClose();
}
},
handleClose () {
this.$emit("input", false)
},
escClose(e) {

View File

@ -123,7 +123,7 @@ export default {
case 'ppt':
return 'pptx'
}
return '';
return type;
},
loadFile() {
@ -144,11 +144,12 @@ export default {
}
//
let fileKey = this.code || this.value.id;
let fileName = $A.strExists(this.fileName, '.') ? this.fileName : (this.fileName + '.' + this.fileType);
const config = {
"document": {
"fileType": this.fileType,
"key": this.fileType + '-' + fileKey,
"title": this.fileName + '.' + this.fileType,
"title": fileName,
"url": 'http://nginx/api/file/content/?id=' + fileKey + '&token=' + this.userToken,
},
"editorConfig": {
@ -164,6 +165,14 @@ export default {
"callbackUrl": 'http://nginx/api/file/content/office?id=' + fileKey + '&token=' + this.userToken,
}
};
if (/\/hideenOfficeTitle\//.test(window.navigator.userAgent)) {
config.document.title = " ";
}
if ($A.leftExists(fileKey, "msgFile_")) {
config.document.url = 'http://nginx/api/dialog/msg/download/?msg_id=' + $A.leftDelete(fileKey, "msgFile_") + '&token=' + this.userToken;
} else if ($A.leftExists(fileKey, "taskFile_")) {
config.document.url = 'http://nginx/api/project/task/filedown/?file_id=' + $A.leftDelete(fileKey, "taskFile_") + '&token=' + this.userToken;
}
if (this.readOnly) {
config.editorConfig.mode = "view";
config.editorConfig.callbackUrl = null;

View File

@ -55,7 +55,7 @@ export default {
},
autoEdit(val) {
if (val === true) {
this.onEdit();
setTimeout(this.onEdit, 0)
}
}
},

View File

@ -1,22 +1,23 @@
<template>
<div v-if="ready" :class="['common-user', maxHiddenClass]">
<div :class="['common-user', maxHiddenClass]">
<Select
v-model="values"
ref="select"
v-model="selects"
:transfer="transfer"
:remote-method="searchUser"
:placeholder="placeholder"
:size="size"
:loading="loading"
:loading="loadIng > 0"
:loading-text="$L('加载中...')"
:default-label="value"
:default-event-object="true"
:multipleMax="multipleMax"
:multipleUncancelable="uncancelable"
:multiple-max="multipleMax"
:multiple-uncancelable="uncancelable"
:remote-method="searchUser"
@on-query-change="searchUser"
@on-open-change="openChange"
multiple
filterable
transfer-class-name="common-user-transfer"
@on-open-change="openChange"
@on-set-default-options="setDefaultOptions">
transfer-class-name="common-user-transfer">
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '')}}</div>
<slot name="option-prepend"></slot>
<Option
@ -33,7 +34,7 @@
</div>
</Option>
</Select>
<div v-if="!initialized" class="common-user-loading"><Loading/></div>
<div v-if="loadIng > 0" class="common-user-loading"><Loading/></div>
</div>
</template>
@ -87,36 +88,23 @@
},
data() {
return {
ready: false,
initialized: false,
loading: false,
openLoad: false,
values: [],
loadIng: 0,
selects: [],
list: [],
options: [],
searchKey: null,
searchHistory: [],
subscribe: null,
}
},
mounted() {
if ($A.isArray(this.value)) {
this.values = $A.cloneJSON(this.value);
} else {
this.$emit('input', this.value ? [this.value] : []);
}
this.$nextTick(() => {
this.ready = true;
});
this.subscribe = Store.subscribe('cacheUserActive', (data) => {
let index = this.list.findIndex(({userid}) => userid == data.userid);
if (index > -1) {
this.initialized = true;
this.$set(this.list, index, Object.assign({}, this.list[index], data));
}
let option = this.options.find(({value}) => value == data.userid);
if (option) {
this.$set(option, 'label', data.nickname)
this.$set(option, 'avatar', data.userimg)
this.handleSelectData();
}
});
},
@ -128,9 +116,9 @@
},
computed: {
maxHiddenClass() {
const {multipleMax, maxHiddenInput, values} = this;
const {multipleMax, maxHiddenInput, selects} = this;
if (multipleMax && maxHiddenInput) {
if (values.length >= multipleMax) {
if (selects.length >= multipleMax) {
return 'hidden-input'
}
}
@ -138,61 +126,62 @@
}
},
watch: {
value(val) {
this.values = val;
value: {
handler() {
this.valueChange()
},
immediate: true,
},
values(val) {
selects(val) {
this.$emit('input', val);
}
},
methods: {
openChange(show) {
if (show && !this.openLoad) {
this.openLoad = true;
if (this.list.length == this.values.length || this.list.length <= 1) {
this.$nextTick(this.searchUser);
searchUser(key) {
if (typeof key !== "string") key = "";
if (key == this.searchKey) return;
this.searchKey = key;
//
const history = this.searchHistory.find(item => item.key == key);
if (history) this.list = history.data;
//
if (!history) this.loadIng++;
setTimeout(() => {
if (this.searchKey != key) {
if (!history) this.loadIng--;
return;
}
}
},
setDefaultOptions(options) {
this.options = options;
options.forEach(({value, label}) => {
this.list.push({
userid: value,
nickname: label,
});
this.$store.dispatch("getUserBasic", {userid: value});
});
if (this.list.length == 0) {
this.initialized = true;
}
},
searchUser(query) {
if (query !== '') {
this.loading = true;
this.$store.dispatch("call", {
url: 'users/search',
data: {
keys: {
key: query || '',
key,
project_id: this.projectId,
no_project_id: this.noProjectId,
},
take: 30
},
}).then(({data}) => {
this.loading = false;
if (!history) this.loadIng--;
this.list = data;
//
const index = this.searchHistory.findIndex(item => item.key == key);
const tmpData = {
key,
data,
time: $A.Time()
};
if (index > -1) {
this.searchHistory.splice(index, 1, tmpData)
} else {
this.searchHistory.push(tmpData)
}
}).catch(({msg}) => {
this.loading = false;
if (!history) this.loadIng--;
this.list = [];
$A.messageWarning(msg);
});
} else {
this.list = [];
}
}, this.searchHistory.length > 0 ? 300 : 0)
},
isDisabled(userid) {
@ -200,6 +189,48 @@
return false;
}
return this.disabledChoice.includes(userid)
},
openChange(show) {
if (show) {
this.$nextTick(this.searchUser);
}
},
valueChange() {
if (this.selects == this.value) {
return
}
if ($A.isArray(this.value)) {
this.selects = $A.cloneJSON(this.value);
} else if (this.value) {
this.selects = [this.value]
} else {
this.selects = [];
}
this.selects.some(userid => {
if (!this.list.find(item => item.userid == userid)) {
this.list.push({userid, nickname: userid});
this.$store.dispatch("getUserBasic", {userid});
}
})
},
handleSelectData() {
this.__handleSelectTimeout && clearTimeout(this.__handleSelectTimeout);
this.__handleSelectTimeout = setTimeout(() => {
if (!this.$refs.select) {
return;
}
const list = this.$refs.select.getValue();
list && list.some(option => {
const data = this.list.find(({userid}) => userid == option.value)
if (data) {
this.$set(option, 'label', data.nickname)
this.$set(option, 'avatar', data.userimg)
}
})
}, 100);
}
}
};

View File

@ -214,6 +214,7 @@
@on-click="show768Menu=!show768Menu">
<div class="manage-mini-menu">
<Icon :type="show768Menu ? 'md-close' : 'md-menu'" />
<Badge :count="unreadTotal"/>
</div>
</DragBallComponent>
</div>
@ -300,10 +301,6 @@ export default {
//
document.addEventListener('keydown', this.shortcutEvent);
window.addEventListener('resize', this.innerHeightListener);
//
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', 0);
}
},
beforeDestroy() {
@ -357,6 +354,10 @@ export default {
return this.dashboardTask.today.length + this.dashboardTask.overdue.length
},
unreadTotal() {
return this.msgAllUnread + this.dashboardTotal + this.reportUnreadNumber;
},
currentLanguage() {
return this.languageList[this.languageType] || 'Language'
},
@ -421,24 +422,6 @@ export default {
}
},
msgAllUnread() {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', this.msgAllUnread + this.dashboardTotal + this.reportUnreadNumber);
}
},
dashboardTotal() {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', this.msgAllUnread + this.dashboardTotal + this.reportUnreadNumber);
}
},
reportUnreadNumber() {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', this.msgAllUnread + this.dashboardTotal + this.reportUnreadNumber);
}
},
projectKeyValue(val) {
if (val == '') {
return;
@ -468,6 +451,15 @@ export default {
}, 5000)
},
unreadTotal: {
handler(num) {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('setDockBadge', num);
}
},
immediate: true
},
wsMsg: {
handler(info) {
const {type, action} = info;

View File

@ -1,16 +1,18 @@
<template>
<div class="dialog-view" :data-id="msgData.id">
<div :class="`dialog-view ${msgData.type}`" :data-id="msgData.id">
<!--文本-->
<div v-if="msgData.type === 'text'" class="dialog-content">
<pre class="no-dark-mode" v-html="textMsg(msgData.msg.text)"></pre>
<pre class="no-dark-mode">{{textMsg(msgData.msg.text)}}</pre>
</div>
<!--等待-->
<div v-else-if="msgData.type === 'loading'" class="dialog-content loading"><Loading/></div>
<!--文件-->
<div v-else-if="msgData.type === 'file'" :class="['dialog-content', msgData.msg.type]">
<div class="dialog-file" @click="downFile">
<img v-if="msgData.msg.type === 'img'" class="file-img" :style="imageStyle(msgData.msg)" :src="msgData.msg.thumb"/>
<div class="dialog-file">
<img v-if="msgData.msg.type === 'img'" class="file-img" :style="imageStyle(msgData.msg)" :src="msgData.msg.thumb" @click="viewFile"/>
<div v-else class="file-box">
<img class="file-thumb" :src="msgData.msg.thumb"/>
<div class="file-info">
@ -19,7 +21,15 @@
</div>
</div>
</div>
<div class="dialog-file-menu">
<div class="file-menu-warp"></div>
<div class="file-menu-icon">
<Icon @click="viewFile" type="md-eye" />
<Icon @click="downFile" type="md-arrow-round-down" />
</div>
</div>
</div>
<!--未知-->
<div v-else class="dialog-content unknown">{{$L("未知的消息类型")}}</div>
@ -138,8 +148,7 @@ export default {
if (!text) {
return ""
}
text = text.trim().replace(/(\n\x20*){3,}/g, "<br/><br/>");
text = text.trim().replace(/\n/g, "<br/>");
text = text.trim().replace(/(\n\x20*){3,}/g, "\n\n");
return text;
},
@ -167,6 +176,25 @@ export default {
return {};
},
viewFile() {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: `${this.msgData.msg.name} (${$A.bytesToSize(this.msgData.msg.size)})`,
titleFixed: true,
name: 'file-msg-' + this.msgData.id,
path: "/single/file/msg/" + this.msgData.id,
force: false,
config: {
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
}
});
} else {
window.open($A.apiUrl(`../single/file/msg/${this.msgData.id}`))
}
},
downFile() {
$A.modalConfirm({
title: '下载文件',

View File

@ -213,12 +213,21 @@ export default {
watch: {
'$route': {
handler (route) {
if (route.query && route.query.sendmsg && this.msgText == '') {
if ($A.isJson(window.__sendDialogMsg) && window.__sendDialogMsg.time > $A.Time()) {
const {msgFile, msgText} = window.__sendDialogMsg;
window.__sendDialogMsg = null;
this.$nextTick(() => {
if ($A.isArray(msgFile) && msgFile.length > 0) {
this.sendFileMsg(msgFile);
} else if (msgText) {
this.sendMsg(msgText);
}
});
}
if (route.query && route.query._) {
let query = $A.cloneJSON(route.query);
delete query.sendmsg;
delete query._;
this.goForward({query}, true);
this.msgText = route.query.sendmsg;
this.$nextTick(this.sendMsg);
}
},
immediate: true
@ -279,24 +288,11 @@ export default {
this.msgText = '';
},
chatKeydown(e) {
if (e.keyCode === 13) {
if (e.shiftKey) {
return;
}
e.preventDefault();
this.sendMsg();
}
},
pasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
sendFileMsg(files) {
if (files.length > 0) {
this.pasteFile = [];
this.pasteItem = [];
postFiles.some(file => {
files.some(file => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ({target}) => {
@ -313,10 +309,28 @@ export default {
}
},
pasteSend() {
this.pasteFile.some(file => {
this.$refs.chatUpload.upload(file)
});
chatKeydown(e) {
if (e.keyCode === 13) {
if (e.shiftKey) {
return;
}
e.preventDefault();
this.sendMsg();
}
},
pasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
this.sendFileMsg(postFiles);
}
},
chatPasteDrag(e, type) {
this.dialogDrag = false;
this.pasteDrag(e, type);
},
chatDragOver(show, e) {
@ -335,29 +349,10 @@ export default {
}
},
chatPasteDrag(e, type) {
this.dialogDrag = false;
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
const postFiles = Array.prototype.slice.call(files);
if (postFiles.length > 0) {
e.preventDefault();
this.pasteFile = [];
this.pasteItem = [];
postFiles.some(file => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ({target}) => {
this.pasteFile.push(file)
this.pasteItem.push({
type: $A.getMiddle(file.type, null, '/'),
name: file.name,
size: file.size,
result: target.result
})
this.pasteShow = true
}
});
}
pasteSend() {
this.pasteFile.some(file => {
this.$refs.chatUpload.upload(file)
});
},
chatFile(type, file) {

View File

@ -112,6 +112,7 @@ export default {
document.addEventListener('keydown', this.keySave);
window.addEventListener('message', this.handleMessage)
},
beforeDestroy() {
document.removeEventListener('keydown', this.keySave);
window.removeEventListener('message', this.handleMessage)
@ -130,6 +131,18 @@ export default {
deep: true,
},
value: {
handler(val) {
if (val) {
this.ready = true;
this.editUser = [this.userId];
} else {
this.fileContent[this.fileId] = this.contentDetail;
}
},
immediate: true,
},
wsMsg: {
handler(info) {
const {type, data} = info;
@ -158,21 +171,15 @@ export default {
deep: true,
},
value: {
handler(val) {
if (val) {
this.ready = true;
this.editUser = [this.userId];
} else {
this.fileContent[this.fileId] = this.contentDetail;
}
},
immediate: true,
wsOpenNum() {
if (this.$isSubElectron) {
this.$store.dispatch("websocketPath", "file/content/" + this.fileId);
}
},
},
computed: {
...mapState(['fileContent', 'wsMsg', 'userId']),
...mapState(['fileContent', 'wsMsg', 'userId', 'wsOpenNum']),
equalContent() {
return this.contentBak == $A.jsonStringify(this.contentDetail);
@ -246,6 +253,10 @@ export default {
this.loadContent--;
this.contentDetail = data.content;
this.updateBak();
//
if (this.$isSubElectron) {
this.$store.dispatch("websocketConnection")
}
}).catch(({msg}) => {
$A.modalError(msg);
this.loadIng--;

View File

@ -35,7 +35,7 @@
</ETooltip>
</li>
<li :class="['project-icon', searchText!='' ? 'active' : '']">
<Tooltip :always="searchAlways" @on-popper-show="searchFocus" theme="light">
<Tooltip :always="searchText!=''" @on-popper-show="searchFocus" theme="light" :rawIndex="10">
<Icon class="menu-icon" type="ios-search" @click="searchFocus" />
<div slot="content">
<Input v-model="searchText" ref="searchInput" :placeholder="$L('名称、描述...')" class="search-input" clearable/>
@ -343,7 +343,7 @@
:mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('项目成员')">
<UserInput v-if="userShow" v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/>
<UserInput v-model="userData.userids" :uncancelable="userData.uncancelable" :multiple-max="100" :placeholder="$L('选择项目成员')"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
@ -405,7 +405,7 @@
:mask-closable="false">
<Form :model="transferData" label-width="auto" @submit.native.prevent>
<FormItem prop="owner_userid" :label="$L('项目负责人')">
<UserInput v-if="transferShow" v-model="transferData.owner_userid" :multiple-max="1" :placeholder="$L('选择项目负责人')"/>
<UserInput v-model="transferData.owner_userid" :multiple-max="1" :placeholder="$L('选择项目负责人')"/>
</FormItem>
</Form>
<div slot="footer" class="adaption">
@ -418,8 +418,9 @@
<DrawerOverlay
v-model="workflowShow"
placement="right"
:beforeClose="workflowBeforeClose"
:size="1280">
<ProjectWorkflow v-if="workflowShow" :project-id="projectId"/>
<ProjectWorkflow ref="workflow" v-if="workflowShow" :project-id="projectId"/>
</DrawerOverlay>
<!--查看项目动态-->
@ -550,17 +551,6 @@ export default {
...mapGetters(['projectData', 'projectParameter', 'transforTasks']),
searchAlways() {
return !(!this.searchText
|| this.settingShow
|| this.userShow
|| this.inviteShow
|| this.transferShow
|| this.workflowShow
|| this.logShow
|| this.archivedTaskShow);
},
userWaitRemove() {
const {userids, useridbak} = this.userData;
if (!userids) {
@ -1230,6 +1220,27 @@ export default {
this.$store.dispatch('toggleProjectParameter', 'completedTask');
},
workflowBeforeClose() {
return new Promise(resolve => {
if (!this.$refs.workflow.existDiff()) {
resolve()
return
}
$A.modalConfirm({
content: '设置尚未保存,是否放弃修改?',
cancelText: '放弃',
okText: '保存',
onCancel: () => {
resolve()
},
onOk: () => {
this.$refs.workflow.saveAll()
resolve()
}
});
})
},
myFilter(task, chackCompleted = true) {
if (task.archived_at) {
return false;

View File

@ -143,14 +143,16 @@
:mask-closable="false">
<Form :model="userData" label-width="auto" @submit.native.prevent>
<FormItem prop="userids" :label="$L('状态负责人')">
<UserInput v-if="userShow" v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择状态负责人')"/>
<UserInput v-model="userData.userids" :project-id="projectId" :multiple-max="5" :placeholder="$L('选择状态负责人')"/>
</FormItem>
<FormItem prop="usertype" :label="$L('流转模式')">
<RadioGroup v-model="userData.usertype">
<Radio label="add">{{$L('添加模式')}}</Radio>
<Radio label="replace">{{$L('流转模式')}}</Radio>
<Radio label="merge">{{$L('剔除模式')}}</Radio>
</RadioGroup>
<div v-if="userData.usertype=='replace'" class="form-tip">{{$L('流转到此状态时改变任务负责人为状态负责人原本的任务负责人移至协助人员')}}</div>
<div v-else-if="userData.usertype=='merge'" class="form-tip">{{$L('流转到此状态时改变任务负责人为状态负责人(并保留操作状态的人员),原本的任务负责人移至协助人员。')}}</div>
<div v-else class="form-tip">{{$L('流转到此状态时添加状态负责人至任务负责人。')}}</div>
</FormItem>
<FormItem prop="userlimit" :label="$L('限制负责人')">
@ -259,6 +261,12 @@ export default {
return JSON.stringify(project_flow_item) != project_flow_bak
},
existDiff() {
return !!this.list.find(data => {
return this.contrast(data.project_flow_item, data.project_flow_bak)
});
},
onCreate() {
let id = -1 * $A.randNum(1000, 10000);
this.list.push({
@ -466,6 +474,14 @@ export default {
$A.modalError(msg);
});
},
saveAll() {
this.list.some(data => {
if (this.contrast(data.project_flow_item, data.project_flow_bak)) {
this.onSave(data)
}
});
},
}
}
</script>

View File

@ -39,7 +39,6 @@
<Col span="22">
<div class="report-users">
<UserInput
v-if="userInputShow"
v-model="reportData.receive"
:disabledChoice="[userId]"
:placeholder="$L('选择接收人')"
@ -95,7 +94,6 @@ export default {
offset: 0 // -1
},
disabledType: false,
userInputShow: true,
prevCycleText: "",
nextCycleText: "",
};
@ -140,7 +138,7 @@ export default {
title: '覆盖提交',
content: '你已提交过此日期的报告,是否覆盖提交?',
loading: true,
zIndex: 2000,
append: this.$el,
onOk: () => {
this.doSubmit();
}
@ -217,7 +215,6 @@ export default {
},
getDetail(reportId) {
this.userInputShow = false;
this.$store.dispatch("call", {
url: 'report/detail',
data: {
@ -231,12 +228,10 @@ export default {
this.reportData.type = data.type_val;
this.reportData.id = reportId;
this.disabledType = true;
this.userInputShow = true;
// msg
}).catch(({msg}) => {
// msg
$A.messageError(msg);
this.userInputShow = true;
});
},
@ -259,18 +254,15 @@ export default {
//
getLastSubmitter() {
this.userInputShow = false;
this.$store.dispatch("call", {
url: 'report/last_submitter',
}).then(({data, msg}) => {
// data
this.reportData.receive = data;
this.userInputShow = true;
// msg
}).catch(({msg}) => {
// msg
$A.messageError(msg);
this.userInputShow = true;
});
},

View File

@ -7,6 +7,7 @@
:clearable="false"
:placeholder="$L('请选择项目')"
:load-data="cascaderLoadData"
@on-change="cascaderChange"
@on-input-change="cascaderInputChange"
@on-visible-change="cascaderShow=!cascaderShow"
filterable/>
@ -81,8 +82,13 @@
v-model="addData.owner"
:multiple-max="10"
:placeholder="$L('选择任务负责人')"
:project-id="addData.project_id"
:transfer="false"/>
:project-id="addData.project_id"/>
<div v-if="showAddAssist" class="task-add-assist">
<Checkbox v-model="addData.add_assist" :true-value="1" :false-value="0">{{$L('加入任务协助人员列表')}}</Checkbox>
<ETooltip :content="$L('你不是任务负责人时建议加入任务协助人员列表')">
<Icon type="ios-alert-outline" />
</ETooltip>
</div>
</FormItem>
<div class="subtasks">
<div v-if="addData.subtasks.length > 0" class="sublist">
@ -170,6 +176,7 @@ export default {
name: "",
content: "",
owner: 0,
add_assist: 1,
project_id: 0,
column_id: 0,
times: [],
@ -236,6 +243,14 @@ export default {
}
}
return 0;
},
showAddAssist() {
const {owner} = this.addData;
if ($A.isArray(owner) && owner.includes(this.userId)) {
return false;
}
return owner != this.userId;
}
},
watch: {
@ -256,18 +271,22 @@ export default {
}
},
'addData.project_id'(id) {
$A.setStorage("cacheAddTaskProjectId", id);
if (id > 0) {
$A.setStorage("cacheAddTaskProjectId", id);
}
},
'addData.column_id'(id) {
const {project_id, column_id} = this.addData;
const {project_id} = this.addData;
this.$nextTick(() => {
if (project_id && column_id) {
this.$set(this.addData, 'cascader', [project_id, column_id]);
if (project_id && id) {
this.$set(this.addData, 'cascader', [project_id, id]);
} else {
this.$set(this.addData, 'cascader', []);
}
})
$A.setStorage("cacheAddTaskColumnId", id);
if (id > 0) {
$A.setStorage("cacheAddTaskColumnId", id);
}
}
},
methods: {
@ -412,6 +431,10 @@ export default {
});
},
cascaderChange(value) {
value[1] && this.$set(this.addData, 'column_id', value[1])
},
cascaderInputChange(key) {
this.cascaderValue = key || "";
//
@ -470,6 +493,7 @@ export default {
name: "",
content: "",
owner: 0,
add_assist: 1,
column_id: 0,
times: [],
subtasks: [],

View File

@ -45,12 +45,10 @@
:width="240"
placement="bottom"
@on-popper-show="openOwner"
@on-popper-hide="ownerShow=false"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-if="ownerShow"
v-model="ownerData.owner_userid"
:multiple-max="1"
:project-id="taskDetail.project_id"
@ -66,7 +64,12 @@
</Poptip>
</li>
<!--主任务-->
<div v-else-if="ready" :class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}">
<div
v-else-if="ready"
:class="{'task-detail':true, 'open-dialog': hasOpenDialog, 'completed': taskDetail.complete_at}"
@drop.prevent="taskPasteDrag($event, 'drag')"
@dragover.prevent="taskDragOver(true, $event)"
@dragleave.prevent="taskDragOver(false, $event)">
<div v-show="taskDetail.id > 0" class="task-info">
<div class="head">
<TaskMenu
@ -188,12 +191,10 @@
class="item-content user"
placement="bottom"
@on-popper-show="openOwner"
@on-popper-hide="ownerShow=false"
@on-ok="onOwner"
transfer>
<div slot="content">
<UserInput
v-if="ownerShow"
v-model="ownerData.owner_userid"
:multiple-max="10"
:project-id="taskDetail.project_id"
@ -218,12 +219,10 @@
class="item-content user"
placement="bottom"
@on-popper-show="openAssist"
@on-popper-hide="assistShow=false"
@on-ok="onAssist"
transfer>
<div slot="content">
<UserInput
v-if="assistShow"
v-model="assistData.assist_userid"
:multiple-max="10"
:project-id="taskDetail.project_id"
@ -274,18 +273,22 @@
<li v-for="file in fileList">
<img v-if="file.id" class="file-ext" :src="file.thumb"/>
<Loading v-else class="file-load"/>
<div class="file-name" @click="downFile(file)">{{file.name}}</div>
<div class="file-name">{{file.name}}</div>
<div class="file-size">{{$A.bytesToSize(file.size)}}</div>
<EPopover v-model="file._deling" class="file-delete">
<div class="task-detail-delete-file-popover">
<p>{{$L('你确定要删除这个文件吗?')}}</p>
<div class="buttons">
<Button size="small" type="text" @click="file._deling=false">{{$L('取消')}}</Button>
<Button size="small" type="primary" @click="deleteFile(file)">{{$L('确定')}}</Button>
<div class="file-menu" :class="{show:file._show_menu}">
<Icon @click="viewFile(file)" type="md-eye" />
<Icon @click="downFile(file)" type="md-arrow-round-down" />
<EPopover v-model="file._show_menu" class="file-delete">
<div class="task-detail-delete-file-popover">
<p>{{$L('你确定要删除这个文件吗?')}}</p>
<div class="buttons">
<Button size="small" type="text" @click="file._show_menu=false">{{$L('取消')}}</Button>
<Button size="small" type="primary" @click="deleteFile(file)">{{$L('确定')}}</Button>
</div>
</div>
</div>
<i slot="reference" :class="['taskfont', file._deling ? 'deling' : '']">&#xe6ea;</i>
</EPopover>
<i slot="reference" class="taskfont del">&#xe6ea;</i>
</EPopover>
</div>
</li>
</ul>
<ul class="item-content">
@ -342,7 +345,7 @@
</EDropdown>
</div>
</div>
<TaskUpload ref="upload" class="upload"/>
<TaskUpload ref="upload" class="upload" @on-select-file="onSelectFile"/>
</div>
<div v-show="taskDetail.id > 0" class="task-dialog" :style="dialogStyle">
<template v-if="hasOpenDialog">
@ -377,7 +380,7 @@
<div v-else class="no-dialog">
<div class="no-tip">{{$L('暂无消息')}}</div>
<div class="no-input">
<Input
<DragInput
class="dialog-input"
v-model="msgText"
type="textarea"
@ -386,8 +389,9 @@
:autosize="{ minRows: 1, maxRows: 3 }"
:maxlength="255"
:placeholder="$L('输入消息...')"
@on-keydown="msgKeydown"/>
<div class="no-send" @click="openSend">
@on-keydown="msgKeydown"
@on-input-paste="msgPasteDrag"/>
<div class="no-send" @click="msgDialog">
<Loading v-if="sendLoad > 0"/>
<Icon v-else type="md-send" />
</div>
@ -396,6 +400,9 @@
</div>
</div>
<div v-if="!taskDetail.id" class="task-load"><Loading/></div>
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
</div>
</div>
</template>
@ -409,10 +416,11 @@ import DialogWrapper from "./DialogWrapper";
import ProjectLog from "./ProjectLog";
import {Store} from "le5le-store";
import TaskMenu from "./TaskMenu";
import DragInput from "../../../components/DragInput";
export default {
name: "TaskDetail",
components: {TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
components: {DragInput, TaskMenu, ProjectLog, DialogWrapper, TaskUpload, UserInput, TaskPriority, TEditor},
props: {
taskId: {
type: Number,
@ -434,14 +442,12 @@ export default {
taskDetail: {},
ownerShow: false,
ownerData: {},
ownerLoad: 0,
receiveShow: false,
assistForce: false,
assistShow: false,
assistData: {},
assistLoad: 0,
@ -461,6 +467,7 @@ export default {
innerHeight: Math.min(1100, window.innerHeight),
msgText: '',
msgFile: [],
navActive: 'dialog',
logLoadIng: false,
@ -489,6 +496,7 @@ export default {
toolbar: 'uploadImages | uploadFiles | bold italic underline forecolor backcolor | codesample | preview screenload'
},
dialogDrag: false,
receiveTaskSubscribe: null,
}
},
@ -524,6 +532,8 @@ export default {
'taskContents',
'taskFiles',
'taskPriority',
'windowMax768'
]),
projectName() {
@ -582,7 +592,7 @@ export default {
},
hasOpenDialog() {
return this.taskDetail.dialog_id > 0 && !this.$store.state.windowMax768;
return this.taskDetail.dialog_id > 0 && !this.windowMax768;
},
dialogStyle() {
@ -779,7 +789,6 @@ export default {
const list = this.getOwner.map(({userid}) => userid)
this.$set(this.taskDetail, 'owner_userid', list)
this.$set(this.ownerData, 'owner_userid', list)
this.ownerShow = true;
},
onOwner(pick) {
@ -815,13 +824,11 @@ export default {
this.$store.dispatch("taskUpdate", data).then(({msg}) => {
$A.messageSuccess(msg);
this.ownerLoad--;
this.ownerShow = false;
this.receiveShow = false;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
}).catch(({msg}) => {
$A.modalError(msg);
this.ownerLoad--;
this.ownerShow = false;
this.receiveShow = false;
})
},
@ -831,7 +838,6 @@ export default {
this.$set(this.taskDetail, 'assist_userid', list)
this.$set(this.assistData, 'assist_userid', list);
this.$set(this.assistData, 'disabled', this.getOwner.map(({userid}) => userid))
this.assistShow = true;
},
onAssist() {
@ -847,12 +853,10 @@ export default {
}).then(({msg}) => {
$A.messageSuccess(msg);
this.assistLoad--;
this.assistShow = false;
this.$store.dispatch("getTaskOne", this.taskDetail.id).catch(() => {})
}).catch(({msg}) => {
$A.modalError(msg);
this.assistLoad--;
this.assistShow = false;
})
},
@ -988,14 +992,13 @@ export default {
return;
}
e.preventDefault();
this.msgDialog();
if (this.msgText) {
this.msgDialog();
}
}
},
msgDialog() {
if (!this.msgText) {
return;
}
if (this.sendLoad > 0) {
return;
}
@ -1011,18 +1014,26 @@ export default {
this.$store.dispatch("getDialogOne", data.dialog_id).then(() => {
this.sendLoad--;
if ($A.isSubElectron) {
this.resizeDialog();
this.resizeDialog().then(() => {
this.sendDialogMsg();
});
} else {
this.$nextTick(() => {
if (this.$store.state.windowMax768) {
this.goForward({path: '/manage/messenger', query: {sendmsg: this.msgText}});
if (this.windowMax768) {
window.__sendDialogMsg = {
time: $A.Time() + 10,
msgText: this.msgText,
msgFile: this.msgFile
};
this.msgFile = [];
this.msgText = "";
this.goForward({path: '/manage/messenger', query: {_: $A.randomString(6)}});
$A.setStorage("messenger::dialogId", data.dialog_id)
this.$store.state.dialogOpenId = data.dialog_id;
this.$store.dispatch('openTask', 0);
} else {
this.$refs.dialog.sendMsg(this.msgText);
this.sendDialogMsg();
}
this.msgText = "";
});
}
}).catch(({msg}) => {
@ -1035,39 +1046,53 @@ export default {
});
},
openSend() {
if (this.sendLoad > 0) {
return;
sendDialogMsg() {
if (this.msgFile.length > 0) {
this.$refs.dialog.sendFileMsg(this.msgFile);
} else if (this.msgText) {
this.$refs.dialog.sendMsg(this.msgText);
}
this.sendLoad++;
//
this.$store.dispatch("call", {
url: 'project/task/dialog',
data: {
task_id: this.taskDetail.id,
},
}).then(({data}) => {
this.sendLoad--;
this.$store.dispatch("saveTask", data);
this.$store.dispatch("getDialogOne", data.dialog_id).catch(() => {})
if ($A.isSubElectron) {
this.resizeDialog();
} else {
this.$nextTick(() => {
this.goForward({path: '/manage/messenger', query: {sendmsg: this.msgText}});
$A.setStorage("messenger::dialogId", data.dialog_id)
this.$store.state.dialogOpenId = data.dialog_id;
this.$store.dispatch('openTask', 0);
});
this.msgFile = [];
this.msgText = "";
},
msgPasteDrag(e, type) {
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
this.msgFile = Array.prototype.slice.call(files);
if (this.msgFile.length > 0) {
e.preventDefault();
this.msgDialog()
}
},
taskPasteDrag(e, type) {
this.dialogDrag = false;
this.msgPasteDrag(e, type);
},
taskDragOver(show, e) {
let random = (this.__dialogDrag = $A.randomString(8));
if (!show) {
setTimeout(() => {
if (random === this.__dialogDrag) {
this.dialogDrag = show;
}
}, 150);
} else {
if (e.dataTransfer.effectAllowed === 'move') {
return;
}
}).catch(({msg}) => {
this.sendLoad--;
$A.modalError(msg);
});
this.dialogDrag = true;
}
},
onSelectFile(file) {
this.msgFile = [file];
this.msgDialog()
},
deleteFile(file) {
this.$set(file, '_deling', false);
this.$set(file, '_show_menu', false);
this.$store.dispatch("forgetTaskFile", file.id)
//
this.$store.dispatch("call", {
@ -1083,9 +1108,7 @@ export default {
openMenu(task) {
const el = this.$refs[`taskMenu_${task.id}`];
if (el) {
el.handleClick()
}
el && el.handleClick()
},
openNewWin() {
@ -1112,25 +1135,43 @@ export default {
},
resizeDialog() {
this.$Electron.ipcRenderer.sendSync('windowSize', {
width: Math.max(1100, window.innerWidth),
height: Math.max(720, window.innerHeight),
minWidth: 800,
minHeight: 600,
autoZoom: true,
});
if (this.msgText) {
return new Promise(resolve => {
this.$Electron.ipcRenderer.sendSync('windowSize', {
width: Math.max(1100, window.innerWidth),
height: Math.max(720, window.innerHeight),
minWidth: 800,
minHeight: 600,
autoZoom: true,
});
let num = 0;
let interval = setInterval(() => {
num++;
if (this.$refs.dialog || num > 20) {
clearInterval(interval);
if (this.$refs.dialog) {
this.$refs.dialog.sendMsg(this.msgText);
this.msgText = "";
resolve()
}
}
}, 100);
})
},
viewFile(file) {
if (this.$Electron) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: `${file.name} (${$A.bytesToSize(file.size)})`,
titleFixed: true,
name: 'file-task-' + file.id,
path: "/single/file/task/" + file.id,
force: false,
config: {
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
}
});
} else {
window.open($A.apiUrl(`../single/file/task/${file.id}`))
}
},

View File

@ -2,23 +2,18 @@
<Upload
name="files"
ref="upload"
:action="actionUrl"
:headers="headers"
:data="params"
action=""
multiple
:format="uploadFormat"
:show-upload-list="false"
:max-size="maxSize"
:on-progress="handleProgress"
:on-success="handleSuccess"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize">
:on-exceeded-size="handleMaxSize"
:before-upload="handleBeforeUpload">
</Upload>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'TaskUpload',
props: {
@ -31,54 +26,10 @@ export default {
data() {
return {
uploadFormat: ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'],
actionUrl: $A.apiUrl('project/task/upload'),
}
},
computed: {
...mapState(['userToken', 'taskId', 'taskFiles']),
headers() {
return {
fd: $A.getStorageString("userWsFd"),
token: this.userToken,
}
},
params() {
return {
task_id: this.taskId,
}
},
},
methods: {
handleProgress(event, file) {
//
if (typeof file.tempId === "undefined") {
file.tempId = $A.randomString(8);
file.task_id = this.taskId;
this.taskFiles.push(file);
}
},
handleSuccess({ret, data, msg}, file) {
//
let index = this.taskFiles.findIndex(({tempId}) => tempId == file.tempId);
if (index > -1) {
this.taskFiles.splice(index, 1);
}
if (ret === 1) {
this.taskFiles.push(data);
} else {
this.$refs.upload.fileList.pop();
$A.modalWarning({
title: '发送失败',
content: '文件 ' + file.name + ' 发送失败,' + msg
});
}
},
handleFormatError(file) {
//
$A.modalWarning({
@ -95,15 +46,16 @@ export default {
});
},
handleBeforeUpload(file) {
//
this.$emit("on-select-file", file)
return false;
},
handleClick() {
//
this.$refs.upload.handleClick()
},
upload(file) {
//file
this.$refs.upload.upload(file);
},
}
}
</script>

View File

@ -62,10 +62,12 @@
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>
</div>
<ETooltip :content="item.end_at" placement="right">
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>
</div>
</ETooltip>
</li>
</ul>
</template>

View File

@ -775,8 +775,8 @@ export default {
})
},
openFile(item) {
if (this.contextMenuVisible) {
openFile(item, checkMenuVisible = true) {
if (checkMenuVisible && this.contextMenuVisible) {
return;
}
if (this.fileList.findIndex((file) => file._edit === true) > -1) {
@ -800,8 +800,9 @@ export default {
openSingle(item) {
this.$Electron.ipcRenderer.send('windowRouter', {
title: item.name,
title: this.formatName(item),
titleFixed: true,
userAgent: "/hideenOfficeTitle/",
name: 'file-' + item.id,
path: "/single/file/" + item.id,
force: false, //
@ -842,7 +843,7 @@ export default {
dropFile(item, command) {
switch (command) {
case 'open':
this.openFile(item);
this.openFile(item, false);
break;
case 'rename':
@ -912,7 +913,6 @@ export default {
case 'download':
if (!item.ext) {
$A.modalError("此文件不支持下载");
return;
}
$A.modalConfirm({

View File

@ -0,0 +1,151 @@
<template>
<div class="single-file-msg">
<PageTitle :title="title"/>
<Loading v-if="loadIng > 0"/>
<template v-else>
<AceEditor v-if="isCode" v-model="codeContent" :ext="codeExt" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isOffice" v-model="officeContent" :code="officeCode" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
</template>
<style lang="scss" scoped>
.single-file-msg {
display: flex;
align-items: center;
.preview-iframe,
.ace_editor,
.no-support {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
margin: 0;
outline: 0;
padding: 0;
}
.preview-iframe {
background: 0 0;
float: none;
max-width: none;
}
.view-editor,
.no-support {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
<style lang="scss">
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
export default {
components: {OnlyOffice, AceEditor},
data() {
return {
loadIng: 0,
msgDetail: {},
}
},
mounted() {
//
},
watch: {
'$route': {
handler() {
this.getInfo();
},
immediate: true
},
},
computed: {
title() {
const {msg} = this.msgDetail;
if (msg && msg.name) {
return msg.name;
}
return "Loading..."
},
isCode() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.msgDetail.content;
}
return '';
},
codeExt() {
if (this.isCode) {
return this.msgDetail.msg.ext;
}
return 'txt'
},
isOffice() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 2;
},
officeContent() {
return {
id: this.isOffice ? this.msgDetail.id : 0,
type: this.msgDetail.msg.ext,
name: this.title
}
},
officeCode() {
if (this.isOffice) {
return "msgFile_" + this.msgDetail.id;
}
return ''
},
isPreview() {
return this.msgDetail.type == 'file' && this.msgDetail.file_mode == 3;
},
previewUrl() {
if (this.isPreview) {
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.msgDetail.url))
}
return ''
}
},
methods: {
getInfo() {
let msg_id = $A.runNum(this.$route.params.id);
if (msg_id <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'dialog/msg/detail',
data: {
msg_id,
},
}).then(({data}) => {
this.loadIng--;
this.msgDetail = data;
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({
content: msg,
onOk: () => {
if (this.$Electron) {
window.close();
}
}
});
});
}
}
}
</script>

View File

@ -0,0 +1,151 @@
<template>
<div class="single-file-task">
<PageTitle :title="title"/>
<Loading v-if="loadIng > 0"/>
<template v-else>
<AceEditor v-if="isCode" v-model="codeContent" :ext="codeExt" class="view-editor" readOnly/>
<OnlyOffice v-else-if="isOffice" v-model="officeContent" :code="officeCode" readOnly/>
<iframe v-else-if="isPreview" class="preview-iframe" :src="previewUrl"/>
<div v-else class="no-support">{{$L('不支持单独查看此消息')}}</div>
</template>
</div>
</template>
<style lang="scss" scoped>
.single-file-task {
display: flex;
align-items: center;
.preview-iframe,
.ace_editor,
.no-support {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
margin: 0;
outline: 0;
padding: 0;
}
.preview-iframe {
background: 0 0;
float: none;
max-width: none;
}
.view-editor,
.no-support {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
<style lang="scss">
</style>
<script>
import AceEditor from "../../components/AceEditor";
import OnlyOffice from "../../components/OnlyOffice";
export default {
components: {OnlyOffice, AceEditor},
data() {
return {
loadIng: 0,
fileDetail: {},
}
},
mounted() {
//
},
watch: {
'$route': {
handler() {
this.getInfo();
},
immediate: true
},
},
computed: {
title() {
const {name} = this.fileDetail;
if (name) {
return name;
}
return "Loading..."
},
isCode() {
return this.fileDetail.file_mode == 1;
},
codeContent() {
if (this.isCode) {
return this.fileDetail.content;
}
return '';
},
codeExt() {
if (this.isCode) {
return this.fileDetail.ext;
}
return 'txt'
},
isOffice() {
return this.fileDetail.file_mode == 2;
},
officeContent() {
return {
id: this.isOffice ? this.fileDetail.id : 0,
type: this.fileDetail.ext,
name: this.title
}
},
officeCode() {
if (this.isOffice) {
return "taskFile_" + this.fileDetail.id;
}
return ''
},
isPreview() {
return this.fileDetail.file_mode == 3;
},
previewUrl() {
if (this.isPreview) {
return $A.apiUrl("../fileview/onlinePreview?url=" + encodeURIComponent(this.fileDetail.url))
}
return ''
}
},
methods: {
getInfo() {
let file_id = $A.runNum(this.$route.params.id);
if (file_id <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("call", {
url: 'project/task/filedetail',
data: {
file_id,
},
}).then(({data}) => {
this.loadIng--;
this.fileDetail = data;
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({
content: msg,
onOk: () => {
if (this.$Electron) {
window.close();
}
}
});
});
}
}
}
</script>

View File

@ -38,18 +38,25 @@
</style>
<script>
import TaskDetail from "../manage/components/TaskDetail";
import {mapState} from "vuex";
export default {
components: {TaskDetail},
data() {
return {
loadIng: 0,
taskInfo: {},
taskId: 0,
}
},
mounted() {
//
},
computed: {
...mapState(['cacheTasks']),
taskInfo() {
return this.cacheTasks.find(({id}) => id === this.taskId) || {}
}
},
watch: {
'$route': {
handler() {
@ -60,20 +67,19 @@ export default {
},
methods: {
getInfo() {
let task_id = $A.runNum(this.$route.params.id);
if (task_id <= 0) {
this.taskId = $A.runNum(this.$route.params.id);
if (this.taskId <= 0) {
return;
}
this.loadIng++;
this.$store.dispatch("getTaskOne", {
task_id,
task_id: this.taskId,
archived: 'all'
}).then(({data}) => {
}).then(() => {
this.loadIng--;
this.taskInfo = data;
this.$store.dispatch("getTaskContent", task_id);
this.$store.dispatch("getTaskFiles", task_id);
this.$store.dispatch("getTaskForParent", task_id).catch(() => {})
this.$store.dispatch("getTaskContent", this.taskId);
this.$store.dispatch("getTaskFiles", this.taskId);
this.$store.dispatch("getTaskForParent", this.taskId).catch(() => {})
}).catch(({msg}) => {
this.loadIng--;
$A.modalError({

View File

@ -75,6 +75,16 @@ export default [
},
]
},
{
name: 'single-file-msg',
path: '/single/file/msg/:id',
component: () => import('./pages/single/fileMsg.vue'),
},
{
name: 'single-file-task',
path: '/single/file/task/:id',
component: () => import('./pages/single/fileTask.vue'),
},
{
name: 'single-file',
path: '/single/file/:id',

View File

@ -273,11 +273,11 @@ export default {
dispatch("call", {
url: 'users/basic',
data: {
userid: array.map(({userid}) => userid)
userid: [...new Set(array.map(({userid}) => userid))]
},
}).then(result => {
time = $A.Time();
array.forEach((value) => {
array.forEach(value => {
let data = result.data.find(({userid}) => userid == value.userid) || Object.assign(value, {email: ""});
data._time = time;
dispatch("saveUserBasic", data);

View File

@ -145,6 +145,10 @@
line-height: 24px;
}
}
img {
max-width: 100%;
}
}
}

View File

@ -45,6 +45,7 @@
line-height: 22px;
max-width: 100%;
.ivu-tag {
flex-shrink: 0;
margin: 0 6px 0 0;
padding: 0 5px;
&.ivu-tag-success {
@ -110,19 +111,50 @@
height: 30px;
}
.dialog-view {
max-width: 70%;
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 0 8px;
position: relative;
&.text {
max-width: 70%;
}
.dialog-content {
color: #333333;
background-color: #F4F5F7;
padding: 8px;
min-width: 32px;
border-radius: 6px 6px 6px 0;
.dialog-file {
cursor: pointer;
.dialog-file-menu {
opacity: 0;
transition: all 0.3s;
position: absolute;
left: 0;
bottom: -8px;
.file-menu-warp {
width: 100%;
height: 12px;
}
.file-menu-icon {
display: flex;
align-items: center;
border-radius: 4px;
border: 1px solid #ddd;
> i {
flex: 1;
display: inline-block;
padding: 4px 6px;
color: #999;
font-size: 13px;
cursor: pointer;
&:hover {
color: #777;
}
&+i {
border-left: 1px solid #ddd;
}
}
}
}
> pre {
display: block;
@ -133,6 +165,11 @@
word-wrap: break-word;
word-break: break-word;
}
&:hover {
.dialog-file-menu {
opacity: 1;
}
}
&.loading {
display: flex;
.common-loading {
@ -187,6 +224,7 @@
overflow: hidden;
.file-img {
display: flex;
cursor: pointer;
}
}
&.unknown {

View File

@ -179,6 +179,16 @@
width: 100%;
}
}
.task-add-assist {
margin-top: 6px;
display: flex;
align-items: center;
justify-content: flex-end;
.ivu-icon {
font-size: 16px;
color: #999999;
}
}
.ivu-modal-footer {
padding: 26px 0 22px !important;
}

View File

@ -251,10 +251,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
&:hover {
color: $primary-color;
}
}
.file-size {
flex-shrink: 0;
@ -263,27 +259,38 @@
font-size: 12px;
color: #bbbbbb;
}
.file-delete {
.file-menu {
display: flex;
align-items: center;
opacity: 0;
transition: all 0.3s;
padding-left: 12px;
.taskfont {
display: none;
&.show {
opacity: 1;
}
i {
font-size: 14px;
color: #aaaaaa;
transition: color 0.3s;
cursor: pointer;
padding: 0 4px;
&:hover {
color: #ff0000;
color: #777777;
}
&.deling {
display: inline-block;
&.del {
font-size: 13px;
&:hover {
color: #ff0000;
}
}
}
}
&:hover {
.file-delete {
.taskfont {
display: inline-block;
}
.file-name {
color: $primary-title-color;
}
.file-menu {
opacity: 1;
}
}
}
@ -593,6 +600,33 @@
height: 32px;
}
}
.drag-over {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3;
background-color: rgba(255, 255, 255, 0.78);
display: flex;
align-items: center;
justify-content: center;
&:before {
content: "";
position: absolute;
top: 16px;
left: 16px;
right: 16px;
bottom: 16px;
border: 2px dashed #7b7b7b;
border-radius: 12px;
}
.drag-text {
padding: 12px;
font-size: 18px;
color: #666666;
}
}
&.open-dialog {
flex-direction: row;

View File

@ -298,6 +298,13 @@
}
.manage-mini-menu {
display: none;
position: relative;
.ivu-badge {
position: absolute;
top: -6px;
left: 30px;
transform: scale(0.9);
}
}
}

File diff suppressed because one or more lines are too long