Przeglądaj źródła

平台新闻模块

lrf 8 miesięcy temu
rodzic
commit
fe027e5029

+ 49 - 13
src/views/information/parts/info.vue

@@ -1,27 +1,63 @@
 <template>
   <el-row>
-    <el-col :span="24">
-      <el-segmented v-model="view" :options="options" @change="viewChange" block>
+    <el-col :span="2">
+      <el-link type="primary" :icon="BackIcon" @click="toBack">{{ title }}</el-link>
+    </el-col>
+    <el-col :span="22">
+      <el-segmented v-model="value" :options="options" @change="viewChange" block>
         <template #default="{ item }"> {{ item.label }} </template>
       </el-segmented>
     </el-col>
   </el-row>
+  <el-divider />
+  <el-row>
+    <el-col :span="24">
+      <template v-if="!value">
+        <el-alert title="请在上方选择您要查看的信息" type="warning" effect="dark" :closable="false" />
+      </template>
+      <template v-else>
+        <component :is="viewComponent"></component>
+      </template>
+    </el-col>
+  </el-row>
 </template>
 
 <script setup>
-const view = ref()
+import { Back as BackIcon } from '@element-plus/icons-vue'
+import { get } from 'lodash-es'
+import { defineAsyncComponent } from 'vue'
+const props = defineProps({
+  title: { type: String }
+})
+const emits = defineEmits(['back'])
+const value = ref()
+const viewComponent = ref()
+const componentList = ref({
+  // supply: defineAsyncComponent(() => import('./platform/supply.vue')),
+  // demand: defineAsyncComponent(() => import('./platform/demand.vue')),
+  // achievement: defineAsyncComponent(() => import('./platform/achievement.vue')),
+  // match: defineAsyncComponent(() => import('./platform/match.vue')),
+  // project: defineAsyncComponent(() => import('./platform/project.vue')),
+  // footplate: defineAsyncComponent(() => import('./platform/footplate.vue')),
+  // support: defineAsyncComponent(() => import('./platform/support.vue'))
+})
 const options = [
-  { label: '供方信息', value: 1 },
-  { label: '需求信息', value: 2 },
-  { label: '技术成果', value: 3 },
-  { label: '转化成果', value: 4 },
-  { label: '双创活动', value: 5 },
-  { label: '项目信息', value: 6 },
-  { label: '中试信息', value: 7 },
-  { label: '服务支撑', value: 8 }
+  { label: '专家库', value: 1 },
+  { label: '企业库', value: 2 },
+  { label: '项目库', value: 3 },
+  { label: '供需库', value: 4 }
 ]
