Ethan 6 лет назад
Родитель
Сommit
030c16037f
100 измененных файлов с 8246 добавлено и 2 удалено
  1. 2 0
      .gitignore
  2. 42 0
      .travis.yml
  3. 674 0
      LICENSE
  4. 32 0
      LICENSE.txt
  5. 28 2
      README.md
  6. 1 0
      application/.htaccess
  7. 1 0
      application/admin/common.php
  8. 23 0
      application/admin/config.php
  9. 150 0
      application/admin/controller/Admins.php
  10. 18 0
      application/admin/controller/Base.php
  11. 151 0
      application/admin/controller/Groups.php
  12. 114 0
      application/admin/controller/Index.php
  13. 75 0
      application/admin/controller/Login.php
  14. 98 0
      application/admin/controller/System.php
  15. 197 0
      application/admin/controller/Users.php
  16. 139 0
      application/admin/controller/Words.php
  17. 126 0
      application/admin/view/admins/addadmin.html
  18. 126 0
      application/admin/view/admins/editadmin.html
  19. 130 0
      application/admin/view/admins/index.html
  20. 120 0
      application/admin/view/groups/addgroup.html
  21. 120 0
      application/admin/view/groups/editgroup.html
  22. 129 0
      application/admin/view/groups/index.html
  23. 79 0
      application/admin/view/groups/manageuser.html
  24. 318 0
      application/admin/view/index.html
  25. 191 0
      application/admin/view/index/index.html
  26. 74 0
      application/admin/view/login/index.html
  27. 103 0
      application/admin/view/menu.html
  28. 120 0
      application/admin/view/system/customerservice.html
  29. 120 0
      application/admin/view/system/reply.html
  30. 153 0
      application/admin/view/system/wordslog.html
  31. 175 0
      application/admin/view/users/adduser.html
  32. 175 0
      application/admin/view/users/edituser.html
  33. 131 0
      application/admin/view/users/index.html
  34. 120 0
      application/admin/view/words/addword.html
  35. 120 0
      application/admin/view/words/editword.html
  36. 129 0
      application/admin/view/words/index.html
  37. 12 0
      application/command.php
  38. 37 0
      application/common.php
  39. 35 0
      application/config.php
  40. 53 0
      application/database.php
  41. 1 0
      application/index/common.php
  42. 5 0
      application/index/config.php
  43. 51 0
      application/index/controller/Index.php
  44. 70 0
      application/index/controller/Upload.php
  45. 54 0
      application/index/view/index/chat.html
  46. 331 0
      application/index/view/index/index.html
  47. 58 0
      application/index/view/index/mobile.html
  48. 21 0
      application/route.php
  49. 1 0
      application/service/common.php
  50. 7 0
      application/service/config.php
  51. 20 0
      application/service/controller/Base.php
  52. 89 0
      application/service/controller/Index.php
  53. 52 0
      application/service/controller/Login.php
  54. 68 0
      application/service/controller/Upload.php
  55. 1 0
      application/service/database.php
  56. 150 0
      application/service/view/index/index.html
  57. 75 0
      application/service/view/login/index.html
  58. 28 0
      application/tags.php
  59. 25 0
      build.php
  60. 29 0
      composer.json
  61. 2 0
      extend/.gitignore
  62. 8 0
      public/.htaccess
  63. BIN
      public/favicon.ico
  64. 17 0
      public/index.php
  65. 2 0
      public/robots.txt
  66. 20 0
      public/router.php
  67. 11 0
      public/static/admin/css/animate.min.css
  68. 4 0
      public/static/admin/css/bootstrap.min.css
  69. 0 0
      public/static/admin/css/demo/webuploader-demo.min.css
  70. 3 0
      public/static/admin/css/font-awesome.min.css
  71. 124 0
      public/static/admin/css/login.css
  72. BIN
      public/static/admin/css/patterns/header-profile-skin-1.png
  73. BIN
      public/static/admin/css/patterns/header-profile-skin-3.png
  74. BIN
      public/static/admin/css/patterns/header-profile.png
  75. BIN
      public/static/admin/css/patterns/shattered.png
  76. 251 0
      public/static/admin/css/plugins/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css
  77. 0 0
      public/static/admin/css/plugins/blueimp/css/blueimp-gallery.min.css
  78. BIN
      public/static/admin/css/plugins/blueimp/img/error.png
  79. 5 0
      public/static/admin/css/plugins/blueimp/img/error.svg
  80. BIN
      public/static/admin/css/plugins/blueimp/img/loading.gif
  81. BIN
      public/static/admin/css/plugins/blueimp/img/play-pause.png
  82. 6 0
      public/static/admin/css/plugins/blueimp/img/play-pause.svg
  83. BIN
      public/static/admin/css/plugins/blueimp/img/video-play.png
  84. 5 0
      public/static/admin/css/plugins/blueimp/img/video-play.svg
  85. 0 0
      public/static/admin/css/plugins/bootstrap-table/bootstrap-table.min.css
  86. BIN
      public/static/admin/css/plugins/chosen/chosen-sprite.png
  87. BIN
      public/static/admin/css/plugins/chosen/chosen-sprite@2x.png
  88. 423 0
      public/static/admin/css/plugins/chosen/chosen.css
  89. 168 0
      public/static/admin/css/plugins/clockpicker/clockpicker.css
  90. 75 0
      public/static/admin/css/plugins/codemirror/ambiance.css
  91. 309 0
      public/static/admin/css/plugins/codemirror/codemirror.css
  92. 8 0
      public/static/admin/css/plugins/colorpicker/css/bootstrap-colorpicker.min.css
  93. BIN
      public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/alpha-horizontal.png
  94. BIN
      public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/alpha.png
  95. BIN
      public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/hue-horizontal.png
  96. BIN
      public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/hue.png
  97. BIN
      public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/saturation.png
  98. 8 0
      public/static/admin/css/plugins/cropper/cropper.min.css
  99. 231 0
      public/static/admin/css/plugins/dataTables/dataTables.bootstrap.css
  100. 789 0
      public/static/admin/css/plugins/datapicker/datepicker3.css

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.idea
+runtime

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+branches:
+  only:
+    - stable
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+
+install:
+  - composer install --no-dev --no-interaction --ignore-platform-reqs
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
+  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
+  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
+
+script:
+  - php think unit
+
+deploy:
+  provider: releases
+  api_key:
+    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
+  file:
+    - ThinkPHP_Core.zip
+    - ThinkPHP_Full.zip
+  skip_cleanup: true
+  on:
+    tags: true

+ 674 - 0
LICENSE

