Kaynağa Gözat

Merge branch 'sso' of shuzhihua/randd-vue into master

yanghao 3 hafta önce
ebeveyn
işleme
47ea76d778

+ 25 - 0
src/api/technology‌/index.ts

@@ -59,3 +59,28 @@ export const getResultDetail = (id: number) => {
 export const getResultExtendList = (type) => {
 export const getResultExtendList = (type) => {
   return request.get({ url: `/rd/research-achievement-attrs/attrs?achievementType=${type}` })
   return request.get({ url: `/rd/research-achievement-attrs/attrs?achievementType=${type}` })
 }
 }
+
+// 资讯信息
+export const getNewsList = (params) => {
+  return request.get({ url: '/rd/news/page', params })
+}
+
+// 新增资讯
+export const createNews = (data: any) => {
+  return request.post({ url: '/rd/news/create', data })
+}
+
+// 修改资讯
+export const updateNews = (data: any) => {
+  return request.put({ url: '/rd/news/update', data })
+}
+
+// 删除资讯
+export const deleteNews = (id: number) => {
+  return request.delete({ url: `/rd/news/delete?id=${id}` })
+}
+
+// 获取资讯详情
+export const getNewsDetail = (id: number) => {
+  return request.get({ url: `/rd/news/get?id=${id}` })
+}

+ 15 - 4
src/components/Editor/src/Editor.vue

@@ -13,6 +13,13 @@ defineOptions({ name: 'Editor' })
 
 
 type InsertFnType = (url: string, alt: string, href: string) => void
 type InsertFnType = (url: string, alt: string, href: string) => void
 
 
+const getUploadedFileUrl = (response: any): string => {
+  const data = response?.data ?? response
+  if (typeof data === 'string') return data
+  if (!data || typeof data !== 'object') return ''
+  return data.url || data.path || data.fileUrl || data.filePath || ''
+}
+
 const localeStore = useLocaleStore()
 const localeStore = useLocaleStore()
 
 
 const currentLocale = computed(() => localeStore.getCurrentLocale)
 const currentLocale = computed(() => localeStore.getCurrentLocale)
@@ -133,8 +140,10 @@ const editorConfig = computed((): IEditorConfig => {
                 onSuccess: () => {},
                 onSuccess: () => {},
                 onError: () => {}
                 onError: () => {}
               } as any)
               } as any)
-              // 兼容前端直连上传和后端上传两种模式的返回格式
-              const url = (res as any).data?.data || (res as any).data
+              const url = getUploadedFileUrl(res)
+              if (!url) {
+                throw new Error('上传接口未返回图片地址')
+              }
               insertFn(url, 'image', url)
               insertFn(url, 'image', url)
             } catch (error: any) {
             } catch (error: any) {
               ElMessage.error(error.msg || '图片上传失败')
               ElMessage.error(error.msg || '图片上传失败')
@@ -159,8 +168,10 @@ const editorConfig = computed((): IEditorConfig => {
                 onSuccess: () => {},
                 onSuccess: () => {},
                 onError: () => {}
                 onError: () => {}
               } as any)
               } as any)
-              // 兼容前端直连上传和后端上传两种模式的返回格式
-              const url = (res as any).data?.data || (res as any).data
+              const url = getUploadedFileUrl(res)
+              if (!url) {
+                throw new Error('上传接口未返回视频地址')
+              }
               insertFn(url, 'mp4', url)
               insertFn(url, 'mp4', url)
             } catch (error: any) {
             } catch (error: any) {
               ElMessage.error(error.msg || '视频上传失败')
               ElMessage.error(error.msg || '视频上传失败')

+ 4 - 1
src/utils/dict.ts

@@ -269,5 +269,8 @@ export enum DICT_TYPE {
   PATENT_SUB_TYPE = 'rd_patent_sub_type', // 专利子类型
   PATENT_SUB_TYPE = 'rd_patent_sub_type', // 专利子类型
   PAPER_SUB_TYPE = 'rd_paper_sub_type', // 论文子类型
   PAPER_SUB_TYPE = 'rd_paper_sub_type', // 论文子类型
   COPYRIGHT_SUB_TYPE = 'rd_copyright_sub_type', // 软著子类型
   COPYRIGHT_SUB_TYPE = 'rd_copyright_sub_type', // 软著子类型
-  HONOR_SUB_TYPE = 'rd_honor_sub_type' // 荣誉级别子类型
+  HONOR_SUB_TYPE = 'rd_honor_sub_type', // 荣誉级别子类型
+
+  // 资讯类别   数据字典
+  NEWS_CATEGORY = 'rd_news_category' // 资讯类别
 }
 }

+ 273 - 0
src/views/news/list/index.vue