-const viewChange = (val) =>{
-  console.log(val)
+const viewChange = (val) => {
+  const i = options.find((f) => f.value === val)
+  if (!i) return
+  const componentName = get(i, 'component')
+  if (!componentName) return
+  const component = componentList.value[componentName]
+  if (!component) return
+  viewComponent.value = component
+}
+const toBack = () => {
+  emits('back')
 }
 </script>
 <style scoped></style>

+ 54 - 4
src/views/information/parts/news.vue

@@ -1,8 +1,58 @@
 <template>
-  <div id="news">
-    <p>news</p>
-  </div>
+  <el-row>
+    <el-col :span="2">
+      <el-link type="primary" :icon="BackIcon" @click="toBack">{{ title }}</el-link>
+    </el-col>
+    <el-col :span="22">
+      <el-segmented v-model="value" :options="options" @change="viewChange" block>
+        <template #default="{ item }"> {{ item.label }} </template>
+      </el-segmented>
+    </el-col>
+  </el-row>
+  <el-divider />
+  <el-row>
+    <el-col :span="24">
+      <template v-if="!value">
+        <el-alert title="请在上方选择您要查看的信息" type="warning" effect="dark" :closable="false" />
+      </template>
+      <template v-else>
+        <component :is="viewComponent"></component>
+      </template>
+    </el-col>
+  </el-row>
 </template>
 
-<script setup></script>
+<script setup>
+import { Back as BackIcon } from '@element-plus/icons-vue'
+import { get } from 'lodash-es'
+import { defineAsyncComponent } from 'vue'
+const props = defineProps({
+  title: { type: String }
+})
+const emits = defineEmits(['back'])
+const value = ref()
+const viewComponent = ref()
+const componentList = ref({
+  news: defineAsyncComponent(() => import('./news/news.vue')),
+  policy: defineAsyncComponent(() => import('./news/policy.vue')),
+  trends: defineAsyncComponent(() => import('./news/trends.vue'))
+})
+const options = [
+  { label: '政策信息', value: 1, component: 'policy' },
+  { label: '新闻通知', value: 2, component: 'news' },
+  { label: '行业动态', value: 3, component: 'trends' }
+]
+const viewChange = (val) => {
+  const i = options.find((f) => f.value === val)
+  if (!i) return
+  const componentName = get(i, 'component')
+  if (!componentName) return
+  const component = componentList.value[componentName]
+  if (!component) return
+  viewComponent.value = component
+}
+const toBack = () => {
+  emits('back')
+}
+</script>
 <style scoped></style>

+ 248 - 0
src/views/information/parts/news/news.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">{{ $t('common.is_use_disabled') }}</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #logo>
+              <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/cxyy_news/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #is_show>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import moment from 'moment'
+import { UserStore } from '@/store/user'
+import { cloneDeep, get } from 'lodash-es'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { NewsStore } from '@/store/api/platform/news'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.news.person'), model: 'person', isSearch: true },
+  { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true, format: (i) => getDict(i, 'time') },
+  { label: t('pages.news.number'), model: 'number' },
+  {
+    label: t('pages.news.is_use'),
+    model: 'is_use',
+    custom: true,
+    format: (i) => getDict(i, 'is_use')
+  },
+  { label: t('pages.news.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  {
+    label: t('common.delete'),
+    method: 'delete',
+    confirm: true,
+    type: 'danger',
+    display: (i) => i.is_use === '1'
+  }
+]
+const buttonFields = [{ label: t('common.create'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const isUseList = ref([])
+const statusList = ref([])
+const tagsList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
+  { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
+  { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const rules = reactive({
+  title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }]
+})
+const dialog = ref({ type: '1', show: false, title: t('pages.news.addDialogTitle') })
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.news.status'), model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }]
+})
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '1' }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'tags') return data.join(',')
+    else if (model === 'time') return moment(data).format('YYYY-MM-DD')
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '1', logo: [] }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  if (!form.value.logo) form.value.logo = []
+  dialog.value = { type: '1', show: true, title: t('pages.news.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.news.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.title}】数据?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use, status: get(data, 'status') })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 248 - 0
src/views/information/parts/news/policy.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">{{ $t('common.is_use_disabled') }}</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #logo>
+              <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/cxyy_news/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #is_show>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import moment from 'moment'
+import { UserStore } from '@/store/user'
+import { cloneDeep, get } from 'lodash-es'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { NewsStore } from '@/store/api/platform/news'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.news.person'), model: 'person', isSearch: true },
+  { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true, format: (i) => getDict(i, 'time') },
+  { label: t('pages.news.number'), model: 'number' },
+  {
+    label: t('pages.news.is_use'),
+    model: 'is_use',
+    custom: true,
+    format: (i) => getDict(i, 'is_use')
+  },
+  { label: t('pages.news.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  {
+    label: t('common.delete'),
+    method: 'delete',
+    confirm: true,
+    type: 'danger',
+    display: (i) => i.is_use === '1'
+  }
+]
+const buttonFields = [{ label: t('common.create'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const isUseList = ref([])
+const statusList = ref([])
+const tagsList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
+  { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
+  { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const rules = reactive({
+  title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }]
+})
+const dialog = ref({ type: '1', show: false, title: t('pages.news.addDialogTitle') })
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.news.status'), model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }]
+})
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '0' }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'tags') return data.join(',')
+    else if (model === 'time') return moment(data).format('YYYY-MM-DD')
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '0', logo: [] }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  if (!form.value.logo) form.value.logo = []
+  dialog.value = { type: '1', show: true, title: t('pages.news.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.news.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.title}】数据?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use, status: get(data, 'status') })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 248 - 0
src/views/information/parts/news/trends.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">{{ $t('common.is_use_disabled') }}</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #logo>
+              <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/cxyy_news/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #is_show>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import moment from 'moment'
+import { UserStore } from '@/store/user'
+import { cloneDeep, get } from 'lodash-es'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { NewsStore } from '@/store/api/platform/news'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.news.person'), model: 'person', isSearch: true },
+  { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true, format: (i) => getDict(i, 'time') },
+  { label: t('pages.news.number'), model: 'number' },
+  {
+    label: t('pages.news.is_use'),
+    model: 'is_use',
+    custom: true,
+    format: (i) => getDict(i, 'is_use')
+  },
+  { label: t('pages.news.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  {
+    label: t('common.delete'),
+    method: 'delete',
+    confirm: true,
+    type: 'danger',
+    display: (i) => i.is_use === '1'
+  }
+]
+const buttonFields = [{ label: t('common.create'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const isUseList = ref([])
+const statusList = ref([])
+const tagsList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
+  { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
+  { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.is_show'), model: 'is_show', type: 'radio' },
+  { label: t('pages.news.order_num'), model: 'order_num', type: 'number' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const rules = reactive({
+  title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }]
+})
+const dialog = ref({ type: '1', show: false, title: t('pages.news.addDialogTitle') })
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.news.status'), model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }]
+})
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '2' }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'tags') return data.join(',')
+    else if (model === 'time') return moment(data).format('YYYY-MM-DD')
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '2', logo: [] }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  if (!form.value.logo) form.value.logo = []
+  dialog.value = { type: '1', show: true, title: t('pages.news.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.news.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.title}】数据?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use, status: get(data, 'status') })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>