update addon

This commit is contained in:
全栈小学生 2023-10-23 17:14:04 +08:00
parent 5e58652a4e
commit 2f59c7d5f0
126 changed files with 6 additions and 6630 deletions

View File

@ -1,37 +0,0 @@
<?php
namespace addon\cms;
/**
* 插件安装之后单独的插件方法
* Class Manage
* @package addon\cms
*/
class Manage
{
/**
* 插件安装执行
*/
public function install()
{
return true;
}
/**
* 插件卸载执行
*/
public function uninstall()
{
return true;
}
/**
* 插件升级执行
*/
public function upgrade()
{
return true;
}
}

View File

@ -1,102 +0,0 @@
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});
}

View File

@ -1,17 +0,0 @@
{
"name": "栏目名称",
"sort": "排序",
"isShow": "是否显示",
"namePlaceholder": "请输入栏目名称",
"sortPlaceholder": "请输入排序",
"isShowPlaceholder": "是否显示",
"addArticleCategory": "添加栏目",
"updateArticleCategory": "编辑栏目",
"articleCategoryDeleteTips": "确定要删除该栏目吗?",
"nameMax": "名称不能超过20个字符",
"sortNumber": "排序号必须是数字",
"sortBetween": "排序号不能超过10000",
"show": "显示",
"hide": "不显示",
"articleNumber": "文章数量"
}

View File

@ -1,36 +0,0 @@
{
"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": "未读取到文章信息!"
}

View File

@ -1,21 +0,0 @@
{
"categoryName": "栏目",
"ID": "ID",
"title": "标题",
"intro": "简介",
"summary": "摘要",
"image": "封面",
"author": "作者",
"content": "文章内容",
"visit": "浏览量",
"visitVirtual": "初始浏览量",
"isShow": "是否显示",
"sort": "排序",
"createTime": "创建时间",
"updateTime": "更新时间",
"addArticle": "添加文章",
"updateArticle": "编辑文章",
"titlePlaceholder": "请输入文章标题",
"categoryIdPlaceholder": "请选择文章栏目",
"articleDeleteTips": "确定要删除该文章吗?"
}

View File

@ -1,144 +0,0 @@
<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>

View File

@ -1,135 +0,0 @@
<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>

View File

@ -1,176 +0,0 @@
<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(/&nbsp;/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>

View File

@ -1,181 +0,0 @@
<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>

View File

@ -1,177 +0,0 @@
<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>

View File

@ -1,108 +0,0 @@
<?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');
}
}

View File

@ -1,95 +0,0 @@
<?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');
}
}

View File

@ -1,64 +0,0 @@
<?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
]);

View File

@ -1,59 +0,0 @@
<?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));
}
}

View File

@ -1,48 +0,0 @@
<?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));
}
}

View File

@ -1,44 +0,0 @@
<?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;
/**
* 微官网1
*/
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);

View File

@ -1,23 +0,0 @@
<?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' => []
],
],
],
],
];

View File

@ -1,18 +0,0 @@
<?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' => ''
],
]
],
];

View File

@ -1,73 +0,0 @@
<?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,
],
]
]
]
]
];

View File

@ -1,7 +0,0 @@
<?php
return [
'listen' => [
'WapIndex' => [ 'addon\cms\app\listener\WapIndexListener' ],
]
];

View File

@ -1,14 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,20 +0,0 @@
<?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',
],
];

View File

@ -1,27 +0,0 @@
<?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',
]
];

View File

@ -1,14 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,22 +0,0 @@
<?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' => '文章栏目管理',
],
];

View File

@ -1,30 +0,0 @@
<?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' => '文章内容必须填写',
],
];

View File

@ -1,31 +0,0 @@
<?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'
],
];
}
}

View File

@ -1,139 +0,0 @@
<?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);
}
}
}

View File

@ -1,54 +0,0 @@
<?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();
}
}

View File

@ -1,102 +0,0 @@
<?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, name, sort, is_show, create_time, update_time';
$order = 'create_time desc';
$search_model = $this->model->withSearch(['name'], $where)->field($field)->order($order)->append(["article_num"]);
return $this->pageQuery($search_model);
}
/**
* 查询所有分类(文章添加)
*/
public function getAll()
{
$field = 'category_id, name, sort';
$order = 'sort desc';
return $this->model->where([['is_show', '=', 1]])->field($field)->order($order)->select()->toArray();
}
/**
* 获取文章分类信息
* @param int $id
* @return array
*/
public function getInfo(int $id)
{
$field = 'category_id, name, sort, is_show, create_time, update_time';
return $this->model->field($field)->where([['category_id', '=', $id]])->append(["article_num"])->findOrEmpty()->toArray();
}
/**
* 添加文章分类
* @param array $data
* @return mixed
*/
public function add(array $data)
{
$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]])->update($data);
return true;
}
/**
* 删除文章分类
* @param int $id
* @return bool
*/
public function del(int $id)
{
return $this->model->where([['category_id', '=', $id]])->delete();
}
}

View File

@ -1,92 +0,0 @@
<?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 = [])
{
$field = 'id, category_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->withSearch([ 'title', 'category_id', 'is_show'], $where)->with('cmsArticleCategory')->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, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
return $this->model->where([ [ 'id', '=', $id ]])->with('cmsArticleCategory')->field($field)->append(['image_thumb_small'])->findOrEmpty()->toArray();
}
/**
* 添加文章
* @param array $data
* @return mixed
*/
public function add(array $data)
{
$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 ]])->update($data);
return true;
}
/**
* 删除文章
* @param int $id
* @return bool
*/
public function del(int $id)
{
return $this->model->where([ [ 'id', '=', $id ] ])->delete();
}
}

View File

@ -1,53 +0,0 @@
<?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, name, sort, is_show, create_time, update_time';
$order = 'create_time desc';
$search_model = $this->model->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, name, sort, is_show, create_time, update_time';
return $this->model->field($field)->where([['category_id', '=', $id]])->findOrEmpty()->toArray();
}
}

View File

@ -1,74 +0,0 @@
<?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 = [])
{
$field = 'id, category_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->withSearch([ 'title', 'category_id'], $where)->with('cmsArticleCategory')->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){
$field = 'id, category_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([ ['is_show', '=', 1]])->withSearch([ 'title', 'category_id', 'ids' ], $where)->limit($limit)->with('cmsArticleCategory')->field($field)->append(['image_thumb_mid'])->order($order)->select()->toArray();
}
/**
* 获取文章信息
* @param int $id
* @return array
*/
public function getInfo(int $id)
{
$field = 'id, category_id, title, intro, summary, image, author, content, visit, visit_virtual, is_show, sort, create_time, update_time';
return $this->model->with('cmsArticleCategory')->field($field)->where([ [ 'id', '=', $id ] ])->append(['image_thumb_big'])->findOrEmpty()->toArray();
}
}