@@ -0,0 +1,273 @@
+<template>
+  <ContentWrap>
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="80px"
+    >
+      <el-form-item label="资讯标题" prop="newsName">
+        <el-input
+          v-model="queryParams.newsName"
+          clearable
+          class="!w-240px"
+          placeholder="请输入资讯标题"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="资讯类别" prop="newsCategory">
+        <el-select
+          v-model="queryParams.newsCategory"
+          clearable
+          class="!w-240px"
+          placeholder="请选择资讯类别"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.NEWS_CATEGORY)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
+        <el-button type="primary" plain @click="openForm('create')">
+          <Icon icon="ep:plus" class="mr-5px" />新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <ContentWrap>
+    <zm-table :loading="loading" :data="list" row-key="id">
+      <zm-table-column label="资讯标题" prop="newsName" min-width="260" show-overflow-tooltip />
+      <zm-table-column label="资讯类别" prop="newsCategory" align="center" width="180">
+        <template #default="{ row }">
+          <dict-tag :type="DICT_TYPE.NEWS_CATEGORY" :value="row.newsCategory" />
+        </template>
+      </zm-table-column>
+      <zm-table-column label="创建时间" prop="createTime" align="center" width="180">
+        <template #default="{ row }">{{ formatDate(row.createTime) }}</template>
+      </zm-table-column>
+      <zm-table-column label="操作" align="center" width="220" fixed="right">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="openDetail(row.id)">查看</el-button>
+          <el-button link type="primary" @click="openForm('update', row.id)">编辑</el-button>
+          <el-button link type="danger" @click="handleDelete(row.id)">删除</el-button>
+        </template>
+      </zm-table-column>
+    </zm-table>
+
+    <Pagination
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px" destroy-on-close>
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="资讯标题" prop="newsName">
+        <el-input v-model="formData.newsName" placeholder="请输入资讯标题" />
+      </el-form-item>
+      <el-form-item label="资讯类别" prop="newsCategory">
+        <el-select v-model="formData.newsCategory" clearable placeholder="请选择资讯类别">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.NEWS_CATEGORY)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="资讯内容" prop="content">
+        <Editor v-model="formData.content" height="300px" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+      <el-button type="primary" :loading="submitLoading" @click="submitForm">确 定</el-button>
+    </template>
+  </Dialog>
+
+  <Dialog v-model="detailVisible" title="资讯详情" width="800px" destroy-on-close>
+    <div v-loading="detailLoading" class="news-detail">
+      <h2>{{ detailData.newsName }}</h2>
+      <div class="news-detail-meta">
+        <dict-tag :type="DICT_TYPE.NEWS_CATEGORY" :value="detailData.newsCategory" />
+        <span>{{ formatDate(detailData.createTime) }}</span>
+      </div>
+      <div class="news-detail-content" v-html="detailData.content"></div>
+    </div>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue'
+import { ElMessage, ElMessageBox, FormRules } from 'element-plus'
+import { createNews, deleteNews, getNewsDetail, getNewsList, updateNews } from '@/api/technology‌'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { formatDate } from '@/utils/formatTime'
+
+type NewsFormData = {
+  id?: number
+  newsName: string
+  newsCategory?: string | number
+  content: string
+  createTime?: string
+}
+
+defineOptions({ name: 'NewsList' })
+
+const createDefaultFormData = (): NewsFormData => ({
+  id: undefined,
+  newsName: '',
+  newsCategory: undefined,
+  content: ''
+})
+
+const loading = ref(false)
+const total = ref(0)
+const list = ref<any[]>([])
+const queryFormRef = ref()
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  newsName: undefined as string | undefined,
+  newsCategory: undefined as string | number | undefined
+})
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const dialogType = ref<'create' | 'update'>('create')
+const formLoading = ref(false)
+const submitLoading = ref(false)
+const formRef = ref()
+const formData = ref<NewsFormData>(createDefaultFormData())
+const formRules = reactive<FormRules>({
+  newsName: [{ required: true, message: '请输入资讯标题', trigger: 'blur' }],
+  newsCategory: [{ required: true, message: '请选择资讯类别', trigger: 'change' }],
+  content: [{ required: true, message: '请输入资讯内容', trigger: 'blur' }]
+})
+
+const detailVisible = ref(false)
+const detailLoading = ref(false)
+const detailData = ref<NewsFormData>(createDefaultFormData())
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await getNewsList(queryParams)
+    list.value = data.list || []
+    total.value = data.total || 0
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+
+const resetForm = () => {
+  formData.value = createDefaultFormData()
+  formRef.value?.resetFields()
+}
+
+const openForm = async (type: 'create' | 'update', id?: number) => {
+  dialogVisible.value = true
+  dialogType.value = type
+  dialogTitle.value = type === 'create' ? '新增资讯' : '编辑资讯'
+  resetForm()
+  if (type === 'update' && id) {
+    formLoading.value = true
+    try {
+      formData.value = { ...createDefaultFormData(), ...(await getNewsDetail(id)) }
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+
+const submitForm = async () => {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+  submitLoading.value = true
+  try {
+    if (dialogType.value === 'create') {
+      await createNews(formData.value)
+      ElMessage.success('新增成功')
+    } else {
+      await updateNews(formData.value)
+      ElMessage.success('修改成功')
+    }
+    dialogVisible.value = false
+    await getList()
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+const openDetail = async (id: number) => {
+  detailVisible.value = true
+  detailLoading.value = true
+  try {
+    detailData.value = { ...createDefaultFormData(), ...(await getNewsDetail(id)) }
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+const handleDelete = async (id: number) => {
+  try {
+    await ElMessageBox.confirm('确定要删除该资讯吗?', '提示', {
+      type: 'warning',
+      confirmButtonText: '确定',
+      cancelButtonText: '取消'
+    })
+    await deleteNews(id)
+    ElMessage.success('删除成功')
+    await getList()
+  } catch {}
+}
+
+onMounted(getList)
+</script>
+
+<style scoped lang="scss">
+.news-detail h2 {
+  margin: 0;
+  text-align: center;
+}
+
+.news-detail-meta {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 16px;
+  margin: 16px 0 24px;
+  color: var(--el-text-color-secondary);
+}
+
+.news-detail-content {
+  min-height: 180px;
+  line-height: 1.8;
+  overflow-wrap: anywhere;
+}
+</style>