WebpageInput.vue 6.0 KB

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