View File

@ -1,57 +0,0 @@
<?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'],
];
}

View File

@ -1,45 +0,0 @@
<?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'],
];
}

View File

@ -1,9 +0,0 @@
{
"title": "微官网",
"desc": "文章栏目管理",
"key": "cms",
"version": "1.0.1",
"author": "niucloud",
"type": "app",
"support_app": ""
}

View File

@ -1,19 +0,0 @@
<?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
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,37 +0,0 @@
DROP TABLE IF EXISTS `{{prefix}}cms_article`;
CREATE TABLE `{{prefix}}cms_article` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文章id',
`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 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',
`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 COMMENT='文章分类表';

View File

@ -1,4 +0,0 @@
DROP TABLE IF EXISTS `{{prefix}}cms_article`;
DROP TABLE IF EXISTS `{{prefix}}cms_article_category`;

View File

@ -1,29 +0,0 @@
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')
}

View File

@ -1,132 +0,0 @@
<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', 'pullDownRefresh']);
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.pullDownRefresh,
(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>

View File

@ -1,2 +0,0 @@
{
}

View File

@ -1,4 +0,0 @@
{
"pages.list": "资讯中心",
"pages.detail": "文章详情"
}

View File

@ -1,5 +0,0 @@
{
"detail": "文章详情",
"abstract": "摘要",
"loadingText": "正在加载"
}

View File

@ -1,7 +0,0 @@
{
"list": "文章列表",
"noData": "~ 暂无数据 ~",
"all": "全部",
"end": "-- 到底了 --",
"searchPlaceholder": "请输入搜索关键词"
}

View File

@ -1,65 +0,0 @@
<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>

View File

@ -1,164 +0,0 @@
<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>

View File

@ -1,27 +0,0 @@
/**
*
*/
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')
}

View File

@ -1,3 +0,0 @@
{
"title": "文章"
}

View File

@ -1,3 +0,0 @@
{
"title": "文章"
}

View File

@ -1,10 +0,0 @@
{
"pages": {
"cms": {
"article": {
"list": "文章",
"detail": "文章"
}
}
}
}

View File

@ -1,86 +0,0 @@
<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>

View File

@ -1,160 +0,0 @@
<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>

View File

@ -1,10 +0,0 @@
export default [
{
path: "/cms/article/list",
component: () => import('~/cms/pages/article/list.vue')
},
{
path: "/cms/article/detail",
component: () => import('~/cms/pages/article/detail.vue')
}
]

View File

@ -3,12 +3,12 @@
return [
'DIY_HELLO_WORLD_INDEX' => [
'title' => get_lang('dict_diy.page_hello_world_index'),
'page' => 'hello_world/pages/index',
'page' => '/hello_world/pages/index',
'action' => ''
],
'DIY_HELLO_WORLD_INFO' => [
'title' => get_lang('dict_diy.page_hello_world_info'),
'page' => 'hello_world/pages/info',
'page' => '/hello_world/pages/info',
'action' => ''
],
];

View File

