mirror of
https://gitee.com/niucloud-team/javashop.git
synced 2026-04-25 11:18:23 +00:00
up
This commit is contained in:
parent
633f2e7dc4
commit
7cedf9a874
37
cms/Addon.php
Normal file
37
cms/Addon.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace addon\cms;
|
||||
|
||||
|
||||
/**
|
||||
* 插件安装之后单独的插件方法
|
||||
* Class Manage
|
||||
* @package addon\cms
|
||||
*/
|
||||
class Addon
|
||||
{
|
||||
/**
|
||||
* 插件安装执行
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件卸载执行
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件升级执行
|
||||
*/
|
||||
public function upgrade()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
102
cms/admin/api/article.ts
Normal file
102
cms/admin/api/article.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/***************************************************** 文章表 ****************************************************/
|
||||
|
||||
/**
|
||||
* 获取文章表列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getArticleList(params: Record<string, any>) {
|
||||
return request.get(`cms/article`, {params})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章表详情
|
||||
* @param id 文章表id
|
||||
* @returns
|
||||
*/
|
||||
export function getArticleInfo(id: number) {
|
||||
return request.get(`cms/article/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function addArticle(params: Record<string, any>) {
|
||||
return request.post('cms/article', params, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文章表
|
||||
* @param params
|
||||
*/
|
||||
export function editArticle(params: Record<string, any>) {
|
||||
return request.put(`cms/article/${params.id}`, params, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章表
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function deleteArticle(id: number) {
|
||||
return request.delete(`cms/article/${id}`, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/***************************************************** 文章分类管理 ****************************************************/
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getArticleCategoryList(params: Record<string, any>) {
|
||||
return request.get(`cms/category`, {params})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取文章全部分类
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function getArticleCategoryAll(params: Record<string, any>) {
|
||||
return request.get(`cms/category/all`, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类详情
|
||||
* @param category_id
|
||||
*/
|
||||
export function getArticleCategoryInfo(category_id: number) {
|
||||
return request.get(`cms/category/${category_id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章分类
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function addArticleCategory(params: Record<string, any>) {
|
||||
return request.post('cms/category', params, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文章分类
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export function editArticleCategory(params: Record<string, any>) {
|
||||
return request.put(`cms/category/${params.category_id}`, params, {showSuccessMessage: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类删除
|
||||
* @param category_id
|
||||
*/
|
||||
export function deleteArticleCategory(category_id: number) {
|
||||
return request.delete(`cms/category/${category_id}`, {showSuccessMessage: true});
|
||||
}
|
||||
17
cms/admin/lang/zh-cn/article.category.json
Normal file
17
cms/admin/lang/zh-cn/article.category.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "栏目名称",
|
||||
"sort": "排序",
|
||||
"isShow": "是否显示",
|
||||
"namePlaceholder": "请输入栏目名称",
|
||||
"sortPlaceholder": "请输入排序",
|
||||
"isShowPlaceholder": "是否显示",
|
||||
"addArticleCategory": "添加栏目",
|
||||
"updateArticleCategory": "编辑栏目",
|
||||
"articleCategoryDeleteTips": "确定要删除该栏目吗?",
|
||||
"nameMax": "名称不能超过20个字符",
|
||||
"sortNumber": "排序号必须是数字",
|
||||
"sortBetween": "排序号不能超过10000",
|
||||
"show": "显示",
|
||||
"hide": "不显示",
|
||||
"articleNumber": "文章数量"
|
||||
}
|
||||
36
cms/admin/lang/zh-cn/article.edit.json
Normal file
36
cms/admin/lang/zh-cn/article.edit.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"categoryName": "文章栏目",
|
||||
"title": "文章标题",
|
||||
"intro": "简介",
|
||||
"summary": "文章摘要",
|
||||
"image": "文章图片",
|
||||
"author": "作者",
|
||||
"content": "文章内容",
|
||||
"visit": "实际浏览量",
|
||||
"visitVirtual": "初始浏览量",
|
||||
"isShow": "是否显示",
|
||||
"sort": "排序",
|
||||
"categoryIdPlaceholder": "请选择文章栏目",
|
||||
"titlePlaceholder": "请输入文章标题",
|
||||
"introPlaceholder": "请输入简介",
|
||||
"summaryPlaceholder": "请输入文章摘要",
|
||||
"imagePlaceholder": "请上传文章图片",
|
||||
"authorPlaceholder": "请输入作者",
|
||||
"contentPlaceholder": "请输入文章内容",
|
||||
"visitPlaceholder": "请输入实际浏览量",
|
||||
"visitVirtualPlaceholder": "请输入初始浏览量",
|
||||
"isShowPlaceholder": "是否显示",
|
||||
"sortPlaceholder": "请输入排序",
|
||||
"addArticle": "添加文章",
|
||||
"updateArticle": "编辑文章",
|
||||
"titleMax": "文章标题不能超过20个字符",
|
||||
"introMax": "文章简介不能超过50个字符",
|
||||
"summaryMax": "文章摘要不能超过50个字符",
|
||||
"imageMax": "图片路径太长",
|
||||
"authorMax": "文章作者不能超过20个字符",
|
||||
"isShowNumber": "是否显示必须是数字",
|
||||
"isShowBetween": "是否显示只能是0或者1",
|
||||
"sortNumber": "排序号必须是数字",
|
||||
"sortBetween": "排序号需要在0-10000之间",
|
||||
"articleNull": "未读取到文章信息!"
|
||||
}
|
||||
21
cms/admin/lang/zh-cn/article.list.json
Normal file
21
cms/admin/lang/zh-cn/article.list.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"categoryName": "栏目",
|
||||
"ID": "ID",
|
||||
"title": "标题",
|
||||
"intro": "简介",
|
||||
"summary": "摘要",
|
||||
"image": "封面",
|
||||
"author": "作者",
|
||||
"content": "文章内容",
|
||||
"visit": "浏览量",
|
||||
"visitVirtual": "初始浏览量",
|
||||
"isShow": "是否显示",
|
||||
"sort": "排序",
|
||||
"createTime": "创建时间",
|
||||
"updateTime": "更新时间",
|
||||
"addArticle": "添加文章",
|
||||
"updateArticle": "编辑文章",
|
||||
"titlePlaceholder": "请输入文章标题",
|
||||
"categoryIdPlaceholder": "请选择文章栏目",
|
||||
"articleDeleteTips": "确定要删除该文章吗?"
|
||||
}
|
||||
144
cms/admin/views/article/category.vue
Normal file
144
cms/admin/views/article/category.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{ pageName }}</span>
|
||||
<el-button type="primary" @click="addEvent">{{ t('addArticleCategory') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="categoryTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('name')" prop="name">
|
||||
<el-input v-model="categoryTableData.searchParam.name" :placeholder="t('namePlaceholder')" class="w-[190px]" prefix-icon="Search" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadCategoryList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<el-table :data="categoryTableData.data" size="large" v-loading="categoryTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !categoryTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
<el-table-column prop="name" :label="t('name')" min-width="150" />
|
||||
<el-table-column prop="article_num" :label="t('articleNumber')" min-width="140" />
|
||||
<el-table-column prop="is_show" :label="t('isShow')" min-width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.is_show == 1 ? t('show') : t('hide') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sort" :label="t('sort')" min-width="120" />
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" width="130" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.category_id)">{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="categoryTableData.page" v-model:page-size="categoryTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="categoryTableData.total" @size-change="loadCategoryList()" @current-change="loadCategoryList" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<edit-category ref="editCategoryDialog" @complete="loadCategoryList()" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getArticleCategoryList, deleteArticleCategory } from '@/cms/api/article'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import EditCategory from '@/cms/views/article/components/edit-category.vue'
|
||||
import { debounce } from '@/utils/common'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const categoryTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
name: ''
|
||||
}
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined)=>{
|
||||
if (!formEl) return
|
||||
formEl.resetFields();
|
||||
loadCategoryList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
*/
|
||||
const loadCategoryList = debounce((page: number = 1) => {
|
||||
categoryTableData.loading = true
|
||||
categoryTableData.page = page
|
||||
|
||||
getArticleCategoryList({
|
||||
page: categoryTableData.page,
|
||||
limit: categoryTableData.limit,
|
||||
...categoryTableData.searchParam
|
||||
}).then(res => {
|
||||
categoryTableData.loading = false
|
||||
categoryTableData.data = res.data.data
|
||||
categoryTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
categoryTableData.loading = false
|
||||
})
|
||||
})
|
||||
loadCategoryList()
|
||||
|
||||
const editCategoryDialog: Record<string, any> | null = ref(null)
|
||||
|
||||
/**
|
||||
* 添加文章分类
|
||||
*/
|
||||
const addEvent = () => {
|
||||
editCategoryDialog.value.setFormData()
|
||||
editCategoryDialog.value.showDialog = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文章分类
|
||||
* @param data
|
||||
*/
|
||||
const editEvent = (data: any) => {
|
||||
editCategoryDialog.value.setFormData(data)
|
||||
editCategoryDialog.value.showDialog = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章分类
|
||||
*/
|
||||
const deleteEvent = (id: number) => {
|
||||
ElMessageBox.confirm(t('articleCategoryDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
deleteArticleCategory(id).then(() => {
|
||||
loadCategoryList()
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
135
cms/admin/views/article/components/edit-category.vue
Normal file
135
cms/admin/views/article/components/edit-category.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<el-dialog v-model="showDialog" :title="popTitle" width="500px" :destroy-on-close="true">
|
||||
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('name')" prop="name">
|
||||
<el-input v-model="formData.name" clearable :placeholder="t('namePlaceholder')" class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('sort')" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('isShow')">
|
||||
<el-radio-group v-model="formData.is_show" :placeholder="t('isShowPlaceholder')">
|
||||
<el-radio :label="1">{{ t('show') }}</el-radio>
|
||||
<el-radio :label="0">{{ t('hidden') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="loading" @click="confirm(formRef)">{{t('confirm')}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { addArticleCategory, editArticleCategory, getArticleCategoryInfo } from '@/cms/api/article'
|
||||
|
||||
let popTitle: string = '';
|
||||
|
||||
let showDialog = ref(false)
|
||||
const loading = ref(true)
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
category_id: '',
|
||||
name: '',
|
||||
sort: '',
|
||||
is_show: 1,
|
||||
}
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
name: [
|
||||
{ required: true, message: t('namePlaceholder'), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
if (value.length > 20) {
|
||||
callback(new Error(t('nameMax')))
|
||||
}
|
||||
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sort: [
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
if (value === "" || isNaN(value)) {
|
||||
callback(new Error(t('sortNumber')))
|
||||
}
|
||||
if (parseInt(value) > 10000) {
|
||||
callback(new Error(t('sortBetween')))
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['complete'])
|
||||
|
||||
/**
|
||||
* 确认
|
||||
* @param formEl
|
||||
*/
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
let save = formData.category_id ? editArticleCategory : addArticleCategory
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
|
||||
let data = formData
|
||||
|
||||
save(data).then(res => {
|
||||
loading.value = false
|
||||
showDialog.value = false
|
||||
emit('complete')
|
||||
}).catch(err => {
|
||||
loading.value = false
|
||||
// showDialog.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const setFormData = async (row: any = null) => {
|
||||
loading.value = true
|
||||
Object.assign(formData, initialFormData)
|
||||
popTitle = t('addArticleCategory');
|
||||
if (row) {
|
||||
popTitle = t('updateArticleCategory')
|
||||
const data = await (await getArticleCategoryInfo(row.category_id)).data
|
||||
Object.keys(formData).forEach((key: string) => {
|
||||
if (data[key] != undefined) formData[key] = data[key]
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
setFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
176
cms/admin/views/article/edit.vue
Normal file
176
cms/admin/views/article/edit.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<div class="detail-head">
|
||||
<div class="left" @click="router.push({ path: '/cms/article/list' })">
|
||||
<span class="iconfont iconxiangzuojiantou !text-xs"></span>
|
||||
<span class="ml-[1px]">{{t('returnToPreviousPage')}}</span>
|
||||
</div>
|
||||
<span class="adorn">|</span>
|
||||
<span class="right">{{ pageName }}</span>
|
||||
</div>
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
<el-form :model="formData" label-width="90px" ref="formRef" :rules="formRules" class="page-form" v-loading="loading">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="formData.title" clearable :placeholder="t('titlePlaceholder')" class="input-width" maxlength="20" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('categoryName')" prop="category_id">
|
||||
<el-select v-model="formData.category_id" clearable :placeholder="t('categoryIdPlaceholder')" class="input-width">
|
||||
<el-option :label="item['name']" :value="item['category_id']" v-for="item in categoryList" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('intro')" prop="intro">
|
||||
<el-input v-model="formData.intro" type="textarea" rows="4" clearable :placeholder="t('introPlaceholder')" class="input-width" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('summary')" prop="summary">
|
||||
<el-input v-model="formData.summary" type="textarea" rows="4" clearable :placeholder="t('summaryPlaceholder')" class="input-width" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('image')">
|
||||
<upload-image v-model="formData.image" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('author')" prop="author">
|
||||
<el-input v-model="formData.author" clearable :placeholder="t('authorPlaceholder')" class="input-width" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('content')" prop="content">
|
||||
<editor v-model="formData.content" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="t('visitVirtual')">
|
||||
<el-input v-model="formData.visit_virtual" clearable :placeholder="t('visitVirtualPlaceholder')" class="input-width" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('isShow')">
|
||||
<el-radio-group v-model="formData.is_show" :placeholder="t('isShowPlaceholder')">
|
||||
<el-radio :label="1">{{ t('show') }}</el-radio>
|
||||
<el-radio :label="0">{{ t('hidden') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('sort')" prop="sort">
|
||||
<el-input-number v-model="formData.sort" :min="0" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="fixed-footer-wrap">
|
||||
<div class="fixed-footer">
|
||||
<el-button type="primary" @click="onSave(formRef)">{{ t('save') }}</el-button>
|
||||
<el-button @click="back()">{{ t('cancel') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { getArticleInfo, getArticleCategoryAll, addArticle, editArticle } from '@/cms/api/article'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const id: number = parseInt(route.query.id || 0)
|
||||
const loading = ref(false)
|
||||
const categoryList = ref([])
|
||||
const appStore = useAppStore()
|
||||
const pageName = route.meta.title
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
*/
|
||||
const initialFormData = {
|
||||
id: '',
|
||||
category_id: '',
|
||||
title: '',
|
||||
intro: '',
|
||||
summary: '',
|
||||
image: '',
|
||||
author: '',
|
||||
content: '',
|
||||
visit: '',
|
||||
visit_virtual: '',
|
||||
is_show: 1,
|
||||
sort: 0
|
||||
}
|
||||
|
||||
const formData: Record<string, any> = reactive({ ...initialFormData })
|
||||
|
||||
const setFormData = async (id: number = 0) => {
|
||||
loading.value = true
|
||||
Object.assign(formData, initialFormData)
|
||||
if (id) {
|
||||
const data = await (await getArticleInfo(id)).data
|
||||
if (!data || Object.keys(data).length == 0) {
|
||||
ElMessage.error(t('articleNull'))
|
||||
setTimeout(() => {
|
||||
router.go(-1)
|
||||
}, 2000)
|
||||
return false
|
||||
}
|
||||
Object.keys(formData).forEach((key: string) => {
|
||||
if (data[key] != undefined) formData[key] = data[key]
|
||||
})
|
||||
loading.value = false
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
if (id) setFormData(id)
|
||||
|
||||
const setCategoryList = async () => {
|
||||
categoryList.value = await (await getArticleCategoryAll({})).data
|
||||
// if (!id && categoryList.value.length > 0) formData.category_id = categoryList.value[0].category_id
|
||||
}
|
||||
setCategoryList()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = computed(() => {
|
||||
return {
|
||||
title: [
|
||||
{ required: true, message: t('titlePlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
category_id: [
|
||||
{ required: true, message: t('categoryIdPlaceholder'), trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: t('contentPlaceholder'), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: string, callback: any) => {
|
||||
const content = value.replace(/<[^<>]+>/g, '').replace(/ /gi, '')
|
||||
if (!content && value.indexOf('img') === -1) {
|
||||
callback(new Error(t('contentPlaceholder')))
|
||||
} else callback()
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (loading.value || !formEl) return
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
const data = formData
|
||||
const save = id ? editArticle : addArticle
|
||||
save(data).then(res => {
|
||||
loading.value = false
|
||||
back()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const back = () => {
|
||||
router.push({ path: '/cms/article/list' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
181
cms/admin/views/article/list.vue
Normal file
181
cms/admin/views/article/list.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<el-card class="box-card !border-none" shadow="never">
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[20px]">{{ pageName }}</span>
|
||||
<el-button type="primary" @click="addEvent">{{ t('addArticle') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-card class="box-card !border-none my-[10px] table-search-wrap" shadow="never">
|
||||
<el-form :inline="true" :model="articleTableData.searchParam" ref="searchFormRef">
|
||||
<el-form-item :label="t('title')" prop="title">
|
||||
<el-input v-model="articleTableData.searchParam.title" :placeholder="t('titlePlaceholder')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('categoryName')" prop="category_id">
|
||||
<el-select v-model="articleTableData.searchParam.category_id" clearable :placeholder="t('categoryIdPlaceholder')" class="input-width">
|
||||
<el-option :label="t('selectPlaceholder')" value="" />
|
||||
<el-option :label="item['name']" :value="item['category_id']" v-for="item in categoryList" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadArticleList()">{{ t('search') }}</el-button>
|
||||
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="mt-[10px]">
|
||||
<el-table :data="articleTableData.data" size="large" v-loading="articleTableData.loading">
|
||||
<template #empty>
|
||||
<span>{{ !articleTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column prop="id" :show-overflow-tooltip="true" :label="t('ID')" width="100" />
|
||||
|
||||
<el-table-column prop="category_name" :label="t('categoryName')" width="120" />
|
||||
|
||||
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('title')" width="180">
|
||||
<template #default="{ row }">
|
||||
<a :href="row.article_url.web_url" target="_blank">{{ row.title }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('image')" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-image class="w-12 h-12" v-if="row.image_thumb_small" :src="img(row.image_thumb_small)" fit="contain" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="visit" :label="t('visit')" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ parseInt(row.visit + row.visit_virtual) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('isShow')" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.is_show == 1">{{ t('show') }}</span>
|
||||
<span v-if="row.is_show == 0">{{t('hidden')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="sort" :label="t('sort')" width="100" align="center" />
|
||||
|
||||
<el-table-column :label="t('createTime')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('operation')" fixed="right" align="right" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="editEvent(row)">{{ t('edit') }}</el-button>
|
||||
<el-button type="primary" link @click="deleteEvent(row.id)">{{ t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="articleTableData.page" v-model:page-size="articleTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="articleTableData.total" @size-change="loadArticleList()" @current-change="loadArticleList" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { t } from '@/lang'
|
||||
import { getArticleList, deleteArticle, getArticleCategoryAll } from '@/cms/api/article'
|
||||
import { img } from '@/utils/common'
|
||||
import { ElMessageBox, FormInstance } from 'element-plus'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const pageName = route.meta.title
|
||||
|
||||
const articleTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
category_id: ''
|
||||
}
|
||||
})
|
||||
const categoryList = ref([])
|
||||
|
||||
const searchFormRef = ref<FormInstance>()
|
||||
|
||||
const setCategoryList = async () => {
|
||||
categoryList.value = await (await getArticleCategoryAll({})).data
|
||||
}
|
||||
setCategoryList()
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
const loadArticleList = (page: number = 1) => {
|
||||
articleTableData.loading = true
|
||||
articleTableData.page = page
|
||||
|
||||
getArticleList({
|
||||
page: articleTableData.page,
|
||||
limit: articleTableData.limit,
|
||||
...articleTableData.searchParam
|
||||
}).then(res => {
|
||||
articleTableData.loading = false
|
||||
articleTableData.data = res.data.data
|
||||
articleTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
articleTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadArticleList()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
/**
|
||||
* 添加文章
|
||||
*/
|
||||
const addEvent = () => {
|
||||
router.push('/cms/article/edit')
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文章
|
||||
* @param data
|
||||
*/
|
||||
const editEvent = (data: any) => {
|
||||
router.push(`/cms/article/edit?id=${data.id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章
|
||||
*/
|
||||
const deleteEvent = (id: number) => {
|
||||
ElMessageBox.confirm(t('articleDeleteTips'), t('warning'),
|
||||
{
|
||||
confirmButtonText: t('confirm'),
|
||||
cancelButtonText: t('cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
deleteArticle(id).then(() => {
|
||||
loadArticleList()
|
||||
}).catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
loadArticleList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
177
cms/admin/views/diy/components/edit-article.vue
Normal file
177
cms/admin/views/diy/components/edit-article.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<!-- 内容 -->
|
||||
<div class="content-wrap" v-show="diyStore.editTab == 'content'">
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('articleData') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('dataSources')">
|
||||
<el-radio-group v-model="diyStore.editComponent.sources">
|
||||
<el-radio :label="'initial'">{{t('defaultSources')}}</el-radio>
|
||||
<el-radio :label="'diy'">{{t('manualSelectionSources')}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('articleNum')" v-show="diyStore.editComponent.sources == 'initial'">
|
||||
<el-slider v-model="diyStore.editComponent.count" show-input size="small" class="ml-[10px] article-slider" :min="1" :max="30"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('manualSelectionSources')" v-show="diyStore.editComponent.sources == 'diy'" class=" flex">
|
||||
<span @click="showArticle" class="cursor-pointer flex-1" :class="{ 'text-primary' : diyStore.editComponent.articleIds.length > 0 }">{{ diyStore.editComponent.articleIds.length > 0 ? t('selected') + diyStore.editComponent.articleIds.length + t('piece') : t('selectPlaceholder') }}</span>
|
||||
<el-icon>
|
||||
<ArrowRight/>
|
||||
</el-icon>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="showDialog" :title="t('selectArticleTips')" width="60%" :close-on-press-escape="false" :close-on-click-modal="false">
|
||||
|
||||
<div>
|
||||
<el-table :data="articleTableData.data" size="large" v-loading="articleTableData.loading" @selection-change="handleSelectionChange">
|
||||
<template #empty>
|
||||
<span>{{ !articleTableData.loading ? t('emptyData') : '' }}</span>
|
||||
</template>
|
||||
|
||||
<el-table-column type="selection" width="55"/>
|
||||
|
||||
<el-table-column prop="title" :show-overflow-tooltip="true" :label="t('articleTitle')" width="140"/>
|
||||
|
||||
<el-table-column :label="t('articleImage')" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-image class="w-12 h-12" v-if="row.image" :src="img(row.image)" fit="contain"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="category_name" :label="t('articleCategoryName')" align="center" min-width="140"/>
|
||||
|
||||
<el-table-column prop="summary" :label="t('articleSummary')" width="180" :show-overflow-tooltip="true"/>
|
||||
|
||||
<el-table-column :label="t('createTime')" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.create_time || '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="mt-[16px] flex justify-end">
|
||||
<el-pagination v-model:current-page="articleTableData.page" v-model:page-size="articleTableData.limit" layout="total, sizes, prev, pager, next, jumper" :total="articleTableData.total" @size-change="loadArticleList()" @current-change="loadArticleList"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">{{ t('cancel')}}</el-button>
|
||||
<el-button type="primary" @click="save">{{ t('confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<!-- 样式 -->
|
||||
<div class="style-wrap" v-show="diyStore.editTab == 'style'">
|
||||
<div class="edit-attr-item-wrap">
|
||||
<h3 class="mb-[10px]">{{ t('articleStyle') }}</h3>
|
||||
<el-form label-width="80px" class="px-[10px]">
|
||||
<el-form-item :label="t('articleBgColor')">
|
||||
<el-color-picker v-model="diyStore.editComponent.elementBgColor" show-alpha :predefine="diyStore.predefineColors"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('topRounded')">
|
||||
<el-slider v-model="diyStore.editComponent.topElementRounded" show-input size="small" class="ml-[10px] graphic-nav-slider" :max="50"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('bottomRounded')">
|
||||
<el-slider v-model="diyStore.editComponent.bottomElementRounded" show-input size="small" class="ml-[10px] graphic-nav-slider" :max="50"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 组件样式 -->
|
||||
<slot name="style"></slot>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {t} from '@/lang'
|
||||
import useDiyStore from '@/stores/modules/diy'
|
||||
import {ref, reactive} from 'vue'
|
||||
import {img} from '@/utils/common'
|
||||
import {getArticleList} from '@/cms/api/article'
|
||||
|
||||
const diyStore = useDiyStore()
|
||||
diyStore.editComponent.ignore = []; // 忽略公共属性
|
||||
|
||||
// 组件验证
|
||||
diyStore.editComponent.verify = (index: number) => {
|
||||
var res = {code: true, message: ''};
|
||||
if (diyStore.value[index].sources === 'diy' && diyStore.value[index].articleIds.length === 0) {
|
||||
res.code = false;
|
||||
res.message = t('selectArticleTip');
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const showDialog = ref(false)
|
||||
|
||||
const showArticle = () => {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
const articleTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
category_id: '',
|
||||
is_show: 1
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
const loadArticleList = (page: number = 1) => {
|
||||
articleTableData.loading = true
|
||||
articleTableData.page = page
|
||||
|
||||
getArticleList({
|
||||
page: articleTableData.page,
|
||||
limit: articleTableData.limit,
|
||||
...articleTableData.searchParam
|
||||
}).then(res => {
|
||||
articleTableData.loading = false
|
||||
articleTableData.data = res.data.data
|
||||
articleTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
articleTableData.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
loadArticleList()
|
||||
|
||||
const multipleSelection: any = ref([])
|
||||
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
diyStore.editComponent.articleIds = [];
|
||||
multipleSelection.value.forEach((item: any) => {
|
||||
diyStore.editComponent.articleIds.push(item.id)
|
||||
})
|
||||
showDialog.value = false
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.article-slider {
|
||||
.el-slider__input {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
108
cms/app/adminapi/controller/article/Article.php
Normal file
108
cms/app/adminapi/controller/article/Article.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\adminapi\controller\article;
|
||||
|
||||
use addon\cms\app\service\admin\article\ArticleService;
|
||||
use core\base\BaseAdminController;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* 文章控制器
|
||||
* Class CmsArticle
|
||||
* @package app\adminapi\controller\article
|
||||
*/
|
||||
class Article extends BaseAdminController
|
||||
{
|
||||
/**
|
||||
* 文章列表
|
||||
* @return Response
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'title', '' ],
|
||||
[ 'category_id', '' ],
|
||||
[ 'sort', '' ],
|
||||
[ 'is_show', '' ],
|
||||
]);
|
||||
return success(( new ArticleService() )->getPage($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function info(int $id)
|
||||
{
|
||||
return success(( new ArticleService() )->getInfo($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章
|
||||
* @return Response
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'title', '' ],
|
||||
[ 'category_id', '' ],
|
||||
[ 'intro', '' ],
|
||||
[ 'summary', '' ],
|
||||
[ 'image', '' ],
|
||||
[ 'author', '' ],
|
||||
[ 'content', '', false ],
|
||||
[ 'visit_virtual', 0 ],
|
||||
[ 'is_show', 1 ],
|
||||
[ 'sort', 0 ],
|
||||
]);
|
||||
$this->validate($data, 'addon\cms\app\validate\article\Article.add');
|
||||
$id = ( new ArticleService() )->add($data);
|
||||
return success('ADD_SUCCESS', [ 'id' => $id ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章编辑
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit(int $id)
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'title', '' ],
|
||||
[ 'category_id', '' ],
|
||||
[ 'intro', '' ],
|
||||
[ 'summary', '' ],
|
||||
[ 'image', '' ],
|
||||
[ 'author', '' ],
|
||||
[ 'content', '', false ],
|
||||
[ 'visit_virtual', 0 ],
|
||||
[ 'is_show', 1 ],
|
||||
[ 'sort', 0 ],
|
||||
]);
|
||||
$this->validate($data, 'addon\cms\app\validate\article\Article.edit');
|
||||
( new ArticleService() )->edit($id, $data);
|
||||
return success('EDIT_SUCCESS');
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章删除
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function del(int $id)
|
||||
{
|
||||
( new ArticleService() )->del($id);
|
||||
return success('DELETE_SUCCESS');
|
||||
}
|
||||
|
||||
}
|
||||
95
cms/app/adminapi/controller/article/ArticleCategory.php
Normal file
95
cms/app/adminapi/controller/article/ArticleCategory.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\adminapi\controller\article;
|
||||
|
||||
use addon\cms\app\service\admin\article\ArticleCategoryService;
|
||||
use core\base\BaseAdminController;
|
||||
use think\Response;
|
||||
|
||||
class ArticleCategory extends BaseAdminController
|
||||
{
|
||||
/**
|
||||
* 文章分类列表
|
||||
* @return Response
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'name', '' ],
|
||||
]);
|
||||
return success(( new ArticleCategoryService() )->getPage($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有分类(文章添加,编辑,索引)
|
||||
* @return Response
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return success(( new ArticleCategoryService() )->getAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类详情
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function info(int $id)
|
||||
{
|
||||
return success(( new ArticleCategoryService() )->getInfo($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章分类
|
||||
* @return Response
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'name', '' ],
|
||||
[ 'is_show', 1 ],
|
||||
[ 'sort', 0 ],
|
||||
]);
|
||||
$this->validate($data, 'addon\cms\app\validate\article\ArticleCategory.add');
|
||||
$id = ( new ArticleCategoryService() )->add($data);
|
||||
return success('ADD_SUCCESS', [ 'id' => $id ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类编辑
|
||||
* @param int $category_id //分类id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit(int $category_id)
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'name', '' ],
|
||||
[ 'is_show', 1 ],
|
||||
[ 'sort', 0 ],
|
||||
]);
|
||||
$this->validate($data, 'addon\cms\app\validate\article\ArticleCategory.edit');
|
||||
( new ArticleCategoryService() )->edit($category_id, $data);
|
||||
return success('EDIT_SUCCESS');
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类删除
|
||||
* @param int $category_id
|
||||
* @return Response
|
||||
*/
|
||||
public function del(int $category_id)
|
||||
{
|
||||
( new ArticleCategoryService() )->del($category_id);
|
||||
return success('DELETE_SUCCESS');
|
||||
}
|
||||
|
||||
}
|
||||
64
cms/app/adminapi/route/route.php
Normal file
64
cms/app/adminapi/route/route.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
use app\adminapi\middleware\AdminCheckRole;
|
||||
use app\adminapi\middleware\AdminCheckToken;
|
||||
use app\adminapi\middleware\AdminLog;
|
||||
|
||||
/**
|
||||
* 微官网
|
||||
*/
|
||||
Route::group('cms', function() {
|
||||
|
||||
/***************************************************** 文章管理 ****************************************************/
|
||||
|
||||
//文章列表
|
||||
Route::get('article', 'addon\cms\app\adminapi\controller\article\Article@lists');
|
||||
|
||||
//文章详情
|
||||
Route::get('article/:id', 'addon\cms\app\adminapi\controller\article\Article@info');
|
||||
|
||||
//添加文章
|
||||
Route::post('article', 'addon\cms\app\adminapi\controller\article\Article@add');
|
||||
|
||||
//编辑文章
|
||||
Route::put('article/:id', 'addon\cms\app\adminapi\controller\article\Article@edit');
|
||||
|
||||
//删除文章
|
||||
Route::delete('article/:id', 'addon\cms\app\adminapi\controller\article\Article@del');
|
||||
|
||||
/***************************************************** 文章分类管理 ****************************************************/
|
||||
|
||||
//文章分类列表
|
||||
Route::get('category', 'addon\cms\app\adminapi\controller\article\ArticleCategory@lists');
|
||||
|
||||
//所有文章分类
|
||||
Route::get('category/all', 'addon\cms\app\adminapi\controller\article\ArticleCategory@all');
|
||||
|
||||
//文章分类详情
|
||||
Route::get('category/:id', 'addon\cms\app\adminapi\controller\article\ArticleCategory@info');
|
||||
|
||||
//添加文章分类
|
||||
Route::post('category', 'addon\cms\app\adminapi\controller\article\ArticleCategory@add');
|
||||
|
||||
//编辑文章分类
|
||||
Route::put('category/:id', 'addon\cms\app\adminapi\controller\article\ArticleCategory@edit');
|
||||
|
||||
//删除文章分类
|
||||
Route::delete('category/:category_id', 'addon\cms\app\adminapi\controller\article\ArticleCategory@del');
|
||||
|
||||
})->middleware([
|
||||
AdminCheckToken::class,
|
||||
AdminCheckRole::class,
|
||||
AdminLog::class
|
||||
]);
|
||||
59
cms/app/api/controller/article/Article.php
Normal file
59
cms/app/api/controller/article/Article.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\api\controller\article;
|
||||
|
||||
use addon\cms\app\service\api\article\ArticleService;
|
||||
use core\base\BaseApiController;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* 文章控制器
|
||||
* Class CmsArticle
|
||||
* @package app\api\controller\article
|
||||
*/
|
||||
class Article extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* 文章列表
|
||||
* @return Response
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'title', '' ],
|
||||
[ 'category_id', '' ],
|
||||
]);
|
||||
return success(( new ArticleService() )->getPage($data));
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'title', '' ],
|
||||
[ 'category_id', '' ],
|
||||
[ 'ids', [] ],
|
||||
[ 'limit', 0 ]
|
||||
]);
|
||||
return success(( new ArticleService() )->getAll($data, $data[ 'limit' ]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function info(int $id)
|
||||
{
|
||||
return success(( new ArticleService() )->getInfo($id));
|
||||
}
|
||||
|
||||
}
|
||||
48
cms/app/api/controller/article/ArticleCategory.php
Normal file
48
cms/app/api/controller/article/ArticleCategory.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\api\controller\article;
|
||||
|
||||
use addon\cms\app\service\api\article\ArticleCategoryService;
|
||||
use core\base\BaseApiController;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* 文章分类
|
||||
* Class CmsArticleCategory
|
||||
* @package app\api\controller\article
|
||||
*/
|
||||
class ArticleCategory extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* 文章分类列表
|
||||
* @return Response
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$data = $this->request->params([
|
||||
[ 'name', '' ],
|
||||
]);
|
||||
return success(( new ArticleCategoryService() )->getPage($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类详情
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function info(int $id)
|
||||
{
|
||||
return success(( new ArticleCategoryService() )->getInfo($id));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
44
cms/app/api/route/route.php
Normal file
44
cms/app/api/route/route.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use app\api\middleware\ApiCheckToken;
|
||||
use app\api\middleware\ApiLog;
|
||||
use app\api\middleware\ApiChannel;
|
||||
use think\facade\Route;
|
||||
|
||||
|
||||
/**
|
||||
* 微官网
|
||||
*/
|
||||
Route::group('cms', function() {
|
||||
|
||||
/***************************************************** 文章管理 ****************************************************/
|
||||
|
||||
//文章列表
|
||||
Route::get('article', 'addon\cms\app\api\controller\article\Article@lists');
|
||||
|
||||
//文章详情
|
||||
Route::get('article/:id', 'addon\cms\app\api\controller\article\Article@info');
|
||||
|
||||
//文章
|
||||
Route::get('article/all', 'addon\cms\app\api\controller\article\Article@all');
|
||||
|
||||
/***************************************************** 文章分类管理 ****************************************************/
|
||||
|
||||
//文章分类列表
|
||||
Route::get('category', 'addon\cms\app\api\controller\article\ArticleCategory@lists');
|
||||
|
||||
//文章分类详情
|
||||
Route::get('category/:id', 'addon\cms\app\api\controller\article\ArticleCategory@info');
|
||||
|
||||
})->middleware(ApiChannel::class)
|
||||
->middleware(ApiCheckToken::class, false) //false表示不验证登录
|
||||
->middleware(ApiLog::class);
|
||||
23
cms/app/dict/diy/components.php
Normal file
23
cms/app/dict/diy/components.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'BASIC' => [
|
||||
'title' => get_lang('dict_diy.component_type_basic'),
|
||||
'list' => [
|
||||
'Article' => [
|
||||
'title' => '文章',
|
||||
'icon' => 'iconfont-iconwenzhang',
|
||||
'path' => 'edit-article',
|
||||
'support_page' => [],
|
||||
'uses' => 0,
|
||||
'sort' => 10007,
|
||||
'value' => [
|
||||
'sources' => 'initial',
|
||||
'count' => 8,
|
||||
'articleIds' => []
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
18
cms/app/dict/diy/links.php
Normal file
18
cms/app/dict/diy/links.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'CMS_LINK' => [
|
||||
'key' => 'cms',
|
||||
'addon_title' => get_lang('dict_diy.cms_title'),
|
||||
'title' => get_lang('dict_diy.cms_link'),
|
||||
'child_list' => [
|
||||
[
|
||||
'name' => 'ARTICLE_LIST',
|
||||
'title' => get_lang('dict_diy.cms_link_article_list'),
|
||||
'url' => '/cms/pages/list',
|
||||
'is_share' => 1,
|
||||
'action' => ''
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
4
cms/app/dict/menu/admin.php
Normal file
4
cms/app/dict/menu/admin.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
];
|
||||
73
cms/app/dict/menu/site.php
Normal file
73
cms/app/dict/menu/site.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
return [
|
||||
[
|
||||
'menu_name' => '微官网',
|
||||
'menu_key' => 'cms',
|
||||
'menu_type' => 0,
|
||||
'icon' => 'element-Tickets',
|
||||
'api_url' => '',
|
||||
'router_path' => 'cms',
|
||||
'view_path' => '',
|
||||
'methods' => '',
|
||||
'sort' => 100,
|
||||
'status' => 1,
|
||||
'is_show' => 1,
|
||||
'children' => [
|
||||
[
|
||||
'menu_name' => '文章管理',
|
||||
'menu_short_name' => '文章',
|
||||
'menu_key' => 'cms_article',
|
||||
'menu_type' => 0,
|
||||
'icon' => 'iconfont-iconwenzhangguanli1',
|
||||
'api_url' => '',
|
||||
'router_path' => 'article',
|
||||
'view_path' => '',
|
||||
'methods' => '',
|
||||
'sort' => 98,
|
||||
'status' => 1,
|
||||
'is_show' => 1,
|
||||
'children' => [
|
||||
[
|
||||
'menu_name' => '文章列表',
|
||||
'menu_key' => 'cms_article_list',
|
||||
'menu_type' => 1,
|
||||
'icon' => 'element-ChatDotSquare',
|
||||
'api_url' => 'cms/article',
|
||||
'router_path' => 'list',
|
||||
'view_path' => 'article/list',
|
||||
'methods' => 'get',
|
||||
'sort' => 100,
|
||||
'status' => 1,
|
||||
'is_show' => 1,
|
||||
],
|
||||
[
|
||||
'menu_name' => '文章添加/编辑',
|
||||
'menu_key' => 'cms_article_edit',
|
||||
'menu_type' => 1,
|
||||
'icon' => '',
|
||||
'api_url' => 'cms/article',
|
||||
'router_path' => 'edit',
|
||||
'view_path' => 'article/edit',
|
||||
'methods' => 'post',
|
||||
'sort' => 90,
|
||||
'status' => 1,
|
||||
'is_show' => 0,
|
||||
],
|
||||
[
|
||||
'menu_name' => '文章栏目',
|
||||
'menu_key' => 'cms_article_category',
|
||||
'menu_type' => 1,
|
||||
'icon' => 'element-CollectionTag',
|
||||
'api_url' => 'article/category',
|
||||
'router_path' => 'category',
|
||||
'view_path' => 'article/category',
|
||||
'methods' => 'get',
|
||||
'sort' => 80,
|
||||
'status' => 1,
|
||||
'is_show' => 1,
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
7
cms/app/event.php
Normal file
7
cms/app/event.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'listen' => [
|
||||
'WapIndex' => [ 'addon\cms\app\listener\WapIndexListener' ],
|
||||
]
|
||||
];
|
||||
14
cms/app/lang/en/api.php
Normal file
14
cms/app/lang/en/api.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
20
cms/app/lang/en/dict.php
Normal file
20
cms/app/lang/en/dict.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'dict_menu_admin' => [
|
||||
'article' => 'article',
|
||||
'article_list' => 'article list',
|
||||
'article_edit' => 'article edit',
|
||||
'article_category' => 'article category',
|
||||
],
|
||||
|
||||
];
|
||||
27
cms/app/lang/en/validate.php
Normal file
27
cms/app/lang/en/validate.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'validate_article' => [
|
||||
'title_require' => 'title is require',
|
||||
'title_max' => 'title must not be exceed 20 points',
|
||||
'intro_max' => 'intro must not be exceed 50 points',
|
||||
'summary_max' => 'summary must not be exceed 50 points',
|
||||
'image_max' => 'image is exceed max',
|
||||
'author_max' => 'author must not be exceed 20 points',
|
||||
'is_show_number' => 'is_show must be a number',
|
||||
'is_show_between' => 'is_show must be 0 or 1',
|
||||
'sort_number' => 'sort must be a number',
|
||||
'sort_between' => 'sort must not be exceed 10000',
|
||||
'cate_name_require' => 'cate_name is require',
|
||||
'cate_name_max' => 'cate_name must not be exceed 120 points',
|
||||
]
|
||||
];
|
||||
14
cms/app/lang/zh-cn/api.php
Normal file
14
cms/app/lang/zh-cn/api.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
22
cms/app/lang/zh-cn/dict.php
Normal file
22
cms/app/lang/zh-cn/dict.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'dict_diy' => [
|
||||
'cms_title' => '微官网',
|
||||
'cms_link' => '微官网链接',
|
||||
'cms_link_article_list' => '文章资讯',
|
||||
],
|
||||
'dict_wap_index' => [
|
||||
'cms' => '微官网',
|
||||
'cms_desc' => '文章栏目管理',
|
||||
],
|
||||
];
|
||||
30
cms/app/lang/zh-cn/validate.php
Normal file
30
cms/app/lang/zh-cn/validate.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'validate_article' => [
|
||||
'title_require' => '文章标题必须填写',
|
||||
'title_max' => '文章标题不能超过20个字符',
|
||||
'intro_max' => '文章简介不能超过50个字符',
|
||||
'summary_max' => '文章摘要不能超过50个字符',
|
||||
'image_max' => '图片路径太长',
|
||||
'author_max' => '文章作者不能超过20个字符',
|
||||
'is_show_number' => '是否显示必须是数字',
|
||||
'is_show_between' => '是否显示只能是0或者1',
|
||||
'sort_number' => '排序号必须是数字',
|
||||
'sort_between' => '排序号不能超过10000',
|
||||
'cate_name_require' => '栏目名称必须填写',
|
||||
'cate_name_max' => '栏目不能超过20个字符',
|
||||
'category_id_require' => '文章栏目必须填写',
|
||||
'category_id_num' => '文章栏目必须是整数',
|
||||
'content_require' => '文章内容必须填写',
|
||||
],
|
||||
];
|
||||
31
cms/app/listener/WapIndexListener.php
Normal file
31
cms/app/listener/WapIndexListener.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\listener;
|
||||
|
||||
/**
|
||||
* 手机端首页加载事件
|
||||
*/
|
||||
class WapIndexListener
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'key' => 'cms',
|
||||
"title" => get_lang("dict_wap_index.cms"),
|
||||
'desc' => get_lang("dict_wap_index.cms_desc"),
|
||||
"url" => "/cms/pages/list",
|
||||
'icon'=>'addon/cms/icon.png'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
139
cms/app/model/article/CmsArticle.php
Normal file
139
cms/app/model/article/CmsArticle.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\model\article;
|
||||
|
||||
use app\dict\sys\FileDict;
|
||||
use core\base\BaseModel;
|
||||
use think\db\Query;
|
||||
use think\model\relation\HasOne;
|
||||
|
||||
/**
|
||||
* 文章模型
|
||||
* Class CmsArticle
|
||||
* @package app\model\article
|
||||
*/
|
||||
class CmsArticle extends BaseModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 数据表主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cms_article';
|
||||
|
||||
/**
|
||||
* @return HasOne
|
||||
*/
|
||||
public function cmsArticleCategory()
|
||||
{
|
||||
return $this->hasOne(CmsArticleCategory::class, 'category_id', 'category_id')->joinType('left')->withField('category_id, name')->bind([ 'category_name' => 'name' ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类搜索器
|
||||
* @param $query
|
||||
* @param $value
|
||||
* @param $data
|
||||
*/
|
||||
public function searchCategoryIdAttr($query, $value, $data)
|
||||
{
|
||||
if ($value) {
|
||||
$query->where('category_id', $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章标题搜索器
|
||||
* @param $query
|
||||
* @param $value
|
||||
* @param $data
|
||||
*/
|
||||
public function searchTitleAttr($query, $value, $data)
|
||||
{
|
||||
if ($value) {
|
||||
$query->where('title', 'like', '%' . $value . '%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章标题搜索器
|
||||
* @param $query
|
||||
* @param $value
|
||||
* @param $data
|
||||
*/
|
||||
public function searchIsShowAttr($query, $value, $data)
|
||||
{
|
||||
if ($value != '') {
|
||||
$query->where('is_show', $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function searchIdsAttr(Query $query, $value, $data)
|
||||
{
|
||||
if (!empty($value)) {
|
||||
$query->whereIn('id', $data[ 'ids' ]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文章标题搜索器
|
||||
* @param $query
|
||||
* @param $value
|
||||
* @param $data
|
||||
*/
|
||||
public function searchSortAttr($query, $value, $data)
|
||||
{
|
||||
if ($value) {
|
||||
$query->where('sort', $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function getArticleUrlAttr($value, $data)
|
||||
{
|
||||
|
||||
$wap_domain = !empty(env("system.wap_domain")) ? preg_replace('#/$#', '', env("system.wap_domain")) : request()->domain();
|
||||
$web_domain = !empty(env("system.web_domain")) ? preg_replace('#/$#', '', env("system.web_domain")) : request()->domain();
|
||||
|
||||
return [
|
||||
'wap_url' => $wap_domain . "/wap/cms/pages/detail?id={$data['id']}",
|
||||
'web_url' => $web_domain . "/web/article/detail?id={$data['id']}"
|
||||
];
|
||||
}
|
||||
|
||||
public function getImageThumbBigAttr($value, $data)
|
||||
{
|
||||
if ($data[ 'image' ] != '') {
|
||||
return get_thumb_images($data[ 'image' ], FileDict::BIG);
|
||||
}
|
||||
}
|
||||
|
||||
public function getImageThumbMidAttr($value, $data)
|
||||
{
|
||||
if ($data[ 'image' ] != '') {
|
||||
return get_thumb_images($data[ 'image' ], FileDict::MID);
|
||||
}
|
||||
}
|
||||
|
||||
public function getImageThumbSmallAttr($value, $data)
|
||||
{
|
||||
if ($data[ 'image' ] != '') {
|
||||
return get_thumb_images($data[ 'image' ], FileDict::SMALL);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
cms/app/model/article/CmsArticleCategory.php
Normal file
54
cms/app/model/article/CmsArticleCategory.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\model\article;
|
||||
|
||||
use core\base\BaseModel;
|
||||
|
||||
/**
|
||||
* 文章栏目模型
|
||||
* Class CmsArticleCategory
|
||||
* @package app\model\article
|
||||
*/
|
||||
class CmsArticleCategory extends BaseModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 数据表主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'category_id';
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cms_article_category';
|
||||
|
||||
/**
|
||||
* 文章分类名称搜索器
|
||||
* @param $query
|
||||
* @param $value
|
||||
* @param $data
|
||||
*/
|
||||
public function searchNameAttr($query, $value, $data)
|
||||
{
|
||||
if ($value) {
|
||||
$query->where([ [ 'name', 'like', "%$value%" ] ]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getArticleNumAttr($value, $data)
|
||||
{
|
||||
return ( new CmsArticle() )->where([ [ 'category_id', '=', $data[ 'category_id' ] ] ])->count();
|
||||
}
|
||||
|
||||
}
|
||||
104
cms/app/service/admin/article/ArticleCategoryService.php
Normal file
104
cms/app/service/admin/article/ArticleCategoryService.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\service\admin\article;
|
||||
|
||||
use addon\cms\app\model\article\CmsArticleCategory;
|
||||
use core\base\BaseAdminService;
|
||||
|
||||
/**
|
||||
* 文章分类(栏目)服务层
|
||||
* Class ArticleService
|
||||
* @package app\service\admin\article
|
||||
*/
|
||||
class ArticleCategoryService extends BaseAdminService
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->model = new CmsArticleCategory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public function getPage(array $where = [])
|
||||
{
|
||||
|
||||
$field = 'category_id, site_id, name, sort, is_show, create_time, update_time';
|
||||
$order = 'create_time desc';
|
||||
$search_model = $this->model->where([['site_id', '=', $this->site_id]])->withSearch(['name'], $where)->field($field)->order($order)->append(["article_num"]);
|
||||
return $this->pageQuery($search_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有分类(文章添加)
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
|
||||
$field = 'category_id, site_id, name, sort';
|
||||
$order = 'sort desc';
|
||||
return $this->model->where([['site_id', '=', $this->site_id], ['is_show', '=', 1]])->field($field)->order($order)->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类信息
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo(int $id)
|
||||
{
|
||||
$field = 'category_id, site_id, name, sort, is_show, create_time, update_time';
|
||||
|
||||
return $this->model->field($field)->where([['category_id', '=', $id], ['site_id', '=', $this->site_id]])->append(["article_num"])->findOrEmpty()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章分类
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*/
|
||||
public function add(array $data)
|
||||
{
|
||||
$data['site_id'] = $this->site_id;
|
||||
$res = $this->model->create($data);
|
||||
return $res->category_id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类编辑
|
||||
* @param int $id
|
||||
* @param array $data
|
||||
* @return true
|
||||
*/
|
||||
public function edit(int $id, array $data)
|
||||
{
|
||||
$data['update_time'] = time();
|
||||
$this->model->where([['category_id', '=', $id], ['site_id', '=', $this->site_id]])->update($data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章分类
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function del(int $id)
|
||||
{
|
||||
return $this->model->where([['category_id', '=', $id], ['site_id', '=', $this->site_id]])->delete();
|
||||
}
|
||||
|
||||
}
|
||||
94
cms/app/service/admin/article/ArticleService.php
Normal file
94
cms/app/service/admin/article/ArticleService.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\service\admin\article;
|
||||
|
||||
use addon\cms\app\model\article\CmsArticle;
|
||||
use core\base\BaseAdminService;
|
||||
|
||||
/**
|
||||
* 文章服务层
|
||||
* Class ArticleService
|
||||
* @package app\service\admin\article
|
||||
*/
|
||||
class ArticleService extends BaseAdminService
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->model = new CmsArticle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public function getPage(array $where = [])
|
||||
{
|
||||
$where[] = [ 'site_id', '=', $this->site_id ];
|
||||
$field = 'id, category_id, site_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
|
||||
$order = 'create_time desc';
|
||||
$search_model = $this->model->where([ [ 'site_id', '=', $this->site_id ] ])->withSearch([ 'title', 'category_id', 'is_show'], $where)->with('articleCategory')->field($field)->order($order)->append(['article_url','image_thumb_small']);
|
||||
return $this->pageQuery($search_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章信息
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo(int $id)
|
||||
{
|
||||
$field = 'id, category_id, site_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
|
||||
|
||||
return $this->model->where([ [ 'id', '=', $id ], [ 'site_id', '=', $this->site_id ] ])->with('articleCategory')->field($field)->append(['image_thumb_small'])->findOrEmpty()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文章
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*/
|
||||
public function add(array $data)
|
||||
{
|
||||
$data[ 'site_id' ] = $this->site_id;
|
||||
$data[ 'create_time' ] = time();
|
||||
$res = $this->model->create($data);
|
||||
return $res->id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章编辑
|
||||
* @param int $id
|
||||
* @param array $data
|
||||
* @return true
|
||||
*/
|
||||
public function edit(int $id, array $data)
|
||||
{
|
||||
$data[ 'update_time' ] = time();
|
||||
$this->model->where([ [ 'id', '=', $id ], [ 'site_id', '=', $this->site_id ] ])->update($data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function del(int $id)
|
||||
{
|
||||
return $this->model->where([ [ 'id', '=', $id ], [ 'site_id', '=', $this->site_id ] ])->delete();
|
||||
}
|
||||
|
||||
}
|
||||
53
cms/app/service/api/article/ArticleCategoryService.php
Normal file
53
cms/app/service/api/article/ArticleCategoryService.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\service\api\article;
|
||||
|
||||
use addon\cms\app\model\article\CmsArticleCategory;
|
||||
use core\base\BaseApiService;
|
||||
|
||||
/**
|
||||
* 文章分类(栏目)服务层
|
||||
* Class ArticleService
|
||||
* @package app\service\api\article
|
||||
*/
|
||||
class ArticleCategoryService extends BaseApiService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->model = new CmsArticleCategory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public function getPage(array $where = [])
|
||||
{
|
||||
$field = 'category_id, site_id, name, sort, is_show, create_time, update_time';
|
||||
$order = 'create_time desc';
|
||||
$search_model = $this->model->where([['site_id', '=', $this->site_id]])->withSearch(['name'], $where)->field($field)->order($order);
|
||||
return $this->pageQuery($search_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类信息
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo(int $id)
|
||||
{
|
||||
$field = 'category_id, site_id, name, sort, is_show, create_time, update_time';
|
||||
return $this->model->field($field)->where([['category_id', '=', $id], ['site_id', '=', $this->site_id]])->findOrEmpty()->toArray();
|
||||
}
|
||||
}
|
||||
76
cms/app/service/api/article/ArticleService.php
Normal file
76
cms/app/service/api/article/ArticleService.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\service\api\article;
|
||||
|
||||
use addon\cms\app\model\article\CmsArticle;
|
||||
use core\base\BaseApiService;
|
||||
use think\db\exception\DataNotFoundException;
|
||||
use think\db\exception\DbException;
|
||||
use think\db\exception\ModelNotFoundException;
|
||||
|
||||
/**
|
||||
* 文章服务层
|
||||
* Class ArticleService
|
||||
* @package app\service\api\article
|
||||
*/
|
||||
class ArticleService extends BaseApiService
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->model = new CmsArticle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
* @param array $where
|
||||
* @return array
|
||||
*/
|
||||
public function getPage(array $where = [])
|
||||
{
|
||||
$where[] = [ 'site_id', '=', $this->site_id ];
|
||||
$field = 'id, category_id, site_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
|
||||
$order = 'create_time desc';
|
||||
$search_model = $this->model->where([ [ 'site_id', '=', $this->site_id ] ])->withSearch([ 'title', 'category_id'], $where)->with('articleCategory')->field($field)->order($order)->append(['image_thumb_mid']);
|
||||
return $this->pageQuery($search_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
* @param array $where
|
||||
* @param int $limit
|
||||
* @return array
|
||||
* @throws DataNotFoundException
|
||||
* @throws DbException
|
||||
* @throws ModelNotFoundException
|
||||
*/
|
||||
public function getAll(array $where = [], int $limit = 0){
|
||||
$where[] = [ 'site_id', '=', $this->site_id ];
|
||||
$field = 'id, category_id, site_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
|
||||
$order = 'create_time desc';
|
||||
return $this->model->where([ [ 'site_id', '=', $this->site_id ] , ['is_show', '=', 1]])->withSearch([ 'title', 'category_id', 'ids' ], $where)->limit($limit)->with('articleCategory')->field($field)->append(['image_thumb_mid'])->order($order)->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章信息
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo(int $id)
|
||||
{
|
||||
$field = 'id, category_id, site_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
|
||||
|
||||
return $this->model->with('articleCategory')->field($field)->where([ [ 'id', '=', $id ], [ 'site_id', '=', $this->site_id ] ])->append(['image_thumb_big'])->findOrEmpty()->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
57
cms/app/validate/article/Article.php
Normal file
57
cms/app/validate/article/Article.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\validate\article;
|
||||
|
||||
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* Class CmsArticle
|
||||
* @package app\validate\article
|
||||
*/
|
||||
class Article extends Validate
|
||||
{
|
||||
|
||||
//用户名或密码的规范可能是从数据库中获取的
|
||||
protected $rule = [
|
||||
'title' => 'require|max:20',
|
||||
'intro' => 'max:50',
|
||||
'summary' => 'max:50',
|
||||
'image' => 'max:100',
|
||||
'author' => 'max:20',
|
||||
'is_show' => 'number|between:0,1',
|
||||
'sort' => 'number|between:0,10000',
|
||||
'category_id' => 'number|require',
|
||||
'content' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'title.require' => 'validate_article.title_require',
|
||||
'title.max' => 'validate_article.title_max',
|
||||
'intro.max' => 'validate_article.intro_max',
|
||||
'summary.max' => 'validate_article.summary_max',
|
||||
'image.max' => 'validate_article.image_max',
|
||||
'author.max' => 'validate_article.author_max',
|
||||
'is_show.number' => 'validate_article.is_show_number',
|
||||
'is_show.between' => 'validate_article.is_show_between',
|
||||
'sort.number' => 'validate_article.sort_number',
|
||||
'sort.between' => 'validate_article.sort_between',
|
||||
'category_id.require' => 'validate_article.category_id_require',
|
||||
'category_id.number' => 'validate_article.category_id_number',
|
||||
'content.require' => 'validate_article.content_require',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['title', 'intro', 'summary', 'image', 'author', 'is_show', 'sort', 'content', 'category_id'],
|
||||
'edit' => ['title', 'intro', 'summary', 'image', 'author', 'is_show', 'sort', 'content', 'category_id'],
|
||||
];
|
||||
}
|
||||
45
cms/app/validate/article/ArticleCategory.php
Normal file
45
cms/app/validate/article/ArticleCategory.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Niucloud-admin 企业快速开发的多应用管理平台
|
||||
// +----------------------------------------------------------------------
|
||||
// | 官方网址:https://www.niucloud.com
|
||||
// +----------------------------------------------------------------------
|
||||
// | niucloud团队 版权所有 开源版本可自由商用
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: Niucloud Team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace addon\cms\app\validate\article;
|
||||
|
||||
|
||||
use think\Validate;
|
||||
|
||||
/**
|
||||
* 文章分类(栏目)验证
|
||||
* Class CmsArticle
|
||||
* @package app\validate\article
|
||||
*/
|
||||
class ArticleCategory extends Validate
|
||||
{
|
||||
|
||||
//用户名或密码的规范可能是从数据库中获取的
|
||||
protected $rule = [
|
||||
'name' => 'require|max:20',
|
||||
'is_show' => 'number|between:0,1',
|
||||
'sort' => 'number|between:0,10000'
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'name.require' => 'validate_article.cate_name_require',
|
||||
'name.max' => 'validate_article.cate_name_max',
|
||||
'is_show.number' => 'validate_article.is_show_number',
|
||||
'is_show.between' => 'validate_article.is_show_between',
|
||||
'sort.number' => 'validate_article.sort_number',
|
||||
'sort.between' => 'validate_article.sort_between',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'add' => ['name', 'is_show', 'sort'],
|
||||
'edit' => ['name', 'is_show', 'sort'],
|
||||
];
|
||||
}
|
||||
9
cms/info.json
Normal file
9
cms/info.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "微官网",
|
||||
"desc": "文章栏目管理",
|
||||
"key": "cms",
|
||||
"version": "1.0.2",
|
||||
"author": "niucloud",
|
||||
"type": "app",
|
||||
"support_app": ""
|
||||
}
|
||||
19
cms/package/uni-app-pages.php
Normal file
19
cms/package/uni-app-pages.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
return [
|
||||
'pages' => <<<EOT
|
||||
// PAGE_BEGIN
|
||||
{
|
||||
"path": "{{addon_name}}/pages/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "%{{addon_name}}.pages.list%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "{{addon_name}}/pages/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "%{{addon_name}}.pages.detail%"
|
||||
}
|
||||
}
|
||||
// PAGE_END
|
||||
EOT
|
||||
];
|
||||
BIN
cms/resource/cms.png
Normal file
BIN
cms/resource/cms.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
cms/resource/cover.png
Normal file
BIN
cms/resource/cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
BIN
cms/resource/icon.png
Normal file
BIN
cms/resource/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
39
cms/sql/install.sql
Normal file
39
cms/sql/install.sql
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
DROP TABLE IF EXISTS `{{prefix}}cms_article`;
|
||||
CREATE TABLE `{{prefix}}cms_article` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文章id',
|
||||
site_id int NOT NULL DEFAULT 0,
|
||||
`category_id` int(11) NOT NULL COMMENT '文章分类',
|
||||
`title` varchar(255) NOT NULL COMMENT '文章标题',
|
||||
`intro` varchar(255) NOT NULL DEFAULT '' COMMENT '简介',
|
||||
`summary` varchar(255) NOT NULL DEFAULT '' COMMENT '文章摘要',
|
||||
`image` varchar(128) NOT NULL DEFAULT '' COMMENT '文章图片',
|
||||
`author` varchar(255) NOT NULL DEFAULT '' COMMENT '作者',
|
||||
`content` text DEFAULT NULL COMMENT '文章内容',
|
||||
`visit` int(11) NOT NULL DEFAULT '0' COMMENT '实际浏览量',
|
||||
`visit_virtual` int(11) NOT NULL DEFAULT '0' COMMENT '初始浏览量',
|
||||
`is_show` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否显示:1-是.0-否',
|
||||
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||
`update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `IDX_article_category_id` (`category_id`),
|
||||
KEY `IDX_article_create_time` (`create_time`),
|
||||
KEY `IDX_article_is_show` (`is_show`),
|
||||
KEY `IDX_ns_cms_article_sort` (`sort`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci COMMENT='文章表';
|
||||
|
||||
DROP TABLE IF EXISTS `{{prefix}}cms_article_category`;
|
||||
CREATE TABLE `{{prefix}}cms_article_category` (
|
||||
`category_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文章分类id',
|
||||
site_id int NOT NULL DEFAULT 0,
|
||||
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '分类名称',
|
||||
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`is_show` tinyint(4) NOT NULL DEFAULT '1' COMMENT '是否显示:1-是;0-否',
|
||||
`create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||
`update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||
PRIMARY KEY (`category_id`),
|
||||
KEY `create_time` (`create_time`),
|
||||
KEY `is_show` (`is_show`),
|
||||
KEY `sort` (`sort`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci COMMENT='文章分类表';
|
||||
4
cms/sql/uninstall.sql
Normal file
4
cms/sql/uninstall.sql
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
DROP TABLE IF EXISTS `{{prefix}}cms_article`;
|
||||
|
||||
DROP TABLE IF EXISTS `{{prefix}}cms_article_category`;
|
||||
29
cms/uni-app/api/article.ts
Normal file
29
cms/uni-app/api/article.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleList(params: Record<string, any>) {
|
||||
return request.get('cms/article', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleAll(params: Record<string, any>) {
|
||||
return request.get('cms/article/all', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情
|
||||
*/
|
||||
export function getArticleDetail(id: number) {
|
||||
return request.get(`cms/article/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类
|
||||
*/
|
||||
export function getArticleCategory() {
|
||||
return request.get('cms/category')
|
||||
}
|
||||
132
cms/uni-app/components/diy/article/index.vue
Normal file
132
cms/uni-app/components/diy/article/index.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view :style="warpCss">
|
||||
<view v-for="(item,index) in articleList" :key="item.id"
|
||||
:class="['item flex align-center p-[20rpx]',{'border-solid border-t-0 border-l-0 border-r-0 border-b border-gray-200 mb-[20rpx]': articleList.length-1 !== index}] "
|
||||
@click="toLink(item.id)" :style="itemCss">
|
||||
<u--image width="260rpx" height="200rpx" :src="img(item.image)" v-if="item.image" model="aspectFill">
|
||||
<template #error>
|
||||
<u-icon name="photo" color="#999" size="50"></u-icon>
|
||||
</template>
|
||||
</u--image>
|
||||
<view class="flex-1 flex flex-col justify-between ml-[20rpx]">
|
||||
<view class="text-[32rpx] leading-[1.3] multi-hidden mt-[4rpx]">{{item.title}}</view>
|
||||
<view class="text-[28rpx] using-hidden mb-[auto] mt-[20rpx] text-gray-500">{{item.summary}}</view>
|
||||
<view class="text-[24rpx] text-gray-400 flex justify-between mt-[10rpx]">
|
||||
<text>{{item.create_time}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 文章
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { redirect, img } from '@/utils/common';
|
||||
import useDiyStore from '@/app/stores/diy';
|
||||
import { getArticleAll } from '@/cms/api/article';
|
||||
|
||||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']);
|
||||
const diyStore = useDiyStore();
|
||||
const articleList = ref<Array<any>>([]);
|
||||
|
||||
const diyComponent = computed(() => {
|
||||
if (diyStore.mode == 'decorate') {
|
||||
return diyStore.value[props.index];
|
||||
} else {
|
||||
return props.component;
|
||||
}
|
||||
})
|
||||
|
||||
const warpCss = computed(() => {
|
||||
var style = '';
|
||||
if (diyComponent.value.componentBgColor) style += 'background-color:' + diyComponent.value.componentBgColor + ';';
|
||||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;';
|
||||
return style;
|
||||
})
|
||||
|
||||
const itemCss = computed(() => {
|
||||
var style = '';
|
||||
if (diyComponent.value.elementBgColor) style += 'background-color:' + diyComponent.value.elementBgColor + ';';
|
||||
if (diyComponent.value.topElementRounded) style += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.topElementRounded) style += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
|
||||
if (diyComponent.value.bottomElementRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;';
|
||||
return style;
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.pullDownRefreshCount,
|
||||
(newValue, oldValue) => {
|
||||
// 处理下拉刷新业务
|
||||
}
|
||||
)
|
||||
|
||||
const getArticleListFn = () => {
|
||||
interface dataStructure {
|
||||
ids ?: Array<number>,
|
||||
limit ?: number
|
||||
}
|
||||
let data : dataStructure = {};
|
||||
|
||||
if (diyComponent.value.sources == "diy")
|
||||
data.ids = diyComponent.value.articleIds;
|
||||
else
|
||||
data.limit = diyComponent.value.count;
|
||||
|
||||
interface takeDataStructure {
|
||||
data : Array<Object>,
|
||||
msg : string,
|
||||
code : number
|
||||
}
|
||||
getArticleAll(data).then((res : takeDataStructure) => {
|
||||
articleList.value = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
if (diyStore.mode == 'decorate') {
|
||||
let obj = {
|
||||
image: "",
|
||||
summary: "文章摘要",
|
||||
title: "文章标题",
|
||||
create_time: "2023-03-28 09:00:00"
|
||||
};
|
||||
articleList.value.push(obj);
|
||||
articleList.value.push(obj);
|
||||
} else {
|
||||
getArticleListFn();
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
const toLink = (id : string) => {
|
||||
redirect({ url: '/cms/pages/detail', param: { id } })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 单行超出隐藏 */
|
||||
.using-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
/* 多行超出隐藏 */
|
||||
.multi-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
2
cms/uni-app/locale/en.json
Normal file
2
cms/uni-app/locale/en.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
4
cms/uni-app/locale/zh-Hans.json
Normal file
4
cms/uni-app/locale/zh-Hans.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"pages.list": "资讯中心",
|
||||
"pages.detail": "文章详情"
|
||||
}
|
||||
5
cms/uni-app/locale/zh-Hans/pages.detail.json
Normal file
5
cms/uni-app/locale/zh-Hans/pages.detail.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"detail": "文章详情",
|
||||
"abstract": "摘要",
|
||||
"loadingText": "正在加载"
|
||||
}
|
||||
7
cms/uni-app/locale/zh-Hans/pages.list.json
Normal file
7
cms/uni-app/locale/zh-Hans/pages.list.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"list": "文章列表",
|
||||
"noData": "~ 暂无数据 ~",
|
||||
"all": "全部",
|
||||
"end": "-- 到底了 --",
|
||||
"searchPlaceholder": "请输入搜索关键词"
|
||||
}
|
||||
65
cms/uni-app/pages/detail.vue
Normal file
65
cms/uni-app/pages/detail.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view class="bg-white">
|
||||
<block v-if="!loading">
|
||||
<view class="border-solid border-t-0 border-l-0 border-r-0 border-b-[1px] border-gray-200 p-[10px]">
|
||||
<view class="text-[16px]">
|
||||
{{articleDetail.title}}
|
||||
</view>
|
||||
<view class="flex align-center justify-between text-[12px] text-gray-400 mt-[15px]">
|
||||
<text>{{articleDetail.create_time}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mx-[10px] my-[10px] bg-gray-100 p-[8px] text-[14px] rounded-[5px] leading-[1.3]">
|
||||
{{t('abstract')}}:{{articleDetail.summary}}
|
||||
</view>
|
||||
<view class="px-[10px] pd-[10px]">
|
||||
<u-parse :content="articleDetail.content" :tagStyle="style"></u-parse>
|
||||
</view>
|
||||
</block>
|
||||
<u-loading-page bg-color="rgb(248,248,248)" :loading="loading" fontSize="16" color="#333" :loadingText="t('loadingText')"></u-loading-page>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { t } from '@/locale'
|
||||
import { getArticleDetail } from '@/cms/api/article';
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
|
||||
const { setShare, onShareAppMessage, onShareTimeline } = useShare()
|
||||
onShareAppMessage()
|
||||
onShareTimeline()
|
||||
|
||||
let articleDetail = ref<Array<any>>([]);
|
||||
let loading = ref<boolean>(true);
|
||||
let style = {
|
||||
h2: 'margin-bottom: 15px;',
|
||||
p: 'margin-bottom: 10px;line-height: 1.5;',
|
||||
img: 'margin: 10px 0;',
|
||||
};
|
||||
onLoad((option) => {
|
||||
loading.value = true;
|
||||
getArticleDetail(option.id).then((res) => {
|
||||
articleDetail.value = res.data;
|
||||
loading.value = false;
|
||||
let share = {
|
||||
title: articleDetail.value.title,
|
||||
desc: articleDetail.value.intro,
|
||||
url: articleDetail.value.image
|
||||
}
|
||||
uni.setNavigationBarTitle({
|
||||
title: articleDetail.value.title
|
||||
})
|
||||
setShare({
|
||||
wechat: {
|
||||
...share
|
||||
},
|
||||
weapp: {
|
||||
...share
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
164
cms/uni-app/pages/list.vue
Normal file
164
cms/uni-app/pages/list.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<view class="bg-gray-100 min-h-[100vh]">
|
||||
<view class="fixed top-0 inset-x-0 z-10">
|
||||
<view class='p-[10px] bg-white border-solid border-t-0 border-l-0 border-r-0 border-b-[1px] border-gray-200'>
|
||||
<u-search :placeholder="t('searchPlaceholder')" actionText :actionStyle="{'width':0,'margin':0}" v-model="articleTitle" @clickIcon="searchFn"></u-search>
|
||||
</view>
|
||||
<scroll-view :scroll-x="true" :enable-flex="true"
|
||||
class="nav-list bg-white align-center px-[10px] box-border">
|
||||
<view class="flex scroll-view-wrap">
|
||||
<view
|
||||
:class="['nav-item text-[14px] mx-[5px] h-[30px] leading-[30px] my-[5px] border-t-0 border-l-0 border-r-0',{'border-solid border-b-[2px] active': currCategoryId==item.category_id}]"
|
||||
@click="loadCategory(item.category_id)" v-for="(item,index) in categoryList"
|
||||
:key="item.category_id">
|
||||
{{item.name}}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<mescroll-body ref="mescrollRef" @init="mescrollInit" top="220rpx" @down="downCallback" @up="getArticleListFn">
|
||||
<view v-for="(item,index) in articleList" :key="item.id"
|
||||
:class="['bg-white flex align-center p-[10px]',{'border-solid border-t-0 border-l-0 border-r-0 border-b-[1px] border-gray-200': articleList.length-1 !== index}] "
|
||||
@click="toLink(item.id)">
|
||||
<u--image width="174rpx" height="174rpx" :src="img(item.image)" model="aspectFill">
|
||||
<template #error>
|
||||
<u-icon name="photo" color="#999" size="50"></u-icon>
|
||||
</template>
|
||||
</u--image>
|
||||
<view class="flex-1 flex flex-col justify-between ml-[10px]">
|
||||
<view class="text-[16px] leading-[1.3] multi-hidden mt-[2px]">{{item.title}}</view>
|
||||
<view class="text-[14px] using-hidden mb-[10px] mt-[10px] text-gray-500">{{item.summary}}</view>
|
||||
<view class="text-[12px] text-gray-400 flex justify-between mb-[5px]">
|
||||
<text class="">{{item.create_time}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<mescroll-empty v-if="!articleList.length && loading"></mescroll-empty>
|
||||
</mescroll-body>
|
||||
<tabbar />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { t } from '@/locale'
|
||||
import { redirect, img } from '@/utils/common';
|
||||
import { getArticleList, getArticleCategory } from '@/cms/api/article';
|
||||
import MescrollBody from '@/components/mescroll/mescroll-body/mescroll-body.vue';
|
||||
import MescrollEmpty from '@/components/mescroll/mescroll-empty/mescroll-empty.vue';
|
||||
import useMescroll from '@/components/mescroll/hooks/useMescroll.js';
|
||||
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app';
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
|
||||
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom);
|
||||
const { setShare, onShareAppMessage, onShareTimeline } = useShare()
|
||||
setShare()
|
||||
onShareAppMessage()
|
||||
onShareTimeline()
|
||||
|
||||
let categoryList = ref<Array<Object>>([]);
|
||||
let articleList = ref<Array<any>>([]);
|
||||
let currCategoryId = ref<number | string>('');
|
||||
let articleTitle = ref<string>('');
|
||||
let mescrollRef = ref(null);
|
||||
let loading = ref<boolean>(false);
|
||||
|
||||
interface acceptingDataStructure {
|
||||
data : acceptingDataItemStructure,
|
||||
msg : string,
|
||||
code : number
|
||||
}
|
||||
interface acceptingDataItemStructure {
|
||||
data : object,
|
||||
[propName : string] : number | string | object
|
||||
}
|
||||
onLoad(async () => {
|
||||
await getArticleCategory().then((res : acceptingDataStructure) => {
|
||||
const initData = { name: t("all"), category_id: '' };
|
||||
categoryList.value.push(initData);
|
||||
categoryList.value = categoryList.value.concat(res.data.data);
|
||||
});
|
||||
})
|
||||
|
||||
interface mescrollStructure {
|
||||
num : number,
|
||||
size : number,
|
||||
endSuccess : Function,
|
||||
[propName : string] : any
|
||||
}
|
||||
const getArticleListFn = (mescroll : mescrollStructure) => {
|
||||
loading.value = false;
|
||||
let data : object = {
|
||||
category_id: currCategoryId.value,
|
||||
title: articleTitle.value,
|
||||
page: mescroll.num,
|
||||
limit: mescroll.size
|
||||
};
|
||||
|
||||
getArticleList(data).then((res : acceptingDataStructure) => {
|
||||
let newArr = (res.data.data as Array<Object>);
|
||||
//设置列表数据
|
||||
if (mescroll.num == 1) {
|
||||
articleList.value = []; //如果是第一页需手动制空列表
|
||||
}
|
||||
articleList.value = articleList.value.concat(newArr);
|
||||
mescroll.endSuccess(newArr.length);
|
||||
loading.value = true;
|
||||
}).catch(() => {
|
||||
loading.value = true;
|
||||
mescroll.endErr(); // 请求失败, 结束加载
|
||||
})
|
||||
}
|
||||
|
||||
const loadCategory = (id : string) => {
|
||||
currCategoryId.value = id;
|
||||
getMescroll().resetUpScroll();
|
||||
}
|
||||
|
||||
const searchFn = () => {
|
||||
getMescroll().resetUpScroll();
|
||||
}
|
||||
|
||||
const toLink = (id : string) => {
|
||||
redirect({ url: '/cms/pages/detail', param: { id } })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
getMescroll().optUp.textNoMore = t("end");
|
||||
}, 500)
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-item.active {
|
||||
color: $u-primary;
|
||||
}
|
||||
|
||||
.scroll-view-wrap {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
/* 单行超出隐藏 */
|
||||
.using-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
/* 多行超出隐藏 */
|
||||
.multi-hidden {
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
27
cms/web/api/article.ts
Normal file
27
cms/web/api/article.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleList(params: Record<string, any>) {
|
||||
return request.get('cms/article', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
*/
|
||||
export function getArticleAll(params: Record<string, any>) {
|
||||
return request.get('cms/article/all', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情
|
||||
*/
|
||||
export function getArticleDetail(id: number) {
|
||||
return request.get(`cms/article/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章分类
|
||||
*/
|
||||
export function getArticleCategory() {
|
||||
return request.get('cms/category')
|
||||
}
|
||||
3
cms/web/lang/zh-cn/article.detail.json
Normal file
3
cms/web/lang/zh-cn/article.detail.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "文章"
|
||||
}
|
||||
3
cms/web/lang/zh-cn/article.list.json
Normal file
3
cms/web/lang/zh-cn/article.list.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "文章"
|
||||
}
|
||||
10
cms/web/lang/zh-cn/pages.json
Normal file
10
cms/web/lang/zh-cn/pages.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"pages": {
|
||||
"cms": {
|
||||
"article": {
|
||||
"list": "文章",
|
||||
"detail": "文章"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
cms/web/pages/article/detail.vue
Normal file
86
cms/web/pages/article/detail.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="w-full min-h-[100%] main-container pt-5">
|
||||
<div class="mt-[20px] mb-[50px]" v-if="articleDetail">
|
||||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item :to="{ path: '/cms/article/list' }">文章</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>{{ articleDetail.category_name }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div>
|
||||
<p class="py-[20px] text-center text-[24px]">{{ articleDetail.title }}</p>
|
||||
<div class="flex justify-center">
|
||||
<!-- <div class="mr-3 flex items-center text-gray-500 text-sm text-[#999]"><el-icon><View /></el-icon> <span class="ml-1">浏览量:158</span></div> -->
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm text-[#999]">
|
||||
<el-icon>
|
||||
<Clock />
|
||||
</el-icon>
|
||||
<span class="ml-1">时间:{{ articleDetail.create_time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[50px]" v-html="articleDetail.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { getArticleDetail } from '@/cms/api/article'
|
||||
import { ArrowRight } from '@element-plus/icons-vue'
|
||||
import { nMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const Route = useRoute(); //获取到值
|
||||
const articleDetail = ref();
|
||||
onMounted(() => {
|
||||
obtainArticleInfo(Route.query.id)
|
||||
});
|
||||
|
||||
const obtainArticleInfo = (id) => {
|
||||
getArticleDetail(id).then(res => {
|
||||
articleDetail.value = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.index-carousel {
|
||||
background-image: url('@/assets/images/index_carousel.png');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.article-wrap {
|
||||
span {
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 5px var(--el-color-primary-light-7);
|
||||
|
||||
&.active {
|
||||
background-image: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-image: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tow-line-overflow {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.custom-tabs-label span {
|
||||
font-size: 20px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
</style>
|
||||
160
cms/web/pages/article/list.vue
Normal file
160
cms/web/pages/article/list.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="w-full main-container pt-5">
|
||||
<el-carousel height="350px" indicator-position="none" arrow="never">
|
||||
<el-carousel-item>
|
||||
<div class="h-full index-carousel"></div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<div class="mt-[20px] mb-[50px]">
|
||||
<div>
|
||||
<div class="w-full">
|
||||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||
<el-breadcrumb-item :to="{ path: '/cms/article/list' }">文章</el-breadcrumb-item>
|
||||
<el-breadcrumb-item v-if="selectedCategoryName">{{ selectedCategoryName }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div class="flex mt-[20px] items-start">
|
||||
<div class="w-[50px]">类目:</div>
|
||||
<el-row>
|
||||
<el-button class="mb-[10px]" @click="selectedCategory(categoryItem)" v-for="(categoryItem, categoryIndex) in activeCategoryLsit" :key="categoryIndex">{{ categoryItem.name }}</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="article-list mb-[20px] cursor-pointer" v-for="(activeItem, activeIndex) in articleTableData.data" :key="activeIndex" @click="toLink(activeItem.id)">
|
||||
<div class="flex justify-between relative py-[20px] border-b-1 border-gray-300 border-solid">
|
||||
<div class="w-[150px] h-[150px] flex items-center">
|
||||
<img :src="img(activeItem.image)" />
|
||||
</div>
|
||||
<div class="w-[1030px]">
|
||||
<p class="text-xl font-bold">{{ activeItem.title }}</p>
|
||||
<span class="overflow-ellipsis mt-2 mb-2 tow-line-overflow text-gray-500">{{ activeItem.intro }}</span>
|
||||
</div>
|
||||
<!-- <div class="activeBo flex items-right mt-2 justify-end absolute">
|
||||
<span class="mr-5 text-sm text-gray-500">{{ activeItem.create_time }}</span>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><View /></el-icon> <span class="ml-1">158</span></div>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><Pointer /></el-icon> <span class="ml-1">22</span></div>
|
||||
<div class="mr-3 flex items-center text-gray-500 text-sm"><el-icon><Star /></el-icon> <span class="ml-1">55</span></div>
|
||||
<div class="flex items-center text-gray-500 text-sm"><el-icon><ChatDotRound /></el-icon> <span class="ml-1">655</span></div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<el-pagination class="justify-center" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" :page-size="articleTableData.limit" background
|
||||
layout="prev, pager, next" :total="articleTableData.total" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { getArticleCategory, getArticleList } from '@/cms/api/article'
|
||||
import { ArrowRight } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const activeCategoryLsit = ref([])
|
||||
const selectedCategoryName = ref()
|
||||
const articleTableData = reactive({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
total: 0,
|
||||
loading: true,
|
||||
data: [],
|
||||
searchParam: {
|
||||
title: '',
|
||||
category_id: ''
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
const loadArticleList = (page: number = 1) => {
|
||||
articleTableData.loading = true
|
||||
articleTableData.page = page
|
||||
|
||||
getArticleList({
|
||||
page: articleTableData.page,
|
||||
limit: articleTableData.limit,
|
||||
...articleTableData.searchParam
|
||||
}).then(res => {
|
||||
articleTableData.loading = false
|
||||
articleTableData.data = res.data.data
|
||||
articleTableData.total = res.data.total
|
||||
}).catch(() => {
|
||||
articleTableData.loading = false
|
||||
})
|
||||
}
|
||||
loadArticleList()
|
||||
|
||||
const checkArticleCategory = () => {
|
||||
getArticleCategory().then(res => {
|
||||
activeCategoryLsit.value = res.data.data;
|
||||
})
|
||||
}
|
||||
checkArticleCategory()
|
||||
|
||||
const selectedCategory = (item) => {
|
||||
articleTableData.searchParam.category_id = item.category_id;
|
||||
selectedCategoryName.value = item.name
|
||||
}
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
loadArticleList(val)
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
loadArticleList(val)
|
||||
}
|
||||
const toLink = (id) => {
|
||||
router.push(`/cms/article/detail?id=${id}`)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.index-carousel {
|
||||
background-image: url('@/assets/images/index_carousel.png');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.article-wrap {
|
||||
span {
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 5px var(--el-color-primary-light-7);
|
||||
|
||||
&.active {
|
||||
background-image: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-image: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tow-line-overflow {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.custom-tabs-label span {
|
||||
font-size: 20px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.activeBo {
|
||||
bottom: 20px;
|
||||
right: 0px
|
||||
}
|
||||
</style>
|
||||
10
cms/web/pages/routes.ts
Normal file
10
cms/web/pages/routes.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default [
|
||||
{
|
||||
path: "/cms/article/list",
|
||||
component: () => import('~/cms/pages/article/list.vue')
|
||||
},
|
||||
{
|
||||
path: "/cms/article/detail",
|
||||
component: () => import('~/cms/pages/article/detail.vue')
|
||||
}
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user