memos.html 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <meta
  6. name="viewport"
  7. content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
  8. />
  9. <title>memos记录</title>
  10. <script src="js/util.js"></script>
  11. <script src="js/login.js"></script>
  12. <link rel="stylesheet" href="memos.css"/>
  13. <script src="js/vue.min.js"></script>
  14. <script src="elementUI/index.js"></script>
  15. <link rel="stylesheet" href="elementUI/index.css"/>
  16. <link rel="stylesheet" href="css/vditor.css"/>
  17. <script src="js/vditor.js"></script>
  18. </head>
  19. <body>
  20. <div id="app">
  21. <div id="all" class="main">
  22. <div class="myTop">
  23. <div class="main">
  24. <el-link href="https://memos.tianyunperfect.cn/">memos主页</el-link>
  25. <el-button @click="page = page - 1;search()" :disabled="page === 1"
  26. >上一页
  27. </el-button
  28. >
  29. <el-input
  30. type="number"
  31. style="width: 80px"
  32. v-model="page"
  33. @change="search"
  34. ></el-input>
  35. <el-button @click="page = page + 1;search()">下一页</el-button>
  36. <el-select
  37. v-model="size"
  38. placeholder="请选择"
  39. style="width: 80px"
  40. @change="search"
  41. >
  42. <el-option
  43. v-for="item in [10, 20, 50, 100]"
  44. :key="item"
  45. :label="item"
  46. :value="item"
  47. >
  48. </el-option>
  49. </el-select>
  50. <!--输入框,绑定回车事件,触发查询-->
  51. <el-input
  52. style="margin-left: 30px; width: 20%; padding-left: 10px"
  53. placeholder="关键字"
  54. v-model="searchStr"
  55. @change="searchStrChange"
  56. ></el-input>
  57. <el-input
  58. style="margin-left: 30px; width: 30%; padding-left: 10px"
  59. placeholder="AI 问答"
  60. v-model="searchStr2"
  61. @change="searchStrChange2"
  62. ></el-input>
  63. <br/><br/>
  64. <div></div>
  65. <div style="display: flex; align-items: stretch; width: 90%">
  66. <!-- 创建一个用于编辑的容器 -->
  67. <el-input
  68. @paste.native="handlePaste"
  69. @keydown.enter.native="keyDown"
  70. type="textarea"
  71. :autosize="{minRows:2}"
  72. placeholder="记录内容"
  73. @input="localStorage.setItem('contentStr', contentStr)"
  74. v-model="contentStr"
  75. >
  76. </el-input>
  77. <!-- 添加发送按钮 -->
  78. <el-button
  79. style="margin-left: 20px; width: 10%; font-size: 2rem"
  80. @click="sendData"
  81. :disabled="!sendBtnAble"
  82. >记录
  83. </el-button
  84. >
  85. <el-button
  86. style="margin-left: 20px; width: 10%; font-size: 1rem"
  87. @click="mergeByIds"
  88. :disabled="selectedIds.length<=1"
  89. >合并
  90. </el-button
  91. >
  92. <div
  93. @click="dialogVisible2=true"
  94. style="font-size: 0.3em; margin-left: 10px;margin-top: 10px"
  95. >
  96. 替换
  97. </div>
  98. </div>
  99. <div style="display: flex">
  100. <div v-for="item in tmpFileIds">
  101. <el-image
  102. v-if="item['type'].startsWith('image')"
  103. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=150'"
  104. :preview-src-list="['https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=750']"
  105. class="img"
  106. ></el-image>
  107. <audio controls v-if="item['type'].startsWith('audio')">
  108. <source
  109. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  110. type="audio/ogg"
  111. />
  112. <source
  113. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  114. type="audio/mpeg"
  115. />
  116. 您的浏览器不支持 audio 元素。
  117. </audio>
  118. <video
  119. width="320"
  120. height="240"
  121. controls
  122. v-if="item['type'].startsWith('video')"
  123. >
  124. <source
  125. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  126. type="video/mp4"
  127. />
  128. <source
  129. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  130. type="video/ogg"
  131. />
  132. 您的浏览器不支持 HTML5 video 元素。
  133. </video>
  134. <el-link
  135. v-if="item['type'].startsWith('application')"
  136. :href="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  137. target="_blank"
  138. >{{item['filename']}}
  139. </el-link
  140. >
  141. <div @click="removeTmp(item)">删除</div>
  142. </div>
  143. </div>
  144. <!-- 添加多选按钮 -->
  145. <div style="display: flex">
  146. <div style="max-width: 55vw; height: 2.5em; display: flex;align-items: center">
  147. <el-checkbox-group v-model="tagChecked" @change="checkChange1()">
  148. <el-checkbox-button
  149. v-for="tag in allTags"
  150. :label="tag"
  151. ></el-checkbox-button>
  152. </el-checkbox-group>
  153. </div>
  154. <div style="margin-left: 20px; display: flex; align-items: center">
  155. <el-switch
  156. @change="duoxuanChange"
  157. v-model="duoxuan"
  158. active-text="多选"
  159. inactive-text="单选"
  160. >
  161. </el-switch>
  162. </div>
  163. </div>
  164. <div id="tags"></div>
  165. </div>
  166. </div>
  167. <div style="margin-top: 180px"></div>
  168. <div v-if="searchStr2" style="margin: 10px; padding: 10px">
  169. <el-input
  170. v-model="searchResStr"
  171. type="textarea"
  172. :autosize="{minRows: 1}"
  173. readonly="true"
  174. ></el-input>
  175. </div>
  176. <div>
  177. <div v-for="item in contentList" class="myLine">
  178. <el-row style="margin: 10px; padding: 10px" class="border">
  179. <el-col :span="18" class="contentLine">
  180. <div style="display: flex">
  181. <!-- 日期字符串-->
  182. <div style="font-family: ui-sans-serif, system-ui; display: flex;align-items: center;color: grey;font-size: 0.9em">
  183. {{ item.created_ts.replace('T', ' ')}}
  184. </div>
  185. <div style="margin-left: 20px">
  186. <el-checkbox-button
  187. v-for="tag in commonTags"
  188. :label="tag"
  189. :key="item"
  190. @change="checked=>checkChange(checked,item,tag)"
  191. :checked="isChooseTag(item,tag)"
  192. >{{tag}}
  193. </el-checkbox-button>
  194. </div>
  195. </div>
  196. <div class="myText" @dblclick="edit(item)">
  197. <!-- <el-input v-model="item.content" type="textarea" :autosize="{minRows: 1}" readonly="true"></el-input>-->
  198. <div :id="item.resource_name"></div>
  199. </div>
  200. <div style="display: flex">
  201. <div v-for="item in resourceMap[item.id]">
  202. <el-image
  203. v-if="item['type'].startsWith('image')"
  204. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=150'"
  205. :preview-src-list="['https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=750']"
  206. fit="cover"
  207. class="img"
  208. >
  209. </el-image>
  210. <audio controls v-if="item['type'].startsWith('audio')">
  211. <source
  212. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  213. type="audio/ogg"
  214. />
  215. <source
  216. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  217. type="audio/mpeg"
  218. />
  219. 您的浏览器不支持 audio 元素。
  220. </audio>
  221. <video
  222. width="320"
  223. height="240"
  224. controls
  225. v-if="item['type'].startsWith('video')"
  226. >
  227. <source
  228. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  229. type="video/mp4"
  230. />
  231. <source
  232. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  233. type="video/ogg"
  234. />
  235. 您的浏览器不支持 HTML5 video 元素。
  236. </video>
  237. <el-link
  238. v-if="item['type'].startsWith('application')"
  239. :href="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  240. target="_blank"
  241. >{{item['filename']}}
  242. </el-link
  243. >
  244. </div>
  245. </div>
  246. </el-col>
  247. <el-col :span="5" style="display: flex; justify-content: flex-end">
  248. <div style="display: flex; flex-direction: column; gap: 8px">
  249. <el-button
  250. size="mini"
  251. type="info"
  252. icon="el-icon-link"
  253. @click="window.open('https://memos.tianyunperfect.cn/m/' + item.resource_name)"
  254. style="width: 80px"
  255. >跳转
  256. </el-button>
  257. <el-button
  258. size="mini"
  259. type="info"
  260. icon="el-icon-folder-opened"
  261. @click="archive(item)"
  262. style="width: 80px; margin-left: 0"
  263. >归档
  264. </el-button>
  265. <el-button
  266. size="mini"
  267. :type="item.pendingDelete ? 'success' : 'danger'"
  268. :icon="item.pendingDelete ? 'el-icon-refresh' : 'el-icon-delete'"
  269. @click="del(item)"
  270. style="width: 80px; margin-left: 0"
  271. >
  272. {{ item.pendingDelete ? `(${item.countdown}s)` : '删除' }}
  273. </el-button>
  274. </div>
  275. <el-checkbox
  276. :value="selectedIds.includes(item.id)"
  277. @change="handleCheckboxChange(item.id, $event)"
  278. style="margin-top: 6px; margin-left: 20px"
  279. >合并
  280. </el-checkbox>
  281. </el-col>
  282. </el-row>
  283. </div>
  284. </div>
  285. </div>
  286. <el-dialog title="提示" :visible.sync="dialogVisible" width="60%">
  287. <div v-if="tmpItem.id" class="contentLine">
  288. <div>
  289. <el-date-picker
  290. v-model="tmpItem.created_ts"
  291. type="datetime"
  292. value-format="yyyy-MM-ddTHH:mm:ss"
  293. placeholder="选择日期时间"
  294. >
  295. </el-date-picker>
  296. <!-- el-input 只处理粘贴事件 -->
  297. <el-input
  298. @paste.native="handlePaste2"
  299. placeholder="粘贴图片"
  300. style="width: 50%"
  301. >
  302. </el-input>
  303. </div>
  304. <div>
  305. <div id="vditor"></div>
  306. <!-- <el-input
  307. ref="contentEdit"
  308. v-model="tmpItem.content"
  309. type="textarea"
  310. :autosize="{minRows: 2}"
  311. ></el-input> -->
  312. </div>
  313. <div style="display: flex">
  314. <div v-for="item in cloneFileIds">
  315. <el-image
  316. v-if="item['type'].startsWith('image')"
  317. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=150'"
  318. :preview-src-list="['https://memos_assert.tianyunperfect.cn/' + item['internal_path'] + '?width=750']"
  319. class="img"
  320. >
  321. </el-image>
  322. <audio controls v-if="item['type'].startsWith('audio')">
  323. <source
  324. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  325. type="audio/ogg"
  326. />
  327. <source
  328. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  329. type="audio/mpeg"
  330. />
  331. 您的浏览器不支持 audio 元素。
  332. </audio>
  333. <video
  334. width="320"
  335. height="240"
  336. controls
  337. v-if="item['type'].startsWith('video')"
  338. >
  339. <source
  340. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  341. type="video/mp4"
  342. />
  343. <source
  344. :src="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  345. type="video/ogg"
  346. />
  347. 您的浏览器不支持 HTML5 video 元素。
  348. </video>
  349. <el-link
  350. v-if="item['type'].startsWith('application')"
  351. :href="'https://memos_assert.tianyunperfect.cn/' + item['internal_path']"
  352. target="_blank"
  353. >{{item['filename']}}
  354. </el-link
  355. >
  356. <div @click="delPic(item)">删除</div>
  357. </div>
  358. </div>
  359. </div>
  360. <span slot="footer" class="dialog-footer">
  361. <el-button @click="dialogVisible = false">取 消</el-button>
  362. <el-button type="primary" @click="confirmSubmit">确 定</el-button>
  363. </span>
  364. </el-dialog>
  365. <el-dialog
  366. title="替换(危险操作,不可撤销)"
  367. :visible.sync="dialogVisible2"
  368. width="60%"
  369. >
  370. <div class="contentLine">
  371. <div>
  372. <el-input v-model="old_text" placeholder="old"></el-input>
  373. <el-input v-model="new_text" placeholder="new"></el-input>
  374. </div>
  375. </div>
  376. <span slot="footer" class="dialog-footer">
  377. <el-button @click="dialogVisible2 = false">取 消</el-button>
  378. <el-button type="primary" @click="confirmSubmit2">确 定</el-button>
  379. </span>
  380. </el-dialog>
  381. </div>
  382. <!--异步请求示例:requestUtil.sync('https://jsonplaceholder.typicode.com/posts/1', 'post', data, headers) .then(data => console.log(data))-->
  383. <script>
  384. let myHeaders = {
  385. "Content-type": "application/json",
  386. Authorization: "",
  387. };
  388. function getRandomId() {
  389. return Math.random().toString(36).substr(2);
  390. }
  391. let vm = new Vue({
  392. el: "#app",
  393. data: {
  394. contentEditor: "", // vditor实例
  395. dialogVisible2: false,
  396. old_text: "",
  397. new_text: "",
  398. searchStr2: "",
  399. searchResStr: "",
  400. selectedIds: [],
  401. duoxuan: false,
  402. tmpItem: {},
  403. dialogVisible: false,
  404. page: 1,
  405. size: 20,
  406. searchStr: "",
  407. contentStr: "",
  408. tagChecked: [],
  409. allTags: [],
  410. contentList: [],
  411. sendBtnAble: true,
  412. resourceMap: {}, // 资源map,
  413. commonTags: ["todo"],
  414. upload_url: "https://memos.tianyunperfect.cn/api/v1/resource/blob",
  415. tmpFileIds: [],
  416. cloneFileIds: [],
  417. },
  418. watch: {
  419. page: function () {
  420. this.search();
  421. },
  422. size: function () {
  423. this.search();
  424. },
  425. contentList: function () {
  426. console.log(1);
  427. this.$nextTick(() => {
  428. console.log(2);
  429. this.contentList.forEach((item) => {
  430. this.resetView(item);
  431. });
  432. });
  433. },
  434. },
  435. mounted() {
  436. if (!checkLogin()) {
  437. // 警告并刷新页面
  438. alert("登录失败,请先登录");
  439. location.reload();
  440. }
  441. myHeaders.Authorization = "bearer " + getCookie("token");
  442. if (this.getQueryStr()) {
  443. this.searchStr = this.getQueryStr();
  444. }
  445. this.duoxuan = this.getDuoXuan();
  446. // 初始化contentStr,如果本地有缓存
  447. let item = localStorage.getItem("contentStr");
  448. if (item) {
  449. this.contentStr = item;
  450. }
  451. let queryStr = this.getQueryStr();
  452. if (queryStr) {
  453. // queryStr 解码
  454. queryStr = decodeURIComponent(queryStr);
  455. this.searchStr = queryStr;
  456. }
  457. // 解码
  458. let tagsStr = decodeURIComponent(getQueryString("tags"));
  459. if (tagsStr) {
  460. this.tagChecked = tagsStr.split(",");
  461. }
  462. // this.getAllTags();
  463. this.search();
  464. requestUtil
  465. .async(
  466. "https://web_history.tianyunperfect.cn/memos/getCustomMemoTags",
  467. "get"
  468. )
  469. .then((res) => {
  470. this.commonTags = JSON.parse(res["res"][0]["content"]);
  471. this.allTags = this.commonTags;
  472. });
  473. },
  474. methods: {
  475. resetView(item) {
  476. Vditor.preview(
  477. document.getElementById(item.resource_name),
  478. item.content,
  479. {
  480. cdn: "https://ld246.com/js/lib/vditor",
  481. speech: {
  482. enable: false,
  483. },
  484. anchor: 1,
  485. after() {
  486. },
  487. }
  488. );
  489. },
  490. mergeByIds() {
  491. if (this.selectedIds.length < 2) {
  492. return;
  493. }
  494. requestUtil.sync(
  495. "https://web_history.tianyunperfect.cn/memos/mergeMemo",
  496. "post",
  497. {ids: this.selectedIds},
  498. {}
  499. );
  500. location.reload();
  501. },
  502. handleCheckboxChange(id, checked) {
  503. if (checked) {
  504. this.selectedIds.push(id);
  505. } else {
  506. this.selectedIds = this.selectedIds.filter(
  507. (selectedId) => selectedId !== id
  508. );
  509. }
  510. console.log(this.selectedIds);
  511. },
  512. searchStrChange() {
  513. let queryStr = this.searchStr;
  514. // url编码
  515. queryStr = encodeURIComponent(queryStr);
  516. location.href = requestUtil.buildUrl(location.href, {
  517. queryStr: queryStr,
  518. });
  519. },
  520. searchStrChange2() {
  521. this.searchResStr = "";
  522. requestUtil
  523. .async(
  524. "https://wx.tianyunperfect.cn/full_search?",
  525. "post",
  526. {search: this.searchStr2},
  527. {}
  528. )
  529. .then((res) => {
  530. if (res["list"]) {
  531. let resList = res["list"];
  532. this.searchResStr = res["res"];
  533. this.contentList = resList;
  534. this.searchByContent();
  535. }
  536. });
  537. },
  538. duoxuanChange() {
  539. if (this.duoxuan) {
  540. this.setDuoXuan("ok");
  541. } else {
  542. this.setDuoXuan("");
  543. }
  544. },
  545. checkChange1(newVal) {
  546. console.log(this.tagChecked);
  547. if (!this.getDuoXuan()) {
  548. if (this.tagChecked.length > 1) {
  549. // 保留最后一个
  550. this.tagChecked = [this.tagChecked[this.tagChecked.length - 1]];
  551. }
  552. }
  553. location.href = requestUtil.buildUrl(location.href, {
  554. tags: this.tagChecked.join(","),
  555. });
  556. },
  557. getQueryStr() {
  558. return getQueryString("queryStr");
  559. },
  560. getDuoXuan() {
  561. return getQueryString("duoxuan") == "ok";
  562. },
  563. setDuoXuan(ok) {
  564. location.href = requestUtil.buildUrl(location.href, {
  565. duoxuan: ok,
  566. });
  567. },
  568. removeTmp(item) {
  569. // tmpFileIds 根据id删除
  570. let index = this.tmpFileIds.findIndex(
  571. (item) => item.id === item.id
  572. );
  573. if (index !== -1) {
  574. this.tmpFileIds.splice(index, 1);
  575. }
  576. },
  577. delPic(img) {
  578. // 根据id从 cloneFileIds 删除
  579. let index = this.cloneFileIds.findIndex(
  580. (item) => item.id === img.id
  581. );
  582. if (index !== -1) {
  583. this.cloneFileIds.splice(index, 1);
  584. }
  585. },
  586. getCloneResourceIds(id) {
  587. let resourceMapElement = this.resourceMap[id];
  588. if (resourceMapElement) {
  589. return JSON.parse(JSON.stringify(resourceMapElement));
  590. }
  591. return [];
  592. },
  593. uploadFile(url, file, headers = {}) {
  594. return new Promise((resolve, reject) => {
  595. const xhr = new XMLHttpRequest();
  596. xhr.open("POST", url, true); // 使用异步的方式
  597. const formData = new FormData();
  598. formData.append("file", file);
  599. for (const [key, value] of Object.entries(headers)) {
  600. xhr.setRequestHeader(key, value);
  601. }
  602. xhr.onload = function () {
  603. if (xhr.status === 200) {
  604. resolve(JSON.parse(xhr.responseText));
  605. } else {
  606. reject(new Error(xhr.statusText));
  607. }
  608. };
  609. xhr.onerror = function () {
  610. reject(new Error("Network Error"));
  611. };
  612. xhr.send(formData);
  613. });
  614. },
  615. getFileFromPaste(e) {
  616. let clipboardData = e.clipboardData; // IE
  617. if (!clipboardData) {
  618. //chrome
  619. clipboardData = e.originalEvent.clipboardData;
  620. }
  621. let items = "";
  622. items = (e.clipboardData || window.clipboardData).items;
  623. let file = null;
  624. if (!items || items.length === 0) {
  625. this.$message.error(
  626. "当前浏览器不支持粘贴本地图片,请打开图片复制后再粘贴!"
  627. );
  628. return;
  629. }
  630. // 搜索剪切板items
  631. for (let i = 0; i < items.length; i++) {
  632. // 限制上传文件类型
  633. if (items[i].type.indexOf("image") !== -1) {
  634. file = items[i].getAsFile();
  635. break;
  636. }
  637. }
  638. // 对复制黏贴的类型进行判断,若是非文件类型,比如复制黏贴的文字,则不会调用上传文件的函数
  639. if (file) {
  640. return file;
  641. }
  642. },
  643. handlePaste(e) {
  644. let file = this.getFileFromPaste(e);
  645. // 对复制黏贴的类型进行判断,若是非文件类型,比如复制黏贴的文字,则不会调用上传文件的函数
  646. if (file) {
  647. this.uploadFile(this.upload_url, file, {
  648. Authorization: myHeaders["Authorization"],
  649. }).then((res) => {
  650. let fileId = res["id"];
  651. if (fileId) {
  652. let sourceRes = requestUtil.sync(
  653. "https://web_history.tianyunperfect.cn/memos/resourceById",
  654. "post",
  655. {ids: fileId},
  656. {}
  657. );
  658. let sourceRe = sourceRes["res"];
  659. this.tmpFileIds.push(...sourceRe);
  660. }
  661. });
  662. }
  663. },
  664. handlePaste2(e) {
  665. let file = this.getFileFromPaste(e);
  666. // 对复制黏贴的类型进行判断,若是非文件类型,比如复制黏贴的文字,则不会调用上传文件的函数
  667. if (file) {
  668. this.uploadFile(this.upload_url, file, {
  669. Authorization: myHeaders["Authorization"],
  670. }).then((res) => {
  671. let fileId = res["id"];
  672. if (fileId) {
  673. let sourceRes = requestUtil.sync(
  674. "https://web_history.tianyunperfect.cn/memos/resourceById",
  675. "post",
  676. {ids: fileId},
  677. {}
  678. );
  679. let sourceRe = sourceRes["res"];
  680. this.cloneFileIds.push(...sourceRe);
  681. }
  682. });
  683. }
  684. },
  685. keyDown() {
  686. // 如果是ctrl + enter \ meta + enter 则发送
  687. if (event.ctrlKey || event.metaKey) {
  688. this.sendData();
  689. }
  690. },
  691. msg(content) {
  692. this.$message({
  693. message: content,
  694. type: "success",
  695. });
  696. },
  697. isChooseTag(item, tag) {
  698. return item.content.indexOf("#" + tag + " ") !== -1;
  699. },
  700. checkChange(val, item, tag) {
  701. if (val) {
  702. this.addTag(item, tag);
  703. } else {
  704. this.delTag(item, tag);
  705. }
  706. this.resetView(item);
  707. },
  708. addTag(item, cTag) {
  709. let content = item.content;
  710. // 如果已经有了,就不添加
  711. let tmpTag = "#" + cTag + " ";
  712. if (content.indexOf(tmpTag) !== -1) {
  713. return;
  714. }
  715. item.content = tmpTag + content;
  716. // 提交记录
  717. let res = requestUtil.sync(
  718. "https://web_history.tianyunperfect.cn/memos/updateContent",
  719. "post",
  720. {
  721. id: item.id,
  722. content: item.content,
  723. created_ts: item.created_ts,
  724. },
  725. {}
  726. );
  727. if (res.code === 200) {
  728. this.msg("更新成功");
  729. // this.search();
  730. }
  731. },
  732. delTag(item, cTag) {
  733. let content = item.content;
  734. // 如果没有,就不删除
  735. if (content.indexOf(cTag) === -1) {
  736. return;
  737. }
  738. item.content = content.replaceAll("#" + cTag + " ", "");
  739. // 最少有一个标签
  740. if (!item.content.includes("#")) {
  741. item.content = "#todo " + item.content;
  742. }
  743. // 提交记录
  744. let res = requestUtil.sync(
  745. "https://web_history.tianyunperfect.cn/memos/updateContent",
  746. "post",
  747. {
  748. id: item.id,
  749. content: item.content,
  750. created_ts: item.created_ts,
  751. },
  752. {}
  753. );
  754. if (res.code === 200) {
  755. this.msg("更新成功");
  756. // this.search();
  757. }
  758. },
  759. // 修正后的删除逻辑,总计时3秒支持撤销
  760. del(item) {
  761. // 如果正在等待删除,则取消删除
  762. if (item.pendingDelete) {
  763. clearInterval(item.countdownInterval);
  764. this.$set(item, "pendingDelete", false);
  765. this.$set(item, "countdown", 0);
  766. this.$notify({
  767. type: "info",
  768. title: "已取消",
  769. message: "已取消删除操作",
  770. duration: 1500,
  771. });
  772. return;
  773. }
  774. // 开始删除倒计时
  775. this.$set(item, "pendingDelete", true);
  776. this.$set(item, "countdown", 5);
  777. // 设置倒计时通知
  778. item.countdownInterval = setInterval(() => {
  779. this.$set(item, "countdown", item.countdown - 1);
  780. if (item.countdown <= 0) {
  781. clearInterval(item.countdownInterval);
  782. // 执行删除请求
  783. requestUtil
  784. .async(
  785. `https://web_history.tianyunperfect.cn/memos/delete`,
  786. "post",
  787. {id: item.id},
  788. {}
  789. )
  790. .then((res) => {
  791. if (res.code === 200) {
  792. this.msg("删除成功");
  793. this.contentList = this.contentList.filter(
  794. (it) => it.id !== item.id
  795. );
  796. }
  797. });
  798. }
  799. }, 1000);
  800. },
  801. archive(item) {
  802. requestUtil
  803. .async(
  804. `https://web_history.tianyunperfect.cn/memos/archive`,
  805. "post",
  806. {id: item.id},
  807. {}
  808. )
  809. .then((res) => {
  810. this.msg("归档成功");
  811. // 本地缓存更新
  812. this.contentList = this.contentList.filter(
  813. (it) => it.id !== item.id
  814. );
  815. });
  816. },
  817. handleClose(done) {
  818. this.dialogVisible = false;
  819. done();
  820. },
  821. confirmSubmit(done) {
  822. // 提交记录
  823. let res = requestUtil.sync(
  824. "https://web_history.tianyunperfect.cn/memos/update",
  825. "post",
  826. {
  827. id: this.tmpItem.id,
  828. content: this.contentEditor.getValue(),
  829. created_ts: this.tmpItem.created_ts,
  830. resourceIdList: this.cloneFileIds.map((it) => it.id),
  831. },
  832. {}
  833. );
  834. if (res.code === 200) {
  835. this.msg("更新成功");
  836. this.tmpItem.content = this.contentEditor.getValue();
  837. this.resetView(this.tmpItem);
  838. // 更新主页面的图片
  839. this.resourceMap[this.tmpItem.id] = this.cloneFileIds;
  840. // this.search();
  841. }
  842. this.dialogVisible = false;
  843. done();
  844. },
  845. edit(item) {
  846. this.tmpItem = item;
  847. this.cloneFileIds = this.getCloneResourceIds(item.id);
  848. this.dialogVisible = true;
  849. this.$nextTick(() => {
  850. this.contentEditor = new Vditor("vditor", {
  851. height: 360,
  852. toolbarConfig: {
  853. // pin: true,
  854. },
  855. cache: {
  856. enable: false,
  857. },
  858. mode: "ir",
  859. // 其他配置...
  860. after: () => {
  861. this.contentEditor.setValue(this.tmpItem.content);
  862. this.contentEditor.focus();
  863. },
  864. });
  865. });
  866. },
  867. isDisabled(item) {
  868. // 默认不可编辑,这个时候 没有disable属性
  869. return item["disable"] !== false;
  870. },
  871. sendData: function () {
  872. this.sendBtnAble = false;
  873. let content = this.contentStr;
  874. let categories = this.tagChecked;
  875. // 循环拼接类似 #tag #tag2 content
  876. let updatedContent = content;
  877. if (categories.length > 0) {
  878. // 循环拼接类似 #tag #tag2 content
  879. updatedContent =
  880. categories.map((tag) => `#${tag}`).join(" ") + " " + content;
  881. }
  882. // updatedContent 去除尾部的\n,可能有多个
  883. updatedContent = updatedContent.replace(/\n+$/, "");
  884. // updatedContent 最少有一个标签,如果没有就添加一个标签 #todo,检测是否有 #标签
  885. if (!updatedContent.includes("#")) {
  886. updatedContent = "#todo " + updatedContent;
  887. }
  888. let res = requestUtil.sync(
  889. "https://memos.tianyunperfect.cn/api/v1/memo",
  890. "post",
  891. {
  892. content: updatedContent,
  893. resourceIdList: this.tmpFileIds.map((it) => it.id),
  894. },
  895. myHeaders
  896. );
  897. if (res["name"]) {
  898. // window.location.href = `https://memos.tianyunperfect.cn/m/${res['name']}`;
  899. this.msg("记录成功");
  900. localStorage.setItem("contentStr", "");
  901. this.contentStr = "";
  902. this.tmpFileIds = [];
  903. this.search();
  904. }
  905. this.sendBtnAble = true;
  906. },
  907. search: function () {
  908. document.documentElement.scrollTo(0, 0);
  909. console.log(this.tagChecked);
  910. let params = {
  911. search: this.searchStr,
  912. tag_str: this.tagChecked.join(","),
  913. page: this.page,
  914. page_size: this.size,
  915. };
  916. let url = "https://web_history.tianyunperfect.cn/memos/list";
  917. let urlWithParams = requestUtil.buildUrl(url, params);
  918. let res = requestUtil.sync(urlWithParams, "get", null, myHeaders);
  919. if (res.code !== 200) {
  920. return;
  921. }
  922. this.contentList = res.res;
  923. this.searchByContent();
  924. },
  925. getAllTags: function () {
  926. this.allTags = requestUtil
  927. .async(
  928. "https://memos.tianyunperfect.cn/api/v1/tag",
  929. "get",
  930. null,
  931. myHeaders
  932. )
  933. .then((res) => {
  934. this.allTags = res;
  935. });
  936. },
  937. confirmSubmit2() {
  938. res = requestUtil.sync(
  939. "https://web_history.tianyunperfect.cn/memos/replace",
  940. "post",
  941. {
  942. old_text: this.old_text,
  943. new_text: this.new_text,
  944. },
  945. {}
  946. );
  947. if (res.code === 200) {
  948. this.msg("替换成功");
  949. this.search();
  950. }
  951. this.dialogVisible2 = false;
  952. },
  953. searchByContent: function () {
  954. this.resourceMap = {};
  955. // contentList 循环获取里面所有的id数组
  956. let idArr = this.contentList.map((item) => item["id"]);
  957. // 获取资源
  958. let ids = idArr.join(",");
  959. if (!ids) {
  960. return;
  961. }
  962. let sourceRes = requestUtil.sync(
  963. "https://web_history.tianyunperfect.cn/memos/resource",
  964. "post",
  965. {ids: ids},
  966. {}
  967. );
  968. // {'code': 200, 'res': [{'memo_id': 1, 'resource_name': 'EfdmvRsodBviDQWRmDiTaV'}]}
  969. // 循环遍历,以memo_id为key,resource_name数组为value
  970. sourceRes["res"].forEach((item) => {
  971. if (this.resourceMap[item["memo_id"]]) {
  972. this.resourceMap[item["memo_id"]].push(item);
  973. } else {
  974. this.resourceMap[item["memo_id"]] = [item];
  975. }
  976. });
  977. },
  978. },
  979. });
  980. // 如果处于编辑状态,则执行esc取消
  981. window.onkeydown = function (event) {
  982. let edit = vm.dialogVisible;
  983. if (!edit) {
  984. // 如果是右箭头,则翻页
  985. if (event.keyCode === 39) {
  986. vm.page++;
  987. }
  988. // 如果是左箭头,则翻页
  989. if (event.keyCode === 37 && vm.page > 1) {
  990. vm.page--;
  991. }
  992. return;
  993. }
  994. // esc
  995. if (event.keyCode === 27) {
  996. vm.dialogVisible = false;
  997. }
  998. // 如果是meta + enter 则保存
  999. if (event.metaKey && event.keyCode === 13) {
  1000. vm.confirmSubmit();
  1001. }
  1002. // 如果是ctrl + enter 则保存
  1003. if (event.ctrlKey && event.keyCode === 13) {
  1004. vm.confirmSubmit();
  1005. }
  1006. };
  1007. </script>
  1008. </body>
  1009. </html>