@ -2,6 +2,7 @@
return [
//充值成功通知,站点端发送
'recharge_success' => [
'addon' => 'hello_world',
'key' => 'recharge_success',
'receiver_type' => 1,
'name' => '充值成功通知',

View File

@ -2,5 +2,5 @@ CREATE TABLE IF NOT EXISTS `{{prefix}}hello_world` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='演示插件表';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci COMMENT='演示插件表';
INSERT INTO `{{prefix}}hello_world`(`name`) VALUES ('名称');

View File

@ -2,13 +2,13 @@
<view>
hello-index固定模板示例我也可以装修
<!-- 自定义模板渲染 -->
<diy-group :data="props.data" :pullDownRefresh="props.pullDownRefresh"></diy-group>
<diy-group :data="props.data" :pullDownRefreshCount="props.pullDownRefreshCount"></diy-group>
</view>
</template>
<script setup lang="ts">
import { computed, watch } from 'vue';
const props = defineProps(['data', 'pullDownRefresh']);
const props = defineProps(['data', 'pullDownRefreshCount']);
</script>
<style></style>

View File

@ -1,37 +0,0 @@
<?php
namespace addon\recharge;
/**
* 插件安装之后单独的插件方法
* Class Manage
* @package addon\recharge
*/
class Manage
{
/**
* 插件安装执行
*/
public function install()
{
return true;
}
/**
* 插件卸载执行
*/
public function uninstall()
{
return true;
}
/**
* 插件升级执行
*/
public function upgrade()
{
return true;
}
}

View File

@ -1,70 +0,0 @@
import request from '@/utils/request'
/***************************************************** 充值订单 ****************************************************/
/**
*
* @param params
* @returns
*/
export function getRechargeOrderList(params: Record<string, any>) {
return request.get(`recharge/order`, {params})
}
/**
*
* @param params
* @returns
*/
export function getRechargeStat(params: Record<string, any>) {
return request.get(`recharge/order/stat`, {params})
}
/**
*
* @param order_id
* @returns
*/
export function getRechargeOrderInfo(order_id: number) {
return request.get(`recharge/order/${order_id}`);
}
/**
*
* @returns
*/
export function getRechargeOrderStatusList() {
return request.get(`recharge/order/status`)
}
/**
* 退
* @returns
*/
export function getRechargeRefund(params: Record<string, any>) {
return request.get(`recharge/refund`, {params})
}
/**
* 退
* @returns
*/
export function getRechargeRefundStatus() {
return request.get(`recharge/refund/status`)
}
/**
* 退
* @returns
*/
export function rechargeRefund(order_id: number) {
return request.put(`recharge/refund/${order_id}`, {}, {showSuccessMessage: true});
}
/**
* 退
* @returns
*/
export function getRechargeRefundStat() {
return request.get(`recharge/refund/stat`);
}

View File

@ -1,19 +0,0 @@
{
"orderInfo":"订单详情",
"orderDiscountMoney":"优惠金额",
"ip":"下单IP",
"payTime":"支付时间",
"remark":"商家留言",
"memberMessage":"买家留言",
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间"
}

View File

@ -1,23 +0,0 @@
{
"totalRechargeRefundMoney":"充值退款(元)",
"totalRechargeMoney":"充值金额(元)",
"rechargeRefundMoney":"充值退款",
"rechargeNo":"充值单号",
"orderStatus":"订单状态",
"rechargeNoPlaceholder":"请输入充值单号",
"createTime":"充值时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"会员信息",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"refundBtn":"退款",
"refundContent":"是否确认退款",
"payTime": "支付时间",
"refundStatus": "退款状态",
"startMoney": "起始金额",
"endMoney": "结束金额"
}

View File

@ -1,24 +0,0 @@
{
"refundNumber":"退款单号",
"userInfo":"用户信息",
"sourceNumber":"来源单号",
"refundAmount":"退款金额",
"refundTime":"退款时间",
"detail": "详情",
"statusName": "状态",
"memberInfo": "会员信息",
"refundSource": "退款来源",
"startDate":"开始时间",
"endDate":"结束时间",
"refundStatus": "退款状态",
"accumulateRefundMoney": "累计退款金额(元)",
"haveRefundMoney": "退款中金额(元)",
"refundSuccessMonry": "退款成功金额(元)",
"refundFailMoney": "退款失败金额(元)",
"memberInfoPlaceholder":"请输入会员编号/昵称/手机号",
"refundNumberPlaceholder":"请输入退款单号",
"orderNumber": "来源单号",
"orderNumberPlaceholder": "请输入来源单号",
"refundDetail": "退款详情",
"nickname": "会员昵称"
}

View File

@ -1,19 +0,0 @@
{
"orderInfo":"订单详情",
"orderDiscountMoney":"优惠金额",
"ip":"下单IP",
"payTime":"支付时间",
"remark":"商家留言",
"memberMessage":"买家留言",
"orderNo":"订单编号",
"orderStatus":"订单状态",
"orderNoPlaceholder":"请输入订单编号",
"createTime":"创建时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"买家",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间"
}

View File

@ -1,23 +0,0 @@
{
"totalRechargeRefundMoney":"充值退款(元)",
"totalRechargeMoney":"充值金额(元)",
"rechargeRefundMoney":"充值退款",
"rechargeNo":"充值单号",
"orderStatus":"订单状态",
"rechargeNoPlaceholder":"请输入充值单号",
"createTime":"充值时间",
"rechargeMoney":"充值金额",
"orderMoney":"订单金额",
"member":"会员信息",
"orderFromName":"订单来源",
"payTypeName":"支付方式",
"startDate":"开始时间",
"endDate":"结束时间",
"namePlaceholder":"请选择",
"refundBtn":"退款",
"refundContent":"是否确认退款",
"payTime": "支付时间",
"refundStatus": "退款状态",
"startMoney": "起始金额",
"endMoney": "结束金额"
}

View File

@ -1,24 +0,0 @@
{
"refundNumber":"退款单号",
"userInfo":"用户信息",
"sourceNumber":"来源单号",
"refundAmount":"退款金额",
"refundTime":"退款时间",
"detail": "详情",
"statusName": "状态",
"memberInfo": "会员信息",
"refundSource": "退款来源",
"startDate":"开始时间",
"endDate":"结束时间",
"refundStatus": "退款状态",
"accumulateRefundMoney": "累计退款金额(元)",
"haveRefundMoney": "退款中金额(元)",
"refundSuccessMonry": "退款成功金额(元)",
"refundFailMoney": "退款失败金额(元)",
"memberInfoPlaceholder":"请输入会员编号/昵称/手机号",
"refundNumberPlaceholder":"请输入退款单号",
"orderNumber": "来源单号",
"orderNumberPlaceholder": "请输入来源单号",
"refundDetail": "退款详情",
"nickname": "会员昵称"
}

View File

@ -1,110 +0,0 @@
<template>
<div class="main-container">
<el-form :model="formData" label-width="150px" ref="formRef" class="page-form" v-loading="loading">
<el-card class="box-card !border-none relative" shadow="never" v-if="formData">
<h3 class="panel-title">{{ t('orderInfo') }}</h3>
<el-form-item :label="t('orderNo')">
<div class="input-width">{{ formData.order_no }}</div>
</el-form-item>
<el-form-item :label="t('orderMoney')">
<div class="input-width">{{ formData.order_money }}</div>
</el-form-item>
<el-form-item :label="t('orderDiscountMoney')">
<div class="input-width">{{ formData.order_discount_money }}</div>
</el-form-item>
<el-form-item :label="t('member')">
<div class="input-width">
<div class="flex flex flex-col cursor-pointer" @click="toMember(formData.member_id)">
<span class="">{{ formData.member.nickname || '' }}</span>
<span class="">{{ formData.member.mobile || '' }}</span>
</div>
</div>
</el-form-item>
<el-form-item :label="t('ip')">
<div class="input-width">{{ formData.ip }}</div>
</el-form-item>
<el-form-item :label="t('orderFromName')">
<div class="input-width">{{ formData.order_from_name }}</div>
</el-form-item>
<el-form-item :label="t('orderStatus')">
<div class="input-width">{{ formData.order_status_info.name }}</div>
</el-form-item>
<el-form-item :label="t('payTypeName')">
<div class="input-width">{{ formData.pay_type_name }}</div>
</el-form-item>
<el-form-item :label="t('createTime')">
<div class="input-width">{{ formData.create_time || '' }}</div>
</el-form-item>
<el-form-item :label="t('payTime')">
<div class="input-width">{{ formData.pay_time || '' }}</div>
</el-form-item>
<el-form-item :label="t('remark')">
<div class="input-width">{{ formData.remark || '' }}</div>
</el-form-item>
<el-form-item :label="t('memberMessage')">
<div class="input-width">{{ formData.member_message || '' }}</div>
</el-form-item>
</el-card>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
import { getRechargeOrderInfo } from '@/recharge/api/recharge'
import { useRoute, useRouter } from 'vue-router'
import useAppStore from '@/stores/modules/app'
const route = useRoute()
const router = useRouter()
const orderId: number = parseInt(route.query.order_id)
const loading = ref(true)
const formData: Record<string, any> | null = ref(null)
const setFormData = async (orderId: number = 0) => {
loading.value = true
formData.value = null
await getRechargeOrderInfo(orderId)
.then(({ data }) => {
formData.value = data
})
.catch(() => {
})
loading.value = false
}
if (orderId) setFormData(orderId)
else loading.value = false
const formRef = ref<FormInstance>()
const back = () => {
router.push({ path: '/finance/recharge' })
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,280 +0,0 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-[20px]">{{pageName}}</span>
</div>
<el-card class="box-card !border-none table-search-wra base-bg !px-[35px]" shadow="never">
<el-row class="flex">
<el-col :span="12" class="min-w-[100px]">
<el-statistic :value="rechargeStatistics.recharge_money ? Number.parseFloat(rechargeStatistics.recharge_money).toFixed(2) : '0.00'">
<template #title>
<div class="text-[14px] mb-[9px]">{{ t('totalRechargeMoney') }}</div>
</template>
</el-statistic>
</el-col>
<el-col :span="12" class="min-w-[100px]">
<el-statistic :value="rechargeStatistics.recharge_refund_money ? Number.parseFloat(rechargeStatistics.recharge_refund_money).toFixed(2) : '0.00'">
<template #title>
<div class="text-[14px] mb-[9px]">{{ t('totalRechargeRefundMoney') }}</div>
</template>
</el-statistic>
</el-col>
</el-row>
</el-card>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="orderTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('rechargeNo')" prop="order_no">
<el-input v-model="orderTableData.searchParam.order_no" :placeholder="t('rechargeNoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('orderFromName')" prop="order_from">
<el-select v-model="orderTableData.searchParam.order_from" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item" :value="key" v-for="(item, key) in channelList" />
</el-select>
</el-form-item>
<el-form-item :label="t('orderStatus')" prop="order_status">
<el-select v-model="orderTableData.searchParam.order_status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item['name']" :value="item['status']" v-for="item in statusList" />
</el-select>
</el-form-item>
<el-form-item :label="t('createTime')" prop="create_time">
<el-date-picker v-model="orderTableData.searchParam.create_time" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item :label="t('rechargeMoney')">
<div class="region-input">
<el-form-item prop="start_money">
<input type="text" :placeholder="t('startMoney')" v-model="orderTableData.searchParam.start_money">
</el-form-item>
<span class="separator">-</span>
<el-form-item prop="end_money">
<input type="text" :placeholder="t('endMoney')" v-model="orderTableData.searchParam.end_money">
</el-form-item>
</div>
</el-form-item>
<el-form-item :label="t('payTime')">
<el-date-picker v-model="orderTableData.searchParam.pay_time" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadOrderList()">{{ t('search') }}</el-button>
<el-button @click="searchFormRef?.resetFields()">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="mt-[16px]">
<el-table :data="orderTableData.data" size="large" v-loading="orderTableData.loading">
<template #empty>
<span>{{ !orderTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column :show-overflow-tooltip="true" :label="t('member')" align="left" min-width="140">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span class="">{{ row.member.nickname || '' }}</span>
<span class="">{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="order_no" :show-overflow-tooltip="true" :label="t('rechargeNo')" align="center" min-width="140" />
<el-table-column prop="order_money" :label="t('rechargeMoney')" align="center" min-width="140" />
<el-table-column prop="order_from_name" :label="t('orderFromName')" align="center" min-width="140" />
<el-table-column :label="t('orderStatus')" min-width="120" align="center">
<template #default="{ row }">
{{ row.order_status_info.name }}
</template>
</el-table-column>
<el-table-column prop="pay_type_name" :label="t('payTypeName')" align="center" min-width="140" />
<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('payTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.pay_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="infoEvent(row)">{{ t('info') }}</el-button>
<el-button v-if="[1, 10].includes(row.order_status_info.status) && row.is_enable_refund && row.refund_status == 0" type="primary" link @click="refundFn(row)">{{ t('refundBtn') }}</el-button>
<template v-for="(item, index) in row.order_status_info.action">
<el-button type="danger" link @click="orderEvent(row, item.class)">{{ item.name }}</el-button>
</template>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="orderTableData.page" v-model:page-size="orderTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="orderTableData.total"
@size-change="loadOrderList()" @current-change="loadOrderList" />
</div>
</div>
</el-card>
<!-- 是否退款 -->
<el-dialog v-model="refundShowDialog" :title="t('refundBtn')" width="500px" :destroy-on-close="true">
<p>{{ t('refundContent') }}</p>
<template #footer>
<span class="dialog-footer">
<el-button @click="refundShowDialog = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="confirmRefund" :loading="refundLoading">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { getRechargeOrderStatusList, getRechargeOrderList, rechargeRefund, getRechargeStat } from '@/recharge/api/recharge'
import { getChannelType } from '@/app/api/sys'
import { img } from '@/utils/common'
import { useRouter, useRoute } from 'vue-router'
import { AnyObject } from '@/types/global'
import type { FormInstance } from 'element-plus'
const route = useRoute()
const router = useRouter()
const pageName = route.meta.title
const memberId: number = parseInt(route.query.id || 0)
const channelList = ref([])
const setChannelList = async () => {
channelList.value = await (await getChannelType({})).data
}
setChannelList()
const orderTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
order_no: '',
order_status: '',
member_id: memberId,
create_time: [],
pay_time: [],
order_from: '',
start_money: '',
end_money: ''
}
})
const rechargeStatistics = ref([])
const checkRechargeInfo = () => {
getRechargeStat({
member_id: memberId
}).then(res => {
rechargeStatistics.value = res.data
})
}
checkRechargeInfo()
const statusList = ref([])
const searchFormRef = ref<FormInstance>()
const setCategoryList = async () => {
statusList.value = await (await getRechargeOrderStatusList({})).data
}
setCategoryList()
const loadOrderList = (page: number = 1) => {
orderTableData.loading = true
orderTableData.page = page
getRechargeOrderList({
page: orderTableData.page,
limit: orderTableData.limit,
...orderTableData.searchParam
}).then(res => {
orderTableData.loading = false
orderTableData.data = res.data.data
orderTableData.total = res.data.total
}).catch(() => {
orderTableData.loading = false
})
}
loadOrderList()
/**
* 订单详情
* @param data
*/
const infoEvent = (data: any) => {
router.push(`/order/recharge/detail?order_id=${data.order_id}`)
}
/**
* 订单操作
*/
const orderEvent = (data: any, type: string) => {
}
/**
* 退款操作
*/
const refundShowDialog = ref(false)
let refundData: AnyObject | null = null
const refundLoading = ref(false)
const refundFn = (data: AnyObject) => {
refundShowDialog.value = true
refundData = data
}
/**
* 确认退款
*/
const confirmRefund = () => {
if (refundLoading.value) return
refundLoading.value = true
rechargeRefund(refundData?.order_id).then(res => {
refundShowDialog.value = false
refundLoading.value = false
}).catch(() => {
refundLoading.value = false
})
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,238 +0,0 @@
<template>
<div class="main-container">
<el-card class="box-card !border-none" shadow="never">
<div class="flex justify-between items-center mb-[5px]">
<span class="text-[20px]">{{ pageName }}</span>
</div>
<el-card class="box-card !border-none table-search-wra base-bg !px-[35px]" shadow="never">
<el-row class="flex">
<el-col :span="12">
<div class="statistic-card">
<el-statistic :value="refundStat.refund_all_money"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('accumulateRefundMoney') }}</span>
</div>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="statistic-card">
<el-statistic :value="refundStat.refund_have_money"></el-statistic>
<div class="statistic-footer">
<div class="footer-item text-[14px] text-[#666]">
<span>{{ t('haveRefundMoney') }}</span>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
<el-card class="box-card !border-none mb-[10px] table-search-wrap" shadow="never">
<el-form :inline="true" :model="refundTableData.searchParam" ref="searchFormRef">
<el-form-item :label="t('memberInfo')" prop="keywords">
<el-input v-model="refundTableData.searchParam.keywords" class="w-[240px]" :placeholder="t('memberInfoPlaceholder')" />
</el-form-item>
<el-form-item :label="t('refundNumber')" prop="refund_no">
<el-input v-model="refundTableData.searchParam.refund_no" class="w-[240px]" :placeholder="t('refundNumberPlaceholder')" />
</el-form-item>
<el-form-item :label="t('orderNumber')" prop="order_no">
<el-input v-model="refundTableData.searchParam.order_no" class="w-[240px]" :placeholder="t('orderNumberPlaceholder')" />
</el-form-item>
<el-form-item :label="t('refundStatus')" prop="status">
<el-select v-model="refundTableData.searchParam.status" clearable class="input-width">
<el-option :label="t('selectPlaceholder')" value="" />
<el-option :label="item.name" :value="key" v-for="(item, key) in refundList" />
</el-select>
</el-form-item>
<el-form-item :label="t('refundTime')" prop="create_time">
<el-date-picker v-model="refundTableData.searchParam.create_time" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" :start-placeholder="t('startDate')" :end-placeholder="t('endDate')" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadRefundList()">{{ t('search') }}</el-button>
<el-button @click="resetForm(searchFormRef)">{{ t('reset') }}</el-button>
</el-form-item>
</el-form>
</el-card>
<div>
<el-table :data="refundTableData.data" size="large" v-loading="refundTableData.loading">
<template #empty>
<span>{{ !refundTableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column :show-overflow-tooltip="true" :label="t('memberInfo')" align="left" min-width="140">
<template #default="{ row }">
<div class="flex items-center cursor-pointer " @click="toMember(row.member.member_id)">
<img class="w-[50px] h-[50px] mr-[10px]" v-if="row.member.headimg" :src="img(row.member.headimg)" alt="">
<img class="w-[50px] h-[50px] mr-[10px]" v-else src="@/app/assets/images/default_headimg.png" alt="">
<div class="flex flex flex-col">
<span class="">{{ row.member.nickname || '' }}</span>
<span class="">{{ row.member.mobile || '' }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="refund_no" :label="t('refundNumber')" align="center" min-width="200" />
<el-table-column prop="item.item_name" :label="t('refundSource')" align="center" min-width="140" />
<el-table-column prop="money" :label="t('refundAmount')" align="center" min-width="140" />
<el-table-column :label="t('refundTime')" min-width="180" align="center">
<template #default="{ row }">
{{ row.create_time || '' }}
</template>
</el-table-column>
<el-table-column :label="t('statusName')" min-width="180" align="center">
<template #default="{ row }">
{{ row.status_name || '' }}
</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="infoEvent(row)">{{ t('info') }}</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination v-model:current-page="refundTableData.page" v-model:page-size="refundTableData.limit"
layout="total, sizes, prev, pager, next, jumper" :total="refundTableData.total"
@size-change="loadRefundList()" @current-change="loadRefundList" />
</div>
</div>
</el-card>
<el-dialog v-model="refundInfoShowDialog" :title="t('refundDetail')" width="500px" :destroy-on-close="true">
<el-form :model="refundInfo" label-width="120px" ref="formRef" :rules="formRules" class="page-form">
<el-form-item :label="t('nickname')">
<div class="input-width"> {{ refundInfo.member.nickname }} </div>
</el-form-item>
<el-form-item :label="t('refundSource')">
<div class="input-width"> {{ refundInfo.item.item_name }} </div>
</el-form-item>
<el-form-item :label="t('refundAmount')">
<div class="input-width"> {{ refundInfo.money }} </div>
</el-form-item>
<el-form-item :label="t('orderNumber')">
<div class="input-width"> {{ refundInfo.item.order_no }} </div>
</el-form-item>
<el-form-item :label="t('refundNumber')">
<div class="input-width"> {{ refundInfo.refund_no }} </div>
</el-form-item>
<el-form-item :label="t('refundTime')">
<div class="input-width"> {{ refundInfo.create_time }} </div>
</el-form-item>
<el-form-item :label="t('statusName')">
<div class="input-width"> {{ refundInfo.status_name }} </div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="refundInfoShowDialog = false">{{ t('confirm') }}</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { t } from '@/lang'
import { img } from '@/utils/common'
import { useRouter, useRoute } from 'vue-router'
import { getRechargeRefund, getRechargeRefundStatus, getRechargeRefundStat } from '@/recharge/api/recharge'
import { FormInstance } from 'element-plus'
const router = useRouter()
const route = useRoute()
const pageName = route.meta.title
const searchFormRef = ref<FormInstance>()
const refundTableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: true,
data: [],
searchParam: {
refund_no: '',
// member_id,
create_time: [],
status: '',
keywords: '',
order_no: ''
}
})
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
loadRefundList()
}
/**
* 获取退款列表
*/
const loadRefundList = (page: number = 1) => {
refundTableData.loading = true
refundTableData.page = page
getRechargeRefund({
page: refundTableData.page,
limit: refundTableData.limit,
...refundTableData.searchParam
}).then(res => {
refundTableData.loading = false
refundTableData.data = res.data.data
refundTableData.total = res.data.total
}).catch(() => {
refundTableData.loading = false
})
}
loadRefundList()
const refundList = ref([])
const checkRefundList = () => {
getRechargeRefundStatus().then(res => {
refundList.value = res.data
})
}
checkRefundList()
const refundStat = reactive({
refund_all_money: 0.00,
refund_have_money: 0.00,
refund_Success_money: 0.00,
refund_fail_moey: 0.00
})
const checkRefundStat = () => {
getRechargeRefundStat().then(res => {
refundStat.refund_all_money = res.data.all.money
refundStat.refund_have_money = res.data.have.money
refundStat.refund_Success_money = res.data['3'].money
refundStat.refund_fail_moey = res.data['-1'].money
})
}
checkRefundStat()
const refundInfoShowDialog = ref(false)
const refundInfo = ref({})
const infoEvent = (info) => {
refundInfo.value = info
refundInfoShowDialog.value = true
}
/**
* 会员详情
*/
const toMember = (memberId: number) => {
router.push(`/member/detail?id=${memberId}`)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,119 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\adminapi\controller;
use addon\recharge\app\service\admin\RechargeOrderRefundService;
use addon\recharge\app\service\admin\RechargeOrderService;
use core\base\BaseAdminController;
use think\Response;
class Order extends BaseAdminController
{
/**
* 充值订单列表
* @return Response
*/
public function lists()
{
$data = $this->request->params([
[ 'order_no', '' ],
[ 'order_status', '' ],
[ 'order_from', '' ],
[ 'create_time', [] ],
[ 'pay_time', [] ],
[ 'member_id', '' ],
[ 'start_money', 0 ],
[ 'end_money', 0 ]
]);
return success(( new RechargeOrderService() )->getPage($data));
}
/**
* 充值订单详情
* @param int $order_id
* @return Response
*/
public function detail(int $order_id)
{
return success(( new RechargeOrderService() )->getDetail($order_id));
}
public function status()
{
return success(( new RechargeOrderService() )->getStatus());
}
public function refund($order_id)
{
$res = ( new RechargeOrderRefundService() )->create($order_id);
if ($res === true) return success();
return fail($res);
}
/**
* 退款列表
* @return Response
*/
public function refundLists()
{
$data = $this->request->params([
[ 'create_time', [] ],
[ 'member_id', '' ],
[ 'refund_no', '' ],
[ 'status', '' ],
[ 'keywords', '' ],
[ 'order_no', '' ],
]);
return success(( new RechargeOrderRefundService() )->getPage($data));
}
/**
* 退款详情
* @param int $refund_id
* @return Response
*/
public function refundDetail(int $refund_id)
{
return success(( new RechargeOrderRefundService() )->getDetail($refund_id));
}
/**
* 查询退款状态
* @return Response
*/
public function refundStatus()
{
return success(( new RechargeOrderRefundService() )->getStatus());
}
/**
* 退款统计
*/
public function refundStat()
{
return success(( new RechargeOrderRefundService() )->stat());
}
/**
* 充值统计
*/
public function stat()
{
$data = $this->request->params([
[ 'member_id', '' ],
]);
$res = ( new RechargeOrderService() )->stat($data);
return success($res);
}
}

View File

@ -1,56 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use app\adminapi\middleware\AdminCheckRole;
use app\adminapi\middleware\AdminCheckToken;
use app\adminapi\middleware\AdminLog;
use think\facade\Route;
/**
* 订单相关路由
*/
Route::group('recharge', function() {
/***************************************************** 充值订单 *************************************************/
//订单列表
Route::get('order', 'addon\recharge\app\adminapi\controller\Order@lists');
//订单详情
Route::get('order/:order_id', 'addon\recharge\app\adminapi\controller\Order@detail');
//订单状态
Route::get('order/status', 'addon\recharge\app\adminapi\controller\Order@status');
//订单统计
Route::get('order/stat', 'addon\recharge\app\adminapi\controller\Order@stat');
// 订单发起退款
Route::put('refund/:order_id', 'addon\recharge\app\adminapi\controller\Order@refund');
//退款订单列表
Route::get('refund', 'addon\recharge\app\adminapi\controller\Order@refundLists');
//退款订单详情
Route::get('refund/:refund_id', 'addon\recharge\app\adminapi\controller\Order@refundDetail');
//退款订单状态
Route::get('refund/status', 'addon\recharge\app\adminapi\controller\Order@refundStatus');
//退款订单统计
Route::get('refund/stat', 'addon\recharge\app\adminapi\controller\Order@refundStat');
})->middleware([
AdminCheckToken::class,
AdminCheckRole::class,
AdminLog::class
]);

View File

@ -1,53 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\api\controller;
use addon\recharge\app\service\api\RechargeOrderService;
use core\base\BaseApiController;
use think\Response;
class Recharge extends BaseApiController
{
/**
* 充值订单创建
* @return Response
*/
public function create()
{
$data = $this->request->params([
[ 'member_message', '' ],
[ 'recharge_money', 0 ]
]);
$res = ( new RechargeOrderService() )->recharge($data);
return success($res);
}
public function lists()
{
$data = $this->request->params([
[ 'order_status', '' ]
]);
$res = ( new RechargeOrderService() )->getPage($data);
return success($res);
}
/**
* 查询充值订单详情
* @param int $order_id
* @return Response
*/
public function detail(int $order_id)
{
$res = ( new RechargeOrderService() )->getDetail($order_id);
return success($res);
}
}

View File

@ -1,36 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的saas管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud-admin.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
use app\api\middleware\ApiChannel;
use app\api\middleware\ApiCheckToken;
use app\api\middleware\ApiLog;
use think\facade\Route;
/**
* 会员个人信息管理
*/
Route::group('recharge', function() {
/***************************************************** 充值订单相关 *************************************************/
//充值订单创建
Route::post('recharge', 'addon\recharge\app\api\controller\Recharge@create');
// 充值订单列表
Route::get('recharge', 'addon\recharge\app\api\controller\Recharge@lists');
// 充值订单详情
Route::get('recharge/:order_id', 'addon\recharge\app\api\controller\Recharge@detail');
})->middleware(ApiChannel::class)
->middleware(ApiCheckToken::class, true)
->middleware(ApiLog::class);

View File

@ -1,132 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\dict;
use app\dict\pay\PayDict;
/**
*充值订单相关枚举类
* Class RechargeOrderDict
* @package app\dict\order
*/
class RechargeOrderDict
{
//订单状态
//待支付
const WAIT_PAY = 0;
//已完成
const FINISH = 10;
//已关闭
const CLOSE = -1;
// 退款相关状态
// 未申请
const NOT_APPLAY = 0;
// 退款中
const REFUNDING = 1;
// 退款完成
const REFUND_COMPLETED = 2;
// 退款失败
const REFUND_FAIL = -1;
/**
* 当前订单支持的支付方式
*/
const ALLOW_PAY = [
PayDict::WECHATPAY,
PayDict::ALIPAY,
PayDict::OFFLINEPAY,
];
/**
* 订单类型以及名称
* @return array
*/
public static function getOrderType()
{
return [
'type' => 'recharge',
'name' => get_lang('dict_order.order_type_recharge')
];
}
public static function getStatus($status = '')
{
$data = [
self::WAIT_PAY => [
'name' => '待支付',
'status' => self::WAIT_PAY,
'is_refund' => 0,
'action' => [],
'member_action' => [
[
'name' => '支付',
'class' => '',
'params' => ''
],
],
],
self::FINISH => [
'name' => '已完成',
'status' => self::FINISH,
'is_refund' => 0,
'action' => [],
'member_action' => [
],
],
self::CLOSE => [
'name' => '已关闭',
'status' => self::CLOSE,
'is_refund' => 0,
'action' => [],
'member_action' => [
],
]
];
if ($status == '') {
return $data;
}
return $data[$status] ?? '';
}
/**
* 获取退款状态
* @param string $status
* @return array|array[]|string
*/
public static function getRefundStatus(string $status = '')
{
$data = [
self::REFUNDING => [
'name' => get_lang('dict_order_refund.refunding'),
'status' => self::REFUNDING
],
self::REFUND_COMPLETED => [
'name' => get_lang('dict_order_refund.refund_complete'),
'status' => self::REFUND_COMPLETED
],
self::REFUND_FAIL => [
'name' => get_lang('dict_order_refund.refund_fail'),
'status' => self::REFUND_FAIL
]
];
if ($status == '') {
return $data;
}
return $data[$status] ?? '';
}
}

View File

@ -1,18 +0,0 @@
<?php
return [
'RECHARGE_LINK' => [
'key' => 'recharge',
'addon_title' => get_lang('dict_diy.recharge_title'),
'title' => get_lang('dict_diy.recharge_link'),
'child_list' => [
[
'name' => 'RECHARGE_INDEX',
'title' => get_lang('dict_diy.recharge_index'),
'url' => '/recharge/pages/recharge',
'is_share' => 1,
'action' => ''
]
]
],
];

View File

@ -1,38 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\dict\member;
use core\dict\DictLoader;
/**
* 会员账户变动类型
* Class MemberAccountTypeDict
* @package app\dict\member
*/
class MemberAccountChangeTypeDict
{
/**
* 获取账户变动方式
* @param string $type
* @return array|mixed|string
*/
public static function getType($type = '')
{
$account_change_type = (new DictLoader("MemberAccountChangeType"))->load();
if (empty($type)) {
return $account_change_type;
}
return $account_change_type[$type] ?? '';
}
}

View File

@ -1,39 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\dict\member;
/**
* 会员账户类型
* Class MemberAccountTypeDict
* @package app\dict\member
*/
class MemberAccountTypeDict
{
//会员积分
public const POINT = 'point';
//会员余额
public const BALANCE = 'balance';
public static function getType($type = '')
{
$data = [
self::POINT => get_lang('dict_member.account_point'),
self::BALANCE => get_lang('dict_member.account_balance'),
];
if (empty($type)) {
return $data;
}
return $data[$type] ?? '';
}
}

View File

@ -1,37 +0,0 @@
<?php
use addon\recharge\app\dict\member\MemberAccountTypeDict;
return [
MemberAccountTypeDict::POINT => [
//充值赠送
'recharge_give' => [
//名称
'name' => get_lang('dict_member.account_point_recharge_give'),
//是否增加
'inc' => 1,
//是否减少
'dec' => 0,
],
],
MemberAccountTypeDict::BALANCE => [
//充值
'recharge' => [
//名称
'name' => get_lang('dict_member.account_balance_recharge'),
//是否增加
'inc' => 1,
//是否减少
'dec' => 0,
],
'recharge_refund' => [
//名称
'name' => get_lang('dict_member.account_balance_recharge_refund'),
//是否增加
'inc' => 0,
//是否减少
'dec' => 1,
],
]
];

View File

@ -1,104 +0,0 @@
<?php
return
[
[
'menu_name' => '会员充值',
'menu_key' => 'recharge',
'menu_type' => 0,
'icon' => 'element-Help',
'api_url' => '',
'router_path' => 'recharge',
'view_path' => '',
'methods' => '',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '充值订单',
'menu_key' => 'recharge_order',
'menu_type' => 0,
'icon' => 'iconfont-iconchongzhidingdan',
'api_url' => '',
'router_path' => 'order',
'view_path' => '',
'methods' => 'get',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '订单列表',
'menu_key' => 'recharge_order_list',
'menu_type' => 1,
'icon' => '',
'api_url' => 'recharge/order',
'router_path' => 'list',
'view_path' => 'order/list',
'methods' => 'get',
'sort' => 100,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '退款',
'menu_key' => 'recharge_order_refund',
'menu_type' => 2,
'icon' => '',
'api_url' => 'recharge/refund/<order_id>',
'router_path' => '',
'view_path' => '',
'methods' => 'put',
'sort' => 100,
'status' => 1,
'is_show' => 0,
],
]
],
[
'menu_name' => '订单详情',
'menu_key' => 'recharge_order_detail',
'menu_type' => 1,
'icon' => '',
'api_url' => 'recharge/order/<order_id>',
'router_path' => 'detail',
'view_path' => 'order/detail',
'methods' => 'get',
'sort' => 90,
'status' => 1,
'is_show' => 0,
],
[
'menu_name' => '退款记录',
'menu_key' => 'recharge_order_refund_list',
'menu_type' => 1,
'icon' => 'iconfont-icontuikuanjilu',
'api_url' => 'recharge/refund',
'router_path' => 'refund',
'view_path' => 'order/refund',
'methods' => 'get',
'sort' => 90,
'status' => 1,
'is_show' => 1,
'children' => [
[
'menu_name' => '退款详情',
'menu_key' => 'recharge_refund_detail',
'menu_type' => 2,
'icon' => '',
'api_url' => 'recharge/refund/<refund_id>',
'router_path' => '',
'view_path' => '',
'methods' => 'get',
'sort' => 100,
'status' => 1,
'is_show' => 0,
]
]
]
]
]
]
]
];

