historyComponent.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <script setup lang="ts">
  2. import {ref, inject} from 'vue';
  3. import {Search, Delete} from "@element-plus/icons-vue";
  4. const drawer = ref(false);
  5. const count = ref(0);
  6. const input = ref('');
  7. const loading = ref(false);
  8. const dataList = ref<any[]>([]);
  9. // 获取父组件提供的 Hook 实例
  10. const {db, useStore, deleteDB} = inject('indexedDBHook') as any;
  11. const emit = defineEmits(['currentData']);
  12. const props = defineProps({
  13. msgUuid: {
  14. type: String,
  15. default: ''
  16. }
  17. })
  18. watch(drawer, (newVal) => {
  19. if (newVal) {
  20. loading.value = true;
  21. getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
  22. dataList.value = data.filter((item: any) => item !== null);
  23. count.value = dataList.value.length || 0;
  24. loading.value = false;
  25. // console.log('First two records from each store:', data);
  26. }).catch((error) => {
  27. console.error('Error:', error);
  28. });
  29. }
  30. })
  31. function handleDeleteDB() {
  32. deleteDB().then((res) => {
  33. console.log('Database deleted successfully.', res);
  34. // // 重新加载页面
  35. // window.location.reload();
  36. }).catch((error: any) => {
  37. console.error('Error deleting database:', error);
  38. });
  39. }
  40. function getFirstTwoRecordsFromObjectStoreWithCursor(db: any, storeName: any) {
  41. return new Promise((resolve, reject) => {
  42. const transaction = db.transaction([storeName], 'readonly');
  43. const objectStore = transaction.objectStore(storeName);
  44. const request = objectStore.openCursor();
  45. const results: any[] = [];
  46. let count = 0;
  47. request.onerror = (event: any) => {
  48. reject(`Failed to get data from object store ${storeName}: ${event.target.error}`);
  49. };
  50. request.onsuccess = (event: any) => {
  51. const cursor = event.target.result;
  52. if (cursor && count < 2) {
  53. results.push(cursor.value);
  54. count++;
  55. cursor.continue();
  56. } else {
  57. if (results.length) {
  58. resolve({
  59. storeName: storeName,
  60. data: results
  61. });
  62. } else {
  63. resolve(null)
  64. }
  65. }
  66. };
  67. });
  68. }
  69. async function getFirstTwoRecordsFromEachStore(db: any) {
  70. return new Promise((resolve, reject) => {
  71. // 获取所有对象存储的名称
  72. const storeNames = Array.from(db.objectStoreNames);
  73. const result: any[] = [];
  74. // 遍历每个对象存储并获取前 2 条数据
  75. const promises = storeNames.map((storeName) => {
  76. return getFirstTwoRecordsFromObjectStoreWithCursor(db, storeName);
  77. });
  78. // 等待所有对象存储的数据读取完成
  79. Promise.all(promises).then((dataArrays) => {
  80. // 将每个对象存储的数据合并到结果数组中
  81. dataArrays.forEach((data: any) => {
  82. result.push(data);
  83. });
  84. resolve(result);
  85. }).catch((error) => {
  86. reject(error);
  87. });
  88. })
  89. }
  90. function handleDeleteStore(e: any, item: any) {
  91. e.stopPropagation();
  92. if (item === props.msgUuid) {
  93. const result = getNextOrPreviousId(dataList.value, item);
  94. emit('currentData', result)
  95. }
  96. useStore(item).clearAll().then((res: any) => {
  97. loading.value = true;
  98. getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
  99. dataList.value = data.filter((item: any) => item !== null);
  100. count.value = dataList.value.length || 0;
  101. console.log(dataList.value)
  102. loading.value = false;
  103. // console.log('First two records from each store:', data);
  104. }).catch((error) => {
  105. console.error('Error:', error);
  106. });
  107. });
  108. }
  109. function getNextOrPreviousId(array: any, currentId: any) {
  110. const currentIndex = array.findIndex((item: any) => item.storeName === currentId);
  111. if (currentIndex === -1) {
  112. throw new Error("当前 storeName 不在数组中");
  113. }
  114. if (currentIndex < array.length - 1) {
  115. return array[currentIndex + 1].storeName; // 返回下一个对象的 id
  116. } else if (currentIndex > 0) {
  117. return array[currentIndex - 1].storeName; // 返回上一个对象的 id
  118. }
  119. return null; // 没有下一个或上一个对象
  120. }
  121. defineExpose({
  122. drawer
  123. })
  124. </script>
  125. <template>
  126. <el-drawer style="height: 70%" v-model="drawer" direction="btt" :show-close="true"
  127. :close-on-click-modal="false" :destroy-on-close="true"
  128. :close-on-press-escape="false" class="custom_drawer">
  129. <template #header>
  130. <div class="his_flex"><span class="his_title">历史聊天</span><span class="his_count">({{ count }})</span></div>
  131. </template>
  132. <div style="height: 100%;overflow: hidden;" v-loading="loading">
  133. <div class="his_delete">
  134. <el-input style="margin-right: 12px" v-model="input" placeholder="搜索" clearable :prefix-icon="Search"/>
  135. <el-button :icon="Delete" circle @click="handleDeleteDB"/>
  136. </div>
  137. <div class="his_content">
  138. <template v-for="item in dataList" :key="item.storeName">
  139. <div :class="`his_list ${msgUuid === item.storeName ? 'his_list_change' : '' }`"
  140. @click="emit('currentData',item.storeName)">
  141. <p class="ellipsis" style="color:#000000;font-weight: 900;">{{ item.data[0]?.content ?? '--' }}</p>
  142. <p class="ellipsis" style="color: #888888">{{ item.data[1] ? item.data[1].content : '--' }}</p>
  143. <p class="his_list_op">
  144. <span>{{ item.data[0]?.timestamp }}</span>
  145. <el-button :icon="Delete" link @click="(e:any)=>handleDeleteStore(e,item.storeName)"/>
  146. </p>
  147. </div>
  148. </template>
  149. </div>
  150. </div>
  151. </el-drawer>
  152. </template>
  153. <style lang="scss">
  154. .his_flex {
  155. display: flex;
  156. align-items: center;
  157. }
  158. .custom_drawer {
  159. height: 70vh !important;
  160. border-top-left-radius: 15px;
  161. border-top-right-radius: 15px;
  162. .el-drawer__header {
  163. padding: 16px;
  164. margin-bottom: 0;
  165. }
  166. .el-drawer__close-btn, .el-drawer__body {
  167. padding: 0;
  168. }
  169. .el-drawer__body {
  170. overflow: hidden;
  171. height: 100%;
  172. }
  173. }
  174. .his_title {
  175. display: inline-block;
  176. color: #000000;
  177. font-size: 20px;
  178. font-weight: 900;
  179. margin-right: 3px;
  180. height: 24px;
  181. line-height: 24px;
  182. }
  183. .his_count {
  184. display: inline-block;
  185. height: 24px;
  186. line-height: 24px;
  187. }
  188. .his_delete {
  189. padding: 0 12px 10px;
  190. display: flex;
  191. position: sticky;
  192. align-items: center;
  193. justify-content: space-between;
  194. .is-circle {
  195. border-radius: 8px;
  196. }
  197. }
  198. .his_content {
  199. height: calc(100% - 40px);
  200. overflow: auto;
  201. padding: 0 12px;
  202. .ellipsis {
  203. white-space: nowrap; /* 强制文本不换行 */
  204. overflow: hidden; /* 隐藏溢出内容 */
  205. text-overflow: ellipsis; /* 显示省略号 */
  206. width: 100%; /* 设置宽度(必须) */
  207. }
  208. .his_list {
  209. font-size: 12px;
  210. padding: 5px 10px;
  211. border-radius: 8px;
  212. margin-bottom: 4px;
  213. box-shadow: 0 0 6px rgba(122, 89, 255, .16);
  214. cursor: pointer;
  215. .his_list_op {
  216. display: flex;
  217. justify-content: space-between;
  218. align-items: center;
  219. }
  220. }
  221. .his_list:hover {
  222. background-color: rgba(122, 89, 255, .06);
  223. }
  224. .his_list_change {
  225. background-color: rgba(122, 89, 255, .2) !important;
  226. }
  227. }
  228. </style>