WebpageInput.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. 74: lang.ssVideo,
  94. 75: lang.lang == 'cn' ? lang.ssBiliVideo : lang.ssYouTube,
  95. 76: lang.ssCreative,
  96. 77: lang.ssEnglishSpeakingTool,
  97. 78: lang.ssVote,
  98. }
  99. return typeMap[type] || lang.ssUnknown
  100. }
  101. // 获取类型样式类
  102. const getTypeClass = (type: number) => {
  103. const classMap: Record<number, string> = {
  104. 45: 'type-choice',
  105. 15: 'type-question',
  106. 72: 'type-ai',
  107. 73: 'type-h5',
  108. 74: 'type-video',
  109. 75: 'type-bilibili',
  110. 76: 'type-app-center'
  111. }
  112. return classMap[type] || 'type-default'
  113. }
  114. </script>
  115. <style lang="scss" scoped>
  116. .webpage-input {
  117. width: 480px;
  118. padding: 10px;
  119. }
  120. .title {
  121. font-size: 16px;
  122. font-weight: 600;
  123. margin-bottom: 8px;
  124. color: #333;
  125. }
  126. .description {
  127. font-size: 14px;
  128. color: #666;
  129. margin-bottom: 15px;
  130. }
  131. .empty-state {
  132. text-align: center;
  133. padding: 40px 20px;
  134. color: #666;
  135. }
  136. .empty-icon {
  137. font-size: 48px;
  138. margin-bottom: 16px;
  139. }
  140. .empty-title {
  141. font-size: 16px;
  142. font-weight: 500;
  143. color: #333;
  144. margin-bottom: 8px;
  145. }
  146. .empty-desc {
  147. font-size: 14px;
  148. color: #999;
  149. margin-bottom: 24px;
  150. }
  151. .webpage-list {
  152. max-height: 250px; /* 设置最大高度,大约显示5个项目 */
  153. overflow-y: auto;
  154. margin: 15px 0;
  155. border: 1px solid #e0e0e0;
  156. border-radius: 6px;
  157. /* 自定义滚动条样式 */
  158. &::-webkit-scrollbar {
  159. width: 6px;
  160. }
  161. &::-webkit-scrollbar-track {
  162. background: #f1f1f1;
  163. border-radius: 3px;
  164. }
  165. &::-webkit-scrollbar-thumb {
  166. background: #c1c1c1;
  167. border-radius: 3px;
  168. &:hover {
  169. background: #a8a8a8;
  170. }
  171. }
  172. }
  173. .webpage-item {
  174. display: flex;
  175. align-items: center;
  176. justify-content: space-between;
  177. padding: 12px 16px;
  178. border-bottom: 1px solid #f0f0f0;
  179. cursor: pointer;
  180. transition: all 0.2s ease;
  181. height: 50px; /* 固定每个项目的高度 */
  182. box-sizing: border-box;
  183. &:last-child {
  184. border-bottom: none;
  185. }
  186. &:hover {
  187. background-color: #f8f9fa;
  188. }
  189. &.active {
  190. background-color: #e3f2fd;
  191. border-left: 3px solid #2196f3;
  192. }
  193. }
  194. .webpage-info {
  195. flex: 1;
  196. min-width: 0;
  197. }
  198. .webpage-title {
  199. font-size: 14px;
  200. font-weight: 500;
  201. color: #333;
  202. margin-bottom: 4px;
  203. white-space: nowrap;
  204. overflow: hidden;
  205. text-overflow: ellipsis;
  206. display: flex;
  207. align-items: center;
  208. gap: 8px;
  209. }
  210. .type-tag {
  211. padding: 2px 6px;
  212. border-radius: 4px;
  213. font-size: 11px;
  214. font-weight: 500;
  215. color: #fff;
  216. white-space: nowrap;
  217. flex-shrink: 0;
  218. &.type-choice {
  219. background-color: #4caf50;
  220. }
  221. &.type-question {
  222. background-color: #ff9800;
  223. }
  224. &.type-ai {
  225. background-color: #2196f3;
  226. }
  227. &.type-h5 {
  228. background-color: #9c27b0;
  229. }
  230. &.type-video {
  231. background-color: #f44336;
  232. }
  233. &.type-bilibili {
  234. background-color: #fb7299;
  235. }
  236. &.type-default {
  237. background-color: #757575;
  238. }
  239. &.type-app-center {
  240. background-color: #673ab7;
  241. }
  242. }
  243. .webpage-url {
  244. font-size: 12px;
  245. color: #666;
  246. white-space: nowrap;
  247. overflow: hidden;
  248. text-overflow: ellipsis;
  249. }
  250. .webpage-status {
  251. margin-left: 12px;
  252. }
  253. .selected-icon {
  254. color: #2196f3;
  255. font-weight: bold;
  256. font-size: 16px;
  257. }
  258. .btns {
  259. margin-top: 15px;
  260. text-align: right;
  261. }
  262. </style>