Browse Source

修改新闻

zs 1 year ago
parent
commit
4d3899277c

File diff suppressed because it is too large
+ 895 - 0
package-lock.json


+ 3 - 0
package.json

@@ -13,9 +13,12 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
     "@vueuse/core": "^10.7.2",
+    "@wangeditor/editor": "^5.1.23",
+    "@wangeditor/editor-for-vue": "5.1.10",
     "axios": "^1.6.7",
     "element-plus": "^2.5.6",
     "lodash-es": "^4.17.21",
+    "moment": "^2.30.1",
     "nprogress": "^0.2.0",
     "path-browserify": "^1.0.1",
     "path-to-regexp": "^6.2.1",

+ 77 - 0
src/components/WangEditor/index.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="editor-wrapper">
+    <!-- 工具栏 -->
+    <Toolbar id="toolbar-container" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
+    <!-- 编辑器 -->
+    <Editor style="height: 350px" id="editor-container" v-model="modelValue" :default-config="editorConfig" :mode="mode" @on-change="handleChange" @on-created="handleCreated" />
+  </div>
+</template>
+
+<script setup>
+import Axios from 'axios'
+import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
+const $checkRes = inject('$checkRes')
+const props = defineProps({
+  modelValue: {
+    type: [String],
+    default: ''
+  }
+})
+
+const emit = defineEmits(['update:modelValue'])
+
+const modelValue = useVModel(props, 'modelValue', emit)
+
+const editorRef = shallowRef() // 编辑器实例,必须用 shallowRef
+const mode = ref('default') // 编辑器模式
+const toolbarConfig = ref({}) // 工具条配置
+// 编辑器配置
+const editorConfig = ref({
+  placeholder: '请输入内容...',
+  MENU_CONF: {
+    uploadImage: {
+      // 自定义图片上传
+      async customUpload(file, insertFn) {
+        const formData = new FormData()
+        formData.append('file', file)
+        const axios = Axios.create({
+          baseURL: 'http://192.168.1.197'
+        })
+        const res = await axios.request({
+          url: '/files',
+          method: 'post',
+          data: formData,
+          headers: {
+            'Content-Type': 'multipart/form-data'
+          }
+        })
+        if ($checkRes(res, true)) {
+          console.log(res)
+        }
+
+        // uploadFileApi(file).then((response) => {
+        //   const url = response.data.url
+        //   insertFn(url)
+        // })
+      }
+    }
+  }
+})
+
+const handleCreated = (editor) => {
+  editorRef.value = editor // 记录 editor 实例,重要!
+}
+
+function handleChange(editor) {
+  modelValue.value = editor.getHtml()
+}
+
+// 组件销毁时,也及时销毁编辑器
+onBeforeUnmount(() => {
+  const editor = editorRef.value
+  if (editor == null) return
+  editor.destroy()
+})
+</script>
+
+<style src="@wangeditor/editor/dist/css/style.css"></style>

+ 5 - 1
src/lang/package/zh-cn/menus.js

@@ -7,5 +7,9 @@ export default {
   system_parameter: '系统参数',
   system_dict: '字典管理',
   system_dict_data: '字典数据',
-  user_admin: '管理员用户'
+  user_admin: '管理员用户',
+  user_user: '平台用户',
+  platform: '平台管理',
+  platform_policy: '政策法规',
+  platform_news: '新闻资讯'
 }

+ 11 - 0
src/lang/package/zh-cn/pages.js

@@ -71,5 +71,16 @@ export default {
     is_use: '是否启用',
     labelMessage: '请输入数据显示值',
     valueMessage: '请输入数据选择值'
+  },
+  news: {
+    addDialogTitle: '新增新闻',
+    upDialogTitle: '修改新闻',
+    title: '标题',
+    person: '发布人',
+    time: '发布时间',
+    number: '浏览次数',
+    content: '内容',
+    is_use: '是否启用',
+    titleMessage: '请输入标题'
   }
 }

+ 40 - 0
src/store/api/platform/news.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/news'
+const axios = new AxiosWrapper()
+
+export const NewsStore = defineStore('news', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 138 - 0
src/views/platform/news/index.vue

@@ -0,0 +1,138 @@
+<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" @edit="toEdit" @delete="toDelete"></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 #is_use>
+              <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </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'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.person'), model: 'person', isSearch: true },
+  { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true },
+  { label: t('pages.news.number'), model: 'number' },
+  { label: t('pages.news.is_use'), model: 'is_use', format: (i) => getDict(i) }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+const isUseList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.title'), model: 'title' },
+  { 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.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({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+}
+const search = async (query = { skip: 0, 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) => {
+  const res = isUseList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 添加
+const toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '1' }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  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: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  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: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 138 - 0
src/views/platform/policy/index.vue

@@ -0,0 +1,138 @@
+<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" @edit="toEdit" @delete="toDelete"></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 #is_use>
+              <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </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'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.person'), model: 'person', isSearch: true },
+  { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true },
+  { label: t('pages.news.number'), model: 'number' },
+  { label: t('pages.news.is_use'), model: 'is_use', format: (i) => getDict(i) }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+const isUseList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.title'), model: 'title' },
+  { 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.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({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+}
+const search = async (query = { skip: 0, 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) => {
+  const res = isUseList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 添加
+const toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '0' }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  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: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  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: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 15 - 3
src/views/user/user/index.vue

@@ -1,8 +1,20 @@
 <template>
   <div id="index">
-    <p>index</p>
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one"> 平台用户 </el-col>
+      </el-col>
+    </el-row>
   </div>
 </template>
 
-<script setup></script>
-<style scoped></style>
+<script setup>
+// 加载中
+const loading = ref(false)
+// 请求
+onMounted(async () => {
+  loading.value = true
+  loading.value = false
+})
+</script>
+<style scoped lang="scss"></style>

+ 5 - 1
vite.config.js

@@ -23,12 +23,16 @@ export default defineConfig(({ mode }) => {
       // 运行是否自动打开浏览器
       open: true,
       proxy: {
+        '/files': {
+          target: 'http://192.168.1.197', // https://broadcast.waityou24.cn
+          changeOrigin: true
+        },
         /**
          * env.VITE_APP_BASE_API: /dev-api
          */
         [env.VITE_APP_BASE_API]: {
           changeOrigin: true,
-          target: 'http://192.168.1.197:9700'
+          target: 'http://192.168.1.113:9700'
         }
       }
     },