Silent 6 سال پیش
والد
کامیت
9d994396d1
100فایلهای تغییر یافته به همراه8028 افزوده شده و 3 حذف شده
  1. 72 0
      app/Http/Controllers/Admin/CheckCardController.php
  2. 124 0
      app/Http/Controllers/Admin/ContentController.php
  3. 36 0
      app/Http/Controllers/Admin/Controller.php
  4. 50 0
      app/Http/Controllers/Admin/LeaveController.php
  5. 80 0
      app/Http/Controllers/Admin/RemarkController.php
  6. 138 0
      app/Http/Controllers/Admin/RemarkDetailController.php
  7. 146 0
      app/Http/Controllers/Admin/Student/CourseController.php
  8. 115 0
      app/Http/Controllers/Admin/StudentController.php
  9. 140 0
      app/Http/Controllers/Admin/TeacherController.php
  10. 1 0
      app/Http/Controllers/Admin/TestController.php
  11. 79 0
      app/Http/Controllers/Admin/UploadController.php
  12. 22 0
      app/Http/Controllers/TestController.php
  13. 2 1
      app/Http/Middleware/VerifyCsrfToken.php
  14. 23 0
      app/Models/BaseAttachmentModel.php
  15. 55 0
      app/Models/CheckCard.php
  16. 36 0
      app/Models/Content.php
  17. 12 0
      app/Models/Course.php
  18. 27 0
      app/Models/Leave.php
  19. 27 0
      app/Models/Remark.php
  20. 12 0
      app/Models/RemarkDetail.php
  21. 42 0
      app/Models/Student.php
  22. 56 0
      app/Models/StudentCourse.php
  23. 12 0
      app/Models/StudentCourseTeacher.php
  24. 17 0
      app/Models/Teacher.php
  25. 12 0
      app/Models/TeacherCourse.php
  26. 12 0
      app/Models/TeacherStudent.php
  27. 367 0
      app/Services/Uploader.php
  28. 2 1
      config/app.php
  29. 7 0
      config/filesystems.php
  30. 1 1
      database/migrations/2017_11_10_145204_create_cache_table.php
  31. 39 0
      database/migrations/2018_06_21_011003_create_students_table.php
  32. 32 0
      database/migrations/2018_06_21_082321_create_courses_table.php
  33. 32 0
      database/migrations/2018_06_21_082621_create_teachers_table.php
  34. 33 0
      database/migrations/2018_06_21_114238_create_teacher_students_table.php
  35. 37 0
      database/migrations/2018_06_22_153652_create_student_courses_table.php
  36. 34 0
      database/migrations/2018_06_22_154453_create_student_course_teachers_table.php
  37. 35 0
      database/migrations/2018_06_23_132837_create_check_cards_table.php
  38. 30 0
      database/migrations/2018_06_23_133738_add_course_id_to_check_card.php
  39. 37 0
      database/migrations/2018_06_25_230153_create_leaves_table.php
  40. 30 0
      database/migrations/2018_06_25_231401_add_date_to_leaves.php
  41. 34 0
      database/migrations/2018_06_28_092245_create_remarks_table.php
  42. 34 0
      database/migrations/2018_06_28_092556_create_remark_details_table.php
  43. 33 0
      database/migrations/2018_06_28_093400_create_teacher_courses_table.php
  44. 35 0
      database/migrations/2018_06_29_231340_create_contents_table.php
  45. 28 0
      database/seeds/CheckCardSeeder.php
  46. 6 0
      public/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css
  47. 6 0
      public/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css
  48. 2035 0
      public/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.js
  49. 6 0
      public/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js
  50. 1 0
      public/plugins/bootstrap-datepicker/dist/locales/bootstrap-datepicker.zh-CN.min.js
  51. 93 0
      public/plugins/multi-select/css/multi-select.css
  52. 108 0
      public/plugins/multi-select/css/multi-select.dev.css
  53. 7 0
      public/plugins/multi-select/css/multi-select.dev.css.map
  54. 1 0
      public/plugins/multi-select/css/multi-select.dist.css
  55. BIN
      public/plugins/multi-select/img/switch.png
  56. 544 0
      public/plugins/multi-select/js/jquery.multi-select.js
  57. 94 0
      public/plugins/ueditor/config.json
  58. 40 0
      public/plugins/ueditor/dialogs/anchor/anchor.html
  59. 681 0
      public/plugins/ueditor/dialogs/attachment/attachment.css
  60. 60 0
      public/plugins/ueditor/dialogs/attachment/attachment.html
  61. 754 0
      public/plugins/ueditor/dialogs/attachment/attachment.js
  62. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_chm.gif
  63. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_default.png
  64. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_doc.gif
  65. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_exe.gif
  66. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_jpg.gif
  67. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_mp3.gif
  68. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_mv.gif
  69. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_pdf.gif
  70. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_ppt.gif
  71. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_psd.gif
  72. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_rar.gif
  73. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_txt.gif
  74. BIN
      public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_xls.gif
  75. BIN
      public/plugins/ueditor/dialogs/attachment/images/alignicon.gif
  76. BIN
      public/plugins/ueditor/dialogs/attachment/images/alignicon.png
  77. BIN
      public/plugins/ueditor/dialogs/attachment/images/bg.png
  78. BIN
      public/plugins/ueditor/dialogs/attachment/images/file-icons.gif
  79. BIN
      public/plugins/ueditor/dialogs/attachment/images/file-icons.png
  80. BIN
      public/plugins/ueditor/dialogs/attachment/images/icons.gif
  81. BIN
      public/plugins/ueditor/dialogs/attachment/images/icons.png
  82. BIN
      public/plugins/ueditor/dialogs/attachment/images/image.png
  83. BIN
      public/plugins/ueditor/dialogs/attachment/images/progress.png
  84. BIN
      public/plugins/ueditor/dialogs/attachment/images/success.gif
  85. BIN
      public/plugins/ueditor/dialogs/attachment/images/success.png
  86. 94 0
      public/plugins/ueditor/dialogs/background/background.css
  87. 56 0
      public/plugins/ueditor/dialogs/background/background.html
  88. 376 0
      public/plugins/ueditor/dialogs/background/background.js
  89. BIN
      public/plugins/ueditor/dialogs/background/images/bg.png
  90. BIN
      public/plugins/ueditor/dialogs/background/images/success.png
  91. 65 0
      public/plugins/ueditor/dialogs/charts/chart.config.js
  92. 165 0
      public/plugins/ueditor/dialogs/charts/charts.css
  93. 89 0
      public/plugins/ueditor/dialogs/charts/charts.html
  94. 519 0
      public/plugins/ueditor/dialogs/charts/charts.js
  95. BIN
      public/plugins/ueditor/dialogs/charts/images/charts0.png
  96. BIN
      public/plugins/ueditor/dialogs/charts/images/charts1.png
  97. BIN
      public/plugins/ueditor/dialogs/charts/images/charts2.png
  98. BIN
      public/plugins/ueditor/dialogs/charts/images/charts3.png
  99. BIN
      public/plugins/ueditor/dialogs/charts/images/charts4.png
  100. BIN
      public/plugins/ueditor/dialogs/charts/images/charts5.png

+ 72 - 0
app/Http/Controllers/Admin/CheckCardController.php

xqd
@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\CheckCard;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+class CheckCardController extends Controller
+{
+    protected $redirect_index = '/admin/CheckCard/index';
+
+    protected $view_path = 'admin.check-cards.';
+
+    protected $pre_uri = '/admin/CheckCard/';
+
+    protected $model_name = '打卡';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new CheckCard();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc')->get();
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = trim($request->input('keyword'));
+
+            $list = $list->filter(function ($value) use($keyword) {
+                if(!empty($value->student) && !(strpos($value->student->name, $keyword) === false)) {
+                    return true;
+                }
+                if(!empty($value->course) && !(strpos($value->course->name, $keyword) === false)) {
+                    return true;
+                }
+                return false;
+            });
+        }
+
+        if(!empty($request->input('begin_date'))) {
+            $begin_date_time = Carbon::createFromTimestamp(strtotime($request->input('begin_date')))->toDateTimeString();
+        } else {
+            $begin_date_time = Carbon::now()->subYears(10)->toDateTimeString();
+        }
+
+        if(!empty($request->input('end_date'))) {
+            $end_date_time = Carbon::createFromTimestamp(strtotime($request->input('end_date')))->addDay()->toDateTimeString();
+        } else {
+            $end_date_time = Carbon::now()->addYears(10)->toDateTimeString();
+        }
+
+        if(!empty($begin_date_time) || !empty($end_date_time)) {
+            $list = $list->filter(function ($value) use($begin_date_time, $end_date_time) {
+                return $value->begin_date_time >= $begin_date_time && $value->end_date_time < $end_date_time;
+            });
+        }
+
+        $list = $this->paginate($list);
+
+        foreach($list as $item) {
+            $item->duration = $item->getDuration();
+        }
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+}

+ 124 - 0
app/Http/Controllers/Admin/ContentController.php

xqd
@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\BaseAttachmentModel;
+use App\Models\Content;
+use Illuminate\Http\Request;
+
+class ContentController extends Controller
+{
+    protected $redirect_index = '/admin/Content/index';
+
+    protected $view_path = 'admin.contents.';
+
+    protected $pre_uri = '/admin/Content/';
+
+    protected $model_name = '内容';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Content();
+    }
+
+    public function index(Request $request)
+    {
+        $type = $this->model->getModelType($request->input('type', 1));
+        $list = $this->model->where('type', $type)->orderBy('sort');
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = '%' . trim($request->input('keyword')) . '%';
+            $list = $list->where('title', 'like', $keyword);
+        }
+
+        $list = $list->paginate()->withPath($this->getPaginateUrl());
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model->getModelName($type));
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name', 'type'));
+    }
+
+    public function create(Request $request)
+    {
+        $type = $this->model->getModelType($request->input('type', 1));
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model->getModelName($type));
+        return view($this->view_path . 'create', compact('pre_uri', 'model_name', 'type', 'model'));
+    }
+
+    public function store(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $data = $request->input('data');
+        if($request->hasFile('video')) {
+            $data['content'] = (new BaseAttachmentModel())->upload($request->file('video'), '视频');
+        }
+        $res = $this->model->create($request->input('data'));
+
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+        return $this->showMessage('操作成功', $this->redirect_index . '?type=' . $res->type);
+    }
+
+    public function edit(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('数据错误!');
+        }
+        $type = $this->model->getModelType($item->type);
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model->getModelName($type), $this->model);
+
+        return view($this->view_path . 'edit', compact('item','pre_uri', 'model_name', 'model', 'type'));
+    }
+
+    public function update(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('id')) || empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $data = $request->input('data');
+        if($request->hasFile('video')) {
+            $data['content'] = (new BaseAttachmentModel())->upload($request->file('video'), '视频');
+        }
+        $res = $this->model->where('id', $request->input('id'))->update($data);
+
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+        $item = $this->model->where('id', $request->input('id'))->first();
+        return $this->showMessage('操作成功', $this->redirect_index . '?type=' . $);
+    }
+
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+
+        StudentCourse::where('student_id', $item->id)->delete();
+        StudentCourseTeacher::where('student_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 36 - 0
app/Http/Controllers/Admin/Controller.php

xqd xqd
@@ -4,6 +4,10 @@ namespace App\Http\Controllers\Admin;
 
 use Illuminate\Routing\Controller as BaseController;
 use Request,Auth;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Pagination\Paginator;
+use Illuminate\Support\Collection;
+
 /**
  * 父控制类类
  *
@@ -164,4 +168,36 @@ abstract class Controller extends BaseController
         exit($html);
     }
 
+    public function paginate($items, $perPage = 15, $page = null, $options = [])
+    {
+        $page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
+        $items = $items instanceof Collection ? $items : Collection::make($items);
+        $options['path'] = $this->getPaginateUrl();
+        return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
+    }
+
+    public function getPaginateUrl()
+    {
+        $url = url()->full();
+        $url_arr = explode('?', $url);
+        $url_end = '';
+
+        if(count($url_arr) > 1) {
+            $params = explode('&', $url_arr[1]);
+            $params_arr = [];
+            foreach($params as $key => $value) {
+                $param = explode('=', $value);
+                $params_arr[$param[0]] = count($param) > 1 ? $param[1] : '';
+            }
+            $id = 0;
+            foreach($params_arr as $key => $value) {
+                if($key != 'page') {
+                    $prefix = $id == 0 ? '?' : '&';
+                    $url_end .= $prefix . $key . '=' . $value;
+                    ++$id;
+                }
+            }
+        }
+        return $url_arr[0] . $url_end;
+    }
 }

+ 50 - 0
app/Http/Controllers/Admin/LeaveController.php

xqd
@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Leave;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+class LeaveController extends Controller
+{
+    protected $redirect_index = '/admin/Leave/index';
+
+    protected $view_path = 'admin.leaves.';
+
+    protected $pre_uri = '/admin/Leave/';
+
+    protected $model_name = '请假';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Leave();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc')->get();
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = trim($request->input('keyword'));
+
+            $list = $list->filter(function ($value) use($keyword) {
+                if(!empty($value->student) && !(strpos($value->student->name, $keyword) === false)) {
+                    return true;
+                }
+                if(!empty($value->course) && !(strpos($value->course->name, $keyword) === false)) {
+                    return true;
+                }
+                return false;
+            });
+        }
+
+        $list = $this->paginate($list)->withPath($this->getPaginateUrl());
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+}

+ 80 - 0
app/Http/Controllers/Admin/RemarkController.php

xqd
@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Remark;
+use App\Models\RemarkDetail;
+use Illuminate\Http\Request;
+
+class RemarkController extends Controller
+{
+    protected $redirect_index = '/admin/Remark/index';
+
+    protected $view_path = 'admin.remarks.';
+
+    protected $pre_uri = '/admin/Remark/';
+
+    protected $model_name = '评价';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Remark();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc');
+        $list = $list->get();
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = trim($request->input('keyword'));
+            $list = $list->filter(function ($value) use($keyword) {
+                if(!empty($value->teacher) && !(strpos($value->teacher->name, $keyword) === false)) {
+                    return true;
+                }
+                if(!empty($value->course) && !(strpos($value->course->name, $keyword) === false)) {
+                    return true;
+                }
+                if(!empty($value->student) && !(strpos($value->student->name, $keyword) === false)) {
+                    return true;
+                }
+                return false;
+            });
+        }
+
+        $list = $this->paginate($list);
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+
+    public function detail(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('参数错误');
+        }
+        $list = RemarkDetail::where('remark_id', $item->id)->paginate();
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'detail', compact('item', 'list', 'pre_uri', 'model_name'));
+    }
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+
+        RemarkDetail::where('remark_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 138 - 0
app/Http/Controllers/Admin/RemarkDetailController.php

xqd
@@ -0,0 +1,138 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class RemarkDetailController extends Controller
+{
+    protected $redirect_index = '/admin/RemarkDetail/index';
+
+    protected $view_path = 'admin.remark-details.';
+
+    protected $pre_uri = '/admin/RemarkDetail/';
+
+    protected $model_name = '讲师';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Teacher();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc');
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = '%' . trim($request->input('keyword')) . '%';
+            $list = $list->where('name', 'like', $keyword);
+        }
+
+        $list = $list->paginate()->withPath($this->getPaginateUrl());
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+
+    public function create(Request $request)
+    {
+        $courses = Course::all();
+
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+        return view($this->view_path . 'create', compact('pre_uri', 'model_name', 'model', 'courses', 'teachers'));
+    }
+
+    public function store(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->create($request->input('data'));
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+
+        if(!empty($request->input('courses'))) {
+            TeacherCourse::where('teacher_id', $res->id)->delete();
+            foreach($request->input('courses') as $item) {
+                TeacherCourse::create([
+                    'teacher_id' => $res->id,
+                    'course_id' => $item
+                ]);
+            }
+        }
+
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function edit(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('数据错误!');
+        }
+
+        $courses = Course::all();
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+
+        $ids_string = $item->courses->implode('id', ',');
+        return view($this->view_path . 'edit', compact('item','pre_uri', 'model_name', 'model', 'courses', 'ids_string'));
+    }
+
+    public function update(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('id')) || empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->where('id', $request->input('id'))->update($request->input('data'));
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+
+        $teacher = $this->model->find($request->input('id'));
+        if(empty($teacher)) {
+            return $this->showWarning('找不到讲师!');
+        }
+        if(!empty($request->input('courses'))) {
+            TeacherCourse::where('teacher_id', $teacher->id)->delete();
+            foreach($request->input('courses') as $item) {
+                TeacherCourse::create([
+                    'teacher_id' => $teacher->id,
+                    'course_id' => $item
+                ]);
+            }
+        }
+
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+
+        TeacherCourse::where('teacher_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 146 - 0
app/Http/Controllers/Admin/Student/CourseController.php

xqd
@@ -0,0 +1,146 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Student;
+
+use App\Http\Controllers\Admin\Controller;
+use App\Models\Course;
+use App\Models\Student;
+use App\Models\StudentCourse;
+use App\Models\StudentCourseTeacher;
+use App\Models\Teacher;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+class CourseController extends Controller
+{
+    protected $redirect_index = '/admin/Student/Course/index';
+
+    protected $view_path = 'admin.students.courses.';
+
+    protected $pre_uri = '/admin/Student/Course/';
+
+    protected $model_name = '课程';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new StudentCourse();
+    }
+
+    public function index(Request $request)
+    {
+        if(empty($request->input('student_id')) || empty($student = Student::find($request->input('student_id')))) {
+            return $this->showWarning('找不到学员');
+        }
+        $list = $this->model->where('student_id', $student->id)->orderBy('created_at', 'desc');
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = '%' . trim($request->input('keyword')) . '%';
+            $list = $list->where('name', 'like', $keyword);
+        }
+
+        $list = $list->paginate();
+
+        foreach($list as $item) {
+            $item->end_date = Carbon::createFromTimestamp(strtotime($item->apply_date))->addDays($item->duration)->toDateString();
+            $now = Carbon::now()->toDateString();
+            if($now > $item->end_date) {
+                $item->remain_days = 0;
+            } else {
+                $item->remain_days = Carbon::now()->diffInDays($item->end_date) + 1;
+            }
+        }
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name', 'student'));
+    }
+
+    public function create(Request $request)
+    {
+        if(empty($request->input('student_id')) || empty($student = Student::find($request->input('student_id')))) {
+            return $this->showWarning('找不到学员');
+        }
+        $courses = Course::orderBy('created_at', 'desc')->get();
+        $teachers = Teacher::orderBy('created_at', 'desc')->get();
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+        return view($this->view_path . 'create', compact('pre_uri', 'model_name', 'model', 'courses', 'teachers', 'courses', 'student'));
+    }
+
+    public function store(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $data = $request->input('data');
+        $data['assign_teacher'] = $request->input('assign_teacher') == 1 ? 1 : 2;
+        $res = $this->model->create($data);
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+
+        $res->updateStudentCourseTeachers($request->input('teachers'));
+
+        return $this->showMessage('操作成功', $this->redirect_index . '?student_id=' . $res->student_id);
+    }
+
+    public function edit(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('数据错误!');
+        }
+
+        $courses = Course::orderBy('created_at', 'desc')->get();
+        $teachers = Teacher::orderBy('created_at', 'desc')->get();
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+        return view($this->view_path . 'edit', compact('item','pre_uri', 'model_name', 'model', 'courses', 'teachers'));
+    }
+
+    public function update(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('id')) || empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('找不到合同');
+        }
+
+        $data = $request->input('data');
+        $data['assign_teacher'] = $request->input('assign_teacher') == 1 ? 1 : 2;
+        $res = $this->model->where('id', $item->id)->update($data);
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+        if(!empty($item)) {
+            $item->updateStudentCourseTeachers($request->input('teachers'));
+        }
+
+        return $this->showMessage('操作成功', $this->redirect_index . '?student_id=' . $item->student_id);
+    }
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+        StudentCourseTeacher::where('student_course_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 115 - 0
app/Http/Controllers/Admin/StudentController.php

xqd
@@ -0,0 +1,115 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Course;
+use App\Models\Student;
+use App\Models\StudentCourse;
+use App\Models\StudentCourseTeacher;
+use App\Models\Teacher;
+use App\Models\TeacherStudent;
+use Illuminate\Http\Request;
+
+class StudentController extends Controller
+{
+    protected $redirect_index = '/admin/Student/index';
+
+    protected $view_path = 'admin.students.';
+
+    protected $pre_uri = '/admin/Student/';
+
+    protected $model_name = '学员';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Student();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc');
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = '%' . trim($request->input('keyword')) . '%';
+            $list = $list->where('name', 'like', $keyword);
+        }
+
+        $list = $list->paginate()->withPath($this->getPaginateUrl());
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+
+    public function create(Request $request)
+    {
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+        return view($this->view_path . 'create', compact('pre_uri', 'model_name', 'model', 'courses', 'teachers'));
+    }
+
+    public function store(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->create($request->input('data'));
+
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function edit(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('数据错误!');
+        }
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+
+        return view($this->view_path . 'edit', compact('item','pre_uri', 'model_name', 'model'));
+    }
+
+    public function update(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('id')) || empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->where('id', $request->input('id'))->update($request->input('data'));
+
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+
+        StudentCourse::where('student_id', $item->id)->delete();
+        StudentCourseTeacher::where('student_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 140 - 0
app/Http/Controllers/Admin/TeacherController.php

xqd
@@ -0,0 +1,140 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\Course;
+use App\Models\Teacher;
+use App\Models\TeacherCourse;
+use Illuminate\Http\Request;
+
+class TeacherController extends Controller
+{
+    protected $redirect_index = '/admin/Teacher/index';
+
+    protected $view_path = 'admin.teachers.';
+
+    protected $pre_uri = '/admin/Teacher/';
+
+    protected $model_name = '讲师';
+
+    protected $model;
+
+    public function __construct()
+    {
+        parent::__construct();
+        $this->model = new Teacher();
+    }
+
+    public function index(Request $request)
+    {
+        $list = $this->model->where('id', '>', 0)->orderBy('created_at', 'desc');
+
+        if(!empty($request->input('keyword')) && !empty(trim($request->input('keyword')))) {
+            $keyword = '%' . trim($request->input('keyword')) . '%';
+            $list = $list->where('name', 'like', $keyword);
+        }
+
+        $list = $list->paginate()->withPath($this->getPaginateUrl());
+
+        list($pre_uri, $model_name) = array($this->pre_uri, $this->model_name);
+        return view($this->view_path . 'index', compact('list', 'pre_uri', 'model_name'));
+    }
+
+    public function create(Request $request)
+    {
+        $courses = Course::all();
+
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+        return view($this->view_path . 'create', compact('pre_uri', 'model_name', 'model', 'courses', 'teachers'));
+    }
+
+    public function store(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->create($request->input('data'));
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+
+        if(!empty($request->input('courses'))) {
+            TeacherCourse::where('teacher_id', $res->id)->delete();
+            foreach($request->input('courses') as $item) {
+                TeacherCourse::create([
+                    'teacher_id' => $res->id,
+                    'course_id' => $item
+                ]);
+            }
+        }
+
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function edit(Request $request)
+    {
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('数据错误!');
+        }
+
+        $courses = Course::all();
+        list($pre_uri, $model_name, $model) = array($this->pre_uri, $this->model_name, $this->model);
+
+        $ids_string = $item->courses->implode('id', ',');
+        return view($this->view_path . 'edit', compact('item','pre_uri', 'model_name', 'model', 'courses', 'ids_string'));
+    }
+
+    public function update(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+
+        if(empty($request->input('id')) || empty($request->input('data')) || !is_array($request->input('data'))) {
+            return $this->showWarning('数据错误');
+        }
+
+        $res = $this->model->where('id', $request->input('id'))->update($request->input('data'));
+        if(!$res) {
+            return $this->showWarning('数据库保存失败!');
+        }
+
+        $teacher = $this->model->find($request->input('id'));
+        if(empty($teacher)) {
+            return $this->showWarning('找不到讲师!');
+        }
+        if(!empty($request->input('courses'))) {
+            TeacherCourse::where('teacher_id', $teacher->id)->delete();
+            foreach($request->input('courses') as $item) {
+                TeacherCourse::create([
+                    'teacher_id' => $teacher->id,
+                    'course_id' => $item
+                ]);
+            }
+        }
+
+        return $this->showMessage('操作成功', $this->redirect_index);
+    }
+
+    public function delete(Request $request)
+    {
+        if(!$request->isMethod('POST')) {
+            return $this->showWarning('访问错误');
+        }
+        if(empty($request->input('id')) || empty($item = $this->model->find($request->input('id')))) {
+            return $this->showWarning('访问错误');
+        }
+
+        TeacherCourse::where('teacher_id', $item->id)->delete();
+        $res = $item->delete();
+        if(!$res) {
+            return $this->showWarning('数据库删除失败');
+        }
+        return $this->showMessage('操作成功');
+    }
+}

+ 1 - 0
app/Http/Controllers/Admin/TestController.php

xqd
@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers\Admin;
 
+use Carbon\Carbon;
 use Illuminate\Http\Request;
 
 

+ 79 - 0
app/Http/Controllers/Admin/UploadController.php

xqd
@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Models\BaseAttachmentModel;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use App\Services\Uploader;
+
+class UploadController extends Controller
+{
+    public function ueditorUpload(Request $request)
+    {
+        $CONFIG = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", "", file_get_contents(url('/plugins/ueditor/config.json'))), true);
+        $action = $_GET['action'];
+        switch ($action) {
+            case 'config':
+                $result =  json_encode($CONFIG);
+                break;
+
+            /* 上传图片 */
+            case 'uploadimage':
+                $config = array(
+                    "pathFormat" => $CONFIG['imagePathFormat'],
+                    "maxSize" => $CONFIG['imageMaxSize'],
+                    "allowFiles" => $CONFIG['imageAllowFiles']
+                );
+                $fieldName = $CONFIG['imageFieldName'];
+                $up = new Uploader($fieldName, $config);
+                $fileInfo = $up->getFileInfo();
+                if($fileInfo['state'] == 'SUCCESS') {
+                    BaseAttachmentModel::create([
+                        'path' => $fileInfo['url'],
+                        'name' => $fileInfo['title'],
+//                        'type' => $fileInfo['type'],
+                        'class' => 'ueditor',
+                        'size' => $fileInfo['size'],
+                    ]);
+                }
+                return json_encode($up->getFileInfo());
+            /* 上传涂鸦 */
+            case 'uploadscrawl':
+                /* 上传视频 */
+            case 'uploadvideo':
+                /* 上传文件 */
+            case 'uploadfile':
+
+
+                /* 列出图片 */
+            case 'listimage':
+
+                /* 列出文件 */
+            case 'listfile':
+
+
+                /* 抓取远程文件 */
+            case 'catchimage':
+
+
+            default:
+                $result = json_encode(array(
+                    'state'=> '请求地址出错'
+                ));
+                break;
+        }
+        /* 输出结果 */
+        if (isset($_GET["callback"])) {
+            if (preg_match("/^[\w_]+$/", $_GET["callback"])) {
+                return htmlspecialchars($_GET["callback"]) . '(' . $result . ')';
+            } else {
+                return json_encode(array(
+                    'state'=> 'callback参数不合法'
+                ));
+            }
+        } else {
+            return $result;
+        }
+    }
+}

+ 22 - 0
app/Http/Controllers/TestController.php

xqd
@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Models\StudentCourse;
+use App\Services\Admin\AdminUser;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+
+class TestController extends Controller
+{
+    public function index(Request $request)
+    {
+        $items = collect([
+            collect(['key' => 1, 'value' => 1, 'show_value' => '公告']),
+            collect(['key' => 2, 'value' => 2, 'show_value' => '活动']),
+            collect(['key' => 3, 'value' => 3, 'show_value' => '视频']),
+            collect(['key' => 4, 'value' => 4, 'show_value' => '文章']),
+        ]);
+        dd($items->where('key', 1)->first());
+    }
+}

+ 2 - 1
app/Http/Middleware/VerifyCsrfToken.php

xqd
@@ -14,7 +14,8 @@ class VerifyCsrfToken extends BaseVerifier
     protected $except = [
         //
         "notify",
-        "admin/Base/Attachment/*"
+        "admin/Base/Attachment/*",
+        'admin/upload/*'
     ];
 
 }

+ 23 - 0
app/Models/BaseAttachmentModel.php

xqd xqd
@@ -12,6 +12,8 @@
 
 namespace App\Models;
 