@@ -0,0 +1,674 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. [http://fsf.org/]
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {one line to give the program's name and a brief idea of what it does.}
+    Copyright (C) {year}  {fullname}
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see [http://www.gnu.org/licenses/].
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    whisper_tp5  Copyright (C) 2017  白云飞
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+[http://www.gnu.org/licenses/].
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+[http://www.gnu.org/philosophy/why-not-lgpl.html].

+ 32 - 0
LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 28 - 2
README.md

@@ -1,3 +1,29 @@
-# Customer-Service
+## whsiper
+whisper客服系统,是采用thinkphp5+Gatewayworker开的高性能客服系统  
+欢迎您给我打星,更多的内容请进入官网查看
 
 
-客服项目
+***
+## whisper客服系统交流群 
+QQ: 345146554
+
+***
+## 打赏作者  
+支付宝
+![支付宝](https://images.gitee.com/uploads/images/2019/0522/142641_89579267_552304.png)  
+微信
+![微信](https://images.gitee.com/uploads/images/2019/0522/142636_9476f13d_552304.png) 
+
+***  
+## 重要通知
+vendor下放了两套环境的 gatewayworker (因为开发的时候用的gatewayworker还没合并两个版本)  
+gatewayworker_windows 是windows下用的  
+gatewayworker_linux 是linux环境下用的  
+
+多看看文档,不会的,欢迎入群讨论。
+
+***
+## 官网:
+[whisper官网](http://whisper.baiyf.com)  
+[whisper文档](https://www.kancloud.cn/nickbai/whisper/552736)
+
+### 很重要的一点,开源版本仅供学习参考使用,切勿商用(包括但不限于在自己的网站上使用,二次开发出售等),商用请授权!

+ 1 - 0
application/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 1 - 0
application/admin/common.php

@@ -0,0 +1 @@
+<?php

+ 23 - 0
application/admin/config.php

@@ -0,0 +1,23 @@
+<?php
+//配置文件
+return [
+
+    // 模板参数替换
+    'view_replace_str' => [
+        '__CSS__' => '/static/admin/css',
+        '__JS__'  => '/static/admin/js',
+        '__IMG__' => '/static/admin/images',
+    ],
+
+    // 客服状态
+    'kf_status' => [
+        1 => '启用',
+        2 => '禁用'
+    ],
+
+    // 是否在线
+    'online' => [
+        1 => '在线',
+        2 => '离线'
+    ]
+];

+ 150 - 0
application/admin/controller/Admins.php

@@ -0,0 +1,150 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/23 13:33
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+class Admins extends Base
+{
+    // 管理员列表
+    public function index()
+    {
+        if(request()->isAjax()){
+
+            $param = input('param.');
+
+            $limit = $param['pageSize'];
+            $offset = ($param['pageNumber'] - 1) * $limit;
+
+            $where = [];
+            if (!empty($param['searchText'])) {
+                $where['user_name'] = ['like', '%' . $param['searchText'] . '%'];
+            }
+
+            $result = db('admins')->where($where)->limit($offset, $limit)->select();
+            foreach($result as $key=>$vo){
+                // 优化显示状态
+                if(1 == $vo['status']){
+                    $result[$key]['status'] = '<span class="label label-primary">启用</span>';
+                }else{
+                    $result[$key]['status'] = '<span class="label label-danger">禁用</span>';
+                }
+
+                // 上次登录时间
+                $result[$key]['last_login_time'] = empty($vo['last_login_time']) ? '' : date('Y-m-d H:i:s', $vo['last_login_time']);
+
+                // 生成操作按钮
+                if(1 != $vo['id']){
+                    $result[$key]['operate'] = $this->makeBtn($vo['id']);
+                }
+            }
+
+            $return['total'] = db('admins')->where($where)->count();  //总数据
+            $return['rows'] = $result;
+
+            return json($return);
+
+        }
+
+        return $this->fetch();
+    }
+
+    // 添加管理员
+    public function addAdmin()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+
+            $has = db('admins')->field('id')->where('user_name', $param['user_name'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该管理员已经存在']);
+            }
+
+            $param['password'] = md5($param['password'] . config('salt'));
+
+            try{
+                db('admins')->insert($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '添加管理员成功']);
+        }
+
+        $this->assign([
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 编辑管理员
+    public function editAdmin()
+    {
+        if(request()->isAjax()){
+
+            $param = input('post.');
+
+            // 检测用户修改的管理员是否重复
+            $has = db('admins')->where('user_name', $param['user_name'])->where('id', '<>', $param['id'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该管理员已经存在']);
+            }
+
+            // 修改用户密码
+            if(empty($param['password'])){
+                unset($param['password']);
+            }else{
+                $param['password'] = md5($param['password'] . config('salt'));
+            }
+
+            try{
+                db('admins')->where('id', $param['id'])->update($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '编辑管理员成功']);
+        }
+
+        $id = input('param.id/d');
+        $info = db('admins')->where('id', $id)->find();
+
+        $this->assign([
+            'info' => $info,
+            'status' => config('kf_status')
+        ]);
+        return $this->fetch();
+    }
+
+    // 删除管理员
+    public function delAdmin()
+    {
+        if(request()->isAjax()){
+            $id = input('param.id/d');
+
+            try{
+                db('admins')->where('id', $id)->delete();
+            }catch(\Exception $e){
+                return json(['code' => -1, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '删除客服成功']);
+        }
+    }
+
+    // 生成按钮
+    private function makeBtn($id)
+    {
+        $operate = '<a href="' . url('admins/editadmin', ['id' => $id]) . '">';
+        $operate .= '<button type="button" class="btn btn-primary btn-sm"><i class="fa fa-paste"></i> 编辑</button></a> ';
+
+        $operate .= '<a href="javascript:userDel(' . $id . ')"><button type="button" class="btn btn-danger btn-sm">';
+        $operate .= '<i class="fa fa-trash-o"></i> 删除</button></a> ';
+
+        return $operate;
+    }
+}

+ 18 - 0
application/admin/controller/Base.php

@@ -0,0 +1,18 @@
+<?php
+namespace app\admin\controller;
+
+use think\Controller;
+
+class Base extends Controller
+{
+    public function _initialize()
+    {
+        if(empty(cookie('user_name'))){
+            $this->redirect(url('login/index'));
+        }
+
+        $this->assign([
+            'version' => config('version')
+        ]);
+    }
+}

+ 151 - 0
application/admin/controller/Groups.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/23 13:33
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+class Groups extends Base
+{
+    // 分组列表
+    public function index()
+    {
+        if(request()->isAjax()){
+
+            $result = db('groups')->select();
+            foreach($result as $key=>$vo){
+                // 优化显示状态
+                if(1 == $vo['status']){
+                    $result[$key]['status'] = '<span class="label label-primary">启用</span>';
+                }else{
+                    $result[$key]['status'] = '<span class="label label-danger">禁用</span>';
+                }
+
+                // 优化显示状态
+                if(1 == $vo['status']){
+                    $result[$key]['status'] = '<span class="label label-primary">启用</span>';
+                }else{
+                    $result[$key]['status'] = '<span class="label label-danger">禁用</span>';
+                }
+
+                // 统计分组人数
+                $result[$key]['users_num'] = db('users')->where('group_id', $vo['id'])->count();
+
+                // 生成操作按钮
+                $result[$key]['operate'] = $this->makeBtn($vo['id']);
+            }
+
+            $return['total'] = db('users')->count();  //总数据
+            $return['rows'] = $result;
+
+            return json($return);
+
+        }
+
+        return $this->fetch();
+    }
+
+    // 添加分组
+    public function addGroup()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+
+            $has = db('groups')->field('id')->where('name', $param['name'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该分组已经存在']);
+            }
+
+            try{
+                db('groups')->insert($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '添加分组成功']);
+        }
+
+        $this->assign([
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 编辑分组
+    public function editGroup()
+    {
+        if(request()->isAjax()){
+
+            $param = input('post.');
+
+            // 检测用户修改的用户名是否重复
+            $has = db('groups')->where('name', $param['name'])->where('id', '<>', $param['id'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该分组已经存在']);
+            }
+
+            try{
+                db('groups')->where('id', $param['id'])->update($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '编辑分组成功']);
+        }
+
+        $id = input('param.id/d');
+        $info = db('groups')->where('id', $id)->find();
+
+        $this->assign([
+            'info' => $info,
+            'status' => config('kf_status')
+        ]);
+        return $this->fetch();
+    }
+
+    // 删除分组
+    public function delGroup()
+    {
+        if(request()->isAjax()){
+            $id = input('param.id/d');
+
+            // 查询该分组下是否有客服
+            $has = db('users')->where('group_id', $id)->count();
+            if($has > 0){
+                return json(['code' => -2, 'data' => '', 'msg' => '该分组下有客服,不可删除']);
+            }
+
+            try{
+                db('groups')->where('id', $id)->delete();
+            }catch(\Exception $e){
+                return json(['code' => -1, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '删除分组成功']);
+        }
+    }
+
+    // 管理组员
+    public function manageUser()
+    {
+        return $this->fetch();
+    }
+
+    // 生成按钮
+    private function makeBtn($id)
+    {
+        $operate = '<a href="' . url('groups/editgroup', ['id' => $id]) . '">';
+        $operate .= '<button type="button" class="btn btn-primary btn-sm"><i class="fa fa-paste"></i> 编辑</button></a> ';
+
+        $operate .= '<a href="javascript:userGroup(' . $id . ')"><button type="button" class="btn btn-danger btn-sm">';
+        $operate .= '<i class="fa fa-trash-o"></i> 删除</button></a> ';
+
+        //$operate .= '<a href="' . url('groups/manageUser') . '">';
+        //$operate .= '<button type="button" class="btn btn-info btn-sm"><i class="fa fa-user-plus"></i> 管理组员</button></a>';
+
+        return $operate;
+    }
+}

+ 114 - 0
application/admin/controller/Index.php

@@ -0,0 +1,114 @@
+<?php
+namespace app\admin\controller;
+
+class Index extends Base
+{
+    // 后台总体框架
+    public function index()
+    {
+        return $this->fetch('/index');
+    }
+
+    // 后台默认首页
+    public function indexPage()
+    {
+        $data = db('now_data')->where('id', 1)->find();
+
+        // 生成从 8点 到 22点的时间数组
+        $dateLine = array_map(function($vo){
+            if($vo < 10){
+                return '0' . $vo;
+            }else{
+                return $vo;
+            }
+        }, range(8, 22));
+
+        // 初始化数据
+        $line = [];
+        foreach($dateLine as $key=>$vo){
+            $line[$vo] = [
+                'is_talking' => 0,
+                'in_queue' => 0,
+                'success_in' => 0,
+                'total_in' => 0
+            ];
+        }
+
+        $dbData = db('service_data')->where('add_date', date('Y-m-d'))->group('add_hour')->select();
+
+        foreach($line as $key=>$vo){
+            foreach($dbData as $k=>$v){
+                if($v['add_hour'] == $key){
+                    $line[$key]['is_talking'] = $v['is_talking'];
+                    $line[$key]['in_queue'] = $v['in_queue'];
+                    $line[$key]['success_in'] = $v['success_in'];
+                    $line[$key]['total_in'] = $v['total_in'];
+
+                    unset($dbData[$k]);
+                    continue;
+                }
+            }
+        }
+
+        $showData = [];
+        foreach($line as $key=>$vo){
+            $showData['is_talking'][] = $vo['is_talking'];
+            $showData['in_queue'][] = $vo['in_queue'];
+            $showData['success_in'][] = $vo['success_in'];
+            $showData['total_in'][] = $vo['total_in'];
+        }
+
+        $this->assign([
+            'data' => $data,
+            'show_data' => json_encode($showData)
+        ]);
+
+        return $this->fetch('index');
+    }
+
+    // 清除缓存
+    public function clear()
+    {
+        if (false === removeDir(RUNTIME_PATH)) {
+            return json(['code' => -1, 'data' => '', 'msg' => '清除缓存失败']);
+        }
+        return json(['code' => 1, 'data' => '', 'msg' => '清除缓存成功']);
+    }
+
+    // 修改管理员密码
+    public function changePassword()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+            $reLogin = false;
+
+            if(empty($param['old_pwd']) && !empty($param['password'])){
+                return json(['code' => -2, 'data' => '', 'msg' => '请输入旧密码']);
+            }
+
+            if(!empty($param['old_pwd']) && empty($param['password'])){
+                return json(['code' => -3, 'data' => '', 'msg' => '请输入新密码']);
+            }
+
+            if(!empty($param['old_pwd']) && !empty($param['password'])){
+
+                $userPwd = db('admins')->where('id', cookie('user_id'))->find();
+                if(empty($userPwd)){
+                    return json(['code' => -4, 'data' => '', 'msg' => '管理员不存在']);
+                }
+
+                if(md5($param['old_pwd'] . config('salt')) != $userPwd['password']){
+                    return json(['code' => -1, 'data' => '', 'msg' => '旧密码错误']);
+                }
+
+                $info['password'] = md5($param['password'] . config('salt'));
+                $reLogin = true;
+            }
+
+            db('admins')->where('id', cookie('user_id'))->setField('password', $info['password']);
+
+            return json(['code' => 1, 'data' => $reLogin, 'msg' => '修改信息成功']);
+        }
+    }
+}

+ 75 - 0
application/admin/controller/Login.php

@@ -0,0 +1,75 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/24 10:46
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+use think\Controller;
+
+class Login extends Controller
+{
+    // 登录首页
+    public function index()
+    {
+        $this->assign([
+            'version' => config('version')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 处理登录
+    public function doLogin()
+    {
+        if(request()->isPost()){
+
+            $userName = input("param.user_name/s");
+            $password = input("param.password/s");
+
+            if(empty($userName)){
+                return json(['code' => -1, 'data' => '', 'msg' => '用户名不能为空']);
+            }
+
+            if(empty($password)){
+                return json(['code' => -2, 'data' => '', 'msg' => '密码不能为空']);
+            }
+
+            $userInfo = db('admins')->where('user_name', $userName)->find();
+            if(empty($userInfo)){
+                return json(['code' => -3, 'data' => '', 'msg' => '管理员不存在']);
+            }
+
+            if(md5($password . config('salt')) != $userInfo['password']){
+                return json(['code' => -4, 'data' => '', 'msg' => '密码错误']);
+            }
+
+            if(1 != $userInfo['status']){
+                return json(['code' => -5, 'data' => '', 'msg' => '您已被禁用']);
+            }
+
+            // 记录管理员状态
+            cookie('user_name', $userName, config('save_time'));
+            cookie('user_id', $userInfo['id'], config('save_time'));
+
+            // 更新管理员状态
+            $param = [
+                'last_login_ip' => request()->ip(),
+                'last_login_time' => time()
+            ];
+            db('admins')->where('id', $userInfo['id'])->update($param);
+
+            return json(['code' => 1, 'data' => url('index/index'), 'msg' => '登录成功']);
+        }
+    }
+
+    public function loginOut()
+    {
+        cookie('user_name', '');
+        cookie('user_id', '');
+
+        $this->redirect(url('login/index'));
+    }
+
+}

+ 98 - 0
application/admin/controller/System.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/31 12:47
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+class System extends Base
+{
+    // 自动回复设置
+    public function reply()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+            if(empty($param['word'])){
+                return json(['code' => -1, 'data' => '', 'msg' => '回复内容不能为空']);
+            }
+
+            try{
+                db('reply')->where('id', 1)->update($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '设置成功']);
+        }
+
+        $info = db('reply')->where('id', 1)->find();
+        $this->assign([
+            'info' => $info,
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 客服设置
+    public function customerService()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+            db('kf_config')->where('id', 1)->update($param);
+
+            return json(['code' => 1, 'data' => '', 'msg' => '设置成功']);
+        }
+
+        $this->assign([
+            'config' => db('kf_config')->where('id', 1)->find(),
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 历史会话记录
+    public function wordsLog()
+    {
+        if(request()->isAjax()){
+
+            $param = input('param.');
+
+            $limit = $param['pageSize'];
+            $offset = ($param['pageNumber'] - 1) * $limit;
+
+            // 默认显示最近7天
+            $start = input('param.start');
+            $end = input('param.end');
+
+            $temp = db('chat_log');
+            $countTmp = db('chat_log');
+            if(!empty($param['searchText'])){
+                $temp = $temp->where('from_name', $param['searchText'])->whereOr('to_name', $param['searchText']);
+                $countTmp = $countTmp->where('from_name', $param['searchText'])->whereOr('to_name', $param['searchText']);
+            }
+
+            if(!empty($start) && !empty($end) && $start <= $end){
+                $temp = $temp->whereBetween('time_line', [strtotime($start), strtotime($end . ' 23:59:59')]);
+                $countTmp = $countTmp->whereBetween('time_line', [strtotime($start), strtotime($end . ' 23:59:59')]);
+            }
+
+            $result = $temp->limit($offset, $limit)->order('id', 'desc')->select();
+            foreach($result as $key=>$vo){
+                $result[$key]['time_line'] = date('Y-m-d H:i:s', $vo['time_line']);
+            }
+
+            $return['total'] = $countTmp->count();  //总数据
+            $return['rows'] = $result;
+
+            return json($return);
+
+        }
+
+        return $this->fetch();
+    }
+}

+ 197 - 0
application/admin/controller/Users.php

@@ -0,0 +1,197 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/23 13:33
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+class Users extends Base
+{
+    // 客服列表
+    public function index()
+    {
+        if(request()->isAjax()){
+
+            $result = db('users')->order('id', 'desc')->select();
+            foreach($result as $key=>$vo){
+                // 优化显示头像
+                $result[$key]['user_avatar'] = '<img src="' . $vo['user_avatar'] . '" width="40px" height="40px">';
+
+                // 优化显示状态
+                if(1 == $vo['status']){
+                    $result[$key]['status'] = '<span class="label label-primary">启用</span>';
+                }else{
+                    $result[$key]['status'] = '<span class="label label-danger">禁用</span>';
+                }
+
+                // 查询分组
+                $result[$key]['group'] = '-';
+                $groups = db('groups')->field('name')->where('id', $vo['group_id'])->find();
+                if(!empty($groups)){
+                    $result[$key]['group'] = $groups['name'];
+                }
+
+                // 优化显示在线状态
+                /*if(1 == $vo['online']){
+                    $result[$key]['online'] = '<span class="label label-primary">在线</span>';
+                }else{
+                    $result[$key]['online'] = '<span class="label label-danger">离线</span>';
+                }*/
+
+                // 生成操作按钮
+                $result[$key]['operate'] = $this->makeBtn($vo['id']);
+            }
+
+            $return['total'] = db('users')->count();  //总数据
+            $return['rows'] = $result;
+
+            return json($return);
+
+        }
+
+        return $this->fetch();
+    }
+
+    // 添加客服
+    public function addUser()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+            unset($param['file']); // 删除layui头像上传隐藏字段
+            // 检测头像
+            if(empty($param['user_avatar'])){
+                return json(['code' => -1, 'data' => '', 'msg' => '请上传头像']);
+            }
+
+            if(empty($param['group_id'])){
+                return json(['code' => -4, 'data' => '', 'msg' => '请选择分组']);
+            }
+
+            $has = db('users')->field('id')->where('user_name', $param['user_name'])->find();
+            if(!empty($has)){
+                return json(['code' => -2, 'data' => '', 'msg' => '该客服已经存在']);
+            }
+
+            $param['user_pwd'] = md5($param['user_pwd'] . config('salt'));
+            $param['online'] = 2; // 离线状态
+
+            try{
+                db('users')->insert($param);
+            }catch(\Exception $e){
+                return json(['code' => -3, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '添加客服成功']);
+        }
+
+        $this->assign([
+            'groups' => db('groups')->select(),
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 编辑客服
+    public function editUser()
+    {
+        if(request()->isAjax()){
+
+            $param = input('post.');
+            unset($param['file']); // 删除layui头像上传隐藏字段
+
+            if(empty($param['group_id'])){
+                return json(['code' => -4, 'data' => '', 'msg' => '请选择分组']);
+            }
+
+            // 检测用户修改的用户名是否重复
+            $has = db('users')->where('user_name', $param['user_name'])->where('id', '<>', $param['id'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该客服已经存在']);
+            }
+
+            // 修改用户头像
+            if(empty($param['user_avatar'])){
+                unset($param['user_avatar']);
+            }
+
+            // 修改用户密码
+            if(empty($param['user_pwd'])){
+                unset($param['user_pwd']);
+            }else{
+                $param['user_pwd'] = md5($param['user_pwd'] . config('salt'));
+            }
+
+            try{
+                db('users')->where('id', $param['id'])->update($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '编辑客服成功']);
+        }
+
+        $id = input('param.id/d');
+        $info = db('users')->where('id', $id)->find();
+
+        $this->assign([
+            'info' => $info,
+            'status' => config('kf_status'),
+            'groups' => db('groups')->select()
+        ]);
+        return $this->fetch();
+    }
+
+    // 删除客服
+    public function delUser()
+    {
+        if(request()->isAjax()){
+            $id = input('param.id/d');
+
+            try{
+                db('users')->where('id', $id)->delete();
+            }catch(\Exception $e){
+                return json(['code' => -1, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '删除客服成功']);
+        }
+    }
+
+    // 上传客服头像
+    public function upAvatar()
+    {
+        if(request()->isAjax()) {
+
+            $file = request()->file('file');
+            if (!empty($file)) {
+                // 移动到框架应用根目录/public/uploads/ 目录下
+                $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
+                if ($info) {
+                    $src = '/uploads' . '/' . date('Ymd') . '/' . $info->getFilename();
+                    return json(['code' => 0, 'data' => ['src' => $src], 'msg' => 'ok']);
+                } else {
+                    // 上传失败获取错误信息
+                    return json(['code' => -1, 'data' => '', 'msg' => $file->getError()]);
+                }
+            }
+        }
+    }
+
+    // 生成按钮
+    private function makeBtn($id)
+    {
+        $operate = '<a href="' . url('users/edituser', ['id' => $id]) . '">';
+        $operate .= '<button type="button" class="btn btn-primary btn-sm"><i class="fa fa-paste"></i> 编辑</button></a> ';
+
+        $operate .= '<a href="javascript:userDel(' . $id . ')"><button type="button" class="btn btn-danger btn-sm">';
+        $operate .= '<i class="fa fa-trash-o"></i> 删除</button></a> ';
+
+        //$operate .= '<a href="javascript:;">';
+        //$operate .= '<button type="button" class="btn btn-info btn-sm"><i class="fa fa-institution"></i> 详情</button></a>';
+
+        return $operate;
+    }
+}

+ 139 - 0
application/admin/controller/Words.php

@@ -0,0 +1,139 @@
+<?php
+/**
+ * User: nickbai
+ * Date: 2017/10/23 13:33
+ * Email: 1902822973@qq.com
+ */
+namespace app\admin\controller;
+
+class Words extends Base
+{
+    // 常用语列表
+    public function index()
+    {
+        if(request()->isAjax()){
+
+            $param = input('param.');
+
+            $limit = $param['pageSize'];
+            $offset = ($param['pageNumber'] - 1) * $limit;
+
+            $where = [];
+            if (!empty($param['searchText'])) {
+                $where['content'] = $param['searchText'];
+            }
+
+            $result = db('words')->where($where)->limit($offset, $limit)->select();
+            foreach($result as $key=>$vo){
+                // 优化显示状态
+                if(1 == $vo['status']){
+                    $result[$key]['status'] = '<span class="label label-primary">启用</span>';
+                }else{
+                    $result[$key]['status'] = '<span class="label label-danger">禁用</span>';
+                }
+
+                // 生成操作按钮
+                $result[$key]['operate'] = $this->makeBtn($vo['id']);
+            }
+
+            $return['total'] = db('words')->where($where)->count();  //总数据
+            $return['rows'] = $result;
+
+            return json($return);
+
+        }
+
+        return $this->fetch();
+    }
+
+    // 添加常用语
+    public function addWord()
+    {
+        if(request()->isPost()){
+
+            $param = input('post.');
+            $param['content'] = trim($param['content']);
+
+            $has = db('words')->field('id')->where('content', $param['content'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该常用语已经存在']);
+            }
+
+            $param['add_time'] = date('Y-m-d H:i:s');
+            try{
+                db('words')->insert($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '添加常用语成功']);
+        }
+
+        $this->assign([
+            'status' => config('kf_status')
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 编辑常用语
+    public function editWord()
+    {
+        if(request()->isAjax()){
+
+            $param = input('post.');
+            $param['content'] = trim($param['content']);
+
+            // 检测用户修改的常用语是否重复
+            $has = db('words')->where('content', $param['content'])->where('id', '<>', $param['id'])->find();
+            if(!empty($has)){
+                return json(['code' => -1, 'data' => '', 'msg' => '该常用语已经存在']);
+            }
+
+            try{
+                db('words')->where('id', $param['id'])->update($param);
+            }catch(\Exception $e){
+                return json(['code' => -2, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '编辑常用语成功']);
+        }
+
+        $id = input('param.id/d');
+        $info = db('words')->where('id', $id)->find();
+
+        $this->assign([
+            'info' => $info,
+            'status' => config('kf_status')
+        ]);
+        return $this->fetch();
+    }
+
+    // 删除常用语
+    public function delWord()
+    {
+        if(request()->isAjax()){
+            $id = input('param.id/d');
+
+            try{
+                db('words')->where('id', $id)->delete();
+            }catch(\Exception $e){
+                return json(['code' => -1, 'data' => '', 'msg' => $e->getMessage()]);
+            }
+
+            return json(['code' => 1, 'data' => '', 'msg' => '删除常用语成功']);
+        }
+    }
+
+    // 生成按钮
+    private function makeBtn($id)
+    {
+        $operate = '<a href="' . url('words/editword', ['id' => $id]) . '">';
+        $operate .= '<button type="button" class="btn btn-primary btn-sm"><i class="fa fa-paste"></i> 编辑</button></a> ';
+
+        $operate .= '<a href="javascript:userDel(' . $id . ')"><button type="button" class="btn btn-danger btn-sm">';
+        $operate .= '<i class="fa fa-trash-o"></i> 删除</button></a> ';
+
+        return $operate;
+    }
+}

+ 126 - 0
application/admin/view/admins/addadmin.html

@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>添加管理员</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>添加管理员</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('admins/addadmin')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">管理员名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="username" type="text" class="form-control" name="user_name" required="" aria-required="true">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">登录密码:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="password" type="text" class="form-control" name="password" required="" aria-required="true">
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq 1}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 126 - 0
application/admin/view/admins/editadmin.html

@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>编辑管理员</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>编辑管理员</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('admins/editadmin')}">
+                        <input type="hidden" value="{$info['id']}" name="id"/>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">管理员名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="username" type="text" class="form-control" name="user_name" required="" aria-required="true" value="{$info['user_name']}">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">登录密码:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="password" type="text" class="form-control" name="password" placeholder="重新输入则为修改">
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq $info['status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 130 - 0
application/admin/view/admins/index.html

@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>管理员列表</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <!-- Panel Other -->
+    <div class="ibox float-e-margins">
+        <div class="ibox-title">
+            <h5>管理员列表</h5>
+        </div>
+        <div class="ibox-content">
+            <!--搜索框开始-->
+            <form id='commentForm' role="form" method="post" class="form-inline pull-right">
+                <div class="content clearfix m-b">
+                    <div class="form-group">
+                        <label>管理员名称:</label>
+                        <input type="text" class="form-control" id="username" name="user_name">
+                    </div>
+                    <div class="form-group">
+                        <button class="btn btn-primary" type="button" style="margin-top:5px" id="search"><strong>搜 索</strong>
+                        </button>
+                    </div>
+                </div>
+            </form>
+            <!--搜索框结束-->
+            <div class="example-wrap">
+                <div class="example">
+                    <table id="cusTable">
+                        <thead>
+                        <th data-field="id">管理员ID</th>
+                        <th data-field="user_name">管理员名称</th>
+                        <th data-field="last_login_ip">上次登录ip</th>
+                        <th data-field="last_login_time">上次登录时间</th>
+                        <th data-field="status">状态</th>
+                        <th data-field="operate">操作</th>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <!-- End Example Pagination -->
+        </div>
+    </div>
+</div>
+<!-- End Panel Other -->
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script type="text/javascript">
+    function initTable() {
+        //先销毁表格
+        $('#cusTable').bootstrapTable('destroy');
+        //初始化表格,动态从服务器加载数据
+        $("#cusTable").bootstrapTable({
+            method: "get",  //使用get请求到服务器获取数据
+            url: "{:url('admins/index')}", //获取数据的地址
+            striped: true,  //表格显示条纹
+            pagination: true, //启动分页
+            pageSize: 10,  //每页显示的记录数
+            pageNumber:1, //当前第几页
+            pageList: [5, 10, 15, 20, 25],  //记录数可选列表
+            sidePagination: "server", //表示服务端请求
+            paginationFirstText: "首页",
+            paginationPreText: "上一页",
+            paginationNextText: "下一页",
+            paginationLastText: "尾页",
+            queryParamsType : "undefined",
+            queryParams: function queryParams(params) {   //设置查询参数
+                var param = {
+                    pageNumber: params.pageNumber,
+                    pageSize: params.pageSize,
+                    searchText:$('#username').val()
+                };
+                return param;
+            },
+            onLoadSuccess: function(res){  //加载成功时执行
+                if(111 == res.code){
+                    window.location.reload();
+                }
+                layer.msg("加载成功", {time : 1000});
+            },
+            onLoadError: function(){  //加载失败时执行
+                layer.msg("加载数据失败");
+            }
+        });
+    }
+
+    $(document).ready(function () {
+        //调用函数,初始化表格
+        initTable();
+
+        //当点击查询按钮的时候执行
+        $("#search").bind("click", initTable);
+    });
+
+    function userDel(id){
+        layer.confirm('确认删除此客服?', {icon: 3, title:'提示'}, function(index){
+            //do something
+            $.getJSON("{:url('users/delUser')}", {'id' : id}, function(res){
+                if(1 == res.code){
+                    layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                        initTable();
+                    });
+                }else if(111 == res.code){
+                    window.location.reload();
+                }else{
+                    layer.alert(res.msg, {title: '友情提示', icon: 2});
+                }
+            });
+
+            layer.close(index);
+        })
+
+    }
+</script>
+</body>
+</html>

+ 120 - 0
application/admin/view/groups/addgroup.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>添加分组</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>添加分组</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('groups/addgroup')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">分组名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input class="form-control" name="name" required="" aria-required="true" />
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq 1}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 120 - 0
application/admin/view/groups/editgroup.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>编辑分组</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>编辑分组</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('groups/editgroup')}">
+                        <input type="hidden" name="id" value="{$info['id']}"/>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">分组名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input class="form-control" name="name" required="" aria-required="true" value="{$info['name']}"/>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq $info['status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 129 - 0
application/admin/view/groups/index.html

@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>分组列表</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <!-- Panel Other -->
+    <div class="ibox float-e-margins">
+        <div class="ibox-title">
+            <h5>分组列表</h5>
+        </div>
+        <div class="ibox-content">
+            <!--搜索框开始-->
+            <form id='commentForm' role="form" method="post" class="form-inline pull-right">
+                <div class="content clearfix m-b">
+                    <div class="form-group">
+                        <label>分组名称:</label>
+                        <input type="text" class="form-control" id="username" name="name">
+                    </div>
+                    <div class="form-group">
+                        <button class="btn btn-primary" type="button" style="margin-top:5px" id="search"><strong>搜 索</strong>
+                        </button>
+                    </div>
+                </div>
+            </form>
+            <!--搜索框结束-->
+            <div class="example-wrap">
+                <div class="example">
+                    <table id="cusTable">
+                        <thead>
+                        <th data-field="id">分组ID</th>
+                        <th data-field="name">分组名称</th>
+                        <th data-field="users_num">客服人数</th>
+                        <th data-field="status">分组状态</th>
+                        <th data-field="operate">操作</th>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <!-- End Example Pagination -->
+        </div>
+    </div>
+</div>
+<!-- End Panel Other -->
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script type="text/javascript">
+    function initTable() {
+        //先销毁表格
+        $('#cusTable').bootstrapTable('destroy');
+        //初始化表格,动态从服务器加载数据
+        $("#cusTable").bootstrapTable({
+            method: "get",  //使用get请求到服务器获取数据
+            url: "{:url('groups/index')}", //获取数据的地址
+            striped: true,  //表格显示条纹
+            pagination: true, //启动分页
+            pageSize: 10,  //每页显示的记录数
+            pageNumber:1, //当前第几页
+            pageList: [5, 10, 15, 20, 25],  //记录数可选列表
+            sidePagination: "server", //表示服务端请求
+            paginationFirstText: "首页",
+            paginationPreText: "上一页",
+            paginationNextText: "下一页",
+            paginationLastText: "尾页",
+            queryParamsType : "undefined",
+            queryParams: function queryParams(params) {   //设置查询参数
+                var param = {
+                    pageNumber: params.pageNumber,
+                    pageSize: params.pageSize,
+                    searchText:$('#username').val()
+                };
+                return param;
+            },
+            onLoadSuccess: function(res){  //加载成功时执行
+                if(111 == res.code){
+                    window.location.reload();
+                }
+                layer.msg("加载成功", {time : 1000});
+            },
+            onLoadError: function(){  //加载失败时执行
+                layer.msg("加载数据失败");
+            }
+        });
+    }
+
+    $(document).ready(function () {
+        //调用函数,初始化表格
+        initTable();
+
+        //当点击查询按钮的时候执行
+        $("#search").bind("click", initTable);
+    });
+
+    function userGroup(id){
+        layer.confirm('确认删除此分组?', {icon: 3, title:'提示'}, function(index){
+            //do something
+            $.getJSON("{:url('groups/delGroup')}", {'id' : id}, function(res){
+                if(1 == res.code){
+                    layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                        initTable();
+                    });
+                }else if(111 == res.code){
+                    window.location.reload();
+                }else{
+                    layer.alert(res.msg, {title: '友情提示', icon: 2});
+                }
+            });
+
+            layer.close(index);
+        })
+
+    }
+</script>
+</body>
+</html>

+ 79 - 0
application/admin/view/groups/manageuser.html

@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>组员管理</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+
+<body class="gray-bg">
+<div class="wrapper wrapper-content  animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-6">
+            <div class="ibox">
+                <div class="ibox-content">
+                    <h2>客户管理</h2>
+                    <div class="input-group">
+                        <input type="text" placeholder="查找客户" class="input form-control">
+                        <span class="input-group-btn">
+                            <button type="button" class="btn btn btn-primary"> <i class="fa fa-plus"></i> 添加</button>
+                        </span>
+                    </div>
+                    <div class="clients-list">
+                        <ul class="nav nav-tabs">
+                            <li class="active">
+                                <a data-toggle="tab" href="#tab-1"><i class="fa fa-user"></i> 客服</a>
+                            </li>
+                        </ul>
+                        <div class="tab-content">
+                            <div id="tab-1" class="tab-pane active">
+                                <div class="full-height-scroll">
+                                    <div class="table-responsive">
+                                        <table class="table table-striped table-hover">
+                                            <tbody>
+                                                <tr>
+                                                    <td class="client-avatar">
+                                                        <img alt="image" src="http://ozwpnu2pa.bkt.clouddn.com/a2.jpg">
+                                                    </td>
+                                                    <td>
+                                                        <a data-toggle="tab" href="#contact-1" class="client-link">袁岳</a>
+                                                    </td>
+                                                    <td class="client-status">
+                                                            <span class="label label-primary">启用</span>
+                                                    </td>
+                                                    <td> <a href="javascript:;"><span class="label label-danger">移除</span></a></td>
+                                                </tr>
+                                            </tbody>
+                                        </table>
+                                    </div>
+                                </div>
+                            </div>
+
+                        </div>
+
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js"></script>
+<script src="__JS__/bootstrap.min.js"></script>
+<script src="__JS__//plugins/slimscroll/jquery.slimscroll.min.js"></script>
+<script src="__JS__//content.min.js?v=1.0.0"></script>
+<script>
+    $(function () {
+        $(".full-height-scroll").slimScroll({height: "100%"})
+    });
+</script>
+
+</body>
+
+</html>

+ 318 - 0
application/admin/view/index.html

@@ -0,0 +1,318 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="Cache-Control" content="no-siteapp"/>
+    <title>whisper-{$version}管理后台</title>
+    <meta name="keywords" content="">
+    <meta name="description" content="">
+    <!--[if lt IE 9]>
+    <meta http-equiv="refresh" content="0;ie.html"/>
+    <![endif]-->
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+</head>
+
+<body class="fixed-sidebar full-height-layout gray-bg" style="overflow:hidden">
+<div id="wrapper">
+    <!--左侧导航开始-->
+    <nav class="navbar-default navbar-static-side" role="navigation">
+        <div class="nav-close"><i class="fa fa-times-circle"></i>
+        </div>
+        <div class="sidebar-collapse">
+            <ul class="nav" id="side-menu">
+                <li class="nav-header">
+                    <div class="dropdown profile-element">
+                        <span><img alt="image" class="img-circle" src="__IMG__/profile_small.jpg"/></span>
+                        <a data-toggle="dropdown" class="dropdown-toggle" href="#">
+                            <span class="clear">
+                                <span class="block m-t-xs">欢迎您 -- <strong class="font-bold">管理员</strong></span>
+                                <span class="text-muted text-xs block">管理员<b class="caret"></b></span>
+                            </span>
+                        </a>
+                        <ul class="dropdown-menu animated fadeInRight m-t-xs">
+                            <li>
+                                <a href="javascript:change()">修改密码</a>
+                            </li>
+                            <li>
+                                <a href="{:url('admin/login/loginOut')}">安全退出</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="logo-element">AD
+                    </div>
+                </li>
+                <!-- 菜单开始 -->
+                {include file="/menu" /}
+                <!-- 菜单结束 -->
+            </ul>
+        </div>
+    </nav>
+    <!--左侧导航结束-->
+    <!--右侧部分开始-->
+    <div id="page-wrapper" class="gray-bg dashbard-1">
+        <div class="row border-bottom">
+            <nav class="navbar navbar-static-top" role="navigation" style="margin-bottom: 0">
+                <div class="navbar-header"><a class="navbar-minimalize minimalize-styl-2 btn btn-primary " href="#"><i
+                        class="fa fa-bars"></i> </a>
+                </div>
+                <ul class="nav navbar-top-links navbar-right">
+                    <li class="hidden-xs">
+                        <a class="J_menuItem" id="clear"><i class="fa fa-trash"></i>
+                            清理缓存
+                        </a>
+                    </li>
+                    <li class="dropdown hidden-xs">
+                        <a class="right-sidebar-toggle" aria-expanded="false">
+                            <i class="fa fa-tasks"></i> 主题
+                        </a>
+                    </li>
+                </ul>
+            </nav>
+        </div>
+        <div class="row content-tabs">
+            <button class="roll-nav roll-left J_tabLeft"><i class="fa fa-backward"></i>
+            </button>
+            <nav class="page-tabs J_menuTabs">
+                <div class="page-tabs-content">
+                    <a href="javascript:;" class="active J_menuTab" data-id="index_v1.html">首页</a>
+                </div>
+            </nav>
+            <button class="roll-nav roll-right J_tabRight"><i class="fa fa-forward"></i>
+            </button>
+            <div class="btn-group roll-nav roll-right">
+                <button class="dropdown J_tabClose" data-toggle="dropdown">常用操作<span class="caret"></span>
+
+                </button>
+                <ul role="menu" class="dropdown-menu dropdown-menu-right">
+                    <li class="J_tabGo"><a>前进</a>
+                    </li>
+                    <li class="J_tabBack"><a>后退</a>
+                    </li>
+                    <li class="J_tabFresh"><a>刷新</a>
+                    </li>
+                    <li class="divider"></li>
+                    <li class="J_tabShowActive"><a>定位当前选项卡</a>
+                    </li>
+                    <li class="divider"></li>
+                    <li class="J_tabCloseAll"><a>关闭全部选项卡</a>
+                    </li>
+                    <li class="J_tabCloseOther"><a>关闭其他选项卡</a>
+                    </li>
+                </ul>
+            </div>
+            <a href="{:url('admin/login/loginOut')}" class="roll-nav roll-right J_tabExit">
+                <i class="fa fa fa-sign-out"></i>
+                退出
+            </a>
+        </div>
+        <div class="row J_mainContent" id="content-main">
+            <iframe class="J_iframe" name="iframe0" width="100%" height="100%"
+                    src="{:url('Index/indexPage')}" frameborder="0"
+                    data-id="index_v1.html" seamless></iframe>
+        </div>
+        <div class="footer">
+            <div class="pull-right">&copy; 2018-2020 {$version} <a href="http://baiyf.com" target="_blank">baiyf.com</a>
+            </div>
+        </div>
+    </div>
+    <!--右侧部分结束-->
+    <!--右侧边栏开始-->
+    <div id="right-sidebar">
+        <div class="sidebar-container">
+            <ul class="nav nav-tabs navs-3">
+                <li class="active">
+                    <a data-toggle="tab" href="#tab-1">
+                        <i class="fa fa-gear"></i> 主题
+                    </a>
+                </li>
+            </ul>
+            <div class="tab-content">
+                <div id="tab-1" class="tab-pane active">
+                    <div class="sidebar-title">
+                        <h3> <i class="fa fa-comments-o"></i> 主题设置</h3>
+                        <small><i class="fa fa-tim"></i> 你可以从这里选择和预览主题的布局和样式,这些设置会被保存在本地,下次打开的时候会直接应用这些设置。</small>
+                    </div>
+                    <div class="skin-setttings">
+                        <div class="title">主题设置</div>
+                        <div class="setings-item">
+                            <span>收起左侧菜单</span>
+                            <div class="switch">
+                                <div class="onoffswitch">
+                                    <input type="checkbox" name="collapsemenu" class="onoffswitch-checkbox" id="collapsemenu">
+                                    <label class="onoffswitch-label" for="collapsemenu">
+                                        <span class="onoffswitch-inner"></span>
+                                        <span class="onoffswitch-switch"></span>
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="setings-item">
+                            <span>固定顶部</span>
+
+                            <div class="switch">
+                                <div class="onoffswitch">
+                                    <input type="checkbox" name="fixednavbar" class="onoffswitch-checkbox" id="fixednavbar">
+                                    <label class="onoffswitch-label" for="fixednavbar">
+                                        <span class="onoffswitch-inner"></span>
+                                        <span class="onoffswitch-switch"></span>
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="setings-item">
+                                <span>
+                        固定宽度
+                    </span>
+
+                            <div class="switch">
+                                <div class="onoffswitch">
+                                    <input type="checkbox" name="boxedlayout" class="onoffswitch-checkbox" id="boxedlayout">
+                                    <label class="onoffswitch-label" for="boxedlayout">
+                                        <span class="onoffswitch-inner"></span>
+                                        <span class="onoffswitch-switch"></span>
+                                    </label>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="title">皮肤选择</div>
+                        <div class="setings-item default-skin nb">
+                                <span class="skin-name ">
+                         <a href="#" class="s-skin-0">
+                             默认皮肤
+                         </a>
+                    </span>
+                        </div>
+                        <div class="setings-item blue-skin nb">
+                                <span class="skin-name ">
+                        <a href="#" class="s-skin-1">
+                            蓝色主题
+                        </a>
+                    </span>
+                        </div>
+                        <div class="setings-item yellow-skin nb">
+                                <span class="skin-name ">
+                        <a href="#" class="s-skin-3">
+                            黄色/紫色主题
+                        </a>
+                    </span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--右侧边栏结束-->
+    <!--mini聊天窗口开始-->
+</div>
+
+<!-- 修改个人信息 -->
+<div class="ibox-content" id="box" style="display: none">
+    <form class="form-horizontal m-t" method="post" action="{:url('index/changePassword')}" id="editForm">
+        <div class="form-group">
+            <label class="col-sm-3 control-label">旧密码:</label>
+            <div class="input-group col-sm-7">
+                <input id="old_pwd" type="password" class="form-control" name="old_pwd" placeholder="请输入旧密码">
+            </div>
+        </div>
+
+        <div class="form-group">
+            <label class="col-sm-3 control-label">新密码:</label>
+            <div class="input-group col-sm-7">
+                <input id="new_pwc" type="password" class="form-control" name="password" placeholder="请输入新密码">
+            </div>
+        </div>
+
+        <div class="form-group">
+            <div class="col-sm-4 col-sm-offset-8">
+                <button class="btn btn-primary" type="submit">确认修改</button>
+            </div>
+        </div>
+    </form>
+</div>
+<!-- 修改个人信息 -->
+
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/plugins/metisMenu/jquery.metisMenu.js"></script>
+<script src="__JS__/plugins/slimscroll/jquery.slimscroll.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/hplus.min.js?v=4.1.0"></script>
+<script type="text/javascript" src="__JS__/contabs.js"></script>
+<script src="__JS__/plugins/pace/pace.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script>
+    var box;
+    function change(){
+
+
+        layui.use(['layer'], function(){
+            box = layer.ready(function(){
+
+                box = layer.open({
+                    type: 1,
+                    title: '修改个人信息',
+                    anim: 2,
+                    skin: 'layui-layer-molv', //加上边框
+                    area: ['620px', '250px'], //宽高
+                    content: $('#box')
+                });
+            });
+
+        });
+    }
+
+    function showStart(){
+        return true;
+    }
+
+    function showSuccess(res){
+        layui.use(['layer'], function(){
+            layer.ready(function(){
+                if(1 == res.code){
+                    layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                        window.location.href = "{:url('login/loginOut')}";
+                    });
+                }else if(111 == res.code){
+                    window.location.reload();
+                }else{
+                    layer.msg(res.msg, {anim: 6});
+                }
+            });
+        });
+    }
+
+    $(function(){
+
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#editForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+
+        $("#clear").click(function(){
+            $.getJSON("{:url('index/clear')}", function(res){
+                if(1 == res.code){
+                    layer.tips(res.msg, "#clear", {time: 1500});
+                }else{
+                    layer.tips(res.msg, "#clear", {time: 1500});
+                }
+            });
+        });
+
+    });
+
+
+</script>
+</body>
+</html>

+ 191 - 0
application/admin/view/index/index.html

@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>后台首页</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
+    <link href="https://cdn.staticfile.org/font-awesome/4.4.0/css/font-awesome.css?v=4.4.0" rel="stylesheet">
+    <link href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content">
+    <div class="row">
+        <div class="col-sm-2">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <span class="label label-primary pull-right">今天</span>
+                    <h5>正在咨询的人数</h5>
+                </div>
+                <div class="ibox-content">
+                    <h1 class="no-margins">{$data['is_talking']}</h1>
+                    <div class="stat-percent font-bold text-navy"></div>
+                    <small></small>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <span class="label label-primary pull-right">今天</span>
+                    <h5>正在排队的用户</h5>
+                </div>
+                <div class="ibox-content">
+                    <h1 class="no-margins">{$data['in_queue']}</h1>
+                    <div class="stat-percent font-bold text-danger"></div>
+                    <small></small>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <span class="label label-primary pull-right">今天</span>
+                    <h5>当前在线客服数</h5>
+                </div>
+                <div class="ibox-content">
+                    <h1 class="no-margins">{$data['online_kf']}</h1>
+                    <div class="stat-percent font-bold text-danger"></div>
+                    <small></small>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <span class="label label-primary pull-right">今天</span>
+                    <h5>接入会话量</h5>
+                </div>
+                <div class="ibox-content">
+                    <h1 class="no-margins">{$data['success_in']}</h1>
+                    <div class="stat-percent font-bold text-danger"></div>
+                    <small></small>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <span class="label label-primary pull-right">今天</span>
+                    <h5>总会话量</h5>
+                </div>
+                <div class="ibox-content">
+                    <h1 class="no-margins">{$data['total_in']}</h1>
+                    <div class="stat-percent font-bold text-danger"></div>
+                    <small></small>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>今日数据分析</h5>
+                </div>
+                <div class="ibox-content no-padding">
+                    <div class="ibox-content" style="height: 350px" id="main">
+
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-4">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>系统说明</h5>
+                </div>
+                <div class="ibox-content">
+                    <p>whisper客服系统:
+                       为了让更多的站长拥有自己可以控制的客服系统而开发的。
+                    </p>
+                    <div class="alert alert-info">
+                        您可以通过<a href="baiyf.com" target="_blank"> 这里 </a> 查看详细的文档,以及更新说明。
+                    </div>
+                    <p>
+                        whisper采取一次授权终身免费升级的策略。
+                    </p>
+                    <div class="alert alert-info">
+                        作者私人 QQ:876337011。 邮箱:  876337011@qq.com
+                    </div>
+                    <div class="alert alert-danger">
+                        一切未授权就使用的用户,本人保留追究责任的权利!
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
+<script src="https://cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
+<script src="/static/admin/js/plugins/echarts/echarts.min.js"></script>
+<script type="text/javascript">
+    var data = {$show_data};
+    // 基于准备好的dom,初始化echarts实例
+    var myChart = echarts.init(document.getElementById('main'));
+
+    // 指定图表的配置项和数据
+    var option = {
+        tooltip: {
+            trigger: 'axis'
+        },
+        legend: {
+            data:['正在咨询的会员','排队的会员','接入会话量','总会话量']
+        },
+        grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            containLabel: true
+        },
+        toolbox: {
+            feature: {
+                saveAsImage: {}
+            }
+        },
+        xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: ['08:00','09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00',
+                '18:00','19:00','20:00','21:00','22:00']
+        },
+        yAxis: {
+            type: 'value'
+        },
+        series: [
+            {
+                name:'正在咨询的会员',
+                type:'line',
+                stack: '总量',
+                data: data.is_talking
+            },
+            {
+                name:'排队的会员',
+                type:'line',
+                stack: '总量',
+                data: data.in_queue
+            },
+            {
+                name:'接入会话量',
+                type:'line',
+                stack: '总量',
+                data: data.success_in
+            },
+            {
+                name:'总会话量',
+                type:'line',
+                stack: '总量',
+                data: data.total_in
+            }
+        ]
+    };
+
+    // 使用刚指定的配置项和数据显示图表。
+    myChart.setOption(option);
+</script>
+</body>
+</html>

+ 74 - 0
application/admin/view/login/index.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta http-equiv="Pragma" content="no-cache">
+    <meta http-equiv="Cache-Control" content="no-cache">
+    <meta http-equiv="Expires" content="0">
+    <title>whisper {$version} -- 后台登录</title>
+    <link href="__CSS__/login.css" type="text/css" rel="stylesheet">
+</head>
+<body>
+
+<div class="login">
+    <div class="message">whisper {$version}-管理登录</div>
+    <div id="darkbannerwrap"></div>
+    <input name="user_name" placeholder="用户名" type="text" id="user_name">
+    <hr class="hr15">
+    <input name="password" placeholder="密码" type="password" id="password">
+    <hr class="hr15">
+    <input value="登录" style="width:100%;" type="button" id="login">
+    <hr class="hr20">
+</div>
+<div class="copyright">© 2017-2018 whisper {$version} by<a href="http://baiyf.com/" target="_blank"> baiyf.com</a></div>
+<script src="__JS__/jquery.min.js" type="text/javascript"></script>
+<script src="__JS__/layui/layui.js" type="text/javascript"></script>
+
+<script>
+    document.onkeydown=function(event){
+        var e = event || window.event || arguments.callee.caller.arguments[0];
+        if(e && e.keyCode==13){ // enter 键
+            doLogin();
+        }
+    };
+    $(function(){
+
+        $('#login').click(function(){
+            doLogin();
+        });
+    });
+
+    function doLogin(){
+        var user_name = $("#user_name").val();
+        var password = $("#password").val();
+
+        layui.use('layer', function(){
+            var layer = layui.layer;
+
+            layer.ready(function(){
+                if('' == user_name){
+                    layer.tips('用户名不能为空', '#user_name');
+                    return false;
+                }
+
+                if('' == password){
+                    layer.tips('密码不能为空', '#password');
+                    return false;
+                }
+
+                var index = layer.load(0, {shade: false});
+                $.post("{:url('login/doLogin')}", {user_name: user_name, password: password}, function(res){
+                    layer.close(index);
+                    if(1 == res.code){
+                        layer.msg(res.msg);
+                        window.location.href = res.data;
+                    }else{
+                        return layer.msg(res.msg, {anim: 6, time: 1000});
+                    }
+                }, 'json');
+            });
+        });
+    }
+</script>
+</body>
+</html>

+ 103 - 0
application/admin/view/menu.html

@@ -0,0 +1,103 @@
+<li class="menu">
+    <a href="#">
+        <i class="fa fa-qq"></i>
+        <span class="nav-label">客服管理</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('users/index')}">客服列表</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('users/addUser')}">添加客服</a>
+        </li>
+    </ul>
+</li>
+<li class="menu">
+    <a href="#">
+        <i class="fa fa-group"></i>
+        <span class="nav-label">分组管理</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('groups/index')}">分组列表</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('groups/addGroup')}">添加分组</a>
+        </li>
+    </ul>
+</li>
+<li class="menu">
+    <a href="#">
+        <i class="fa fa-user"></i>
+        <span class="nav-label">管理员管理</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('admins/index')}">管理员列表</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('admins/addAdmin')}">添加管理员</a>
+        </li>
+    </ul>
+</li>
+<li class="menu">
+    <a href="#">
+        <i class="fa fa-comments-o"></i>
+        <span class="nav-label">常用语管理</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('words/index')}">常用语列表</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('words/addword')}">添加常用语</a>
+        </li>
+    </ul>
+</li>
+<!--<li class="menu">
+    <a href="#">
+        <i class="fa fa-android"></i>
+        <span class="nav-label">机器应答管理</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('words/index')}">应答列表</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('words/addword')}">添加应答</a>
+        </li>
+    </ul>
+</li>-->
+<li class="menu">
+    <a href="#">
+        <i class="fa fa-gear"></i>
+        <span class="nav-label">系统设置</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('system/reply')}">自动回复设置</a>
+        </li>
+        <li>
+            <a class="J_menuItem" href="{:url('system/customerService')}">客服设置</a>
+        </li>
+    </ul>
+</li>
+
+<li class="menu">
+    <a href="#">
+        <i class="fa fa fa-bar-chart-o"></i>
+        <span class="nav-label">历史会话记录</span>
+        <span class="fa arrow"></span>
+    </a>
+    <ul class="nav nav-second-level">
+        <li>
+            <a class="J_menuItem" href="{:url('system/wordsLog')}">历史会话</a>
+        </li>
+    </ul>
+</li>

+ 120 - 0
application/admin/view/system/customerservice.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>客服设置</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>客服设置</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('system/customerService')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">每个客服最大服务人数:</label>
+                            <div class="input-group col-sm-4">
+                                <input type="number" class="form-control" name="max_service" required="" aria-required="true" value="{$config['max_service']}">
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用转接:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="change_status" value="{$key}" title="{$vo}" {if $key eq $config['change_status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+                layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                    window.location.href = res.data;
+                });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 120 - 0
application/admin/view/system/reply.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>自动回复设置</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>自动回复设置</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('system/reply')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">回复内容:</label>
+                            <div class="input-group col-sm-4">
+                                <textarea class="form-control" name="word" required="" aria-required="true" style="width: 400px;height: 150px;resize:none">{$info['word']}</textarea>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq $info['status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+                layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                    window.location.href = res.data;
+                });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 153 - 0
application/admin/view/system/wordslog.html

@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>历史会话</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <!-- Panel Other -->
+    <div class="ibox float-e-margins">
+        <div class="ibox-title">
+            <h5>历史会话</h5>
+        </div>
+        <div class="ibox-content">
+            <!--搜索框开始-->
+            <form id='commentForm' role="form" method="post" class="form-inline pull-right">
+                <div class="content clearfix m-b">
+                    <div class="form-group">
+                        <label>聊天日期:</label>
+                        <input type="text" class="form-control" id="start" placeholder="开始日期"> --
+                        <input type="text" class="form-control" id="end" placeholder="结束日期">
+                    </div>
+                    &nbsp;&nbsp;
+                    <div class="form-group">
+                        <label>客服名称:</label>
+                        <input type="text" class="form-control" id="username" name="user_name">
+                    </div>
+                    <div class="form-group">
+                        <button class="btn btn-primary" type="button" style="margin-top:5px" id="search">
+                            <strong>搜 索</strong>
+                        </button>
+                    </div>
+                </div>
+            </form>
+            <!--搜索框结束-->
+            <div class="example-wrap">
+                <div class="example">
+                    <table id="cusTable">
+                        <thead>
+                        <th data-field="from_name">发送者</th>
+                        <th data-field="to_name">接受者</th>
+                        <th data-field="content">会话内容</th>
+                        <th data-field="time_line">会话时间</th>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <!-- End Example Pagination -->
+        </div>
+    </div>
+</div>
+<!-- End Panel Other -->
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script type="text/javascript">
+    function initTable() {
+        //先销毁表格
+        $('#cusTable').bootstrapTable('destroy');
+        //初始化表格,动态从服务器加载数据
+        $("#cusTable").bootstrapTable({
+            method: "get",  //使用get请求到服务器获取数据
+            url: "{:url('system/wordsLog')}", //获取数据的地址
+            striped: true,  //表格显示条纹
+            pagination: true, //启动分页
+            pageSize: 15,  //每页显示的记录数
+            pageNumber:1, //当前第几页
+            pageList: [5, 10, 15, 20, 25],  //记录数可选列表
+            sidePagination: "server", //表示服务端请求
+            paginationFirstText: "首页",
+            paginationPreText: "上一页",
+            paginationNextText: "下一页",
+            paginationLastText: "尾页",
+            queryParamsType : "undefined",
+            queryParams: function queryParams(params) {   //设置查询参数
+                var param = {
+                    pageNumber: params.pageNumber,
+                    pageSize: params.pageSize,
+                    searchText:$('#username').val(),
+                    start:$('#start').val(),
+                    end: $('#end').val()
+                };
+                return param;
+            },
+            onLoadSuccess: function(res){  //加载成功时执行
+                if(111 == res.code){
+                    window.location.reload();
+                }
+                layer.msg("加载成功", {time : 1000});
+            },
+            onLoadError: function(){  //加载失败时执行
+                layer.msg("加载数据失败");
+            }
+        });
+    }
+
+    $(document).ready(function () {
+        // 调用函数,初始化表格
+        initTable();
+
+        // 当点击查询按钮的时候执行
+        $("#search").bind("click", function(){
+            var start = $('#start').val();
+            var end = $('#end').val();
+
+            if('' == start){
+                layer.tips("输入开始时间", '#start');
+                return false;
+            }
+
+            if('' == end){
+                layer.tips("输入结束时间", '#end');
+                return false;
+            }
+
+            if(start > end){
+                layer.msg('开始时间不能大于结束时间');
+                return false;
+            }
+
+            initTable();
+        });
+    });
+
+    layui.use('laydate', function(){
+        var laydate = layui.laydate;
+        laydate.render({
+            elem: '#start'
+            ,type: 'date'
+        });
+
+        laydate.render({
+            elem: '#end'
+            ,type: 'date'
+        });
+    });
+
+</script>
+</body>
+</html>

+ 175 - 0
application/admin/view/users/adduser.html

@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>添加客服</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>添加客服</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('users/adduser')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">客服名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="username" type="text" class="form-control" name="user_name" required="" aria-required="true">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">登录密码:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="password" type="text" class="form-control" name="user_pwd" required="" aria-required="true">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">选择分组:</label>
+                            <input type="hidden" id="group_id" name="group_id"/>
+                            <div class="input-group col-sm-4 layui-form">
+                                <select lay-verify="required" lay-filter="group">
+                                    <option value="">请选择分组</option>
+                                    {if !empty($groups)}
+                                    {foreach name="groups" item="vo"}
+                                    <option value="{$vo['id']}">{$vo['name']}</option>
+                                    {/foreach}
+                                    {/if}
+                                </select>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq 1}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item form-inline" style="height: 60px;">
+                            <input type="hidden" name="user_avatar" id="user_avatar"/>
+                            <label class="col-sm-3 control-label">客服头像:</label>
+                            <div class="input-group col-sm-2">
+                                <button type="button" class="layui-btn layui-btn-small" id="up-avatar">
+                                    <i class="layui-icon"></i>上传图片</button>
+                            </div>
+                            <div class="input-group col-sm-3" id="avatar">
+
+                            </div>
+                        </div>
+
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+        var upload = layui.upload;
+        //执行实例
+        var uploadInst = upload.render({
+            elem: '#up-avatar' //绑定元素
+            ,url: "{:url('users/upAvatar')}" //上传接口
+            ,exts: 'png|jpg|jpeg|gif'
+            ,done: function(res){
+                //上传完毕回调
+                if(0 == res.code){
+                    $("#avatar").html('<img src="' + res.data.src + '" width="50px" height="50px">');
+                    $("#user_avatar").val(res.data.src);
+                }else{
+                    layer.msg(res.msg);
+                }
+            }
+            ,error: function(){
+                //请求异常回调
+            }
+        });
+
+        form.on('select(group)', function(value){
+            $("#group_id").val(value.value);
+        });
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 175 - 0
application/admin/view/users/edituser.html

@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>编辑客服</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>编辑客服</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('users/edituser')}">
+                        <input type="hidden" value="{$info['id']}" name="id"/>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">客服名称:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="username" type="text" class="form-control" name="user_name" required="" aria-required="true" value="{$info['user_name']}">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">登录密码:</label>
+                            <div class="input-group col-sm-4">
+                                <input id="password" type="text" class="form-control" name="user_pwd" placeholder="重新输入则为修改">
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">选择分组:</label>
+                            <input type="hidden" id="group_id" name="group_id" value="{$info['group_id']}"/>
+                            <div class="input-group col-sm-4 layui-form">
+                                <select lay-verify="required" lay-filter="group">
+                                    <option value="">请选择分组</option>
+                                    {if !empty($groups)}
+                                    {foreach name="groups" item="vo"}
+                                    <option value="{$vo['id']}" {if $vo['id'] eq $info['group_id']}selected{/if}>{$vo['name']}</option>
+                                    {/foreach}
+                                    {/if}
+                                </select>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq $info['status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item form-inline" style="height: 60px;">
+                            <input type="hidden" name="user_avatar" id="user_avatar"/>
+                            <label class="col-sm-3 control-label">客服头像:</label>
+                            <div class="input-group col-sm-2">
+                                <button type="button" class="layui-btn layui-btn-small" id="up-avatar">
+                                    <i class="layui-icon"></i>上传图片</button>
+                            </div>
+                            <div class="input-group col-sm-3" id="avatar">
+                                <img src="{$info['user_avatar']}" width="40px" height="40px"/>
+                            </div>
+                        </div>
+
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+        var upload = layui.upload;
+        //执行实例
+        var uploadInst = upload.render({
+            elem: '#up-avatar' //绑定元素
+            ,url: "{:url('users/upAvatar')}" //上传接口
+            ,exts: 'png|jpg|jpeg|gif'
+            ,done: function(res){
+                //上传完毕回调
+                if(0 == res.code){
+                    $("#avatar").html('<img src="' + res.data.src + '" width="50px" height="50px">');
+                    $("#user_avatar").val(res.data.src);
+                }else{
+                    layer.msg(res.msg);
+                }
+            }
+            ,error: function(){
+                //请求异常回调
+            }
+        });
+
+        form.on('select(group)', function(value){
+            $("#group_id").val(value.value);
+        });
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 131 - 0
application/admin/view/users/index.html

@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>客服列表</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <!-- Panel Other -->
+    <div class="ibox float-e-margins">
+        <div class="ibox-title">
+            <h5>客服列表</h5>
+        </div>
+        <div class="ibox-content">
+            <!--搜索框开始-->
+            <form id='commentForm' role="form" method="post" class="form-inline pull-right">
+                <div class="content clearfix m-b">
+                    <div class="form-group">
+                        <label>客服名称:</label>
+                        <input type="text" class="form-control" id="username" name="user_name">
+                    </div>
+                    <div class="form-group">
+                        <button class="btn btn-primary" type="button" style="margin-top:5px" id="search"><strong>搜 索</strong>
+                        </button>
+                    </div>
+                </div>
+            </form>
+            <!--搜索框结束-->
+            <div class="example-wrap">
+                <div class="example">
+                    <table id="cusTable">
+                        <thead>
+                        <th data-field="id">客服ID</th>
+                        <th data-field="user_name">客服名称</th>
+                        <th data-field="user_avatar">客服头像</th>
+                        <th data-field="group">所属组别</th>
+                        <th data-field="status">客服状态</th>
+                        <!--<th data-field="online">是否在线</th>-->
+                        <th data-field="operate">操作</th>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <!-- End Example Pagination -->
+        </div>
+    </div>
+</div>
+<!-- End Panel Other -->
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script type="text/javascript">
+    function initTable() {
+        //先销毁表格
+        $('#cusTable').bootstrapTable('destroy');
+        //初始化表格,动态从服务器加载数据
+        $("#cusTable").bootstrapTable({
+            method: "get",  //使用get请求到服务器获取数据
+            url: "{:url('users/index')}", //获取数据的地址
+            striped: true,  //表格显示条纹
+            pagination: true, //启动分页
+            pageSize: 10,  //每页显示的记录数
+            pageNumber:1, //当前第几页
+            pageList: [5, 10, 15, 20, 25],  //记录数可选列表
+            sidePagination: "server", //表示服务端请求
+            paginationFirstText: "首页",
+            paginationPreText: "上一页",
+            paginationNextText: "下一页",
+            paginationLastText: "尾页",
+            queryParamsType : "undefined",
+            queryParams: function queryParams(params) {   //设置查询参数
+                var param = {
+                    pageNumber: params.pageNumber,
+                    pageSize: params.pageSize,
+                    searchText:$('#username').val()
+                };
+                return param;
+            },
+            onLoadSuccess: function(res){  //加载成功时执行
+                if(111 == res.code){
+                    window.location.reload();
+                }
+                layer.msg("加载成功", {time : 1000});
+            },
+            onLoadError: function(){  //加载失败时执行
+                layer.msg("加载数据失败");
+            }
+        });
+    }
+
+    $(document).ready(function () {
+        //调用函数,初始化表格
+        initTable();
+
+        //当点击查询按钮的时候执行
+        $("#search").bind("click", initTable);
+    });
+
+    function userDel(id){
+        layer.confirm('确认删除此客服?', {icon: 3, title:'提示'}, function(index){
+            //do something
+            $.getJSON("{:url('users/delUser')}", {'id' : id}, function(res){
+                if(1 == res.code){
+                    layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                        initTable();
+                    });
+                }else if(111 == res.code){
+                    window.location.reload();
+                }else{
+                    layer.alert(res.msg, {title: '友情提示', icon: 2});
+                }
+            });
+
+            layer.close(index);
+        })
+
+    }
+</script>
+</body>
+</html>

+ 120 - 0
application/admin/view/words/addword.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>添加常用语</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>添加常用语</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('words/addword')}">
+
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">常用语内容:</label>
+                            <div class="input-group col-sm-4">
+                                <textarea class="form-control" name="content" required="" aria-required="true" style="width: 400px;height: 150px;resize:none"></textarea>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq 1}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+               layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                   window.location.href = res.data;
+               });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 120 - 0
application/admin/view/words/editword.html

@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>编辑常用语</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__JS__/layui/css/layui.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-8">
+            <div class="ibox float-e-margins">
+                <div class="ibox-title">
+                    <h5>编辑常用语</h5>
+                </div>
+                <div class="ibox-content">
+                    <form class="form-horizontal m-t layui-form" id="commentForm" method="post" action="{:url('words/editWord')}">
+                        <input type="hidden" name="id" value="{$info['id']}"/>
+                        <div class="form-group">
+                            <label class="col-sm-3 control-label">常用语内容:</label>
+                            <div class="input-group col-sm-4">
+                                <textarea class="form-control" name="content" required="" aria-required="true" style="width: 400px;height: 150px;resize:none">{$info['content']}</textarea>
+                            </div>
+                        </div>
+                        <div class="form-group layui-form-item">
+                            <label class="col-sm-3 control-label">是否启用:</label>
+                            <div class="input-group col-sm-6">
+                                {if !empty($status)}
+                                {foreach name="status" item="vo" key="key"}
+                                <input type="radio" name="status" value="{$key}" title="{$vo}" {if $key eq $info['status']}checked{/if}>
+                                {/foreach}
+                                {/if}
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <div class="col-sm-4 col-sm-offset-6">
+                                <button class="btn btn-primary" type="submit">提交</button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/validate/jquery.validate.min.js"></script>
+<script src="__JS__/plugins/validate/messages_zh.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script src="__JS__/layui/layui.js"></script>
+<script src="__JS__/jquery.form.js"></script>
+<script type="text/javascript">
+
+    layui.use(['form', 'upload'], function(){
+        var form = layui.form;
+    });
+
+    var index = '';
+    function showStart(){
+        index = layer.load(0, {shade: false});
+        return true;
+    }
+
+    function showSuccess(res){
+
+        layer.ready(function(){
+            layer.close(index);
+            if(1 == res.code){
+                layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                    window.location.href = res.data;
+                });
+            }else if(111 == res.code){
+                window.location.reload();
+            }else{
+                layer.msg(res.msg, {anim: 6});
+            }
+        });
+    }
+
+    $(document).ready(function(){
+        // 添加管理员
+        var options = {
+            beforeSubmit:showStart,
+            success:showSuccess
+        };
+
+        $('#commentForm').submit(function(){
+            $(this).ajaxSubmit(options);
+            return false;
+        });
+    });
+
+    // 表单验证
+    $.validator.setDefaults({
+        highlight: function(e) {
+            $(e).closest(".form-group").removeClass("has-success").addClass("has-error")
+        },
+        success: function(e) {
+            e.closest(".form-group").removeClass("has-error").addClass("has-success")
+        },
+        errorElement: "span",
+        errorPlacement: function(e, r) {
+            e.appendTo(r.is(":radio") || r.is(":checkbox") ? r.parent().parent().parent() : r.parent())
+        },
+        errorClass: "help-block m-b-none",
+        validClass: "help-block m-b-none"
+    });
+
+</script>
+</body>
+</html>

+ 129 - 0
application/admin/view/words/index.html

@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>常用语列表</title>
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
+    <link href="__CSS__/font-awesome.min.css?v=4.4.0" rel="stylesheet">
+    <link href="__CSS__/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
+    <link href="__CSS__/animate.min.css" rel="stylesheet">
+    <link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
+</head>
+<body class="gray-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <!-- Panel Other -->
+    <div class="ibox float-e-margins">
+        <div class="ibox-title">
+            <h5>常用语列表</h5>
+        </div>
+        <div class="ibox-content">
+            <!--搜索框开始-->
+            <form id='commentForm' role="form" method="post" class="form-inline pull-right">
+                <div class="content clearfix m-b">
+                    <div class="form-group">
+                        <label>常用语内容:</label>
+                        <input type="text" class="form-control" id="content" name="content">
+                    </div>
+                    <div class="form-group">
+                        <button class="btn btn-primary" type="button" style="margin-top:5px" id="search"><strong>搜 索</strong>
+                        </button>
+                    </div>
+                </div>
+            </form>
+            <!--搜索框结束-->
+            <div class="example-wrap">
+                <div class="example">
+                    <table id="cusTable">
+                        <thead>
+                        <th data-field="id">内容ID</th>
+                        <th data-field="content">内容</th>
+                        <th data-field="add_time">添加时间</th>
+                        <th data-field="status">状态</th>
+                        <th data-field="operate">操作</th>
+                        </thead>
+                    </table>
+                </div>
+            </div>
+            <!-- End Example Pagination -->
+        </div>
+    </div>
+</div>
+<!-- End Panel Other -->
+<script src="__JS__/jquery.min.js?v=2.1.4"></script>
+<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
+<script src="__JS__/content.min.js?v=1.0.0"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
+<script src="__JS__/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
+<script src="__JS__/plugins/layer/layer.min.js"></script>
+<script type="text/javascript">
+    function initTable() {
+        //先销毁表格
+        $('#cusTable').bootstrapTable('destroy');
+        //初始化表格,动态从服务器加载数据
+        $("#cusTable").bootstrapTable({
+            method: "get",  //使用get请求到服务器获取数据
+            url: "{:url('words/index')}", //获取数据的地址
+            striped: true,  //表格显示条纹
+            pagination: true, //启动分页
+            pageSize: 10,  //每页显示的记录数
+            pageNumber:1, //当前第几页
+            pageList: [5, 10, 15, 20, 25],  //记录数可选列表
+            sidePagination: "server", //表示服务端请求
+            paginationFirstText: "首页",
+            paginationPreText: "上一页",
+            paginationNextText: "下一页",
+            paginationLastText: "尾页",
+            queryParamsType : "undefined",
+            queryParams: function queryParams(params) {   //设置查询参数
+                var param = {
+                    pageNumber: params.pageNumber,
+                    pageSize: params.pageSize,
+                    searchText:$('#content').val()
+                };
+                return param;
+            },
+            onLoadSuccess: function(res){  //加载成功时执行
+                if(111 == res.code){
+                    window.location.reload();
+                }
+                layer.msg("加载成功", {time : 1000});
+            },
+            onLoadError: function(){  //加载失败时执行
+                layer.msg("加载数据失败");
+            }
+        });
+    }
+
+    $(document).ready(function () {
+        //调用函数,初始化表格
+        initTable();
+
+        //当点击查询按钮的时候执行
+        $("#search").bind("click", initTable);
+    });
+
+    function userDel(id){
+        layer.confirm('确认删除此常用语?', {icon: 3, title:'提示'}, function(index){
+            //do something
+            $.getJSON("{:url('words/delWord')}", {'id' : id}, function(res){
+                if(1 == res.code){
+                    layer.alert(res.msg, {title: '友情提示', icon: 1, closeBtn: 0}, function(){
+                        initTable();
+                    });
+                }else if(111 == res.code){
+                    window.location.reload();
+                }else{
+                    layer.alert(res.msg, {title: '友情提示', icon: 2});
+                }
+            });
+
+            layer.close(index);
+        })
+
+    }
+</script>
+</body>
+</html>

+ 12 - 0
application/command.php

@@ -0,0 +1,12 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+return [];

+ 37 - 0
application/common.php

@@ -0,0 +1,37 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 流年 <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用公共文件
+/**
+ * 删除目录以及其下的文件
+ * @param $directory
+ * @return bool
+ */
+function removeDir($directory)
+{
+    if (false == is_dir($directory)) {
+        return false;
+    }
+
+    $handle = opendir($directory);
+    while (false !== ($file = readdir($handle))) {
+        if ('.' != $file && '..' != $file) {
+            is_dir("$directory/$file") ? removeDir("$directory/$file") : @unlink("$directory/$file");
+        }
+    }
+
+    if (readdir($handle) == false) {
+        closedir($handle);
+        rmdir($directory);
+    }
+
+    return true;
+}

+ 35 - 0
application/config.php

@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    // +----------------------------------------------------------------------
+    // | 应用设置
+    // +----------------------------------------------------------------------
+
+    // 当前系统版本
+    'version' => 'v1.0.1',
+
+    // 加密盐
+    'salt' => '~NickBai!@#',
+
+    // socket server
+    'socket' => '192.168.2.186:8282',
+
+    // 管理员登录时间
+    'save_time' => 86400,
+
+    // 应用命名空间
+    'app_namespace'          => 'app',
+    // 应用调试模式
+    'app_debug'              => true,
+    // 应用Trace
+    'app_trace'              => false
+];

+ 53 - 0
application/database.php

@@ -0,0 +1,53 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    // 数据库类型
+    'type'            => 'mysql',
+    // 服务器地址
+    'hostname'        => '192.168.2.186',
+    // 数据库名
+    'database'        => 'customer_service',
+    // 用户名
+    'username'        => 'root',
+    // 密码
+    'password'        => '',
+    // 端口
+    'hostport'        => '3306',
+    // 连接dsn
+    'dsn'             => '',
+    // 数据库连接参数
+    'params'          => [],
+    // 数据库编码默认采用utf8
+    'charset'         => 'utf8',
+    // 数据库表前缀
+    'prefix'          => 'ws_',
+    // 数据库调试模式
+    'debug'           => true,
+    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+    'deploy'          => 0,
+    // 数据库读写是否分离 主从式有效
+    'rw_separate'     => false,
+    // 读写分离后 主服务器数量
+    'master_num'      => 1,
+    // 指定从服务器序号
+    'slave_no'        => '',
+    // 是否严格检查字段是否存在
+    'fields_strict'   => true,
+    // 数据集返回类型
+    'resultset_type'  => 'array',
+    // 自动写入时间戳字段
+    'auto_timestamp'  => false,
+    // 时间字段取出后的默认时间格式
+    'datetime_format' => 'Y-m-d H:i:s',
+    // 是否需要进行SQL性能分析
+    'sql_explain'     => false,
+];

