WebpageInput.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <div class="webpage-input">
  3. <div class="title">{{ lang.ssInsertLearn }}</div>
  4. <div class="description">{{ lang.ssPickLearn }}</div>
  5. <!-- 当列表为空时显示上传提示 -->
  6. <div v-if="webpageList.length === 0" class="empty-state">
  7. <div class="empty-icon">📚</div>
  8. <div class="empty-title">{{ lang.ssNoLearn }}</div>
  9. <div class="empty-desc">{{ lang.ssNeedUpload }}</div>
  10. </div>
  11. <!-- 当有内容时显示列表 -->
  12. <div v-else class="webpage-list">
  13. <div
  14. v-for="(webpage, index) in webpageList"
  15. :key="index"
  16. class="webpage-item"
  17. :class="{ active: selectedIndex === index }"
  18. @click="toggleWebpageSelection(index)"
  19. >
  20. <div class="webpage-info">
  21. <div class="webpage-title">
  22. <span class="type-tag" :class="getTypeClass(webpage.type)">{{ getTypeLabel(webpage.type) }}</span>
  23. {{ webpage.title }}
  24. </div>
  25. <div class="webpage-url">{{ webpage.url }}</div>
  26. </div>
  27. <div class="webpage-status">
  28. <div v-if="selectedIndex === index" class="selected-icon">✓</div>
  29. </div>
  30. </div>
  31. </div>
  32. <div class="btns">
  33. <Button @click="emit('close')" style="margin-right: 10px;">{{ lang.ssCancel }}</Button>
  34. <Button
  35. v-if="webpageList.length > 0"
  36. type="primary"
  37. @click="insertWebpage()"
  38. :disabled="selectedIndex === null"
  39. >
  40. {{ lang.ssConfirm }}
  41. </Button>
  42. </div>
  43. </div>
  44. </template>
  45. <script lang="ts" setup>
  46. import { ref } from 'vue'
  47. import message from '@/utils/message'
  48. import Button from '@/components/Button.vue'
  49. import { lang } from '@/main'
  50. interface Webpage {
  51. type: number
  52. title: string
  53. url: string
  54. }
  55. interface Props {
  56. webpageList: Webpage[]
  57. }
  58. const props = withDefaults(defineProps<Props>(), {
  59. webpageList: () => []
  60. })
  61. const emit = defineEmits<{
  62. (event: 'insertWebpage', payload: { url: string, type: number }): void
  63. (event: 'close'): void
  64. }>()
  65. const selectedIndex = ref<number | null>(null)
  66. const toggleWebpageSelection = (index: number) => {
  67. if (selectedIndex.value === index) {
  68. // 如果点击已选中的网页,则取消选择
  69. selectedIndex.value = null
  70. }
  71. else {
  72. // 否则选择新网页
  73. selectedIndex.value = index
  74. }
  75. }
  76. const insertWebpage = () => {
  77. if (selectedIndex.value === null) {
  78. return message.error(lang.ssPickWebFirst)
  79. }
  80. // 根据选中的index获取对应的链接
  81. const selectedWebpage = props.webpageList[selectedIndex.value]
  82. if (selectedWebpage) {
  83. emit('insertWebpage', { url: selectedWebpage.url, type: selectedWebpage.type })
  84. }
  85. }
  86. // 获取类型标签
  87. const getTypeLabel = (type: number) => {
  88. const typeMap: Record<number, string> = {
  89. 45: lang.ssChoiceQ,
  90. 15: lang.ssQATest,
  91. 72: lang.ssAiApp,
  92. 73: lang.ssHPage,
  93. 81: lang.ssHPage,
  94. 74: lang.ssVideo,
  95. 75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
  96. 76: lang.ssCreative,
  97. 77: lang.ssEnglishSpeakingTool,
  98. 78: lang.ssVote,
  99. 79: lang.ssPhoto,
  100. 84: lang.ssFile,
  101. 831: lang.ssKwlTitle,
  102. 832: lang.ssSixHatTitle,
  103. }
  104. return typeMap[type] || lang.ssUnknown
  105. }
  106. // 获取类型样式类
  107. const getTypeClass = (type: number) => {
  108. const classMap: Record<number, string> = {
  109. 45: 'type-choice',
  110. 15: 'type-question',
  111. 72: 'type-ai',
  112. 73: 'type-h5',
  113. 74: 'type-video',
  114. 75: 'type-bilibili',
  115. 76: 'type-app-center',
  116. 831: 'type-kwl',
  117. 832: 'type-kwl-title',
  118. }
  119. return classMap[type] || 'type-default'
  120. }
  121. </script>
  122. <style lang="scss" scoped>
  123. .webpage-input {
  124. width: 480px;
  125. padding: 10px;
  126. }
  127. .title {
  128. font-size: 16px;
  129. font-weight: 600;
  130. margin-bottom: 8px;
  131. color: #333;
  132. }
  133. .description {
  134. font-size: 14px;
  135. color: #666;
  136. margin-bottom: 15px;
  137. }
  138. .empty-state {
  139. text-align: center;
  140. padding: 40px 20px;
  141. color: #666;
  142. }
  143. .empty-icon {
  144. font-size: 48px;
  145. margin-bottom: 16px;
  146. }
  147. .empty-title {
  148. font-size: 16px;
  149. font-weight: 500;
  150. color: #333;
  151. margin-bottom: 8px;
  152. }
  153. .empty-desc {
  154. font-size: 14px;
  155. color: #999;
  156. margin-bottom: 24px;
  157. }
  158. .webpage-list {
  159. max-height: 250px; /* 设置最大高度,大约显示5个项目 */
  160. overflow-y: auto;
  161. margin: 15px 0;
  162. border: 1px solid #e0e0e0;
  163. border-radius: 6px;
  164. /* 自定义滚动条样式 */
  165. &::-webkit-scrollbar {
  166. width: 6px;
  167. }
  168. &::-webkit-scrollbar-track {
  169. background: #f1f1f1;
  170. border-radius: 3px;
  171. }
  172. &::-webkit-scrollbar-thumb {
  173. background: #c1c1c1;
  174. border-radius: 3px;
  175. &:hover {
  176. background: #a8a8a8;
  177. }
  178. }
  179. }
  180. .webpage-item {
  181. display: flex;
  182. align-items: center;
  183. justify-content: space-between;
  184. padding: 12px 16px;
  185. border-bottom: 1px solid #f0f0f0;
  186. cursor: pointer;
  187. transition: all 0.2s ease;
  188. height: 50px; /* 固定每个项目的高度 */
  189. box-sizing: border-box;
  190. &:last-child {
  191. border-bottom: none;
  192. }
  193. &:hover {
  194. background-color: #f8f9fa;
  195. }
  196. &.active {
  197. background-color: #e3f2fd;
  198. border-left: 3px solid #2196f3;
  199. }
  200. }
  201. .webpage-info {
  202. flex: 1;
  203. min-width: 0;
  204. }
  205. .webpage-title {
  206. font-size: 14px;
  207. font-weight: 500;
  208. color: #333;
  209. margin-bottom: 4px;
  210. white-space: nowrap;
  211. overflow: hidden;
  212. text-overflow: ellipsis;
  213. display: flex;
  214. align-items: center;
  215. gap: 8px;
  216. }
  217. .type-tag {
  218. padding: 2px 6px;
  219. border-radius: 4px;
  220. font-size: 11px;
  221. font-weight: 500;
  222. color: #fff;
  223. white-space: nowrap;
  224. flex-shrink: 0;
  225. &.type-choice {
  226. background-color: #4caf50;
  227. }
  228. &.type-question {
  229. background-color: #ff9800;
  230. }
  231. &.type-ai {
  232. background-color: #2196f3;
  233. }
  234. &.type-h5 {
  235. background-color: #9c27b0;
  236. }
  237. &.type-video {
  238. background-color: #f44336;
  239. }
  240. &.type-bilibili {
  241. background-color: #fb7299;
  242. }
  243. &.type-default {
  244. background-color: #757575;
  245. }
  246. &.type-app-center {
  247. background-color: #673ab7;
  248. }
  249. }
  250. .webpage-url {
  251. font-size: 12px;
  252. color: #666;
  253. white-space: nowrap;
  254. overflow: hidden;
  255. text-overflow: ellipsis;
  256. }
  257. .webpage-status {
  258. margin-left: 12px;
  259. }
  260. .selected-icon {
  261. color: #2196f3;
  262. font-weight: bold;
  263. font-size: 16px;
  264. }
  265. .btns {
  266. margin-top: 15px;
  267. text-align: right;
  268. }
  269. </style>