+use Illuminate\Http\UploadedFile;
+
 class BaseAttachmentModel extends BaseModel
 {
     //
@@ -29,4 +31,25 @@ class BaseAttachmentModel extends BaseModel
 
     //分页
     protected $perPage = PAGE_NUMS;
+
+    protected $guarded = [];
+
+    public function upload(UploadedFile $file, $class = '')
+    {
+        $fileName = uniqid() . '.' . $file->getClientOriginalExtension();
+        $date = date("Ymd");
+        $res = $file->storeAs('upload/files' . '/' . $date, $fileName, 'upload');
+
+        if($res) {
+            $res = '/' . $res;
+            self::create([
+                'name' => $fileName,
+                'path' => $res,
+//                'type' => $file->getClientMimeType(),
+                'class' => $class,
+                'size' => $file->getClientSize()
+            ]);
+        }
+        return $res;
+    }
 }

+ 55 - 0
app/Models/CheckCard.php

xqd
@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class CheckCard extends Model
+{
+    protected $table = 'check_cards';
+
+    protected $guarded = [];
+
+    public function student()
+    {
+        return $this->belongsTo('App\Models\Student');
+    }
+
+    public function studentCourse()
+    {
+        return $this->belongsTo('App\Models\StudentCourse');
+    }
+
+    public function course()
+    {
+        return $this->belongsTo('App\Models\Course');
+    }
+
+    public function getDuration()
+    {
+        $res = '';
+        if(!empty($this['begin_date_time']) && !empty($this['end_date_time'])) {
+            $begin_time = strtotime($this['begin_date_time']);
+            $end_time = strtotime($this['end_date_time']);
+            if($end_time > $begin_time) {
+                $diff_time = $end_time - $begin_time;
+                $tmp = $diff_time / 3600;
+                $diff_time = $diff_time % 3600;
+                if(!empty($tmp)) {
+                    $res .= $tmp . '小时';
+                }
+                $tmp = $diff_time / 60;
+                $diff_time = $diff_time % 60;
+                if(!empty($tmp)) {
+                    $res .= $tmp . '分钟';
+                }
+                if(!empty($diff_time)) {
+                    $res .= $diff_time . '秒';
+                }
+                return $res;
+            }
+        }
+
+        return $res;
+    }
+}

+ 36 - 0
app/Models/Content.php

xqd
@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Content extends Model
+{
+    protected $table = 'contents';
+
+    protected $guarded = [];
+
+    protected $types;
+
+    public function __construct(array $attributes = [])
+    {
+        parent::__construct($attributes);
+        $this->types = collect([
+            collect(['key' => 1, 'value' => 1, 'show_value' => '公告']),
+            collect(['key' => 2, 'value' => 2, 'show_value' => '活动']),
+            collect(['key' => 3, 'value' => 3, 'show_value' => '视频']),
+            collect(['key' => 4, 'value' => 4, 'show_value' => '文章']),
+        ]);
+    }
+
+    public function getModelType($type)
+    {
+        return in_array($type, [1, 2, 3, 4]) ? $type : 1;
+    }
+
+    public function getModelName($type)
+    {
+        $item = $this->types->where('key', $type)->first();
+        return empty($item) ? '公告' : $item['show_value'];
+    }
+}

+ 12 - 0
app/Models/Course.php

xqd
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Course extends Model
+{
+    protected $table = 'courses';
+
+    protected $guarded = [];
+}

+ 27 - 0
app/Models/Leave.php

xqd
@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Leave extends Model
+{
+    protected $table = 'leaves';
+
+    protected $guarded = [];
+
+    public function student()
+    {
+        return $this->belongsTo('App\Models\Student');
+    }
+
+    public function course()
+    {
+        return $this->belongsTo('App\Models\Course');
+    }
+
+    public function studentCourse()
+    {
+        return $this->belongsTo('App\Models\StudentCourse');
+    }
+}

+ 27 - 0
app/Models/Remark.php

xqd
@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Remark extends Model
+{
+    protected $table = 'remarks';
+
+    protected $guarded = [];
+
+    public function teacher()
+    {
+        return $this->belongsTo('App\Models\Teacher');
+    }
+
+    public function student()
+    {
+        return $this->belongsTo('App\Models\Student');
+    }
+
+    public function course()
+    {
+        return $this->belongsTo('App\Models\Course');
+    }
+}

+ 12 - 0
app/Models/RemarkDetail.php

xqd
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class RemarkDetail extends Model
+{
+    protected $table = 'remark_details';
+
+    protected $guarded = [];
+}

+ 42 - 0
app/Models/Student.php

xqd
@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Student extends Model
+{
+    protected $table = 'students';
+
+    protected $guarded = [];
+
+    public $marry_list;
+
+    public function __construct(array $attributes = [])
+    {
+        parent::__construct($attributes);
+
+        $this->marry_list = collect([
+            ['key' => 1, 'value' => 1, 'show_value' => '未婚'],
+            ['key' => 2, 'value' => 2, 'show_value' => '已婚'],
+//            ['key' => 3, 'value' => 3, 'show_value' => '离婚'],
+        ]);
+    }
+
+    public function getMarryList()
+    {
+        return $this->marry_list;
+    }
+
+    public function getMarry($key = null)
+    {
+        $key = empty($key) ? $this['marry'] : 1;
+        $item = $this->marry_list->where('key', $key)->first();
+        return empty($item) ? '未婚' : $item['show_value'];
+    }
+
+    public function course()
+    {
+        return $this->belongsTo('App\Models\Course');
+    }
+}

+ 56 - 0
app/Models/StudentCourse.php

xqd
@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class StudentCourse extends Model
+{
+    protected $table = 'student_courses';
+
+    protected $guarded = [];
+
+    public function course()
+    {
+        return $this->hasOne('App\Models\Course', 'id', 'course_id');
+    }
+
+    public function getTeacherNames()
+    {
+        if($this['assign_teacher'] == 1) {
+            return '全部';
+        }
+
+        $ids = $this['studentCourseTeachers']->pluck('teacher_id');
+        return (new Teacher())->whereIn('id', $ids)->get()->implode('name', ',');
+    }
+
+    public function studentCourseTeachers()
+    {
+        return $this->hasMany('App\Models\StudentCourseTeacher');
+    }
+
+    public function getTeacherIds()
+    {
+        if($this['assign_teacher'] == 1) {
+            return collect();
+        }
+        return $this['studentCourseTeachers']->pluck('teacher_id');
+    }
+
+    public function updateStudentCourseTeachers($teachers)
+    {
+        if($this['assign_teacher'] == 2) {
+            if(!empty($teachers) && is_array($teachers)) {
+                StudentCourseTeacher::where('student_course_id', $this['id'])->delete();
+                foreach($teachers as $teacher) {
+                    StudentCourseTeacher::create([
+                        'student_course_id' => $this['id'],
+                        'teacher_id' => $teacher,
+                        'student_id' => $this['student_id']
+                    ]);
+                }
+            }
+        }
+    }
+}

+ 12 - 0
app/Models/StudentCourseTeacher.php

xqd
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class StudentCourseTeacher extends Model
+{
+    protected $table = 'student_course_teachers';
+
+    protected $guarded = [];
+}

+ 17 - 0
app/Models/Teacher.php

xqd
@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Teacher extends Model
+{
+    protected $table = 'teachers';
+
+    protected $guarded = [];
+
+    public function courses()
+    {
+        return $this->belongsToMany('App\Models\Course', 'teacher_courses', 'teacher_id', 'course_id');
+    }
+}

+ 12 - 0
app/Models/TeacherCourse.php

xqd
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TeacherCourse extends Model
+{
+    protected $table = 'teacher_courses';
+
+    protected $guarded = [];
+}

+ 12 - 0
app/Models/TeacherStudent.php

xqd
@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TeacherStudent extends Model
+{
+    protected $table = 'teacher_students';
+
+    protected $guarded = [];
+}

+ 367 - 0
app/Services/Uploader.php

xqd
@@ -0,0 +1,367 @@
+<?php
+
+namespace App\Services;
+
+class Uploader
+{
+    private $fileField; //文件域名
+    private $file; //文件上传对象
+    private $base64; //文件上传对象
+    private $config; //配置信息
+    private $oriName; //原始文件名
+    private $fileName; //新文件名
+    private $fullName; //完整文件名,即从当前配置目录开始的URL
+    private $filePath; //完整文件名,即从当前配置目录开始的URL
+    private $fileSize; //文件大小
+    private $fileType; //文件类型
+    private $stateInfo; //上传状态信息,
+    private $stateMap = array( //上传状态映射表,国际化用户需考虑此处数据的国际化
+        "SUCCESS", //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
+        "文件大小超出 upload_max_filesize 限制",
+        "文件大小超出 MAX_FILE_SIZE 限制",
+        "文件未被完整上传",
+        "没有文件被上传",
+        "上传文件为空",
+        "ERROR_TMP_FILE" => "临时文件错误",
+        "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
+        "ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
+        "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
+        "ERROR_CREATE_DIR" => "目录创建失败",
+        "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
+        "ERROR_FILE_MOVE" => "文件保存时出错",
+        "ERROR_FILE_NOT_FOUND" => "找不到上传文件",
+        "ERROR_WRITE_CONTENT" => "写入文件内容错误",
+        "ERROR_UNKNOWN" => "未知错误",
+        "ERROR_DEAD_LINK" => "链接不可用",
+        "ERROR_HTTP_LINK" => "链接不是http链接",
+        "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确",
+        "INVALID_URL" => "非法 URL",
+        "INVALID_IP" => "非法 IP"
+    );
+
+    /**
+     * 构造函数
+     * @param string $fileField 表单名称
+     * @param array $config 配置项
+     * @param bool $base64 是否解析base64编码,可省略。若开启,则$fileField代表的是base64编码的字符串表单名
+     */
+    public function __construct($fileField, $config, $type = "upload")
+    {
+        $this->fileField = $fileField;
+        $this->config = $config;
+        $this->type = $type;
+        if ($type == "remote") {
+            $this->saveRemote();
+        } else if($type == "base64") {
+            $this->upBase64();
+        } else {
+            $this->upFile();
+        }
+
+//        $this->stateMap['ERROR_TYPE_NOT_ALLOWED'] = iconv('unicode', 'utf-8', $this->stateMap['ERROR_TYPE_NOT_ALLOWED']);
+    }
+
+    /**
+     * 上传文件的主处理方法
+     * @return mixed
+     */
+    private function upFile()
+    {
+        $file = $this->file = $_FILES[$this->fileField];
+        if (!$file) {
+            $this->stateInfo = $this->getStateInfo("ERROR_FILE_NOT_FOUND");
+            return;
+        }
+        if ($this->file['error']) {
+            $this->stateInfo = $this->getStateInfo($file['error']);
+            return;
+        } else if (!file_exists($file['tmp_name'])) {
+            $this->stateInfo = $this->getStateInfo("ERROR_TMP_FILE_NOT_FOUND");
+            return;
+        } else if (!is_uploaded_file($file['tmp_name'])) {
+            $this->stateInfo = $this->getStateInfo("ERROR_TMPFILE");
+            return;
+        }
+
+        $this->oriName = $file['name'];
+        $this->fileSize = $file['size'];
+        $this->fileType = $this->getFileExt();
+        $this->fullName = $this->getFullName();
+        $this->filePath = $this->getFilePath();
+        $this->fileName = $this->getFileName();
+        $dirname = dirname($this->filePath);
+
+        //检查文件大小是否超出限制
+        if (!$this->checkSize()) {
+            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
+            return;
+        }
+
+        //检查是否不允许的文件格式
+        if (!$this->checkType()) {
+            $this->stateInfo = $this->getStateInfo("ERROR_TYPE_NOT_ALLOWED");
+            return;
+        }
+
+        //创建目录失败
+        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR");
+            return;
+        } else if (!is_writeable($dirname)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE");
+            return;
+        }
+
+        //移动文件
+        if (!(move_uploaded_file($file["tmp_name"], $this->filePath) && file_exists($this->filePath))) { //移动失败
+            $this->stateInfo = $this->getStateInfo("ERROR_FILE_MOVE");
+        } else { //移动成功
+            $this->stateInfo = $this->stateMap[0];
+        }
+    }
+
+    /**
+     * 处理base64编码的图片上传
+     * @return mixed
+     */
+    private function upBase64()
+    {
+        $base64Data = $_POST[$this->fileField];
+        $img = base64_decode($base64Data);
+
+        $this->oriName = $this->config['oriName'];
+        $this->fileSize = strlen($img);
+        $this->fileType = $this->getFileExt();
+        $this->fullName = $this->getFullName();
+        $this->filePath = $this->getFilePath();
+        $this->fileName = $this->getFileName();
+        $dirname = dirname($this->filePath);
+
+        //检查文件大小是否超出限制
+        if (!$this->checkSize()) {
+            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
+            return;
+        }
+
+        //创建目录失败
+        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR");
+            return;
+        } else if (!is_writeable($dirname)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE");
+            return;
+        }
+
+        //移动文件
+        if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败
+            $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT");
+        } else { //移动成功
+            $this->stateInfo = $this->stateMap[0];
+        }
+
+    }
+
+    /**
+     * 拉取远程图片
+     * @return mixed
+     */
+    private function saveRemote()
+    {
+        $imgUrl = htmlspecialchars($this->fileField);
+        $imgUrl = str_replace("&amp;", "&", $imgUrl);
+
+        //http开头验证
+        if (strpos($imgUrl, "http") !== 0) {
+            $this->stateInfo = $this->getStateInfo("ERROR_HTTP_LINK");
+            return;
+        }
+
+        preg_match('/(^https*:\/\/[^:\/]+)/', $imgUrl, $matches);
+        $host_with_protocol = count($matches) > 1 ? $matches[1] : '';
+
+        // 判断是否是合法 url
+        if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) {
+            $this->stateInfo = $this->getStateInfo("INVALID_URL");
+            return;
+        }
+
+        preg_match('/^https*:\/\/(.+)/', $host_with_protocol, $matches);
+        $host_without_protocol = count($matches) > 1 ? $matches[1] : '';
+
+        // 此时提取出来的可能是 ip 也有可能是域名,先获取 ip
+        $ip = gethostbyname($host_without_protocol);
+        // 判断是否是私有 ip
+        if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
+            $this->stateInfo = $this->getStateInfo("INVALID_IP");
+            return;
+        }
+
+        //获取请求头并检测死链
+        $heads = get_headers($imgUrl, 1);
+        if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) {
+            $this->stateInfo = $this->getStateInfo("ERROR_DEAD_LINK");
+            return;
+        }
+        //格式验证(扩展名验证和Content-Type验证)
+        $fileType = strtolower(strrchr($imgUrl, '.'));
+        if (!in_array($fileType, $this->config['allowFiles']) || !isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")) {
+            $this->stateInfo = $this->getStateInfo("ERROR_HTTP_CONTENTTYPE");
+            return;
+        }
+
+        //打开输出缓冲区并获取远程图片
+        ob_start();
+        $context = stream_context_create(
+            array('http' => array(
+                'follow_location' => false // don't follow redirects
+            ))
+        );
+        readfile($imgUrl, false, $context);
+        $img = ob_get_contents();
+        ob_end_clean();
+        preg_match("/[\/]([^\/]*)[\.]?[^\.\/]*$/", $imgUrl, $m);
+
+        $this->oriName = $m ? $m[1]:"";
+        $this->fileSize = strlen($img);
+        $this->fileType = $this->getFileExt();
+        $this->fullName = $this->getFullName();
+        $this->filePath = $this->getFilePath();
+        $this->fileName = $this->getFileName();
+        $dirname = dirname($this->filePath);
+
+        //检查文件大小是否超出限制
+        if (!$this->checkSize()) {
+            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
+            return;
+        }
+
+        //创建目录失败
+        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR");
+            return;
+        } else if (!is_writeable($dirname)) {
+            $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE");
+            return;
+        }
+
+        //移动文件
+        if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败
+            $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT");
+        } else { //移动成功
+            $this->stateInfo = $this->stateMap[0];
+        }
+
+    }
+
+    /**
+     * 上传错误检查
+     * @param $errCode
+     * @return string
+     */
+    private function getStateInfo($errCode)
+    {
+        return !$this->stateMap[$errCode] ? $this->stateMap["ERROR_UNKNOWN"] : $this->stateMap[$errCode];
+    }
+
+    /**
+     * 获取文件扩展名
+     * @return string
+     */
+    private function getFileExt()
+    {
+        return strtolower(strrchr($this->oriName, '.'));
+    }
+
+    /**
+     * 重命名文件
+     * @return string
+     */
+    private function getFullName()
+    {
+        //替换日期事件
+        $t = time();
+        $d = explode('-', date("Y-y-m-d-H-i-s"));
+        $format = $this->config["pathFormat"];
+        $format = str_replace("{yyyy}", $d[0], $format);
+        $format = str_replace("{yy}", $d[1], $format);
+        $format = str_replace("{mm}", $d[2], $format);
+        $format = str_replace("{dd}", $d[3], $format);
+        $format = str_replace("{hh}", $d[4], $format);
+        $format = str_replace("{ii}", $d[5], $format);
+        $format = str_replace("{ss}", $d[6], $format);
+        $format = str_replace("{time}", $t, $format);
+
+        //过滤文件名的非法自负,并替换文件名
+        $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.'));
+        $oriName = preg_replace("/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName);
+        $format = str_replace("{filename}", $oriName, $format);
+
+        //替换随机字符串
+        $randNum = rand(1, 10000000000) . rand(1, 10000000000);
+        if (preg_match("/\{rand\:([\d]*)\}/i", $format, $matches)) {
+            $format = preg_replace("/\{rand\:[\d]*\}/i", substr($randNum, 0, $matches[1]), $format);
+        }
+
+        $ext = $this->getFileExt();
+        return $format . $ext;
+    }
+
+    /**
+     * 获取文件名
+     * @return string
+     */
+    private function getFileName () {
+        return substr($this->filePath, strrpos($this->filePath, '/') + 1);
+    }
+
+    /**
+     * 获取文件完整路径
+     * @return string
+     */
+    private function getFilePath()
+    {
+        $fullname = $this->fullName;
+        $rootPath = $_SERVER['DOCUMENT_ROOT'];
+
+        if (substr($fullname, 0, 1) != '/') {
+            $fullname = '/' . $fullname;
+        }
+
+        return $rootPath . $fullname;
+    }
+
+    /**
+     * 文件类型检测
+     * @return bool
+     */
+    private function checkType()
+    {
+        return in_array($this->getFileExt(), $this->config["allowFiles"]);
+    }
+
+    /**
+     * 文件大小检测
+     * @return bool
+     */
+    private function  checkSize()
+    {
+        return $this->fileSize <= ($this->config["maxSize"]);
+    }
+
+    /**
+     * 获取当前上传成功文件的各项信息
+     * @return array
+     */
+    public function getFileInfo()
+    {
+        return array(
+            "state" => $this->stateInfo,
+            "url" => $this->fullName,
+            "title" => $this->fileName,
+            "original" => $this->oriName,
+            "type" => $this->fileType,
+            "size" => $this->fileSize
+        );
+    }
+
+}

+ 2 - 1
config/app.php