+ 1 - 0
application/index/common.php

@@ -0,0 +1 @@
+<?php

+ 5 - 0
application/index/config.php

@@ -0,0 +1,5 @@
+<?php
+//配置文件
+return [
+
+];

+ 51 - 0
application/index/controller/Index.php

@@ -0,0 +1,51 @@
+<?php
+namespace app\index\controller;
+
+use think\Controller;
+
+class Index extends Controller
+{
+    public function index()
+    {
+        return $this->fetch();
+    }
+
+    // pc客户端
+    public function chat()
+    {
+        // 跳转到移动端
+        if(request()->isMobile()){
+            $param = http_build_query([
+                'id' => input('param.id'),
+                'name' => input('param.name'),
+                'group' => input('param.group'),
+                'avatar' => input('param.avatar')
+            ]);
+            $this->redirect('/index/index/mobile?' . $param);
+        }
+
+        $this->assign([
+            'socket' => config('socket'),
+            'id' => input('param.id'),
+            'name' => input('param.name'),
+            'group' => input('param.group'),
+            'avatar' => input('param.avatar'),
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 移动客户端
+    public function mobile()
+    {
+        $this->assign([
+            'socket' => config('socket'),
+            'id' => input('param.id'),
+            'name' => input('param.name'),
+            'group' => input('param.group'),
+            'avatar' => input('param.avatar'),
+        ]);
+
+        return $this->fetch();
+    }
+}

+ 70 - 0
application/index/controller/Upload.php

@@ -0,0 +1,70 @@
+<?php
+namespace app\index\controller;
+
+use think\Controller;
+
+class Upload extends Controller
+{
+
+    //上传图片
+    public function uploadImg()
+    {
+        $file = request()->file('file');
+
+        $fileInfo = $file->getInfo();
+        /*if($fileInfo['size'] > 1024 * 1024 * 2){
+            // 上传失败获取错误信息
+            return json( ['code' => -2, 'data' => '', 'msg' => '文件超过2M'] );
+        }*/
+
+        //检测图片格式
+        $ext = explode('.', $fileInfo['name']);
+        $ext = array_pop($ext);
+
+        $extArr = explode('|', 'jpg|png|gif|jpeg');
+        if(!in_array($ext, $extArr)){
+            return json(['code' => -3, 'data' => '', 'msg' => '只能上传jpg|png|gif|jpeg的文件']);
+        }
+
+        // 移动到框架应用根目录/public/uploads/ 目录下
+        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
+        if($info){
+            $src =  '/uploads' . '/' . date('Ymd') . '/' . $info->getFilename();
+            return json(['code' => 0, 'data' => ['src' => $src ], 'msg' => '']);
+        }else{
+            // 上传失败获取错误信息
+            return json(['code' => -1, 'data' => '', 'msg' => $file->getError()]);
+        }
+    }
+
+    //上传文件
+    public function uploadFile()
+    {
+        $file = request()->file('file');
+
+        $fileInfo = $file->getInfo();
+        /*if($fileInfo['size'] > 1024*1024*2){
+            // 上传失败获取错误信息
+            return json( ['code' => -2, 'data' => '', 'msg' => '文件超过2M'] );
+        }*/
+
+        // 检测文件格式
+        $ext = explode('.', $fileInfo['name']);
+        $ext = array_pop($ext);
+
+        $extArr = explode('|', 'zip|rar');
+        if(!in_array($ext, $extArr)){
+            return json(['code' => -3, 'data' => '', 'msg' => '只能上传zip|rar的文件']);
+        }
+
+        // 移动到框架应用根目录/public/uploads/ 目录下
+        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
+        if($info){
+            $src =  '/uploads' . '/' . date('Ymd') . '/' . $info->getFilename();
+            return json(['code' => 0, 'data' => ['src' => $src ], 'msg' => $fileInfo['name']]);
+        }else{
+            // 上传失败获取错误信息
+            return json(['code' => -1, 'data' => '', 'msg' => $file->getError()]);
+        }
+    }
+}

+ 54 - 0
application/index/view/index/chat.html

@@ -0,0 +1,54 @@
+<html>
+<head>
+    <title>whisper客服客户端</title>
+    <meta charset="utf-8"/>
+    <link href="/static/customer/css/whisper.css" rel="stylesheet" type="text/css" />
+    <link href="/static/service/js/layui/css/layui.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div class="small-chat-box fadeInRight animated">
+    <div class="heading" draggable="true">
+        <small class="chat-date pull-right">
+            <a href="http://whisper.baiyf.com" target="_blank">whisper客服</a>
+        </small>
+        <span id="title">与 客服 交流中</span>
+    </div>
+    <div class="slimScrollDiv" style="position: relative; width: auto; height: 60%;" id="chat-content-box">
+        <div class="content" style="width: auto; height: 92%;overflow: auto" id="chat-list">
+
+        </div>
+    </div>
+    <div class="bar">
+        <div style="padding-left:15px;cursor:pointer;width:100px">
+            <i class="layui-icon" style="font-size: 30px;" id="up-face">&#xe60c;</i> &nbsp;
+            <i class="layui-icon" style="font-size: 30px;" id="up-image">&#xe60d;</i>
+        </div>
+    </div>
+    <div class="form-chat">
+        <textarea class="form-control chat-area" id="msg" style="width: 100%;height: 100px;resize: none"></textarea>
+        <div class="input-group" style="margin-top: 5px;width: 100%">
+            <span class="input-group-btn" style="float: right;">
+                <button class="btn btn-primary" type="button" id="send">发送</button>
+            </span>
+        </div>
+    </div>
+
+    <div class="face-box" style="display:none" id="face-box">
+
+    </div>
+</div>
+<script>
+    var config = {
+        uid: '{$id}',
+        name: '{$name}',
+        avatar: '{$avatar}',
+        group: '{$group}',
+        socket: '{$socket}'
+    };
+</script>
+<script src="/static/service/js/layui/layui.js"></script>
+<script src="/static/service/js/functions.js"></script>
+<script src="/static/customer/js/whisper-cli.js"></script>
+
+</body>
+</html>

+ 331 - 0
application/index/view/index/index.html

@@ -0,0 +1,331 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>whisper客服测试页面</title>
+    <meta name="keywords" content="whisper客服">
+    <meta name="description" content="whisper客服">
+    <link rel="shortcut icon" href="favicon.ico">
+    <link href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
+    <link href="https://cdn.staticfile.org/font-awesome/4.4.0/css/font-awesome.css?v=4.4.0" rel="stylesheet">
+    <link href="https://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/static/admin/css/style.min.css?v=4.1.0" rel="stylesheet">
+    <link href="/static/demo/css/common.css" rel="stylesheet"  />
+</head>
+
+<body class="gray-bg">
+<div class="row">
+    <div class="wrapper wrapper-content animated fadeInRight">
+        <div class="row">
+            <div class="col-sm-12">
+                <div class="wrapper wrapper-content animated fadeInRight">
+
+                    <div class="ibox-content m-b-sm border-bottom">
+                        <div class="p-xs">
+                            <div class="pull-left m-r-md">
+                                <i class="fa fa-globe text-navy mid-icon"></i>
+                            </div>
+                            <h2><a href="http://whisper.baiyf.com">欢迎来到whisper商城</a></h2>
+                            <span>你可以自由选择你感兴趣的商品。</span>
+                        </div>
+                    </div>
+
+                    <div class="ibox-content forum-container">
+
+                        <div class="forum-title">
+                            <div class="pull-right forum-desc">
+                                <samll>总商品数: 320,800</samll>
+                            </div>
+                        </div>
+                        <div class="row">
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                            <div class="col-sm-4">
+                                <div class="contact-box">
+                                    <a href="javascript:;">
+                                        <div class="col-sm-4">
+                                            <div class="text-center">
+                                                <img alt="image" class="img-circle m-t-xs img-responsive" style="border-radius:0" src="//img13.360buyimg.com/n1/s450x450_jfs/t10675/253/1344769770/66891/92d54ca4/59df2e7fN86c99a27.jpg">
+                                            </div>
+                                        </div>
+                                    </a>
+                                    <div class="col-sm-8">
+                                        <a href="javascript:;">
+                                            <h2><strong>Apple iPhone X (A1865) 64GB 深空灰色 移动联通电信4G手机</strong></h2>
+                                            <h3 style="color: #e4393c">领券12期免息,每月仅需699!勾选[保障服务][原厂保2年],原厂延保更安心。点此领券
+                                                选下方移动联通电信优惠购,下单立减550元!</h3>
+                                            <h2>售价 <span style="color: red">¥8388.00</span></h2>
+                                        </a>
+                                    </div>
+                                    <div class="clearfix"></div>
+                                </div>
+                            </div>
+
+                        </div>
+
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 右侧悬浮 -->
+<div id="service">
+    <ul>
+        <li>
+            <a href="javascript:;">
+                <div class="hides" style="width:161px;display:none;">
+                    <div class="hides p1">
+                        <img src="/static/demo/images/ll04.png">
+                    </div>
+                    <div class="hides p2" id="buy-1" data-group="1">
+                        <span style="color:#FFF;font-size:13px">售前客服</span>
+                    </div>
+                </div>
+                <img src="/static/demo/images/l04.png" width="47" height="49" class="shows" />
+            </a>
+        </li>
+
+        <li>
+            <a href="javascript:;">
+                <div class="hides" style="width:161px;display:none;">
+                    <div class="hides p1">
+                        <img src="/static/demo/images/ll04.png">
+                    </div>
+                    <div class="hides p2" id="buy-2" data-group="2">
+                        <span style="color:#FFF;font-size:13px">售后客服</span>
+                    </div>
+                </div>
+                <img src="/static/demo/images/l04.png" width="47" height="49" class="shows" />
+            </a>
+        </li>
+
+        <li id="btn">
+            <a id="top_btn">
+                <div class="hides" style="width:161px;display:none">
+                    <img src="/static/demo/images/ll06.png" width="161" height="49" />
+                </div>
+                <img src="/static/demo/images/l06.png" width="47" height="49" class="shows" />
+            </a>
+        </li>
+    </ul>
+</div>
+<!-- 右侧悬浮 -->
+
+<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
+<script src="https://cdn.bootcss.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
+<script src="/static/admin/js/content.min.js?v=1.0.0"></script>
+<script src="/static/customer/js/layer/layer.js"></script>
+<script src="/static/customer/js/whisper-tool.js"></script>
+
+<script type="text/javascript">
+    $(function() {
+        $("#service a").hover(function() {
+            if ($(this).prop("className") == "weixin_area") {
+                $(this).children("img.hides").show();
+            } else {
+                $(this).children("div.hides").show();
+                $(this).children("img.shows").hide();
+                $(this).children("div.hides").animate({marginRight: '0px'}, '0');
+            }
+        }, function() {
+            if ($(this).prop("className") == "weixin_area") {
+                $(this).children("img.hides").hide();
+            } else {
+                $(this).children("div.hides").animate({marginRight: '-163px'}, 0, function() {
+                    $(this).hide();
+                    $(this).next("img.shows").show();
+                });
+            }
+        });
+
+        var ws = new whisper();
+        var uid = parseInt(Math.random() * 40) + 1;
+
+        $("#buy-1,#buy-2").click(function(){
+            var group = $(this).attr('data-group');
+            ws.init({
+                id: uid,
+                url: '/',
+                name: '会员' + uid,
+                avatar: 'http://wx2.sinaimg.cn/mw690/5db11ff4gy1flxmew7edlj203d03wt8n.jpg',
+                group: group
+            });
+        });
+
+        $("#top_btn").click(function() {
+            $("html,body").animate({scrollTop: 0}, 600);
+        });
+    });
+</script>
+</body>
+</html>

+ 58 - 0
application/index/view/index/mobile.html

@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <title>whisper客服移动端</title>
+    <link href="/static/service/js/layui/css/layui.mobile.css" rel="stylesheet"/>
+    <link href="/static/service/js/layui/css/modules/layim/mobile/layim.css" rel="stylesheet"/>
+</head>
+<body>
+<div class="layim-panel layui-m-anim-left">
+    <div class="layim-title" style="background-color: #36373C;">
+        <p><i class="layui-icon layim-chat-back" onclick="loginOut()">&#xe603;</i> <span id="title">客服</span></p>
+    </div>
+    <div class="layui-unselect layim-content">
+        <div class="layim-chat layim-chat-friend">
+            <div class="layim-chat-main">
+                <ul id="chat-list">
+
+                </ul>
+            </div>
+            <div class="layim-chat-footer">
+                <div class="layim-chat-send">
+                    <input type="text" autocomplete="off" id="msg"/>
+                    <button class="layim-send layui-disabled" id="send">发送</button>
+                </div>
+                <div class="layim-chat-tool">
+                    <span class="layui-icon layim-tool-face" title="选择表情" id="up-face">&#xe60c;</span>
+                    <span class="layui-icon layim-tool-image" title="上传图片" id="up-image">&#xe60d;</span>
+                    <!--<span class="layui-icon layim-tool-image" title="发送文件"></span>-->
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="layui-m-layerchild layim-layer layui-m-anim-scale">
+    <div class="layui-m-layercont" style="display:none;padding:0" id="face-box">
+
+    </div>
+</div>
+
+<script src="/static/service/js/jquery-1.9.0.min.js"></script>
+<script src="/static/service/js/jquery.cookie.js"></script>
+<script>
+    var config = {
+        uid: '{$id}',
+        name: '{$name}',
+        avatar: '{$avatar}',
+        group: '{$group}',
+        socket: '{$socket}'
+    };
+</script>
+<script src="/static/service/js/layui/layui.js"></script>
+<script src="/static/service/js/functions.js"></script>
+<script src="/static/customer/js/whisper-mobile.js"></script>
+</body>
+</html>

+ 21 - 0
application/route.php

@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    '__pattern__' => [
+        'name' => '\w+',
+    ],
+    '[hello]'     => [
+        ':id'   => ['index/hello', ['method' => 'get'], ['id' => '\d+']],
+        ':name' => ['index/hello', ['method' => 'post']],
+    ],
+
+];

+ 1 - 0
application/service/common.php

@@ -0,0 +1 @@
+<?php

+ 7 - 0
application/service/config.php

@@ -0,0 +1,7 @@
+<?php
+//配置文件
+return [
+
+    // cookie有效时长
+    'cookie_save_time' => 30 * 86400
+];

+ 20 - 0
application/service/controller/Base.php

@@ -0,0 +1,20 @@
+<?php
+namespace app\service\controller;
+
+use think\Controller;
+
+class Base extends Controller
+{
+    public function _initialize()
+    {
+        if(empty(cookie('l_user_name'))){
+
+            $this->redirect(url('login/index'));
+        }
+        $this->assign([
+            'version' => config('version'),
+            'socket' => config('socket')
+        ]);
+
+    }
+}

+ 89 - 0
application/service/controller/Index.php

@@ -0,0 +1,89 @@
+<?php
+namespace app\service\controller;
+
+class Index extends Base
+{
+    public function index()
+    {
+        // 客服信息
+        $userInfo = db('users')->where('id', cookie('l_user_id'))->find();
+
+        $this->assign([
+            'uinfo' => $userInfo,
+            'word' => db('words')->select(),
+            'groups' => db('groups')->where('status', 1)->select(),
+            'status' => db('kf_config')->where('id', 1)->find()
+        ]);
+
+        return $this->fetch();
+    }
+
+    // 获取服务用户列表
+    // 此方法是为了防止客服工作期间错误的刷新工作台,导致服务人员消失的问题
+    public function getUserList()
+    {
+        if(request()->isAjax()){
+
+            // 此处只查询过去 三个小时 内的未服务完的用户
+            $userList = db('service_log')->field('user_id id,user_name name,user_avatar avatar,user_ip ip')
+                ->where('kf_id', cookie('l_user_id'))
+                ->where('start_time', '>', time() - 3600 * 3)->where('end_time', 0)->select();
+
+            return json(['code' => 1, 'data' => $userList, 'msg' => 'ok']);
+        }
+    }
+
+    // 获取聊天记录
+    public function getChatLog()
+    {
+        if(request()->isAjax()){
+
+            $param = input('param.');
+
+            $limit = 10; // 一次显示10 条聊天记录
+            $offset = ($param['page'] - 1) * $limit;
+
+            $logs = db('chat_log')->where(function($query) use($param){
+                    $query->where('from_id', $param['uid'])->where('to_id', 'KF' . cookie('l_user_id'));
+            })->whereOr(function($query) use($param){
+                $query->where('from_id', 'KF' . cookie('l_user_id'))->where('to_id', $param['uid']);
+            })->limit($offset, $limit)->order('id', 'desc')->select();
+
+            $total =  db('chat_log')->where(function($query) use($param){
+                $query->where('from_id', $param['uid'])->where('to_id', 'KF' . cookie('l_user_id'));
+            })->whereOr(function($query) use($param){
+                $query->where('from_id', 'KF' . cookie('l_user_id'))->where('to_id', $param['uid']);
+            })->count();
+
+            foreach($logs as $key=>$vo){
+
+                $logs[$key]['type'] = 'user';
+                $logs[$key]['time_line'] = date('Y-m-d H:i:s', $vo['time_line']);
+
+                if($vo['from_id'] == 'KF' . cookie('l_user_id')){
+                    $logs[$key]['type'] = 'mine';
+                }
+            }
+
+            return json(['code' => 1, 'data' => $logs, 'msg' => intval($param['page']), 'total' => ceil($total / $limit)]);
+        }
+    }
+
+    // ip 定位
+    public function getCity()
+    {
+        $ip = input('param.ip');
+
+        $ip2region = new \Ip2Region();
+        $info = $ip2region->btreeSearch($ip);
+
+        $city = explode('|', $info['region']);
+
+        if(0 != $info['city_id']){
+            return json(['code' => 1, 'data' => $city['2'] . $city['3'] . $city['4'], 'msg' => 'ok']);
+        }else{
+
+            return json(['code' => 1, 'data' => $city['0'], 'msg' => 'ok']);
+        }
+    }
+}

+ 52 - 0
application/service/controller/Login.php

@@ -0,0 +1,52 @@
+<?php
+namespace app\service\controller;
+
+use think\Controller;
+
+class Login extends Controller
+{
+    public function index()
+    {
+        $this->assign([
+            'version' => config('version')
+        ]);
+
+        return $this->fetch();
+    }
+
+    public function doLogin()
+    {
+        if(request()->isAjax()){
+
+            $userName = input('post.username');
+            $password = input('post.password');
+			
+            $user = db('users')->where('user_name', $userName)->find();
+            if(empty($user)){
+                return json(['code' => -1, 'data' => '', 'msg' => '客服不存在']);
+            }
+
+            if(md5($password . config('salt')) != $user['user_pwd']){
+                return json(['code' => -2, 'data' => '', 'msg' => '密码错误']);
+            }
+
+            // 设置session标识状态
+            cookie('l_user_name', $user['user_name'], config('save_time'));
+            cookie('l_user_id', $user['id'], config('save_time'));
+            cookie('l_user_avatar', $user['user_avatar'], config('save_time'));
+
+            return json(['code' => 1, 'data' => url('index/index'), 'msg' => '登录成功']);
+        }
+
+        $this->error('非法访问');
+    }
+
+    public function loginOut()
+    {
+        cookie('l_user_name', null);
+        cookie('l_user_id', null);
+        cookie('l_user_avatar', null);
+
+        $this->redirect(url('login/index'));
+    }
+}

+ 68 - 0
application/service/controller/Upload.php

@@ -0,0 +1,68 @@
+<?php
+namespace app\service\controller;
+
+class Upload extends Base
+{
+
+    //上传图片
+    public function uploadImg()
+    {
+        $file = request()->file('file');
+
+        $fileInfo = $file->getInfo();
+        /*if($fileInfo['size'] > 1024 * 1024 * 2){
+            // 上传失败获取错误信息
+            return json( ['code' => -2, 'data' => '', 'msg' => '文件超过2M'] );
+        }*/
+
+        //检测图片格式
+        $ext = explode('.', $fileInfo['name']);
+        $ext = array_pop($ext);
+
+        $extArr = explode('|', 'jpg|png|gif|jpeg');
+        if(!in_array($ext, $extArr)){
+            return json(['code' => -3, 'data' => '', 'msg' => '只能上传jpg|png|gif|jpeg的文件']);
+        }
+
+        // 移动到框架应用根目录/public/uploads/ 目录下
+        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
+        if($info){
+            $src =  '/uploads' . '/' . date('Ymd') . '/' . $info->getFilename();
+            return json(['code' => 0, 'data' => ['src' => $src ], 'msg' => '']);
+        }else{
+            // 上传失败获取错误信息
+            return json(['code' => -1, 'data' => '', 'msg' => $file->getError()]);
+        }
+    }
+
+    //上传文件
+    public function uploadFile()
+    {
+        $file = request()->file('file');
+
+        $fileInfo = $file->getInfo();
+        /*if($fileInfo['size'] > 1024*1024*2){
+            // 上传失败获取错误信息
+            return json( ['code' => -2, 'data' => '', 'msg' => '文件超过2M'] );
+        }*/
+
+        // 检测文件格式
+        $ext = explode('.', $fileInfo['name']);
+        $ext = array_pop($ext);
+
+        $extArr = explode('|', 'zip|rar');
+        if(!in_array($ext, $extArr)){
+            return json(['code' => -3, 'data' => '', 'msg' => '只能上传zip|rar的文件']);
+        }
+
+        // 移动到框架应用根目录/public/uploads/ 目录下
+        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
+        if($info){
+            $src =  '/uploads' . '/' . date('Ymd') . '/' . $info->getFilename();
+            return json(['code' => 0, 'data' => ['src' => $src ], 'msg' => $fileInfo['name']]);
+        }else{
+            // 上传失败获取错误信息
+            return json(['code' => -1, 'data' => '', 'msg' => $file->getError()]);
+        }
+    }
+}

+ 1 - 0
application/service/database.php

@@ -0,0 +1 @@
+<?php

+ 150 - 0
application/service/view/index/index.html

@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+    <title>whisper客服工作台</title>
+    <link rel="stylesheet" href="/static/service/js/layui/css/layui.css">
+    <link rel="stylesheet" href="/static/service/css/whisper.css">
+</head>
+<body class="layui-layout-body">
+<div class="layui-layout layui-layout-admin">
+    <div class="layui-header">
+        <div class="layui-logo" style="color: white">whisper客服【工作台】</div>
+        <ul class="layui-nav layui-layout-right">
+            <li style="margin-top: 10%">
+                <a href="javascript:;" onclick="loginOut();">
+                    <button class="layui-btn layui-bg-red">
+                        <i class="layui-icon">&#xe609;</i> 退出下班
+                    </button>
+                </a>
+            </li>
+        </ul>
+    </div>
+
+    <div class="layui-side" style="background:#f2f2f2">
+        <div class="layui-side-scroll">
+            <blockquote class="layui-elem-quote layui-bg-cyan" style="color: white">正在咨询的会员</blockquote>
+            <ul class="layui-unselect" id="user_list">
+
+            </ul>
+        </div>
+    </div>
+
+    <div class="layui-body" style="bottom:0">
+        <input type="hidden" id="active-user" data-avatar="" data-name="" data-id=""><!-- 当前对话的用户 -->
+        <div class="chat-left">
+            <div class="chat-box whisper-chat-main">
+
+            </div>
+            <div class="msg-send">
+                <div class="tools-bar">
+                    <i class="layui-icon" style="font-size: 30px;" id="face">&#xe60c;</i>
+                    <i class="layui-icon" style="font-size: 30px;" id="image">&#xe60d;</i>
+                    <i class="layui-icon" style="font-size: 30px;" id="file">&#xe61d;</i>
+                </div>
+                <div class="msg-box">
+                    <textarea class="msg-area" id="msg-area"></textarea>
+                </div>
+                <div class="send-area">
+                    <span style="margin-left:10px;color:gray">快捷键 Enter</span>
+                    <button class="layui-btn layui-btn-small layui-bg-cyan" style="float:right;margin-right:10px;height: 40px;padding: 0 15px;" id="send">
+                        <i class="layui-icon">&#xe609;</i>发送
+                    </button>
+                </div>
+            </div>
+        </div>
+
+        <div style="width:28%;height:100%;float:left;margin-left:1%">
+            <div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
+                <ul class="layui-tab-title">
+                    <li class="layui-this">访客信息</li>
+                    <li>常用语</li>
+                </ul>
+                <div class="layui-tab-content" style="height: 100px;">
+                    <div class="layui-tab-item layui-show">
+                        <div class="layui-form layui-form-pane">
+                            <div class="layui-form-item">
+                                <label class="layui-form-label">访客名</label>
+                                <div class="layui-input-block">
+                                    <input type="text" id="f-user" class="layui-input" readonly>
+                                </div>
+                            </div>
+                            <div class="layui-form-item">
+                                <label class="layui-form-label">IP</label>
+                                <div class="layui-input-block">
+                                    <input type="text" id="f-ip" class="layui-input" readonly>
+                                </div>
+                            </div>
+                            <div class="layui-form-item">
+                                <label class="layui-form-label">地区</label>
+                                <div class="layui-input-block">
+                                    <input type="text" id="f-area" class="layui-input" readonly>
+                                </div>
+                            </div>
+                            {if(1 == $status['change_status'])}
+                            <div class="layui-form-item">
+                                <label class="layui-form-label layui-bg-cyan" style="cursor: pointer;color:white" id="scroll-link">转接</label>
+                            </div>
+                            {/if}
+                        </div>
+                    </div>
+                    <div class="layui-tab-item">
+                        <div class="layui-form">
+                            <table class="layui-table">
+                                <thead>
+                                <tr>
+                                    <th>内容</th>
+                                    <th>操作</th>
+                                </tr>
+                                </thead>
+                                <tbody>
+                                {foreach name="word" item="vo"}
+                                <tr>
+                                    <td>{$vo.content}</td>
+                                    <td>
+                                        <a href="javascript:;" onclick="sendWord(this)" data-word="{$vo.content}" style="color:#009688">应用</a>
+                                    </td>
+                                </tr>
+                                {/foreach}
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 转接提示层 -->
+<div class="layui-form" id="change-box" style="display: none">
+    <div class="layui-form-item" style="margin-top: 20px">
+        <label class="layui-form-label">选择分组</label>
+        <div class="layui-input-block" style="width: 70%" >
+            <select lay-verify="required" lay-filter="group">
+                <option value=""></option>
+                {if !empty($groups)}
+                {foreach name="groups" item="vo"}
+                <option value="{$vo['id']}">{$vo['name']}</option>
+                {/foreach}
+                {/if}
+            </select>
+        </div>
+    </div>
+</div>
+<!-- 转接提示层 -->
+
+<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
+<script src="/static/service/js/layui/layui.js"></script>
+<script>
+    var uid = "{$uinfo['id']}";
+    var uname = "{$uinfo['user_name']}";
+    var avatar = "{$uinfo['user_avatar']}";
+    var group = "{$uinfo['group_id']}";
+    var socket_server = "{$socket}";
+</script>
+<script type="text/javascript" src="/static/service/js/functions.js"></script>
+<script type="text/javascript" src="/static/service/js/whisper.js"></script>
+</body>
+</html>

+ 75 - 0
application/service/view/login/index.html

@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta http-equiv="Pragma" content="no-cache">
+    <meta http-equiv="Cache-Control" content="no-cache">
+    <meta http-equiv="Expires" content="0">
+    <title>whisper {$version}客服登录页面</title>
+    <script type="text/javascript" src="/static/service/js/jquery-1.9.0.min.js"></script>
+    <script type="text/javascript" src="/static/service/js/layui/layui.js"></script>
+    <link href="/static/service/css/login.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<div class="login_box">
+    <div class="login_l_img"><img src="/static/service/images/login-img.png" /></div>
+    <div class="login">
+        <div class="login_logo"><a href="javascript:;"><img src="/static/service/images/login_logo.png" /></a></div>
+        <div class="login_name">
+            <p>whisper 登录页面</p>
+        </div>
+        <div class="login-form">
+            <input name="username" type="text" placeholder="用户名" id="u" >
+            <input name="password" type="password" id="p" placeholder="密码"/>
+            <input value="登录" style="width:100%;" type="button" id="btn">
+        </div>
+    </div>
+    <div class="copyright"><a href="http://baiyf.com" target="_blank" style="color:white;">whisper</a> 版权所有 ©2018-2019 </div>
+</div>
+<script>
+    document.onkeydown=function(event){
+        var e = event || window.event || arguments.callee.caller.arguments[0];
+        if(e && e.keyCode==13){ // enter 键
+            doLogin();
+        }
+    };
+
+    $(function(){
+        $("#btn").click(function(){
+            doLogin();
+        });
+    });
+
+    function doLogin(){
+        layui.use(['layer'], function(){
+            var layer = layui.layer;
+            layer.ready(function(){
+                var user_name = $("#u").val();
+                var password = $("#p").val();
+
+                if('' == user_name){
+                    layer.tips('请输入用户名', '#u');
+                    return false;
+                }
+
+                if('' == password){
+                    layer.tips('请输入密码', '#p');
+                    return false;
+                }
+
+                var index = layer.load(0, {shade: false});
+                $.post('/service/login/doLogin', {username: user_name, password: password}, function(res){
+                    layer.close(index);
+                    if(1 == res.code){
+                        window.location.href = res.data;
+                    }else{
+                        return layer.msg(res.msg, {icon: 2, anim: 6});
+                    }
+                }, 'json');
+            });
+        });
+    }
+</script>
+</body>
+</html>

+ 28 - 0
application/tags.php

@@ -0,0 +1,28 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用行为扩展定义文件
+return [
+    // 应用初始化
+    'app_init'     => [],
+    // 应用开始
+    'app_begin'    => [],
+    // 模块初始化
+    'module_init'  => [],
+    // 操作开始执行
+    'action_begin' => [],
+    // 视图内容过滤
+    'view_filter'  => [],
+    // 日志写入
+    'log_write'    => [],
+    // 应用结束
+    'app_end'      => [],
+];

+ 25 - 0
build.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    // 生成应用公共文件
+    '__file__' => ['common.php', 'config.php', 'database.php'],
+
+    // 定义index模块的自动生成 (按照实际定义的文件名生成)
+    'index'     => [
+        '__file__'   => ['common.php'],
+        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
+        'controller' => ['Index'],
+        'model'      => [],
+        'view'       => ['index/index'],
+    ],
+    // 其他更多的模块定义
+];

+ 29 - 0
composer.json

@@ -0,0 +1,29 @@
+{
+    "name": "topthink/think",
+    "description": "the new thinkphp framework",
+    "type": "project",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.4.0",
+        "topthink/framework": "~5.0.0",
+        "zoujingli/ip2region": "^1.0"
+    },
+    "extra": {
+        "think-path": "thinkphp"
+    },
+    "config": {
+        "preferred-install": "dist"
+    }
+}