View File

@ -1,18 +0,0 @@
<?php
return [
//充值成功通知,站点端发送
'recharge_success' => [
'key' => 'recharge_success',
'receiver_type' => 1,
'name' => '充值成功通知',
'title' => '会员充值成功后发送',
'async' => true,
'variable' => [
'price' => '充值金额',
'balance' => '充值后账户',
'time' => '充值时间',
'trade_no' => '交易单号'
],
]
];

View File

@ -1,7 +0,0 @@
<?php
return [
'recharge_success' => [
'content' => '您充值金额¥{price}, 充值后金额¥{balance}',
]
];

View File

@ -1,14 +0,0 @@
<?php
return [
'recharge_success' => [
'tid' => '755',
'content' => [
['交易单号', '{trade_no}', 'keyword1'],
['充值金额', '{price}', 'keyword2'],
['账户余额', '{balance}', 'keyword3'],
['充值时间', '{time}', 'keyword4'],
],
'kid_list' => [1, 3, 4, 2],
'scene_desc' => ''
]
];

View File

@ -1,11 +0,0 @@
<?php
return [
'recharge_success' => [
'temp_key' => '52552',
'keyword_name_list' => [ '充值时间', '充值金额' ],
'content' => [
[ '充值时间', '{time}', 'time1' ],
[ '充值金额', '{price}', 'amount3' ],
]
]
];