xqd
@@ -64,7 +64,8 @@ return [
     |
     */
 
-    'timezone' => 'UTC',
+//    'timezone' => 'UTC',
+    'timezone' => 'PRC',
 
     /*
     |--------------------------------------------------------------------------

+ 7 - 0
config/filesystems.php

xqd
@@ -68,6 +68,13 @@ return [
             'bucket' => env('AWS_BUCKET'),
         ],
 
+        'upload' => [
+            'driver' => 'local',
+            'root' => public_path(),
+            'url' => env('APP_URL'),
+            'visibility' => 'public',
+        ],
+
     ],
 
 ];

+ 1 - 1
database/migrations/2017_11_10_145204_create_cache_table.php

xqd
@@ -14,7 +14,7 @@ class CreateCacheTable extends Migration
     public function up()
     {
         Schema::create('cache', function (Blueprint $table) {
-            $table->string('key')->unique();
+            $table->string('key', 200)->unique();
             $table->text('value');
             $table->integer('expiration');
         });

+ 39 - 0
database/migrations/2018_06_21_011003_create_students_table.php

xqd
@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateStudentsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('students', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('name', 200)->nullable()->comment('学员姓名');
+            $table->string('age', 200)->nullable()->comment('年龄');
+            $table->tinyInteger('marry')->nullable()->default(1)->comment('婚姻状况:1未婚;2已婚;3离婚');
+            $table->string('job', 200)->nullable()->comment('职业');
+            $table->string('work_addr', 200)->nullable()->comment('工作地点');
+            $table->string('addr', 200)->nullable()->comment('住址');
+            $table->string('short_leave_times', 50)->default(0)->nullable()->comment('请假次数(短假)');
+            $table->string('long_leave_times', 50)->default(0)->nullable()->comment('请假次数(长假)');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('students');
+    }
+}

+ 32 - 0
database/migrations/2018_06_21_082321_create_courses_table.php

xqd
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateCoursesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('courses', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('name', '200')->nullable()->comment('课程名称');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('courses');
+    }
+}

+ 32 - 0
database/migrations/2018_06_21_082621_create_teachers_table.php

xqd
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateTeachersTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('teachers', function (Blueprint $table) {
+            $table->increments('id');
+            $table->string('name', 200)->nullable()->comment('姓名');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('teachers');
+    }
+}

+ 33 - 0
database/migrations/2018_06_21_114238_create_teacher_students_table.php

xqd
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateTeacherStudentsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('teacher_students', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('teacher_id')->nullable()->comment('讲师ID');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('teacher_students');
+    }
+}

+ 37 - 0
database/migrations/2018_06_22_153652_create_student_courses_table.php

xqd
@@ -0,0 +1,37 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateStudentCoursesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('student_courses', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->unsignedInteger('course_id')->nullable()->comment('课程ID');
+            $table->string('number', 200)->nullable()->comment('合同编号');
+            $table->date('apply_date')->nullable()->comment('报名日期');
+            $table->string('duration', 200)->nullable()->comment('课程持续时间');
+            $table->tinyInteger('assign_teacher')->nullable()->default(1)->comment('讲师分配:1全部;2指定');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('student_courses');
+    }
+}

+ 34 - 0
database/migrations/2018_06_22_154453_create_student_course_teachers_table.php

xqd
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateStudentCourseTeachersTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('student_course_teachers', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('student_course_id')->nullable()->comment('合同ID');
+            $table->unsignedInteger('teacher_id')->nullable()->comment('讲师ID');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('student_course_teachers');
+    }
+}

+ 35 - 0
database/migrations/2018_06_23_132837_create_check_cards_table.php

xqd
@@ -0,0 +1,35 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateCheckCardsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('check_cards', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->unsignedInteger('student_course_id')->nullable()->comment('所选课程ID');
+            $table->dateTime('begin_date_time')->nullable()->comment('开始打卡时间');
+            $table->dateTime('end_date_time')->nullable()->comment('结束打卡时间');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('check_cards');
+    }
+}

+ 30 - 0
database/migrations/2018_06_23_133738_add_course_id_to_check_card.php

xqd
@@ -0,0 +1,30 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddCourseIdToCheckCard extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('check_cards', function (Blueprint $table) {
+            $table->unsignedInteger('course_id')->nullable()->after('student_id')->comment('课程ID');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        //
+    }
+}

+ 37 - 0
database/migrations/2018_06_25_230153_create_leaves_table.php

xqd
@@ -0,0 +1,37 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateLeavesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('leaves', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->unsignedInteger('course_id')->nullable()->comment('课程ID');
+            $table->unsignedInteger('student_course_id')->nullable()->comment('学员课程(合同)ID');
+            $table->tinyInteger('type')->nullable()->comment('请假类型:1短假;2长假');
+            $table->string('days', 50)->nullable()->comment('天数');
+            $table->string('remark', 200)->nullable()->comment('说明');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('leaves');
+    }
+}

+ 30 - 0
database/migrations/2018_06_25_231401_add_date_to_leaves.php

xqd
@@ -0,0 +1,30 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddDateToLeaves extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('leaves', function (Blueprint $table) {
+            $table->date('date')->nullable()->after('student_course_id')->comment('请假日期');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        //
+    }
+}

+ 34 - 0
database/migrations/2018_06_28_092245_create_remarks_table.php

xqd
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateRemarksTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('remarks', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('teacher_id')->nullable()->comment('讲师ID');
+            $table->unsignedInteger('course_id')->nullable()->comment('课程ID');
+            $table->unsignedInteger('student_id')->nullable()->comment('学员ID');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('remarks');
+    }
+}

+ 34 - 0
database/migrations/2018_06_28_092556_create_remark_details_table.php

xqd
@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateRemarkDetailsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('remark_details', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('remark_id')->nullable()->comment('评价ID');
+            $table->string('question', 200)->nullable()->comment('问题');
+            $table->string('score', 50)->nullable()->comment('评分');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('remark_details');
+    }
+}

+ 33 - 0
database/migrations/2018_06_28_093400_create_teacher_courses_table.php

xqd
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateTeacherCoursesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('teacher_courses', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('course_id')->nullable()->comment('课程ID');
+            $table->unsignedInteger('teacher_id')->nullable()->comment('学员ID');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('teacher_courses');
+    }
+}

+ 35 - 0
database/migrations/2018_06_29_231340_create_contents_table.php

xqd
@@ -0,0 +1,35 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateContentsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('contents', function (Blueprint $table) {
+            $table->increments('id');
+            $table->tinyInteger('type')->nullable()->comment('类型:1公告;2活动;3视频;4文章');
+            $table->string('title', 200)->nullable()->comment('标题');
+            $table->text('content')->nullable()->comment('内容');
+            $table->unsignedInteger('sort')->nullable()->default(0)->comment('排序');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('contents');
+    }
+}

+ 28 - 0
database/seeds/CheckCardSeeder.php

xqd
@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Seeder;
+
+class CheckCardSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $faker = Faker\Factory::create('zh_CN');
+        $student_courses = \App\Models\StudentCourse::all();
+        foreach($student_courses as $student_course) {
+            $begin_date_time = $faker->dateTimeBetween('-1 years', 'now', 'PRC')->format('Y-m-d H:i:s');
+            $end_date_time = \Carbon\Carbon::createFromTimestamp(strtotime($begin_date_time))->addHour()->toDateTimeString();
+            \App\Models\CheckCard::create([
+                'student_id' => $student_course->student_id,
+                'course_id' => $student_course->course_id,
+                'student_course_id' => $student_course->id,
+                'begin_date_time' => $begin_date_time,
+                'end_date_time' => $end_date_time
+            ]);
+        }
+    }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 0
public/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 0
public/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css


+ 2035 - 0
public/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.js

xqd
@@ -0,0 +1,2035 @@
+/*!
+ * Datepicker for Bootstrap v1.8.0 (https://github.com/uxsolutions/bootstrap-datepicker)
+ *
+ * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ */
+
+(function(factory){
+    if (typeof define === 'function' && define.amd) {
+        define(['jquery'], factory);
+    } else if (typeof exports === 'object') {
+        factory(require('jquery'));
+    } else {
+        factory(jQuery);
+    }
+}(function($, undefined){
+	function UTCDate(){
+		return new Date(Date.UTC.apply(Date, arguments));
+	}
+	function UTCToday(){
+		var today = new Date();
+		return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
+	}
+	function isUTCEquals(date1, date2) {
+		return (
+			date1.getUTCFullYear() === date2.getUTCFullYear() &&
+			date1.getUTCMonth() === date2.getUTCMonth() &&
+			date1.getUTCDate() === date2.getUTCDate()
+		);
+	}
+	function alias(method, deprecationMsg){
+		return function(){
+			if (deprecationMsg !== undefined) {
+				$.fn.datepicker.deprecated(deprecationMsg);
+			}
+
+			return this[method].apply(this, arguments);
+		};
+	}
+	function isValidDate(d) {
+		return d && !isNaN(d.getTime());
+	}
+
+	var DateArray = (function(){
+		var extras = {
+			get: function(i){
+				return this.slice(i)[0];
+			},
+			contains: function(d){
+				// Array.indexOf is not cross-browser;
+				// $.inArray doesn't work with Dates
+				var val = d && d.valueOf();
+				for (var i=0, l=this.length; i < l; i++)
+          // Use date arithmetic to allow dates with different times to match
+          if (0 <= this[i].valueOf() - val && this[i].valueOf() - val < 1000*60*60*24)
+						return i;
+				return -1;
+			},
+			remove: function(i){
+				this.splice(i,1);
+			},
+			replace: function(new_array){
+				if (!new_array)
+					return;
+				if (!$.isArray(new_array))
+					new_array = [new_array];
+				this.clear();
+				this.push.apply(this, new_array);
+			},
+			clear: function(){
+				this.length = 0;
+			},
+			copy: function(){
+				var a = new DateArray();
+				a.replace(this);
+				return a;
+			}
+		};
+
+		return function(){
+			var a = [];
+			a.push.apply(a, arguments);
+			$.extend(a, extras);
+			return a;
+		};
+	})();
+
+
+	// Picker object
+
+	var Datepicker = function(element, options){
+		$.data(element, 'datepicker', this);
+		this._process_options(options);
+
+		this.dates = new DateArray();
+		this.viewDate = this.o.defaultViewDate;
+		this.focusDate = null;
+
+		this.element = $(element);
+		this.isInput = this.element.is('input');
+		this.inputField = this.isInput ? this.element : this.element.find('input');
+		this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
+		if (this.component && this.component.length === 0)
+			this.component = false;
+		this.isInline = !this.component && this.element.is('div');
+
+		this.picker = $(DPGlobal.template);
+
+		// Checking templates and inserting
+		if (this._check_template(this.o.templates.leftArrow)) {
+			this.picker.find('.prev').html(this.o.templates.leftArrow);
+		}
+
+		if (this._check_template(this.o.templates.rightArrow)) {
+			this.picker.find('.next').html(this.o.templates.rightArrow);
+		}
+
+		this._buildEvents();
+		this._attachEvents();
+
+		if (this.isInline){
+			this.picker.addClass('datepicker-inline').appendTo(this.element);
+		}
+		else {
+			this.picker.addClass('datepicker-dropdown dropdown-menu');
+		}
+
+		if (this.o.rtl){
+			this.picker.addClass('datepicker-rtl');
+		}
+
+		if (this.o.calendarWeeks) {
+			this.picker.find('.datepicker-days .datepicker-switch, thead .datepicker-title, tfoot .today, tfoot .clear')
+				.attr('colspan', function(i, val){
+					return Number(val) + 1;
+				});
+		}
+
+		this._process_options({
+			startDate: this._o.startDate,
+			endDate: this._o.endDate,
+			daysOfWeekDisabled: this.o.daysOfWeekDisabled,
+			daysOfWeekHighlighted: this.o.daysOfWeekHighlighted,
+			datesDisabled: this.o.datesDisabled
+		});
+
+		this._allow_update = false;
+		this.setViewMode(this.o.startView);
+		this._allow_update = true;
+
+		this.fillDow();
+		this.fillMonths();
+
+		this.update();
+
+		if (this.isInline){
+			this.show();
+		}
+	};
+
+	Datepicker.prototype = {
+		constructor: Datepicker,
+
+		_resolveViewName: function(view){
+			$.each(DPGlobal.viewModes, function(i, viewMode){
+				if (view === i || $.inArray(view, viewMode.names) !== -1){
+					view = i;
+					return false;
+				}
+			});
+
+			return view;
+		},
+
+		_resolveDaysOfWeek: function(daysOfWeek){
+			if (!$.isArray(daysOfWeek))
+				daysOfWeek = daysOfWeek.split(/[,\s]*/);
+			return $.map(daysOfWeek, Number);
+		},
+
+		_check_template: function(tmp){
+			try {
+				// If empty
+				if (tmp === undefined || tmp === "") {
+					return false;
+				}
+				// If no html, everything ok
+				if ((tmp.match(/[<>]/g) || []).length <= 0) {
+					return true;
+				}
+				// Checking if html is fine
+				var jDom = $(tmp);
+				return jDom.length > 0;
+			}
+			catch (ex) {
+				return false;
+			}
+		},
+
+		_process_options: function(opts){
+			// Store raw options for reference
+			this._o = $.extend({}, this._o, opts);
+			// Processed options
+			var o = this.o = $.extend({}, this._o);
+
+			// Check if "de-DE" style date is available, if not language should
+			// fallback to 2 letter code eg "de"
+			var lang = o.language;
+			if (!dates[lang]){
+				lang = lang.split('-')[0];
+				if (!dates[lang])
+					lang = defaults.language;
+			}
+			o.language = lang;
+
+			// Retrieve view index from any aliases
+			o.startView = this._resolveViewName(o.startView);
+			o.minViewMode = this._resolveViewName(o.minViewMode);
+			o.maxViewMode = this._resolveViewName(o.maxViewMode);
+
+			// Check view is between min and max
+			o.startView = Math.max(this.o.minViewMode, Math.min(this.o.maxViewMode, o.startView));
+
+			// true, false, or Number > 0
+			if (o.multidate !== true){
+				o.multidate = Number(o.multidate) || false;
+				if (o.multidate !== false)
+					o.multidate = Math.max(0, o.multidate);
+			}
+			o.multidateSeparator = String(o.multidateSeparator);
+
+			o.weekStart %= 7;
+			o.weekEnd = (o.weekStart + 6) % 7;
+
+			var format = DPGlobal.parseFormat(o.format);
+			if (o.startDate !== -Infinity){
+				if (!!o.startDate){
+					if (o.startDate instanceof Date)
+						o.startDate = this._local_to_utc(this._zero_time(o.startDate));
+					else
+						o.startDate = DPGlobal.parseDate(o.startDate, format, o.language, o.assumeNearbyYear);
+				}
+				else {
+					o.startDate = -Infinity;
+				}
+			}
+			if (o.endDate !== Infinity){
+				if (!!o.endDate){
+					if (o.endDate instanceof Date)
+						o.endDate = this._local_to_utc(this._zero_time(o.endDate));
+					else
+						o.endDate = DPGlobal.parseDate(o.endDate, format, o.language, o.assumeNearbyYear);
+				}
+				else {
+					o.endDate = Infinity;
+				}
+			}
+
+			o.daysOfWeekDisabled = this._resolveDaysOfWeek(o.daysOfWeekDisabled||[]);
+			o.daysOfWeekHighlighted = this._resolveDaysOfWeek(o.daysOfWeekHighlighted||[]);
+
+			o.datesDisabled = o.datesDisabled||[];
+			if (!$.isArray(o.datesDisabled)) {
+				o.datesDisabled = o.datesDisabled.split(',');
+			}
+			o.datesDisabled = $.map(o.datesDisabled, function(d){
+				return DPGlobal.parseDate(d, format, o.language, o.assumeNearbyYear);
+			});
+
+			var plc = String(o.orientation).toLowerCase().split(/\s+/g),
+				_plc = o.orientation.toLowerCase();
+			plc = $.grep(plc, function(word){
+				return /^auto|left|right|top|bottom$/.test(word);
+			});
+			o.orientation = {x: 'auto', y: 'auto'};
+			if (!_plc || _plc === 'auto')
+				; // no action
+			else if (plc.length === 1){
+				switch (plc[0]){
+					case 'top':
+					case 'bottom':
+						o.orientation.y = plc[0];
+						break;
+					case 'left':
+					case 'right':
+						o.orientation.x = plc[0];
+						break;
+				}
+			}
+			else {
+				_plc = $.grep(plc, function(word){
+					return /^left|right$/.test(word);
+				});
+				o.orientation.x = _plc[0] || 'auto';
+
+				_plc = $.grep(plc, function(word){
+					return /^top|bottom$/.test(word);
+				});
+				o.orientation.y = _plc[0] || 'auto';
+			}
+			if (o.defaultViewDate instanceof Date || typeof o.defaultViewDate === 'string') {
+				o.defaultViewDate = DPGlobal.parseDate(o.defaultViewDate, format, o.language, o.assumeNearbyYear);
+			} else if (o.defaultViewDate) {
+				var year = o.defaultViewDate.year || new Date().getFullYear();
+				var month = o.defaultViewDate.month || 0;
+				var day = o.defaultViewDate.day || 1;
+				o.defaultViewDate = UTCDate(year, month, day);
+			} else {
+				o.defaultViewDate = UTCToday();
+			}
+		},
+		_events: [],
+		_secondaryEvents: [],
+		_applyEvents: function(evs){
+			for (var i=0, el, ch, ev; i < evs.length; i++){
+				el = evs[i][0];
+				if (evs[i].length === 2){
+					ch = undefined;
+					ev = evs[i][1];
+				} else if (evs[i].length === 3){
+					ch = evs[i][1];
+					ev = evs[i][2];
+				}
+				el.on(ev, ch);
+			}
+		},
+		_unapplyEvents: function(evs){
+			for (var i=0, el, ev, ch; i < evs.length; i++){
+				el = evs[i][0];
+				if (evs[i].length === 2){
+					ch = undefined;
+					ev = evs[i][1];
+				} else if (evs[i].length === 3){
+					ch = evs[i][1];
+					ev = evs[i][2];
+				}
+				el.off(ev, ch);
+			}
+		},
+		_buildEvents: function(){
+            var events = {
+                keyup: $.proxy(function(e){
+                    if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1)
+                        this.update();
+                }, this),
+                keydown: $.proxy(this.keydown, this),
+                paste: $.proxy(this.paste, this)
+            };
+
+            if (this.o.showOnFocus === true) {
+                events.focus = $.proxy(this.show, this);
+            }
+
+            if (this.isInput) { // single input
+                this._events = [
+                    [this.element, events]
+                ];
+            }
+            // component: input + button
+            else if (this.component && this.inputField.length) {
+                this._events = [
+                    // For components that are not readonly, allow keyboard nav
+                    [this.inputField, events],
+                    [this.component, {
+                        click: $.proxy(this.show, this)
+                    }]
+                ];
+            }
+			else {
+				this._events = [
+					[this.element, {
+						click: $.proxy(this.show, this),
+						keydown: $.proxy(this.keydown, this)
+					}]
+				];
+			}
+			this._events.push(
+				// Component: listen for blur on element descendants
+				[this.element, '*', {
+					blur: $.proxy(function(e){
+						this._focused_from = e.target;
+					}, this)
+				}],
+				// Input: listen for blur on element
+				[this.element, {
+					blur: $.proxy(function(e){
+						this._focused_from = e.target;
+					}, this)
+				}]
+			);
+
+			if (this.o.immediateUpdates) {
+				// Trigger input updates immediately on changed year/month
+				this._events.push([this.element, {
+					'changeYear changeMonth': $.proxy(function(e){
+						this.update(e.date);
+					}, this)
+				}]);
+			}
+
+			this._secondaryEvents = [
+				[this.picker, {
+					click: $.proxy(this.click, this)
+				}],
+				[this.picker, '.prev, .next', {
+					click: $.proxy(this.navArrowsClick, this)
+				}],
+				[this.picker, '.day:not(.disabled)', {
+					click: $.proxy(this.dayCellClick, this)
+				}],
+				[$(window), {
+					resize: $.proxy(this.place, this)
+				}],
+				[$(document), {
+					'mousedown touchstart': $.proxy(function(e){
+						// Clicked outside the datepicker, hide it
+						if (!(
+							this.element.is(e.target) ||
+							this.element.find(e.target).length ||
+							this.picker.is(e.target) ||
+							this.picker.find(e.target).length ||
+							this.isInline
+						)){
+							this.hide();
+						}
+					}, this)
+				}]
+			];
+		},
+		_attachEvents: function(){
+			this._detachEvents();
+			this._applyEvents(this._events);
+		},
+		_detachEvents: function(){
+			this._unapplyEvents(this._events);
+		},
+		_attachSecondaryEvents: function(){
+			this._detachSecondaryEvents();
+			this._applyEvents(this._secondaryEvents);
+		},
+		_detachSecondaryEvents: function(){
+			this._unapplyEvents(this._secondaryEvents);
+		},
+		_trigger: function(event, altdate){
+			var date = altdate || this.dates.get(-1),
+				local_date = this._utc_to_local(date);
+
+			this.element.trigger({
+				type: event,
+				date: local_date,
+				viewMode: this.viewMode,
+				dates: $.map(this.dates, this._utc_to_local),
+				format: $.proxy(function(ix, format){
+					if (arguments.length === 0){
+						ix = this.dates.length - 1;
+						format = this.o.format;
+					} else if (typeof ix === 'string'){
+						format = ix;
+						ix = this.dates.length - 1;
+					}
+					format = format || this.o.format;
+					var date = this.dates.get(ix);
+					return DPGlobal.formatDate(date, format, this.o.language);
+				}, this)
+			});
+		},
+
+		show: function(){
+			if (this.inputField.prop('disabled') || (this.inputField.prop('readonly') && this.o.enableOnReadonly === false))
+				return;
+			if (!this.isInline)
+				this.picker.appendTo(this.o.container);
+			this.place();
+			this.picker.show();
+			this._attachSecondaryEvents();
+			this._trigger('show');
+			if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) {
+				$(this.element).blur();
+			}
+			return this;
+		},
+
+		hide: function(){
+			if (this.isInline || !this.picker.is(':visible'))
+				return this;
+			this.focusDate = null;
+			this.picker.hide().detach();
+			this._detachSecondaryEvents();
+			this.setViewMode(this.o.startView);
+
+			if (this.o.forceParse && this.inputField.val())
+				this.setValue();
+			this._trigger('hide');
+			return this;
+		},
+
+		destroy: function(){
+			this.hide();
+			this._detachEvents();
+			this._detachSecondaryEvents();
+			this.picker.remove();
+			delete this.element.data().datepicker;
+			if (!this.isInput){
+				delete this.element.data().date;
+			}
+			return this;
+		},
+
+		paste: function(e){
+			var dateString;
+			if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.types
+				&& $.inArray('text/plain', e.originalEvent.clipboardData.types) !== -1) {
+				dateString = e.originalEvent.clipboardData.getData('text/plain');
+			} else if (window.clipboardData) {
+				dateString = window.clipboardData.getData('Text');
+			} else {
+				return;
+			}
+			this.setDate(dateString);
+			this.update();
+			e.preventDefault();
+		},
+
+		_utc_to_local: function(utc){
+			if (!utc) {
+				return utc;
+			}
+
+			var local = new Date(utc.getTime() + (utc.getTimezoneOffset() * 60000));
+
+			if (local.getTimezoneOffset() !== utc.getTimezoneOffset()) {
+				local = new Date(utc.getTime() + (local.getTimezoneOffset() * 60000));
+			}
+
+			return local;
+		},
+		_local_to_utc: function(local){
+			return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000));
+		},
+		_zero_time: function(local){
+			return local && new Date(local.getFullYear(), local.getMonth(), local.getDate());
+		},
+		_zero_utc_time: function(utc){
+			return utc && UTCDate(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
+		},
+
+		getDates: function(){
+			return $.map(this.dates, this._utc_to_local);
+		},
+
+		getUTCDates: function(){
+			return $.map(this.dates, function(d){
+				return new Date(d);
+			});
+		},
+
+		getDate: function(){
+			return this._utc_to_local(this.getUTCDate());
+		},
+
+		getUTCDate: function(){
+			var selected_date = this.dates.get(-1);
+			if (selected_date !== undefined) {
+				return new Date(selected_date);
+			} else {
+				return null;
+			}
+		},
+
+		clearDates: function(){
+			this.inputField.val('');
+			this.update();
+			this._trigger('changeDate');
+
+			if (this.o.autoclose) {
+				this.hide();
+			}
+		},
+
+		setDates: function(){
+			var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
+			this.update.apply(this, args);
+			this._trigger('changeDate');
+			this.setValue();
+			return this;
+		},
+
+		setUTCDates: function(){
+			var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
+			this.setDates.apply(this, $.map(args, this._utc_to_local));
+			return this;
+		},
+
+		setDate: alias('setDates'),
+		setUTCDate: alias('setUTCDates'),
+		remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead'),
+
+		setValue: function(){
+			var formatted = this.getFormattedDate();
+			this.inputField.val(formatted);
+			return this;
+		},
+
+		getFormattedDate: function(format){
+			if (format === undefined)
+				format = this.o.format;
+
+			var lang = this.o.language;
+			return $.map(this.dates, function(d){
+				return DPGlobal.formatDate(d, format, lang);
+			}).join(this.o.multidateSeparator);
+		},
+
+		getStartDate: function(){
+			return this.o.startDate;
+		},
+
+		setStartDate: function(startDate){
+			this._process_options({startDate: startDate});
+			this.update();
+			this.updateNavArrows();
+			return this;
+		},
+
+		getEndDate: function(){
+			return this.o.endDate;
+		},
+
+		setEndDate: function(endDate){
+			this._process_options({endDate: endDate});
+			this.update();
+			this.updateNavArrows();
+			return this;
+		},
+
+		setDaysOfWeekDisabled: function(daysOfWeekDisabled){
+			this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
+			this.update();
+			return this;
+		},
+
+		setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){
+			this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted});
+			this.update();
+			return this;
+		},
+
+		setDatesDisabled: function(datesDisabled){
+			this._process_options({datesDisabled: datesDisabled});
+			this.update();
+			return this;
+		},
+
+		place: function(){
+			if (this.isInline)
+				return this;
+			var calendarWidth = this.picker.outerWidth(),
+				calendarHeight = this.picker.outerHeight(),
+				visualPadding = 10,
+				container = $(this.o.container),
+				windowWidth = container.width(),
+				scrollTop = this.o.container === 'body' ? $(document).scrollTop() : container.scrollTop(),
+				appendOffset = container.offset();
+
+			var parentsZindex = [0];
+			this.element.parents().each(function(){
+				var itemZIndex = $(this).css('z-index');
+				if (itemZIndex !== 'auto' && Number(itemZIndex) !== 0) parentsZindex.push(Number(itemZIndex));
+			});
+			var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset;
+			var offset = this.component ? this.component.parent().offset() : this.element.offset();
+			var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
+			var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
+			var left = offset.left - appendOffset.left;
+			var top = offset.top - appendOffset.top;
+
+			if (this.o.container !== 'body') {
+				top += scrollTop;
+			}
+
+			this.picker.removeClass(
+				'datepicker-orient-top datepicker-orient-bottom '+
+				'datepicker-orient-right datepicker-orient-left'
+			);
+
+			if (this.o.orientation.x !== 'auto'){
+				this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
+				if (this.o.orientation.x === 'right')
+					left -= calendarWidth - width;
+			}
+			// auto x orientation is best-placement: if it crosses a window
+			// edge, fudge it sideways
+			else {
+				if (offset.left < 0) {
+					// component is outside the window on the left side. Move it into visible range
+					this.picker.addClass('datepicker-orient-left');
+					left -= offset.left - visualPadding;
+				} else if (left + calendarWidth > windowWidth) {
+					// the calendar passes the widow right edge. Align it to component right side
+					this.picker.addClass('datepicker-orient-right');
+					left += width - calendarWidth;
+				} else {
+					if (this.o.rtl) {
+						// Default to right
+						this.picker.addClass('datepicker-orient-right');
+					} else {
+						// Default to left
+						this.picker.addClass('datepicker-orient-left');
+					}
+				}
+			}
+
+			// auto y orientation is best-situation: top or bottom, no fudging,
+			// decision based on which shows more of the calendar
+			var yorient = this.o.orientation.y,
+				top_overflow;
+			if (yorient === 'auto'){
+				top_overflow = -scrollTop + top - calendarHeight;
+				yorient = top_overflow < 0 ? 'bottom' : 'top';
+			}
+
+			this.picker.addClass('datepicker-orient-' + yorient);
+			if (yorient === 'top')
+				top -= calendarHeight + parseInt(this.picker.css('padding-top'));
+			else
+				top += height;
+
+			if (this.o.rtl) {
+				var right = windowWidth - (left + width);
+				this.picker.css({
+					top: top,
+					right: right,
+					zIndex: zIndex
+				});
+			} else {
+				this.picker.css({
+					top: top,
+					left: left,
+					zIndex: zIndex
+				});
+			}
+			return this;
+		},
+
+		_allow_update: true,
+		update: function(){
+			if (!this._allow_update)
+				return this;
+
+			var oldDates = this.dates.copy(),
+				dates = [],
+				fromArgs = false;
+			if (arguments.length){
+				$.each(arguments, $.proxy(function(i, date){
+					if (date instanceof Date)
+						date = this._local_to_utc(date);
+					dates.push(date);
+				}, this));
+				fromArgs = true;
+			} else {
+				dates = this.isInput
+						? this.element.val()
+						: this.element.data('date') || this.inputField.val();
+				if (dates && this.o.multidate)
+					dates = dates.split(this.o.multidateSeparator);
+				else
+					dates = [dates];
+				delete this.element.data().date;
+			}
+
+			dates = $.map(dates, $.proxy(function(date){
+				return DPGlobal.parseDate(date, this.o.format, this.o.language, this.o.assumeNearbyYear);
+			}, this));
+			dates = $.grep(dates, $.proxy(function(date){
+				return (
+					!this.dateWithinRange(date) ||
+					!date
+				);
+			}, this), true);
+			this.dates.replace(dates);
+
+			if (this.o.updateViewDate) {
+				if (this.dates.length)
+					this.viewDate = new Date(this.dates.get(-1));
+				else if (this.viewDate < this.o.startDate)
+					this.viewDate = new Date(this.o.startDate);
+				else if (this.viewDate > this.o.endDate)
+					this.viewDate = new Date(this.o.endDate);
+				else
+					this.viewDate = this.o.defaultViewDate;
+			}
+
+			if (fromArgs){
+				// setting date by clicking
+				this.setValue();
+				this.element.change();
+			}
+			else if (this.dates.length){
+				// setting date by typing
+				if (String(oldDates) !== String(this.dates) && fromArgs) {
+					this._trigger('changeDate');
+					this.element.change();
+				}
+			}
+			if (!this.dates.length && oldDates.length) {
+				this._trigger('clearDate');
+				this.element.change();
+			}
+
+			this.fill();
+			return this;
+		},
+
+		fillDow: function(){
+      if (this.o.showWeekDays) {
+			var dowCnt = this.o.weekStart,
+				html = '<tr>';
+			if (this.o.calendarWeeks){
+				html += '<th class="cw">&#160;</th>';
+			}
+			while (dowCnt < this.o.weekStart + 7){
+				html += '<th class="dow';
+        if ($.inArray(dowCnt, this.o.daysOfWeekDisabled) !== -1)
+          html += ' disabled';
+        html += '">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
+			}
+			html += '</tr>';
+			this.picker.find('.datepicker-days thead').append(html);
+      }
+		},
+
+		fillMonths: function(){
+      var localDate = this._utc_to_local(this.viewDate);
+			var html = '';
+			var focused;
+			for (var i = 0; i < 12; i++){
+				focused = localDate && localDate.getMonth() === i ? ' focused' : '';
+				html += '<span class="month' + focused + '">' + dates[this.o.language].monthsShort[i] + '</span>';
+			}
+			this.picker.find('.datepicker-months td').html(html);
+		},
+
+		setRange: function(range){
+			if (!range || !range.length)
+				delete this.range;
+			else
+				this.range = $.map(range, function(d){
+					return d.valueOf();
+				});
+			this.fill();
+		},
+
+		getClassNames: function(date){
+			var cls = [],
+				year = this.viewDate.getUTCFullYear(),
+				month = this.viewDate.getUTCMonth(),
+				today = UTCToday();
+			if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){
+				cls.push('old');
+			} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){
+				cls.push('new');
+			}
+			if (this.focusDate && date.valueOf() === this.focusDate.valueOf())
+				cls.push('focused');
+			// Compare internal UTC date with UTC today, not local today
+			if (this.o.todayHighlight && isUTCEquals(date, today)) {
+				cls.push('today');
+			}
+			if (this.dates.contains(date) !== -1)
+				cls.push('active');
+			if (!this.dateWithinRange(date)){
+				cls.push('disabled');
+			}
+			if (this.dateIsDisabled(date)){
+				cls.push('disabled', 'disabled-date');
+			}
+			if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){
+				cls.push('highlighted');
+			}
+
+			if (this.range){
+				if (date > this.range[0] && date < this.range[this.range.length-1]){
+					cls.push('range');
+				}
+				if ($.inArray(date.valueOf(), this.range) !== -1){
+					cls.push('selected');
+				}
+				if (date.valueOf() === this.range[0]){
+          cls.push('range-start');
+        }
+        if (date.valueOf() === this.range[this.range.length-1]){
+          cls.push('range-end');
+        }
+			}
+			return cls;
+		},
+
+		_fill_yearsView: function(selector, cssClass, factor, year, startYear, endYear, beforeFn){
+			var html = '';
+			var step = factor / 10;
+			var view = this.picker.find(selector);
+			var startVal = Math.floor(year / factor) * factor;
+			var endVal = startVal + step * 9;
+			var focusedVal = Math.floor(this.viewDate.getFullYear() / step) * step;
+			var selected = $.map(this.dates, function(d){
+				return Math.floor(d.getUTCFullYear() / step) * step;
+			});
+
+			var classes, tooltip, before;
+			for (var currVal = startVal - step; currVal <= endVal + step; currVal += step) {
+				classes = [cssClass];
+				tooltip = null;
+
+				if (currVal === startVal - step) {
+					classes.push('old');
+				} else if (currVal === endVal + step) {
+					classes.push('new');
+				}
+				if ($.inArray(currVal, selected) !== -1) {
+					classes.push('active');
+				}
+				if (currVal < startYear || currVal > endYear) {
+					classes.push('disabled');
+				}
+				if (currVal === focusedVal) {
+				  classes.push('focused');
+        }
+
+				if (beforeFn !== $.noop) {
+					before = beforeFn(new Date(currVal, 0, 1));
+					if (before === undefined) {
+						before = {};
+					} else if (typeof before === 'boolean') {
+						before = {enabled: before};
+					} else if (typeof before === 'string') {
+						before = {classes: before};
+					}
+					if (before.enabled === false) {
+						classes.push('disabled');
+					}
+					if (before.classes) {
+						classes = classes.concat(before.classes.split(/\s+/));
+					}
+					if (before.tooltip) {
+						tooltip = before.tooltip;
+					}
+				}
+
+				html += '<span class="' + classes.join(' ') + '"' + (tooltip ? ' title="' + tooltip + '"' : '') + '>' + currVal + '</span>';
+			}
+
+			view.find('.datepicker-switch').text(startVal + '-' + endVal);
+			view.find('td').html(html);
+		},
+
+		fill: function(){
+			var d = new Date(this.viewDate),
+				year = d.getUTCFullYear(),
+				month = d.getUTCMonth(),
+				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
+				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
+				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
+				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
+				todaytxt = dates[this.o.language].today || dates['en'].today || '',
+				cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
+				titleFormat = dates[this.o.language].titleFormat || dates['en'].titleFormat,
+				tooltip,
+				before;
+			if (isNaN(year) || isNaN(month))
+				return;
+			this.picker.find('.datepicker-days .datepicker-switch')
+						.text(DPGlobal.formatDate(d, titleFormat, this.o.language));
+			this.picker.find('tfoot .today')
+						.text(todaytxt)
+						.css('display', this.o.todayBtn === true || this.o.todayBtn === 'linked' ? 'table-cell' : 'none');
+			this.picker.find('tfoot .clear')
+						.text(cleartxt)
+						.css('display', this.o.clearBtn === true ? 'table-cell' : 'none');
+			this.picker.find('thead .datepicker-title')
+						.text(this.o.title)
+						.css('display', typeof this.o.title === 'string' && this.o.title !== '' ? 'table-cell' : 'none');
+			this.updateNavArrows();
+			this.fillMonths();
+			var prevMonth = UTCDate(year, month, 0),
+				day = prevMonth.getUTCDate();
+			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
+			var nextMonth = new Date(prevMonth);
+			if (prevMonth.getUTCFullYear() < 100){
+        nextMonth.setUTCFullYear(prevMonth.getUTCFullYear());
+      }
+			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
+			nextMonth = nextMonth.valueOf();
+			var html = [];
+			var weekDay, clsName;
+			while (prevMonth.valueOf() < nextMonth){
+				weekDay = prevMonth.getUTCDay();
+				if (weekDay === this.o.weekStart){
+					html.push('<tr>');
+					if (this.o.calendarWeeks){
+						// ISO 8601: First week contains first thursday.
+						// ISO also states week starts on Monday, but we can be more abstract here.
+						var
+							// Start of current week: based on weekstart/current date
+							ws = new Date(+prevMonth + (this.o.weekStart - weekDay - 7) % 7 * 864e5),
+							// Thursday of this week
+							th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
+							// First Thursday of year, year from thursday
+							yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay()) % 7 * 864e5),
+							// Calendar week: ms between thursdays, div ms per day, div 7 days
+							calWeek = (th - yth) / 864e5 / 7 + 1;
+						html.push('<td class="cw">'+ calWeek +'</td>');
+					}
+				}
+				clsName = this.getClassNames(prevMonth);
+				clsName.push('day');
+
+				var content = prevMonth.getUTCDate();
+
+				if (this.o.beforeShowDay !== $.noop){
+					before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
+					if (before === undefined)
+						before = {};
+					else if (typeof before === 'boolean')
+						before = {enabled: before};
+					else if (typeof before === 'string')
+						before = {classes: before};
+					if (before.enabled === false)
+						clsName.push('disabled');
+					if (before.classes)
+						clsName = clsName.concat(before.classes.split(/\s+/));
+					if (before.tooltip)
+						tooltip = before.tooltip;
+					if (before.content)
+						content = before.content;
+				}
+
+				//Check if uniqueSort exists (supported by jquery >=1.12 and >=2.2)
+				//Fallback to unique function for older jquery versions
+				if ($.isFunction($.uniqueSort)) {
+					clsName = $.uniqueSort(clsName);
+				} else {
+					clsName = $.unique(clsName);
+				}
+
+				html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + ' data-date="' + prevMonth.getTime().toString() + '">' + content + '</td>');
+				tooltip = null;
+				if (weekDay === this.o.weekEnd){
+					html.push('</tr>');
+				}
+				prevMonth.setUTCDate(prevMonth.getUTCDate() + 1);
+			}
+			this.picker.find('.datepicker-days tbody').html(html.join(''));
+
+			var monthsTitle = dates[this.o.language].monthsTitle || dates['en'].monthsTitle || 'Months';
+			var months = this.picker.find('.datepicker-months')
+						.find('.datepicker-switch')
+							.text(this.o.maxViewMode < 2 ? monthsTitle : year)
+							.end()
+						.find('tbody span').removeClass('active');
+
+			$.each(this.dates, function(i, d){
+				if (d.getUTCFullYear() === year)
+					months.eq(d.getUTCMonth()).addClass('active');
+			});
+
+			if (year < startYear || year > endYear){
+				months.addClass('disabled');
+			}
+			if (year === startYear){
+				months.slice(0, startMonth).addClass('disabled');
+			}
+			if (year === endYear){
+				months.slice(endMonth+1).addClass('disabled');
+			}
+
+			if (this.o.beforeShowMonth !== $.noop){
+				var that = this;
+				$.each(months, function(i, month){
+          var moDate = new Date(year, i, 1);
+          var before = that.o.beforeShowMonth(moDate);
+					if (before === undefined)
+						before = {};
+					else if (typeof before === 'boolean')
+						before = {enabled: before};
+					else if (typeof before === 'string')
+						before = {classes: before};
+					if (before.enabled === false && !$(month).hasClass('disabled'))
+					    $(month).addClass('disabled');
+					if (before.classes)
+					    $(month).addClass(before.classes);
+					if (before.tooltip)
+					    $(month).prop('title', before.tooltip);
+				});
+			}
+
+			// Generating decade/years picker
+			this._fill_yearsView(
+				'.datepicker-years',
+				'year',
+				10,
+				year,
+				startYear,
+				endYear,
+				this.o.beforeShowYear
+			);
+
+			// Generating century/decades picker
+			this._fill_yearsView(
+				'.datepicker-decades',
+				'decade',
+				100,
+				year,
+				startYear,
+				endYear,
+				this.o.beforeShowDecade
+			);
+
+			// Generating millennium/centuries picker
+			this._fill_yearsView(
+				'.datepicker-centuries',
+				'century',
+				1000,
+				year,
+				startYear,
+				endYear,
+				this.o.beforeShowCentury
+			);
+		},
+
+		updateNavArrows: function(){
+			if (!this._allow_update)
+				return;
+
+			var d = new Date(this.viewDate),
+				year = d.getUTCFullYear(),
+				month = d.getUTCMonth(),
+				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
+				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
+				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
+				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
+				prevIsDisabled,
+				nextIsDisabled,
+				factor = 1;
+			switch (this.viewMode){
+				case 4:
+					factor *= 10;
+					/* falls through */
+				case 3:
+					factor *= 10;
+					/* falls through */
+				case 2:
+					factor *= 10;
+					/* falls through */
+				case 1:
+					prevIsDisabled = Math.floor(year / factor) * factor < startYear;
+					nextIsDisabled = Math.floor(year / factor) * factor + factor > endYear;
+					break;
+				case 0:
+					prevIsDisabled = year <= startYear && month < startMonth;
+					nextIsDisabled = year >= endYear && month > endMonth;
+					break;
+			}
+
+			this.picker.find('.prev').toggleClass('disabled', prevIsDisabled);
+			this.picker.find('.next').toggleClass('disabled', nextIsDisabled);
+		},
+
+		click: function(e){
+			e.preventDefault();
+			e.stopPropagation();
+
+			var target, dir, day, year, month;
+			target = $(e.target);
+
+			// Clicked on the switch
+			if (target.hasClass('datepicker-switch') && this.viewMode !== this.o.maxViewMode){
+				this.setViewMode(this.viewMode + 1);
+			}
+
+			// Clicked on today button
+			if (target.hasClass('today') && !target.hasClass('day')){
+				this.setViewMode(0);
+				this._setDate(UTCToday(), this.o.todayBtn === 'linked' ? null : 'view');
+			}
+
+			// Clicked on clear button
+			if (target.hasClass('clear')){
+				this.clearDates();
+			}
+
+			if (!target.hasClass('disabled')){
+				// Clicked on a month, year, decade, century
+				if (target.hasClass('month')
+						|| target.hasClass('year')
+						|| target.hasClass('decade')
+						|| target.hasClass('century')) {
+					this.viewDate.setUTCDate(1);
+
+					day = 1;
+					if (this.viewMode === 1){
+						month = target.parent().find('span').index(target);
+						year = this.viewDate.getUTCFullYear();
+						this.viewDate.setUTCMonth(month);
+					} else {
+						month = 0;
+						year = Number(target.text());
+						this.viewDate.setUTCFullYear(year);
+					}
+
+					this._trigger(DPGlobal.viewModes[this.viewMode - 1].e, this.viewDate);
+
+					if (this.viewMode === this.o.minViewMode){
+						this._setDate(UTCDate(year, month, day));
+					} else {
+						this.setViewMode(this.viewMode - 1);
+						this.fill();
+					}
+				}
+			}
+
+			if (this.picker.is(':visible') && this._focused_from){
+				this._focused_from.focus();
+			}
+			delete this._focused_from;
+		},
+
+		dayCellClick: function(e){
+			var $target = $(e.currentTarget);
+			var timestamp = $target.data('date');
+			var date = new Date(timestamp);
+
+			if (this.o.updateViewDate) {
+				if (date.getUTCFullYear() !== this.viewDate.getUTCFullYear()) {
+					this._trigger('changeYear', this.viewDate);
+				}
+
+				if (date.getUTCMonth() !== this.viewDate.getUTCMonth()) {
+					this._trigger('changeMonth', this.viewDate);
+				}
+			}
+			this._setDate(date);
+		},
+
+		// Clicked on prev or next
+		navArrowsClick: function(e){
+			var $target = $(e.currentTarget);
+			var dir = $target.hasClass('prev') ? -1 : 1;
+			if (this.viewMode !== 0){
+				dir *= DPGlobal.viewModes[this.viewMode].navStep * 12;
+			}
+			this.viewDate = this.moveMonth(this.viewDate, dir);
+			this._trigger(DPGlobal.viewModes[this.viewMode].e, this.viewDate);
+			this.fill();
+		},
+
+		_toggle_multidate: function(date){
+			var ix = this.dates.contains(date);
+			if (!date){
+				this.dates.clear();
+			}
+
+			if (ix !== -1){
+				if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){
+					this.dates.remove(ix);
+				}
+			} else if (this.o.multidate === false) {
+				this.dates.clear();
+				this.dates.push(date);
+			}
+			else {
+				this.dates.push(date);
+			}
+
+			if (typeof this.o.multidate === 'number')
+				while (this.dates.length > this.o.multidate)
+					this.dates.remove(0);
+		},
+
+		_setDate: function(date, which){
+			if (!which || which === 'date')
+				this._toggle_multidate(date && new Date(date));
+			if ((!which && this.o.updateViewDate) || which === 'view')
+				this.viewDate = date && new Date(date);
+
+			this.fill();
+			this.setValue();
+			if (!which || which !== 'view') {
+				this._trigger('changeDate');
+			}
+			this.inputField.trigger('change');
+			if (this.o.autoclose && (!which || which === 'date')){
+				this.hide();
+			}
+		},
+
+		moveDay: function(date, dir){
+			var newDate = new Date(date);
+			newDate.setUTCDate(date.getUTCDate() + dir);
+
+			return newDate;
+		},
+
+		moveWeek: function(date, dir){
+			return this.moveDay(date, dir * 7);
+		},
+
+		moveMonth: function(date, dir){
+			if (!isValidDate(date))
+				return this.o.defaultViewDate;
+			if (!dir)
+				return date;
+			var new_date = new Date(date.valueOf()),
+				day = new_date.getUTCDate(),
+				month = new_date.getUTCMonth(),
+				mag = Math.abs(dir),
+				new_month, test;
+			dir = dir > 0 ? 1 : -1;
+			if (mag === 1){
+				test = dir === -1
+					// If going back one month, make sure month is not current month
+					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
+					? function(){
+						return new_date.getUTCMonth() === month;
+					}
+					// If going forward one month, make sure month is as expected
+					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
+					: function(){
+						return new_date.getUTCMonth() !== new_month;
+					};
+				new_month = month + dir;
+				new_date.setUTCMonth(new_month);
+				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
+				new_month = (new_month + 12) % 12;
+			}
+			else {
+				// For magnitudes >1, move one month at a time...
+				for (var i=0; i < mag; i++)
+					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
+					new_date = this.moveMonth(new_date, dir);
+				// ...then reset the day, keeping it in the new month
+				new_month = new_date.getUTCMonth();
+				new_date.setUTCDate(day);
+				test = function(){
+					return new_month !== new_date.getUTCMonth();
+				};
+			}
+			// Common date-resetting loop -- if date is beyond end of month, make it
+			// end of month
+			while (test()){
+				new_date.setUTCDate(--day);
+				new_date.setUTCMonth(new_month);
+			}
+			return new_date;
+		},
+
+		moveYear: function(date, dir){
+			return this.moveMonth(date, dir*12);
+		},
+
+		moveAvailableDate: function(date, dir, fn){
+			do {
+				date = this[fn](date, dir);
+
+				if (!this.dateWithinRange(date))
+					return false;
+
+				fn = 'moveDay';
+			}
+			while (this.dateIsDisabled(date));
+
+			return date;
+		},
+
+		weekOfDateIsDisabled: function(date){
+			return $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1;
+		},
+
+		dateIsDisabled: function(date){
+			return (
+				this.weekOfDateIsDisabled(date) ||
+				$.grep(this.o.datesDisabled, function(d){
+					return isUTCEquals(date, d);
+				}).length > 0
+			);
+		},
+
+		dateWithinRange: function(date){
+			return date >= this.o.startDate && date <= this.o.endDate;
+		},
+
+		keydown: function(e){
+			if (!this.picker.is(':visible')){
+				if (e.keyCode === 40 || e.keyCode === 27) { // allow down to re-show picker
+					this.show();
+					e.stopPropagation();
+        }
+				return;
+			}
+			var dateChanged = false,
+				dir, newViewDate,
+				focusDate = this.focusDate || this.viewDate;
+			switch (e.keyCode){
+				case 27: // escape
+					if (this.focusDate){
+						this.focusDate = null;
+						this.viewDate = this.dates.get(-1) || this.viewDate;
+						this.fill();
+					}
+					else
+						this.hide();
+					e.preventDefault();
+					e.stopPropagation();
+					break;
+				case 37: // left
+				case 38: // up
+				case 39: // right
+				case 40: // down
+					if (!this.o.keyboardNavigation || this.o.daysOfWeekDisabled.length === 7)
+						break;
+					dir = e.keyCode === 37 || e.keyCode === 38 ? -1 : 1;
+          if (this.viewMode === 0) {
+  					if (e.ctrlKey){
+  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear');
+
+  						if (newViewDate)
+  							this._trigger('changeYear', this.viewDate);
+  					} else if (e.shiftKey){
+  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth');
+
+  						if (newViewDate)
+  							this._trigger('changeMonth', this.viewDate);
+  					} else if (e.keyCode === 37 || e.keyCode === 39){
+  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveDay');
+  					} else if (!this.weekOfDateIsDisabled(focusDate)){
+  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveWeek');
+  					}
+          } else if (this.viewMode === 1) {
+            if (e.keyCode === 38 || e.keyCode === 40) {
+              dir = dir * 4;
+            }
+            newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth');
+          } else if (this.viewMode === 2) {
+            if (e.keyCode === 38 || e.keyCode === 40) {
+              dir = dir * 4;
+            }
+            newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear');
+          }
+					if (newViewDate){
+						this.focusDate = this.viewDate = newViewDate;
+						this.setValue();
+						this.fill();
+						e.preventDefault();
+					}
+					break;
+				case 13: // enter
+					if (!this.o.forceParse)
+						break;
+					focusDate = this.focusDate || this.dates.get(-1) || this.viewDate;
+					if (this.o.keyboardNavigation) {
+						this._toggle_multidate(focusDate);
+						dateChanged = true;
+					}
+					this.focusDate = null;
+					this.viewDate = this.dates.get(-1) || this.viewDate;
+					this.setValue();
+					this.fill();
+					if (this.picker.is(':visible')){
+						e.preventDefault();
+						e.stopPropagation();
+						if (this.o.autoclose)
+							this.hide();
+					}
+					break;
+				case 9: // tab
+					this.focusDate = null;
+					this.viewDate = this.dates.get(-1) || this.viewDate;
+					this.fill();
+					this.hide();
+					break;
+			}
+			if (dateChanged){
+				if (this.dates.length)
+					this._trigger('changeDate');
+				else
+					this._trigger('clearDate');
+				this.inputField.trigger('change');
+			}
+		},
+
+		setViewMode: function(viewMode){
+			this.viewMode = viewMode;
+			this.picker
+				.children('div')
+				.hide()
+				.filter('.datepicker-' + DPGlobal.viewModes[this.viewMode].clsName)
+					.show();
+			this.updateNavArrows();
+      this._trigger('changeViewMode', new Date(this.viewDate));
+		}
+	};
+
+	var DateRangePicker = function(element, options){
+		$.data(element, 'datepicker', this);
+		this.element = $(element);
+		this.inputs = $.map(options.inputs, function(i){
+			return i.jquery ? i[0] : i;
+		});
+		delete options.inputs;
+
+		this.keepEmptyValues = options.keepEmptyValues;
+		delete options.keepEmptyValues;
+
+		datepickerPlugin.call($(this.inputs), options)
+			.on('changeDate', $.proxy(this.dateUpdated, this));
+
+		this.pickers = $.map(this.inputs, function(i){
+			return $.data(i, 'datepicker');
+		});
+		this.updateDates();
+	};
+	DateRangePicker.prototype = {
+		updateDates: function(){
+			this.dates = $.map(this.pickers, function(i){
+				return i.getUTCDate();
+			});
+			this.updateRanges();
+		},
+		updateRanges: function(){
+			var range = $.map(this.dates, function(d){
+				return d.valueOf();
+			});
+			$.each(this.pickers, function(i, p){
+				p.setRange(range);
+			});
+		},
+		clearDates: function(){
+			$.each(this.pickers, function(i, p){
+				p.clearDates();
+			});
+		},
+		dateUpdated: function(e){
+			// `this.updating` is a workaround for preventing infinite recursion
+			// between `changeDate` triggering and `setUTCDate` calling.  Until
+			// there is a better mechanism.
+			if (this.updating)
+				return;
+			this.updating = true;
+
+			var dp = $.data(e.target, 'datepicker');
+
+			if (dp === undefined) {
+				return;
+			}
+
+			var new_date = dp.getUTCDate(),
+				keep_empty_values = this.keepEmptyValues,
+				i = $.inArray(e.target, this.inputs),
+				j = i - 1,
+				k = i + 1,
+				l = this.inputs.length;
+			if (i === -1)
+				return;
+
+			$.each(this.pickers, function(i, p){
+				if (!p.getUTCDate() && (p === dp || !keep_empty_values))
+					p.setUTCDate(new_date);
+			});
+
+			if (new_date < this.dates[j]){
+				// Date being moved earlier/left
+				while (j >= 0 && new_date < this.dates[j]){
+					this.pickers[j--].setUTCDate(new_date);
+				}
+			} else if (new_date > this.dates[k]){
+				// Date being moved later/right
+				while (k < l && new_date > this.dates[k]){
+					this.pickers[k++].setUTCDate(new_date);
+				}
+			}
+			this.updateDates();
+
+			delete this.updating;
+		},
+		destroy: function(){
+			$.map(this.pickers, function(p){ p.destroy(); });
+			$(this.inputs).off('changeDate', this.dateUpdated);
+			delete this.element.data().datepicker;
+		},
+		remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead')
+	};
+
+	function opts_from_el(el, prefix){
+		// Derive options from element data-attrs
+		var data = $(el).data(),
+			out = {}, inkey,
+			replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])');
+		prefix = new RegExp('^' + prefix.toLowerCase());
+		function re_lower(_,a){
+			return a.toLowerCase();
+		}
+		for (var key in data)
+			if (prefix.test(key)){
+				inkey = key.replace(replace, re_lower);
+				out[inkey] = data[key];
+			}
+		return out;
+	}
+
+	function opts_from_locale(lang){
+		// Derive options from locale plugins
+		var out = {};
+		// Check if "de-DE" style date is available, if not language should
+		// fallback to 2 letter code eg "de"
+		if (!dates[lang]){
+			lang = lang.split('-')[0];
+			if (!dates[lang])
+				return;
+		}
+		var d = dates[lang];
+		$.each(locale_opts, function(i,k){
+			if (k in d)
+				out[k] = d[k];
+		});
+		return out;
+	}
+
+	var old = $.fn.datepicker;
+	var datepickerPlugin = function(option){
+		var args = Array.apply(null, arguments);
+		args.shift();
+		var internal_return;
+		this.each(function(){
+			var $this = $(this),
+				data = $this.data('datepicker'),
+				options = typeof option === 'object' && option;
+			if (!data){
+				var elopts = opts_from_el(this, 'date'),
+					// Preliminary otions
+					xopts = $.extend({}, defaults, elopts, options),
+					locopts = opts_from_locale(xopts.language),
+					// Options priority: js args, data-attrs, locales, defaults
+					opts = $.extend({}, defaults, locopts, elopts, options);
+				if ($this.hasClass('input-daterange') || opts.inputs){
+					$.extend(opts, {
+						inputs: opts.inputs || $this.find('input').toArray()
+					});
+					data = new DateRangePicker(this, opts);
+				}
+				else {
+					data = new Datepicker(this, opts);
+				}
+				$this.data('datepicker', data);
+			}
+			if (typeof option === 'string' && typeof data[option] === 'function'){
+				internal_return = data[option].apply(data, args);
+			}
+		});
+
+		if (
+			internal_return === undefined ||
+			internal_return instanceof Datepicker ||
+			internal_return instanceof DateRangePicker
+		)
+			return this;
+
+		if (this.length > 1)
+			throw new Error('Using only allowed for the collection of a single element (' + option + ' function)');
+		else
+			return internal_return;
+	};
+	$.fn.datepicker = datepickerPlugin;
+
+	var defaults = $.fn.datepicker.defaults = {
+		assumeNearbyYear: false,
+		autoclose: false,
+		beforeShowDay: $.noop,
+		beforeShowMonth: $.noop,
+		beforeShowYear: $.noop,
+		beforeShowDecade: $.noop,
+		beforeShowCentury: $.noop,
+		calendarWeeks: false,
+		clearBtn: false,
+		toggleActive: false,
+		daysOfWeekDisabled: [],
+		daysOfWeekHighlighted: [],
+		datesDisabled: [],
+		endDate: Infinity,
+		forceParse: true,
+		format: 'mm/dd/yyyy',
+		keepEmptyValues: false,
+		keyboardNavigation: true,
+		language: 'en',
+		minViewMode: 0,
+		maxViewMode: 4,
+		multidate: false,
+		multidateSeparator: ',',
+		orientation: "auto",
+		rtl: false,
+		startDate: -Infinity,
+		startView: 0,
+		todayBtn: false,
+		todayHighlight: false,
+		updateViewDate: true,
+		weekStart: 0,
+		disableTouchKeyboard: false,
+		enableOnReadonly: true,
+		showOnFocus: true,
+		zIndexOffset: 10,
+		container: 'body',
+		immediateUpdates: false,
+		title: '',
+		templates: {
+			leftArrow: '&#x00AB;',
+			rightArrow: '&#x00BB;'
+		},
+    showWeekDays: true
+	};
+	var locale_opts = $.fn.datepicker.locale_opts = [
+		'format',
+		'rtl',
+		'weekStart'
+	];
+	$.fn.datepicker.Constructor = Datepicker;
+	var dates = $.fn.datepicker.dates = {
+		en: {
+			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
+			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+			today: "Today",
+			clear: "Clear",
+			titleFormat: "MM yyyy"
+		}
+	};
+
+	var DPGlobal = {
+		viewModes: [
+			{
+				names: ['days', 'month'],
+				clsName: 'days',
+				e: 'changeMonth'
+			},
+			{
+				names: ['months', 'year'],
+				clsName: 'months',
+				e: 'changeYear',
+				navStep: 1
+			},
+			{
+				names: ['years', 'decade'],
+				clsName: 'years',
+				e: 'changeDecade',
+				navStep: 10
+			},
+			{
+				names: ['decades', 'century'],
+				clsName: 'decades',
+				e: 'changeCentury',
+				navStep: 100
+			},
+			{
+				names: ['centuries', 'millennium'],
+				clsName: 'centuries',
+				e: 'changeMillennium',
+				navStep: 1000
+			}
+		],
+		validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
+		nonpunctuation: /[^ -\/:-@\u5e74\u6708\u65e5\[-`{-~\t\n\r]+/g,
+		parseFormat: function(format){
+			if (typeof format.toValue === 'function' && typeof format.toDisplay === 'function')
+                return format;
+            // IE treats \0 as a string end in inputs (truncating the value),
+			// so it's a bad format delimiter, anyway
+			var separators = format.replace(this.validParts, '\0').split('\0'),
+				parts = format.match(this.validParts);
+			if (!separators || !separators.length || !parts || parts.length === 0){
+				throw new Error("Invalid date format.");
+			}
+			return {separators: separators, parts: parts};
+		},
+		parseDate: function(date, format, language, assumeNearby){
+			if (!date)
+				return undefined;
+			if (date instanceof Date)
+				return date;
+			if (typeof format === 'string')
+				format = DPGlobal.parseFormat(format);
+			if (format.toValue)
+				return format.toValue(date, format, language);
+			var fn_map = {
+					d: 'moveDay',
+					m: 'moveMonth',
+					w: 'moveWeek',
+					y: 'moveYear'
+				},
+				dateAliases = {
+					yesterday: '-1d',
+					today: '+0d',
+					tomorrow: '+1d'
+				},
+				parts, part, dir, i, fn;
+			if (date in dateAliases){
+				date = dateAliases[date];
+			}
+			if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/i.test(date)){
+				parts = date.match(/([\-+]\d+)([dmwy])/gi);
+				date = new Date();
+				for (i=0; i < parts.length; i++){
+					part = parts[i].match(/([\-+]\d+)([dmwy])/i);
+					dir = Number(part[1]);
+					fn = fn_map[part[2].toLowerCase()];
+					date = Datepicker.prototype[fn](date, dir);
+				}
+				return Datepicker.prototype._zero_utc_time(date);
+			}
+
+			parts = date && date.match(this.nonpunctuation) || [];
+
+			function applyNearbyYear(year, threshold){
+				if (threshold === true)
+					threshold = 10;
+
+				// if year is 2 digits or less, than the user most likely is trying to get a recent century
+				if (year < 100){
+					year += 2000;
+					// if the new year is more than threshold years in advance, use last century
+					if (year > ((new Date()).getFullYear()+threshold)){
+						year -= 100;
+					}
+				}
+
+				return year;
+			}
+
+			var parsed = {},
+				setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
+				setters_map = {
+					yyyy: function(d,v){
+						return d.setUTCFullYear(assumeNearby ? applyNearbyYear(v, assumeNearby) : v);
+					},
+					m: function(d,v){
+						if (isNaN(d))
+							return d;
+						v -= 1;
+						while (v < 0) v += 12;
+						v %= 12;
+						d.setUTCMonth(v);
+						while (d.getUTCMonth() !== v)
+							d.setUTCDate(d.getUTCDate()-1);
+						return d;
+					},
+					d: function(d,v){
+						return d.setUTCDate(v);
+					}
+				},
+				val, filtered;
+			setters_map['yy'] = setters_map['yyyy'];
+			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
+			setters_map['dd'] = setters_map['d'];
+			date = UTCToday();
+			var fparts = format.parts.slice();
+			// Remove noop parts
+			if (parts.length !== fparts.length){
+				fparts = $(fparts).filter(function(i,p){
+					return $.inArray(p, setters_order) !== -1;
+				}).toArray();
+			}
+			// Process remainder
+			function match_part(){
+				var m = this.slice(0, parts[i].length),
+					p = parts[i].slice(0, m.length);
+				return m.toLowerCase() === p.toLowerCase();
+			}
+			if (parts.length === fparts.length){
+				var cnt;
+				for (i=0, cnt = fparts.length; i < cnt; i++){
+					val = parseInt(parts[i], 10);
+					part = fparts[i];
+					if (isNaN(val)){
+						switch (part){
+							case 'MM':
+								filtered = $(dates[language].months).filter(match_part);
+								val = $.inArray(filtered[0], dates[language].months) + 1;
+								break;
+							case 'M':
+								filtered = $(dates[language].monthsShort).filter(match_part);
+								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
+								break;
+						}
+					}
+					parsed[part] = val;
+				}
+				var _date, s;
+				for (i=0; i < setters_order.length; i++){
+					s = setters_order[i];
+					if (s in parsed && !isNaN(parsed[s])){
+						_date = new Date(date);
+						setters_map[s](_date, parsed[s]);
+						if (!isNaN(_date))
+							date = _date;
+					}
+				}
+			}
+			return date;
+		},
+		formatDate: function(date, format, language){
+			if (!date)
+				return '';
+			if (typeof format === 'string')
+				format = DPGlobal.parseFormat(format);
+			if (format.toDisplay)
+                return format.toDisplay(date, format, language);
+            var val = {
+				d: date.getUTCDate(),
+				D: dates[language].daysShort[date.getUTCDay()],
+				DD: dates[language].days[date.getUTCDay()],
+				m: date.getUTCMonth() + 1,
+				M: dates[language].monthsShort[date.getUTCMonth()],
+				MM: dates[language].months[date.getUTCMonth()],
+				yy: date.getUTCFullYear().toString().substring(2),
+				yyyy: date.getUTCFullYear()
+			};
+			val.dd = (val.d < 10 ? '0' : '') + val.d;
+			val.mm = (val.m < 10 ? '0' : '') + val.m;
+			date = [];
+			var seps = $.extend([], format.separators);
+			for (var i=0, cnt = format.parts.length; i <= cnt; i++){
+				if (seps.length)
+					date.push(seps.shift());
+				date.push(val[format.parts[i]]);
+			}
+			return date.join('');
+		},
+		headTemplate: '<thead>'+
+			              '<tr>'+
+			                '<th colspan="7" class="datepicker-title"></th>'+
+			              '</tr>'+
+							'<tr>'+
+								'<th class="prev">'+defaults.templates.leftArrow+'</th>'+
+								'<th colspan="5" class="datepicker-switch"></th>'+
+								'<th class="next">'+defaults.templates.rightArrow+'</th>'+
+							'</tr>'+
+						'</thead>',
+		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
+		footTemplate: '<tfoot>'+
+							'<tr>'+
+								'<th colspan="7" class="today"></th>'+
+							'</tr>'+
+							'<tr>'+
+								'<th colspan="7" class="clear"></th>'+
+							'</tr>'+
+						'</tfoot>'
+	};
+	DPGlobal.template = '<div class="datepicker">'+
+							'<div class="datepicker-days">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									'<tbody></tbody>'+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-months">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-years">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-decades">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+							'<div class="datepicker-centuries">'+
+								'<table class="table-condensed">'+
+									DPGlobal.headTemplate+
+									DPGlobal.contTemplate+
+									DPGlobal.footTemplate+
+								'</table>'+
+							'</div>'+
+						'</div>';
+
+	$.fn.datepicker.DPGlobal = DPGlobal;
+
+
+	/* DATEPICKER NO CONFLICT
+	* =================== */
+
+	$.fn.datepicker.noConflict = function(){
+		$.fn.datepicker = old;
+		return this;
+	};
+
+	/* DATEPICKER VERSION
+	 * =================== */
+	$.fn.datepicker.version = '1.8.0';
+
+	$.fn.datepicker.deprecated = function(msg){
+		var console = window.console;
+		if (console && console.warn) {
+			console.warn('DEPRECATED: ' + msg);
+		}
+	};
+
+
+	/* DATEPICKER DATA-API
+	* ================== */
+
+	$(document).on(
+		'focus.datepicker.data-api click.datepicker.data-api',
+		'[data-provide="datepicker"]',
+		function(e){
+			var $this = $(this);
+			if ($this.data('datepicker'))
+				return;
+			e.preventDefault();
+			// component click requires us to explicitly show it
+			datepickerPlugin.call($this, 'show');
+		}
+	);
+	$(function(){
+		datepickerPlugin.call($('[data-provide="datepicker-inline"]'));
+	});
+
+}));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 0
public/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js


+ 1 - 0
public/plugins/bootstrap-datepicker/dist/locales/bootstrap-datepicker.zh-CN.min.js

xqd
@@ -0,0 +1 @@
+!function(a){a.fn.datepicker.dates["zh-CN"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["周日","周一","周二","周三","周四","周五","周六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今日",clear:"清除",format:"yyyy年mm月dd日",titleFormat:"yyyy年mm月",weekStart:1}}(jQuery);

+ 93 - 0
public/plugins/multi-select/css/multi-select.css

xqd
@@ -0,0 +1,93 @@
+.ms-container{
+  background: transparent url('../img/switch.png') no-repeat 50% 50%;
+  width: 370px;
+}
+
+.ms-container:after{
+  content: ".";
+  display: block;
+  height: 0;
+  line-height: 0;
+  font-size: 0;
+  clear: both;
+  min-height: 0;
+  visibility: hidden;
+}
+
+.ms-container .ms-selectable, .ms-container .ms-selection{
+  background: #fff;
+  color: #555555;
+  float: left;
+  width: 45%;
+}
+.ms-container .ms-selection{
+  float: right;
+}
+
+.ms-container .ms-list{
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+  transition: border linear 0.2s, box-shadow linear 0.2s;
+  border: 1px solid #ccc;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  position: relative;
+  height: 200px;
+  padding: 0;
+  overflow-y: auto;
+}
+
+.ms-container .ms-list.ms-focus{
+  border-color: rgba(82, 168, 236, 0.8);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  outline: 0;
+  outline: thin dotted \9;
+}
+
+.ms-container ul{
+  margin: 0;
+  list-style-type: none;
+  padding: 0;
+}
+
+.ms-container .ms-optgroup-container{
+  width: 100%;
+}
+
+.ms-container .ms-optgroup-label{
+  margin: 0;
+  padding: 5px 0px 0px 5px;
+  cursor: pointer;
+  color: #999;
+}
+
+.ms-container .ms-selectable li.ms-elem-selectable,
+.ms-container .ms-selection li.ms-elem-selection{
+  border-bottom: 1px #eee solid;
+  padding: 2px 10px;
+  color: #555;
+  font-size: 14px;
+}
+
+.ms-container .ms-selectable li.ms-hover,
+.ms-container .ms-selection li.ms-hover{
+  cursor: pointer;
+  color: #fff;
+  text-decoration: none;
+  background-color: #08c;
+}
+
+.ms-container .ms-selectable li.disabled,
+.ms-container .ms-selection li.disabled{
+  background-color: #eee;
+  color: #aaa;
+  cursor: text;
+}

+ 108 - 0
public/plugins/multi-select/css/multi-select.dev.css

xqd
@@ -0,0 +1,108 @@
+/* line 1, ../scss/multi-select.scss */
+.ms-container {
+  background: transparent url("../img/switch.png") no-repeat 50% 50%;
+  width: 370px;
+}
+
+/* line 6, ../scss/multi-select.scss */
+.ms-container:after {
+  content: ".";
+  display: block;
+  height: 0;
+  line-height: 0;
+  font-size: 0;
+  clear: both;
+  min-height: 0;
+  visibility: hidden;
+}
+
+/* line 17, ../scss/multi-select.scss */
+.ms-container .ms-selectable, .ms-container .ms-selection {
+  background: #fff;
+  color: #555555;
+  float: left;
+  width: 45%;
+}
+
+/* line 23, ../scss/multi-select.scss */
+.ms-container .ms-selection {
+  float: right;
+}
+
+/* line 27, ../scss/multi-select.scss */
+.ms-container .ms-list {
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
+  -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+  transition: border linear 0.2s, box-shadow linear 0.2s;
+  border: 1px solid #ccc;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+  position: relative;
+  height: 200px;
+  padding: 0;
+  overflow-y: auto;
+}
+
+/* line 46, ../scss/multi-select.scss */
+.ms-container .ms-list.ms-focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+  outline: 0;
+  outline: thin dotted \9;
+}
+
+/* line 55, ../scss/multi-select.scss */
+.ms-container ul {
+  margin: 0;
+  list-style-type: none;
+  padding: 0;
+}
+
+/* line 61, ../scss/multi-select.scss */
+.ms-container .ms-optgroup-container {
+  width: 100%;
+}
+
+/* line 65, ../scss/multi-select.scss */
+.ms-container .ms-optgroup-label {
+  margin: 0;
+  padding: 5px 0px 0px 5px;
+  cursor: pointer;
+  color: #999;
+}
+
+/* line 72, ../scss/multi-select.scss */
+.ms-container .ms-selectable li.ms-elem-selectable,
+.ms-container .ms-selection li.ms-elem-selection {
+  border-bottom: 1px #eee solid;
+  padding: 2px 10px;
+  color: #555;
+  font-size: 14px;
+}
+
+/* line 80, ../scss/multi-select.scss */
+.ms-container .ms-selectable li.ms-hover,
+.ms-container .ms-selection li.ms-hover {
+  cursor: pointer;
+  color: #fff;
+  text-decoration: none;
+  background-color: #08c;
+}
+
+/* line 88, ../scss/multi-select.scss */
+.ms-container .ms-selectable li.disabled,
+.ms-container .ms-selection li.disabled {
+  background-color: #eee;
+  color: #aaa;
+  cursor: text;
+}
+
+/*# sourceMappingURL=multi-select.dev.css.map */

+ 7 - 0
public/plugins/multi-select/css/multi-select.dev.css.map

xqd
@@ -0,0 +1,7 @@
+{
+"version": 3,
+"mappings": ";AAAA,aAAa;EACX,UAAU,EAAE,sDAAsD;EAClE,KAAK,EAAE,KAAK;;;;AAGd,mBAAmB;EACjB,OAAO,EAAE,GAAG;EACZ,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,CAAC;EACT,WAAW,EAAE,CAAC;EACd,SAAS,EAAE,CAAC;EACZ,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,CAAC;EACb,UAAU,EAAE,MAAM;;;;AAGpB,yDAAyD;EACvD,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,OAAO;EACd,KAAK,EAAE,IAAI;EACX,KAAK,EAAE,GAAG;;;;AAEZ,2BAA2B;EACzB,KAAK,EAAE,KAAK;;;;AAGd,sBAAsB;EACpB,kBAAkB,EAAE,oCAAoC;EACxD,eAAe,EAAE,oCAAoC;EACrD,UAAU,EAAE,oCAAoC;EAChD,kBAAkB,EAAE,0CAA0C;EAC9D,eAAe,EAAE,0CAA0C;EAC3D,cAAc,EAAE,0CAA0C;EAC1D,aAAa,EAAE,0CAA0C;EACzD,UAAU,EAAE,0CAA0C;EACtD,MAAM,EAAE,cAAc;EACtB,qBAAqB,EAAE,GAAG;EAC1B,kBAAkB,EAAE,GAAG;EACvB,aAAa,EAAE,GAAG;EAClB,QAAQ,EAAE,QAAQ;EAClB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,IAAI;;;;AAGlB,+BAA+B;EAC7B,YAAY,EAAE,uBAAuB;EACrC,kBAAkB,EAAE,qEAAqE;EACzF,eAAe,EAAE,qEAAqE;EACtF,UAAU,EAAE,qEAAqE;EACjF,OAAO,EAAE,CAAC;EACV,OAAO,EAAE,cAAc;;;;AAGzB,gBAAgB;EACd,MAAM,EAAE,CAAC;EACT,eAAe,EAAE,IAAI;EACrB,OAAO,EAAE,CAAC;;;;AAGZ,oCAAoC;EAClC,KAAK,EAAE,IAAI;;;;AAGb,gCAAgC;EAC9B,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,eAAe;EACxB,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,IAAI;;;;AAGb;gDACgD;EAC9C,aAAa,EAAE,cAAc;EAC7B,OAAO,EAAE,QAAQ;EACjB,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,IAAI;;;;AAGjB;uCACuC;EACrC,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,IAAI;EACX,eAAe,EAAE,IAAI;EACrB,gBAAgB,EAAE,IAAI;;;;AAGxB;uCACuC;EACrC,gBAAgB,EAAE,IAAI;EACtB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI",
+"sources": ["../scss/multi-select.scss"],
+"names": [],
+"file": "multi-select.dev.css"
+}

+ 1 - 0
public/plugins/multi-select/css/multi-select.dist.css

xqd
@@ -0,0 +1 @@
+.ms-container{background:transparent url("../img/switch.png") no-repeat 50% 50%;width:370px}.ms-container:after{content:".";display:block;height:0;line-height:0;font-size:0;clear:both;min-height:0;visibility:hidden}.ms-container .ms-selectable,.ms-container .ms-selection{background:#fff;color:#555555;float:left;width:45%}.ms-container .ms-selection{float:right}.ms-container .ms-list{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear 0.2s, box-shadow linear 0.2s;-moz-transition:border linear 0.2s, box-shadow linear 0.2s;-ms-transition:border linear 0.2s, box-shadow linear 0.2s;-o-transition:border linear 0.2s, box-shadow linear 0.2s;transition:border linear 0.2s, box-shadow linear 0.2s;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;height:200px;padding:0;overflow-y:auto}.ms-container .ms-list.ms-focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);outline:0;outline:thin dotted \9}.ms-container ul{margin:0;list-style-type:none;padding:0}.ms-container .ms-optgroup-container{width:100%}.ms-container .ms-optgroup-label{margin:0;padding:5px 0px 0px 5px;cursor:pointer;color:#999}.ms-container .ms-selectable li.ms-elem-selectable,.ms-container .ms-selection li.ms-elem-selection{border-bottom:1px #eee solid;padding:2px 10px;color:#555;font-size:14px}.ms-container .ms-selectable li.ms-hover,.ms-container .ms-selection li.ms-hover{cursor:pointer;color:#fff;text-decoration:none;background-color:#08c}.ms-container .ms-selectable li.disabled,.ms-container .ms-selection li.disabled{background-color:#eee;color:#aaa;cursor:text}

BIN
public/plugins/multi-select/img/switch.png


+ 544 - 0
public/plugins/multi-select/js/jquery.multi-select.js

xqd
@@ -0,0 +1,544 @@
+/*
+* MultiSelect v0.9.12
+* Copyright (c) 2012 Louis Cuny
+*
+* This program is free software. It comes without any warranty, to
+* the extent permitted by applicable law. You can redistribute it
+* and/or modify it under the terms of the Do What The Fuck You Want
+* To Public License, Version 2, as published by Sam Hocevar. See
+* http://sam.zoy.org/wtfpl/COPYING for more details.
+*/
+
+!function ($) {
+
+  "use strict";
+
+
+ /* MULTISELECT CLASS DEFINITION
+  * ====================== */
+
+  var MultiSelect = function (element, options) {
+    this.options = options;
+    this.$element = $(element);
+    this.$container = $('<div/>', { 'class': "ms-container" });
+    this.$selectableContainer = $('<div/>', { 'class': 'ms-selectable' });
+    this.$selectionContainer = $('<div/>', { 'class': 'ms-selection' });
+    this.$selectableUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
+    this.$selectionUl = $('<ul/>', { 'class': "ms-list", 'tabindex' : '-1' });
+    this.scrollTo = 0;
+    this.elemsSelector = 'li:visible:not(.ms-optgroup-label,.ms-optgroup-container,.'+options.disabledClass+')';
+  };
+
+  MultiSelect.prototype = {
+    constructor: MultiSelect,
+
+    init: function(){
+      var that = this,
+          ms = this.$element;
+
+      if (ms.next('.ms-container').length === 0){
+        ms.css({ position: 'absolute', left: '-9999px' });
+        ms.attr('id', ms.attr('id') ? ms.attr('id') : Math.ceil(Math.random()*1000)+'multiselect');
+        this.$container.attr('id', 'ms-'+ms.attr('id'));
+        this.$container.addClass(that.options.cssClass);
+        ms.find('option').each(function(){
+          that.generateLisFromOption(this);
+        });
+
+        this.$selectionUl.find('.ms-optgroup-label').hide();
+
+        if (that.options.selectableHeader){
+          that.$selectableContainer.append(that.options.selectableHeader);
+        }
+        that.$selectableContainer.append(that.$selectableUl);
+        if (that.options.selectableFooter){
+          that.$selectableContainer.append(that.options.selectableFooter);
+        }
+
+        if (that.options.selectionHeader){
+          that.$selectionContainer.append(that.options.selectionHeader);
+        }
+        that.$selectionContainer.append(that.$selectionUl);
+        if (that.options.selectionFooter){
+          that.$selectionContainer.append(that.options.selectionFooter);
+        }
+
+        that.$container.append(that.$selectableContainer);
+        that.$container.append(that.$selectionContainer);
+        ms.after(that.$container);
+
+        that.activeMouse(that.$selectableUl);
+        that.activeKeyboard(that.$selectableUl);
+
+        var action = that.options.dblClick ? 'dblclick' : 'click';
+
+        that.$selectableUl.on(action, '.ms-elem-selectable', function(){
+          that.select($(this).data('ms-value'));
+        });
+        that.$selectionUl.on(action, '.ms-elem-selection', function(){
+          that.deselect($(this).data('ms-value'));
+        });
+
+        that.activeMouse(that.$selectionUl);
+        that.activeKeyboard(that.$selectionUl);
+
+        ms.on('focus', function(){
+          that.$selectableUl.focus();
+        });
+      }
+
+      var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
+      that.select(selectedValues, 'init');
+
+      if (typeof that.options.afterInit === 'function') {
+        that.options.afterInit.call(this, this.$container);
+      }
+    },
+
+    'generateLisFromOption' : function(option, index, $container){
+      var that = this,
+          ms = that.$element,
+          attributes = "",
+          $option = $(option);
+
+      for (var cpt = 0; cpt < option.attributes.length; cpt++){
+        var attr = option.attributes[cpt];
+
+        if(attr.name !== 'value' && attr.name !== 'disabled'){
+          attributes += attr.name+'="'+attr.value+'" ';
+        }
+      }
+      var selectableLi = $('<li '+attributes+'><span>'+that.escapeHTML($option.text())+'</span></li>'),
+          selectedLi = selectableLi.clone(),
+          value = $option.val(),
+          elementId = that.sanitize(value);
+
+      selectableLi
+        .data('ms-value', value)
+        .addClass('ms-elem-selectable')
+        .attr('id', elementId+'-selectable');
+
+      selectedLi
+        .data('ms-value', value)
+        .addClass('ms-elem-selection')
+        .attr('id', elementId+'-selection')
+        .hide();
+
+      if ($option.prop('disabled') || ms.prop('disabled')){
+        selectedLi.addClass(that.options.disabledClass);
+        selectableLi.addClass(that.options.disabledClass);
+      }
+
+      var $optgroup = $option.parent('optgroup');
+
+      if ($optgroup.length > 0){
+        var optgroupLabel = $optgroup.attr('label'),
+            optgroupId = that.sanitize(optgroupLabel),
+            $selectableOptgroup = that.$selectableUl.find('#optgroup-selectable-'+optgroupId),
+            $selectionOptgroup = that.$selectionUl.find('#optgroup-selection-'+optgroupId);
+
+        if ($selectableOptgroup.length === 0){
+          var optgroupContainerTpl = '<li class="ms-optgroup-container"></li>',
+              optgroupTpl = '<ul class="ms-optgroup"><li class="ms-optgroup-label"><span>'+optgroupLabel+'</span></li></ul>';
+
+          $selectableOptgroup = $(optgroupContainerTpl);
+          $selectionOptgroup = $(optgroupContainerTpl);
+          $selectableOptgroup.attr('id', 'optgroup-selectable-'+optgroupId);
+          $selectionOptgroup.attr('id', 'optgroup-selection-'+optgroupId);
+          $selectableOptgroup.append($(optgroupTpl));
+          $selectionOptgroup.append($(optgroupTpl));
+          if (that.options.selectableOptgroup){
+            $selectableOptgroup.find('.ms-optgroup-label').on('click', function(){
+              var values = $optgroup.children(':not(:selected, :disabled)').map(function(){ return $(this).val();}).get();
+              that.select(values);
+            });
+            $selectionOptgroup.find('.ms-optgroup-label').on('click', function(){
+              var values = $optgroup.children(':selected:not(:disabled)').map(function(){ return $(this).val();}).get();
+              that.deselect(values);
+            });
+          }
+          that.$selectableUl.append($selectableOptgroup);
+          that.$selectionUl.append($selectionOptgroup);
+        }
+        index = index === undefined ? $selectableOptgroup.find('ul').children().length : index + 1;
+        selectableLi.insertAt(index, $selectableOptgroup.children());
+        selectedLi.insertAt(index, $selectionOptgroup.children());
+      } else {
+        index = index === undefined ? that.$selectableUl.children().length : index;
+
+        selectableLi.insertAt(index, that.$selectableUl);
+        selectedLi.insertAt(index, that.$selectionUl);
+      }
+    },
+
+    'addOption' : function(options){
+      var that = this;
+
+      if (options.value !== undefined && options.value !== null){
+        options = [options];
+      } 
+      $.each(options, function(index, option){
+        if (option.value !== undefined && option.value !== null &&
+            that.$element.find("option[value='"+option.value+"']").length === 0){
+          var $option = $('<option value="'+option.value+'">'+option.text+'</option>'),
+              $container = option.nested === undefined ? that.$element : $("optgroup[label='"+option.nested+"']"),
+              index = parseInt((typeof option.index === 'undefined' ? $container.children().length : option.index));
+
+          if (option.optionClass) {
+            $option.addClass(option.optionClass);
+          }
+
+          if (option.disabled) {
+            $option.prop('disabled', true);
+          }
+
+          $option.insertAt(index, $container);
+          that.generateLisFromOption($option.get(0), index, option.nested);
+        }
+      });
+    },
+
+    'escapeHTML' : function(text){
+      return $("<div>").text(text).html();
+    },
+
+    'activeKeyboard' : function($list){
+      var that = this;
+
+      $list.on('focus', function(){
+        $(this).addClass('ms-focus');
+      })
+      .on('blur', function(){
+        $(this).removeClass('ms-focus');
+      })
+      .on('keydown', function(e){
+        switch (e.which) {
+          case 40:
+          case 38:
+            e.preventDefault();
+            e.stopPropagation();
+            that.moveHighlight($(this), (e.which === 38) ? -1 : 1);
+            return;
+          case 37:
+          case 39:
+            e.preventDefault();
+            e.stopPropagation();
+            that.switchList($list);
+            return;
+          case 9:
+            if(that.$element.is('[tabindex]')){
+              e.preventDefault();
+              var tabindex = parseInt(that.$element.attr('tabindex'), 10);
+              tabindex = (e.shiftKey) ? tabindex-1 : tabindex+1;
+              $('[tabindex="'+(tabindex)+'"]').focus();
+              return;
+            }else{
+              if(e.shiftKey){
+                that.$element.trigger('focus');
+              }
+            }
+        }
+        if($.inArray(e.which, that.options.keySelect) > -1){
+          e.preventDefault();
+          e.stopPropagation();
+          that.selectHighlighted($list);
+          return;
+        }
+      });
+    },
+
+    'moveHighlight': function($list, direction){
+      var $elems = $list.find(this.elemsSelector),
+          $currElem = $elems.filter('.ms-hover'),
+          $nextElem = null,
+          elemHeight = $elems.first().outerHeight(),
+          containerHeight = $list.height(),
+          containerSelector = '#'+this.$container.prop('id');
+
+      $elems.removeClass('ms-hover');
+      if (direction === 1){ // DOWN
+
+        $nextElem = $currElem.nextAll(this.elemsSelector).first();
+        if ($nextElem.length === 0){
+          var $optgroupUl = $currElem.parent();
+
+          if ($optgroupUl.hasClass('ms-optgroup')){
+            var $optgroupLi = $optgroupUl.parent(),
+                $nextOptgroupLi = $optgroupLi.next(':visible');
+
+            if ($nextOptgroupLi.length > 0){
+              $nextElem = $nextOptgroupLi.find(this.elemsSelector).first();
+            } else {
+              $nextElem = $elems.first();
+            }
+          } else {
+            $nextElem = $elems.first();
+          }
+        }
+      } else if (direction === -1){ // UP
+
+        $nextElem = $currElem.prevAll(this.elemsSelector).first();
+        if ($nextElem.length === 0){
+          var $optgroupUl = $currElem.parent();
+
+          if ($optgroupUl.hasClass('ms-optgroup')){
+            var $optgroupLi = $optgroupUl.parent(),
+                $prevOptgroupLi = $optgroupLi.prev(':visible');
+
+            if ($prevOptgroupLi.length > 0){
+              $nextElem = $prevOptgroupLi.find(this.elemsSelector).last();
+            } else {
+              $nextElem = $elems.last();
+            }
+          } else {
+            $nextElem = $elems.last();
+          }
+        }
+      }
+      if ($nextElem.length > 0){
+        $nextElem.addClass('ms-hover');
+        var scrollTo = $list.scrollTop() + $nextElem.position().top - 
+                       containerHeight / 2 + elemHeight / 2;
+
+        $list.scrollTop(scrollTo);
+      }
+    },
+
+    'selectHighlighted' : function($list){
+      var $elems = $list.find(this.elemsSelector),
+          $highlightedElem = $elems.filter('.ms-hover').first();
+
+      if ($highlightedElem.length > 0){
+        if ($list.parent().hasClass('ms-selectable')){
+          this.select($highlightedElem.data('ms-value'));
+        } else {
+          this.deselect($highlightedElem.data('ms-value'));
+        }
+        $elems.removeClass('ms-hover');
+      }
+    },
+
+    'switchList' : function($list){
+      $list.blur();
+      this.$container.find(this.elemsSelector).removeClass('ms-hover');
+      if ($list.parent().hasClass('ms-selectable')){
+        this.$selectionUl.focus();
+      } else {
+        this.$selectableUl.focus();
+      }
+    },
+
+    'activeMouse' : function($list){
+      var that = this;
+
+      this.$container.on('mouseenter', that.elemsSelector, function(){
+        $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
+        $(this).addClass('ms-hover');
+      });
+
+      this.$container.on('mouseleave', that.elemsSelector, function () {
+        $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
+      });
+    },
+
+    'refresh' : function() {
+      this.destroy();
+      this.$element.multiSelect(this.options);
+    },
+
+    'destroy' : function(){
+      $("#ms-"+this.$element.attr("id")).remove();
+      this.$element.off('focus');
+      this.$element.css('position', '').css('left', '');
+      this.$element.removeData('multiselect');
+    },
+
+    'select' : function(value, method){
+      if (typeof value === 'string'){ value = [value]; }
+
+      var that = this,
+          ms = this.$element,
+          msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
+          selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
+          selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection').filter(':not(.'+that.options.disabledClass+')'),
+          options = ms.find('option:not(:disabled)').filter(function(){ return($.inArray(this.value, value) > -1); });
+
+      if (method === 'init'){
+        selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
+        selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection');
+      }
+
+      if (selectables.length > 0){
+        selectables.addClass('ms-selected').hide();
+        selections.addClass('ms-selected').show();
+
+        options.prop('selected', true);
+
+        that.$container.find(that.elemsSelector).removeClass('ms-hover');
+
+        var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
+        if (selectableOptgroups.length > 0){
+          selectableOptgroups.each(function(){
+            var selectablesLi = $(this).find('.ms-elem-selectable');
+            if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
+              $(this).find('.ms-optgroup-label').hide();
+            }
+          });
+
+          var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
+          selectionOptgroups.each(function(){
+            var selectionsLi = $(this).find('.ms-elem-selection');
+            if (selectionsLi.filter('.ms-selected').length > 0){
+              $(this).find('.ms-optgroup-label').show();
+            }
+          });
+        } else {
+          if (that.options.keepOrder && method !== 'init'){
+            var selectionLiLast = that.$selectionUl.find('.ms-selected');
+            if((selectionLiLast.length > 1) && (selectionLiLast.last().get(0) != selections.get(0))) {
+              selections.insertAfter(selectionLiLast.last());
+            }
+          }
+        }
+        if (method !== 'init'){
+          ms.trigger('change');
+          if (typeof that.options.afterSelect === 'function') {
+            that.options.afterSelect.call(this, value);
+          }
+        }
+      }
+    },
+
+    'deselect' : function(value){
+      if (typeof value === 'string'){ value = [value]; }
+
+      var that = this,
+          ms = this.$element,
+          msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
+          selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
+          selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected').filter(':not(.'+that.options.disabledClass+')'),
+          options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
+
+      if (selections.length > 0){
+        selectables.removeClass('ms-selected').show();
+        selections.removeClass('ms-selected').hide();
+        options.prop('selected', false);
+
+        that.$container.find(that.elemsSelector).removeClass('ms-hover');
+
+        var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
+        if (selectableOptgroups.length > 0){
+          selectableOptgroups.each(function(){
+            var selectablesLi = $(this).find('.ms-elem-selectable');
+            if (selectablesLi.filter(':not(.ms-selected)').length > 0){
+              $(this).find('.ms-optgroup-label').show();
+            }
+          });
+
+          var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
+          selectionOptgroups.each(function(){
+            var selectionsLi = $(this).find('.ms-elem-selection');
+            if (selectionsLi.filter('.ms-selected').length === 0){
+              $(this).find('.ms-optgroup-label').hide();
+            }
+          });
+        }
+        ms.trigger('change');
+        if (typeof that.options.afterDeselect === 'function') {
+          that.options.afterDeselect.call(this, value);
+        }
+      }
+    },
+
+    'select_all' : function(){
+      var ms = this.$element,
+          values = ms.val();
+
+      ms.find('option:not(":disabled")').prop('selected', true);
+      this.$selectableUl.find('.ms-elem-selectable').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').hide();
+      this.$selectionUl.find('.ms-optgroup-label').show();
+      this.$selectableUl.find('.ms-optgroup-label').hide();
+      this.$selectionUl.find('.ms-elem-selection').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').show();
+      this.$selectionUl.focus();
+      ms.trigger('change');
+      if (typeof this.options.afterSelect === 'function') {
+        var selectedValues = $.grep(ms.val(), function(item){
+          return $.inArray(item, values) < 0;
+        });
+        this.options.afterSelect.call(this, selectedValues);
+      }
+    },
+
+    'deselect_all' : function(){
+      var ms = this.$element,
+          values = ms.val();
+
+      ms.find('option').prop('selected', false);
+      this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
+      this.$selectionUl.find('.ms-optgroup-label').hide();
+      this.$selectableUl.find('.ms-optgroup-label').show();
+      this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
+      this.$selectableUl.focus();
+      ms.trigger('change');
+      if (typeof this.options.afterDeselect === 'function') {
+        this.options.afterDeselect.call(this, values);
+      }
+    },
+
+    sanitize: function(value){
+      var hash = 0, i, character;
+      if (value.length == 0) return hash;
+      var ls = 0;
+      for (i = 0, ls = value.length; i < ls; i++) {
+        character  = value.charCodeAt(i);
+        hash  = ((hash<<5)-hash)+character;
+        hash |= 0; // Convert to 32bit integer
+      }
+      return hash;
+    }
+  };
+
+  /* MULTISELECT PLUGIN DEFINITION
+   * ======================= */
+
+  $.fn.multiSelect = function () {
+    var option = arguments[0],
+        args = arguments;
+
+    return this.each(function () {
+      var $this = $(this),
+          data = $this.data('multiselect'),
+          options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
+
+      if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
+
+      if (typeof option === 'string'){
+        data[option](args[1]);
+      } else {
+        data.init();
+      }
+    });
+  };
+
+  $.fn.multiSelect.defaults = {
+    keySelect: [32],
+    selectableOptgroup: false,
+    disabledClass : 'disabled',
+    dblClick : false,
+    keepOrder: false,
+    cssClass: ''
+  };
+
+  $.fn.multiSelect.Constructor = MultiSelect;
+
+  $.fn.insertAt = function(index, $parent) {
+    return this.each(function() {
+      if (index === 0) {
+        $parent.prepend(this);
+      } else {
+        $parent.children().eq(index - 1).after(this);
+      }
+    });
+};
+
+}(window.jQuery);

+ 94 - 0
public/plugins/ueditor/config.json

xqd
@@ -0,0 +1,94 @@
+/* 前后端通信相关的配置,注释只允许使用多行方式 */
+{
+    /* 上传图片配置项 */
+    "imageActionName": "uploadimage", /* 执行上传图片的action名称 */
+    "imageFieldName": "upfile", /* 提交的图片表单名称 */
+    "imageMaxSize": 2048000, /* 上传大小限制,单位B */
+    "imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */
+    "imageCompressEnable": true, /* 是否压缩图片,默认是true */
+    "imageCompressBorder": 1600, /* 图片压缩最长边限制 */
+    "imageInsertAlign": "none", /* 插入的图片浮动方式 */
+    "imageUrlPrefix": "", /* 图片访问路径前缀 */
+    "imagePathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+                                /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
+                                /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
+                                /* {time} 会替换成时间戳 */
+                                /* {yyyy} 会替换成四位年份 */
+                                /* {yy} 会替换成两位年份 */
+                                /* {mm} 会替换成两位月份 */
+                                /* {dd} 会替换成两位日期 */
+                                /* {hh} 会替换成两位小时 */
+                                /* {ii} 会替换成两位分钟 */
+                                /* {ss} 会替换成两位秒 */
+                                /* 非法字符 \ : * ? " < > | */
+                                /* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */
+
+    /* 涂鸦图片上传配置项 */
+    "scrawlActionName": "uploadscrawl", /* 执行上传涂鸦的action名称 */
+    "scrawlFieldName": "upfile", /* 提交的图片表单名称 */
+    "scrawlPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+    "scrawlMaxSize": 2048000, /* 上传大小限制,单位B */
+    "scrawlUrlPrefix": "", /* 图片访问路径前缀 */
+    "scrawlInsertAlign": "none",
+
+    /* 截图工具上传 */
+    "snapscreenActionName": "uploadimage", /* 执行上传截图的action名称 */
+    "snapscreenPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+    "snapscreenUrlPrefix": "", /* 图片访问路径前缀 */
+    "snapscreenInsertAlign": "none", /* 插入的图片浮动方式 */
+
+    /* 抓取远程图片配置 */
+    "catcherLocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
+    "catcherActionName": "catchimage", /* 执行抓取远程图片的action名称 */
+    "catcherFieldName": "source", /* 提交的图片列表表单名称 */
+    "catcherPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+    "catcherUrlPrefix": "", /* 图片访问路径前缀 */
+    "catcherMaxSize": 2048000, /* 上传大小限制,单位B */
+    "catcherAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 抓取图片格式显示 */
+
+    /* 上传视频配置 */
+    "videoActionName": "uploadvideo", /* 执行上传视频的action名称 */
+    "videoFieldName": "upfile", /* 提交的视频表单名称 */
+    "videoPathFormat": "/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+    "videoUrlPrefix": "", /* 视频访问路径前缀 */
+    "videoMaxSize": 102400000, /* 上传大小限制,单位B,默认100MB */
+    "videoAllowFiles": [
+        ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
+        ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"], /* 上传视频格式显示 */
+
+    /* 上传文件配置 */
+    "fileActionName": "uploadfile", /* controller里,执行上传视频的action名称 */
+    "fileFieldName": "upfile", /* 提交的文件表单名称 */
+    "filePathFormat": "/ueditor/php/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
+    "fileUrlPrefix": "", /* 文件访问路径前缀 */
+    "fileMaxSize": 51200000, /* 上传大小限制,单位B,默认50MB */
+    "fileAllowFiles": [
+        ".png", ".jpg", ".jpeg", ".gif", ".bmp",
+        ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
+        ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
+        ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
+        ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
+    ], /* 上传文件格式显示 */
+
+    /* 列出指定目录下的图片 */
+    "imageManagerActionName": "listimage", /* 执行图片管理的action名称 */
+    "imageManagerListPath": "/ueditor/php/upload/image/", /* 指定要列出图片的目录 */
+    "imageManagerListSize": 20, /* 每次列出文件数量 */
+    "imageManagerUrlPrefix": "", /* 图片访问路径前缀 */
+    "imageManagerInsertAlign": "none", /* 插入的图片浮动方式 */
+    "imageManagerAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 列出的文件类型 */
+
+    /* 列出指定目录下的文件 */
+    "fileManagerActionName": "listfile", /* 执行文件管理的action名称 */
+    "fileManagerListPath": "/ueditor/php/upload/file/", /* 指定要列出文件的目录 */
+    "fileManagerUrlPrefix": "", /* 文件访问路径前缀 */
+    "fileManagerListSize": 20, /* 每次列出文件数量 */
+    "fileManagerAllowFiles": [
+        ".png", ".jpg", ".jpeg", ".gif", ".bmp",
+        ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
+        ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
+        ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
+        ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
+    ] /* 列出的文件类型 */
+
+}

+ 40 - 0
public/plugins/ueditor/dialogs/anchor/anchor.html

xqd
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+    "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+        <title></title>
+        <style type="text/css">
+            *{color: #838383;margin: 0;padding: 0}
+            html,body {font-size: 12px;overflow: hidden; }
+            .content{padding:5px 0 0 15px;}
+            input{width:210px;height:21px;line-height:21px;margin-left: 4px;}
+        </style>
+    </head>
+    <body>
+        <div class="content">
+            <span><var id="lang_input_anchorName"></var></span><input id="anchorName"  value="" />
+        </div>
+        <script type="text/javascript" src="../internal.js"></script>
+        <script type="text/javascript">
+            var anchorInput = $G('anchorName'),
+                node = editor.selection.getRange().getClosedNode();
+            if(node && node.tagName == 'IMG' && (node = node.getAttribute('anchorname'))){
+                anchorInput.value = node;
+            }
+            anchorInput.onkeydown = function(evt){
+                evt = evt || window.event;
+                if(evt.keyCode == 13){
+                    editor.execCommand('anchor', anchorInput.value);
+                    dialog.close();
+                    domUtils.preventDefault(evt)
+                }
+            };
+            dialog.onok = function (){
+                editor.execCommand('anchor', anchorInput.value);
+                dialog.close();
+            };
+            $focus(anchorInput);
+        </script>
+    </body>
+</html>

+ 681 - 0
public/plugins/ueditor/dialogs/attachment/attachment.css

xqd
@@ -0,0 +1,681 @@
+@charset "utf-8";
+/* dialog样式 */
+.wrapper {
+    zoom: 1;
+    width: 630px;
+    *width: 626px;
+    height: 380px;
+    margin: 0 auto;
+    padding: 10px;
+    position: relative;
+    font-family: sans-serif;
+}
+
+/*tab样式框大小*/
+.tabhead {
+    float:left;
+}
+.tabbody {
+    width: 100%;
+    height: 346px;
+    position: relative;
+    clear: both;
+}
+
+.tabbody .panel {
+    position: absolute;
+    width: 0;
+    height: 0;
+    background: #fff;
+    overflow: hidden;
+    display: none;
+}
+
+.tabbody .panel.focus {
+    width: 100%;
+    height: 346px;
+    display: block;
+}
+
+/* 上传附件 */
+.tabbody #upload.panel {
+    width: 0;
+    height: 0;
+    overflow: hidden;
+    position: absolute !important;
+    clip: rect(1px, 1px, 1px, 1px);
+    background: #fff;
+    display: block;
+}
+
+.tabbody #upload.panel.focus {
+    width: 100%;
+    height: 346px;
+    display: block;
+    clip: auto;
+}
+
+#upload .queueList {
+    margin: 0;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    overflow: hidden;
+}
+
+#upload p {
+    margin: 0;
+}
+
+.element-invisible {
+    width: 0 !important;
+    height: 0 !important;
+    border: 0;
+    padding: 0;
+    margin: 0;
+    overflow: hidden;
+    position: absolute !important;
+    clip: rect(1px, 1px, 1px, 1px);
+}
+
+#upload .placeholder {
+    margin: 10px;
+    border: 2px dashed #e6e6e6;
+    *border: 0px dashed #e6e6e6;
+    height: 172px;
+    padding-top: 150px;
+    text-align: center;
+    background: url(./images/image.png) center 70px no-repeat;
+    color: #cccccc;
+    font-size: 18px;
+    position: relative;
+    top:0;
+    *top: 10px;
+}
+
+#upload .placeholder .webuploader-pick {
+    font-size: 18px;
+    background: #00b7ee;
+    border-radius: 3px;
+    line-height: 44px;
+    padding: 0 30px;
+    *width: 120px;
+    color: #fff;
+    display: inline-block;
+    margin: 0 auto 20px auto;
+    cursor: pointer;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+
+#upload .placeholder .webuploader-pick-hover {
+    background: #00a2d4;
+}
+
+
+#filePickerContainer {
+    text-align: center;
+}
+
+#upload .placeholder .flashTip {
+    color: #666666;
+    font-size: 12px;
+    position: absolute;
+    width: 100%;
+    text-align: center;
+    bottom: 20px;
+}
+
+#upload .placeholder .flashTip a {
+    color: #0785d1;
+    text-decoration: none;
+}
+
+#upload .placeholder .flashTip a:hover {
+    text-decoration: underline;
+}
+
+#upload .placeholder.webuploader-dnd-over {
+    border-color: #999999;
+}
+
+#upload .filelist {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    overflow-x: hidden;
+    overflow-y: auto;
+    position: relative;
+    height: 300px;
+}
+
+#upload .filelist:after {
+    content: '';
+    display: block;
+    width: 0;
+    height: 0;
+    overflow: hidden;
+    clear: both;
+}
+
+#upload .filelist li {
+    width: 113px;
+    height: 113px;
+    background: url(./images/bg.png);
+    text-align: center;
+    margin: 9px 0 0 9px;
+    *margin: 6px 0 0 6px;
+    position: relative;
+    display: block;
+    float: left;
+    overflow: hidden;
+    font-size: 12px;
+}
+
+#upload .filelist li p.log {
+    position: relative;
+    top: -45px;
+}
+
+#upload .filelist li p.title {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    top: 5px;
+    text-indent: 5px;
+    text-align: left;
+}
+
+#upload .filelist li p.progress {
+    position: absolute;
+    width: 100%;
+    bottom: 0;
+    left: 0;
+    height: 8px;
+    overflow: hidden;
+    z-index: 50;
+    margin: 0;
+    border-radius: 0;
+    background: none;
+    -webkit-box-shadow: 0 0 0;
+}
+
+#upload .filelist li p.progress span {
+    display: none;
+    overflow: hidden;
+    width: 0;
+    height: 100%;
+    background: #1483d8 url(./images/progress.png) repeat-x;
+
+    -webit-transition: width 200ms linear;
+    -moz-transition: width 200ms linear;
+    -o-transition: width 200ms linear;
+    -ms-transition: width 200ms linear;
+    transition: width 200ms linear;
+
+    -webkit-animation: progressmove 2s linear infinite;
+    -moz-animation: progressmove 2s linear infinite;
+    -o-animation: progressmove 2s linear infinite;
+    -ms-animation: progressmove 2s linear infinite;
+    animation: progressmove 2s linear infinite;
+
+    -webkit-transform: translateZ(0);
+}
+
+@-webkit-keyframes progressmove {
+    0% {
+        background-position: 0 0;
+    }
+    100% {
+        background-position: 17px 0;
+    }
+}
+
+@-moz-keyframes progressmove {
+    0% {
+        background-position: 0 0;
+    }
+    100% {
+        background-position: 17px 0;
+    }
+}
+
+@keyframes progressmove {
+    0% {
+        background-position: 0 0;
+    }
+    100% {
+        background-position: 17px 0;
+    }
+}
+
+#upload .filelist li p.imgWrap {
+    position: relative;
+    z-index: 2;
+    line-height: 113px;
+    vertical-align: middle;
+    overflow: hidden;
+    width: 113px;
+    height: 113px;
+
+    -webkit-transform-origin: 50% 50%;
+    -moz-transform-origin: 50% 50%;
+    -o-transform-origin: 50% 50%;
+    -ms-transform-origin: 50% 50%;
+    transform-origin: 50% 50%;
+
+    -webit-transition: 200ms ease-out;
+    -moz-transition: 200ms ease-out;
+    -o-transition: 200ms ease-out;
+    -ms-transition: 200ms ease-out;
+    transition: 200ms ease-out;
+}
+#upload .filelist li p.imgWrap.notimage {
+    margin-top: 0;
+    width: 111px;
+    height: 111px;
+    border: 1px #eeeeee solid;
+}
+#upload .filelist li p.imgWrap.notimage i.file-preview {
+    margin-top: 15px;
+}
+
+#upload .filelist li img {
+    width: 100%;
+}
+
+#upload .filelist li p.error {
+    background: #f43838;
+    color: #fff;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 28px;
+    line-height: 28px;
+    width: 100%;
+    z-index: 100;
+    display:none;
+}
+
+#upload .filelist li .success {
+    display: block;
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    height: 40px;
+    width: 100%;
+    z-index: 200;
+    background: url(./images/success.png) no-repeat right bottom;
+    background-image: url(./images/success.gif) \9;
+}
+
+#upload .filelist li.filePickerBlock {
+    width: 113px;
+    height: 113px;
+    background: url(./images/image.png) no-repeat center 12px;
+    border: 1px solid #eeeeee;
+    border-radius: 0;
+}
+#upload .filelist li.filePickerBlock div.webuploader-pick  {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    opacity: 0;
+    background: none;
+    font-size: 0;
+}
+
+#upload .filelist div.file-panel {
+    position: absolute;
+    height: 0;
+    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \0;
+    background: rgba(0, 0, 0, 0.5);
+    width: 100%;
+    top: 0;
+    left: 0;
+    overflow: hidden;
+    z-index: 300;
+}
+
+#upload .filelist div.file-panel span {
+    width: 24px;
+    height: 24px;
+    display: inline;
+    float: right;
+    text-indent: -9999px;
+    overflow: hidden;
+    background: url(./images/icons.png) no-repeat;
+    background: url(./images/icons.gif) no-repeat \9;
+    margin: 5px 1px 1px;
+    cursor: pointer;
+    -webkit-tap-highlight-color: rgba(0,0,0,0);
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+#upload .filelist div.file-panel span.rotateLeft {
+    display:none;
+    background-position: 0 -24px;
+}
+
+#upload .filelist div.file-panel span.rotateLeft:hover {
+    background-position: 0 0;
+}
+
+#upload .filelist div.file-panel span.rotateRight {
+    display:none;
+    background-position: -24px -24px;
+}
+
+#upload .filelist div.file-panel span.rotateRight:hover {
+    background-position: -24px 0;
+}
+
+#upload .filelist div.file-panel span.cancel {
+    background-position: -48px -24px;
+}
+
+#upload .filelist div.file-panel span.cancel:hover {
+    background-position: -48px 0;
+}
+
+#upload .statusBar {
+    height: 45px;
+    border-bottom: 1px solid #dadada;
+    margin: 0 10px;
+    padding: 0;
+    line-height: 45px;
+    vertical-align: middle;
+    position: relative;
+}
+
+#upload .statusBar .progress {
+    border: 1px solid #1483d8;
+    width: 198px;
+    background: #fff;
+    height: 18px;
+    position: absolute;
+    top: 12px;
+    display: none;
+    text-align: center;
+    line-height: 18px;
+    color: #6dbfff;
+    margin: 0 10px 0 0;
+}
+#upload .statusBar .progress span.percentage {
+    width: 0;
+    height: 100%;
+    left: 0;
+    top: 0;
+    background: #1483d8;
+    position: absolute;
+}
+#upload .statusBar .progress span.text {
+    position: relative;
+    z-index: 10;
+}
+
+#upload .statusBar .info {
+    display: inline-block;
+    font-size: 14px;
+    color: #666666;
+}
+
+#upload .statusBar .btns {
+    position: absolute;
+    top: 7px;
+    right: 0;
+    line-height: 30px;
+}
+
+#filePickerBtn {
+    display: inline-block;
+    float: left;
+}
+#upload .statusBar .btns .webuploader-pick,
+#upload .statusBar .btns .uploadBtn,
+#upload .statusBar .btns .uploadBtn.state-uploading,
+#upload .statusBar .btns .uploadBtn.state-paused {
+    background: #ffffff;
+    border: 1px solid #cfcfcf;
+    color: #565656;
+    padding: 0 18px;
+    display: inline-block;
+    border-radius: 3px;
+    margin-left: 10px;
+    cursor: pointer;
+    font-size: 14px;
+    float: left;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+#upload .statusBar .btns .webuploader-pick-hover,
+#upload .statusBar .btns .uploadBtn:hover,
+#upload .statusBar .btns .uploadBtn.state-uploading:hover,
+#upload .statusBar .btns .uploadBtn.state-paused:hover {
+    background: #f0f0f0;
+}
+
+#upload .statusBar .btns .uploadBtn,
+#upload .statusBar .btns .uploadBtn.state-paused{
+    background: #00b7ee;
+    color: #fff;
+    border-color: transparent;
+}
+#upload .statusBar .btns .uploadBtn:hover,
+#upload .statusBar .btns .uploadBtn.state-paused:hover{
+    background: #00a2d4;
+}
+
+#upload .statusBar .btns .uploadBtn.disabled {
+    pointer-events: none;
+    filter:alpha(opacity=60);
+    -moz-opacity:0.6;
+    -khtml-opacity: 0.6;
+    opacity: 0.6;
+}
+
+
+
+/* 图片管理样式 */
+#online {
+    width: 100%;
+    height: 336px;
+    padding: 10px 0 0 0;
+}
+#online #fileList{
+    width: 100%;
+    height: 100%;
+    overflow-x: hidden;
+    overflow-y: auto;
+    position: relative;
+}
+#online ul {
+    display: block;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+#online li {
+    float: left;
+    display: block;
+    list-style: none;
+    padding: 0;
+    width: 113px;
+    height: 113px;
+    margin: 0 0 9px 9px;
+    *margin: 0 0 6px 6px;
+    background-color: #eee;
+    overflow: hidden;
+    cursor: pointer;
+    position: relative;
+}
+#online li.clearFloat {
+    float: none;
+    clear: both;
+    display: block;
+    width:0;
+    height:0;
+    margin: 0;
+    padding: 0;
+}
+#online li img {
+    cursor: pointer;
+}
+#online li div.file-wrapper {
+    cursor: pointer;
+    position: absolute;
+    display: block;
+    width: 111px;
+    height: 111px;
+    border: 1px solid #eee;
+    background: url("./images/bg.png") repeat;
+}
+#online li div span.file-title{
+    display: block;
+    padding: 0 3px;
+    margin: 3px 0 0 0;
+    font-size: 12px;
+    height: 13px;
+    color: #555555;
+    text-align: center;
+    width: 107px;
+    white-space: nowrap;
+    word-break: break-all;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+#online li .icon {
+    cursor: pointer;
+    width: 113px;
+    height: 113px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 2;
+    border: 0;
+    background-repeat: no-repeat;
+}
+#online li .icon:hover {
+    width: 107px;
+    height: 107px;
+    border: 3px solid #1094fa;
+}
+#online li.selected .icon {
+    background-image: url(images/success.png);
+    background-image: url(images/success.gif) \9;
+    background-position: 75px 75px;
+}
+#online li.selected .icon:hover {
+    width: 107px;
+    height: 107px;
+    border: 3px solid #1094fa;
+    background-position: 72px 72px;
+}
+
+
+/* 在线文件的文件预览图标 */
+i.file-preview {
+    display: block;
+    margin: 10px auto;
+    width: 70px;
+    height: 70px;
+    background-image: url("./images/file-icons.png");
+    background-image: url("./images/file-icons.gif") \9;
+    background-position: -140px center;
+    background-repeat: no-repeat;
+}
+i.file-preview.file-type-dir{
+    background-position: 0 center;
+}
+i.file-preview.file-type-file{
+    background-position: -140px center;
+}
+i.file-preview.file-type-filelist{
+    background-position: -210px center;
+}
+i.file-preview.file-type-zip,
+i.file-preview.file-type-rar,
+i.file-preview.file-type-7z,
+i.file-preview.file-type-tar,
+i.file-preview.file-type-gz,
+i.file-preview.file-type-bz2{
+    background-position: -280px center;
+}
+i.file-preview.file-type-xls,
+i.file-preview.file-type-xlsx{
+    background-position: -350px center;
+}
+i.file-preview.file-type-doc,
+i.file-preview.file-type-docx{
+    background-position: -420px center;
+}
+i.file-preview.file-type-ppt,
+i.file-preview.file-type-pptx{
+    background-position: -490px center;
+}
+i.file-preview.file-type-vsd{
+    background-position: -560px center;
+}
+i.file-preview.file-type-pdf{
+    background-position: -630px center;
+}
+i.file-preview.file-type-txt,
+i.file-preview.file-type-md,
+i.file-preview.file-type-json,
+i.file-preview.file-type-htm,
+i.file-preview.file-type-xml,
+i.file-preview.file-type-html,
+i.file-preview.file-type-js,
+i.file-preview.file-type-css,
+i.file-preview.file-type-php,
+i.file-preview.file-type-jsp,
+i.file-preview.file-type-asp{
+    background-position: -700px center;
+}
+i.file-preview.file-type-apk{
+    background-position: -770px center;
+}
+i.file-preview.file-type-exe{
+    background-position: -840px center;
+}
+i.file-preview.file-type-ipa{
+    background-position: -910px center;
+}
+i.file-preview.file-type-mp4,
+i.file-preview.file-type-swf,
+i.file-preview.file-type-mkv,
+i.file-preview.file-type-avi,
+i.file-preview.file-type-flv,
+i.file-preview.file-type-mov,
+i.file-preview.file-type-mpg,
+i.file-preview.file-type-mpeg,
+i.file-preview.file-type-ogv,
+i.file-preview.file-type-webm,
+i.file-preview.file-type-rm,
+i.file-preview.file-type-rmvb{
+    background-position: -980px center;
+}
+i.file-preview.file-type-ogg,
+i.file-preview.file-type-wav,
+i.file-preview.file-type-wmv,
+i.file-preview.file-type-mid,
+i.file-preview.file-type-mp3{
+    background-position: -1050px center;
+}
+i.file-preview.file-type-jpg,
+i.file-preview.file-type-jpeg,
+i.file-preview.file-type-gif,
+i.file-preview.file-type-bmp,
+i.file-preview.file-type-png,
+i.file-preview.file-type-psd{
+    background-position: -140px center;
+}

+ 60 - 0
public/plugins/ueditor/dialogs/attachment/attachment.html

xqd
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>ueditor图片对话框</title>
+    <script type="text/javascript" src="../internal.js"></script>
+
+    <!-- jquery -->
+    <script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script>
+
+    <!-- webuploader -->
+    <script src="../../third-party/webuploader/webuploader.min.js"></script>
+    <link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css">
+
+    <!-- attachment dialog -->
+    <link rel="stylesheet" href="attachment.css" type="text/css" />
+</head>
+<body>
+
+    <div class="wrapper">
+        <div id="tabhead" class="tabhead">
+            <span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span>
+            <span class="tab" data-content-id="online"><var id="lang_tab_online"></var></span>
+        </div>
+        <div id="tabbody" class="tabbody">
+            <!-- 上传图片 -->
+            <div id="upload" class="panel focus">
+                <div id="queueList" class="queueList">
+                    <div class="statusBar element-invisible">
+                        <div class="progress">
+                            <span class="text">0%</span>
+                            <span class="percentage"></span>
+                        </div><div class="info"></div>
+                        <div class="btns">
+                            <div id="filePickerBtn"></div>
+                            <div class="uploadBtn"><var id="lang_start_upload"></var></div>
+                        </div>
+                    </div>
+                    <div id="dndArea" class="placeholder">
+                        <div class="filePickerContainer">
+                            <div id="filePickerReady"></div>
+                        </div>
+                    </div>
+                    <ul class="filelist element-invisible">
+                        <li id="filePickerBlock" class="filePickerBlock"></li>
+                    </ul>
+                </div>
+            </div>
+
+            <!-- 在线图片 -->
+            <div id="online" class="panel">
+                <div id="fileList"><var id="lang_imgLoading"></var></div>
+            </div>
+
+        </div>
+    </div>
+    <script type="text/javascript" src="attachment.js"></script>
+
+</body>
+</html>

+ 754 - 0
public/plugins/ueditor/dialogs/attachment/attachment.js

xqd
@@ -0,0 +1,754 @@
+/**
+ * User: Jinqn
+ * Date: 14-04-08
+ * Time: 下午16:34
+ * 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片
+ */
+
+(function () {
+
+    var uploadFile,
+        onlineFile;
+
+    window.onload = function () {
+        initTabs();
+        initButtons();
+    };
+
+    /* 初始化tab标签 */
+    function initTabs() {
+        var tabs = $G('tabhead').children;
+        for (var i = 0; i < tabs.length; i++) {
+            domUtils.on(tabs[i], "click", function (e) {
+                var target = e.target || e.srcElement;
+                setTabFocus(target.getAttribute('data-content-id'));
+            });
+        }
+
+        setTabFocus('upload');
+    }
+
+    /* 初始化tabbody */
+    function setTabFocus(id) {
+        if(!id) return;
+        var i, bodyId, tabs = $G('tabhead').children;
+        for (i = 0; i < tabs.length; i++) {
+            bodyId = tabs[i].getAttribute('data-content-id')
+            if (bodyId == id) {
+                domUtils.addClass(tabs[i], 'focus');
+                domUtils.addClass($G(bodyId), 'focus');
+            } else {
+                domUtils.removeClasses(tabs[i], 'focus');
+                domUtils.removeClasses($G(bodyId), 'focus');
+            }
+        }
+        switch (id) {
+            case 'upload':
+                uploadFile = uploadFile || new UploadFile('queueList');
+                break;
+            case 'online':
+                onlineFile = onlineFile || new OnlineFile('fileList');
+                break;
+        }
+    }
+
+    /* 初始化onok事件 */
+    function initButtons() {
+
+        dialog.onok = function () {
+            var list = [], id, tabs = $G('tabhead').children;
+            for (var i = 0; i < tabs.length; i++) {
+                if (domUtils.hasClass(tabs[i], 'focus')) {
+                    id = tabs[i].getAttribute('data-content-id');
+                    break;
+                }
+            }
+
+            switch (id) {
+                case 'upload':
+                    list = uploadFile.getInsertList();
+                    var count = uploadFile.getQueueCount();
+                    if (count) {
+                        $('.info', '#queueList').html('<span style="color:red;">' + '还有2个未上传文件'.replace(/[\d]/, count) + '</span>');
+                        return false;
+                    }
+                    break;
+                case 'online':
+                    list = onlineFile.getInsertList();
+                    break;
+            }
+
+            editor.execCommand('insertfile', list);
+        };
+    }
+
+
+    /* 上传附件 */
+    function UploadFile(target) {
+        this.$wrap = target.constructor == String ? $('#' + target) : $(target);
+        this.init();
+    }
+    UploadFile.prototype = {
+        init: function () {
+            this.fileList = [];
+            this.initContainer();
+            this.initUploader();
+        },
+        initContainer: function () {
+            this.$queue = this.$wrap.find('.filelist');
+        },
+        /* 初始化容器 */
+        initUploader: function () {
+            var _this = this,
+                $ = jQuery,    // just in case. Make sure it's not an other libaray.
+                $wrap = _this.$wrap,
+            // 图片容器
+                $queue = $wrap.find('.filelist'),
+            // 状态栏,包括进度和控制按钮
+                $statusBar = $wrap.find('.statusBar'),
+            // 文件总体选择信息。
+                $info = $statusBar.find('.info'),
+            // 上传按钮
+                $upload = $wrap.find('.uploadBtn'),
+            // 上传按钮
+                $filePickerBtn = $wrap.find('.filePickerBtn'),
+            // 上传按钮
+                $filePickerBlock = $wrap.find('.filePickerBlock'),
+            // 没选择文件之前的内容。
+                $placeHolder = $wrap.find('.placeholder'),
+            // 总体进度条
+                $progress = $statusBar.find('.progress').hide(),
+            // 添加的文件数量
+                fileCount = 0,
+            // 添加的文件总大小
+                fileSize = 0,
+            // 优化retina, 在retina下这个值是2
+                ratio = window.devicePixelRatio || 1,
+            // 缩略图大小
+                thumbnailWidth = 113 * ratio,
+                thumbnailHeight = 113 * ratio,
+            // 可能有pedding, ready, uploading, confirm, done.
+                state = '',
+            // 所有文件的进度信息,key为file id
+                percentages = {},
+                supportTransition = (function () {
+                    var s = document.createElement('p').style,
+                        r = 'transition' in s ||
+                            'WebkitTransition' in s ||
+                            'MozTransition' in s ||
+                            'msTransition' in s ||
+                            'OTransition' in s;
+                    s = null;
+                    return r;
+                })(),
+            // WebUploader实例
+                uploader,
+                actionUrl = editor.getActionUrl(editor.getOpt('fileActionName')),
+                fileMaxSize = editor.getOpt('fileMaxSize'),
+                acceptExtensions = (editor.getOpt('fileAllowFiles') || []).join('').replace(/\./g, ',').replace(/^[,]/, '');;
+
+            if (!WebUploader.Uploader.support()) {
+                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();
+                return;
+            } else if (!editor.getOpt('fileActionName')) {
+                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();
+                return;
+            }
+
+            uploader = _this.uploader = WebUploader.create({
+                pick: {
+                    id: '#filePickerReady',
+                    label: lang.uploadSelectFile
+                },
+                swf: '../../third-party/webuploader/Uploader.swf',
+                server: actionUrl,
+                fileVal: editor.getOpt('fileFieldName'),
+                duplicate: true,
+                fileSingleSizeLimit: fileMaxSize,
+                compress: false
+            });
+            uploader.addButton({
+                id: '#filePickerBlock'
+            });
+            uploader.addButton({
+                id: '#filePickerBtn',
+                label: lang.uploadAddFile
+            });
+
+            setState('pedding');
+
+            // 当有文件添加进来时执行,负责view的创建
+            function addFile(file) {
+                var $li = $('<li id="' + file.id + '">' +
+                        '<p class="title">' + file.name + '</p>' +
+                        '<p class="imgWrap"></p>' +
+                        '<p class="progress"><span></span></p>' +
+                        '</li>'),
+
+                    $btns = $('<div class="file-panel">' +
+                        '<span class="cancel">' + lang.uploadDelete + '</span>' +
+                        '<span class="rotateRight">' + lang.uploadTurnRight + '</span>' +
+                        '<span class="rotateLeft">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),
+                    $prgress = $li.find('p.progress span'),
+                    $wrap = $li.find('p.imgWrap'),
+                    $info = $('<p class="error"></p>').hide().appendTo($li),
+
+                    showError = function (code) {
+                        switch (code) {
+                            case 'exceed_size':
+                                text = lang.errorExceedSize;
+                                break;
+                            case 'interrupt':
+                                text = lang.errorInterrupt;
+                                break;
+                            case 'http':
+                                text = lang.errorHttp;
+                                break;
+                            case 'not_allow_type':
+                                text = lang.errorFileType;
+                                break;
+                            default:
+                                text = lang.errorUploadRetry;
+                                break;
+                        }
+                        $info.text(text).show();
+                    };
+
+                if (file.getStatus() === 'invalid') {
+                    showError(file.statusText);
+                } else {
+                    $wrap.text(lang.uploadPreview);
+                    if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {
+                        $wrap.empty().addClass('notimage').append('<i class="file-preview file-type-' + file.ext.toLowerCase() + '"></i>' +
+                        '<span class="file-title" title="' + file.name + '">' + file.name + '</span>');
+                    } else {
+                        if (browser.ie && browser.version <= 7) {
+                            $wrap.text(lang.uploadNoPreview);
+                        } else {
+                            uploader.makeThumb(file, function (error, src) {
+                                if (error || !src) {
+                                    $wrap.text(lang.uploadNoPreview);
+                                } else {
+                                    var $img = $('<img src="' + src + '">');
+                                    $wrap.empty().append($img);
+                                    $img.on('error', function () {
+                                        $wrap.text(lang.uploadNoPreview);
+                                    });
+                                }
+                            }, thumbnailWidth, thumbnailHeight);
+                        }
+                    }
+                    percentages[ file.id ] = [ file.size, 0 ];
+                    file.rotation = 0;
+
+                    /* 检查文件格式 */
+                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {
+                        showError('not_allow_type');
+                        uploader.removeFile(file);
+                    }
+                }
+
+                file.on('statuschange', function (cur, prev) {
+                    if (prev === 'progress') {
+                        $prgress.hide().width(0);
+                    } else if (prev === 'queued') {
+                        $li.off('mouseenter mouseleave');
+                        $btns.remove();
+                    }
+                    // 成功
+                    if (cur === 'error' || cur === 'invalid') {
+                        showError(file.statusText);
+                        percentages[ file.id ][ 1 ] = 1;
+                    } else if (cur === 'interrupt') {
+                        showError('interrupt');
+                    } else if (cur === 'queued') {
+                        percentages[ file.id ][ 1 ] = 0;
+                    } else if (cur === 'progress') {
+                        $info.hide();
+                        $prgress.css('display', 'block');
+                    } else if (cur === 'complete') {
+                    }
+
+                    $li.removeClass('state-' + prev).addClass('state-' + cur);
+                });
+
+                $li.on('mouseenter', function () {
+                    $btns.stop().animate({height: 30});
+                });
+                $li.on('mouseleave', function () {
+                    $btns.stop().animate({height: 0});
+                });
+
+                $btns.on('click', 'span', function () {
+                    var index = $(this).index(),
+                        deg;
+
+                    switch (index) {
+                        case 0:
+                            uploader.removeFile(file);
+                            return;
+                        case 1:
+                            file.rotation += 90;
+                            break;
+                        case 2:
+                            file.rotation -= 90;
+                            break;
+                    }
+
+                    if (supportTransition) {
+                        deg = 'rotate(' + file.rotation + 'deg)';
+                        $wrap.css({
+                            '-webkit-transform': deg,
+                            '-mos-transform': deg,
+                            '-o-transform': deg,
+                            'transform': deg
+                        });
+                    } else {
+                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');
+                    }
+
+                });
+
+                $li.insertBefore($filePickerBlock);
+            }
+
+            // 负责view的销毁
+            function removeFile(file) {
+                var $li = $('#' + file.id);
+                delete percentages[ file.id ];
+                updateTotalProgress();
+                $li.off().find('.file-panel').off().end().remove();
+            }
+
+            function updateTotalProgress() {
+                var loaded = 0,
+                    total = 0,
+                    spans = $progress.children(),
+                    percent;
+
+                $.each(percentages, function (k, v) {
+                    total += v[ 0 ];
+                    loaded += v[ 0 ] * v[ 1 ];
+                });
+
+                percent = total ? loaded / total : 0;
+
+                spans.eq(0).text(Math.round(percent * 100) + '%');
+                spans.eq(1).css('width', Math.round(percent * 100) + '%');
+                updateStatus();
+            }
+
+            function setState(val, files) {
+
+                if (val != state) {
+
+                    var stats = uploader.getStats();
+
+                    $upload.removeClass('state-' + state);
+                    $upload.addClass('state-' + val);
+
+                    switch (val) {
+
+                        /* 未选择文件 */
+                        case 'pedding':
+                            $queue.addClass('element-invisible');
+                            $statusBar.addClass('element-invisible');
+                            $placeHolder.removeClass('element-invisible');
+                            $progress.hide(); $info.hide();
+                            uploader.refresh();
+                            break;
+
+                        /* 可以开始上传 */
+                        case 'ready':
+                            $placeHolder.addClass('element-invisible');
+                            $queue.removeClass('element-invisible');
+                            $statusBar.removeClass('element-invisible');
+                            $progress.hide(); $info.show();
+                            $upload.text(lang.uploadStart);
+                            uploader.refresh();
+                            break;
+
+                        /* 上传中 */
+                        case 'uploading':
+                            $progress.show(); $info.hide();
+                            $upload.text(lang.uploadPause);
+                            break;
+
+                        /* 暂停上传 */
+                        case 'paused':
+                            $progress.show(); $info.hide();
+                            $upload.text(lang.uploadContinue);
+                            break;
+
+                        case 'confirm':
+                            $progress.show(); $info.hide();
+                            $upload.text(lang.uploadStart);
+
+                            stats = uploader.getStats();
+                            if (stats.successNum && !stats.uploadFailNum) {
+                                setState('finish');
+                                return;
+                            }
+                            break;
+
+                        case 'finish':
+                            $progress.hide(); $info.show();
+                            if (stats.uploadFailNum) {
+                                $upload.text(lang.uploadRetry);
+                            } else {
+                                $upload.text(lang.uploadStart);
+                            }
+                            break;
+                    }
+
+                    state = val;
+                    updateStatus();
+
+                }
+
+                if (!_this.getQueueCount()) {
+                    $upload.addClass('disabled')
+                } else {
+                    $upload.removeClass('disabled')
+                }
+
+            }
+
+            function updateStatus() {
+                var text = '', stats;
+
+                if (state === 'ready') {
+                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));
+                } else if (state === 'confirm') {
+                    stats = uploader.getStats();
+                    if (stats.uploadFailNum) {
+                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);
+                    }
+                } else {
+                    stats = uploader.getStats();
+                    text = lang.updateStatusFinish.replace('_', fileCount).
+                        replace('_KB', WebUploader.formatSize(fileSize)).
+                        replace('_', stats.successNum);
+
+                    if (stats.uploadFailNum) {
+                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);
+                    }
+                }
+
+                $info.html(text);
+            }
+
+            uploader.on('fileQueued', function (file) {
+                fileCount++;
+                fileSize += file.size;
+
+                if (fileCount === 1) {
+                    $placeHolder.addClass('element-invisible');
+                    $statusBar.show();
+                }
+
+                addFile(file);
+            });
+
+            uploader.on('fileDequeued', function (file) {
+                fileCount--;
+                fileSize -= file.size;
+
+                removeFile(file);
+                updateTotalProgress();
+            });
+
+            uploader.on('filesQueued', function (file) {
+                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {
+                    setState('ready');
+                }
+                updateTotalProgress();
+            });
+
+            uploader.on('all', function (type, files) {
+                switch (type) {
+                    case 'uploadFinished':
+                        setState('confirm', files);
+                        break;
+                    case 'startUpload':
+                        /* 添加额外的GET参数 */
+                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',
+                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);
+                        uploader.option('server', url);
+                        setState('uploading', files);
+                        break;
+                    case 'stopUpload':
+                        setState('paused', files);
+                        break;
+                }
+            });
+
+            uploader.on('uploadBeforeSend', function (file, data, header) {
+                //这里可以通过data对象添加POST参数
+                header['X_Requested_With'] = 'XMLHttpRequest';
+            });
+
+            uploader.on('uploadProgress', function (file, percentage) {
+                var $li = $('#' + file.id),
+                    $percent = $li.find('.progress span');
+
+                $percent.css('width', percentage * 100 + '%');
+                percentages[ file.id ][ 1 ] = percentage;
+                updateTotalProgress();
+            });
+
+            uploader.on('uploadSuccess', function (file, ret) {
+                var $file = $('#' + file.id);
+                try {
+                    var responseText = (ret._raw || ret),
+                        json = utils.str2json(responseText);
+                    if (json.state == 'SUCCESS') {
+                        _this.fileList.push(json);
+                        $file.append('<span class="success"></span>');
+                    } else {
+                        $file.find('.error').text(json.state).show();
+                    }
+                } catch (e) {
+                    $file.find('.error').text(lang.errorServerUpload).show();
+                }
+            });
+
+            uploader.on('uploadError', function (file, code) {
+            });
+            uploader.on('error', function (code, file) {
+                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {
+                    addFile(file);
+                }
+            });
+            uploader.on('uploadComplete', function (file, ret) {
+            });
+
+            $upload.on('click', function () {
+                if ($(this).hasClass('disabled')) {
+                    return false;
+                }
+
+                if (state === 'ready') {
+                    uploader.upload();
+                } else if (state === 'paused') {
+                    uploader.upload();
+                } else if (state === 'uploading') {
+                    uploader.stop();
+                }
+            });
+
+            $upload.addClass('state-' + state);
+            updateTotalProgress();
+        },
+        getQueueCount: function () {
+            var file, i, status, readyFile = 0, files = this.uploader.getFiles();
+            for (i = 0; file = files[i++]; ) {
+                status = file.getStatus();
+                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;
+            }
+            return readyFile;
+        },
+        getInsertList: function () {
+            var i, link, data, list = [],
+                prefix = editor.getOpt('fileUrlPrefix');
+            for (i = 0; i < this.fileList.length; i++) {
+                data = this.fileList[i];
+                link = data.url;
+                list.push({
+                    title: data.original || link.substr(link.lastIndexOf('/') + 1),
+                    url: prefix + link
+                });
+            }
+            return list;
+        }
+    };
+
+
+    /* 在线附件 */
+    function OnlineFile(target) {
+        this.container = utils.isString(target) ? document.getElementById(target) : target;
+        this.init();
+    }
+    OnlineFile.prototype = {
+        init: function () {
+            this.initContainer();
+            this.initEvents();
+            this.initData();
+        },
+        /* 初始化容器 */
+        initContainer: function () {
+            this.container.innerHTML = '';
+            this.list = document.createElement('ul');
+            this.clearFloat = document.createElement('li');
+
+            domUtils.addClass(this.list, 'list');
+            domUtils.addClass(this.clearFloat, 'clearFloat');
+
+            this.list.appendChild(this.clearFloat);
+            this.container.appendChild(this.list);
+        },
+        /* 初始化滚动事件,滚动到地步自动拉取数据 */
+        initEvents: function () {
+            var _this = this;
+
+            /* 滚动拉取图片 */
+            domUtils.on($G('fileList'), 'scroll', function(e){
+                var panel = this;
+                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {
+                    _this.getFileData();
+                }
+            });
+            /* 选中图片 */
+            domUtils.on(this.list, 'click', function (e) {
+                var target = e.target || e.srcElement,
+                    li = target.parentNode;
+
+                if (li.tagName.toLowerCase() == 'li') {
+                    if (domUtils.hasClass(li, 'selected')) {
+                        domUtils.removeClasses(li, 'selected');
+                    } else {
+                        domUtils.addClass(li, 'selected');
+                    }
+                }
+            });
+        },
+        /* 初始化第一次的数据 */
+        initData: function () {
+
+            /* 拉取数据需要使用的值 */
+            this.state = 0;
+            this.listSize = editor.getOpt('fileManagerListSize');
+            this.listIndex = 0;
+            this.listEnd = false;
+
+            /* 第一次拉取数据 */
+            this.getFileData();
+        },
+        /* 向后台拉取图片列表数据 */
+        getFileData: function () {
+            var _this = this;
+
+            if(!_this.listEnd && !this.isLoadingData) {
+                this.isLoadingData = true;
+                ajax.request(editor.getActionUrl(editor.getOpt('fileManagerActionName')), {
+                    timeout: 100000,
+                    data: utils.extend({
+                            start: this.listIndex,
+                            size: this.listSize
+                        }, editor.queryCommandValue('serverparam')),
+                    method: 'get',
+                    onsuccess: function (r) {
+                        try {
+                            var json = eval('(' + r.responseText + ')');
+                            if (json.state == 'SUCCESS') {
+                                _this.pushData(json.list);
+                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);
+                                if(_this.listIndex >= json.total) {
+                                    _this.listEnd = true;
+                                }
+                                _this.isLoadingData = false;
+                            }
+                        } catch (e) {
+                            if(r.responseText.indexOf('ue_separate_ue') != -1) {
+                                var list = r.responseText.split(r.responseText);
+                                _this.pushData(list);
+                                _this.listIndex = parseInt(list.length);
+                                _this.listEnd = true;
+                                _this.isLoadingData = false;
+                            }
+                        }
+                    },
+                    onerror: function () {
+                        _this.isLoadingData = false;
+                    }
+                });
+            }
+        },
+        /* 添加图片到列表界面上 */
+        pushData: function (list) {
+            var i, item, img, filetype, preview, icon, _this = this,
+                urlPrefix = editor.getOpt('fileManagerUrlPrefix');
+            for (i = 0; i < list.length; i++) {
+                if(list[i] && list[i].url) {
+                    item = document.createElement('li');
+                    icon = document.createElement('span');
+                    filetype = list[i].url.substr(list[i].url.lastIndexOf('.') + 1);
+
+                    if ( "png|jpg|jpeg|gif|bmp".indexOf(filetype) != -1 ) {
+                        preview = document.createElement('img');
+                        domUtils.on(preview, 'load', (function(image){
+                            return function(){
+                                _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);
+                            };
+                        })(preview));
+                        preview.width = 113;
+                        preview.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );
+                    } else {
+                        var ic = document.createElement('i'),
+                            textSpan = document.createElement('span');
+                        textSpan.innerHTML = list[i].url.substr(list[i].url.lastIndexOf('/') + 1);
+                        preview = document.createElement('div');
+                        preview.appendChild(ic);
+                        preview.appendChild(textSpan);
+                        domUtils.addClass(preview, 'file-wrapper');
+                        domUtils.addClass(textSpan, 'file-title');
+                        domUtils.addClass(ic, 'file-type-' + filetype);
+                        domUtils.addClass(ic, 'file-preview');
+                    }
+                    domUtils.addClass(icon, 'icon');
+                    item.setAttribute('data-url', urlPrefix + list[i].url);
+                    if (list[i].original) {
+                        item.setAttribute('data-title', list[i].original);
+                    }
+
+                    item.appendChild(preview);
+                    item.appendChild(icon);
+                    this.list.insertBefore(item, this.clearFloat);
+                }
+            }
+        },
+        /* 改变图片大小 */
+        scale: function (img, w, h, type) {
+            var ow = img.width,
+                oh = img.height;
+
+            if (type == 'justify') {
+                if (ow >= oh) {
+                    img.width = w;
+                    img.height = h * oh / ow;
+                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
+                } else {
+                    img.width = w * ow / oh;
+                    img.height = h;
+                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
+                }
+            } else {
+                if (ow >= oh) {
+                    img.width = w * ow / oh;
+                    img.height = h;
+                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
+                } else {
+                    img.width = w;
+                    img.height = h * oh / ow;
+                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
+                }
+            }
+        },
+        getInsertList: function () {
+            var i, lis = this.list.children, list = [];
+            for (i = 0; i < lis.length; i++) {
+                if (domUtils.hasClass(lis[i], 'selected')) {
+                    var url = lis[i].getAttribute('data-url');
+                    var title = lis[i].getAttribute('data-title') || url.substr(url.lastIndexOf('/') + 1);
+                    list.push({
+                        title: title,
+                        url: url
+                    });
+                }
+            }
+            return list;
+        }
+    };
+
+
+})();

BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_chm.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_default.png


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_doc.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_exe.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_jpg.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_mp3.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_mv.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_pdf.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_ppt.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_psd.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_rar.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_txt.gif


BIN
public/plugins/ueditor/dialogs/attachment/fileTypeImages/icon_xls.gif


BIN
public/plugins/ueditor/dialogs/attachment/images/alignicon.gif


BIN
public/plugins/ueditor/dialogs/attachment/images/alignicon.png


BIN
public/plugins/ueditor/dialogs/attachment/images/bg.png


BIN
public/plugins/ueditor/dialogs/attachment/images/file-icons.gif


BIN
public/plugins/ueditor/dialogs/attachment/images/file-icons.png


BIN
public/plugins/ueditor/dialogs/attachment/images/icons.gif


BIN
public/plugins/ueditor/dialogs/attachment/images/icons.png


BIN
public/plugins/ueditor/dialogs/attachment/images/image.png


BIN
public/plugins/ueditor/dialogs/attachment/images/progress.png


BIN
public/plugins/ueditor/dialogs/attachment/images/success.gif


BIN
public/plugins/ueditor/dialogs/attachment/images/success.png


+ 94 - 0
public/plugins/ueditor/dialogs/background/background.css

xqd
@@ -0,0 +1,94 @@
+.wrapper{ width: 424px;margin: 10px auto; zoom:1;position: relative}
+.tabbody{height:225px;}
+.tabbody .panel { position: absolute;width:100%; height:100%;background: #fff; display: none;}
+.tabbody .focus { display: block;}
+
+body{font-size: 12px;color: #888;overflow: hidden;}
+input,label{vertical-align:middle}
+.clear{clear: both;}
+.pl{padding-left: 18px;padding-left: 23px\9;}
+
+#imageList {width: 420px;height: 215px;margin-top: 10px;overflow: hidden;overflow-y: auto;}
+#imageList div {float: left;width: 100px;height: 95px;margin: 5px 10px;}
+#imageList img {cursor: pointer;border: 2px solid white;}
+
+.bgarea{margin: 10px;padding: 5px;height: 84%;border: 1px solid #A8A297;}
+.content div{margin: 10px 0 10px 5px;}
+.content .iptradio{margin: 0px 5px 5px 0px;}
+.txt{width:280px;}
+
+.wrapcolor{height: 19px;}
+div.color{float: left;margin: 0;}
+#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;margin: 0;float: left;}
+div.alignment,#custom{margin-left: 23px;margin-left: 28px\9;}
+#custom input{height: 15px;min-height: 15px;width:20px;}
+#repeatType{width:100px;}
+
+
+/* 图片管理样式 */
+#imgManager {
+    width: 100%;
+    height: 225px;
+}
+#imgManager #imageList{
+    width: 100%;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+#imgManager ul {
+    display: block;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+#imgManager li {
+    float: left;
+    display: block;
+    list-style: none;
+    padding: 0;
+    width: 113px;
+    height: 113px;
+    margin: 9px 0 0 19px;
+    background-color: #eee;
+    overflow: hidden;
+    cursor: pointer;
+    position: relative;
+}
+#imgManager li.clearFloat {
+    float: none;
+    clear: both;
+    display: block;
+    width:0;
+    height:0;
+    margin: 0;
+    padding: 0;
+}
+#imgManager li img {
+    cursor: pointer;
+}
+#imgManager li .icon {
+    cursor: pointer;
+    width: 113px;
+    height: 113px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 2;
+    border: 0;
+    background-repeat: no-repeat;
+}
+#imgManager li .icon:hover {
+    width: 107px;
+    height: 107px;
+    border: 3px solid #1094fa;
+}
+#imgManager li.selected .icon {
+    background-image: url(images/success.png);
+    background-position: 75px 75px;
+}
+#imgManager li.selected .icon:hover {
+    width: 107px;
+    height: 107px;
+    border: 3px solid #1094fa;
+    background-position: 72px 72px;
+}

+ 56 - 0
public/plugins/ueditor/dialogs/background/background.html

xqd
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+    <script type="text/javascript" src="../internal.js"></script>
+    <link rel="stylesheet" type="text/css" href="background.css">
+</head>
+<body>
+    <div id="bg_container" class="wrapper">
+        <div id="tabHeads" class="tabhead">
+            <span class="focus" data-content-id="normal"><var id="lang_background_normal"></var></span>
+            <span class="" data-content-id="imgManager"><var id="lang_background_local"></var></span>
+        </div>
+        <div id="tabBodys" class="tabbody">
+            <div id="normal" class="panel focus">
+                <fieldset class="bgarea">
+                    <legend><var id="lang_background_set"></var></legend>
+                    <div class="content">
+                        <div>
+                            <label><input id="nocolorRadio" class="iptradio" type="radio" name="t" value="none" checked="checked"><var id="lang_background_none"></var></label>
+                            <label><input id="coloredRadio" class="iptradio" type="radio" name="t" value="color"><var id="lang_background_colored"></var></label>
+                        </div>
+                        <div class="wrapcolor pl">
+                            <div class="color">
+                                <var id="lang_background_color"></var>:
+                            </div>
+                            <div id="colorPicker"></div>
+                            <div class="clear"></div>
+                        </div>
+                        <div class="wrapcolor pl">
+                            <label><var id="lang_background_netimg"></var>:</label><input class="txt" type="text" id="url">
+                        </div>
+                        <div id="alignment" class="alignment">
+                            <var id="lang_background_align"></var>:<select id="repeatType">
+                                <option value="center"></option>
+                                <option value="repeat-x"></option>
+                                <option value="repeat-y"></option>
+                                <option value="repeat"></option>
+                                <option value="self"></option>
+                            </select>
+                        </div>
+                        <div id="custom" >
+                            <var id="lang_background_position"></var>:x:<input type="text" size="1" id="x" maxlength="4" value="0">px&nbsp;&nbsp;y:<input type="text" size="1" id="y" maxlength="4" value="0">px
+                        </div>
+                    </div>
+                </fieldset>
+
+            </div>
+            <div id="imgManager" class="panel">
+                <div id="imageList" style=""></div>
+            </div>
+        </div>
+    </div>
+    <script type="text/javascript" src="background.js"></script>
+</body>
+</html>

+ 376 - 0
public/plugins/ueditor/dialogs/background/background.js

xqd
@@ -0,0 +1,376 @@
+(function () {
+
+    var onlineImage,
+        backupStyle = editor.queryCommandValue('background');
+
+    window.onload = function () {
+        initTabs();
+        initColorSelector();
+    };
+
+    /* 初始化tab标签 */
+    function initTabs(){
+        var tabs = $G('tabHeads').children;
+        for (var i = 0; i < tabs.length; i++) {
+            domUtils.on(tabs[i], "click", function (e) {
+                var target = e.target || e.srcElement;
+                for (var j = 0; j < tabs.length; j++) {
+                    if(tabs[j] == target){
+                        tabs[j].className = "focus";
+                        var contentId = tabs[j].getAttribute('data-content-id');
+                        $G(contentId).style.display = "block";
+                        if(contentId == 'imgManager') {
+                            initImagePanel();
+                        }
+                    }else {
+                        tabs[j].className = "";
+                        $G(tabs[j].getAttribute('data-content-id')).style.display = "none";
+                    }
+                }
+            });
+        }
+    }
+
+    /* 初始化颜色设置 */
+    function initColorSelector () {
+        var obj = editor.queryCommandValue('background');
+        if (obj) {
+            var color = obj['background-color'],
+                repeat = obj['background-repeat'] || 'repeat',
+                image = obj['background-image'] || '',
+                position = obj['background-position'] || 'center center',
+                pos = position.split(' '),
+                x = parseInt(pos[0]) || 0,
+                y = parseInt(pos[1]) || 0;
+
+            if(repeat == 'no-repeat' && (x || y)) repeat = 'self';
+
+            image = image.match(/url[\s]*\(([^\)]*)\)/);
+            image = image ? image[1]:'';
+            updateFormState('colored', color, image, repeat, x, y);
+        } else {
+            updateFormState();
+        }
+
+        var updateHandler = function () {
+            updateFormState();
+            updateBackground();
+        }
+        domUtils.on($G('nocolorRadio'), 'click', updateBackground);
+        domUtils.on($G('coloredRadio'), 'click', updateHandler);
+        domUtils.on($G('url'), 'keyup', function(){
+            if($G('url').value && $G('alignment').style.display == "none") {
+                utils.each($G('repeatType').children, function(item){
+                    item.selected = ('repeat' == item.getAttribute('value') ? 'selected':false);
+                });
+            }
+            updateHandler();
+        });
+        domUtils.on($G('repeatType'), 'change', updateHandler);
+        domUtils.on($G('x'), 'keyup', updateBackground);
+        domUtils.on($G('y'), 'keyup', updateBackground);
+
+        initColorPicker();
+    }
+
+    /* 初始化颜色选择器 */
+    function initColorPicker() {
+        var me = editor,
+            cp = $G("colorPicker");
+
+        /* 生成颜色选择器ui对象 */
+        var popup = new UE.ui.Popup({
+            content: new UE.ui.ColorPicker({
+                noColorText: me.getLang("clearColor"),
+                editor: me,
+                onpickcolor: function (t, color) {
+                    updateFormState('colored', color);
+                    updateBackground();
+                    UE.ui.Popup.postHide();
+                },
+                onpicknocolor: function (t, color) {
+                    updateFormState('colored', 'transparent');
+                    updateBackground();
+                    UE.ui.Popup.postHide();
+                }
+            }),
+            editor: me,
+            onhide: function () {
+            }
+        });
+
+        /* 设置颜色选择器 */
+        domUtils.on(cp, "click", function () {
+            popup.showAnchor(this);
+        });
+        domUtils.on(document, 'mousedown', function (evt) {
+            var el = evt.target || evt.srcElement;
+            UE.ui.Popup.postHide(el);
+        });
+        domUtils.on(window, 'scroll', function () {
+            UE.ui.Popup.postHide();
+        });
+    }
+
+    /* 初始化在线图片列表 */
+    function initImagePanel() {
+        onlineImage = onlineImage || new OnlineImage('imageList');
+    }
+
+    /* 更新背景色设置面板 */
+    function updateFormState (radio, color, url, align, x, y) {
+        var nocolorRadio = $G('nocolorRadio'),
+            coloredRadio = $G('coloredRadio');
+
+        if(radio) {
+            nocolorRadio.checked = (radio == 'colored' ? false:'checked');
+            coloredRadio.checked = (radio == 'colored' ? 'checked':false);
+        }
+        if(color) {
+            domUtils.setStyle($G("colorPicker"), "background-color", color);
+        }
+
+        if(url && /^\//.test(url)) {
+            var a = document.createElement('a');
+            a.href = url;
+            browser.ie && (a.href = a.href);
+            url = browser.ie ? a.href:(a.protocol + '//' + a.host + a.pathname + a.search + a.hash);
+        }
+
+        if(url || url === '') {
+            $G('url').value = url;
+        }
+        if(align) {
+            utils.each($G('repeatType').children, function(item){
+                item.selected = (align == item.getAttribute('value') ? 'selected':false);
+            });
+        }
+        if(x || y) {
+            $G('x').value = parseInt(x) || 0;
+            $G('y').value = parseInt(y) || 0;
+        }
+
+        $G('alignment').style.display = coloredRadio.checked && $G('url').value ? '':'none';
+        $G('custom').style.display = coloredRadio.checked && $G('url').value && $G('repeatType').value == 'self' ? '':'none';
+    }
+
+    /* 更新背景颜色 */
+    function updateBackground () {
+        if ($G('coloredRadio').checked) {
+            var color = domUtils.getStyle($G("colorPicker"), "background-color"),
+                bgimg = $G("url").value,
+                align = $G("repeatType").value,
+                backgroundObj = {
+                    "background-repeat": "no-repeat",
+                    "background-position": "center center"
+                };
+
+            if (color) backgroundObj["background-color"] = color;
+            if (bgimg) backgroundObj["background-image"] = 'url(' + bgimg + ')';
+            if (align == 'self') {
+                backgroundObj["background-position"] = $G("x").value + "px " + $G("y").value + "px";
+            } else if (align == 'repeat-x' || align == 'repeat-y' || align == 'repeat') {
+                backgroundObj["background-repeat"] = align;
+            }
+
+            editor.execCommand('background', backgroundObj);
+        } else {
+            editor.execCommand('background', null);
+        }
+    }
+
+
+    /* 在线图片 */
+    function OnlineImage(target) {
+        this.container = utils.isString(target) ? document.getElementById(target) : target;
+        this.init();
+    }
+    OnlineImage.prototype = {
+        init: function () {
+            this.reset();
+            this.initEvents();
+        },
+        /* 初始化容器 */
+        initContainer: function () {
+            this.container.innerHTML = '';
+            this.list = document.createElement('ul');
+            this.clearFloat = document.createElement('li');
+
+            domUtils.addClass(this.list, 'list');
+            domUtils.addClass(this.clearFloat, 'clearFloat');
+
+            this.list.id = 'imageListUl';
+            this.list.appendChild(this.clearFloat);
+            this.container.appendChild(this.list);
+        },
+        /* 初始化滚动事件,滚动到地步自动拉取数据 */
+        initEvents: function () {
+            var _this = this;
+
+            /* 滚动拉取图片 */
+            domUtils.on($G('imageList'), 'scroll', function(e){
+                var panel = this;
+                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {
+                    _this.getImageData();
+                }
+            });
+            /* 选中图片 */
+            domUtils.on(this.container, 'click', function (e) {
+                var target = e.target || e.srcElement,
+                    li = target.parentNode,
+                    nodes = $G('imageListUl').childNodes;
+
+                if (li.tagName.toLowerCase() == 'li') {
+                    updateFormState('nocolor', null, '');
+                    for (var i = 0, node; node = nodes[i++];) {
+                        if (node == li && !domUtils.hasClass(node, 'selected')) {
+                            domUtils.addClass(node, 'selected');
+                            updateFormState('colored', null, li.firstChild.getAttribute("_src"), 'repeat');
+                        } else {
+                            domUtils.removeClasses(node, 'selected');
+                        }
+                    }
+                    updateBackground();
+                }
+            });
+        },
+        /* 初始化第一次的数据 */
+        initData: function () {
+
+            /* 拉取数据需要使用的值 */
+            this.state = 0;
+            this.listSize = editor.getOpt('imageManagerListSize');
+            this.listIndex = 0;
+            this.listEnd = false;
+
+            /* 第一次拉取数据 */
+            this.getImageData();
+        },
+        /* 重置界面 */
+        reset: function() {
+            this.initContainer();
+            this.initData();
+        },
+        /* 向后台拉取图片列表数据 */
+        getImageData: function () {
+            var _this = this;
+
+            if(!_this.listEnd && !this.isLoadingData) {
+                this.isLoadingData = true;
+                var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),
+                    isJsonp = utils.isCrossDomainUrl(url);
+                ajax.request(url, {
+                    'timeout': 100000,
+                    'dataType': isJsonp ? 'jsonp':'',
+                    'data': utils.extend({
+                            start: this.listIndex,
+                            size: this.listSize
+                        }, editor.queryCommandValue('serverparam')),
+                    'method': 'get',
+                    'onsuccess': function (r) {
+                        try {
+                            var json = isJsonp ? r:eval('(' + r.responseText + ')');
+                            if (json.state == 'SUCCESS') {
+                                _this.pushData(json.list);
+                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);
+                                if(_this.listIndex >= json.total) {
+                                    _this.listEnd = true;
+                                }
+                                _this.isLoadingData = false;
+                            }
+                        } catch (e) {
+                            if(r.responseText.indexOf('ue_separate_ue') != -1) {
+                                var list = r.responseText.split(r.responseText);
+                                _this.pushData(list);
+                                _this.listIndex = parseInt(list.length);
+                                _this.listEnd = true;
+                                _this.isLoadingData = false;
+                            }
+                        }
+                    },
+                    'onerror': function () {
+                        _this.isLoadingData = false;
+                    }
+                });
+            }
+        },
+        /* 添加图片到列表界面上 */
+        pushData: function (list) {
+            var i, item, img, icon, _this = this,
+                urlPrefix = editor.getOpt('imageManagerUrlPrefix');
+            for (i = 0; i < list.length; i++) {
+                if(list[i] && list[i].url) {
+                    item = document.createElement('li');
+                    img = document.createElement('img');
+                    icon = document.createElement('span');
+
+                    domUtils.on(img, 'load', (function(image){
+                        return function(){
+                            _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);
+                        }
+                    })(img));
+                    img.width = 113;
+                    img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );
+                    img.setAttribute('_src', urlPrefix + list[i].url);
+                    domUtils.addClass(icon, 'icon');
+
+                    item.appendChild(img);
+                    item.appendChild(icon);
+                    this.list.insertBefore(item, this.clearFloat);
+                }
+            }
+        },
+        /* 改变图片大小 */
+        scale: function (img, w, h, type) {
+            var ow = img.width,
+                oh = img.height;
+
+            if (type == 'justify') {
+                if (ow >= oh) {
+                    img.width = w;
+                    img.height = h * oh / ow;
+                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
+                } else {
+                    img.width = w * ow / oh;
+                    img.height = h;
+                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
+                }
+            } else {
+                if (ow >= oh) {
+                    img.width = w * ow / oh;
+                    img.height = h;
+                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
+                } else {
+                    img.width = w;
+                    img.height = h * oh / ow;
+                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
+                }
+            }
+        },
+        getInsertList: function () {
+            var i, lis = this.list.children, list = [], align = getAlign();
+            for (i = 0; i < lis.length; i++) {
+                if (domUtils.hasClass(lis[i], 'selected')) {
+                    var img = lis[i].firstChild,
+                        src = img.getAttribute('_src');
+                    list.push({
+                        src: src,
+                        _src: src,
+                        floatStyle: align
+                    });
+                }
+
+            }
+            return list;
+        }
+    };
+
+    dialog.onok = function () {
+        updateBackground();
+        editor.fireEvent('saveScene');
+    };
+    dialog.oncancel = function () {
+        editor.execCommand('background', backupStyle);
+    };
+
+})();

BIN
public/plugins/ueditor/dialogs/background/images/bg.png


BIN
public/plugins/ueditor/dialogs/background/images/success.png


+ 65 - 0
public/plugins/ueditor/dialogs/charts/chart.config.js

xqd
@@ -0,0 +1,65 @@
+/*
+ * 图表配置文件
+ * */
+
+
+//不同类型的配置
+var typeConfig = [
+    {
+        chart: {
+            type: 'line'
+        },
+        plotOptions: {
+            line: {
+                dataLabels: {
+                    enabled: false
+                },
+                enableMouseTracking: true
+            }
+        }
+    }, {
+        chart: {
+            type: 'line'
+        },
+        plotOptions: {
+            line: {
+                dataLabels: {
+                    enabled: true
+                },
+                enableMouseTracking: false
+            }
+        }
+    }, {
+        chart: {
+            type: 'area'
+        }
+    }, {
+        chart: {
+            type: 'bar'
+        }
+    }, {
+        chart: {
+            type: 'column'
+        }
+    }, {
+        chart: {
+            plotBackgroundColor: null,
+            plotBorderWidth: null,
+            plotShadow: false
+        },
+        plotOptions: {
+            pie: {
+                allowPointSelect: true,
+                cursor: 'pointer',
+                dataLabels: {
+                    enabled: true,
+                    color: '#000000',
+                    connectorColor: '#000000',
+                    formatter: function() {
+                        return '<b>'+ this.point.name +'</b>: '+ ( Math.round( this.point.percentage*100 ) / 100 ) +' %';
+                    }
+                }
+            }
+        }
+    }
+];

+ 165 - 0
public/plugins/ueditor/dialogs/charts/charts.css

xqd
@@ -0,0 +1,165 @@
+html, body {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+    overflow-x: hidden;
+}
+
+.main {
+    width: 100%;
+    overflow: hidden;
+}
+
+.table-view {
+    height: 100%;
+    float: left;
+    margin: 20px;
+    width: 40%;
+}
+
+.table-view .table-container {
+    width: 100%;
+    margin-bottom: 50px;
+    overflow: scroll;
+}
+
+.table-view th {
+    padding: 5px 10px;
+    background-color: #F7F7F7;
+}
+
+.table-view td {
+    width: 50px;
+    text-align: center;
+    padding:0;
+}
+
+.table-container input {
+    width: 40px;
+    padding: 5px;
+    border: none;
+    outline: none;
+}
+
+.table-view caption {
+    font-size: 18px;
+    text-align: left;
+}
+
+.charts-view {
+    /*margin-left: 49%!important;*/
+    width: 50%;
+    margin-left: 49%;
+    height: 400px;
+}
+
+.charts-container {
+    border-left: 1px solid #c3c3c3;
+}
+
+.charts-format fieldset {
+    padding-left: 20px;
+    margin-bottom: 50px;
+}
+
+.charts-format legend {
+    padding-left: 10px;
+    padding-right: 10px;
+}
+
+.format-item-container {
+    padding: 20px;
+}
+
+.format-item-container label {
+    display: block;
+    margin: 10px 0;
+}
+
+.charts-format .data-item {
+    border: 1px solid black;
+    outline: none;
+    padding: 2px 3px;
+}
+
+/* 图表类型 */
+
+.charts-type {
+    margin-top: 50px;
+    height: 300px;
+}
+
+.scroll-view {
+    border: 1px solid #c3c3c3;
+    border-left: none;
+    border-right: none;
+    overflow: hidden;
+}
+
+.scroll-container {
+    margin: 20px;
+    width: 100%;
+    overflow: hidden;
+}
+
+.scroll-bed {
+    width: 10000px;
+    _margin-top: 20px;
+    -webkit-transition: margin-left .5s ease;
+    -moz-transition: margin-left .5s ease;
+    transition: margin-left .5s ease;
+}
+
+.view-box {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    margin-right: 20px;
+    border: 2px solid white;
+    line-height: 0;
+    overflow: hidden;
+    cursor: pointer;
+}
+
+.view-box img {
+    border: 1px solid #cecece;
+}
+
+.view-box.selected {
+    border-color: #7274A7;
+}
+
+.button-container {
+    margin-bottom: 20px;
+    text-align: center;
+}
+
+.button-container a {
+    display: inline-block;
+    width: 100px;
+    height: 25px;
+    line-height: 25px;
+    border: 1px solid #c2ccd1;
+    margin-right: 30px;
+    text-decoration: none;
+    color: black;
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+}
+
+.button-container a:HOVER {
+    background: #fcfcfc;
+}
+
+.button-container a:ACTIVE {
+    border-top-color: #c2ccd1;
+    box-shadow:inset 0 5px 4px -4px rgba(49, 49, 64, 0.1);
+}
+
+.edui-charts-not-data {
+    height: 100px;
+    line-height: 100px;
+    text-align: center;
+}

+ 89 - 0
public/plugins/ueditor/dialogs/charts/charts.html

xqd
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>chart</title>
+        <meta chartset="utf-8">
+        <link rel="stylesheet" type="text/css" href="charts.css">
+        <script type="text/javascript" src="../internal.js"></script>
+    </head>
+    <body>
+        <div class="main">
+            <div class="table-view">
+                <h3><var id="lang_data_source"></var></h3>
+                <div id="tableContainer" class="table-container"></div>
+                <h3><var id="lang_chart_format"></var></h3>
+                <form name="data-form">
+                    <div class="charts-format">
+                        <fieldset>
+                            <legend><var id="lang_data_align"></var></legend>
+                            <div class="format-item-container">
+                                <label>
+                                    <input type="radio" class="format-ctrl not-pie-item" name="charts-format" value="1" checked="checked">
+                                    <var id="lang_chart_align_same"></var>
+                                </label>
+                                <label>
+                                    <input type="radio" class="format-ctrl not-pie-item" name="charts-format" value="-1">
+                                    <var id="lang_chart_align_reverse"></var>
+                                </label>
+                                <br>
+                            </div>
+                        </fieldset>
+                        <fieldset>
+                            <legend><var id="lang_chart_title"></var></legend>
+                            <div class="format-item-container">
+                                <label>
+                                    <var id="lang_chart_main_title"></var><input type="text" name="title" class="data-item">
+                                </label>
+                                <label>
+                                    <var id="lang_chart_sub_title"></var><input type="text" name="sub-title" class="data-item not-pie-item">
+                                </label>
+                                <label>
+                                    <var id="lang_chart_x_title"></var><input type="text" name="x-title" class="data-item not-pie-item">
+                                </label>
+                                <label>
+                                    <var id="lang_chart_y_title"></var><input type="text" name="y-title" class="data-item not-pie-item">
+                                </label>
+                            </div>
+                        </fieldset>
+                        <fieldset>
+                            <legend><var id="lang_chart_tip"></var></legend>
+                            <div class="format-item-container">
+                                <label>
+                                    <var id="lang_cahrt_tip_prefix"></var>
+                                    <input type="text" id="tipInput" name="tip" class="data-item" disabled="disabled">
+                                </label>
+                                <p><var id="lang_cahrt_tip_description"></var></p>
+                            </div>
+                        </fieldset>
+                        <fieldset>
+                            <legend><var id="lang_chart_data_unit"></var></legend>
+                            <div class="format-item-container">
+                                <label><var id="lang_chart_data_unit_title"></var><input type="text" name="unit" class="data-item"></label>
+                                <p><var id="lang_chart_data_unit_description"></var></p>
+                            </div>
+                        </fieldset>
+                    </div>
+                </form>
+            </div>
+            <div class="charts-view">
+                <div id="chartsContainer" class="charts-container"></div>
+                <div id="chartsType" class="charts-type">
+                    <h3><var id="lang_chart_type"></var></h3>
+                    <div class="scroll-view">
+                        <div class="scroll-container">
+                            <div id="scrollBed" class="scroll-bed"></div>
+                        </div>
+                        <div id="buttonContainer" class="button-container">
+                            <a href="#" data-title="prev"><var id="lang_prev_btn"></var></a>
+                            <a href="#" data-title="next"><var id="lang_next_btn"></var></a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <script src="../../third-party/jquery-1.10.2.min.js"></script>
+        <script src="../../third-party/highcharts/highcharts.js"></script>
+        <script src="chart.config.js"></script>
+        <script src="charts.js"></script>
+    </body>
+</html>

+ 519 - 0
public/plugins/ueditor/dialogs/charts/charts.js

xqd
@@ -0,0 +1,519 @@
+/*
+ * 图片转换对话框脚本
+ **/
+
+var tableData = [],
+    //编辑器页面table
+    editorTable = null,
+    chartsConfig = window.typeConfig,
+    resizeTimer = null,
+    //初始默认图表类型
+    currentChartType = 0;
+
+window.onload = function () {
+
+    editorTable = domUtils.findParentByTagName( editor.selection.getRange().startContainer, 'table', true);
+
+    //未找到表格, 显示错误页面
+    if ( !editorTable ) {
+        document.body.innerHTML = "<div class='edui-charts-not-data'>未找到数据</div>";
+        return;
+    }
+
+    //初始化图表类型选择
+    initChartsTypeView();
+    renderTable( editorTable );
+    initEvent();
+    initUserConfig( editorTable.getAttribute( "data-chart" ) );
+    $( "#scrollBed .view-box:eq("+ currentChartType +")" ).trigger( "click" );
+    updateViewType( currentChartType );
+
+    dialog.addListener( "resize", function () {
+
+        if ( resizeTimer != null ) {
+            window.clearTimeout( resizeTimer );
+        }
+
+        resizeTimer = window.setTimeout( function () {
+
+            resizeTimer = null;
+
+            renderCharts();
+
+        }, 500 );
+
+    } );
+
+};
+
+function initChartsTypeView () {
+
+    var contents = [];
+
+    for ( var i = 0, len = chartsConfig.length; i<len; i++ ) {
+
+        contents.push( '<div class="view-box" data-chart-type="'+ i +'"><img width="300" src="images/charts'+ i +'.png"></div>' );
+
+    }
+
+    $( "#scrollBed" ).html( contents.join( "" ) );
+
+}
+
+//渲染table, 以便用户修改数据
+function renderTable ( table ) {
+
+    var tableHtml = [];
+
+    //构造数据
+    for ( var i = 0, row; row = table.rows[ i ]; i++ ) {
+
+        tableData[ i ] = [];
+        tableHtml[ i ] = [];
+
+        for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {
+
+            var value = getCellValue( cell );
+
+            if ( i > 0 && j > 0 ) {
+                value = +value;
+            }
+
+            if ( i === 0 || j === 0 ) {
+                tableHtml[ i ].push( '<th>'+ value +'</th>' );
+            } else {
+                tableHtml[ i ].push( '<td><input type="text" class="data-item" value="'+ value +'"></td>' );
+            }
+
+            tableData[ i ][ j ] = value;
+
+        }
+
+        tableHtml[ i ] = tableHtml[ i ].join( "" );
+
+    }
+
+    //draw 表格
+    $( "#tableContainer" ).html( '<table id="showTable" border="1"><tbody><tr>'+ tableHtml.join( "</tr><tr>" ) +'</tr></tbody></table>' );
+
+}
+
+/*
+ * 根据表格已有的图表属性初始化当前图表属性
+ */
+function initUserConfig ( config ) {
+
+    var parsedConfig = {};
+
+    if ( !config ) {
+        return;
+    }
+
+    config = config.split( ";" );
+
+    $.each( config, function ( index, item ) {
+
+        item = item.split( ":" );
+        parsedConfig[ item[ 0 ] ] = item[ 1 ];
+
+    } );
+
+    setUserConfig( parsedConfig );
+
+}
+
+function initEvent () {
+
+    var cacheValue = null,
+        //图表类型数
+        typeViewCount = chartsConfig.length- 1,
+        $chartsTypeViewBox = $( '#scrollBed .view-box' );
+
+    $( ".charts-format" ).delegate( ".format-ctrl", "change", function () {
+
+        renderCharts();
+
+    } )
+
+    $( ".table-view" ).delegate( ".data-item", "focus", function () {
+
+        cacheValue = this.value;
+
+    } ).delegate( ".data-item", "blur", function () {
+
+        if ( this.value !== cacheValue ) {
+            renderCharts();
+        }
+
+        cacheValue = null;
+
+    } );
+
+    $( "#buttonContainer" ).delegate( "a", "click", function (e) {
+
+        e.preventDefault();
+
+        if ( this.getAttribute( "data-title" ) === 'prev' ) {
+
+            if ( currentChartType > 0 ) {
+                currentChartType--;
+                updateViewType( currentChartType );
+            }
+
+        } else {
+
+            if ( currentChartType < typeViewCount ) {
+                currentChartType++;
+                updateViewType( currentChartType );
+            }
+
+        }
+
+    } );
+
+    //图表类型变化
+    $( '#scrollBed' ).delegate( ".view-box", "click", function (e) {
+
+        var index = $( this ).attr( "data-chart-type" );
+        $chartsTypeViewBox.removeClass( "selected" );
+        $( $chartsTypeViewBox[ index ] ).addClass( "selected" );
+
+        currentChartType = index | 0;
+
+        //饼图, 禁用部分配置
+        if ( currentChartType === chartsConfig.length - 1 ) {
+
+            disableNotPieConfig();
+
+        //启用完整配置
+        } else {
+
+            enableNotPieConfig();
+
+        }
+
+        renderCharts();
+
+    } );
+
+}
+
+function renderCharts () {
+
+    var data = collectData();
+
+    $('#chartsContainer').highcharts( $.extend( {}, chartsConfig[ currentChartType ], {
+
+        credits: {
+            enabled: false
+        },
+        exporting: {
+            enabled: false
+        },
+        title: {
+            text: data.title,
+            x: -20 //center
+        },
+        subtitle: {
+            text: data.subTitle,
+            x: -20
+        },
+        xAxis: {
+            title: {
+                text: data.xTitle
+            },
+            categories: data.categories
+        },
+        yAxis: {
+            title: {
+                text: data.yTitle
+            },
+            plotLines: [{
+                value: 0,
+                width: 1,
+                color: '#808080'
+            }]
+        },
+        tooltip: {
+            enabled: true,
+            valueSuffix: data.suffix
+        },
+        legend: {
+            layout: 'vertical',
+            align: 'right',
+            verticalAlign: 'middle',
+            borderWidth: 1
+        },
+        series: data.series
+
+    } ));
+
+}
+
+function updateViewType ( index ) {
+
+    $( "#scrollBed" ).css( 'marginLeft', -index*324+'px' );
+
+}
+
+function collectData () {
+
+    var form = document.forms[ 'data-form' ],
+        data = null;
+
+    if ( currentChartType !== chartsConfig.length - 1 ) {
+
+        data = getSeriesAndCategories();
+        $.extend( data, getUserConfig() );
+
+    //饼图数据格式
+    } else {
+        data = getSeriesForPieChart();
+        data.title = form[ 'title' ].value;
+        data.suffix = form[ 'unit' ].value;
+    }
+
+    return data;
+
+}
+
+/**
+ * 获取用户配置信息
+ */
+function getUserConfig () {
+
+    var form = document.forms[ 'data-form' ],
+        info = {
+            title: form[ 'title' ].value,
+            subTitle: form[ 'sub-title' ].value,
+            xTitle: form[ 'x-title' ].value,
+            yTitle: form[ 'y-title' ].value,
+            suffix: form[ 'unit' ].value,
+            //数据对齐方式
+            tableDataFormat: getTableDataFormat (),
+            //饼图提示文字
+            tip: $( "#tipInput" ).val()
+        };
+
+    return info;
+
+}
+
+function setUserConfig ( config ) {
+
+    var form = document.forms[ 'data-form' ];
+
+    config.title && ( form[ 'title' ].value = config.title );
+    config.subTitle && ( form[ 'sub-title' ].value = config.subTitle );
+    config.xTitle && ( form[ 'x-title' ].value = config.xTitle );
+    config.yTitle && ( form[ 'y-title' ].value = config.yTitle );
+    config.suffix && ( form[ 'unit' ].value = config.suffix );
+    config.dataFormat == "-1" && ( form[ 'charts-format' ][ 1 ].checked = true );
+    config.tip && ( form[ 'tip' ].value = config.tip );
+    currentChartType = config.chartType || 0;
+
+}
+
+function getSeriesAndCategories () {
+
+    var form = document.forms[ 'data-form' ],
+        series = [],
+        categories = [],
+        tmp = [],
+        tableData = getTableData();
+
+    //反转数据
+    if ( getTableDataFormat() === "-1" ) {
+
+        for ( var i = 0, len = tableData.length; i < len; i++ ) {
+
+            for ( var j = 0, jlen = tableData[ i ].length; j < jlen; j++ ) {
+
+                if ( !tmp[ j ] ) {
+                    tmp[ j ] = [];
+                }
+
+                tmp[ j ][ i ] = tableData[ i ][ j ];
+
+            }
+
+        }
+
+        tableData = tmp;
+
+    }
+
+    categories = tableData[0].slice( 1 );
+
+    for ( var i = 1, data; data = tableData[ i ]; i++ ) {
+
+        series.push( {
+            name: data[ 0 ],
+            data: data.slice( 1 )
+        } );
+
+    }
+
+    return {
+        series: series,
+        categories: categories
+    };
+
+}
+
+/*
+ * 获取数据源数据对齐方式
+ */
+function getTableDataFormat () {
+
+    var form = document.forms[ 'data-form' ],
+        items = form['charts-format'];
+
+    return items[ 0 ].checked ? items[ 0 ].value : items[ 1 ].value;
+
+}
+
+/*
+ * 禁用非饼图类型的配置项
+ */
+function disableNotPieConfig() {
+
+    updateConfigItem( 'disable' );
+
+}
+
+/*
+ * 启用非饼图类型的配置项
+ */
+function enableNotPieConfig() {
+
+    updateConfigItem( 'enable' );
+
+}
+
+function updateConfigItem ( value ) {
+
+    var table = $( "#showTable" )[ 0 ],
+        isDisable = value === 'disable' ? true : false;
+
+    //table中的input处理
+    for ( var i = 2 , row; row = table.rows[ i ]; i++ ) {
+
+        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {
+
+            $( "input", cell ).attr( "disabled", isDisable );
+
+        }
+
+    }
+
+    //其他项处理
+    $( "input.not-pie-item" ).attr( "disabled", isDisable );
+    $( "#tipInput" ).attr( "disabled", !isDisable )
+
+}
+
+/*
+ * 获取饼图数据
+ * 饼图的数据只取第一行的
+ **/
+function getSeriesForPieChart () {
+
+    var series = {
+            type: 'pie',
+            name: $("#tipInput").val(),
+            data: []
+        },
+        tableData = getTableData();
+
+
+    for ( var j = 1, jlen = tableData[ 0 ].length; j < jlen; j++ ) {
+
+        var title = tableData[ 0 ][ j ],
+            val = tableData[ 1 ][ j ];
+
+        series.data.push( [ title, val ] );
+
+    }
+
+    return {
+        series: [ series ]
+    };
+
+}
+
+function getTableData () {
+
+    var table = document.getElementById( "showTable" ),
+        xCount = table.rows[0].cells.length - 1,
+        values = getTableInputValue();
+
+    for ( var i = 0, value; value = values[ i ]; i++ ) {
+
+        tableData[ Math.floor( i / xCount ) + 1 ][ i % xCount + 1 ] = values[ i ];
+
+    }
+
+    return tableData;
+
+}
+
+function getTableInputValue () {
+
+    var table = document.getElementById( "showTable" ),
+        inputs = table.getElementsByTagName( "input" ),
+        values = [];
+
+    for ( var i = 0, input; input = inputs[ i ]; i++ ) {
+        values.push( input.value | 0 );
+    }
+
+    return values;
+
+}
+
+function getCellValue ( cell ) {
+
+    var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );
+
+    return value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\s+|\s+$/g, '' );
+
+}
+
+
+//dialog确认事件
+dialog.onok = function () {
+
+    //收集信息
+    var form = document.forms[ 'data-form' ],
+        info = getUserConfig();
+
+    //添加图表类型
+    info.chartType = currentChartType;
+
+    //同步表格数据到编辑器
+    syncTableData();
+
+    //执行图表命令
+    editor.execCommand( 'charts', info );
+
+};
+
+/*
+ * 同步图表编辑视图的表格数据到编辑器里的原始表格
+ */
+function syncTableData () {
+
+    var tableData = getTableData();
+
+    for ( var i = 1, row; row = editorTable.rows[ i ]; i++ ) {
+
+        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {
+
+            cell.innerHTML = tableData[ i ] [ j ];
+
+        }
+
+    }
+
+}

BIN
public/plugins/ueditor/dialogs/charts/images/charts0.png


BIN
public/plugins/ueditor/dialogs/charts/images/charts1.png


BIN
public/plugins/ueditor/dialogs/charts/images/charts2.png


BIN
public/plugins/ueditor/dialogs/charts/images/charts3.png


BIN
public/plugins/ueditor/dialogs/charts/images/charts4.png


BIN
public/plugins/ueditor/dialogs/charts/images/charts5.png


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است