+ 2 - 0
extend/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 8 - 0
public/.htaccess

@@ -0,0 +1,8 @@
+<IfModule mod_rewrite.c>
+  Options +FollowSymlinks -Multiviews
+  RewriteEngine On
+
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
+</IfModule>

BIN
public/favicon.ico


+ 17 - 0
public/index.php

@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// [ 应用入口文件 ]
+
+// 定义应用目录
+define('APP_PATH', __DIR__ . '/../application/');
+// 加载框架引导文件
+require __DIR__ . '/../thinkphp/start.php';

+ 2 - 0
public/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:

+ 20 - 0
public/router.php

@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+// $Id$
+
+if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["REQUEST_URI"])) {
+    return false;
+} else {
+    if (!isset($_SERVER['PATH_INFO'])) {
+        $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'];
+    }
+    require __DIR__ . "/index.php";
+}

Разница между файлами не показана из-за своего большого размера
+ 11 - 0
public/static/admin/css/animate.min.css


Разница между файлами не показана из-за своего большого размера
+ 4 - 0
public/static/admin/css/bootstrap.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
public/static/admin/css/demo/webuploader-demo.min.css


Разница между файлами не показана из-за своего большого размера
+ 3 - 0
public/static/admin/css/font-awesome.min.css


+ 124 - 0
public/static/admin/css/login.css