View File

@ -1,30 +0,0 @@
<?php
return [
//文件执行序列号
'bind' => [
],
'listen' => [
//会员账户变化事件
'MemberAccount' => [ 'addon\recharge\app\listener\member\MemberAccountListener' ],
/**
* 支付相关事件
*/
'PayCreate' => [ 'addon\recharge\app\listener\pay\PayCreateListener' ],
//支付成功
'PaySuccess' => [ 'addon\recharge\app\listener\pay\PaySuccessListener' ],
//退款成功
'RefundSuccess' => [ 'addon\recharge\app\listener\pay\RefundSuccessListener' ],
//消息模板数据内容
'NoticeData' => [
'addon\recharge\app\listener\notice_template\RechargeSuccess',
],
'AllowPayTypeByTrade' => [
'addon\recharge\app\listener\pay\PayTypeByTrade',
],
'WapIndex' => [ 'addon\recharge\app\listener\WapIndexListener' ],
],
'subscribe' => [
],
];

View File

@ -1,14 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,34 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
'dict_member' => [
'account_point_recharge_give' => 'recharge give',
'account_balance_recharge' => 'recharge',
],
'dict_order' => [
//订单类型
'order_type_recharge' => 'recharge order',
],
'dict_menu_admin' => [
'order' => 'order',
'recharge_order' => 'recharge order',
'recharge_order_list' => 'order list',
'recharge_refund' => 'refund',
'recharge_order_detail' => 'order detail',
'recharge_refund_list' => 'refund list',
'recharge_refund_detail' => 'refund detail',
],
];

