NewsForm.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <template>
  2. <el-container>
  3. <el-aside width="40%">
  4. <div class="select-item">
  5. <div v-for="(news, index) in newsList" :key="index">
  6. <div
  7. class="news-main father"
  8. v-if="index === 0"
  9. :class="{ activeAddNews: activeNewsIndex === index }"
  10. @click="activeNewsIndex = index"
  11. >
  12. <div class="news-content">
  13. <img class="material-img" :src="news.thumbUrl" />
  14. <div class="news-content-title">{{ news.title }}</div>
  15. </div>
  16. <div class="child" v-if="newsList.length > 1">
  17. <el-button type="info" circle size="small" @click="() => moveDownNews(index)">
  18. <Icon icon="ep:arrow-down-bold" />
  19. </el-button>
  20. <el-button
  21. v-if="isCreating"
  22. type="danger"
  23. circle
  24. size="small"
  25. @click="() => removeNews(index)"
  26. >
  27. <Icon icon="ep:delete" />
  28. </el-button>
  29. </div>
  30. </div>
  31. <div
  32. class="news-main-item father"
  33. v-if="index > 0"
  34. :class="{ activeAddNews: activeNewsIndex === index }"
  35. @click="activeNewsIndex = index"
  36. >
  37. <div class="news-content-item">
  38. <div class="news-content-item-title">{{ news.title }}</div>
  39. <div class="news-content-item-img">
  40. <img class="material-img" :src="news.thumbUrl" width="100%" />
  41. </div>
  42. </div>
  43. <div class="child">
  44. <el-button
  45. v-if="newsList.length > index + 1"
  46. circle
  47. type="info"
  48. size="small"
  49. @click="() => moveDownNews(index)"
  50. >
  51. <Icon icon="ep:arrow-down-bold" />
  52. </el-button>
  53. <el-button
  54. v-if="index > 0"
  55. type="info"
  56. circle
  57. size="small"
  58. @click="() => moveUpNews(index)"
  59. >
  60. <Icon icon="ep:arrow-up-bold" />
  61. </el-button>
  62. <el-button
  63. v-if="isCreating"
  64. type="danger"
  65. size="small"
  66. circle
  67. @click="() => removeNews(index)"
  68. >
  69. <Icon icon="ep:delete" />
  70. </el-button>
  71. </div>
  72. </div>
  73. </div>
  74. <el-row justify="center" class="ope-row">
  75. <el-button
  76. type="primary"
  77. circle
  78. @click="plusNews"
  79. v-if="newsList.length < 8 && isCreating"
  80. >
  81. <Icon icon="ep:plus" />
  82. </el-button>
  83. </el-row>
  84. </div>
  85. </el-aside>
  86. <el-main>
  87. <div v-if="newsList.length > 0">
  88. <!-- 标题、作者、原文地址 -->
  89. <el-row :gutter="20">
  90. <el-input v-model="activeNewsItem.title" placeholder="请输入标题(必填)" />
  91. <el-input
  92. v-model="activeNewsItem.author"
  93. placeholder="请输入作者"
  94. style="margin-top: 5px"
  95. />
  96. <el-input
  97. v-model="activeNewsItem.contentSourceUrl"
  98. placeholder="请输入原文地址"
  99. style="margin-top: 5px"
  100. />
  101. </el-row>
  102. <!-- 封面和摘要 -->
  103. <el-row :gutter="20">
  104. <el-col :span="12">
  105. <CoverSelect v-model="activeNewsItem" :is-first="activeNewsIndex === 0" />
  106. </el-col>
  107. <el-col :span="12">
  108. <p>摘要:</p>
  109. <el-input
  110. :rows="8"
  111. type="textarea"
  112. v-model="activeNewsItem.digest"
  113. placeholder="请输入摘要"
  114. class="digest"
  115. maxlength="120"
  116. />
  117. </el-col>
  118. </el-row>
  119. <!--富文本编辑器组件-->
  120. <el-row>
  121. <Editor v-model="activeNewsItem.content" :editor-config="editorConfig" />
  122. </el-row>
  123. </div>
  124. </el-main>
  125. </el-container>
  126. </template>
  127. <script lang="ts" setup>
  128. import { Editor } from '@/components/Editor'
  129. import { createEditorConfig } from '../editor-config'
  130. import CoverSelect from './CoverSelect.vue'
  131. import { type NewsItem, createEmptyNewsItem } from './types'
  132. defineOptions({ name: 'NewsForm' })
  133. const message = useMessage()
  134. const props = defineProps<{
  135. isCreating: boolean
  136. modelValue: NewsItem[] | null
  137. }>()
  138. const accountId = inject<number>('accountId')
  139. // ========== 文件上传 ==========
  140. const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
  141. const editorConfig = createEditorConfig(UPLOAD_URL, unref(accountId))
  142. // v-model=newsList
  143. const emit = defineEmits<{
  144. (e: 'update:modelValue', v: NewsItem[])
  145. }>()
  146. const newsList = computed<NewsItem[]>({
  147. get() {
  148. return props.modelValue === null ? [createEmptyNewsItem()] : props.modelValue
  149. },
  150. set(val) {
  151. emit('update:modelValue', val)
  152. }
  153. })
  154. const activeNewsIndex = ref(0)
  155. const activeNewsItem = computed<NewsItem>(() => newsList.value[activeNewsIndex.value])
  156. // 将图文向下移动
  157. const moveDownNews = (index: number) => {
  158. const temp = newsList.value[index]
  159. newsList.value[index] = newsList.value[index + 1]
  160. newsList.value[index + 1] = temp
  161. activeNewsIndex.value = index + 1
  162. }
  163. // 将图文向上移动
  164. const moveUpNews = (index: number) => {
  165. const temp = newsList.value[index]
  166. newsList.value[index] = newsList.value[index - 1]
  167. newsList.value[index - 1] = temp
  168. activeNewsIndex.value = index - 1
  169. }
  170. // 删除指定 index 的图文
  171. const removeNews = async (index: number) => {
  172. try {
  173. await message.confirm('确定删除该图文吗?')
  174. newsList.value.splice(index, 1)
  175. if (activeNewsIndex.value === index) {
  176. activeNewsIndex.value = 0
  177. }
  178. } catch {}
  179. }
  180. // 添加一个图文
  181. const plusNews = () => {
  182. newsList.value.push(createEmptyNewsItem())
  183. activeNewsIndex.value = newsList.value.length - 1
  184. }
  185. </script>
  186. <style lang="scss" scoped>
  187. .ope-row {
  188. padding-top: 5px;
  189. margin-top: 5px;
  190. text-align: center;
  191. border-top: 1px solid #eaeaea;
  192. }
  193. .el-row {
  194. margin-bottom: 20px;
  195. }
  196. .el-row:last-child {
  197. margin-bottom: 0;
  198. }
  199. .digest {
  200. display: inline-block;
  201. width: 100%;
  202. vertical-align: top;
  203. }
  204. /* 新增图文 */
  205. .news-main {
  206. width: 100%;
  207. height: 120px;
  208. margin: auto;
  209. background-color: #fff;
  210. }
  211. .news-content {
  212. position: relative;
  213. width: 100%;
  214. height: 120px;
  215. background-color: #acadae;
  216. }
  217. .news-content-title {
  218. position: absolute;
  219. bottom: 0;
  220. left: 0;
  221. display: inline-block;
  222. width: 98%;
  223. height: 25px;
  224. padding: 1%;
  225. overflow: hidden;
  226. font-size: 15px;
  227. color: #fff;
  228. text-overflow: ellipsis;
  229. white-space: nowrap;
  230. background-color: black;
  231. opacity: 0.65;
  232. }
  233. .news-main-item {
  234. width: 100%;
  235. padding: 5px 0;
  236. margin: auto;
  237. background-color: #fff;
  238. border-top: 1px solid #eaeaea;
  239. }
  240. .news-content-item {
  241. position: relative;
  242. margin-left: -3px;
  243. }
  244. .news-content-item-title {
  245. display: inline-block;
  246. width: 70%;
  247. font-size: 12px;
  248. }
  249. .news-content-item-img {
  250. display: inline-block;
  251. width: 25%;
  252. background-color: #acadae;
  253. }
  254. .select-item {
  255. width: 60%;
  256. padding: 10px;
  257. margin: 0 auto 10px;
  258. border: 1px solid #eaeaea;
  259. .activeAddNews {
  260. border: 5px solid #2bb673;
  261. }
  262. }
  263. .father .child {
  264. position: relative;
  265. bottom: 25px;
  266. display: none;
  267. text-align: center;
  268. }
  269. .father:hover .child {
  270. display: block;
  271. }
  272. .material-img {
  273. width: 100%;
  274. height: 100%;
  275. }
  276. </style>