@@ -0,0 +1,124 @@
+*{
+    font: 13px/1.5 '微软雅黑', Verdana, Helvetica, Arial, sans-serif;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -box-sizing: border-box;
+    padding:0;
+    margin:0;
+    list-style:none;
+    box-sizing: border-box;
+}
+
+body,html{
+    height:100%;
+    overflow:hidden;
+}
+body{
+    background:url(../images/web_login_bg.jpg) no-repeat center;
+    background-size: cover;
+}
+a{
+    color:#27A9E3;
+    text-decoration:none;
+    cursor:pointer;
+}
+.login{
+    margin: 150px auto 0 auto;
+    min-height: 420px;
+    max-width: 420px;
+    padding: 40px;
+    background-color: #ffffff;
+    margin-left: auto;
+    margin-right: auto;
+    border-radius: 4px;
+    /* overflow-x: hidden; */
+    box-sizing: border-box;
+}
+a.logo{
+    display: block;
+    height: 58px;
+    width: 167px;
+    margin: 0 auto 30px auto;
+    background-size: 167px 42px;
+}
+.message {
+    margin: 10px 0 0 -58px;
+    padding: 18px 10px 18px 60px;
+    background: #27A9E3;
+    position: relative;
+    color: #fff;
+    font-size: 16px;
+}
+#darkbannerwrap {
+    background: url(../images/aiwrap.png);
+    width: 18px;
+    height: 10px;
+    margin: 0 0 20px -58px;
+    position: relative;
+}
+
+input[type=text],
+input[type=file],
+input[type=password],
+input[type=email], select {
+    border: 1px solid #DCDEE0;
+    vertical-align: middle;
+    border-radius: 3px;
+    height: 50px;
+    padding: 0px 16px;
+    font-size: 14px;
+    color: #555555;
+    outline:none;
+    width:100%;
+}
+input[type=text]:focus,
+input[type=file]:focus,
+input[type=password]:focus,
+input[type=email]:focus, select:focus {
+    border: 1px solid #27A9E3;
+}
+
+
+input[type=submit],
+input[type=button]{
+    display: inline-block;
+    vertical-align: middle;
+    padding: 12px 24px;
+    margin: 0px;
+    font-size: 18px;
+    line-height: 24px;
+    text-align: center;
+    white-space: nowrap;
+    vertical-align: middle;
+    cursor: pointer;
+    color: #ffffff;
+    background-color: #27A9E3;
+    border-radius: 3px;
+    border: none;
+    -webkit-appearance: none;
+    outline:none;
+    width:100%;
+}
+hr.hr15 {
+    height: 15px;
+    border: none;
+    margin: 0px;
+    padding: 0px;
+    width: 100%;
+}
+hr.hr20 {
+    height: 20px;
+    border: none;
+    margin: 0px;
+    padding: 0px;
+    width: 100%;
+}
+
+.copyright{
+    font-size:14px;
+    color:rgba(255,255,255,0.85);
+    display:block;
+    position:absolute;
+    bottom:15px;
+    right:15px;
+}

