index.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <!--
  2. - Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
  3. -
  4. - Licensed under the GNU Affero General Public License, Version 3 (the "License");
  5. - you may not use this file except in compliance with the License.
  6. - You may obtain a copy of the License at
  7. -
  8. - https://www.gnu.org/licenses/agpl-3.0.html
  9. -
  10. - Unless required by applicable law or agreed to in writing, software
  11. - distributed under the License is distributed on an "AS IS" BASIS,
  12. - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. - See the License for the specific language governing permissions and
  14. - limitations under the License.
  15. -->
  16. <script lang="ts" setup>
  17. import Edit from './edit.vue';
  18. import { useDialog, useMessage } from 'naive-ui';
  19. import SvgIcon from '@/components/SvgIcon/index.vue';
  20. import { h, onMounted, ref, toRaw } from 'vue';
  21. import { del, list as getList } from '@/api/aigc/app';
  22. import router from '@/router';
  23. const ms = useMessage();
  24. const dialog = useDialog();
  25. const editRef = ref();
  26. const list = ref();
  27. onMounted(async () => {
  28. await fetchData();
  29. });
  30. async function fetchData() {
  31. list.value = await getList({});
  32. }
  33. function onSelectAction(key, item) {
  34. if (key === 'edit') {
  35. handleEdit(item);
  36. }
  37. if (key === 'delete') {
  38. dialog.info({
  39. title: '提示',
  40. content: `您确定删除 ${item.name} 应用?`,
  41. positiveText: '确定',
  42. negativeText: '取消',
  43. onPositiveClick: async () => {
  44. await del(item.id);
  45. ms.success('删除成功');
  46. await fetchData();
  47. },
  48. onNegativeClick: () => {},
  49. });
  50. }
  51. }
  52. async function onInfo(item) {
  53. await router.push('/app/info/' + item.id);
  54. }
  55. function handleAdd() {
  56. editRef.value.show();
  57. }
  58. function handleEdit(record: Recordable) {
  59. editRef.value.show(toRaw(record.id));
  60. }
  61. function getOptions(item: any) {
  62. return [
  63. { label: '编辑此应用', key: 'edit' },
  64. { type: 'divider' },
  65. {
  66. key: 'delete',
  67. type: 'render',
  68. render: () => {
  69. return h(
  70. 'div',
  71. {
  72. class:
  73. 'hover:text-red-500 transition-colors translation-all px-2.5 cursor-pointer hover:bg-[#f7f7f7] rounded-md py-1 mx-1',
  74. },
  75. [
  76. h(
  77. 'span',
  78. {
  79. class: 'w-full',
  80. },
  81. '删除应用'
  82. ),
  83. ]
  84. );
  85. },
  86. },
  87. { type: 'divider' },
  88. {
  89. key: 'header',
  90. type: 'render',
  91. render: () => {
  92. return h(
  93. 'div',
  94. {
  95. class: 'flex flex-col gap-1 px-3.5 py-2',
  96. },
  97. [
  98. h('span', null, '信息'),
  99. h(
  100. 'span',
  101. {
  102. class: 'text-xs text-stone-500',
  103. },
  104. `创建时间:${item.createTime}`
  105. ),
  106. h(
  107. 'span',
  108. {
  109. class: 'text-xs mt-1 text-blue-500',
  110. },
  111. `LangChat Apps`
  112. ),
  113. ]
  114. );
  115. },
  116. },
  117. ];
  118. }
  119. const activeDropdownId = ref(null);
  120. const handleDropdownShow = (show: boolean, itemId) => {
  121. activeDropdownId.value = show ? itemId : null;
  122. };
  123. </script>
  124. <template>
  125. <section class="overflow-y-auto h-full px-3 py-4">
  126. <div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-4 mb-8">
  127. <div
  128. class="bg-[#eceef0] py-3 pt-4 transition-all duration-300 px-2 hover:border hover:border-blue-400 border border-transparent cursor-pointer rounded-xl group"
  129. >
  130. <div class="font-bold text-xs mb-1.5 px-6 text-gray-500">创建应用</div>
  131. <div
  132. class="w-full transition-all hover:bg-white rounded-lg py-1.5 px-6 flex items-center gap-1 font-medium hover:text-blue-500"
  133. @click="handleAdd"
  134. >
  135. <SvgIcon icon="line-md:file-plus" />
  136. <span class="text-sm">创建空白应用</span>
  137. </div>
  138. </div>
  139. <div
  140. v-for="item in list"
  141. :key="item.id"
  142. :class="[activeDropdownId === item.id ? '!border-blue-400' : '']"
  143. class="bg-white px-4 py-3 pt-4 transition-all hover:border hover:border-blue-400 border border-transparent duration-300 transform cursor-pointer rounded-xl group"
  144. @click="onInfo(item)"
  145. >
  146. <div class="flex flex-col sm:-mx-4 sm:flex-row">
  147. <div class="sm:mx-4">
  148. <div class="relative bg-orange-100 p-4 rounded-lg">
  149. <SvgIcon class="text-3xl" icon="prime:microchip-ai" />
  150. <div
  151. class="absolute bottom-[-6px] p-1 right-[-5px] shadow bg-white mx-auto rounded-lg"
  152. >
  153. <SvgIcon class="text-sm text-orange-500" icon="lucide:bot" />
  154. </div>
  155. </div>
  156. </div>
  157. <div class="pr-4">
  158. <h1 class="text-lg font-semibold text-gray-700 capitalize"> {{ item.name }} </h1>
  159. <p class="mt-2 text-gray-500 capitalize text-xs">
  160. {{ item.des }}
  161. </p>
  162. </div>
  163. </div>
  164. <div class="flex mt-4 -mx-2 px-2 text-gray-400 justify-between items-center">
  165. <div class="flex items-center gap-1">
  166. <SvgIcon class="" icon="mdi:tag-outline" />
  167. <span v-if="item.model != null" class="text-xs">{{ item.model.model }}</span>
  168. </div>
  169. <div class="flex items-center" @click.stop>
  170. <n-dropdown
  171. :options="getOptions(item)"
  172. class="justify-start min-w-[160px] transition-all"
  173. placement="bottom-end"
  174. size="small"
  175. trigger="click"
  176. @select="(key) => onSelectAction(key, item)"
  177. @update:show="(show) => handleDropdownShow(show, item.id)"
  178. >
  179. <div
  180. :class="[activeDropdownId === item.id ? 'bg-gray-200' : 'hover:bg-gray-200']"
  181. class="rounded p-1 transition-all"
  182. >
  183. <SvgIcon class="w-5 h-5" icon="ri:more-fill" />
  184. </div>
  185. </n-dropdown>
  186. </div>
  187. </div>
  188. </div>
  189. </div>
  190. <Edit ref="editRef" @reload="fetchData" />
  191. </section>
  192. </template>
  193. <style lang="less" scoped></style>