edit.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <template>
  2. <div class="w-main docs-edit">
  3. <v-title>{{$L('文档编辑')}}-{{$L('轻量级的团队在线协作')}}</v-title>
  4. <div class="edit-box">
  5. <div class="edit-header">
  6. <div class="header-menu active" @click="handleClick('back')"><Icon type="md-arrow-back" /></div>
  7. <div class="header-menu" @click="handleClick('menu')"><Icon type="md-menu" /></div>
  8. <!--<div class="header-menu" @click="handleClick('share')"><Icon type="md-share" /></div>
  9. <div class="header-menu" @click="handleClick('view')"><Icon type="md-eye" /></div>-->
  10. <div class="header-menu" @click="handleClick('history')"><Icon type="md-time" /></div>
  11. <Poptip class="header-menu synch">
  12. <Icon type="md-contacts" :title="$L('正在协作会员')"/><em v-if="synchUsers.length > 0">{{synchUsers.length}}</em>
  13. <ul class="synch-lists" slot="content">
  14. <li class="title">{{$L('正在协作会员')}}:</li>
  15. <li v-for="item in synchUsersS">
  16. <img class="synch-userimg" :src="item.userimg"/>
  17. <user-view class="synch-username" placement="right" :username="item.username"/>
  18. </li>
  19. </ul>
  20. </Poptip>
  21. <div class="header-title">{{docDetail.title}}</div>
  22. <div v-if="docDetail.type=='mind'" class="header-hint">{{$L('选中节点,按enter键添加子节点,tab键添加同级节点')}}</div>
  23. <Button :disabled="(disabledBtn || loadIng > 0) && hid == 0" class="header-button" size="small" type="primary" @click="handleClick('save')">{{$L('保存')}}</Button>
  24. </div>
  25. <div class="docs-body">
  26. <t-editor v-if="docDetail.type=='document'" class="body-text" v-model="docContent.content" height="100%"></t-editor>
  27. <minder v-else-if="docDetail.type=='mind'" class="body-mind" v-model="docContent"></minder>
  28. <sheet v-else-if="docDetail.type=='sheet'" class="body-sheet" v-model="docContent.content"></sheet>
  29. <flow v-else-if="docDetail.type=='flow'" class="body-flow" v-model="docContent.content"></flow>
  30. </div>
  31. </div>
  32. <WDrawer v-model="docDrawerShow" maxWidth="450">
  33. <Tabs v-if="docDrawerShow" v-model="docDrawerTab">
  34. <TabPane :label="$L('知识库目录')" name="menu">
  35. <nested-draggable :lists="sectionLists" :readonly="true" :activeid="sid" @change="handleSection"></nested-draggable>
  36. <div v-if="sectionLists.length == 0" style="color:#888;padding:32px;text-align:center">{{sectionNoDataText}}</div>
  37. </TabPane>
  38. <TabPane :label="$L('文档历史版本')" name="history">
  39. <Table class="tableFill" :columns="historyColumns" :data="historyLists" :no-data-text="historyNoDataText" size="small" stripe></Table>
  40. </TabPane>
  41. </Tabs>
  42. </WDrawer>
  43. </div>
  44. </template>
  45. <style lang="scss">
  46. .docs-edit {
  47. .body-text {
  48. .teditor-loadedstyle {
  49. .tox-tinymce {
  50. border: 0;
  51. border-radius: 0;
  52. }
  53. .tox-mbtn {
  54. height: 28px;
  55. }
  56. .tox-menubar,
  57. .tox-toolbar-overlord {
  58. padding: 0 12%;
  59. background: #f9f9f9;
  60. }
  61. .tox-toolbar__overflow,
  62. .tox-toolbar__primary {
  63. background: none !important;
  64. border-top: 1px solid #eaeaea !important;
  65. }
  66. .tox-toolbar-overlord {
  67. border-bottom: 1px solid #E9E9E9 !important;
  68. }
  69. .tox-toolbar__group:not(:last-of-type) {
  70. border-right: 1px solid #eaeaea !important;
  71. }
  72. .tox-sidebar-wrap {
  73. margin: 22px 12%;
  74. border: 1px solid #e8e8e8;
  75. border-radius: 2px;
  76. box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.08);
  77. .tox-edit-area {
  78. border-top: 0;
  79. }
  80. }
  81. .tox-statusbar {
  82. border-top: 1px solid #E9E9E9;
  83. .tox-statusbar__resize-handle {
  84. display: none;
  85. }
  86. }
  87. }
  88. }
  89. .body-sheet {
  90. box-sizing: content-box;
  91. * {
  92. box-sizing: content-box;
  93. }
  94. }
  95. }
  96. </style>
  97. <style lang="scss" scoped>
  98. .docs-edit {
  99. .edit-box {
  100. display: flex;
  101. flex-direction: column;
  102. position: absolute;
  103. width: 100%;
  104. height: 100%;
  105. .edit-header {
  106. display: flex;
  107. flex-direction: row;
  108. align-items: center;
  109. width: 100%;
  110. height: 38px;
  111. background-color: #ffffff;
  112. box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.1);
  113. position: relative;
  114. z-index: 9;
  115. .header-menu {
  116. width: 50px;
  117. height: 100%;
  118. text-align: center;
  119. display: flex;
  120. align-items: center;
  121. justify-content: center;
  122. margin-right: 3px;
  123. cursor: pointer;
  124. color: #777777;
  125. position: relative;
  126. .ivu-icon {
  127. font-size: 16px;
  128. }
  129. &.synch {
  130. em {
  131. padding-left: 2px;
  132. }
  133. }
  134. &:hover,
  135. &.active {
  136. color: #fff;
  137. background: #059DFD;
  138. }
  139. .synch-lists {
  140. max-height: 500px;
  141. overflow: auto;
  142. li {
  143. display: flex;
  144. align-items: center;
  145. padding: 6px 0;
  146. border-bottom: 1px dashed #eeeeee;
  147. &.title {
  148. font-size: 14px;
  149. font-weight: 600;
  150. color: #333333;
  151. }
  152. .synch-userimg {
  153. width: 24px;
  154. height: 24px;
  155. border-radius: 50%;
  156. }
  157. .synch-username {
  158. padding-left: 8px;
  159. font-size: 14px;
  160. color: #555555;
  161. }
  162. }
  163. }
  164. }
  165. .header-title {
  166. flex: 1;
  167. color: #333333;
  168. border-left: 1px solid #ddd;
  169. margin-left: 5px;
  170. padding-left: 24px;
  171. padding-right: 24px;
  172. font-size: 16px;
  173. white-space: nowrap;
  174. }
  175. .header-hint {
  176. padding-right: 22px;
  177. font-size: 12px;
  178. color: #666;
  179. white-space: nowrap;
  180. }
  181. .header-button {
  182. font-size: 12px;
  183. margin-right: 12px;
  184. }
  185. }
  186. .docs-body {
  187. flex: 1;
  188. width: 100%;
  189. position: relative;
  190. .body-text {
  191. display: flex;
  192. width: 100%;
  193. height: 100%;
  194. .teditor-loadedstyle {
  195. height: 100%;
  196. }
  197. }
  198. }
  199. }
  200. }
  201. </style>
  202. <script>
  203. import Vue from 'vue'
  204. import minder from '../../components/docs/minder'
  205. Vue.use(minder)
  206. const TEditor = resolve => require(['../../components/TEditor'], resolve);
  207. const Sheet = resolve => require(['../../components/docs/sheet/index'], resolve);
  208. const Flow = resolve => require(['../../components/docs/flow/index'], resolve);
  209. const NestedDraggable = resolve => require(['../../components/docs/NestedDraggable'], resolve);
  210. const WDrawer = resolve => require(['../../components/iview/WDrawer'], resolve);
  211. export default {
  212. components: {WDrawer, Flow, Sheet, TEditor, NestedDraggable},
  213. data () {
  214. return {
  215. loadIng: 0,
  216. sid: 0,
  217. hid: 0,
  218. docDetail: { },
  219. docContent: { },
  220. bakContent: null,
  221. docDrawerShow: false,
  222. docDrawerTab: '',
  223. sectionLists: [],
  224. sectionNoDataText: "",
  225. historyColumns: [],
  226. historyLists: [],
  227. historyNoDataText: "",
  228. userInfo: {},
  229. routeName: '',
  230. synergyNum: 0,
  231. synchUsers: [],
  232. }
  233. },
  234. created() {
  235. this.historyColumns = [{
  236. "title": this.$L("存档日期"),
  237. "minWidth": 160,
  238. "maxWidth": 200,
  239. render: (h, params) => {
  240. return h('span', $A.formatDate("Y-m-d H:i:s", params.row.indate));
  241. }
  242. }, {
  243. "title": this.$L("操作员"),
  244. "key": 'username',
  245. "minWidth": 80,
  246. "maxWidth": 130,
  247. render: (h, params) => {
  248. return h('UserView', {
  249. props: {
  250. username: params.row.username
  251. }
  252. });
  253. }
  254. }, {
  255. "title": " ",
  256. "key": 'action',
  257. "width": 80,
  258. "align": 'center',
  259. render: (h, params) => {
  260. if (this.hid == params.row.id || (this.hid == 0 && params.index == 0)) {
  261. return h('Icon', {
  262. props: { type: 'md-checkmark' },
  263. style: { marginRight: '6px', fontSize: '16px', color: '#FF5722' },
  264. });
  265. }
  266. return h('Button', {
  267. props: {
  268. type: 'text',
  269. size: 'small'
  270. },
  271. style: {
  272. fontSize: '12px'
  273. },
  274. on: {
  275. click: () => {
  276. let data = {sid: this.getSid() + "-" + params.row.id, other: this.$route.params.other}
  277. if (params.index == 0) {
  278. data.sid = this.getSid();
  279. }
  280. this.goForward({name: 'docs-edit', params: data }, true);
  281. this.refreshSid();
  282. this.docDrawerShow = false;
  283. }
  284. }
  285. }, this.$L('还原'));
  286. }
  287. }];
  288. },
  289. mounted() {
  290. this.routeName = this.$route.name;
  291. this.userInfo = $A.getUserInfo((res, isLogin) => {
  292. if (this.userInfo.id != res.id) {
  293. this.userInfo = res;
  294. }
  295. }, false);
  296. },
  297. activated() {
  298. this.refreshSid();
  299. this.synergy();
  300. },
  301. deactivated() {
  302. this.docDrawerShow = false;
  303. if ($A.getToken() === false) {
  304. this.sid = 0;
  305. }
  306. },
  307. watch: {
  308. sid(val) {
  309. if (!val) {
  310. this.goBack();
  311. return;
  312. }
  313. this.hid = $A.runNum($A.strExists(val, '-') ? $A.getMiddle(val, "-", null) : 0);
  314. this.docDetail = { };
  315. this.docContent = { };
  316. this.bakContent = null;
  317. this.getDetail();
  318. },
  319. docDrawerTab(act) {
  320. switch (act) {
  321. case "menu":
  322. if (!this.sectionNoDataText) {
  323. this.sectionNoDataText = this.$L("数据加载中.....");
  324. let bookid = this.docDetail.bookid;
  325. $A.aAjax({
  326. url: 'docs/section/lists',
  327. data: {
  328. bookid: bookid
  329. },
  330. error: () => {
  331. if (bookid != this.docDetail.bookid) {
  332. return;
  333. }
  334. this.sectionNoDataText = this.$L("数据加载失败!");
  335. },
  336. success: (res) => {
  337. if (bookid != this.docDetail.bookid) {
  338. return;
  339. }
  340. if (res.ret === 1) {
  341. this.sectionLists = res.data;
  342. this.sectionNoDataText = this.$L("没有相关的数据");
  343. }else{
  344. this.sectionLists = [];
  345. this.sectionNoDataText = res.msg;
  346. }
  347. }
  348. });
  349. }
  350. break;
  351. case "history":
  352. if (!this.historyNoDataText) {
  353. this.historyNoDataText = this.$L("数据加载中.....");
  354. let sid = this.getSid();
  355. $A.aAjax({
  356. url: 'docs/section/history',
  357. data: {
  358. id: sid,
  359. pagesize: 50
  360. },
  361. error: () => {
  362. if (sid != this.getSid()) {
  363. return;
  364. }
  365. this.historyNoDataText = this.$L("数据加载失败!");
  366. },
  367. success: (res) => {
  368. if (sid != this.getSid()) {
  369. return;
  370. }
  371. if (res.ret === 1) {
  372. this.historyLists = res.data;
  373. this.historyNoDataText = this.$L("没有相关的数据");
  374. }else{
  375. this.historyLists = [];
  376. this.historyNoDataText = res.msg;
  377. }
  378. }
  379. });
  380. }
  381. break;
  382. }
  383. }
  384. },
  385. computed: {
  386. disabledBtn() {
  387. return this.bakContent == $A.jsonStringify(this.docContent);
  388. },
  389. synchUsersS() {
  390. let temp = Math.round(new Date().getTime() / 1000);
  391. return this.synchUsers.filter(item => {
  392. return item.indate + 10 > temp;
  393. });
  394. }
  395. },
  396. methods: {
  397. synergy() {
  398. if (this.routeName !== this.$route.name) {
  399. let tmpNum = this.synergyNum;
  400. setTimeout(() => {
  401. if (tmpNum === this.synergyNum) {
  402. this.synergyNum++;
  403. this.synergy();
  404. }
  405. }, 5000);
  406. return;
  407. }
  408. $A.WSOB.sendTo('docs', null, {
  409. type: 'enter',
  410. sid: this.sid,
  411. username: this.userInfo.username,
  412. userimg: this.userInfo.userimg,
  413. indate: Math.round(new Date().getTime() / 1000),
  414. }, (res) => {
  415. this.synchUsers = res.status === 1 ? res.message : [];
  416. let tmpNum = this.synergyNum;
  417. setTimeout(() => {
  418. if (tmpNum === this.synergyNum) {
  419. this.synergyNum++;
  420. this.synergy();
  421. }
  422. }, 5000);
  423. });
  424. },
  425. refreshSid() {
  426. this.sid = this.$route.params.sid;
  427. if (typeof this.$route.params.other === "object") {
  428. this.$set(this.docDetail, 'title', $A.getObject(this.$route.params.other, 'title'))
  429. }
  430. },
  431. getSid() {
  432. return $A.runNum($A.getMiddle(this.sid, null, '-'));
  433. },
  434. getDetail() {
  435. this.loadIng++;
  436. $A.aAjax({
  437. url: 'docs/section/content',
  438. data: {
  439. id: this.sid,
  440. },
  441. complete: () => {
  442. this.loadIng--;
  443. },
  444. error: () => {
  445. this.goBack();
  446. alert(this.$L('网络繁忙,请稍后再试!'));
  447. },
  448. success: (res) => {
  449. if (res.ret === 1) {
  450. this.docDetail = res.data;
  451. this.docContent = $A.jsonParse(res.data.content);
  452. this.bakContent = $A.jsonStringify(this.docContent);
  453. } else {
  454. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  455. this.goBack();
  456. }
  457. }
  458. });
  459. },
  460. handleSection(act, detail) {
  461. if (act === 'open') {
  462. this.goForward({name: 'docs-edit', params: {sid: detail.id, other: detail || {}}}, true);
  463. this.refreshSid();
  464. this.docDrawerShow = false;
  465. }
  466. },
  467. handleClick(act) {
  468. switch (act) {
  469. case "back":
  470. if (this.bakContent == $A.jsonStringify(this.docContent) && this.hid == 0) {
  471. this.goBack();
  472. return;
  473. }
  474. this.$Modal.confirm({
  475. title: this.$L('温馨提示'),
  476. content: this.$L('是否放弃修改的内容返回?'),
  477. loading: true,
  478. cancelText: this.$L('放弃保存'),
  479. onCancel: () => {
  480. this.goBack();
  481. },
  482. okText: this.$L('保存并返回'),
  483. onOk: () => {
  484. this.bakContent = $A.jsonStringify(this.docContent);
  485. $A.aAjax({
  486. url: 'docs/section/save?id=' + this.getSid(),
  487. method: 'post',
  488. data: {
  489. D: Object.assign(this.docDetail, {content: this.bakContent})
  490. },
  491. error: () => {
  492. this.$Modal.remove();
  493. alert(this.$L('网络繁忙,请稍后再试!'));
  494. },
  495. success: (res) => {
  496. this.$Modal.remove();
  497. this.goBack();
  498. setTimeout(() => {
  499. if (res.ret === 1) {
  500. this.$Message.success(res.msg);
  501. this.historyNoDataText = '';
  502. } else {
  503. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  504. }
  505. }, 350);
  506. }
  507. });
  508. }
  509. });
  510. break;
  511. case "save":
  512. this.bakContent = $A.jsonStringify(this.docContent);
  513. $A.aAjax({
  514. url: 'docs/section/save?id=' + this.getSid(),
  515. method: 'post',
  516. data: {
  517. D: Object.assign(this.docDetail, {content: this.bakContent})
  518. },
  519. error: () => {
  520. alert(this.$L('网络繁忙,保存失败!'));
  521. },
  522. success: (res) => {
  523. if (res.ret === 1) {
  524. this.$Message.success(res.msg);
  525. this.historyNoDataText = '';
  526. } else {
  527. this.$Modal.error({title: this.$L('温馨提示'), content: res.msg});
  528. }
  529. }
  530. });
  531. break;
  532. case "menu":
  533. case "history":
  534. this.docDrawerTab = act;
  535. this.docDrawerShow = true
  536. break;
  537. case "share":
  538. case "view":
  539. this.$Message.info(this.$L("敬请期待!"));
  540. break;
  541. }
  542. }
  543. },
  544. }
  545. </script>