BIN
public/static/admin/css/patterns/header-profile-skin-1.png


BIN
public/static/admin/css/patterns/header-profile-skin-3.png


BIN
public/static/admin/css/patterns/header-profile.png


BIN
public/static/admin/css/patterns/shattered.png


+ 251 - 0
public/static/admin/css/plugins/awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css

@@ -0,0 +1,251 @@
+.checkbox {
+  padding-left: 20px;
+}
+.checkbox label {
+  display: inline-block;
+  vertical-align: middle;
+  position: relative;
+  padding-left: 5px;
+}
+.checkbox label::before {
+  content: "";
+  display: inline-block;
+  position: absolute;
+  width: 17px;
+  height: 17px;
+  left: 0;
+  margin-left: -20px;
+  border: 1px solid #cccccc;
+  border-radius: 3px;
+  background-color: #fff;
+  -webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+  -o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+  transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+}
+.checkbox label::after {
+  display: inline-block;
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  left: 0;
+  top: 0;
+  margin-left: -20px;
+  padding-left: 3px;
+  padding-top: 1px;
+  font-size: 11px;
+  color: #555555;
+}
+.checkbox input[type="checkbox"],
+.checkbox input[type="radio"] {
+  opacity: 0;
+  z-index: 1;
+}
+.checkbox input[type="checkbox"]:focus + label::before,
+.checkbox input[type="radio"]:focus + label::before {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.checkbox input[type="checkbox"]:checked + label::after,
+.checkbox input[type="radio"]:checked + label::after {
+  font-family: "FontAwesome";
+  content: "\f00c";
+}
+.checkbox input[type="checkbox"]:disabled + label,
+.checkbox input[type="radio"]:disabled + label {
+  opacity: 0.65;
+}
+.checkbox input[type="checkbox"]:disabled + label::before,
+.checkbox input[type="radio"]:disabled + label::before {
+  background-color: #eeeeee;
+  cursor: not-allowed;
+}
+.checkbox.checkbox-circle label::before {
+  border-radius: 50%;
+}
+.checkbox.checkbox-inline {
+  margin-top: 0;
+}
+
+.checkbox-primary input[type="checkbox"]:checked + label::before,
+.checkbox-primary input[type="radio"]:checked + label::before {
+  background-color: #337ab7;
+  border-color: #337ab7;
+}
+.checkbox-primary input[type="checkbox"]:checked + label::after,
+.checkbox-primary input[type="radio"]:checked + label::after {
+  color: #fff;
+}
+
+.checkbox-danger input[type="checkbox"]:checked + label::before,
+.checkbox-danger input[type="radio"]:checked + label::before {
+  background-color: #d9534f;
+  border-color: #d9534f;
+}
+.checkbox-danger input[type="checkbox"]:checked + label::after,
+.checkbox-danger input[type="radio"]:checked + label::after {
+  color: #fff;
+}
+
+.checkbox-info input[type="checkbox"]:checked + label::before,
+.checkbox-info input[type="radio"]:checked + label::before {
+  background-color: #5bc0de;
+  border-color: #5bc0de;
+}
+.checkbox-info input[type="checkbox"]:checked + label::after,
+.checkbox-info input[type="radio"]:checked + label::after {
+  color: #fff;
+}
+
+.checkbox-warning input[type="checkbox"]:checked + label::before,
+.checkbox-warning input[type="radio"]:checked + label::before {
+  background-color: #f0ad4e;
+  border-color: #f0ad4e;
+}
+.checkbox-warning input[type="checkbox"]:checked + label::after,
+.checkbox-warning input[type="radio"]:checked + label::after {
+  color: #fff;
+}
+
+.checkbox-success input[type="checkbox"]:checked + label::before,
+.checkbox-success input[type="radio"]:checked + label::before {
+  background-color: #5cb85c;
+  border-color: #5cb85c;
+}
+.checkbox-success input[type="checkbox"]:checked + label::after,
+.checkbox-success input[type="radio"]:checked + label::after {
+  color: #fff;
+}
+
+.radio {
+  padding-left: 20px;
+}
+.radio label {
+  display: inline-block;
+  vertical-align: middle;
+  position: relative;
+  padding-left: 5px;
+}
+.radio label::before {
+  content: "";
+  display: inline-block;
+  position: absolute;
+  width: 17px;
+  height: 17px;
+  left: 0;
+  margin-left: -20px;
+  border: 1px solid #cccccc;
+  border-radius: 50%;
+  background-color: #fff;
+  -webkit-transition: border 0.15s ease-in-out;
+  -o-transition: border 0.15s ease-in-out;
+  transition: border 0.15s ease-in-out;
+}
+.radio label::after {
+  display: inline-block;
+  position: absolute;
+  content: " ";
+  width: 11px;
+  height: 11px;
+  left: 3px;
+  top: 3px;
+  margin-left: -20px;
+  border-radius: 50%;
+  background-color: #555555;
+  -webkit-transform: scale(0, 0);
+  -ms-transform: scale(0, 0);
+  -o-transform: scale(0, 0);
+  transform: scale(0, 0);
+  -webkit-transition: -webkit-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
+  -moz-transition: -moz-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
+  -o-transition: -o-transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
+  transition: transform 0.1s cubic-bezier(0.8, -0.33, 0.2, 1.33);
+}
+.radio input[type="radio"] {
+  opacity: 0;
+  z-index: 1;
+}
+.radio input[type="radio"]:focus + label::before {
+  outline: thin dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.radio input[type="radio"]:checked + label::after {
+  -webkit-transform: scale(1, 1);
+  -ms-transform: scale(1, 1);
+  -o-transform: scale(1, 1);
+  transform: scale(1, 1);
+}
+.radio input[type="radio"]:disabled + label {
+  opacity: 0.65;
+}
+.radio input[type="radio"]:disabled + label::before {
+  cursor: not-allowed;
+}
+.radio.radio-inline {
+  margin-top: 0;
+}
+
+.radio-primary input[type="radio"] + label::after {
+  background-color: #337ab7;
+}
+.radio-primary input[type="radio"]:checked + label::before {
+  border-color: #337ab7;
+}
+.radio-primary input[type="radio"]:checked + label::after {
+  background-color: #337ab7;
+}
+
+.radio-danger input[type="radio"] + label::after {
+  background-color: #d9534f;
+}
+.radio-danger input[type="radio"]:checked + label::before {
+  border-color: #d9534f;
+}
+.radio-danger input[type="radio"]:checked + label::after {
+  background-color: #d9534f;
+}
+
+.radio-info input[type="radio"] + label::after {
+  background-color: #5bc0de;
+}
+.radio-info input[type="radio"]:checked + label::before {
+  border-color: #5bc0de;
+}
+.radio-info input[type="radio"]:checked + label::after {
+  background-color: #5bc0de;
+}
+
+.radio-warning input[type="radio"] + label::after {
+  background-color: #f0ad4e;
+}
+.radio-warning input[type="radio"]:checked + label::before {
+  border-color: #f0ad4e;
+}
+.radio-warning input[type="radio"]:checked + label::after {
+  background-color: #f0ad4e;
+}
+
+.radio-success input[type="radio"] + label::after {
+  background-color: #5cb85c;
+}
+.radio-success input[type="radio"]:checked + label::before {
+  border-color: #5cb85c;
+}
+.radio-success input[type="radio"]:checked + label::after {
+  background-color: #5cb85c;
+}
+
+input[type="checkbox"].styled:checked + label:after,
+input[type="radio"].styled:checked + label:after {
+  font-family: 'FontAwesome';
+  content: "\f00c";
+}
+input[type="checkbox"] .styled:checked + label::before,
+input[type="radio"] .styled:checked + label::before {
+  color: #fff;
+}
+input[type="checkbox"] .styled:checked + label::after,
+input[type="radio"] .styled:checked + label::after {
+  color: #fff;
+}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
public/static/admin/css/plugins/blueimp/css/blueimp-gallery.min.css


BIN
public/static/admin/css/plugins/blueimp/img/error.png


+ 5 - 0
public/static/admin/css/plugins/blueimp/img/error.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
+	<circle cx="32" cy="32" r="25" stroke="red" stroke-width="7" fill="black" fill-opacity="0.2"/>
+	<rect x="28" y="7" width="8" height="50" fill="red" transform="rotate(45, 32, 32)"/>
+</svg>

BIN
public/static/admin/css/plugins/blueimp/img/loading.gif


BIN
public/static/admin/css/plugins/blueimp/img/play-pause.png


+ 6 - 0
public/static/admin/css/plugins/blueimp/img/play-pause.svg

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="30" height="15">
+	<polygon points="2,1 2,14 13,7" stroke="black" stroke-width="1" fill="white"/>
+	<rect x="17" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
+	<rect x="24" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
+</svg>

BIN
public/static/admin/css/plugins/blueimp/img/video-play.png


+ 5 - 0
public/static/admin/css/plugins/blueimp/img/video-play.svg

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
+	<circle cx="32" cy="32" r="25" stroke="white" stroke-width="7" fill="black" fill-opacity="0.2"/>
+	<polygon points="26,22 26,42 43,32" fill="white"/>
+</svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
public/static/admin/css/plugins/bootstrap-table/bootstrap-table.min.css


BIN
public/static/admin/css/plugins/chosen/chosen-sprite.png


BIN
public/static/admin/css/plugins/chosen/chosen-sprite@2x.png


+ 423 - 0
public/static/admin/css/plugins/chosen/chosen.css

@@ -0,0 +1,423 @@
+/*!
+Chosen, a Select Box Enhancer for jQuery and Prototype
+by Patrick Filler for Harvest, http://getharvest.com
+
+Version 1.1.0
+Full source at https://github.com/harvesthq/chosen
+Copyright (c) 2011 Harvest http://getharvest.com
+
+MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
+This file is generated by `grunt build`, do not edit it by hand.
+*/
+
+/* @group Base */
+.chosen-container {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle;
+    font-size: 13px;
+    zoom: 1;
+    *display: inline;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    user-select: none;
+}
+.chosen-container .chosen-drop {
+    position: absolute;
+    top: 100%;
+    left: -9999px;
+    z-index: 1010;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    width: 100%;
+    border: 1px solid #aaa;
+    border-top: 0;
+    background: #fff;
+    box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
+}
+.chosen-container.chosen-with-drop .chosen-drop {
+    left: 0;
+}
+.chosen-container a {
+    cursor: pointer;
+}
+
+/* @end */
+/* @group Single Chosen */
+.chosen-container-single .chosen-single {
+    position: relative;
+    display: block;
+    overflow: hidden;
+    padding: 0 0 0 8px;
+    height: 23px;
+    border: 1px solid #aaa;
+    border-radius: 5px;
+    background-color: #fff;
+    background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
+    background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+    background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+    background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+    background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
+    background-clip: padding-box;
+    box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
+    color: #444;
+    text-decoration: none;
+    white-space: nowrap;
+    line-height: 24px;
+}
+.chosen-container-single .chosen-default {
+    color: #999;
+}
+.chosen-container-single .chosen-single span {
+    display: block;
+    overflow: hidden;
+    margin-right: 26px;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+.chosen-container-single .chosen-single-with-deselect span {
+    margin-right: 38px;
+}
+.chosen-container-single .chosen-single abbr {
+    position: absolute;
+    top: 6px;
+    right: 26px;
+    display: block;
+    width: 12px;
+    height: 12px;
+    background: url('chosen-sprite.png') -42px 1px no-repeat;
+    font-size: 1px;
+}
+.chosen-container-single .chosen-single abbr:hover {
+    background-position: -42px -10px;
+}
+.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
+    background-position: -42px -10px;
+}
+.chosen-container-single .chosen-single div {
+    position: absolute;
+    top: 0;
+    right: 0;
+    display: block;
+    width: 18px;
+    height: 100%;
+}
+.chosen-container-single .chosen-single div b {
+    display: block;
+    width: 100%;
+    height: 100%;
+    background: url('chosen-sprite.png') no-repeat 0px 7px;
+}
+.chosen-container-single .chosen-search {
+    position: relative;
+    z-index: 1010;
+    margin: 0;
+    padding: 3px 4px;
+    white-space: nowrap;
+}
+.chosen-container-single .chosen-search input[type="text"] {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    margin: 1px 0;
+    padding: 4px 20px 4px 5px;
+    width: 100%;
+    height: auto;
+    outline: 0;
+    border: 1px solid #aaa;
+    background: white url('chosen-sprite.png') no-repeat 100% -20px;
+    background: url('chosen-sprite.png') no-repeat 100% -20px;
+    font-size: 1em;
+    font-family: sans-serif;
+    line-height: normal;
+    border-radius: 0;
+}
+.chosen-container-single .chosen-drop {
+    margin-top: -1px;
+    border-radius: 0 0 4px 4px;
+    background-clip: padding-box;
+}
+.chosen-container-single.chosen-container-single-nosearch .chosen-search {
+    position: absolute;
+    left: -9999px;
+}
+
+/* @end */
+/* @group Results */
+.chosen-container .chosen-results {
+    position: relative;
+    overflow-x: hidden;
+    overflow-y: auto;
+    margin: 0 4px 4px 0;
+    padding: 0 0 0 4px;
+    max-height: 240px;
+    -webkit-overflow-scrolling: touch;
+}
+.chosen-container .chosen-results li {
+    display: none;
+    margin: 0;
+    padding: 5px 6px;
+    list-style: none;
+    line-height: 15px;
+    -webkit-touch-callout: none;
+}
+.chosen-container .chosen-results li.active-result {
+    display: list-item;
+    cursor: pointer;
+}
+.chosen-container .chosen-results li.disabled-result {
+    display: list-item;
+    color: #ccc;
+    cursor: default;
+}
+.chosen-container .chosen-results li.highlighted {
+    background-color: #3875d7;
+    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
+    background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
+    background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
+    background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
+    background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
+    color: #fff;
+}
+.chosen-container .chosen-results li.no-results {
+    display: list-item;
+    background: #f4f4f4;
+}
+.chosen-container .chosen-results li.group-result {
+    display: list-item;
+    font-weight: bold;
+    cursor: default;
+}
+.chosen-container .chosen-results li.group-option {
+    padding-left: 15px;
+}
+.chosen-container .chosen-results li em {
+    font-style: normal;
+    text-decoration: underline;
+}
+
+/* @end */
+/* @group Multi Chosen */
+.chosen-container-multi .chosen-choices {
+    -moz-box-sizing: border-box;
+    background-color: #FFFFFF;
+    border: 1px solid #CBD5DD;
+    border-radius: 2px;
+    cursor: text;
+    height: auto !important;
+    margin: 0;
+    min-height: 30px;
+    overflow: hidden;
+    padding: 2px;
+    position: relative;
+    width: 100%;
+}
+.chosen-container-multi .chosen-choices li {
+    float: left;
+    list-style: none;
+}
+.chosen-container-multi .chosen-choices li.search-field {
+    margin: 0;
+    padding: 0;
+    white-space: nowrap;
+}
+.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
+    margin: 1px 0;
+    padding: 5px;
+    height: 25px;
+    outline: 0;
+    border: 0 !important;
+    background: transparent !important;
+    box-shadow: none;
+    color: #666;
+    font-size: 100%;
+    font-family: sans-serif;
+    line-height: normal;
+    border-radius: 0;
+}
+.chosen-container-multi .chosen-choices li.search-field .default {
+    color: #999;
+}
+.chosen-container-multi .chosen-choices li.search-choice {
+    position: relative;
+    margin: 3px 0 3px 5px;
+    padding: 3px 20px 3px 5px;
+    border: 1px solid #aaa;
+    border-radius: 3px;
+    background-color: #e4e4e4;
+    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+    background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-clip: padding-box;
+    box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
+    color: #333;
+    line-height: 13px;
+    cursor: default;
+}
+.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
+    position: absolute;
+    top: 4px;
+    right: 3px;
+    display: block;
+    width: 12px;
+    height: 12px;
+    background: url('chosen-sprite.png') -42px 1px no-repeat;
+    font-size: 1px;
+}
+.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
+    background-position: -42px -10px;
+}
+.chosen-container-multi .chosen-choices li.search-choice-disabled {
+    padding-right: 5px;
+    border: 1px solid #ccc;
+    background-color: #e4e4e4;
+    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
+    background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
+    color: #666;
+}
+.chosen-container-multi .chosen-choices li.search-choice-focus {
+    background: #d4d4d4;
+}
+.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
+    background-position: -42px -10px;
+}
+.chosen-container-multi .chosen-results {
+    margin: 0;
+    padding: 0;
+}
+.chosen-container-multi .chosen-drop .result-selected {
+    display: list-item;
+    color: #ccc;
+    cursor: default;
+}
+
+/* @end */
+/* @group Active  */
+.chosen-container-active .chosen-single {
+    border: 1px solid #5897fb;
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+}
+.chosen-container-active.chosen-with-drop .chosen-single {
+    border: 1px solid #aaa;
+    -moz-border-radius-bottomright: 0;
+    border-bottom-right-radius: 0;
+    -moz-border-radius-bottomleft: 0;
+    border-bottom-left-radius: 0;
+}
+.chosen-container-active.chosen-with-drop .chosen-single div {
+    border-left: none;
+    background: transparent;
+}
+.chosen-container-active.chosen-with-drop .chosen-single div b {
+    background-position: -18px 7px;
+}
+.chosen-container-active .chosen-choices {
+    border: 1px solid #5897fb;
+    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
+}
+.chosen-container-active .chosen-choices li.search-field input[type="text"] {
+    color: #111 !important;
+}
+
+/* @end */
+/* @group Disabled Support */
+.chosen-disabled {
+    opacity: 0.5 !important;
+    cursor: default;
+}
+.chosen-disabled .chosen-single {
+    cursor: default;
+}
+.chosen-disabled .chosen-choices .search-choice .search-choice-close {
+    cursor: default;
+}
+
+/* @end */
+/* @group Right to Left */
+.chosen-rtl {
+    text-align: right;
+}
+.chosen-rtl .chosen-single {
+    overflow: visible;
+    padding: 0 8px 0 0;
+}
+.chosen-rtl .chosen-single span {
+    margin-right: 0;
+    margin-left: 26px;
+    direction: rtl;
+}
+.chosen-rtl .chosen-single-with-deselect span {
+    margin-left: 38px;
+}
+.chosen-rtl .chosen-single div {
+    right: auto;
+    left: 3px;
+}
+.chosen-rtl .chosen-single abbr {
+    right: auto;
+    left: 26px;
+}
+.chosen-rtl .chosen-choices li {
+    float: right;
+}
+.chosen-rtl .chosen-choices li.search-field input[type="text"] {
+    direction: rtl;
+}
+.chosen-rtl .chosen-choices li.search-choice {
+    margin: 3px 5px 3px 0;
+    padding: 3px 5px 3px 19px;
+}
+.chosen-rtl .chosen-choices li.search-choice .search-choice-close {
+    right: auto;
+    left: 4px;
+}
+.chosen-rtl.chosen-container-single-nosearch .chosen-search,
+.chosen-rtl .chosen-drop {
+    left: 9999px;
+}
+.chosen-rtl.chosen-container-single .chosen-results {
+    margin: 0 0 4px 4px;
+    padding: 0 4px 0 0;
+}
+.chosen-rtl .chosen-results li.group-option {
+    padding-right: 15px;
+    padding-left: 0;
+}
+.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
+    border-right: none;
+}
+.chosen-rtl .chosen-search input[type="text"] {
+    padding: 4px 5px 4px 20px;
+    background: white url('chosen-sprite.png') no-repeat -30px -20px;
+    background: url('chosen-sprite.png') no-repeat -30px -20px;
+    direction: rtl;
+}
+.chosen-rtl.chosen-container-single .chosen-single div b {
+    background-position: 6px 2px;
+}
+.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
+    background-position: -12px 2px;
+}
+
+/* @end */
+/* @group Retina compatibility */
+@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) {
+    .chosen-rtl .chosen-search input[type="text"],
+    .chosen-container-single .chosen-single abbr,
+    .chosen-container-single .chosen-single div b,
+    .chosen-container-single .chosen-search input[type="text"],
+    .chosen-container-multi .chosen-choices .search-choice .search-choice-close,
+    .chosen-container .chosen-results-scroll-down span,
+    .chosen-container .chosen-results-scroll-up span {
+        background-image: url('chosen-sprite%402x.png') !important;
+        background-size: 52px 37px !important;
+        background-repeat: no-repeat !important;
+    }
+}
+/* @end */