View File

@ -1,13 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,14 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,33 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
'dict_member' => [
'account_point' => '积分',
'account_balance' => '余额',
'account_balance_recharge_refund' => '充值订单退款',
'account_balance_recharge' => '余额充值',
'account_point_recharge_give' => '充值赠送'
],
'dict_order' => [
'trade_type_recharge' => '充值订单'
],
'dict_wap_index' => [
'recharge' => '会员充值',
'recharge_desc' => '在线充值到会员账户',
],
'dict_diy' => [
'recharge_link' => '充值链接',
'recharge_title'=>'会员充值',
'recharge_index' => '充值'
]
];

View File

@ -1,13 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
return [
];

View File

@ -1,31 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener;
/**
* 手机端首页加载事件
*/
class WapIndexListener
{
public function handle()
{
return [
[
'key' => 'recharge',
"title" => get_lang("dict_wap_index.recharge"),
'desc' => get_lang("dict_wap_index.recharge_desc"),
"url" => "/recharge/pages/recharge",
'icon'=>'addon/recharge/icon.png'
],
];
}
}

View File

@ -1,30 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener\member;
/**
* 会员账户变化事件(积分,余额,零钱)
* Class MemberAccount
* @package app\listener\member
*/
class MemberAccountListener
{
/**
* 接收会员账户变化记录
* @param array $account_log
*/
public function handle(array $account_log)
{
return;
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace addon\recharge\app\listener\notice_template;
use addon\recharge\app\service\core\CoreRechargeOrderService;
use app\listener\notice_template\BaseNoticeTemplate;
use app\service\core\member\CoreMemberService;
class RechargeSuccess extends BaseNoticeTemplate
{
private $key = 'recharge_success';
public function handle(array $params)
{
if ($this->key == $params['key']) {
$data = $params['data'];
$order_id = $data['order_id'];
$core_order_service = new CoreRechargeOrderService();
$order = $core_order_service->orderInfo($order_id);
if (!empty($order)) {
$member = (new CoreMemberService())->getInfoByMemberId($order['member_id']);
//通过订单id查询订单信息
return $this->toReturn(
[
'__wechat_page' => '',//模板消息链接
'__miniprogram' => '',//模板消息小程序
'__weapp_page' => '',//小程序链接
'balance' => $member['balance'],//充值后的余额
'price' => $order['order_item_money'],//订单项总价
'time' => $order['create_time'],//创建时间
'trade_no' => $order['out_trade_no'],//交易流水号
],
[
'member_id' => $order['member_id'],
]
);
}
}
}
}

View File

@ -1,44 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener\pay;
use addon\recharge\app\dict\RechargeOrderDict;
use app\dict\pay\PayDict;
use core\exception\CommonException;
use addon\recharge\app\service\core\CoreRechargeOrderService;
/**
* 支付单据创建事件
*/
class PayCreateListener
{
public function handle(array $params)
{
$trade_type = $params['trade_type'] ?? '';
if ($trade_type == 'recharge') {
$trade_id = $params['trade_id'];
$order_info = (new CoreRechargeOrderService())->orderInfo($trade_id);
if ($order_info['order_status'] != RechargeOrderDict::WAIT_PAY) throw new CommonException('ONLY_PAYING_CAN_PAY');
//添加订单支付表
return [
'main_type' => PayDict::MEMBER,
'main_id' => $order_info['member_id'],//买家id
'money' => $order_info['order_money'],//订单金额
'trade_type' => 'recharge',//业务类型
'trade_id' => $trade_id,
'body' => get_lang("dict_order.trade_type_recharge")
];
}
}
}

View File

@ -1,28 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener\pay;
use addon\recharge\app\service\core\CoreRechargeOrderService;
/**
* 支付异步回调事件
*/
class PaySuccessListener
{
public function handle(array $pay_info)
{
$trade_type = $pay_info['trade_type'] ?? '';
if ($trade_type == 'recharge') {
(new CoreRechargeOrderService())->pay($pay_info);
}
}
}

View File

@ -1,32 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener\pay;
use app\dict\pay\PayDict;
/**
* 支付异步回调事件
*/
class PayTypeByTrade
{
public function handle(array $pay_info)
{
$trade_type = $pay_info[ 'trade_type' ] ?? '';
$pay_type_list = $pay_info[ 'pay_type_list' ] ?? '';
//充值订单不支持余额支付
if (!empty($pay_type_list) && $trade_type == 'recharge') {
unset($pay_type_list[ PayDict::BALANCEPAY ]);
}
return $pay_type_list;
}
}

View File

@ -1,30 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace addon\recharge\app\listener\pay;
use addon\recharge\app\service\core\CoreRechargeRefundService;
/**
* 退款成功事件
*/
class RefundSuccessListener
{
public function handle(array $refund_info)
{
//交易单据处理
$trade_type = $refund_info['trade_type'] ?? '';
if ($trade_type == 'recharge') {
(new CoreRechargeRefundService())->refundComplete($refund_info['refund_no']);
}
}
}

Some files were not shown because too many files have changed in this diff Show More