memos.html 35 KB

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