+ 168 - 0
public/static/admin/css/plugins/clockpicker/clockpicker.css

@@ -0,0 +1,168 @@
+/*!
+ * ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei.
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ */
+
+.clockpicker .input-group-addon {
+	cursor: pointer;
+}
+.clockpicker-moving {
+	cursor: move;
+}
+.clockpicker-align-left.popover > .arrow {
+	left: 25px;
+}
+.clockpicker-align-top.popover > .arrow {
+	top: 17px;
+}
+.clockpicker-align-right.popover > .arrow {
+	left: auto;
+	right: 25px;
+}
+.clockpicker-align-bottom.popover > .arrow {
+	top: auto;
+	bottom: 6px;
+}
+.clockpicker-popover .popover-title {
+	background-color: #fff;
+	color: #999;
+	font-size: 24px;
+	font-weight: bold;
+	line-height: 30px;
+	text-align: center;
+}
+.clockpicker-popover .popover-title span {
+	cursor: pointer;
+}
+.clockpicker-popover .popover-content {
+	background-color: #f8f8f8;
+	padding: 12px;
+}
+.popover-content:last-child {
+	border-bottom-left-radius: 5px;
+	border-bottom-right-radius: 5px;
+}
+.clockpicker-plate {
+	background-color: #fff;
+	border: 1px solid #ccc;
+	border-radius: 50%;
+	width: 200px;
+	height: 200px;
+	overflow: visible;
+	position: relative;
+	/* Disable text selection highlighting. Thanks to Hermanya */
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+}
+.clockpicker-canvas,
+.clockpicker-dial {
+	width: 200px;
+	height: 200px;
+	position: absolute;
+	left: -1px;
+	top: -1px;
+}
+.clockpicker-minutes {
+	visibility: hidden;
+}
+.clockpicker-tick {
+	border-radius: 50%;
+	color: #666;
+	line-height: 26px;
+	text-align: center;
+	width: 26px;
+	height: 26px;
+	position: absolute;
+	cursor: pointer;
+}
+.clockpicker-tick.active,
+.clockpicker-tick:hover {
+	background-color: rgb(192, 229, 247);
+	background-color: rgba(0, 149, 221, .25);
+}
+.clockpicker-button {
+	background-image: none;
+	background-color: #fff;
+	border-width: 1px 0 0;
+	border-top-left-radius: 0;
+	border-top-right-radius: 0;
+	margin: 0;
+	padding: 10px 0;
+}
+.clockpicker-button:hover {
+	background-image: none;
+	background-color: #ebebeb;
+}
+.clockpicker-button:focus {
+	outline: none!important;
+}
+.clockpicker-dial {
+	-webkit-transition: -webkit-transform 350ms, opacity 350ms;
+	-moz-transition: -moz-transform 350ms, opacity 350ms;
+	-ms-transition: -ms-transform 350ms, opacity 350ms;
+	-o-transition: -o-transform 350ms, opacity 350ms;
+	transition: transform 350ms, opacity 350ms;
+}
+.clockpicker-dial-out {
+	opacity: 0;
+}
+.clockpicker-hours.clockpicker-dial-out {
+	-webkit-transform: scale(1.2, 1.2);
+	-moz-transform: scale(1.2, 1.2);
+	-ms-transform: scale(1.2, 1.2);
+	-o-transform: scale(1.2, 1.2);
+	transform: scale(1.2, 1.2);
+}
+.clockpicker-minutes.clockpicker-dial-out {
+	-webkit-transform: scale(.8, .8);
+	-moz-transform: scale(.8, .8);
+	-ms-transform: scale(.8, .8);
+	-o-transform: scale(.8, .8);
+	transform: scale(.8, .8);
+}
+.clockpicker-canvas {
+	-webkit-transition: opacity 175ms;
+	-moz-transition: opacity 175ms;
+	-ms-transition: opacity 175ms;
+	-o-transition: opacity 175ms;
+	transition: opacity 175ms;
+}
+.clockpicker-canvas-out {
+	opacity: 0.25;
+}
+.clockpicker-canvas-bearing,
+.clockpicker-canvas-fg {
+	stroke: none;
+	fill: rgb(0, 149, 221);
+}
+.clockpicker-canvas-bg {
+	stroke: none;
+	fill: rgb(192, 229, 247);
+}
+.clockpicker-canvas-bg-trans {
+	fill: rgba(0, 149, 221, .25);
+}
+.clockpicker-canvas line {
+	stroke: rgb(0, 149, 221);
+	stroke-width: 1;
+	stroke-linecap: round;
+	/*shape-rendering: crispEdges;*/
+}
+.clockpicker-button.am-button {
+	margin: 1px;
+	padding: 5px;
+	border: 1px solid rgba(0, 0, 0, .2);
+	border-radius: 4px;
+
+}
+.clockpicker-button.pm-button {
+	margin: 1px 1px 1px 136px;
+	padding: 5px;
+	border: 1px solid rgba(0, 0, 0, .2);
+	border-radius: 4px;
+}

Разница между файлами не показана из-за своего большого размера
+ 75 - 0
public/static/admin/css/plugins/codemirror/ambiance.css


+ 309 - 0
public/static/admin/css/plugins/codemirror/codemirror.css

@@ -0,0 +1,309 @@
+/* BASICS */
+
+.CodeMirror {
+  /* Set height, width, borders, and global font properties here */
+  font-family: monospace;
+  height: 300px;
+}
+.CodeMirror-scroll {
+  /* Set scrolling behaviour here */
+  overflow: auto;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+  padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+  padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+  border-right: 1px solid #ddd;
+  background-color: #f7f7f7;
+  white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+  padding: 0 3px 0 5px;
+  min-width: 20px;
+  text-align: right;
+  color: #999;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror div.CodeMirror-cursor {
+  border-left: 1px solid black;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+  border-left: 1px solid silver;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+  width: auto;
+  border: 0;
+  background: #7e7;
+}
+.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors {
+  z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+  width: auto;
+  border: 0;
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+}
+@-moz-keyframes blink {
+  0% { background: #7e7; }
+  50% { background: none; }
+  100% { background: #7e7; }
+}
+@-webkit-keyframes blink {
+  0% { background: #7e7; }
+  50% { background: none; }
+  100% { background: #7e7; }
+}
+@keyframes blink {
+  0% { background: #7e7; }
+  50% { background: none; }
+  100% { background: #7e7; }
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+div.CodeMirror-overwrite div.CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-ruler {
+  border-left: 1px solid #ccc;
+  position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+   the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+  line-height: 1;
+  position: relative;
+  overflow: hidden;
+  background: white;
+  color: black;
+}
+
+.CodeMirror-scroll {
+  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* See overflow: hidden in .CodeMirror */
+  margin-bottom: -30px; margin-right: -30px;
+  padding-bottom: 30px;
+  height: 100%;
+  outline: none; /* Prevent dragging from highlighting the element */
+  position: relative;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+.CodeMirror-sizer {
+  position: relative;
+  border-right: 30px solid transparent;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+   before actuall scrolling happens, thus preventing shaking and
+   flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  position: absolute;
+  z-index: 6;
+  display: none;
+}
+.CodeMirror-vscrollbar {
+  right: 0; top: 0;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+  bottom: 0; left: 0;
+  overflow-y: hidden;
+  overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+  right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+  left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+  position: absolute; left: 0; top: 0;
+  padding-bottom: 30px;
+  z-index: 3;
+}
+.CodeMirror-gutter {
+  white-space: normal;
+  height: 100%;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  padding-bottom: 30px;
+  margin-bottom: -32px;
+  display: inline-block;
+  /* Hack to make IE7 behave */
+  *zoom:1;
+  *display:inline;
+}
+.CodeMirror-gutter-elt {
+  position: absolute;
+  cursor: default;
+  z-index: 4;
+}
+
+.CodeMirror-lines {
+  cursor: text;
+  min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+  /* Reset some styles that the rest of the page might have set */
+  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+  border-width: 0;
+  background: transparent;
+  font-family: inherit;
+  font-size: inherit;
+  margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+  line-height: inherit;
+  color: inherit;
+  z-index: 2;
+  position: relative;
+  overflow: visible;
+}
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  word-break: normal;
+}
+
+.CodeMirror-linebackground {
+  position: absolute;
+  left: 0; right: 0; top: 0; bottom: 0;
+  z-index: 0;
+}
+
+.CodeMirror-linewidget {
+  position: relative;
+  z-index: 2;
+  overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-wrap .CodeMirror-scroll {
+  overflow-x: hidden;
+}
+
+.CodeMirror-measure {
+  position: absolute;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  visibility: hidden;
+}
+.CodeMirror-measure pre { position: static; }
+
+.CodeMirror div.CodeMirror-cursor {
+  position: absolute;
+  border-right: none;
+  width: 0;
+}
+
+div.CodeMirror-cursors {
+  visibility: hidden;
+  position: relative;
+  z-index: 3;
+}
+.CodeMirror-focused div.CodeMirror-cursors {
+  visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+
+.cm-searching {
+  background: #ffa;
+  background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+  /* Hide the cursor when printing */
+  .CodeMirror div.CodeMirror-cursors {
+    visibility: hidden;
+  }
+}
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }

Разница между файлами не показана из-за своего большого размера
+ 8 - 0
public/static/admin/css/plugins/colorpicker/css/bootstrap-colorpicker.min.css


BIN
public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/alpha-horizontal.png


BIN
public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/alpha.png


BIN
public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/hue-horizontal.png


BIN
public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/hue.png


BIN
public/static/admin/css/plugins/colorpicker/img/bootstrap-colorpicker/saturation.png


Разница между файлами не показана из-за своего большого размера
+ 8 - 0
public/static/admin/css/plugins/cropper/cropper.min.css


+ 231 - 0
public/static/admin/css/plugins/dataTables/dataTables.bootstrap.css

@@ -0,0 +1,231 @@
+div.dataTables_length label {
+    float: left;
+    text-align: left;
+    font-weight: normal;
+}
+
+div.dataTables_length select {
+    width: 75px;
+}
+
+div.dataTables_filter label {
+    float: right;
+    font-weight: normal;
+}
+
+div.dataTables_filter input {
+    width: 16em;
+}
+
+div.dataTables_info {
+    padding-top: 8px;
+}
+
+div.dataTables_paginate {
+    float: right;
+    margin: 0;
+}
+
+div.dataTables_paginate ul.pagination {
+    margin: 2px 0;
+    white-space: nowrap;
+}
+
+table.dataTable,
+table.dataTable td,
+table.dataTable th {
+    -webkit-box-sizing: content-box;
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+}
+
+table.dataTable {
+    clear: both;
+    margin-top: 6px !important;
+    margin-bottom: 6px !important;
+    max-width: none !important;
+}
+
+table.dataTable thead .sorting,
+table.dataTable thead .sorting_asc,
+table.dataTable thead .sorting_desc,
+table.dataTable thead .sorting_asc_disabled,
+table.dataTable thead .sorting_desc_disabled {
+    cursor: pointer;
+}
+
+table.dataTable thead .sorting {
+
+}
+
+table.dataTable thead .sorting_asc {
+    background: url('../images/sort_asc.png') no-repeat center right;
+}
+
+table.dataTable thead .sorting_desc {
+    background: url('../images/sort_desc.png') no-repeat center right;
+}
+
+table.dataTable thead .sorting_asc_disabled {
+}
+
+table.dataTable thead .sorting_desc_disabled {
+}
+
+table.dataTable th:active {
+    outline: none;
+}
+
+/* Scrolling */
+
+div.dataTables_scrollHead table {
+    margin-bottom: 0 !important;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+div.dataTables_scrollHead table thead tr:last-child th:first-child,
+div.dataTables_scrollHead table thead tr:last-child td:first-child {
+    border-bottom-left-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
+}
+
+div.dataTables_scrollBody table {
+    margin-top: 0 !important;
+    margin-bottom: 0 !important;
+    border-top: none;
+}
+
+div.dataTables_scrollBody tbody tr:first-child th,
+div.dataTables_scrollBody tbody tr:first-child td {
+    border-top: none;
+}
+
+div.dataTables_scrollFoot table {
+    margin-top: 0 !important;
+    border-top: none;
+}
+
+/*
+ * TableTools styles
+ */
+
+.table tbody tr.active td,
+.table tbody tr.active th {
+    color: white;
+    background-color: #08C;
+}
+
+.table tbody tr.active:hover td,
+.table tbody tr.active:hover th {
+    background-color: #0075b0 !important;
+}
+
+.table tbody tr.active a {
+    color: white;
+}
+
+.table-striped tbody tr.active:nth-child(odd) td,
+.table-striped tbody tr.active:nth-child(odd) th {
+    background-color: #017ebc;
+}
+
+table.DTTT_selectable tbody tr {
+    cursor: pointer;
+}
+
+div.DTTT .btn {
+    font-size: 12px;
+    color: #333 !important;
+}
+
+div.DTTT .btn:hover {
+    text-decoration: none !important;
+}
+
+ul.DTTT_dropdown.dropdown-menu {
+    z-index: 2003;
+}
+
+ul.DTTT_dropdown.dropdown-menu a {
+    color: #333 !important; /* needed only when demo_page.css is included */
+}
+
+ul.DTTT_dropdown.dropdown-menu li {
+    position: relative;
+}
+
+ul.DTTT_dropdown.dropdown-menu li:hover a {
+    color: white !important;
+    background-color: #0088cc;
+}
+
+div.DTTT_collection_background {
+    z-index: 2002;
+}
+
+/* TableTools information display */
+
+div.DTTT_print_info.modal {
+    height: 150px;
+    margin-top: -75px;
+    text-align: center;
+}
+
+div.DTTT_print_info h6 {
+    margin: 1em;
+    font-size: 28px;
+    font-weight: normal;
+    line-height: 28px;
+}
+
+div.DTTT_print_info p {
+    font-size: 14px;
+    line-height: 20px;
+}
+
+/*
+ * FixedColumns styles
+ */
+
+div.DTFC_LeftHeadWrapper table,
+div.DTFC_LeftFootWrapper table,
+div.DTFC_RightHeadWrapper table,
+div.DTFC_RightFootWrapper table,
+table.DTFC_Cloned tr.even {
+    background-color: white;
+}
+
+div.DTFC_RightHeadWrapper table,
+div.DTFC_LeftHeadWrapper table {
+    margin-bottom: 0 !important;
+    border-top-right-radius: 0 !important;
+    border-bottom-left-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
+}
+
+div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child,
+div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child,
+div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child,
+div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child {
+    border-bottom-left-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
+}
+
+div.DTFC_RightBodyWrapper table,
+div.DTFC_LeftBodyWrapper table {
+    margin-bottom: 0 !important;
+    border-top: none;
+}
+
+div.DTFC_RightBodyWrapper tbody tr:first-child th,
+div.DTFC_RightBodyWrapper tbody tr:first-child td,
+div.DTFC_LeftBodyWrapper tbody tr:first-child th,
+div.DTFC_LeftBodyWrapper tbody tr:first-child td {
+    border-top: none;
+}
+
+div.DTFC_RightFootWrapper table,
+div.DTFC_LeftFootWrapper table {
+    border-top: none;
+}

+ 789 - 0
public/static/admin/css/plugins/datapicker/datepicker3.css

@@ -0,0 +1,789 @@
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datepicker {
+  padding: 4px;
+  border-radius: 4px;
+  direction: ltr;
+  /*.dow {
+		border-top: 1px solid #ddd !important;
+	}*/
+}
+.datepicker-inline {
+  width: 220px;
+}
+.datepicker.datepicker-rtl {
+  direction: rtl;
+}
+.datepicker.datepicker-rtl table tr td span {
+  float: right;
+}
+.datepicker-dropdown {
+  top: 0;
+  left: 0;
+}
+.datepicker-dropdown:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-top: 0;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+}
+.datepicker-dropdown:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #fff;
+  border-top: 0;
+  position: absolute;
+}
+.datepicker-dropdown.datepicker-orient-left:before {
+  left: 6px;
+}
+.datepicker-dropdown.datepicker-orient-left:after {
+  left: 7px;
+}
+.datepicker-dropdown.datepicker-orient-right:before {
+  right: 6px;
+}
+.datepicker-dropdown.datepicker-orient-right:after {
+  right: 7px;
+}
+.datepicker-dropdown.datepicker-orient-top:before {
+  top: -7px;
+}
+.datepicker-dropdown.datepicker-orient-top:after {
+  top: -6px;
+}
+.datepicker-dropdown.datepicker-orient-bottom:before {
+  bottom: -7px;
+  border-bottom: 0;
+  border-top: 7px solid #999;
+}
+.datepicker-dropdown.datepicker-orient-bottom:after {
+  bottom: -6px;
+  border-bottom: 0;
+  border-top: 6px solid #fff;
+}
+.datepicker > div {
+  display: none;
+}
+.datepicker.days div.datepicker-days {
+  display: block;
+}
+.datepicker.months div.datepicker-months {
+  display: block;
+}
+.datepicker.years div.datepicker-years {
+  display: block;
+}
+.datepicker table {
+  margin: 0;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.datepicker table tr td,
+.datepicker table tr th {
+  text-align: center;
+  width: 30px;
+  height: 30px;
+  border-radius: 4px;
+  border: none;
+}
+.table-striped .datepicker table tr td,
+.table-striped .datepicker table tr th {
+  background-color: transparent;
+}
+.datepicker table tr td.day:hover,
+.datepicker table tr td.day.focused {
+  background: #eeeeee;
+  cursor: pointer;
+}
+.datepicker table tr td.old,
+.datepicker table tr td.new {
+  color: #999999;
+}
+.datepicker table tr td.disabled,
+.datepicker table tr td.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td.today,
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today.disabled:hover {
+  color: #000000;
+  background-color: #ffdb99;
+  border-color: #ffb733;
+}
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today:hover:hover,
+.datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today.disabled:hover:hover,
+.datepicker table tr td.today:focus,
+.datepicker table tr td.today:hover:focus,
+.datepicker table tr td.today.disabled:focus,
+.datepicker table tr td.today.disabled:hover:focus,
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.today,
+.open .dropdown-toggle.datepicker table tr td.today:hover,
+.open .dropdown-toggle.datepicker table tr td.today.disabled,
+.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
+  color: #000000;
+  background-color: #ffcd70;
+  border-color: #f59e00;
+}
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.today,
+.open .dropdown-toggle.datepicker table tr td.today:hover,
+.open .dropdown-toggle.datepicker table tr td.today.disabled,
+.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
+  background-image: none;
+}
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today:hover.disabled,
+.datepicker table tr td.today.disabled.disabled,
+.datepicker table tr td.today.disabled:hover.disabled,
+.datepicker table tr td.today[disabled],
+.datepicker table tr td.today:hover[disabled],
+.datepicker table tr td.today.disabled[disabled],
+.datepicker table tr td.today.disabled:hover[disabled],
+fieldset[disabled] .datepicker table tr td.today,
+fieldset[disabled] .datepicker table tr td.today:hover,
+fieldset[disabled] .datepicker table tr td.today.disabled,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today:hover.disabled:hover,
+.datepicker table tr td.today.disabled.disabled:hover,
+.datepicker table tr td.today.disabled:hover.disabled:hover,
+.datepicker table tr td.today[disabled]:hover,
+.datepicker table tr td.today:hover[disabled]:hover,
+.datepicker table tr td.today.disabled[disabled]:hover,
+.datepicker table tr td.today.disabled:hover[disabled]:hover,
+fieldset[disabled] .datepicker table tr td.today:hover,
+fieldset[disabled] .datepicker table tr td.today:hover:hover,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover:hover,
+.datepicker table tr td.today.disabled:focus,
+.datepicker table tr td.today:hover.disabled:focus,
+.datepicker table tr td.today.disabled.disabled:focus,
+.datepicker table tr td.today.disabled:hover.disabled:focus,
+.datepicker table tr td.today[disabled]:focus,
+.datepicker table tr td.today:hover[disabled]:focus,
+.datepicker table tr td.today.disabled[disabled]:focus,
+.datepicker table tr td.today.disabled:hover[disabled]:focus,
+fieldset[disabled] .datepicker table tr td.today:focus,
+fieldset[disabled] .datepicker table tr td.today:hover:focus,
+fieldset[disabled] .datepicker table tr td.today.disabled:focus,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover:focus,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today:hover.disabled:active,
+.datepicker table tr td.today.disabled.disabled:active,
+.datepicker table tr td.today.disabled:hover.disabled:active,
+.datepicker table tr td.today[disabled]:active,
+.datepicker table tr td.today:hover[disabled]:active,
+.datepicker table tr td.today.disabled[disabled]:active,
+.datepicker table tr td.today.disabled:hover[disabled]:active,
+fieldset[disabled] .datepicker table tr td.today:active,
+fieldset[disabled] .datepicker table tr td.today:hover:active,
+fieldset[disabled] .datepicker table tr td.today.disabled:active,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today:hover.disabled.active,
+.datepicker table tr td.today.disabled.disabled.active,
+.datepicker table tr td.today.disabled:hover.disabled.active,
+.datepicker table tr td.today[disabled].active,
+.datepicker table tr td.today:hover[disabled].active,
+.datepicker table tr td.today.disabled[disabled].active,
+.datepicker table tr td.today.disabled:hover[disabled].active,
+fieldset[disabled] .datepicker table tr td.today.active,
+fieldset[disabled] .datepicker table tr td.today:hover.active,
+fieldset[disabled] .datepicker table tr td.today.disabled.active,
+fieldset[disabled] .datepicker table tr td.today.disabled:hover.active {
+  background-color: #ffdb99;
+  border-color: #ffb733;
+}
+.datepicker table tr td.today:hover:hover {
+  color: #000;
+}
+.datepicker table tr td.today.active:hover {
+  color: #fff;
+}
+.datepicker table tr td.range,
+.datepicker table tr td.range:hover,
+.datepicker table tr td.range.disabled,
+.datepicker table tr td.range.disabled:hover {
+  background: #eeeeee;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today,
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today.disabled:hover {
+  color: #000000;
+  background-color: #f7ca77;
+  border-color: #f1a417;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today:hover:hover,
+.datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover:hover,
+.datepicker table tr td.range.today:focus,
+.datepicker table tr td.range.today:hover:focus,
+.datepicker table tr td.range.today.disabled:focus,
+.datepicker table tr td.range.today.disabled:hover:focus,
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.range.today,
+.open .dropdown-toggle.datepicker table tr td.range.today:hover,
+.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
+.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
+  color: #000000;
+  background-color: #f4bb51;
+  border-color: #bf800c;
+}
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.range.today,
+.open .dropdown-toggle.datepicker table tr td.range.today:hover,
+.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
+.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
+  background-image: none;
+}
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today:hover.disabled,
+.datepicker table tr td.range.today.disabled.disabled,
+.datepicker table tr td.range.today.disabled:hover.disabled,
+.datepicker table tr td.range.today[disabled],
+.datepicker table tr td.range.today:hover[disabled],
+.datepicker table tr td.range.today.disabled[disabled],
+.datepicker table tr td.range.today.disabled:hover[disabled],
+fieldset[disabled] .datepicker table tr td.range.today,
+fieldset[disabled] .datepicker table tr td.range.today:hover,
+fieldset[disabled] .datepicker table tr td.range.today.disabled,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today:hover.disabled:hover,
+.datepicker table tr td.range.today.disabled.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover.disabled:hover,
+.datepicker table tr td.range.today[disabled]:hover,
+.datepicker table tr td.range.today:hover[disabled]:hover,
+.datepicker table tr td.range.today.disabled[disabled]:hover,
+.datepicker table tr td.range.today.disabled:hover[disabled]:hover,
+fieldset[disabled] .datepicker table tr td.range.today:hover,
+fieldset[disabled] .datepicker table tr td.range.today:hover:hover,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:hover,
+.datepicker table tr td.range.today.disabled:focus,
+.datepicker table tr td.range.today:hover.disabled:focus,
+.datepicker table tr td.range.today.disabled.disabled:focus,
+.datepicker table tr td.range.today.disabled:hover.disabled:focus,
+.datepicker table tr td.range.today[disabled]:focus,
+.datepicker table tr td.range.today:hover[disabled]:focus,
+.datepicker table tr td.range.today.disabled[disabled]:focus,
+.datepicker table tr td.range.today.disabled:hover[disabled]:focus,
+fieldset[disabled] .datepicker table tr td.range.today:focus,
+fieldset[disabled] .datepicker table tr td.range.today:hover:focus,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:focus,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:focus,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today:hover.disabled:active,
+.datepicker table tr td.range.today.disabled.disabled:active,
+.datepicker table tr td.range.today.disabled:hover.disabled:active,
+.datepicker table tr td.range.today[disabled]:active,
+.datepicker table tr td.range.today:hover[disabled]:active,
+.datepicker table tr td.range.today.disabled[disabled]:active,
+.datepicker table tr td.range.today.disabled:hover[disabled]:active,
+fieldset[disabled] .datepicker table tr td.range.today:active,
+fieldset[disabled] .datepicker table tr td.range.today:hover:active,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:active,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today:hover.disabled.active,
+.datepicker table tr td.range.today.disabled.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.disabled.active,
+.datepicker table tr td.range.today[disabled].active,
+.datepicker table tr td.range.today:hover[disabled].active,
+.datepicker table tr td.range.today.disabled[disabled].active,
+.datepicker table tr td.range.today.disabled:hover[disabled].active,
+fieldset[disabled] .datepicker table tr td.range.today.active,
+fieldset[disabled] .datepicker table tr td.range.today:hover.active,
+fieldset[disabled] .datepicker table tr td.range.today.disabled.active,
+fieldset[disabled] .datepicker table tr td.range.today.disabled:hover.active {
+  background-color: #f7ca77;
+  border-color: #f1a417;
+}
+.datepicker table tr td.selected,
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected.disabled:hover {
+  color: #ffffff;
+  background-color: #999999;
+  border-color: #555555;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected:hover:hover,
+.datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected.disabled:hover:hover,
+.datepicker table tr td.selected:focus,
+.datepicker table tr td.selected:hover:focus,
+.datepicker table tr td.selected.disabled:focus,
+.datepicker table tr td.selected.disabled:hover:focus,
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.selected,
+.open .dropdown-toggle.datepicker table tr td.selected:hover,
+.open .dropdown-toggle.datepicker table tr td.selected.disabled,
+.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
+  color: #ffffff;
+  background-color: #858585;
+  border-color: #373737;
+}
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.selected,
+.open .dropdown-toggle.datepicker table tr td.selected:hover,
+.open .dropdown-toggle.datepicker table tr td.selected.disabled,
+.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
+  background-image: none;
+}
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected:hover.disabled,
+.datepicker table tr td.selected.disabled.disabled,
+.datepicker table tr td.selected.disabled:hover.disabled,
+.datepicker table tr td.selected[disabled],
+.datepicker table tr td.selected:hover[disabled],
+.datepicker table tr td.selected.disabled[disabled],
+.datepicker table tr td.selected.disabled:hover[disabled],
+fieldset[disabled] .datepicker table tr td.selected,
+fieldset[disabled] .datepicker table tr td.selected:hover,
+fieldset[disabled] .datepicker table tr td.selected.disabled,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected:hover.disabled:hover,
+.datepicker table tr td.selected.disabled.disabled:hover,
+.datepicker table tr td.selected.disabled:hover.disabled:hover,
+.datepicker table tr td.selected[disabled]:hover,
+.datepicker table tr td.selected:hover[disabled]:hover,
+.datepicker table tr td.selected.disabled[disabled]:hover,
+.datepicker table tr td.selected.disabled:hover[disabled]:hover,
+fieldset[disabled] .datepicker table tr td.selected:hover,
+fieldset[disabled] .datepicker table tr td.selected:hover:hover,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover:hover,
+.datepicker table tr td.selected.disabled:focus,
+.datepicker table tr td.selected:hover.disabled:focus,
+.datepicker table tr td.selected.disabled.disabled:focus,
+.datepicker table tr td.selected.disabled:hover.disabled:focus,
+.datepicker table tr td.selected[disabled]:focus,
+.datepicker table tr td.selected:hover[disabled]:focus,
+.datepicker table tr td.selected.disabled[disabled]:focus,
+.datepicker table tr td.selected.disabled:hover[disabled]:focus,
+fieldset[disabled] .datepicker table tr td.selected:focus,
+fieldset[disabled] .datepicker table tr td.selected:hover:focus,
+fieldset[disabled] .datepicker table tr td.selected.disabled:focus,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover:focus,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected:hover.disabled:active,
+.datepicker table tr td.selected.disabled.disabled:active,
+.datepicker table tr td.selected.disabled:hover.disabled:active,
+.datepicker table tr td.selected[disabled]:active,
+.datepicker table tr td.selected:hover[disabled]:active,
+.datepicker table tr td.selected.disabled[disabled]:active,
+.datepicker table tr td.selected.disabled:hover[disabled]:active,
+fieldset[disabled] .datepicker table tr td.selected:active,
+fieldset[disabled] .datepicker table tr td.selected:hover:active,
+fieldset[disabled] .datepicker table tr td.selected.disabled:active,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected:hover.disabled.active,
+.datepicker table tr td.selected.disabled.disabled.active,
+.datepicker table tr td.selected.disabled:hover.disabled.active,
+.datepicker table tr td.selected[disabled].active,
+.datepicker table tr td.selected:hover[disabled].active,
+.datepicker table tr td.selected.disabled[disabled].active,
+.datepicker table tr td.selected.disabled:hover[disabled].active,
+fieldset[disabled] .datepicker table tr td.selected.active,
+fieldset[disabled] .datepicker table tr td.selected:hover.active,
+fieldset[disabled] .datepicker table tr td.selected.disabled.active,
+fieldset[disabled] .datepicker table tr td.selected.disabled:hover.active {
+  background-color: #999999;
+  border-color: #555555;
+}
+.datepicker table tr td.active,
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active.disabled:hover {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active:hover:hover,
+.datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active.disabled:hover:hover,
+.datepicker table tr td.active:focus,
+.datepicker table tr td.active:hover:focus,
+.datepicker table tr td.active.disabled:focus,
+.datepicker table tr td.active.disabled:hover:focus,
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.active,
+.open .dropdown-toggle.datepicker table tr td.active:hover,
+.open .dropdown-toggle.datepicker table tr td.active.disabled,
+.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td.active,
+.open .dropdown-toggle.datepicker table tr td.active:hover,
+.open .dropdown-toggle.datepicker table tr td.active.disabled,
+.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
+  background-image: none;
+}
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active:hover.disabled,
+.datepicker table tr td.active.disabled.disabled,
+.datepicker table tr td.active.disabled:hover.disabled,
+.datepicker table tr td.active[disabled],
+.datepicker table tr td.active:hover[disabled],
+.datepicker table tr td.active.disabled[disabled],
+.datepicker table tr td.active.disabled:hover[disabled],
+fieldset[disabled] .datepicker table tr td.active,
+fieldset[disabled] .datepicker table tr td.active:hover,
+fieldset[disabled] .datepicker table tr td.active.disabled,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active:hover.disabled:hover,
+.datepicker table tr td.active.disabled.disabled:hover,
+.datepicker table tr td.active.disabled:hover.disabled:hover,
+.datepicker table tr td.active[disabled]:hover,
+.datepicker table tr td.active:hover[disabled]:hover,
+.datepicker table tr td.active.disabled[disabled]:hover,
+.datepicker table tr td.active.disabled:hover[disabled]:hover,
+fieldset[disabled] .datepicker table tr td.active:hover,
+fieldset[disabled] .datepicker table tr td.active:hover:hover,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover:hover,
+.datepicker table tr td.active.disabled:focus,
+.datepicker table tr td.active:hover.disabled:focus,
+.datepicker table tr td.active.disabled.disabled:focus,
+.datepicker table tr td.active.disabled:hover.disabled:focus,
+.datepicker table tr td.active[disabled]:focus,
+.datepicker table tr td.active:hover[disabled]:focus,
+.datepicker table tr td.active.disabled[disabled]:focus,
+.datepicker table tr td.active.disabled:hover[disabled]:focus,
+fieldset[disabled] .datepicker table tr td.active:focus,
+fieldset[disabled] .datepicker table tr td.active:hover:focus,
+fieldset[disabled] .datepicker table tr td.active.disabled:focus,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover:focus,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active:hover.disabled:active,
+.datepicker table tr td.active.disabled.disabled:active,
+.datepicker table tr td.active.disabled:hover.disabled:active,
+.datepicker table tr td.active[disabled]:active,
+.datepicker table tr td.active:hover[disabled]:active,
+.datepicker table tr td.active.disabled[disabled]:active,
+.datepicker table tr td.active.disabled:hover[disabled]:active,
+fieldset[disabled] .datepicker table tr td.active:active,
+fieldset[disabled] .datepicker table tr td.active:hover:active,
+fieldset[disabled] .datepicker table tr td.active.disabled:active,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active:hover.disabled.active,
+.datepicker table tr td.active.disabled.disabled.active,
+.datepicker table tr td.active.disabled:hover.disabled.active,
+.datepicker table tr td.active[disabled].active,
+.datepicker table tr td.active:hover[disabled].active,
+.datepicker table tr td.active.disabled[disabled].active,
+.datepicker table tr td.active.disabled:hover[disabled].active,
+fieldset[disabled] .datepicker table tr td.active.active,
+fieldset[disabled] .datepicker table tr td.active:hover.active,
+fieldset[disabled] .datepicker table tr td.active.disabled.active,
+fieldset[disabled] .datepicker table tr td.active.disabled:hover.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+.datepicker table tr td span {
+  display: block;
+  width: 23%;
+  height: 54px;
+  line-height: 54px;
+  float: left;
+  margin: 1%;
+  cursor: pointer;
+  border-radius: 4px;
+}
+.datepicker table tr td span:hover {
+  background: #eeeeee;
+}
+.datepicker table tr td span.disabled,
+.datepicker table tr td span.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td span.active,
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active.disabled:hover {
+  color: #ffffff;
+  background-color: #428bca;
+  border-color: #357ebd;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active:hover:hover,
+.datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active.disabled:hover:hover,
+.datepicker table tr td span.active:focus,
+.datepicker table tr td span.active:hover:focus,
+.datepicker table tr td span.active.disabled:focus,
+.datepicker table tr td span.active.disabled:hover:focus,
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td span.active,
+.open .dropdown-toggle.datepicker table tr td span.active:hover,
+.open .dropdown-toggle.datepicker table tr td span.active.disabled,
+.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
+  color: #ffffff;
+  background-color: #3276b1;
+  border-color: #285e8e;
+}
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active,
+.open .dropdown-toggle.datepicker table tr td span.active,
+.open .dropdown-toggle.datepicker table tr td span.active:hover,
+.open .dropdown-toggle.datepicker table tr td span.active.disabled,
+.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
+  background-image: none;
+}
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active:hover.disabled,
+.datepicker table tr td span.active.disabled.disabled,
+.datepicker table tr td span.active.disabled:hover.disabled,
+.datepicker table tr td span.active[disabled],
+.datepicker table tr td span.active:hover[disabled],
+.datepicker table tr td span.active.disabled[disabled],
+.datepicker table tr td span.active.disabled:hover[disabled],
+fieldset[disabled] .datepicker table tr td span.active,
+fieldset[disabled] .datepicker table tr td span.active:hover,
+fieldset[disabled] .datepicker table tr td span.active.disabled,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active:hover.disabled:hover,
+.datepicker table tr td span.active.disabled.disabled:hover,
+.datepicker table tr td span.active.disabled:hover.disabled:hover,
+.datepicker table tr td span.active[disabled]:hover,
+.datepicker table tr td span.active:hover[disabled]:hover,
+.datepicker table tr td span.active.disabled[disabled]:hover,
+.datepicker table tr td span.active.disabled:hover[disabled]:hover,
+fieldset[disabled] .datepicker table tr td span.active:hover,
+fieldset[disabled] .datepicker table tr td span.active:hover:hover,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
+.datepicker table tr td span.active.disabled:focus,
+.datepicker table tr td span.active:hover.disabled:focus,
+.datepicker table tr td span.active.disabled.disabled:focus,
+.datepicker table tr td span.active.disabled:hover.disabled:focus,
+.datepicker table tr td span.active[disabled]:focus,
+.datepicker table tr td span.active:hover[disabled]:focus,
+.datepicker table tr td span.active.disabled[disabled]:focus,
+.datepicker table tr td span.active.disabled:hover[disabled]:focus,
+fieldset[disabled] .datepicker table tr td span.active:focus,
+fieldset[disabled] .datepicker table tr td span.active:hover:focus,
+fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active:hover.disabled:active,
+.datepicker table tr td span.active.disabled.disabled:active,
+.datepicker table tr td span.active.disabled:hover.disabled:active,
+.datepicker table tr td span.active[disabled]:active,
+.datepicker table tr td span.active:hover[disabled]:active,
+.datepicker table tr td span.active.disabled[disabled]:active,
+.datepicker table tr td span.active.disabled:hover[disabled]:active,
+fieldset[disabled] .datepicker table tr td span.active:active,
+fieldset[disabled] .datepicker table tr td span.active:hover:active,
+fieldset[disabled] .datepicker table tr td span.active.disabled:active,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active:hover.disabled.active,
+.datepicker table tr td span.active.disabled.disabled.active,
+.datepicker table tr td span.active.disabled:hover.disabled.active,
+.datepicker table tr td span.active[disabled].active,
+.datepicker table tr td span.active:hover[disabled].active,
+.datepicker table tr td span.active.disabled[disabled].active,
+.datepicker table tr td span.active.disabled:hover[disabled].active,
+fieldset[disabled] .datepicker table tr td span.active.active,
+fieldset[disabled] .datepicker table tr td span.active:hover.active,
+fieldset[disabled] .datepicker table tr td span.active.disabled.active,
+fieldset[disabled] .datepicker table tr td span.active.disabled:hover.active {
+  background-color: #428bca;
+  border-color: #357ebd;
+}
+.datepicker table tr td span.old,
+.datepicker table tr td span.new {
+  color: #999999;
+}
+.datepicker th.datepicker-switch {
+  width: 145px;
+}
+.datepicker thead tr:first-child th,
+.datepicker tfoot tr th {
+  cursor: pointer;
+}
+.datepicker thead tr:first-child th:hover,
+.datepicker tfoot tr th:hover {
+  background: #eeeeee;
+}
+.datepicker .cw {
+  font-size: 10px;
+  width: 12px;
+  padding: 0 2px 0 5px;
+  vertical-align: middle;
+}
+.datepicker thead tr:first-child th.cw {
+  cursor: default;
+  background-color: transparent;
+}
+.input-group.date .input-group-addon i {
+  cursor: pointer;
+  width: 16px;
+  height: 16px;
+}
+.input-daterange input {
+  text-align: center;
+}
+.input-daterange input:first-child {
+  border-radius: 3px 0 0 3px;
+}
+.input-daterange input:last-child {
+  border-radius: 0 3px 3px 0;
+}
+.input-daterange .input-group-addon {
+  width: auto;
+  min-width: 16px;
+  padding: 4px 5px;
+  font-weight: normal;
+  line-height: 1.428571429;
+  text-align: center;
+  text-shadow: 0 1px 0 #fff;
+  vertical-align: middle;
+  background-color: #eeeeee;
+  border-width: 1px 0;
+  margin-left: -5px;
+  margin-right: -5px;
+}
+.datepicker.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  float: left;
+  display: none;
+  min-width: 160px;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 5px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding;
+  background-clip: padding-box;
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+  color: #333333;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  line-height: 1.428571429;
+}
+.datepicker.dropdown-menu th,
+.datepicker.dropdown-menu td {
+  padding: 4px 5px;
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов