feat(site): integrate multilingual content management / 集成多语言内容管理
完善官网、管理后台与后端的多语言内容编辑和发布流程,调整站点页面及相关接口,并同步数据库脚本。 新增容器部署配置,改用环境变量注入数据库凭据;补充仓库忽略规则,并停止跟踪本地环境文件和 Maven 构建产物。
This commit is contained in:
parent
4ee6e9ae83
commit
85083ab962
10
.env.example
Normal file
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
||||
# Docker Compose database settings
|
||||
MYSQL_ROOT_PASSWORD=replace-with-a-strong-root-password
|
||||
MYSQL_DATABASE=huayang
|
||||
MYSQL_USER=huayang
|
||||
MYSQL_PASSWORD=replace-with-a-strong-application-password
|
||||
|
||||
# Spring datasource settings
|
||||
DB_URL=jdbc:mysql://localhost:3306/huayang?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
|
||||
DB_USERNAME=huayang
|
||||
DB_PASSWORD=replace-with-a-strong-application-password
|
||||
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
vendor/
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
.gradle/
|
||||
|
||||
# Build outputs
|
||||
target/
|
||||
build/
|
||||
!admin/src/views/tool/build/
|
||||
!admin/src/views/tool/build/**
|
||||
dist/
|
||||
out/
|
||||
coverage/
|
||||
|
||||
# Environment and local runtime data
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
logs/
|
||||
*.log
|
||||
*.out
|
||||
*.err
|
||||
uploadPath/
|
||||
mysql-data/
|
||||
|
||||
# Editors and operating systems
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary and archive files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.cache
|
||||
*.zip
|
||||
~$*
|
||||
/.codex_tmp/
|
||||
/package-lock.json
|
||||
|
||||
# AI auxiliary files
|
||||
AI_CONTEXT.md
|
||||
.ai_context.md
|
||||
.codex/
|
||||
30
PROJECT_PROGRESS.md
Normal file
30
PROJECT_PROGRESS.md
Normal file
@ -0,0 +1,30 @@
|
||||
<!-- 本文件由 AI 自动维护,请勿手动编辑 -->
|
||||
|
||||
# 项目进度
|
||||
|
||||
## 当前阶段
|
||||
|
||||
- 阶段:开发与联调
|
||||
- 整体完成度:75%(估算)
|
||||
|
||||
## 模块状态
|
||||
|
||||
| 模块 | 状态 | 完成度 |
|
||||
| --- | --- | ---: |
|
||||
| 公众网站 | 进行中 | 80% |
|
||||
| 管理后台 | 进行中 | 75% |
|
||||
| 后端接口 | 进行中 | 80% |
|
||||
| 多语言与翻译 | 进行中 | 75% |
|
||||
| 数据库脚本 | 进行中 | 75% |
|
||||
| 部署配置 | 进行中 | 60% |
|
||||
|
||||
## 当前任务
|
||||
|
||||
- 整理并提交官网、管理后台、后端、国际化及数据库脚本变更。
|
||||
- 清理版本库中的本地环境文件和 Maven 构建产物。
|
||||
- 官网、管理后台和后端构建验证已完成,国际化键同步检查已通过。
|
||||
|
||||
## 下一步
|
||||
|
||||
- 执行核心接口与多语言发布流程的集成测试。
|
||||
- 在目标环境验证数据库迁移和容器部署配置。
|
||||
@ -1,14 +0,0 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 华阳食品管理系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 华阳食品管理系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
|
||||
# 门户站点根地址(首页工作台「打开门户首页」按钮,默认本地 Vite)
|
||||
VUE_APP_PORTAL_URL=http://localhost:5173/
|
||||
@ -1,11 +0,0 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 华阳食品管理系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# 华阳食品管理系统/生产环境
|
||||
VUE_APP_BASE_API = '/prod-api'
|
||||
|
||||
# 门户官网地址(首页工作台预览;生产环境请改为实际域名,如 https://www.example.com/)
|
||||
# VUE_APP_PORTAL_URL=https://www.example.com/
|
||||
@ -1,12 +0,0 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 华阳食品管理系统
|
||||
|
||||
BABEL_ENV = production
|
||||
|
||||
NODE_ENV = production
|
||||
|
||||
# 测试环境配置
|
||||
ENV = 'staging'
|
||||
|
||||
# 华阳食品管理系统/测试环境
|
||||
VUE_APP_BASE_API = '/stage-api'
|
||||
@ -6,6 +6,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview"
|
||||
@ -43,7 +44,7 @@
|
||||
"vue": "2.6.12",
|
||||
"vue-count-to": "1.0.13",
|
||||
"vue-cropper": "0.5.5",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-i18n": "^7.3.2",
|
||||
"vue-router": "3.4.9",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vuex": "3.6.0"
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listCompanyTech(query) {
|
||||
return request({
|
||||
url: '/huayang/companyTech/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getCompanyTech(techId) {
|
||||
return request({
|
||||
url: '/huayang/companyTech/' + techId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addCompanyTech(data) {
|
||||
return request({
|
||||
url: '/huayang/companyTech',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateCompanyTech(data) {
|
||||
return request({
|
||||
url: '/huayang/companyTech',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function delCompanyTech(techId) {
|
||||
return request({
|
||||
url: '/huayang/companyTech/' + techId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listJoinApply(query) {
|
||||
return request({
|
||||
url: '/huayang/joinApply/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getJoinApply(applyId) {
|
||||
return request({
|
||||
url: '/huayang/joinApply/' + applyId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function handleJoinApply(data) {
|
||||
return request({
|
||||
url: '/huayang/joinApply/handle',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteJoinApply(applyIds) {
|
||||
return request({
|
||||
url: '/huayang/joinApply/' + applyIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listNews(query) {
|
||||
return request({
|
||||
url: '/huayang/news/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getNews(newsId) {
|
||||
return request({
|
||||
url: '/huayang/news/' + newsId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addNews(data) {
|
||||
return request({
|
||||
url: '/huayang/news',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateNews(data) {
|
||||
return request({
|
||||
url: '/huayang/news',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteNews(newsIds) {
|
||||
return request({
|
||||
url: '/huayang/news/' + newsIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function exportNews(query) {
|
||||
return request({
|
||||
url: '/huayang/news/export',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listNewsCategory(query) {
|
||||
return request({
|
||||
url: '/huayang/newsCategory/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getNewsCategory(dictCode) {
|
||||
return request({
|
||||
url: '/huayang/newsCategory/' + dictCode,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addNewsCategory(data) {
|
||||
return request({
|
||||
url: '/huayang/newsCategory',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateNewsCategory(data) {
|
||||
return request({
|
||||
url: '/huayang/newsCategory',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function delNewsCategory(dictCode) {
|
||||
return request({
|
||||
url: '/huayang/newsCategory/' + dictCode,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getNewsPageBanner() {
|
||||
return request({
|
||||
url: '/huayang/newsPage/banner',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateNewsPageBanner(data) {
|
||||
return request({
|
||||
url: '/huayang/newsPage/banner',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
@ -1,32 +1,131 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listTranslation(query) {
|
||||
/**
|
||||
* 列出可翻译页面(左侧页面树)
|
||||
*/
|
||||
export function listTranslationPages() {
|
||||
return request({
|
||||
url: '/huayang/translation/pages',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出当前启用的目标语种(不含默认 zh_cn)
|
||||
*/
|
||||
export function listTargetLangs() {
|
||||
return request({
|
||||
url: '/huayang/translation/targetLangs',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时扫描某页面下的全部可翻译条目
|
||||
* @param {string} pageModule 页面模块标识,如 home / product / culture
|
||||
*/
|
||||
export function scanTranslation(pageModule) {
|
||||
return request({
|
||||
url: '/huayang/translation/scan',
|
||||
method: 'get',
|
||||
params: { pageModule }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存翻译条目
|
||||
* @param {{ pageModule: string, items: Array<{langCode: string, key: string, value: string}> }} payload
|
||||
* items[].value 为空字符串时表示删除该 (langCode, key) 记录
|
||||
*/
|
||||
export function batchSaveTranslation(payload) {
|
||||
return request({
|
||||
url: '/huayang/translation/batch',
|
||||
method: 'put',
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局完成度统计
|
||||
*/
|
||||
export function translationStats() {
|
||||
return request({
|
||||
url: '/huayang/translation/stats',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取翻译菜单树(用于手动选择显示菜单)
|
||||
*/
|
||||
export function getTranslationMenuOptions() {
|
||||
return request({
|
||||
url: '/huayang/translation/menuOptions',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已选择菜单 ID 列表
|
||||
*/
|
||||
export function getTranslationMenuSelection() {
|
||||
return request({
|
||||
url: '/huayang/translation/menuSelection',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存菜单选择
|
||||
* @param {{ menuIds: number[] }} payload
|
||||
*/
|
||||
export function saveTranslationMenuSelection(payload) {
|
||||
return request({
|
||||
url: '/huayang/translation/menuSelection',
|
||||
method: 'put',
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询翻译条目列表(走多语言国际化通用接口)
|
||||
*/
|
||||
export function listTranslationEntries(params) {
|
||||
return request({
|
||||
url: '/huayang/translation/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function addTranslation(data) {
|
||||
/**
|
||||
* 新增翻译条目(按 key 维护静态文案)
|
||||
*/
|
||||
export function addTranslationEntry(data) {
|
||||
return request({
|
||||
url: '/huayang/translation',
|
||||
method: 'post',
|
||||
data: data
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTranslation(data) {
|
||||
/**
|
||||
* 修改翻译条目(按 key 维护静态文案)
|
||||
*/
|
||||
export function updateTranslationEntry(data) {
|
||||
return request({
|
||||
url: '/huayang/translation',
|
||||
method: 'put',
|
||||
data: data
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTranslation(i18nIds) {
|
||||
/**
|
||||
* 删除翻译条目
|
||||
*/
|
||||
export function deleteTranslationEntry(i18nIds) {
|
||||
return request({
|
||||
url: '/huayang/translation/' + i18nIds,
|
||||
url: `/huayang/translation/${i18nIds}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
append-to-body
|
||||
custom-class="editor-media-dialog"
|
||||
>
|
||||
<p class="editor-media-dialog__hint">请选择要插入的类型(新闻正文支持图片与视频混排)</p>
|
||||
<p class="editor-media-dialog__hint">请选择要插入的类型(正文支持图片与视频混排)</p>
|
||||
<div class="editor-media-dialog__actions">
|
||||
<el-button type="primary" plain icon="el-icon-picture" @click="onPickImageFromDialog">插入图片</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-video-camera" @click="onPickVideoFileFromDialog">插入视频(上传文件)</el-button>
|
||||
@ -104,7 +104,7 @@ export default {
|
||||
/* 上传文件大小限制(MB) */
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: null,
|
||||
},
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: {
|
||||
@ -326,14 +326,6 @@ export default {
|
||||
this.$message.error(`图片格式错误,请使用 jpg/png/gif/webp/svg`)
|
||||
return false
|
||||
}
|
||||
// 校检文件大小
|
||||
if (this.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < this.fileSize
|
||||
if (!isLt) {
|
||||
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleUploadSuccess(res, file) {
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" slot="tip" v-if="showTip && !disabled">
|
||||
请上传
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
|
||||
的文件
|
||||
</div>
|
||||
@ -68,13 +67,13 @@ export default {
|
||||
},
|
||||
// 大小限制(MB)
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 5
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["png", "jpg", "jpeg"]
|
||||
default: () => ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"]
|
||||
},
|
||||
// 是否显示提示
|
||||
isShowTip: {
|
||||
@ -90,6 +89,11 @@ export default {
|
||||
drag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 兼容历史页面传入的 spec,界面不再展示建议规格
|
||||
spec: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@ -128,16 +132,7 @@ export default {
|
||||
// 首先将值转为数组
|
||||
const list = Array.isArray(val) ? val : this.value.split(',')
|
||||
// 然后将数组转为对象数组
|
||||
this.fileList = list.map(item => {
|
||||
if (typeof item === "string") {
|
||||
if (item.indexOf(this.baseUrl) === -1 && !isExternal(item)) {
|
||||
item = { name: this.baseUrl + item, url: this.baseUrl + item }
|
||||
} else {
|
||||
item = { name: item, url: item }
|
||||
}
|
||||
}
|
||||
return item
|
||||
})
|
||||
this.fileList = list.map(item => this.normalizeFileItem(item))
|
||||
} else {
|
||||
this.fileList = []
|
||||
return []
|
||||
@ -150,8 +145,8 @@ export default {
|
||||
computed: {
|
||||
// 是否显示提示
|
||||
showTip() {
|
||||
return this.isShowTip && (this.fileType || this.fileSize)
|
||||
},
|
||||
return this.isShowTip && this.fileType
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 上传前loading加载
|
||||
@ -179,15 +174,9 @@ export default {
|
||||
this.$modal.msgError('文件名不正确,不能包含英文逗号!')
|
||||
return false
|
||||
}
|
||||
if (this.fileSize) {
|
||||
const isLt = file.size / 1024 / 1024 < this.fileSize
|
||||
if (!isLt) {
|
||||
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.$modal.loading("正在上传图片,请稍候...")
|
||||
this.number++
|
||||
return true
|
||||
},
|
||||
// 文件个数超出
|
||||
handleExceed() {
|
||||
@ -197,7 +186,7 @@ export default {
|
||||
handleUploadSuccess(res, file) {
|
||||
if (res.code == 200) {
|
||||
const path = res.fileName || res.url || ''
|
||||
this.uploadList.push({ name: path, url: path })
|
||||
this.uploadList.push(this.normalizeFileItem(path))
|
||||
this.uploadedSuccessfully()
|
||||
} else {
|
||||
this.number--
|
||||
@ -244,10 +233,35 @@ export default {
|
||||
separator = separator || ","
|
||||
for (let i in list) {
|
||||
if (list[i].url) {
|
||||
strs += list[i].url.replace(this.baseUrl, "") + separator
|
||||
strs += this.getStorePath(list[i]) + separator
|
||||
}
|
||||
}
|
||||
return strs != '' ? strs.substr(0, strs.length - 1) : ''
|
||||
},
|
||||
normalizeFileItem(item) {
|
||||
if (typeof item === "string") {
|
||||
return { name: item, url: this.getPreviewUrl(item) }
|
||||
}
|
||||
const path = item.url || item.name || ""
|
||||
return {
|
||||
...item,
|
||||
name: item.name || path,
|
||||
url: this.getPreviewUrl(path)
|
||||
}
|
||||
},
|
||||
getPreviewUrl(path) {
|
||||
if (!path || isExternal(path)) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf(this.baseUrl) === 0) {
|
||||
return path
|
||||
}
|
||||
const normalizedPath = path.charAt(0) === "/" ? path : `/${path}`
|
||||
return this.baseUrl + normalizedPath
|
||||
},
|
||||
getStorePath(file) {
|
||||
const path = file.url || file.name || ""
|
||||
return path.indexOf(this.baseUrl) === 0 ? path.replace(this.baseUrl, "") : path
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,5 +286,6 @@ export default {
|
||||
opacity: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@ -5,9 +5,13 @@ import messages from './lang'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
const cookieLang = Cookies.get('language') || 'zh'
|
||||
const cookieLangRaw = Cookies.get('language') || 'zh'
|
||||
if (cookieLangRaw === 'ko') {
|
||||
Cookies.set('language', 'zh')
|
||||
}
|
||||
const cookieLang = cookieLangRaw === 'ko' ? 'zh' : cookieLangRaw
|
||||
const i18n = new VueI18n({
|
||||
locale: cookieLang === 'ko' ? 'zh' : cookieLang,
|
||||
locale: cookieLang,
|
||||
messages
|
||||
})
|
||||
|
||||
|
||||
@ -22,24 +22,6 @@
|
||||
<header-notice id="header-notice" class="right-menu-item hover-effect" />
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown class="right-menu-item hover-effect" trigger="hover">
|
||||
<span class="language-dropdown">
|
||||
<i class="el-icon-language"></i>
|
||||
<span>{{ languageName }}</span>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="changeLanguage('zh')">
|
||||
<span :class="{ 'active-language': locale === 'zh' }">简体中文</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="changeLanguage('en')">
|
||||
<span :class="{ 'active-language': locale === 'en' }">English</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="changeLanguage('ja')">
|
||||
<span :class="{ 'active-language': locale === 'ja' }">日本語</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
</template>
|
||||
|
||||
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
|
||||
@ -91,14 +73,7 @@ export default {
|
||||
HeaderNotice
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locale: 'zh',
|
||||
languageMap: {
|
||||
zh: '简体中文',
|
||||
en: 'English',
|
||||
ja: '日本語'
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
@ -121,9 +96,6 @@ export default {
|
||||
get() {
|
||||
return this.$store.state.settings.sidebarLogo
|
||||
}
|
||||
},
|
||||
languageName() {
|
||||
return this.languageMap[this.locale] || '简体中文'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -149,12 +121,6 @@ export default {
|
||||
location.href = '/index'
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
changeLanguage(lang) {
|
||||
this.locale = lang
|
||||
this.$i18n.locale = lang
|
||||
Cookies.set('language', lang)
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,15 +207,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
margin-right: 0px;
|
||||
padding-right: 0px;
|
||||
@ -285,9 +242,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-language {
|
||||
color: #409EFF;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -80,7 +80,6 @@
|
||||
<span v-else>无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="荣誉描述" align="center" prop="honorDesc" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
@ -128,13 +127,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片" prop="honorImage">
|
||||
<image-upload v-model="form.honorImage" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="荣誉描述" prop="honorDesc">
|
||||
<el-input v-model="form.honorDesc" type="textarea" maxlength="500" placeholder="请输入荣誉描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="linkUrl">
|
||||
<el-input v-model="form.linkUrl" maxlength="500" placeholder="可选,填写后点击荣誉可跳转" />
|
||||
<image-upload v-model="form.honorImage" :limit="1" spec="荣誉/证书图片,建议 900×1200px 或 3:4,证书文字清晰,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
@ -233,8 +226,6 @@ export default {
|
||||
honorTitle: undefined,
|
||||
honorType: 'certificate',
|
||||
honorImage: undefined,
|
||||
honorDesc: undefined,
|
||||
linkUrl: undefined,
|
||||
sortOrder: 0,
|
||||
status: '0'
|
||||
}
|
||||
@ -262,14 +253,19 @@ export default {
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
const payload = {
|
||||
...this.form,
|
||||
honorDesc: '',
|
||||
linkUrl: ''
|
||||
}
|
||||
if (this.form.honorId) {
|
||||
updateCompanyHonor(this.form).then(() => {
|
||||
updateCompanyHonor(payload).then(() => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addCompanyHonor(this.form).then(() => {
|
||||
addCompanyHonor(payload).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
@ -292,4 +288,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@ -9,21 +9,6 @@
|
||||
title="当前列表无数据;请点击下方「编辑公司简介页」打开表单(若库中尚无 about_intro 将引导新增)。执行过库初始化脚本后一般会在此看到 home_about、about_intro 等记录。"
|
||||
/>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="24">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-edit-outline"
|
||||
size="small"
|
||||
v-hasPermi="['huayang:companySection:query', 'huayang:companySection:add', 'huayang:companySection:edit']"
|
||||
@click="openAboutIntroQuickEdit"
|
||||
>
|
||||
编辑公司简介页(关于我们 · about_intro)
|
||||
</el-button>
|
||||
<span class="quick-tip">无需在列表中找数据;门户「关于我们」与此处标识码 about_intro 对应。</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="板块标题" prop="sectionTitle">
|
||||
<el-input
|
||||
@ -124,44 +109,30 @@
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="isAboutIntroForm">
|
||||
<el-form-item label="页面文字" prop="introCombinedHtml">
|
||||
<editor v-model="introCombinedHtml" :min-height="280" />
|
||||
<p class="form-tip">主标题与正文在同一编辑器中编辑;请使用一级标题(H1)作为页面主标题。</p>
|
||||
<el-form-item label="页面标题" prop="sectionTitle">
|
||||
<el-input
|
||||
v-model="form.sectionTitle"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="如:公司简介"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="正文内容" prop="sectionContent">
|
||||
<editor v-model="form.sectionContent" :min-height="280" />
|
||||
<p class="form-tip">正文仅在下方编辑器中编辑;页面主标题请在上方的「页面标题」中填写。</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="顶部横幅图">
|
||||
<image-upload v-model="form.bannerImage" :limit="1" />
|
||||
<image-upload v-model="form.bannerImage" :limit="1" spec="关于我们顶部横幅,建议 1920×520px 或 16:5,主体居中,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="内容区宽图">
|
||||
<image-upload v-model="form.contentImage1" :limit="1" />
|
||||
<image-upload v-model="form.contentImage1" :limit="1" spec="正文通栏宽图,建议 1200×520px 或 21:9,单张不超过 5MB" />
|
||||
<p class="form-tip">标题与正文下方通栏配图(可选)</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容区左图">
|
||||
<image-upload v-model="form.contentImage2" :limit="1" />
|
||||
<image-upload v-model="form.contentImage2" :limit="1" spec="内容区左图,建议 600×420px 或 4:3,与右图保持同尺寸" />
|
||||
</el-form-item>
|
||||
<el-form-item label="内容区右图">
|
||||
<image-upload v-model="form.contentImage3" :limit="1" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isQualityPageForm">
|
||||
<p class="form-tip" style="margin-bottom: 12px;">
|
||||
顶部横幅图与「公司简介」页相同,请在「公司简介板块」中编辑标识为 <strong>about_intro</strong> 的记录,修改其中的「顶部横幅图」即可同步到品质技术页。
|
||||
</p>
|
||||
<el-form-item label="板块标题" prop="sectionTitle">
|
||||
<el-input v-model="form.sectionTitle" maxlength="200" show-word-limit placeholder="后台列表显示用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="页顶说明" prop="sectionContent">
|
||||
<editor v-model="form.sectionContent" :min-height="220" />
|
||||
<p class="form-tip">显示在品质技术各区块列表上方(可选)。各区块标题、正文与配图请在「品质技术」菜单维护。</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="页顶配图1">
|
||||
<image-upload v-model="form.contentImage1" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="页顶配图2">
|
||||
<image-upload v-model="form.contentImage2" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="页顶配图3">
|
||||
<image-upload v-model="form.contentImage3" :limit="1" />
|
||||
<image-upload v-model="form.contentImage3" :limit="1" spec="内容区右图,建议 600×420px 或 4:3,与左图保持同尺寸" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
@ -199,35 +170,27 @@
|
||||
import {
|
||||
listCompanySection,
|
||||
getCompanySection,
|
||||
getCompanySectionByCode,
|
||||
addCompanySection,
|
||||
updateCompanySection,
|
||||
delCompanySection
|
||||
} from '@/api/huayang/companySection'
|
||||
|
||||
const ABOUT_INTRO_CODE = 'about_intro'
|
||||
const QUALITY_PAGE_CODE = 'quality_page'
|
||||
const DISABLED_SECTION_CODES = ['quality_page']
|
||||
|
||||
export default {
|
||||
name: 'HuayangCompanySection',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
const validateIntroCombined = (rule, value, callback) => {
|
||||
if (!this.isAboutIntroForm) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
const v = (this.introCombinedHtml || '').replace(/<[^>]+>/g, '').trim()
|
||||
if (!v) {
|
||||
callback(new Error('页面文字不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateSectionContent = (rule, value, callback) => {
|
||||
const code = this.form.sectionCode || ''
|
||||
if (code === ABOUT_INTRO_CODE || code === QUALITY_PAGE_CODE) {
|
||||
callback()
|
||||
if (code === ABOUT_INTRO_CODE) {
|
||||
const v = (value || '').replace(/<[^>]+>/g, '').trim()
|
||||
if (!v) {
|
||||
callback(new Error('正文内容不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!value || !String(value).trim()) {
|
||||
@ -237,10 +200,6 @@ export default {
|
||||
}
|
||||
}
|
||||
const validateSectionTitle = (rule, value, callback) => {
|
||||
if ((this.form.sectionCode || '') === ABOUT_INTRO_CODE) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if (!value || !String(value).trim()) {
|
||||
callback(new Error('板块标题不能为空'))
|
||||
} else {
|
||||
@ -257,7 +216,6 @@ export default {
|
||||
sectionList: [],
|
||||
title: '',
|
||||
open: false,
|
||||
introCombinedHtml: '',
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@ -269,7 +227,6 @@ export default {
|
||||
sectionCode: [{ required: true, message: '板块标识不能为空', trigger: 'blur' }],
|
||||
sectionTitle: [{ validator: validateSectionTitle, trigger: 'blur' }],
|
||||
sectionContent: [{ validator: validateSectionContent, trigger: 'blur' }],
|
||||
introCombinedHtml: [{ validator: validateIntroCombined, trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
@ -278,34 +235,27 @@ export default {
|
||||
isAboutIntroForm() {
|
||||
return (this.form.sectionCode || '') === ABOUT_INTRO_CODE
|
||||
},
|
||||
isQualityPageForm() {
|
||||
return (this.form.sectionCode || '') === QUALITY_PAGE_CODE
|
||||
},
|
||||
isWideSectionForm() {
|
||||
return this.isAboutIntroForm || this.isQualityPageForm
|
||||
return this.isAboutIntroForm
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
watch: {
|
||||
'form.sectionCode'(val, oldVal) {
|
||||
if (!this.open) return
|
||||
if (val === ABOUT_INTRO_CODE) {
|
||||
if (!this.introCombinedHtml) {
|
||||
this.introCombinedHtml = this.buildIntroCombinedFromForm()
|
||||
}
|
||||
} else if (oldVal === ABOUT_INTRO_CODE) {
|
||||
this.introCombinedHtml = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isCompanySectionRecord(section) {
|
||||
const code = String(section?.sectionCode || section?.section_code || '').trim().toLowerCase()
|
||||
if (!code) return true
|
||||
if (DISABLED_SECTION_CODES.includes(code)) return false
|
||||
return !code.startsWith('join_')
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listCompanySection(this.queryParams).then((response) => {
|
||||
this.sectionList = response.rows
|
||||
this.total = response.total
|
||||
const rows = Array.isArray(response.rows) ? response.rows : []
|
||||
const filteredRows = rows.filter((row) => this.isCompanySectionRecord(row))
|
||||
this.sectionList = filteredRows
|
||||
this.total = filteredRows.length
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
@ -321,31 +271,20 @@ export default {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
buildIntroCombinedFromForm() {
|
||||
const title = this.form.sectionTitle || '公司简介'
|
||||
const content = this.form.sectionContent || ''
|
||||
if (/<h1[\s>]/i.test(content)) {
|
||||
return content
|
||||
}
|
||||
return `<h1>${this.escapeForHtmlText(title)}</h1>${content}`
|
||||
},
|
||||
escapeForHtmlText(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
},
|
||||
splitIntroCombinedToForm() {
|
||||
const raw = this.introCombinedHtml || ''
|
||||
/** 旧版在同一编辑器内用 H1 表示标题:载入时若正文仍以 H1 开头则拆回标题字段(一次性兼容) */
|
||||
normalizeLegacyAboutIntroHtml() {
|
||||
const raw = String(this.form.sectionContent || '').trim()
|
||||
if (!raw) return
|
||||
const tmp = document.createElement('div')
|
||||
tmp.innerHTML = raw.trim()
|
||||
tmp.innerHTML = raw
|
||||
const h1 = tmp.querySelector('h1')
|
||||
if (h1) {
|
||||
this.form.sectionTitle = (h1.textContent || '').trim() || '公司简介'
|
||||
h1.remove()
|
||||
} else {
|
||||
this.form.sectionTitle = '公司简介'
|
||||
if (!h1) return
|
||||
const fromH1 = (h1.textContent || '').trim()
|
||||
const hasTitle = String(this.form.sectionTitle || '').trim()
|
||||
if (fromH1 && !hasTitle) {
|
||||
this.form.sectionTitle = fromH1
|
||||
}
|
||||
h1.remove()
|
||||
this.form.sectionContent = tmp.innerHTML
|
||||
},
|
||||
/** 保证图片字段存在且为字符串,便于 v-model 与 JSON 提交(兼容 snake_case 返回) */
|
||||
@ -364,7 +303,6 @@ export default {
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.introCombinedHtml = ''
|
||||
this.form = {
|
||||
sectionId: undefined,
|
||||
sectionCode: undefined,
|
||||
@ -389,48 +327,16 @@ export default {
|
||||
this.open = true
|
||||
this.title = '添加公司简介板块'
|
||||
},
|
||||
/** 快速打开「关于我们」主板块:有则编辑,无则带 about_intro 新增 */
|
||||
openAboutIntroQuickEdit() {
|
||||
getCompanySectionByCode(ABOUT_INTRO_CODE).then((response) => {
|
||||
const row = response && response.data
|
||||
this.reset()
|
||||
if (row && row.sectionId != null) {
|
||||
this.form = this.normalizeSectionForm(row)
|
||||
if (this.isAboutIntroForm) {
|
||||
this.introCombinedHtml = this.buildIntroCombinedFromForm()
|
||||
}
|
||||
this.open = true
|
||||
this.title = '修改公司简介页(关于我们)'
|
||||
return
|
||||
}
|
||||
this.form.sectionCode = ABOUT_INTRO_CODE
|
||||
this.form.sectionTitle = '公司简介'
|
||||
this.form.sortOrder = 1
|
||||
this.form.status = '0'
|
||||
this.form.bannerImage = ''
|
||||
this.form.contentImage1 = ''
|
||||
this.form.contentImage2 = ''
|
||||
this.form.contentImage3 = ''
|
||||
this.introCombinedHtml = this.buildIntroCombinedFromForm()
|
||||
this.open = true
|
||||
this.title = '新增公司简介页(about_intro)'
|
||||
})
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const sectionId = row.sectionId || this.ids[0]
|
||||
getCompanySection(sectionId).then((response) => {
|
||||
this.form = this.normalizeSectionForm(response.data)
|
||||
if (this.isAboutIntroForm) {
|
||||
this.introCombinedHtml = this.buildIntroCombinedFromForm()
|
||||
this.normalizeLegacyAboutIntroHtml()
|
||||
}
|
||||
this.open = true
|
||||
const code = this.form.sectionCode || ''
|
||||
if (code === QUALITY_PAGE_CODE) {
|
||||
this.title = '修改品质技术页(顶部与页顶区)'
|
||||
} else {
|
||||
this.title = '修改公司简介板块'
|
||||
}
|
||||
this.title = '修改公司简介板块'
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
@ -438,10 +344,12 @@ export default {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
if (this.isAboutIntroForm) {
|
||||
this.splitIntroCombinedToForm()
|
||||
const code = String(this.form.sectionCode || '').trim().toLowerCase()
|
||||
if (DISABLED_SECTION_CODES.includes(code)) {
|
||||
this.$modal.msgError('该板块标识已停用,不能在管理端维护')
|
||||
return
|
||||
}
|
||||
if (this.isAboutIntroForm || this.isQualityPageForm) {
|
||||
if (this.isAboutIntroForm) {
|
||||
this.form.bannerImage = this.form.bannerImage == null ? '' : String(this.form.bannerImage)
|
||||
this.form.contentImage1 = this.form.contentImage1 == null ? '' : String(this.form.contentImage1)
|
||||
this.form.contentImage2 = this.form.contentImage2 == null ? '' : String(this.form.contentImage2)
|
||||
@ -485,10 +393,4 @@ export default {
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.quick-tip {
|
||||
margin-left: 12px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,401 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
title="说明"
|
||||
description="「历程页轮播」用于门户网站「发展历程」底部轮播;「店面展示页」用于门户网站店面展示页底部图集;二者仅需标题、展示图与排序。「店面位置」用于全国门店页左侧网点列表(名称、电话、地址)及底部轮播按名称配图;右侧大图请在「站点配置」配置键 store_location_map_image。「展厅」用于首页等通用店面展示。"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="门店名称" prop="storeName">
|
||||
<el-input
|
||||
v-model="queryParams.storeName"
|
||||
placeholder="请输入门店名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="门店类型" prop="storeType">
|
||||
<el-select v-model="queryParams.storeType" placeholder="全部类型" clearable>
|
||||
<el-option label="展厅" value="showroom" />
|
||||
<el-option label="历程页轮播" value="history" />
|
||||
<el-option label="店面展示页" value="about_showcase" />
|
||||
<el-option label="店面位置" value="map_location" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['huayang:companyStore:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['huayang:companyStore:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="storeList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="门店名称" align="center" prop="storeName" :show-overflow-tooltip="true" min-width="100" />
|
||||
<el-table-column label="电话" align="center" prop="storePhone" width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="地址" align="left" prop="storeAddress" min-width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="类型" align="center" prop="storeType" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="small" v-if="scope.row.storeType === 'showroom'">展厅</el-tag>
|
||||
<el-tag size="small" type="success" v-else-if="scope.row.storeType === 'shop'">直营店</el-tag>
|
||||
<el-tag size="small" type="warning" v-else-if="scope.row.storeType === 'history'">历程页轮播</el-tag>
|
||||
<el-tag size="small" type="primary" v-else-if="scope.row.storeType === 'about_showcase'">店面展示页</el-tag>
|
||||
<el-tag size="small" type="danger" v-else-if="scope.row.storeType === 'map_location'">店面位置</el-tag>
|
||||
<el-tag size="small" type="info" v-else>{{ scope.row.storeType }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图片" align="center" prop="storeImage" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
v-if="scope.row.storeImage"
|
||||
:src="resolveImageUrl(scope.row.storeImage)"
|
||||
:preview-src-list="[resolveImageUrl(scope.row.storeImage)]"
|
||||
fit="cover"
|
||||
style="width: 60px; height: 60px"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="主店" align="center" prop="isMain" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isMain === '1' ? 'danger' : 'info'">{{ scope.row.isMain === '1' ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['huayang:companyStore:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['huayang:companyStore:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="640px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="dynamicRules" label-width="108px">
|
||||
<el-form-item :label="isPageCarouselType ? '轮播标题' : '门店名称'" prop="storeName">
|
||||
<el-input
|
||||
v-model="form.storeName"
|
||||
:placeholder="isPageCarouselType ? '图片下方展示的名称' : '请输入门店名称'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="门店类型" prop="storeType">
|
||||
<el-radio-group v-model="form.storeType" class="store-type-radio-group">
|
||||
<el-radio label="showroom">展厅</el-radio>
|
||||
<el-radio label="history">历程页轮播(发展历程页底部)</el-radio>
|
||||
<el-radio label="about_showcase">店面展示页(门户网站店面展示 Tab)</el-radio>
|
||||
<el-radio label="map_location">店面位置(全国门店页左侧网点列表)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isMapLocationType" label="展示图片" prop="storeImage">
|
||||
<image-upload v-model="form.storeImage" :limit="1" />
|
||||
</el-form-item>
|
||||
<template v-if="isMapLocationType">
|
||||
<el-form-item label="门店地址" prop="storeAddress">
|
||||
<el-input v-model="form.storeAddress" type="textarea" :rows="2" placeholder="请输入详细地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="storePhone">
|
||||
<el-input v-model="form.storePhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配图">
|
||||
<image-upload v-model="form.storeImage" :limit="1" />
|
||||
<div style="color:#909399;font-size:12px;line-height:1.5;margin-top:6px">选填;填写后可用于底部轮播与网点配图匹配。</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="!isPageCarouselType && !isMapLocationType">
|
||||
<el-form-item label="门店地址" prop="storeAddress">
|
||||
<el-input v-model="form.storeAddress" placeholder="请输入门店详细地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="storePhone">
|
||||
<el-input v-model="form.storePhone" placeholder="请输入门店联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="营业时间" prop="businessHours">
|
||||
<el-input v-model="form.businessHours" placeholder="如:周一至周日 9:00-18:00" />
|
||||
</el-form-item>
|
||||
<el-form-item label="门店简介" prop="storeDesc">
|
||||
<el-input v-model="form.storeDesc" type="textarea" placeholder="请输入门店简介" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否主店" prop="isMain">
|
||||
<el-radio-group v-model="form.isMain">
|
||||
<el-radio label="1">是</el-radio>
|
||||
<el-radio label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCompanyStore, getCompanyStore, addCompanyStore, updateCompanyStore, delCompanyStore } from '@/api/huayang/companyStore'
|
||||
|
||||
export default {
|
||||
name: 'HuayangCompanyStore',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
showSearch: true,
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
total: 0,
|
||||
storeList: [],
|
||||
title: '',
|
||||
open: false,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
storeName: undefined,
|
||||
storeType: undefined,
|
||||
status: undefined
|
||||
},
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 历程页轮播与店面展示页专用:仅需图片与标题 */
|
||||
isPageCarouselType() {
|
||||
return (
|
||||
this.form && (this.form.storeType === 'history' || this.form.storeType === 'about_showcase')
|
||||
)
|
||||
},
|
||||
/** 全国门店页左侧「店面位置」列表 */
|
||||
isMapLocationType() {
|
||||
return this.form && this.form.storeType === 'map_location'
|
||||
},
|
||||
dynamicRules() {
|
||||
const r = {
|
||||
storeName: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
|
||||
storeType: [{ required: true, message: '门店类型不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
const st = this.form && this.form.storeType
|
||||
if (st === 'map_location') {
|
||||
r.storeAddress = [{ required: true, message: '请输入门店地址', trigger: 'blur' }]
|
||||
r.storePhone = [{ required: true, message: '请输入联系电话', trigger: 'blur' }]
|
||||
return r
|
||||
}
|
||||
r.storeImage = [{ required: true, message: '展示图片不能为空', trigger: 'blur' }]
|
||||
return r
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'form.storeType'(val) {
|
||||
if (val === 'history' || val === 'about_showcase') {
|
||||
this.form.storeAddress = ''
|
||||
this.form.storePhone = ''
|
||||
this.form.businessHours = ''
|
||||
this.form.storeDesc = ''
|
||||
this.form.isMain = '0'
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) this.$refs.form.clearValidate()
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
resolveImageUrl(raw) {
|
||||
if (!raw) return ''
|
||||
const value = String(raw).trim()
|
||||
if (!value) return ''
|
||||
if (/^https?:\/\//i.test(value)) return value
|
||||
const baseApi = process.env.VUE_APP_BASE_API || ''
|
||||
const baseOrigin = baseApi ? baseApi.replace(/\/api\/?$/, '') : ''
|
||||
if (value.startsWith('/assets/')) return value
|
||||
if (value.startsWith('/profile/')) return `${baseOrigin}${value}`
|
||||
if (value.startsWith('profile/')) return `${baseOrigin}/${value}`
|
||||
if (value.startsWith('/')) return `${baseOrigin}${value}`
|
||||
return `${baseOrigin}/${value}`
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listCompanyStore(this.queryParams).then((response) => {
|
||||
this.storeList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.form = {
|
||||
storeId: undefined,
|
||||
storeName: undefined,
|
||||
storeType: 'showroom',
|
||||
storeImage: undefined,
|
||||
storeAddress: undefined,
|
||||
storePhone: undefined,
|
||||
storeDesc: undefined,
|
||||
businessHours: undefined,
|
||||
isMain: '0',
|
||||
sortOrder: 0,
|
||||
status: '0'
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.storeId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '添加店面展示'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const storeId = row.storeId || this.ids[0]
|
||||
getCompanyStore(storeId).then((response) => {
|
||||
this.form = response.data
|
||||
if (this.form.storeType === 'shop') {
|
||||
this.form.storeType = 'showroom'
|
||||
}
|
||||
this.open = true
|
||||
this.title = '修改店面展示'
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
if (this.form.storeType === 'history' || this.form.storeType === 'about_showcase') {
|
||||
this.form.storeAddress = ''
|
||||
this.form.storePhone = ''
|
||||
this.form.businessHours = ''
|
||||
this.form.storeDesc = ''
|
||||
this.form.isMain = '0'
|
||||
}
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.form.storeId) {
|
||||
updateCompanyStore(this.form).then(() => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addCompanyStore(this.form).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const storeIds = row.storeId ? [row.storeId] : this.ids
|
||||
this.$modal.confirm('是否确认删除选中的数据项?')
|
||||
.then(() => {
|
||||
return delCompanyStore(storeIds)
|
||||
})
|
||||
.then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-type-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.store-type-radio-group .el-radio {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,264 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="技术标题" prop="techTitle">
|
||||
<el-input
|
||||
v-model="queryParams.techTitle"
|
||||
placeholder="请输入技术标题"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['huayang:companyTech:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['huayang:companyTech:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="techList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="标题" align="center" prop="techTitle" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="分类" align="center" prop="techCategory" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="small">{{ scope.row.techCategory }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['huayang:companyTech:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['huayang:companyTech:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="780px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="技术标题" prop="techTitle">
|
||||
<el-input v-model="form.techTitle" maxlength="200" show-word-limit placeholder="请输入技术标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属分类" prop="techCategory">
|
||||
<el-input v-model="form.techCategory" placeholder="如:原料、设备、工艺等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="详细内容" prop="techContent">
|
||||
<editor v-model="form.techContent" :min-height="192"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="展示图片" prop="techImages">
|
||||
<image-upload v-model="form.techImages" :limit="12" />
|
||||
<p class="form-tip">多张图以逗号分隔存储,前台按顺序展示在对应区块正文下方。</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCompanyTech, getCompanyTech, addCompanyTech, updateCompanyTech, delCompanyTech } from '@/api/huayang/companyTech'
|
||||
|
||||
export default {
|
||||
name: 'HuayangCompanyTech',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
showSearch: true,
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
total: 0,
|
||||
techList: [],
|
||||
title: '',
|
||||
open: false,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
techTitle: undefined,
|
||||
status: undefined
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
techTitle: [{ required: true, message: '技术标题不能为空', trigger: 'blur' }],
|
||||
techContent: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
listCompanyTech(this.queryParams).then((response) => {
|
||||
this.techList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.form = {
|
||||
techId: undefined,
|
||||
techTitle: undefined,
|
||||
techContent: undefined,
|
||||
techCategory: undefined,
|
||||
techImages: undefined,
|
||||
sortOrder: 0,
|
||||
status: '0'
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.techId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '添加品质技术'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const techId = row.techId || this.ids[0]
|
||||
getCompanyTech(techId).then((response) => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = '修改品质技术'
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.form.techId) {
|
||||
updateCompanyTech(this.form).then(() => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addCompanyTech(this.form).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const techIds = row.techId ? [row.techId] : this.ids
|
||||
this.$modal.confirm('是否确认删除选中的数据项?')
|
||||
.then(() => {
|
||||
return delCompanyTech(techIds)
|
||||
})
|
||||
.then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-tip {
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@ -1,13 +1,5 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
title="门户网站「发展历程」页"
|
||||
description="本页维护时间线节点。该页底部「店面展示」请在菜单「店面展示」中维护:门店类型选「历程页轮播」(与门户网站「店面展示页」数据源相互独立)。填写轮播标题、上传展示图片并设置排序即可。"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="事件标题" prop="eventTitle">
|
||||
<el-input
|
||||
@ -58,16 +50,6 @@
|
||||
v-hasPermi="['huayang:companyTimeline:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['huayang:companyTimeline:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
@ -83,18 +65,6 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="配图" align="center" prop="eventImage" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
v-if="scope.row.eventImage"
|
||||
:src="resolveImageUrl(scope.row.eventImage)"
|
||||
:preview-src-list="[resolveImageUrl(scope.row.eventImage)]"
|
||||
fit="cover"
|
||||
style="width: 60px; height: 60px"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
@ -146,9 +116,6 @@
|
||||
<el-form-item label="事件详情" prop="eventDetail">
|
||||
<editor v-model="form.eventDetail" :min-height="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配图" prop="eventImage">
|
||||
<image-upload v-model="form.eventImage" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="重点里程碑" prop="isHighlight">
|
||||
<el-radio-group v-model="form.isHighlight">
|
||||
<el-radio label="0">否</el-radio>
|
||||
@ -177,7 +144,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listCompanyTimeline, getCompanyTimeline, addCompanyTimeline, updateCompanyTimeline, delCompanyTimeline, exportCompanyTimeline } from '@/api/huayang/companyTimeline'
|
||||
import { listCompanyTimeline, getCompanyTimeline, addCompanyTimeline, updateCompanyTimeline, delCompanyTimeline } from '@/api/huayang/companyTimeline'
|
||||
|
||||
export default {
|
||||
name: 'HuayangCompanyTimeline',
|
||||
@ -212,19 +179,6 @@ export default {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
resolveImageUrl(raw) {
|
||||
if (!raw) return ''
|
||||
const value = String(raw).trim()
|
||||
if (!value) return ''
|
||||
if (/^https?:\/\//i.test(value)) return value
|
||||
const baseApi = process.env.VUE_APP_BASE_API || ''
|
||||
const baseOrigin = baseApi ? baseApi.replace(/\/api\/?$/, '') : ''
|
||||
if (value.startsWith('/assets/')) return value
|
||||
if (value.startsWith('/profile/')) return `${baseOrigin}${value}`
|
||||
if (value.startsWith('profile/')) return `${baseOrigin}/${value}`
|
||||
if (value.startsWith('/')) return `${baseOrigin}${value}`
|
||||
return `${baseOrigin}/${value}`
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listCompanyTimeline(this.queryParams).then((response) => {
|
||||
@ -288,15 +242,6 @@ export default {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleExport() {
|
||||
exportCompanyTimeline(this.queryParams).then((response) => {
|
||||
const blob = new Blob([response], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = '发展历程数据_' + this.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}') + '.xlsx'
|
||||
link.click()
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
:title="pageTitle"
|
||||
:description="pageDescription"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="配置键名" prop="configKey">
|
||||
<el-input
|
||||
@ -104,7 +112,7 @@
|
||||
<el-input v-model="form.configKey" placeholder="请输入配置键名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="配置值" prop="configValue">
|
||||
<image-upload v-if="isImageConfigKey(form.configKey)" v-model="form.configValue" :limit="1" />
|
||||
<image-upload v-if="isImageConfigKey(form.configKey)" v-model="form.configValue" :limit="1" spec="站点配置图片,请按具体展示位置上传;横幅建议 1920px 宽,图标/Logo 建议透明底 PNG/SVG" />
|
||||
<el-input v-else v-model="form.configValue" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
@ -128,6 +136,14 @@ export default {
|
||||
excludeConfigKeys: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
pageTitle: {
|
||||
type: String,
|
||||
default: '站点参数管理'
|
||||
},
|
||||
pageDescription: {
|
||||
type: String,
|
||||
default: 'SEO、备案、联系方式等站点参数在此维护(对应门户 /site/config/public/list 接口)。'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@ -1,114 +1,345 @@
|
||||
<template>
|
||||
|
||||
<div class="app-container">
|
||||
<el-card class="mb8" shadow="never">
|
||||
|
||||
<el-card shadow="never">
|
||||
|
||||
<div slot="header" class="clearfix">
|
||||
|
||||
<span>联系我们页面配置</span>
|
||||
|
||||
</div>
|
||||
|
||||
<el-form label-width="120px">
|
||||
|
||||
<el-form-item label="顶部图片">
|
||||
<image-upload v-model="pageConfig.contact_top_image" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="我们的位置图片">
|
||||
<image-upload v-model="pageConfig.contact_map_image" :limit="1" />
|
||||
|
||||
<image-upload v-model="pageConfig.contact_top_image" :limit="1" spec="联系我们顶部横幅,建议 1920×520px 或 16:5,主体居中,单张不超过 5MB" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="联系我们内容">
|
||||
|
||||
<el-input
|
||||
|
||||
v-model="pageConfig.contact_content"
|
||||
|
||||
type="textarea"
|
||||
|
||||
:rows="5"
|
||||
|
||||
placeholder="请输入联系我们内容,支持多行"
|
||||
|
||||
/>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="pageConfig.contact_phone" placeholder="请输入联系电话" />
|
||||
|
||||
<el-input
|
||||
|
||||
v-model="pageConfig.contact_phones"
|
||||
|
||||
type="textarea"
|
||||
|
||||
:rows="4"
|
||||
|
||||
placeholder="每行一个电话,前台「商务热线」区块内展示全部号码"
|
||||
|
||||
/>
|
||||
|
||||
<div class="field-tip">在同一电话区块内增加号码:直接换行输入,无需新增其他类型</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="联系邮箱">
|
||||
<el-input v-model="pageConfig.contact_email" placeholder="请输入联系邮箱" />
|
||||
|
||||
<el-input
|
||||
|
||||
v-model="pageConfig.contact_emails"
|
||||
|
||||
type="textarea"
|
||||
|
||||
:rows="4"
|
||||
|
||||
placeholder="每行一个邮箱,前台「E-mail」区块内展示全部邮箱"
|
||||
|
||||
/>
|
||||
|
||||
<div class="field-tip">在同一邮箱区块内增加地址:直接换行输入</div>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="联系地址">
|
||||
|
||||
<el-input v-model="pageConfig.contact_address" placeholder="请输入联系地址" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
|
||||
<el-button type="primary" :loading="savingPageConfig" @click="savePageConfig">保存页面配置</el-button>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
</el-card>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import { listConfig, addConfig, updateConfig } from '@/api/huayang/config'
|
||||
|
||||
import { listContact, addContact, deleteContact } from '@/api/huayang/contact'
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
name: 'HuayangContact',
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
savingPageConfig: false,
|
||||
|
||||
pageConfig: {
|
||||
|
||||
contact_top_image: '',
|
||||
contact_map_image: '',
|
||||
|
||||
contact_content: '',
|
||||
contact_phone: '',
|
||||
contact_email: '',
|
||||
|
||||
contact_phones: '',
|
||||
|
||||
contact_emails: '',
|
||||
|
||||
contact_address: ''
|
||||
|
||||
},
|
||||
|
||||
pageConfigMeta: {
|
||||
|
||||
contact_top_image: null,
|
||||
contact_map_image: null,
|
||||
|
||||
contact_content: null,
|
||||
|
||||
contact_phone: null,
|
||||
|
||||
contact_email: null,
|
||||
|
||||
contact_address: null
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
created() {
|
||||
|
||||
this.getPageConfig()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
parseLines(raw) {
|
||||
|
||||
return String(raw || '')
|
||||
|
||||
.split(/\r?\n/)
|
||||
|
||||
.map((line) => line.trim())
|
||||
|
||||
.filter(Boolean)
|
||||
|
||||
},
|
||||
|
||||
async getPageConfig() {
|
||||
const keys = ['contact_top_image', 'contact_map_image', 'contact_content', 'contact_phone', 'contact_email', 'contact_address']
|
||||
|
||||
const keys = ['contact_top_image', 'contact_content', 'contact_phone', 'contact_email', 'contact_address']
|
||||
|
||||
const rows = await Promise.all(keys.map((key) => this.fetchConfigByKey(key)))
|
||||
|
||||
rows.forEach((row, idx) => {
|
||||
|
||||
const key = keys[idx]
|
||||
this.pageConfig[key] = row?.configValue || ''
|
||||
|
||||
const value = row?.configValue || ''
|
||||
|
||||
if (key === 'contact_top_image' || key === 'contact_content' || key === 'contact_address') {
|
||||
|
||||
this.pageConfig[key] = value
|
||||
|
||||
}
|
||||
|
||||
if (key === 'contact_phone') {
|
||||
|
||||
this.pageConfig.contact_phones = value
|
||||
|
||||
}
|
||||
|
||||
if (key === 'contact_email') {
|
||||
|
||||
this.pageConfig.contact_emails = value
|
||||
|
||||
}
|
||||
|
||||
this.pageConfigMeta[key] = row || null
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
async fetchConfigByKey(key) {
|
||||
|
||||
const response = await listConfig({ pageNum: 1, pageSize: 1, configKey: key })
|
||||
|
||||
const rows = Array.isArray(response?.rows) ? response.rows : []
|
||||
|
||||
return rows.find(item => item.configKey === key) || null
|
||||
|
||||
},
|
||||
|
||||
async fetchAllContacts() {
|
||||
|
||||
const response = await listContact({ pageNum: 1, pageSize: 200 })
|
||||
|
||||
return Array.isArray(response?.rows) ? response.rows : []
|
||||
|
||||
},
|
||||
|
||||
async syncContactGroup(type, lines, defaultLabel, sortOrder) {
|
||||
|
||||
const allContacts = await this.fetchAllContacts()
|
||||
|
||||
const existing = allContacts.filter((item) => item.contactType === type)
|
||||
|
||||
if (existing.length) {
|
||||
|
||||
await deleteContact(existing.map((item) => item.contactId).join(','))
|
||||
|
||||
}
|
||||
|
||||
if (!lines.length) return
|
||||
|
||||
await addContact({
|
||||
|
||||
contactType: type,
|
||||
|
||||
contactLabel: defaultLabel,
|
||||
|
||||
contactValue: lines.join('\n'),
|
||||
|
||||
sortOrder,
|
||||
|
||||
isEnabled: '1'
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
async savePageConfig() {
|
||||
|
||||
this.savingPageConfig = true
|
||||
|
||||
try {
|
||||
|
||||
const phoneLines = this.parseLines(this.pageConfig.contact_phones)
|
||||
|
||||
const emailLines = this.parseLines(this.pageConfig.contact_emails)
|
||||
|
||||
const address = String(this.pageConfig.contact_address || '').trim()
|
||||
|
||||
|
||||
|
||||
await this.upsertConfig('contact_top_image', '联系我们顶部图片', this.pageConfig.contact_top_image, '联系我们页顶部横幅图片')
|
||||
await this.upsertConfig('contact_map_image', '我们的位置图片', this.pageConfig.contact_map_image, '联系我们页位置展示图片')
|
||||
|
||||
await this.upsertConfig('contact_content', '联系我们内容', this.pageConfig.contact_content, '联系我们页顶部说明文案')
|
||||
await this.upsertConfig('contact_phone', '联系电话', this.pageConfig.contact_phone, '联系我们页联系电话')
|
||||
await this.upsertConfig('contact_email', '联系邮箱', this.pageConfig.contact_email, '联系我们页联系邮箱')
|
||||
await this.upsertConfig('contact_address', '联系地址', this.pageConfig.contact_address, '联系我们页联系地址')
|
||||
|
||||
await this.upsertConfig('contact_phone', '联系电话', phoneLines.join('\n'), '联系我们页联系电话(每行一个,首条同步页脚)')
|
||||
|
||||
await this.upsertConfig('contact_email', '联系邮箱', emailLines.join('\n'), '联系我们页联系邮箱(每行一个,首条同步页脚)')
|
||||
|
||||
await this.upsertConfig('contact_address', '联系地址', address, '联系我们页联系地址')
|
||||
|
||||
|
||||
|
||||
await this.syncContactGroup('phone', phoneLines, '商务热线', 0)
|
||||
|
||||
await this.syncContactGroup('email', emailLines, 'E-mail', 1)
|
||||
|
||||
await this.syncContactGroup('address', address ? [address] : [], '地址', 2)
|
||||
|
||||
|
||||
|
||||
this.$modal.msgSuccess('页面配置保存成功')
|
||||
|
||||
await this.getPageConfig()
|
||||
|
||||
} finally {
|
||||
|
||||
this.savingPageConfig = false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async upsertConfig(configKey, configName, configValue, remark) {
|
||||
|
||||
const meta = this.pageConfigMeta[configKey]
|
||||
|
||||
const payload = {
|
||||
|
||||
configKey,
|
||||
|
||||
configName,
|
||||
|
||||
configValue: configValue == null ? '' : String(configValue),
|
||||
|
||||
remark
|
||||
|
||||
}
|
||||
|
||||
if (meta?.configId) {
|
||||
|
||||
await updateConfig({ ...meta, ...payload })
|
||||
|
||||
} else {
|
||||
|
||||
await addConfig(payload)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.field-tip {
|
||||
|
||||
margin-top: 6px;
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
color: #909399;
|
||||
|
||||
line-height: 1.5;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -1,13 +1,5 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
v-hasPermi="['huayang:navigation:list']"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
title="门户文化页底部导航栏请在「页脚一站式配置」或「导航管理(底部导航)」中维护。"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="72px">
|
||||
<el-form-item label="名称" prop="categoryName">
|
||||
<el-input v-model="queryParams.categoryName" placeholder="分类名称" clearable @keyup.enter.native="handleQuery" />
|
||||
|
||||
@ -1,15 +1,8 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
title="请先在「文化分类」中维护分类;此处按分类维护官网卡片标题与详情。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="所属分类" prop="categoryId">
|
||||
<el-select v-model="queryParams.categoryId" placeholder="请选择分类" clearable filterable style="width: 220px" @change="handleQuery">
|
||||
<el-select v-model="queryParams.categoryId" placeholder="所属分类" clearable filterable style="width: 220px">
|
||||
<el-option v-for="c in categoryOptions" :key="c.categoryId" :label="c.categoryName" :value="c.categoryId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -29,13 +22,18 @@
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" :disabled="!queryParams.categoryId" @click="handleAdd" v-hasPermi="['huayang:cultureTag:add']">新增</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['huayang:cultureTag:add']">新增</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" border>
|
||||
<el-table-column label="ID" prop="tagId" width="72" align="center" />
|
||||
<el-table-column label="所属分类" min-width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ categoryLabel(scope.row.categoryId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标题" prop="tagTitle" min-width="160" />
|
||||
<el-table-column label="排序" prop="sortOrder" width="80" align="center" />
|
||||
<el-table-column label="阅读" prop="readCount" width="80" align="center" />
|
||||
@ -56,14 +54,14 @@
|
||||
|
||||
<el-dialog :title="dialogTitle" :visible.sync="open" width="900px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="96px">
|
||||
<el-form-item label="所属分类" prop="categoryId">
|
||||
<el-select v-model="form.categoryId" placeholder="请选择" filterable style="width: 100%">
|
||||
<el-option v-for="c in categoryOptions" :key="c.categoryId" :label="c.categoryName" :value="c.categoryId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="tagTitle">
|
||||
<el-input v-model="form.tagTitle" maxlength="200" show-word-limit placeholder="官网卡片显示名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属分类" prop="categoryId">
|
||||
<el-select v-model="form.categoryId" placeholder="请选择所属分类" filterable style="width: 100%">
|
||||
<el-option v-for="c in categoryOptions" :key="c.categoryId" :label="c.categoryName" :value="c.categoryId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
@ -73,7 +71,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="coverImage">
|
||||
<image-upload v-model="form.coverImage" :limit="1" />
|
||||
<image-upload v-model="form.coverImage" :limit="1" spec="文化内容封面,建议 1200×800px 或 3:2,主体居中,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="详情内容" prop="tagContent">
|
||||
<editor v-model="form.tagContent" :min-height="280" />
|
||||
@ -115,7 +113,7 @@ export default {
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
categoryId: [{ required: true, message: '请选择分类', trigger: 'change' }],
|
||||
categoryId: [{ required: true, message: '请选择所属分类', trigger: 'change' }],
|
||||
tagTitle: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
@ -133,16 +131,14 @@ export default {
|
||||
})
|
||||
},
|
||||
getList() {
|
||||
if (!this.queryParams.categoryId) {
|
||||
this.dataList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
listCultureTag(this.queryParams).then((res) => {
|
||||
this.dataList = res.rows
|
||||
this.total = res.total
|
||||
}).catch(() => {
|
||||
this.dataList = []
|
||||
this.total = 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
@ -158,7 +154,7 @@ export default {
|
||||
resetFormData() {
|
||||
this.form = {
|
||||
tagId: undefined,
|
||||
categoryId: this.queryParams.categoryId,
|
||||
categoryId: undefined,
|
||||
tagTitle: undefined,
|
||||
tagContent: undefined,
|
||||
coverImage: undefined,
|
||||
@ -168,13 +164,13 @@ export default {
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
categoryLabel(categoryId) {
|
||||
if (categoryId == null || categoryId === '') return '-'
|
||||
const c = this.categoryOptions.find((x) => String(x.categoryId) === String(categoryId))
|
||||
return c ? c.categoryName : categoryId
|
||||
},
|
||||
handleAdd() {
|
||||
if (!this.queryParams.categoryId) {
|
||||
this.$modal.msgWarning('请先选择「所属分类」')
|
||||
return
|
||||
}
|
||||
this.resetFormData()
|
||||
this.form.categoryId = this.queryParams.categoryId
|
||||
this.open = true
|
||||
this.dialogTitle = '新增文化详情'
|
||||
},
|
||||
|
||||
@ -2,16 +2,9 @@
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<div slot="header">华阳文化 · 页顶横幅图</div>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="此处设置官网「华阳文化」页面顶部通栏大图(导航下方横幅);保存后前台刷新页面即可生效。"
|
||||
/>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="横幅图片">
|
||||
<image-upload v-model="form.backgroundImage" :limit="1" />
|
||||
<image-upload v-model="form.backgroundImage" :limit="1" spec="文化页顶横幅,建议 1920×520px 或 16:5,主体居中,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit" v-hasPermi="['huayang:cultureBanner:edit']">保存</el-button>
|
||||
|
||||
@ -16,6 +16,9 @@
|
||||
<el-form-item label="描述文字" v-if="showText">
|
||||
<div v-html="mainForm.sectionText || '暂无文字'"></div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="sectionUsageTip">
|
||||
<p class="form-tip">{{ sectionUsageTip }}</p>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@ -26,23 +29,6 @@
|
||||
<el-button style="float: right; padding: 3px 0" type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新增子项</el-button>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="editableSectionName && !showItemIconField"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="item-list-tip"
|
||||
title="子项对应首页「关于华阳」右侧四张数据卡:「标题」填主数字与单位,中间用竖线分隔,如 1992|年、60000|㎡、10|+;「描述」填卡片底部说明文字;「图片」为卡片右侧图标。"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="showItemIconField"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="item-list-tip"
|
||||
title="对应首页「华阳七道鲜」每一竖列:主背景图=整列大图;小图标=大字上方的白色图标;大字标题=单列主字(如「选」);底部说明=小字文案。"
|
||||
/>
|
||||
|
||||
<el-table v-loading="loading" :data="itemList">
|
||||
<el-table-column label="主图" align="center" prop="itemImage" width="100">
|
||||
<template slot-scope="scope">
|
||||
@ -78,11 +64,11 @@
|
||||
<el-input
|
||||
v-model="mainForm.sectionName"
|
||||
:disabled="!editableSectionName"
|
||||
:placeholder="editableSectionName ? (showItemIconField ? '如:华阳七道鲜' : '前台「关于华阳」大标题,如:关于华阳') : ''"
|
||||
:placeholder="editableSectionName ? (showItemIconField ? '请输入模块主标题' : '前台「关于华阳」大标题,如:关于华阳') : ''"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图" v-if="showBg">
|
||||
<image-upload v-model="mainForm.backgroundImage" :limit="1" :drag="false"/>
|
||||
<image-upload v-model="mainForm.backgroundImage" :limit="1" :drag="false" :spec="mainImageSpec"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述文字" v-if="showText">
|
||||
<editor v-model="mainForm.sectionText" :min-height="192"/>
|
||||
@ -101,21 +87,23 @@
|
||||
<el-form-item :label="showItemIconField ? '大字标题' : '标题'" prop="itemTitle">
|
||||
<el-input
|
||||
v-model="form.itemTitle"
|
||||
:placeholder="editableSectionName ? '主数字|单位,如 1992|年、60000|㎡、10|+' : (showItemIconField ? '如:选、留、测(单列主字)' : '请输入标题')"
|
||||
:placeholder="itemTitlePlaceholder"
|
||||
/>
|
||||
<p v-if="itemTitleTip" class="form-tip">{{ itemTitleTip }}</p>
|
||||
</el-form-item>
|
||||
<el-form-item :label="showItemIconField ? '底部说明' : '描述'" prop="itemDesc">
|
||||
<el-input
|
||||
v-model="form.itemDesc"
|
||||
type="textarea"
|
||||
:placeholder="editableSectionName ? '卡片底部说明,如 公司成立时间' : (showItemIconField ? '该列底部小字说明' : '请输入内容')"
|
||||
:placeholder="itemDescPlaceholder"
|
||||
/>
|
||||
<p v-if="itemDescTip" class="form-tip">{{ itemDescTip }}</p>
|
||||
</el-form-item>
|
||||
<el-form-item :label="showItemIconField ? '主背景图' : '图片'" prop="itemImage">
|
||||
<image-upload v-model="form.itemImage"/>
|
||||
<image-upload v-model="form.itemImage" :limit="itemImageLimit" :spec="itemImageSpec"/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showItemIconField" label="小图标" prop="itemIcon">
|
||||
<image-upload v-model="form.itemIcon" :limit="1" :drag="false"/>
|
||||
<image-upload v-model="form.itemIcon" :limit="1" :drag="false" spec="列顶小图标,建议 96×96px 或 SVG/PNG 透明底,单张不超过 1MB"/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!editableSectionName" label="链接" prop="itemLink">
|
||||
<el-input v-model="form.itemLink" placeholder="请输入跳转链接" />
|
||||
@ -176,7 +164,7 @@ export default {
|
||||
type: String,
|
||||
default: "slide"
|
||||
},
|
||||
/** 为 true 时子项多一个「小图标」字段(华阳七道鲜列顶图标) */
|
||||
/** 为 true 时子项多一个「小图标」字段(列顶图标) */
|
||||
showItemIconField: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -199,6 +187,56 @@ export default {
|
||||
computed: {
|
||||
hasMainConfig() {
|
||||
return this.showBg || this.showText || this.editableSectionName;
|
||||
},
|
||||
mainImageSpec() {
|
||||
if (this.sectionCode === 'section4') {
|
||||
return '首页「华阳文化」整屏背景,建议 1920×1080px,主体避开中部文字和卡片区域'
|
||||
}
|
||||
return '板块背景图,建议 1920×900px 或 16:9,主体居中,单张不超过 5MB'
|
||||
},
|
||||
itemImageSpec() {
|
||||
if (this.sectionCode === 'section1') {
|
||||
return '首页轮播图,建议 1920×900px 或 16:9;可上传多张,按顺序轮播'
|
||||
}
|
||||
if (this.sectionCode === 'section4') {
|
||||
return '竖列卡片主图,建议 420×560px 或 3:4,主体居中,单张不超过 5MB'
|
||||
}
|
||||
if (this.sectionCode === 'section2') {
|
||||
return '关于华阳数据卡片图,建议 600×420px 或 4:3,单张不超过 5MB'
|
||||
}
|
||||
return '板块子项图片,建议 800×600px 或按页面展示比例上传,单张不超过 5MB'
|
||||
},
|
||||
itemImageLimit() {
|
||||
return this.sectionCode === 'section1' ? 8 : 1
|
||||
},
|
||||
sectionUsageTip() {
|
||||
if (this.sectionCode === 'section2') {
|
||||
return '首页「关于华阳」四宫格:标题填写“数字|单位”,描述填写下方标签。'
|
||||
}
|
||||
if (this.sectionCode === 'section4') {
|
||||
return '首页「华阳文化」竖列卡片:大字只填单字,底部说明填写对应短语。'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
itemTitlePlaceholder() {
|
||||
if (this.sectionCode === 'section2') return '如:1992|年、60000|㎡、10|+'
|
||||
if (this.sectionCode === 'section4') return '如:选、留、测、藏、制'
|
||||
return this.editableSectionName ? '' : '请输入标题'
|
||||
},
|
||||
itemDescPlaceholder() {
|
||||
if (this.sectionCode === 'section2') return '如:公司成立时间、占地面积、标准化加工车间'
|
||||
if (this.sectionCode === 'section4') return '如:深海鲜冻海珍、急速冻鲜加工'
|
||||
return this.editableSectionName ? '' : '请输入内容'
|
||||
},
|
||||
itemTitleTip() {
|
||||
if (this.sectionCode === 'section2') return '竖线前是滚动数字,竖线后是单位。'
|
||||
if (this.sectionCode === 'section4') return '前台卡片中部展示的大字,建议保持单字。'
|
||||
return ''
|
||||
},
|
||||
itemDescTip() {
|
||||
if (this.sectionCode === 'section2') return '前台显示在数字下方,例如“公司成立时间”。'
|
||||
if (this.sectionCode === 'section4') return '前台显示在卡片底部,不再填写“华阳/大连/食品”等占位词。'
|
||||
return ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -300,9 +338,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.mb20 { margin-bottom: 20px; }
|
||||
.item-list-tip {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.form-tip {
|
||||
margin-top: 8px;
|
||||
color: #909399;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<fixed-section-manager
|
||||
section-code="section4"
|
||||
:section-id="4"
|
||||
:show-bg="true"
|
||||
:show-text="false"
|
||||
<fixed-section-manager
|
||||
section-code="section4"
|
||||
:section-id="4"
|
||||
:show-bg="true"
|
||||
:show-text="false"
|
||||
:editable-section-name="true"
|
||||
:show-item-icon-field="true"
|
||||
list-title="华阳七道鲜 · 竖列卡片"
|
||||
list-title="竖列卡片"
|
||||
item-type="card"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@ -1,29 +1,151 @@
|
||||
<template>
|
||||
<div class="app-container footer-console">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb16"
|
||||
title="站点参数(页脚相关)"
|
||||
description="备案、SEO、外链及页脚红条文案等在此按配置键维护(与门户 /site/config/public/list 对应)。"
|
||||
/>
|
||||
<site-config-manage :exclude-config-keys="excludeConfigKeys" />
|
||||
<el-card class="mb16" shadow="never">
|
||||
<div slot="header" class="card-header">
|
||||
<span>快捷设置</span>
|
||||
</div>
|
||||
<el-form label-width="140px" size="small">
|
||||
<el-form-item label="底部导航背景图">
|
||||
<image-upload v-model="quickConfig.footer_nav_bg" :limit="1" spec="页脚整体背景,建议 1920×640px 或 3:1,画面偏暗/留白适合叠加文字" />
|
||||
<div class="hint">作用于门户页脚整体背景(.s_tmpl_footer)。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="版权文案">
|
||||
<el-input v-model="quickConfig.site_copyright" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备案号">
|
||||
<el-input v-model="quickConfig.site_icp" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="quickConfig.footer_company_name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务热线">
|
||||
<el-input v-model="quickConfig.contact_phone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="E-mail">
|
||||
<el-input v-model="quickConfig.contact_email" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="quickConfig.contact_address" />
|
||||
</el-form-item>
|
||||
<el-form-item label="隐私政策文案">
|
||||
<el-input v-model="quickConfig.footer_policy_text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="隐私政策链接">
|
||||
<el-input v-model="quickConfig.footer_policy_link" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SEO文案">
|
||||
<el-input v-model="quickConfig.footer_seo_text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SEO链接">
|
||||
<el-input v-model="quickConfig.footer_seo_link" />
|
||||
</el-form-item>
|
||||
<el-form-item label="网址建设文案">
|
||||
<el-input v-model="quickConfig.footer_builder_text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="网址建设链接">
|
||||
<el-input v-model="quickConfig.footer_builder_link" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="saveQuickConfig">保存</el-button>
|
||||
<el-button :disabled="saving" @click="reloadQuickConfig">刷新</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SiteConfigManage from '@/views/huayang/config/index'
|
||||
import { listConfig, addConfig, updateConfig } from '@/api/huayang/config'
|
||||
|
||||
export default {
|
||||
name: 'HuayangFooterConsole',
|
||||
components: {
|
||||
SiteConfigManage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 「联系我们」页面专属字段,不应出现在页脚一站式配置中,避免与独立页面管理冲突
|
||||
excludeConfigKeys: ['contact_top_image', 'contact_map_image', 'contact_content']
|
||||
quickConfig: {
|
||||
footer_nav_bg: '',
|
||||
site_copyright: '',
|
||||
site_icp: '',
|
||||
footer_company_name: '',
|
||||
contact_phone: '',
|
||||
contact_email: '',
|
||||
contact_address: '',
|
||||
footer_policy_text: '',
|
||||
footer_policy_link: '',
|
||||
footer_seo_text: '',
|
||||
footer_seo_link: '',
|
||||
footer_builder_text: '',
|
||||
footer_builder_link: ''
|
||||
},
|
||||
quickConfigIdMap: {
|
||||
footer_nav_bg: null,
|
||||
site_copyright: null,
|
||||
site_icp: null,
|
||||
footer_company_name: null,
|
||||
contact_phone: null,
|
||||
contact_email: null,
|
||||
contact_address: null,
|
||||
footer_policy_text: null,
|
||||
footer_policy_link: null,
|
||||
footer_seo_text: null,
|
||||
footer_seo_link: null,
|
||||
footer_builder_text: null,
|
||||
footer_builder_link: null
|
||||
},
|
||||
saving: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.reloadQuickConfig()
|
||||
},
|
||||
methods: {
|
||||
async reloadQuickConfig() {
|
||||
try {
|
||||
const res = await listConfig({ pageNum: 1, pageSize: 9999 })
|
||||
const rows = Array.isArray(res?.rows) ? res.rows : []
|
||||
const keys = Object.keys(this.quickConfig)
|
||||
keys.forEach((key) => {
|
||||
const hit = rows.find((r) => String(r?.configKey || '').trim() === key)
|
||||
this.quickConfig[key] = hit?.configValue || ''
|
||||
this.quickConfigIdMap[key] = hit?.configId ?? null
|
||||
})
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
async upsertConfig(configKey, configValue, configName, remark) {
|
||||
const payload = { configKey, configValue, configName, remark }
|
||||
const existingId = this.quickConfigIdMap[configKey]
|
||||
if (existingId != null) {
|
||||
await updateConfig({ ...payload, configId: existingId })
|
||||
return
|
||||
}
|
||||
const res = await addConfig(payload)
|
||||
if (res) {
|
||||
await this.reloadQuickConfig()
|
||||
}
|
||||
},
|
||||
async saveQuickConfig() {
|
||||
this.saving = true
|
||||
try {
|
||||
await this.upsertConfig('footer_nav_bg', this.quickConfig.footer_nav_bg, '底部导航栏背景图', '作用于前端 .s_tmpl_footer;支持 none/transparent 清除')
|
||||
await this.upsertConfig('site_copyright', this.quickConfig.site_copyright, '页脚版权文案', '作用于页脚版权区域')
|
||||
await this.upsertConfig('site_icp', this.quickConfig.site_icp, '页脚备案号', '作用于页脚备案号区域')
|
||||
await this.upsertConfig('footer_company_name', this.quickConfig.footer_company_name, '页脚公司名称', '作用于页脚公司信息标题')
|
||||
await this.upsertConfig('contact_phone', this.quickConfig.contact_phone, '联系电话', '作用于全站联系方式(含页脚)')
|
||||
await this.upsertConfig('contact_email', this.quickConfig.contact_email, '联系邮箱', '作用于全站联系方式(含页脚)')
|
||||
await this.upsertConfig('contact_address', this.quickConfig.contact_address, '联系地址', '作用于全站联系方式(含页脚)')
|
||||
await this.upsertConfig('footer_policy_text', this.quickConfig.footer_policy_text, '页脚隐私政策文案', '作用于页脚政策链接文案')
|
||||
await this.upsertConfig('footer_policy_link', this.quickConfig.footer_policy_link, '页脚隐私政策链接', '作用于页脚政策链接 URL')
|
||||
await this.upsertConfig('footer_seo_text', this.quickConfig.footer_seo_text, '页脚SEO文案', '作用于页脚 SEO 链接文案')
|
||||
await this.upsertConfig('footer_seo_link', this.quickConfig.footer_seo_link, '页脚SEO链接', '作用于页脚 SEO 链接 URL')
|
||||
await this.upsertConfig('footer_builder_text', this.quickConfig.footer_builder_text, '页脚网址建设文案', '作用于页脚网址建设文案')
|
||||
await this.upsertConfig('footer_builder_link', this.quickConfig.footer_builder_link, '页脚网址建设链接', '作用于页脚网址建设链接 URL')
|
||||
this.$modal?.msgSuccess?.('保存成功')
|
||||
} catch (e) {
|
||||
this.$modal?.msgError?.('保存失败')
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,4 +155,20 @@ export default {
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 6px;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.hint code {
|
||||
padding: 0 4px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -104,10 +104,10 @@
|
||||
<el-input v-model="form.bannerTitle" placeholder="请输入标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="PC图片" prop="bannerImage">
|
||||
<image-upload v-model="form.bannerImage"/>
|
||||
<image-upload v-model="form.bannerImage" :limit="1" spec="PC 首页首屏横幅,建议 1920×900px 或 16:9,主体居中,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="移动端图片" prop="mobileImage">
|
||||
<image-upload v-model="form.mobileImage"/>
|
||||
<image-upload v-model="form.mobileImage" :limit="1" spec="移动端首屏横幅,建议 750×1100px 或 3:4,重要内容居中上方,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="linkUrl">
|
||||
<el-input v-model="form.linkUrl" placeholder="请输入跳转链接" />
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
<el-input v-model="form.featureDesc" type="textarea" placeholder="请输入业务简述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标/图片" prop="featureIcon">
|
||||
<image-upload v-model="form.featureIcon"/>
|
||||
<image-upload v-model="form.featureIcon" :limit="1" spec="首页业务图标/配图,图标建议 160×160px 透明底;配图建议 600×420px 或 4:3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="linkUrl">
|
||||
<el-input v-model="form.linkUrl" placeholder="请输入跳转链接" />
|
||||
|
||||
@ -17,16 +17,6 @@
|
||||
>打开门户首页</el-button>
|
||||
<span class="portal-hint">预览地址来自环境变量 VUE_APP_PORTAL_URL</span>
|
||||
</div>
|
||||
<div class="quick-links">
|
||||
<span class="quick-label">独立菜单:</span>
|
||||
<el-button
|
||||
v-for="l in quickLinks"
|
||||
:key="l.path"
|
||||
size="small"
|
||||
plain
|
||||
@click="go(l.path)"
|
||||
>{{ l.label }}</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
@ -35,7 +25,7 @@
|
||||
<el-tab-pane label="首页轮播图" name="s1" lazy><section1-panel v-if="activeTab === 'fixed' && fixedTab === 's1'" /></el-tab-pane>
|
||||
<el-tab-pane label="首页核心业务" name="s2" lazy><section2-panel v-if="activeTab === 'fixed' && fixedTab === 's2'" /></el-tab-pane>
|
||||
<el-tab-pane label="展示区3-背景轮播" name="s3" lazy><section3-panel v-if="activeTab === 'fixed' && fixedTab === 's3'" /></el-tab-pane>
|
||||
<el-tab-pane label="华阳七道鲜(板块四)" name="s4" lazy><section4-panel v-if="activeTab === 'fixed' && fixedTab === 's4'" /></el-tab-pane>
|
||||
<el-tab-pane label="板块四" name="s4" lazy><section4-panel v-if="activeTab === 'fixed' && fixedTab === 's4'" /></el-tab-pane>
|
||||
<el-tab-pane label="展示区5-背景文字" name="s5" lazy><section5-panel v-if="activeTab === 'fixed' && fixedTab === 's5'" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
@ -62,17 +52,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'fixed',
|
||||
fixedTab: 's1',
|
||||
quickLinks: [
|
||||
{ label: '固定板块-首页轮播图', path: '/huayangSite/fixedSection/section1' },
|
||||
{ label: '固定板块-首页核心业务', path: '/huayangSite/fixedSection/section2' },
|
||||
{ label: '固定板块-展示区3', path: '/huayangSite/fixedSection/section3' },
|
||||
{ label: '固定板块-华阳七道鲜', path: '/huayangSite/fixedSection/section4' },
|
||||
{ label: '产品管理', path: '/huayangSite/productCenter/product' },
|
||||
{ label: '新闻管理', path: '/huayangSite/newsCenter/news' },
|
||||
{ label: '公司简介分块', path: '/huayangSite/brand/companySection' },
|
||||
{ label: '翻译管理', path: '/huayangSite/i18nCenter/translation' }
|
||||
]
|
||||
fixedTab: 's1'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -87,12 +67,9 @@ export default {
|
||||
return 'fixed'
|
||||
},
|
||||
openPortal() {
|
||||
const base = (process.env.VUE_APP_PORTAL_URL || 'http://localhost:5173').replace(/\/$/, '')
|
||||
const base = (process.env.VUE_APP_PORTAL_URL || 'http://192.168.1.159:7002').replace(/\/$/, '')
|
||||
const url = `${base}/?page=home`
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
},
|
||||
go(path) {
|
||||
this.$router.push(path).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,14 +91,6 @@ export default {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
.quick-links {
|
||||
text-align: right;
|
||||
}
|
||||
.quick-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.nested-fixed-tabs {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@ -1,162 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="申请人" prop="applyName">
|
||||
<el-input v-model="queryParams.applyName" placeholder="请输入申请人姓名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理状态" prop="applyStatus">
|
||||
<el-select v-model="queryParams.applyStatus" placeholder="处理状态" clearable>
|
||||
<el-option v-for="dict in dict.type.huayang_apply_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
|
||||
v-hasPermi="['huayang:joinApply:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="applyList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="编号" align="center" prop="applyId" width="80" />
|
||||
<el-table-column label="申请人" align="center" prop="applyName" width="100" />
|
||||
<el-table-column label="联系电话" align="center" prop="applyPhone" width="130" />
|
||||
<el-table-column label="公司/店铺" align="center" prop="applyCompany" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所在地区" align="center" prop="applyArea" width="120" />
|
||||
<el-table-column label="合作意向" align="center" prop="applyIntent" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="applyStatus" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.huayang_apply_status" :value="scope.row.applyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" width="160" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)"
|
||||
v-hasPermi="['huayang:joinApply:query']">查看</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-check" @click="handleProcess(scope.row)"
|
||||
v-hasPermi="['huayang:joinApply:handle']" v-if="scope.row.applyStatus === '0'">处理</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
|
||||
v-hasPermi="['huayang:joinApply:remove']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 查看/处理对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="openDialog" width="600px" append-to-body>
|
||||
<el-form ref="dialogForm" :model="dialogForm" label-width="120px">
|
||||
<el-form-item label="申请人">{{ dialogForm.applyName }}</el-form-item>
|
||||
<el-form-item label="联系电话">{{ dialogForm.applyPhone }}</el-form-item>
|
||||
<el-form-item label="公司/店铺">{{ dialogForm.applyCompany || '-' }}</el-form-item>
|
||||
<el-form-item label="所在地区">{{ dialogForm.applyArea || '-' }}</el-form-item>
|
||||
<el-form-item label="合作意向">{{ dialogForm.applyIntent || '-' }}</el-form-item>
|
||||
<el-form-item label="申请时间">{{ dialogForm.createTime }}</el-form-item>
|
||||
<el-form-item label="处理状态">
|
||||
<dict-tag :options="dict.type.huayang_apply_status" :value="dialogForm.applyStatus" />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理备注" v-if="dialogForm.handleRemark">{{ dialogForm.handleRemark }}</el-form-item>
|
||||
<el-form-item label="处理状态" v-if="dialogForm.applyStatus === '0'">
|
||||
<el-radio-group v-model="dialogForm.applyStatusNew">
|
||||
<el-radio label="1">已联系</el-radio>
|
||||
<el-radio label="2">已拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="处理备注" v-if="dialogForm.applyStatus === '0'">
|
||||
<el-input v-model="dialogForm.handleRemark" type="textarea" :rows="3" placeholder="请输入处理备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitProcess" v-if="dialogForm.applyStatus === '0'">确 定</el-button>
|
||||
<el-button @click="openDialog = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listJoinApply, handleJoinApply, deleteJoinApply } from '@/api/huayang/joinApply'
|
||||
|
||||
export default {
|
||||
name: 'HuayangJoinApply',
|
||||
dicts: ['huayang_apply_status'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
showSearch: true,
|
||||
ids: [],
|
||||
multiple: true,
|
||||
total: 0,
|
||||
applyList: [],
|
||||
queryParams: { pageNum: 1, pageSize: 10, applyName: undefined, applyStatus: undefined },
|
||||
openDialog: false,
|
||||
dialogTitle: '',
|
||||
dialogForm: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
listJoinApply(this.queryParams).then(response => {
|
||||
this.applyList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.applyId)
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
handleView(row) {
|
||||
this.dialogTitle = '申请详情'
|
||||
this.dialogForm = { ...row, applyStatusNew: '1', handleRemark: '' }
|
||||
this.openDialog = true
|
||||
},
|
||||
handleProcess(row) {
|
||||
this.dialogTitle = '处理申请'
|
||||
this.dialogForm = { ...row, applyStatusNew: '1', handleRemark: '' }
|
||||
this.openDialog = true
|
||||
},
|
||||
submitProcess() {
|
||||
handleJoinApply({
|
||||
applyId: this.dialogForm.applyId,
|
||||
applyStatus: this.dialogForm.applyStatusNew,
|
||||
handleRemark: this.dialogForm.handleRemark,
|
||||
updateBy: this.$store.state.user.name
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('处理成功')
|
||||
this.openDialog = false
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const applyIds = row.applyId || this.ids
|
||||
this.$modal.confirm('是否确认删除选中的申请数据项?').then(() => {
|
||||
return deleteJoinApply(applyIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,439 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
:title="activeTab === 'join_rules' ? joinRulesAlert : defaultAlert"
|
||||
/>
|
||||
<el-alert
|
||||
v-hasPermi="['huayang:navigation:list']"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
title="门户招商加盟页底部导航栏请在「页脚一站式配置」或「导航管理(底部导航)」中维护。"
|
||||
/>
|
||||
<el-tabs v-model="activeTab" @tab-click="onTabClick">
|
||||
<el-tab-pane label="加盟细则" name="join_rules" />
|
||||
<el-tab-pane label="加盟流程" name="join_process" />
|
||||
<el-tab-pane label="加盟资质" name="join_qualification" />
|
||||
</el-tabs>
|
||||
|
||||
<!-- 加盟细则:顶部图 + 优势区(文+图) + 双栏 -->
|
||||
<el-form
|
||||
v-if="activeTab === 'join_rules'"
|
||||
ref="form"
|
||||
:model="form"
|
||||
:rules="joinRulesFormRules"
|
||||
label-width="128px"
|
||||
class="join-form"
|
||||
>
|
||||
<el-form-item label="板块标题" prop="sectionTitle">
|
||||
<el-input v-model="form.sectionTitle" maxlength="200" show-word-limit placeholder="后台识别用,如:招商加盟-加盟细则" />
|
||||
</el-form-item>
|
||||
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="card-header">页顶横幅</div>
|
||||
<el-form-item label="顶部横幅图" prop="bannerImage">
|
||||
<image-upload v-model="form.bannerImage" :limit="1" />
|
||||
<p class="form-tip">对应整页最上方通栏视觉(握手/品牌图等)。</p>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="card-header">加盟优势 + 右侧配图</div>
|
||||
<el-form-item label="右侧主图">
|
||||
<image-upload v-model="form.contentImage1" :limit="1" />
|
||||
<p class="form-tip">与左侧红色「加盟优势」文案并排展示的主图(如菜品图)。</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="优势区标题">
|
||||
<el-input v-model="joinRulesDetail.advantageTitle" maxlength="100" show-word-limit placeholder="默认:加盟优势" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优势区正文">
|
||||
<editor v-model="joinRulesDetail.advantageBody" :min-height="220" />
|
||||
<p class="form-tip">支持列表、加粗等;门户左侧红底区域内容。</p>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="block-card">
|
||||
<div slot="header" class="card-header">下方双栏说明</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="左栏标题">
|
||||
<el-input v-model="joinRulesDetail.col1Title" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="左栏正文">
|
||||
<editor v-model="joinRulesDetail.col1Body" :min-height="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="右栏标题">
|
||||
<el-input v-model="joinRulesDetail.col2Title" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="右栏正文">
|
||||
<editor v-model="joinRulesDetail.col2Body" :min-height="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="submitForm" v-hasPermi="['huayang:joinPageContent:edit']">保存当前标签</el-button>
|
||||
<el-button @click="loadCurrent" v-hasPermi="['huayang:joinPageContent:query']">重新加载</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 加盟流程 / 加盟资质:沿用整块富文本 + 多图 -->
|
||||
<el-form v-else ref="form" :model="form" :rules="genericFormRules" label-width="120px" class="join-form">
|
||||
<el-form-item label="板块标题" prop="sectionTitle">
|
||||
<el-input v-model="form.sectionTitle" maxlength="200" show-word-limit placeholder="后台列表与识别用标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="正文内容" prop="sectionContent">
|
||||
<editor v-model="form.sectionContent" :min-height="320" />
|
||||
<p class="form-tip">
|
||||
{{
|
||||
activeTab === 'join_process'
|
||||
? '可排版标题、列表等;保存后门户「加盟流程」区块展示。本页仅维护正文与配图一。'
|
||||
: activeTab === 'join_qualification'
|
||||
? '可排版标题、列表等;保存后门户「加盟资质」区块展示。不含顶部横幅与配图三,请用正文与配图一、二排版。'
|
||||
: '可排版标题、列表等;保存后由门户端按标识码注入对应区域。'
|
||||
}}
|
||||
</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="配图一">
|
||||
<image-upload v-model="form.contentImage1" :limit="1" />
|
||||
</el-form-item>
|
||||
<template v-if="activeTab === 'join_qualification'">
|
||||
<el-form-item label="配图二">
|
||||
<image-upload v-model="form.contentImage2" :limit="1" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" @click="submitForm" v-hasPermi="['huayang:joinPageContent:edit']">保存当前标签</el-button>
|
||||
<el-button @click="loadCurrent" v-hasPermi="['huayang:joinPageContent:query']">重新加载</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getCompanySectionByCode,
|
||||
addCompanySection,
|
||||
updateCompanySection
|
||||
} from '@/api/huayang/companySection'
|
||||
|
||||
const TAB_META = {
|
||||
join_rules: { title: '招商加盟-加盟细则', sortOrder: 80 },
|
||||
join_process: { title: '招商加盟-加盟流程', sortOrder: 81 },
|
||||
join_qualification: { title: '招商加盟-加盟资质', sortOrder: 82 }
|
||||
}
|
||||
|
||||
const JOIN_RULES_JSON_VERSION = 1
|
||||
|
||||
function stripHtml(html) {
|
||||
return (html || '').replace(/<[^>]+>/g, '').replace(/ /gi, ' ').trim()
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'HuayangJoinPageContent',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
return {
|
||||
defaultAlert:
|
||||
'维护门户「招商加盟」页三个子块。流程/资质支持整块富文本与配图;数据保存在公司简介板块表(固定 section_code)。',
|
||||
joinRulesAlert:
|
||||
'「加盟细则」拆分为:页顶横幅图、加盟优势区(标题+正文)与右侧主图、下方双栏(各标题+正文)。保存时同时写入结构化 JSON(extraData)与合并 HTML(sectionContent),便于门户读取。',
|
||||
activeTab: 'join_rules',
|
||||
saving: false,
|
||||
form: {},
|
||||
joinRulesDetail: {
|
||||
advantageTitle: '加盟优势',
|
||||
advantageBody: '<p></p>',
|
||||
col1Title: '互动直分销模式解读——互动',
|
||||
col1Body: '<p></p>',
|
||||
col2Title: '互动直分销模式解读——分销',
|
||||
col2Body: '<p></p>'
|
||||
},
|
||||
genericFormRules: {
|
||||
sectionTitle: [{ required: true, message: '板块标题不能为空', trigger: 'blur' }],
|
||||
sectionContent: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
if (!stripHtml(value)) {
|
||||
callback(new Error('正文不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
},
|
||||
joinRulesFormRules: {
|
||||
sectionTitle: [{ required: true, message: '板块标题不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.resetFormShell()
|
||||
this.loadCurrent()
|
||||
},
|
||||
methods: {
|
||||
defaultJoinRulesDetail() {
|
||||
return {
|
||||
advantageTitle: '加盟优势',
|
||||
advantageBody: '<p></p>',
|
||||
col1Title: '互动直分销模式解读——互动',
|
||||
col1Body: '<p></p>',
|
||||
col2Title: '互动直分销模式解读——分销',
|
||||
col2Body: '<p></p>'
|
||||
}
|
||||
},
|
||||
onTabClick() {
|
||||
this.loadCurrent()
|
||||
},
|
||||
resetFormShell() {
|
||||
this.joinRulesDetail = this.defaultJoinRulesDetail()
|
||||
this.form = {
|
||||
sectionId: undefined,
|
||||
sectionCode: this.activeTab,
|
||||
sectionTitle: (TAB_META[this.activeTab] && TAB_META[this.activeTab].title) || '',
|
||||
sectionContent: '<p></p>',
|
||||
extraData: '',
|
||||
bannerImage: '',
|
||||
contentImage1: '',
|
||||
contentImage2: '',
|
||||
contentImage3: '',
|
||||
sortOrder: (TAB_META[this.activeTab] && TAB_META[this.activeTab].sortOrder) || 0,
|
||||
status: '0'
|
||||
}
|
||||
},
|
||||
normalizeSection(d) {
|
||||
if (!d) return {}
|
||||
const pick = (camel, snake) => {
|
||||
const v = d[camel] !== undefined && d[camel] !== null ? d[camel] : d[snake]
|
||||
return v === undefined || v === null ? '' : String(v)
|
||||
}
|
||||
return {
|
||||
...d,
|
||||
extraData: pick('extraData', 'extra_data'),
|
||||
bannerImage: pick('bannerImage', 'banner_image'),
|
||||
contentImage1: pick('contentImage1', 'content_image1'),
|
||||
contentImage2: pick('contentImage2', 'content_image2'),
|
||||
contentImage3: pick('contentImage3', 'content_image3')
|
||||
}
|
||||
},
|
||||
applyJoinRulesFromRow(extraDataRaw, legacySectionContent) {
|
||||
const base = this.defaultJoinRulesDetail()
|
||||
if (extraDataRaw && String(extraDataRaw).trim()) {
|
||||
try {
|
||||
const o = JSON.parse(extraDataRaw)
|
||||
if (o && typeof o === 'object') {
|
||||
if (o.advantageTitle != null) base.advantageTitle = String(o.advantageTitle)
|
||||
if (o.advantageBody != null) base.advantageBody = String(o.advantageBody)
|
||||
if (o.col1Title != null) base.col1Title = String(o.col1Title)
|
||||
if (o.col1Body != null) base.col1Body = String(o.col1Body)
|
||||
if (o.col2Title != null) base.col2Title = String(o.col2Title)
|
||||
if (o.col2Body != null) base.col2Body = String(o.col2Body)
|
||||
}
|
||||
} catch (e) {
|
||||
/* 保持默认 */
|
||||
}
|
||||
} else if (stripHtml(legacySectionContent)) {
|
||||
base.advantageBody = legacySectionContent
|
||||
}
|
||||
this.joinRulesDetail = base
|
||||
},
|
||||
escapeHtmlTitle(s) {
|
||||
return String(s || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/"/g, '"')
|
||||
},
|
||||
buildJoinRulesMergedHtml(d) {
|
||||
const t1 = this.escapeHtmlTitle(d.advantageTitle)
|
||||
const h1 = this.escapeHtmlTitle(d.col1Title)
|
||||
const h2 = this.escapeHtmlTitle(d.col2Title)
|
||||
return (
|
||||
`<div class="hy-join-rules" data-hy-join-rules="1">` +
|
||||
`<section class="hy-adv-block"><h3 class="hy-adv-title">${t1}</h3>` +
|
||||
`<div class="hy-adv-body">${d.advantageBody || ''}</div></section>` +
|
||||
`<div class="hy-two-col"><div class="hy-col hy-col1"><h4 class="hy-col-title">${h1}</h4>` +
|
||||
`<div class="hy-col-body">${d.col1Body || ''}</div></div>` +
|
||||
`<div class="hy-col hy-col2"><h4 class="hy-col-title">${h2}</h4>` +
|
||||
`<div class="hy-col-body">${d.col2Body || ''}</div></div></div></div>`
|
||||
)
|
||||
},
|
||||
buildJoinRulesExtraJson() {
|
||||
return JSON.stringify({
|
||||
version: JOIN_RULES_JSON_VERSION,
|
||||
advantageTitle: this.joinRulesDetail.advantageTitle,
|
||||
advantageBody: this.joinRulesDetail.advantageBody,
|
||||
col1Title: this.joinRulesDetail.col1Title,
|
||||
col1Body: this.joinRulesDetail.col1Body,
|
||||
col2Title: this.joinRulesDetail.col2Title,
|
||||
col2Body: this.joinRulesDetail.col2Body
|
||||
})
|
||||
},
|
||||
loadCurrent() {
|
||||
const code = this.activeTab
|
||||
this.resetFormShell()
|
||||
this.form.sectionCode = code
|
||||
this.form.sortOrder = (TAB_META[code] && TAB_META[code].sortOrder) || 0
|
||||
this.form.sectionTitle = (TAB_META[code] && TAB_META[code].title) || code
|
||||
getCompanySectionByCode(code)
|
||||
.then((res) => {
|
||||
const row = res && res.data
|
||||
if (row && row.sectionId != null) {
|
||||
this.form = this.normalizeSection(row)
|
||||
this.form.sectionCode = code
|
||||
if (code === 'join_rules') {
|
||||
this.applyJoinRulesFromRow(this.form.extraData, this.form.sectionContent)
|
||||
}
|
||||
} else {
|
||||
this.form.sectionCode = code
|
||||
this.form.sectionTitle = (TAB_META[code] && TAB_META[code].title) || code
|
||||
this.form.sectionContent = '<p></p>'
|
||||
this.form.sortOrder = (TAB_META[code] && TAB_META[code].sortOrder) || 0
|
||||
if (code === 'join_rules') {
|
||||
this.joinRulesDetail = this.defaultJoinRulesDetail()
|
||||
}
|
||||
}
|
||||
if (code === 'join_process') {
|
||||
this.form.bannerImage = ''
|
||||
this.form.contentImage2 = ''
|
||||
this.form.contentImage3 = ''
|
||||
}
|
||||
if (code === 'join_qualification') {
|
||||
this.form.bannerImage = ''
|
||||
this.form.contentImage3 = ''
|
||||
}
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
})
|
||||
.catch(() => {
|
||||
this.form.sectionCode = code
|
||||
this.form.sectionTitle = (TAB_META[code] && TAB_META[code].title) || code
|
||||
this.form.sectionContent = '<p></p>'
|
||||
this.form.sortOrder = (TAB_META[code] && TAB_META[code].sortOrder) || 0
|
||||
if (code === 'join_rules') {
|
||||
this.joinRulesDetail = this.defaultJoinRulesDetail()
|
||||
}
|
||||
if (code === 'join_process') {
|
||||
this.form.bannerImage = ''
|
||||
this.form.contentImage2 = ''
|
||||
this.form.contentImage3 = ''
|
||||
}
|
||||
if (code === 'join_qualification') {
|
||||
this.form.bannerImage = ''
|
||||
this.form.contentImage3 = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
if (this.activeTab === 'join_rules') {
|
||||
const ok =
|
||||
stripHtml(this.joinRulesDetail.advantageBody) ||
|
||||
stripHtml(this.joinRulesDetail.col1Body) ||
|
||||
stripHtml(this.joinRulesDetail.col2Body)
|
||||
if (!ok) {
|
||||
this.$modal.msgError('请填写加盟优势正文,或下方至少一栏正文')
|
||||
return
|
||||
}
|
||||
}
|
||||
this.saving = true
|
||||
let payload = {
|
||||
...this.form,
|
||||
sectionCode: this.activeTab,
|
||||
bannerImage: this.form.bannerImage == null ? '' : String(this.form.bannerImage),
|
||||
contentImage1: this.form.contentImage1 == null ? '' : String(this.form.contentImage1),
|
||||
contentImage2: this.form.contentImage2 == null ? '' : String(this.form.contentImage2),
|
||||
contentImage3: this.form.contentImage3 == null ? '' : String(this.form.contentImage3)
|
||||
}
|
||||
if (this.activeTab === 'join_rules') {
|
||||
payload.extraData = this.buildJoinRulesExtraJson()
|
||||
payload.sectionContent = this.buildJoinRulesMergedHtml(this.joinRulesDetail)
|
||||
payload.contentImage2 = ''
|
||||
payload.contentImage3 = ''
|
||||
} else {
|
||||
payload.extraData = this.form.extraData && String(this.form.extraData).trim() ? this.form.extraData : null
|
||||
}
|
||||
if (this.activeTab === 'join_process') {
|
||||
payload.bannerImage = ''
|
||||
payload.contentImage2 = ''
|
||||
payload.contentImage3 = ''
|
||||
}
|
||||
if (this.activeTab === 'join_qualification') {
|
||||
payload.bannerImage = ''
|
||||
payload.contentImage3 = ''
|
||||
}
|
||||
const done = () => {
|
||||
this.saving = false
|
||||
}
|
||||
if (this.form.sectionId) {
|
||||
updateCompanySection(payload)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.loadCurrent()
|
||||
})
|
||||
.finally(done)
|
||||
} else {
|
||||
addCompanySection(payload)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('已新增并保存')
|
||||
this.loadCurrent()
|
||||
})
|
||||
.finally(done)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.join-form {
|
||||
max-width: 1040px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.form-tip {
|
||||
margin: 6px 0 0;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.block-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@ -1,58 +1,34 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="语言列表" name="languages">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddLang">新增语言</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table v-loading="loading" :data="languageList">
|
||||
<el-table-column label="编号" align="center" prop="langId" width="80" />
|
||||
<el-table-column label="语言名称" align="center" prop="langName" />
|
||||
<el-table-column label="语言代码" align="center" prop="langCode" />
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '显示' : '隐藏' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdateLang(scope.row)">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteLang(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-alert
|
||||
title="已停用 i18n 翻译词典,仅保留语言列表配置。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
/>
|
||||
<el-table v-loading="loading" :data="languageList">
|
||||
<el-table-column label="语言名称" align="center" prop="langName" />
|
||||
<el-table-column label="语言代码" align="center" prop="langCode" />
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '显示' : '隐藏' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" align="center" prop="isDefault" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isDefault === '1' ? 'warning' : 'info'">{{ scope.row.isDefault === '1' ? '默认' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdateLang(scope.row)" v-hasPermi="['huayang:language:edit']">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-check" :disabled="scope.row.isDefault === '1'" @click="handleSetDefault(scope.row)" v-hasPermi="['huayang:language:edit']">设为默认</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-tab-pane label="翻译词典" name="translations">
|
||||
<el-form :inline="true" size="small">
|
||||
<el-form-item label="语言">
|
||||
<el-select v-model="currentLangCode" placeholder="请选择语言" @change="getI18nList">
|
||||
<el-option v-for="l in languageList" :key="l.langCode" :label="l.langName" :value="l.langCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="i18nSearchKey" placeholder="输入Key进行搜索" clearable @input="filterI18n" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="mini" @click="handleSaveAllI18n">保存当前页修改</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="filteredI18nList" border stripe>
|
||||
<el-table-column label="翻译键位 (Key)" prop="i18nKey" width="250" />
|
||||
<el-table-column label="翻译内容 (Value)">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.i18nValue" size="small" placeholder="请输入内容" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 语言编辑弹窗 -->
|
||||
<el-dialog :title="langTitle" :visible.sync="langOpen" width="400px" append-to-body>
|
||||
<el-form ref="langForm" :model="langForm" :rules="langRules" label-width="80px">
|
||||
<el-form-item label="名称" prop="langName">
|
||||
@ -73,18 +49,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 这里假设你已有对应的 api/huayang/language.js 和 api/huayang/i18n.js
|
||||
import { listLanguage, updateLanguage, setDefaultLanguage } from "@/api/huayang/language";
|
||||
|
||||
export default {
|
||||
name: "HuayangLanguage",
|
||||
data() {
|
||||
return {
|
||||
activeTab: "languages",
|
||||
loading: false,
|
||||
languageList: [],
|
||||
i18nList: [],
|
||||
filteredI18nList: [],
|
||||
currentLangCode: "zh_cn",
|
||||
i18nSearchKey: "",
|
||||
langOpen: false,
|
||||
langTitle: "",
|
||||
langForm: {},
|
||||
@ -98,52 +70,39 @@ export default {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
async getList() {
|
||||
this.loading = true;
|
||||
// 模拟请求
|
||||
setTimeout(() => {
|
||||
this.languageList = [
|
||||
{ langId: 1, langName: "简体中文", langCode: "zh_cn", status: "0", sortOrder: 1 },
|
||||
{ langId: 2, langName: "English", langCode: "en_us", status: "0", sortOrder: 2 },
|
||||
{ langId: 3, langName: "日本語", langCode: "ja_jp", status: "0", sortOrder: 3 }
|
||||
];
|
||||
try {
|
||||
const { rows } = await listLanguage({});
|
||||
this.languageList = Array.isArray(rows) ? rows : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.getI18nList();
|
||||
}, 500);
|
||||
},
|
||||
getI18nList() {
|
||||
// 获取翻译列表逻辑...
|
||||
this.i18nList = [
|
||||
{ i18nKey: "common.title.home", i18nValue: this.currentLangCode === 'zh_cn' ? '首页' : 'Home' },
|
||||
{ i18nKey: "common.button.submit", i18nValue: this.currentLangCode === 'zh_cn' ? '提交' : 'Submit' },
|
||||
{ i18nKey: "common.placeholder.phone", i18nValue: this.currentLangCode === 'zh_cn' ? '请输入电话' : 'Enter Phone' }
|
||||
];
|
||||
this.filterI18n();
|
||||
},
|
||||
filterI18n() {
|
||||
if (!this.i18nSearchKey) {
|
||||
this.filteredI18nList = this.i18nList;
|
||||
} else {
|
||||
this.filteredI18nList = this.i18nList.filter(i => i.i18nKey.includes(this.i18nSearchKey));
|
||||
}
|
||||
},
|
||||
handleSaveAllI18n() {
|
||||
this.$modal.msgSuccess("保存成功 (模拟)");
|
||||
},
|
||||
handleAddLang() {
|
||||
this.langForm = { sortOrder: 0 };
|
||||
this.langOpen = true;
|
||||
this.langTitle = "新增语言";
|
||||
},
|
||||
handleUpdateLang(row) {
|
||||
this.langForm = { ...row };
|
||||
this.langOpen = true;
|
||||
this.langTitle = "修改语言";
|
||||
},
|
||||
submitLangForm() {
|
||||
this.$modal.msgSuccess("操作成功");
|
||||
async submitLangForm() {
|
||||
await this.$refs["langForm"].validate();
|
||||
await updateLanguage(this.langForm);
|
||||
this.$modal.msgSuccess("更新成功");
|
||||
this.langOpen = false;
|
||||
await this.getList();
|
||||
},
|
||||
async handleSetDefault(row) {
|
||||
await this.$modal.confirm(`确认将 ${row.langName} 设为默认语言吗?`);
|
||||
await setDefaultLanguage(row.langCode);
|
||||
this.$modal.msgSuccess("设置成功");
|
||||
await this.getList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="Logo类型" prop="logoType">
|
||||
<el-select v-model="queryParams.logoType" placeholder="Logo类型" clearable>
|
||||
<el-option label="头部Logo" value="header" />
|
||||
<el-option label="底部Logo" value="footer" />
|
||||
<el-option label="版权信息" value="copyright" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="isEnabled">
|
||||
<el-select v-model="queryParams.isEnabled" placeholder="状态" clearable>
|
||||
<el-option label="启用" value="1" />
|
||||
@ -48,13 +41,6 @@
|
||||
<el-table v-loading="loading" :data="logoList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="Logo标题" align="center" prop="logoTitle" width="150" />
|
||||
<el-table-column label="类型" align="center" prop="logoType" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.logoType === 'header'" type="primary">头部Logo</el-tag>
|
||||
<el-tag v-else-if="scope.row.logoType === 'footer'" type="info">底部Logo</el-tag>
|
||||
<el-tag v-else type="success">版权信息</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Logo预览" align="center" prop="logoImage" width="200">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
@ -114,13 +100,6 @@
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="Logo类型" prop="logoType">
|
||||
<el-radio-group v-model="form.logoType">
|
||||
<el-radio label="header">头部Logo</el-radio>
|
||||
<el-radio label="footer">底部Logo</el-radio>
|
||||
<el-radio label="copyright">版权信息</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="form.logoType === 'copyright' ? '版权内容' : 'Logo标题'" prop="logoTitle">
|
||||
<el-input
|
||||
v-model="form.logoTitle"
|
||||
@ -130,7 +109,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.logoType !== 'copyright'" label="Logo图片" prop="logoImage">
|
||||
<image-upload v-model="form.logoImage" :limit="1" />
|
||||
<image-upload v-model="form.logoImage" :limit="1" spec="站点 Logo,建议透明底 PNG/SVG,宽约 240px,高 60-100px;避免留白过多" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="logoUrl">
|
||||
<el-input v-model="form.logoUrl" placeholder="请输入点击跳转链接,留空则跳转到首页" />
|
||||
@ -176,12 +155,10 @@ export default {
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
logoType: null,
|
||||
isEnabled: null
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
logoType: [{ required: true, message: 'Logo类型不能为空', trigger: 'change' }],
|
||||
logoTitle: [{ required: true, message: 'Logo标题不能为空', trigger: 'blur' }],
|
||||
isEnabled: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
@ -239,6 +216,7 @@ export default {
|
||||
const logoId = row.logoId || this.ids
|
||||
getLogo(logoId).then(response => {
|
||||
this.form = response.data
|
||||
if (!this.form.logoType || this.form.logoType === 'footer') this.form.logoType = 'header'
|
||||
this.open = true
|
||||
this.title = '修改Logo配置'
|
||||
})
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
>保存背景</el-button>
|
||||
</div>
|
||||
<p class="nav-left-bg-card__tip">配置键 <code>nav_menu_left_bg</code>。仅作用于门户「菜单列表 + 右侧配图」区域;左侧头像/Menu 窄条保持透明。填 <code>transparent</code> 表示该区域无背景图。各菜单项大图请在对应一级导航的「侧栏配图」维护。</p>
|
||||
<image-upload v-model="navLeftBgConfig.configValue" :limit="1" />
|
||||
<image-upload v-model="navLeftBgConfig.configValue" :limit="1" spec="主导航展开区背景,建议 900×760px 或接近菜单展开区域比例,画面不宜过亮" />
|
||||
</el-card>
|
||||
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
@ -136,29 +136,25 @@
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="720px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="导航类型" prop="navType">
|
||||
<el-form-item v-if="!form.navId" label="导航类型" prop="navType">
|
||||
<el-radio-group v-model="form.navType" @change="handleNavTypeChange">
|
||||
<el-radio label="main">主导航</el-radio>
|
||||
<el-radio label="footer">底部导航</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="导航编码" prop="navCode">
|
||||
<el-form-item v-if="!form.navId" label="导航编码" prop="navCode">
|
||||
<el-input v-model="form.navCode" placeholder="请输入导航编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="导航标题" prop="navTitle">
|
||||
<el-input v-model="form.navTitle" placeholder="请输入导航标题" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="链接地址" prop="navUrl">
|
||||
<el-form-item v-if="!form.navId" label="链接地址" prop="navUrl">
|
||||
<el-input v-model="form.navUrl" placeholder="请输入链接地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="navIcon">
|
||||
<image-upload v-model="form.navIcon" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.navType === 'main' && Number(form.parentId) === 0" label="侧栏配图" prop="navCover">
|
||||
<image-upload v-model="form.navCover" :limit="1" />
|
||||
<image-upload v-model="form.navCover" :limit="1" spec="一级导航右侧大图,建议 720×520px 或 4:3,主体居中,单张不超过 5MB" />
|
||||
<div class="form-item-tip">与门户模板中主导航一级项顺序对应,展示在菜单展开区右侧大图位。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="父级" prop="parentId">
|
||||
<el-form-item v-if="!form.navId" label="父级" prop="parentId">
|
||||
<el-select v-model="form.parentId" placeholder="请选择父级导航" clearable @change="handleParentChange">
|
||||
<el-option label="顶级导航" :value="0" />
|
||||
<el-option
|
||||
@ -172,7 +168,7 @@
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新窗口" prop="isBlank">
|
||||
<el-form-item v-if="!form.navId" label="新窗口" prop="isBlank">
|
||||
<el-radio-group v-model="form.isBlank">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
@ -187,7 +183,7 @@
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-form-item v-if="!form.navId" label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -1,14 +1,5 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
v-hasPermi="['huayang:navigation:list']"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb16"
|
||||
title="页脚维护入口"
|
||||
description="若需在同一处维护底部多列链接、Logo/版权、联系方式与站点参数(备案、SEO、页脚红条文案等),请使用菜单「导航与布局 → 页脚一站式配置」。Logo 请通过本模块下的「Logo配置」二级菜单维护。"
|
||||
/>
|
||||
<navigation-manage />
|
||||
</div>
|
||||
</template>
|
||||
@ -23,9 +14,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,358 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="新闻标题" prop="newsTitle">
|
||||
<el-input
|
||||
v-model="queryParams.newsTitle"
|
||||
placeholder="请输入新闻标题"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新闻类型" prop="newsType">
|
||||
<el-select v-model="queryParams.newsType" placeholder="新闻类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.huayang_news_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['huayang:news:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['huayang:news:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['huayang:news:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="newsList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="编号" align="center" prop="newsId" width="80" />
|
||||
<el-table-column label="新闻标题" align="center" prop="newsTitle" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="类型" align="center" prop="newsType" width="120">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.huayang_news_type" :value="scope.row.newsType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="封面图片" align="center" prop="newsCover" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
v-if="scope.row.newsCover"
|
||||
style="width: 60px; height: 60px"
|
||||
:src="resolveImageUrl(scope.row.newsCover)"
|
||||
fit="cover"
|
||||
:preview-src-list="[resolveImageUrl(scope.row.newsCover)]"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="作者" align="center" prop="newsAuthor" width="100" />
|
||||
<el-table-column label="发布时间" align="center" prop="publishTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.publishTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="阅读数" align="center" prop="readCount" width="80" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="首页推荐" align="center" prop="isHome" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.isHome"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
@change="handleIsHomeChange(scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['huayang:news:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['huayang:news:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="新闻标题" prop="newsTitle">
|
||||
<el-input v-model="form.newsTitle" maxlength="200" show-word-limit placeholder="请输入新闻标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新闻类型" prop="newsType">
|
||||
<el-select v-model="form.newsType" placeholder="请选择新闻类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in dict.type.huayang_news_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="newsAuthor">
|
||||
<el-input v-model="form.newsAuthor" maxlength="100" placeholder="请输入作者" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布时间" prop="publishTime">
|
||||
<el-date-picker
|
||||
v-model="form.publishTime"
|
||||
type="datetime"
|
||||
placeholder="选择发布时间"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="首页推荐" prop="isHome">
|
||||
<el-radio-group v-model="form.isHome">
|
||||
<el-radio label="1">是</el-radio>
|
||||
<el-radio label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in dict.type.sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图片" prop="newsCover">
|
||||
<image-upload v-model="form.newsCover" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新闻内容" prop="newsContent">
|
||||
<editor v-model="form.newsContent" :min-height="280" :enable-media-choice="true" :video-file-size="100" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listNews, getNews, addNews, updateNews, deleteNews, exportNews } from '@/api/huayang/news'
|
||||
|
||||
export default {
|
||||
name: 'HuayangNews',
|
||||
dicts: ['sys_normal_disable', 'huayang_news_type'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
showSearch: true,
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
total: 0,
|
||||
newsList: [],
|
||||
title: '',
|
||||
open: false,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
newsTitle: undefined,
|
||||
newsType: undefined,
|
||||
status: undefined,
|
||||
isHome: undefined
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
newsTitle: [{ required: true, message: '新闻标题不能为空', trigger: 'blur' }],
|
||||
newsType: [{ required: true, message: '新闻类型不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
resolveImageUrl(raw) {
|
||||
if (!raw) return ''
|
||||
const value = String(raw).trim()
|
||||
if (!value) return ''
|
||||
if (/^https?:\/\//i.test(value)) return value
|
||||
const baseApi = process.env.VUE_APP_BASE_API || ''
|
||||
const baseOrigin = baseApi ? baseApi.replace(/\/api\/?$/, '') : ''
|
||||
if (value.startsWith('/assets/')) return value
|
||||
if (value.startsWith('/profile/')) return `${baseOrigin}${value}`
|
||||
if (value.startsWith('profile/')) return `${baseOrigin}/${value}`
|
||||
if (value.startsWith('/')) return `${baseOrigin}${value}`
|
||||
return `${baseOrigin}/${value}`
|
||||
},
|
||||
handleIsHomeChange(row) {
|
||||
let text = row.isHome === "1" ? "启用" : "停用";
|
||||
this.$modal.confirm('确认要"' + text + '""' + row.newsTitle + '"的首页推荐吗?').then(function() {
|
||||
return updateNews({ newsId: row.newsId, isHome: row.isHome });
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess(text + "成功");
|
||||
}).catch(function() {
|
||||
row.isHome = row.isHome === "1" ? "0" : "1";
|
||||
});
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listNews(this.queryParams).then((response) => {
|
||||
this.newsList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.newsId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.form = {
|
||||
newsId: undefined,
|
||||
newsTitle: undefined,
|
||||
newsContent: undefined,
|
||||
newsType: undefined,
|
||||
newsCover: undefined,
|
||||
newsAuthor: undefined,
|
||||
publishTime: undefined,
|
||||
sortOrder: 0,
|
||||
status: '0',
|
||||
isHome: '0'
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '添加新闻'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const newsId = row.newsId || this.ids[0]
|
||||
getNews(newsId).then((response) => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = '修改新闻'
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const newsIds = row.newsId || this.ids
|
||||
this.$modal.confirm('是否确认删除新闻编号为"' + newsIds + '"的数据项?').then(() => {
|
||||
return deleteNews(newsIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleExport() {
|
||||
exportNews(this.queryParams).then((response) => {
|
||||
const blob = new Blob([response], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = '新闻数据_' + this.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}') + '.xlsx'
|
||||
link.click()
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.form.newsId != undefined) {
|
||||
updateNews(this.form).then(() => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addNews(this.form).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,192 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="此处维护「新闻类型」字典(huayang_news_type)。字典键值将写入新闻数据的 newsType 字段;修改键值可能影响已发布新闻的分类归属,请谨慎操作。"
|
||||
/>
|
||||
<el-alert
|
||||
v-hasPermi="['huayang:navigation:list']"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="门户新闻页底部导航栏请在「页脚一站式配置」或「导航管理(底部导航)」中维护。"
|
||||
/>
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="类型标签" prop="dictLabel">
|
||||
<el-input v-model="queryParams.dictLabel" placeholder="字典标签" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable>
|
||||
<el-option v-for="d in dict.type.sys_normal_disable" :key="d.value" :label="d.label" :value="d.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['huayang:newsCategory:add']">新增</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" border>
|
||||
<el-table-column label="编码" prop="dictCode" width="88" align="center" />
|
||||
<el-table-column label="标签" prop="dictLabel" min-width="120" />
|
||||
<el-table-column label="键值" prop="dictValue" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="排序" prop="dictSort" width="80" align="center" />
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="160" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['huayang:newsCategory:edit']">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['huayang:newsCategory:remove']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<el-dialog :title="dialogTitle" :visible.sync="open" width="560px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="类型标签" prop="dictLabel">
|
||||
<el-input v-model="form.dictLabel" maxlength="100" placeholder="前台展示名称,如:企业资讯" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型键值" prop="dictValue">
|
||||
<el-input v-model="form.dictValue" maxlength="100" placeholder="英文键值,如:news(勿随意修改已有数据)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="dictSort">
|
||||
<el-input-number v-model="form.dictSort" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="d in dict.type.sys_normal_disable" :key="d.value" :label="d.value">{{ d.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="open = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listNewsCategory, getNewsCategory, addNewsCategory, updateNewsCategory, delNewsCategory } from '@/api/huayang/newsCategory'
|
||||
|
||||
export default {
|
||||
name: 'HuayangNewsCategory',
|
||||
dicts: ['sys_normal_disable'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
dataList: [],
|
||||
open: false,
|
||||
dialogTitle: '',
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
dictLabel: undefined,
|
||||
status: undefined
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
dictLabel: [{ required: true, message: '类型标签不能为空', trigger: 'blur' }],
|
||||
dictValue: [{ required: true, message: '类型键值不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
listNewsCategory(this.queryParams).then((res) => {
|
||||
this.dataList = res.rows
|
||||
this.total = res.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
resetFormData() {
|
||||
this.form = {
|
||||
dictCode: undefined,
|
||||
dictLabel: undefined,
|
||||
dictValue: undefined,
|
||||
dictSort: 0,
|
||||
status: '0',
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleAdd() {
|
||||
this.resetFormData()
|
||||
this.open = true
|
||||
this.dialogTitle = '新增新闻类型'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.resetFormData()
|
||||
getNewsCategory(row.dictCode).then((res) => {
|
||||
this.form = res.data
|
||||
if (this.form.dictSort == null) this.form.dictSort = 0
|
||||
this.open = true
|
||||
this.dialogTitle = '修改新闻类型'
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$modal.confirm('是否确认删除「' + row.dictLabel + '」?若已有新闻使用该类型,请先调整新闻类型再删除。').then(() => {
|
||||
return delNewsCategory(row.dictCode)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
const req = this.form.dictCode ? updateNewsCategory : addNewsCategory
|
||||
req(this.form).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<div slot="header">新闻动态 · 页顶横幅图</div>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="下方第一项为「新闻动态」独立页顶通栏图;第二项为官网首页「华阳新闻」整屏区块背景。保存后前台强刷首页即可看到背景(接口为 no-store)。"
|
||||
/>
|
||||
<el-form label-width="140px">
|
||||
<el-form-item label="新闻页横幅图">
|
||||
<image-upload v-model="form.backgroundImage" :limit="1" />
|
||||
<div class="field-tip">与新闻动态页顶部通栏一致;保存时会同步更新首页顶部轮播第一张图</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="首页新闻区背景">
|
||||
<image-upload v-model="form.homeNewsBackgroundImage" :limit="1" />
|
||||
<div class="field-tip">仅影响首页「华阳新闻」所在整屏 slide 的 background(cover / center)</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit" v-hasPermi="['huayang:newsBanner:edit']">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getNewsPageBanner, updateNewsPageBanner } from '@/api/huayang/newsPageBanner'
|
||||
|
||||
export default {
|
||||
name: 'HuayangNewsPageBanner',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
backgroundImage: '',
|
||||
homeNewsBackgroundImage: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getNewsPageBanner().then((res) => {
|
||||
const d = res.data || {}
|
||||
this.form = {
|
||||
backgroundImage: d.backgroundImage || '',
|
||||
homeNewsBackgroundImage: d.homeNewsBackgroundImage || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
normalizeSingleImage(value) {
|
||||
if (!value || typeof value !== 'string') return ''
|
||||
return value.split(',').map((s) => s.trim()).filter(Boolean)[0] || ''
|
||||
},
|
||||
submit() {
|
||||
const bg = this.normalizeSingleImage(this.form.backgroundImage)
|
||||
const homeNewsBg = this.normalizeSingleImage(this.form.homeNewsBackgroundImage)
|
||||
updateNewsPageBanner({
|
||||
backgroundImage: bg,
|
||||
homeNewsBackgroundImage: homeNewsBg
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.form.backgroundImage = bg
|
||||
this.form.homeNewsBackgroundImage = homeNewsBg
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.field-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
@ -1,30 +1,5 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="mb8" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>产品详情联系方式配置</span>
|
||||
</div>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="detailContactConfig.product_detail_contact_phone" placeholder="4006-535-700" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系邮箱">
|
||||
<el-input v-model="detailContactConfig.product_detail_contact_email" placeholder="haiho@haihofoods.com" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系地址">
|
||||
<el-input
|
||||
v-model="detailContactConfig.product_detail_contact_address"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="中国•烟台•芝罘岛东路51号"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="savingDetailContactConfig" @click="saveDetailContactConfig">保存联系方式配置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input
|
||||
@ -82,16 +57,6 @@
|
||||
v-hasPermi="['huayang:product:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['huayang:product:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
@ -113,12 +78,6 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" align="center" prop="productPrice" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.productPrice">¥{{ scope.row.productPrice }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status" />
|
||||
@ -169,14 +128,6 @@
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="1000px" append-to-body>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb16"
|
||||
title="字段说明(与官网产品详情对应)"
|
||||
description="产品主图:列表封面与详情顶部轮播/大图。产品介绍:富文本,对应详情正文。"
|
||||
/>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="108px">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input v-model="form.productName" maxlength="200" show-word-limit placeholder="请输入产品名称" />
|
||||
@ -191,9 +142,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品价格" prop="productPrice">
|
||||
<el-input-number v-model="form.productPrice" :min="0" :precision="2" controls-position="right" placeholder="请输入产品价格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
</el-form-item>
|
||||
@ -213,7 +161,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品主图" prop="productImages">
|
||||
<image-upload v-model="form.productImages" :limit="8" />
|
||||
<image-upload v-model="form.productImages" :limit="8" spec="产品主图/详情轮播,建议 1000×1000px 或 4:3,白底/浅底,主体居中;最多 8 张" />
|
||||
<div class="form-tip">用于列表缩略图与详情页顶部展示;多张时官网为轮播。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品介绍" prop="productDesc">
|
||||
@ -230,9 +178,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProduct, getProduct, addProduct, updateProduct, deleteProduct, exportProduct } from '@/api/huayang/product'
|
||||
import { listProduct, getProduct, addProduct, updateProduct, deleteProduct } from '@/api/huayang/product'
|
||||
import { listProductCategory } from '@/api/huayang/productCategory'
|
||||
import { listConfig, addConfig, updateConfig } from '@/api/huayang/config'
|
||||
|
||||
export default {
|
||||
name: 'HuayangProduct',
|
||||
@ -247,17 +194,6 @@ export default {
|
||||
total: 0,
|
||||
productList: [],
|
||||
categoryOptions: [],
|
||||
savingDetailContactConfig: false,
|
||||
detailContactConfig: {
|
||||
product_detail_contact_phone: '',
|
||||
product_detail_contact_email: '',
|
||||
product_detail_contact_address: ''
|
||||
},
|
||||
detailContactConfigMeta: {
|
||||
product_detail_contact_phone: null,
|
||||
product_detail_contact_email: null,
|
||||
product_detail_contact_address: null
|
||||
},
|
||||
title: '',
|
||||
open: false,
|
||||
queryParams: {
|
||||
@ -274,7 +210,6 @@ export default {
|
||||
categoryId: undefined,
|
||||
productImages: undefined,
|
||||
productDesc: undefined,
|
||||
productPrice: undefined,
|
||||
sortOrder: 0,
|
||||
status: '0',
|
||||
isHome: '0'
|
||||
@ -289,64 +224,8 @@ export default {
|
||||
created() {
|
||||
this.getList()
|
||||
this.getCategoryOptions()
|
||||
this.getDetailContactConfig()
|
||||
},
|
||||
methods: {
|
||||
async getDetailContactConfig() {
|
||||
const keys = ['product_detail_contact_phone', 'product_detail_contact_email', 'product_detail_contact_address']
|
||||
const rows = await Promise.all(keys.map((key) => this.fetchConfigByKey(key)))
|
||||
rows.forEach((row, idx) => {
|
||||
const key = keys[idx]
|
||||
this.detailContactConfig[key] = row?.configValue || ''
|
||||
this.detailContactConfigMeta[key] = row || null
|
||||
})
|
||||
},
|
||||
async fetchConfigByKey(key) {
|
||||
const response = await listConfig({ pageNum: 1, pageSize: 1, configKey: key })
|
||||
const rows = Array.isArray(response?.rows) ? response.rows : []
|
||||
return rows.find(item => item.configKey === key) || null
|
||||
},
|
||||
async saveDetailContactConfig() {
|
||||
this.savingDetailContactConfig = true
|
||||
try {
|
||||
await this.upsertConfig(
|
||||
'product_detail_contact_phone',
|
||||
'产品详情联系方式-电话',
|
||||
this.detailContactConfig.product_detail_contact_phone,
|
||||
'产品详情页侧栏联系方式中的联系电话'
|
||||
)
|
||||
await this.upsertConfig(
|
||||
'product_detail_contact_email',
|
||||
'产品详情联系方式-邮箱',
|
||||
this.detailContactConfig.product_detail_contact_email,
|
||||
'产品详情页侧栏联系方式中的联系邮箱'
|
||||
)
|
||||
await this.upsertConfig(
|
||||
'product_detail_contact_address',
|
||||
'产品详情联系方式-地址',
|
||||
this.detailContactConfig.product_detail_contact_address,
|
||||
'产品详情页侧栏联系方式中的联系地址'
|
||||
)
|
||||
this.$modal.msgSuccess('联系方式配置保存成功')
|
||||
await this.getDetailContactConfig()
|
||||
} finally {
|
||||
this.savingDetailContactConfig = false
|
||||
}
|
||||
},
|
||||
async upsertConfig(configKey, configName, configValue, remark) {
|
||||
const meta = this.detailContactConfigMeta[configKey]
|
||||
const payload = {
|
||||
configKey,
|
||||
configName,
|
||||
configValue: configValue == null ? '' : String(configValue),
|
||||
remark
|
||||
}
|
||||
if (meta?.configId) {
|
||||
await updateConfig({ ...meta, ...payload })
|
||||
} else {
|
||||
await addConfig(payload)
|
||||
}
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listProduct(this.queryParams).then((response) => {
|
||||
@ -452,7 +331,6 @@ export default {
|
||||
categoryId: undefined,
|
||||
productImages: undefined,
|
||||
productDesc: undefined,
|
||||
productPrice: undefined,
|
||||
sortOrder: 0,
|
||||
status: '0',
|
||||
isHome: '0'
|
||||
@ -505,15 +383,6 @@ export default {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleExport() {
|
||||
exportProduct(this.queryParams).then((response) => {
|
||||
const blob = new Blob([response], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = '产品数据_' + this.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}') + '.xlsx'
|
||||
link.click()
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
|
||||
@ -25,14 +25,6 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-alert
|
||||
v-hasPermi="['huayang:navigation:list']"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb8"
|
||||
title="门户「产品中心」页底部导航栏由「页脚一站式配置」与「导航管理(底部导航)」统一维护,不在本表逐行配置。"
|
||||
/>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
@ -120,7 +112,7 @@
|
||||
<el-input v-model="form.categoryName" maxlength="100" show-word-limit placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类图标" prop="categoryIcon">
|
||||
<image-upload v-model="form.categoryIcon" :limit="1" :drag="false" />
|
||||
<image-upload v-model="form.categoryIcon" :limit="1" :drag="false" spec="产品分类图标,建议 160×160px,透明底 PNG/SVG,单张不超过 1MB" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" :max="9999" controls-position="right" />
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
<el-table-column label="编号" align="center" prop="inquiryId" width="80" />
|
||||
<el-table-column label="咨询人" align="center" prop="inquiryName" width="100" />
|
||||
<el-table-column label="联系电话" align="center" prop="inquiryPhone" width="130" />
|
||||
<el-table-column label="意向产品" align="center" prop="inquiryProduct" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="邮箱" align="center" prop="inquiryProduct" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="咨询内容" align="center" prop="inquiryMessage" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="处理状态" align="center" prop="inquiryStatus" width="100">
|
||||
<template slot-scope="scope">
|
||||
@ -102,7 +102,7 @@
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="咨询人">{{ detailForm.inquiryName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ detailForm.inquiryPhone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="意向产品" :span="2">{{ detailForm.inquiryProduct || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱" :span="2">{{ detailForm.inquiryProduct || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询内容" :span="2">{{ detailForm.inquiryMessage || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="处理状态">
|
||||
<el-tag v-if="detailForm.inquiryStatus === '0'" type="warning">待处理</el-tag>
|
||||
|
||||
@ -1,64 +1,57 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<div slot="header">产品营销中心 · 页顶横幅图</div>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
title="此处设置官网「产品中心」页面顶部通栏大图(导航下方横幅);保存后前台刷新页面即可生效。建议宽幅横图,与现有版式一致。"
|
||||
/>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="横幅图片">
|
||||
<image-upload v-model="form.backgroundImage" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit" v-hasPermi="['huayang:productBanner:edit']">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProductPageBanner, updateProductPageBanner } from '@/api/huayang/productPageBanner'
|
||||
|
||||
export default {
|
||||
name: 'HuayangProductPageBanner',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
backgroundImage: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getProductPageBanner().then((res) => {
|
||||
const d = res.data || {}
|
||||
this.form = {
|
||||
backgroundImage: d.backgroundImage || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
let bg = this.form.backgroundImage
|
||||
if (bg && typeof bg === 'string') {
|
||||
bg = bg.split(',').map((s) => s.trim()).filter(Boolean)[0] || ''
|
||||
}
|
||||
updateProductPageBanner({ backgroundImage: bg }).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.form.backgroundImage = bg
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card>
|
||||
<div slot="header">产品营销中心 · 页顶横幅图</div>
|
||||
<el-form label-width="120px">
|
||||
<el-form-item label="横幅图片">
|
||||
<image-upload v-model="form.backgroundImage" :limit="1" spec="产品中心页顶横幅,建议 1920×520px 或 16:5,主体居中,单张不超过 5MB" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit" v-hasPermi="['huayang:productBanner:edit']">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProductPageBanner, updateProductPageBanner } from '@/api/huayang/productPageBanner'
|
||||
|
||||
export default {
|
||||
name: 'HuayangProductPageBanner',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
backgroundImage: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getProductPageBanner().then((res) => {
|
||||
const d = res.data || {}
|
||||
this.form = {
|
||||
backgroundImage: d.backgroundImage || ''
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
let bg = this.form.backgroundImage
|
||||
if (bg && typeof bg === 'string') {
|
||||
bg = bg.split(',').map((s) => s.trim()).filter(Boolean)[0] || ''
|
||||
}
|
||||
updateProductPageBanner({ backgroundImage: bg }).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.form.backgroundImage = bg
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
278
admin/src/views/huayang/staticText/index.vue
Normal file
278
admin/src/views/huayang/staticText/index.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert
|
||||
title="用于维护无法在业务页面直接编辑的静态文案;建议统一使用 key 命名(如 home.banner.title)。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb12"
|
||||
/>
|
||||
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="语言代码" prop="langCode">
|
||||
<el-select v-model="queryParams.langCode" placeholder="全部语言" clearable filterable style="width: 160px">
|
||||
<el-option v-for="item in languageOptions" :key="item.langCode" :label="item.langName" :value="item.langCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="模块" prop="pageModule">
|
||||
<el-input v-model="queryParams.pageModule" placeholder="如 common / home" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文案Key" prop="i18nKey">
|
||||
<el-input v-model="queryParams.i18nKey" placeholder="请输入 i18nKey" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" prop="onlyStatic">
|
||||
<el-select v-model="queryParams.onlyStatic" placeholder="请选择范围" style="width: 140px">
|
||||
<el-option label="仅静态文案" value="1" />
|
||||
<el-option label="全部文案" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['huayang:translation:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['huayang:translation:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="ID" align="center" prop="i18nId" width="80" />
|
||||
<el-table-column label="语言" align="center" prop="langCode" width="120" />
|
||||
<el-table-column label="模块" align="center" prop="pageModule" width="120" />
|
||||
<el-table-column label="文案Key" align="center" prop="i18nKey" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column label="文案内容" align="center" prop="i18nValue" min-width="260" show-overflow-tooltip />
|
||||
<el-table-column label="状态" align="center" prop="translateStatus" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.translateStatus === '1' ? 'success' : 'info'">
|
||||
{{ scope.row.translateStatus === '1' ? '发布' : '草稿' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['huayang:translation:edit']">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['huayang:translation:remove']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="720px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="语言代码" prop="langCode">
|
||||
<el-select v-model="form.langCode" placeholder="请选择语言" filterable>
|
||||
<el-option v-for="item in languageOptions" :key="item.langCode" :label="item.langName" :value="item.langCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="页面模块" prop="pageModule">
|
||||
<el-input v-model="form.pageModule" placeholder="如 common / home / product" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="文案Key" prop="i18nKey">
|
||||
<el-input v-model="form.i18nKey" placeholder="仅支持字母数字._-,如 home.banner.title" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文案内容" prop="i18nValue">
|
||||
<el-input v-model="form.i18nValue" type="textarea" :rows="5" placeholder="请输入翻译文案内容" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="回退语言" prop="fallbackLangCode">
|
||||
<el-input v-model="form.fallbackLangCode" placeholder="默认 zh_cn" maxlength="20" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发布状态" prop="translateStatus">
|
||||
<el-radio-group v-model="form.translateStatus">
|
||||
<el-radio label="0">草稿</el-radio>
|
||||
<el-radio label="1">发布</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
addTranslationEntry,
|
||||
deleteTranslationEntry,
|
||||
listTranslationEntries,
|
||||
updateTranslationEntry
|
||||
} from '@/api/huayang/translation'
|
||||
import { listLanguage } from '@/api/huayang/language'
|
||||
|
||||
export default {
|
||||
name: 'HuayangStaticText',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
list: [],
|
||||
ids: [],
|
||||
multiple: true,
|
||||
open: false,
|
||||
title: '',
|
||||
languageOptions: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
langCode: undefined,
|
||||
pageModule: undefined,
|
||||
i18nKey: undefined,
|
||||
onlyStatic: '1'
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
langCode: [{ required: true, message: '语言代码不能为空', trigger: 'change' }],
|
||||
pageModule: [{ required: true, message: '页面模块不能为空', trigger: 'blur' }],
|
||||
i18nKey: [
|
||||
{ required: true, message: '文案Key不能为空', trigger: 'blur' },
|
||||
{ pattern: /^[a-zA-Z0-9_.-]{2,100}$/, message: 'key 仅支持字母数字._-,长度2-100', trigger: 'blur' }
|
||||
],
|
||||
i18nValue: [{ required: true, message: '文案内容不能为空', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getLanguageOptions()
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
async getLanguageOptions() {
|
||||
try {
|
||||
const { rows } = await listLanguage({ status: '0', pageNum: 1, pageSize: 100 })
|
||||
this.languageOptions = Array.isArray(rows) ? rows : []
|
||||
} catch (e) {
|
||||
this.languageOptions = []
|
||||
}
|
||||
},
|
||||
async getList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await listTranslationEntries(this.queryParams)
|
||||
this.list = Array.isArray(res.rows) ? res.rows : []
|
||||
this.total = Number(res.total || 0)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.queryParams.onlyStatic = '1'
|
||||
this.handleQuery()
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.i18nId)
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
reset() {
|
||||
this.form = {
|
||||
i18nId: undefined,
|
||||
langCode: '',
|
||||
i18nKey: '',
|
||||
i18nValue: '',
|
||||
pageModule: 'common',
|
||||
fallbackLangCode: 'zh_cn',
|
||||
translateStatus: '1'
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '新增静态文案'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
this.form = { ...row }
|
||||
if (!this.form.fallbackLangCode) {
|
||||
this.form.fallbackLangCode = 'zh_cn'
|
||||
}
|
||||
if (!this.form.translateStatus) {
|
||||
this.form.translateStatus = '1'
|
||||
}
|
||||
this.open = true
|
||||
this.title = '修改静态文案'
|
||||
},
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
async submitForm() {
|
||||
await this.$refs.form.validate()
|
||||
if (this.form.i18nId) {
|
||||
await updateTranslationEntry(this.form)
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
} else {
|
||||
await addTranslationEntry(this.form)
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
}
|
||||
this.open = false
|
||||
await this.getList()
|
||||
},
|
||||
async handleDelete(row) {
|
||||
const i18nIds = row && row.i18nId ? row.i18nId : this.ids.join(',')
|
||||
if (!i18nIds) return
|
||||
await this.$modal.confirm(`确认删除文案 ID 为 "${i18nIds}" 的数据项吗?`)
|
||||
await deleteTranslationEntry(i18nIds)
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
await this.getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="innerVisible"
|
||||
title="富文本翻译"
|
||||
width="80%"
|
||||
top="5vh"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div v-if="field" class="rt-dialog">
|
||||
<div class="rt-meta">
|
||||
<span class="rt-key">{{ field.key }}</span>
|
||||
<span class="rt-label">{{ field.fieldLabel }}</span>
|
||||
</div>
|
||||
<el-tabs v-model="activeLang" type="border-card">
|
||||
<el-tab-pane
|
||||
v-for="lang in tabs"
|
||||
:key="lang.langCode"
|
||||
:name="lang.langCode"
|
||||
>
|
||||
<span slot="label">
|
||||
<i v-if="isFilled(lang.langCode)" class="el-icon-check filled" />
|
||||
<i v-else-if="lang.langCode !== 'zh_cn'" class="el-icon-warning-outline empty" />
|
||||
{{ lang.langName }}
|
||||
</span>
|
||||
<div v-if="lang.langCode === 'zh_cn'" class="zh-readonly">
|
||||
<p class="readonly-tip">
|
||||
<i class="el-icon-info" />
|
||||
中文为业务表只读原文,要修改请到对应业务模块(产品/文化 等)
|
||||
</p>
|
||||
<div class="zh-content" v-html="field.sourceText"></div>
|
||||
</div>
|
||||
<div v-else class="rt-edit-pane">
|
||||
<editor
|
||||
:value="getDraft(lang.langCode)"
|
||||
:min-height="320"
|
||||
:enable-media-choice="true"
|
||||
:video-file-size="100"
|
||||
@input="onInput(lang.langCode, $event)"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="innerVisible = false">取消</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="!hasChange"
|
||||
@click="handleConfirm"
|
||||
>应用更改</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RichTextDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
field: { type: Object, default: null },
|
||||
targetLangs: { type: Array, default: () => [] },
|
||||
dirtyKeys: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
innerVisible: false,
|
||||
activeLang: '',
|
||||
drafts: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tabs() {
|
||||
// 中文 + 各目标语种
|
||||
return [{ langCode: 'zh_cn', langName: '中文(原文)' }, ...this.targetLangs]
|
||||
},
|
||||
hasChange() {
|
||||
if (!this.field) return false
|
||||
for (const lang of this.targetLangs) {
|
||||
if (this.getDraft(lang.langCode) !== this.originalValue(lang.langCode)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(v) {
|
||||
this.innerVisible = v
|
||||
if (v) this.initDrafts()
|
||||
},
|
||||
innerVisible(v) {
|
||||
this.$emit('update:visible', v)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initDrafts() {
|
||||
if (!this.field) return
|
||||
const drafts = {}
|
||||
for (const lang of this.targetLangs) {
|
||||
const t = this.field.translations && this.field.translations[lang.langCode]
|
||||
drafts[lang.langCode] = t && t.value != null ? String(t.value) : ''
|
||||
}
|
||||
this.drafts = drafts
|
||||
this.activeLang = this.targetLangs.length ? this.targetLangs[0].langCode : 'zh_cn'
|
||||
},
|
||||
getDraft(langCode) {
|
||||
return this.drafts[langCode] != null ? this.drafts[langCode] : ''
|
||||
},
|
||||
originalValue(langCode) {
|
||||
if (!this.field) return ''
|
||||
if (this.field._original && this.field._original[langCode] != null) {
|
||||
return this.field._original[langCode]
|
||||
}
|
||||
const t = this.field.translations && this.field.translations[langCode]
|
||||
return t && t.value != null ? String(t.value) : ''
|
||||
},
|
||||
onInput(langCode, html) {
|
||||
this.$set(this.drafts, langCode, html == null ? '' : String(html))
|
||||
},
|
||||
isFilled(langCode) {
|
||||
const v = this.getDraft(langCode)
|
||||
return v != null && String(v).trim() !== ''
|
||||
},
|
||||
handleConfirm() {
|
||||
if (!this.field) return
|
||||
for (const lang of this.targetLangs) {
|
||||
const next = this.getDraft(lang.langCode)
|
||||
const orig = this.originalValue(lang.langCode)
|
||||
if (next !== orig) {
|
||||
this.$emit('field-change', {
|
||||
key: this.field.key,
|
||||
langCode: lang.langCode,
|
||||
value: next,
|
||||
originalValue: orig
|
||||
})
|
||||
}
|
||||
}
|
||||
this.innerVisible = false
|
||||
},
|
||||
handleClosed() {
|
||||
this.drafts = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rt-dialog { padding: 0 4px; }
|
||||
.rt-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.rt-key {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
background: #f1f5f9;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.rt-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.zh-readonly { padding: 12px; }
|
||||
.readonly-tip {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
background: #fffbeb;
|
||||
border: 1px solid #fde68a;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.zh-content {
|
||||
background: #f8fafc;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.7;
|
||||
color: #1f2937;
|
||||
}
|
||||
.zh-content :deep(img) { max-width: 100%; height: auto; }
|
||||
|
||||
.rt-edit-pane { padding: 8px; }
|
||||
|
||||
.filled { color: #10b981; }
|
||||
.empty { color: #ef4444; }
|
||||
</style>
|
||||
@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="translation-section">
|
||||
<div class="section-header">
|
||||
<span class="section-name">{{ section.sectionName }}</span>
|
||||
<span class="section-meta">{{ totalFields }} 个字段 · {{ section.items.length }} 条记录</span>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<template v-for="item in section.items">
|
||||
<div :key="item.sourceId" class="item-block">
|
||||
<div class="item-header">
|
||||
<span class="item-id">#{{ item.sourceId }}</span>
|
||||
<span class="item-label">{{ item.sourceLabel || '(无标题)' }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="field in item.fields"
|
||||
:key="field.key"
|
||||
:class="['field-card', { 'is-dirty': isDirty(field) }]"
|
||||
>
|
||||
<div class="field-row source-row">
|
||||
<span class="field-label">{{ field.fieldLabel }}(中文 · 只读)</span>
|
||||
<div v-if="field.fieldType === 'richtext'" class="source-richtext" v-html="field.sourceText"></div>
|
||||
<div v-else class="source-text">{{ field.sourceText }}</div>
|
||||
<div class="field-key" :title="field.key">{{ field.key }}</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="lang in targetLangs"
|
||||
:key="lang.langCode"
|
||||
class="field-row lang-row"
|
||||
>
|
||||
<span class="field-label">
|
||||
{{ lang.langName }}
|
||||
<i v-if="isFieldLangFilled(field, lang.langCode)" class="el-icon-check filled" />
|
||||
<i v-else class="el-icon-warning-outline empty" />
|
||||
</span>
|
||||
<template v-if="field.fieldType === 'richtext'">
|
||||
<div
|
||||
class="rt-preview"
|
||||
v-html="getValue(field, lang.langCode) || '<span class=\'rt-empty\'>※ 未翻译</span>'"
|
||||
></div>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="$emit('open-richtext', field)"
|
||||
>编辑富文本</el-button>
|
||||
</template>
|
||||
<template v-else-if="field.fieldType === 'textarea'">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:value="getValue(field, lang.langCode)"
|
||||
:placeholder="`请输入 ${lang.langName} 翻译`"
|
||||
@input="onInput(field, lang.langCode, $event)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input
|
||||
:value="getValue(field, lang.langCode)"
|
||||
:placeholder="`请输入 ${lang.langName} 翻译`"
|
||||
@input="onInput(field, lang.langCode, $event)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!item.fields.length" class="empty-fields">该记录无可翻译字段</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TranslationSection',
|
||||
props: {
|
||||
section: { type: Object, required: true },
|
||||
targetLangs: { type: Array, default: () => [] },
|
||||
dirtyKeys: { type: Array, default: () => [] }
|
||||
},
|
||||
computed: {
|
||||
dirtyKeySet() {
|
||||
return new Set(this.dirtyKeys)
|
||||
},
|
||||
totalFields() {
|
||||
let n = 0
|
||||
for (const item of this.section.items || []) {
|
||||
n += (item.fields || []).length
|
||||
}
|
||||
return n
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValue(field, langCode) {
|
||||
const t = field.translations && field.translations[langCode]
|
||||
return t && t.value != null ? t.value : ''
|
||||
},
|
||||
isFieldLangFilled(field, langCode) {
|
||||
const v = this.getValue(field, langCode)
|
||||
return v != null && String(v).trim() !== ''
|
||||
},
|
||||
isDirty(field) {
|
||||
for (const lang of this.targetLangs) {
|
||||
if (this.dirtyKeySet.has(`${lang.langCode}::${field.key}`)) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
onInput(field, langCode, value) {
|
||||
const original = this.originalValue(field, langCode)
|
||||
this.$emit('field-change', {
|
||||
key: field.key,
|
||||
langCode,
|
||||
value,
|
||||
originalValue: original
|
||||
})
|
||||
},
|
||||
originalValue(field, langCode) {
|
||||
// 从 field._original 取(首次扫描的值),如果没存就用当前 translation 值兜底
|
||||
if (field._original && field._original[langCode] != null) {
|
||||
return field._original[langCode]
|
||||
}
|
||||
const t = field.translations && field.translations[langCode]
|
||||
return t && t.value != null ? t.value : ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
section: {
|
||||
immediate: true,
|
||||
handler(sec) {
|
||||
// 首次加载(或 section 整体替换)时,将当前 translations 值快照到 _original
|
||||
if (!sec || !Array.isArray(sec.items)) return
|
||||
for (const item of sec.items) {
|
||||
for (const f of item.fields || []) {
|
||||
if (!f._original) {
|
||||
const snap = {}
|
||||
for (const lang of this.targetLangs) {
|
||||
const t = f.translations && f.translations[lang.langCode]
|
||||
snap[lang.langCode] = t && t.value != null ? t.value : ''
|
||||
}
|
||||
this.$set(f, '_original', snap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.translation-section {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
.section-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
.section-meta {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.section-body {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.item-block {
|
||||
padding: 8px 0 12px;
|
||||
border-bottom: 1px dashed #f1f5f9;
|
||||
}
|
||||
.item-block:last-child { border-bottom: none; }
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.item-id {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
background: #f1f5f9;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.item-label {
|
||||
font-size: 13px;
|
||||
color: #1f2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.field-card {
|
||||
margin: 8px 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
}
|
||||
.field-card.is-dirty {
|
||||
border-color: #fbbf24;
|
||||
background: #fffbeb;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr;
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.field-row + .field-row {
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
.field-label {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
padding-top: 6px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.field-label .filled { color: #10b981; }
|
||||
.field-label .empty { color: #ef4444; }
|
||||
.field-key {
|
||||
grid-column: 2;
|
||||
font-size: 11px;
|
||||
color: #cbd5e1;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.source-row .source-text,
|
||||
.source-row .source-richtext {
|
||||
font-size: 13px;
|
||||
color: #1f2937;
|
||||
background: #f8fafc;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
word-break: break-word;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.source-row .source-richtext :deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.rt-preview {
|
||||
font-size: 13px;
|
||||
color: #1f2937;
|
||||
background: #f8fafc;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px dashed #e2e8f0;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.rt-preview :deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.rt-empty { color: #ef4444; font-size: 12px; }
|
||||
|
||||
.empty-fields {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,7 @@
|
||||
<!-- 顶部欢迎区 -->
|
||||
<div class="welcome-section">
|
||||
<div class="welcome-text">
|
||||
<h2>欢迎使用华阳食品管理系统</h2>
|
||||
<p>烟台华阳食品有限公司 — 网站内容管理后台</p>
|
||||
<h2>华阳网站管理系统</h2>
|
||||
</div>
|
||||
<div class="welcome-time">
|
||||
<span class="time-text">{{ currentTime }}</span>
|
||||
@ -14,8 +13,8 @@
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="20" class="stats-row">
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6">
|
||||
<div class="stat-card stat-card--blue" @click="navigateTo('/huayangProduct/product')">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12">
|
||||
<div class="stat-card stat-card--blue" @click="navigateTo(['/huayangSite/productCenter/product', '/huayangProduct/product'])">
|
||||
<div class="stat-icon">
|
||||
<i class="el-icon-goods"></i>
|
||||
</div>
|
||||
@ -25,19 +24,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6">
|
||||
<div class="stat-card stat-card--green" @click="navigateTo('/news')">
|
||||
<div class="stat-icon">
|
||||
<i class="el-icon-document"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value">{{ stats.newsCount }}</span>
|
||||
<span class="stat-label">新闻文章</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6">
|
||||
<div class="stat-card stat-card--orange" @click="navigateTo('/huayangCustomer/message')">
|
||||
<el-col :xs="12" :sm="12" :md="12" :lg="12">
|
||||
<div class="stat-card stat-card--orange" @click="navigateTo(['/huayangSite/productCenter/productInquiry', '/huayangProduct/productInquiry'])">
|
||||
<div class="stat-icon">
|
||||
<i class="el-icon-chat-dot-round"></i>
|
||||
</div>
|
||||
@ -47,65 +35,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6">
|
||||
<div class="stat-card stat-card--purple" @click="navigateTo('/huayangCustomer/joinApply')">
|
||||
<div class="stat-icon">
|
||||
<i class="el-icon-s-cooperation"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<span class="stat-value">{{ stats.joinApplyCount }}</span>
|
||||
<span class="stat-label">招商申请</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<!-- 快捷入口 -->
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="8">
|
||||
<el-card class="box-card shortcut-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-menu"></i> 快捷入口</span>
|
||||
</div>
|
||||
<div class="shortcut-grid">
|
||||
<div class="shortcut-item" @click="navigateTo('/huayangProduct/product')">
|
||||
<i class="el-icon-goods"></i>
|
||||
<span>产品管理</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/huayangProduct/productCategory')">
|
||||
<i class="el-icon-s-grid"></i>
|
||||
<span>产品分类</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/news')">
|
||||
<i class="el-icon-document"></i>
|
||||
<span>新闻管理</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/companyHonor')">
|
||||
<i class="el-icon-trophy"></i>
|
||||
<span>资质荣誉</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/navigation')">
|
||||
<i class="el-icon-s-operation"></i>
|
||||
<span>导航配置</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/huayangResource/resource')">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
<span>媒体资源</span>
|
||||
</div>
|
||||
<div class="shortcut-item" @click="navigateTo('/huayangLang/language')">
|
||||
<i class="el-icon-s-flag"></i>
|
||||
<span>多语言</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 最近留言 -->
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="8">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-chat-dot-round"></i> 最近留言</span>
|
||||
<el-button type="text" @click="navigateTo('/huayangCustomer/message')">查看全部</el-button>
|
||||
<el-button type="text" @click="navigateTo(['/huayangSite/productCenter/productInquiry', '/huayangProduct/productInquiry'])">查看全部</el-button>
|
||||
</div>
|
||||
<div v-if="recentMessages.length === 0" class="empty-tip">暂无留言数据</div>
|
||||
<div v-else class="recent-list">
|
||||
@ -120,39 +58,13 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 最近招商申请 -->
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="8">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-s-cooperation"></i> 最近招商申请</span>
|
||||
<el-button type="text" @click="navigateTo('/huayangCustomer/joinApply')">查看全部</el-button>
|
||||
</div>
|
||||
<div v-if="recentJoinApplies.length === 0" class="empty-tip">暂无招商申请数据</div>
|
||||
<div v-else class="recent-list">
|
||||
<div v-for="item in recentJoinApplies" :key="item.applyId" class="recent-item">
|
||||
<div class="recent-item-header">
|
||||
<span class="recent-name">{{ item.applicantName || '匿名' }}</span>
|
||||
<el-tag size="mini" :type="item.status === '0' ? 'warning' : 'success'">
|
||||
{{ item.status === '0' ? '待处理' : '已处理' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="recent-content">
|
||||
<span v-if="item.applicantPhone">{{ item.applicantPhone }}</span>
|
||||
<span v-if="item.applicantArea"> · {{ item.applicantArea }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProduct } from '@/api/huayang/product'
|
||||
import { listNews } from '@/api/huayang/news'
|
||||
import { listMessage } from '@/api/huayang/message'
|
||||
import { listJoinApply } from '@/api/huayang/joinApply'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@ -163,12 +75,9 @@ export default {
|
||||
timer: null,
|
||||
stats: {
|
||||
productCount: 0,
|
||||
newsCount: 0,
|
||||
messageCount: 0,
|
||||
joinApplyCount: 0
|
||||
messageCount: 0
|
||||
},
|
||||
recentMessages: [],
|
||||
recentJoinApplies: []
|
||||
recentMessages: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -190,33 +99,36 @@ export default {
|
||||
},
|
||||
async loadStats() {
|
||||
try {
|
||||
const [productRes, newsRes, messageRes, joinRes] = await Promise.allSettled([
|
||||
const [productRes, messageRes] = await Promise.allSettled([
|
||||
listProduct({ pageNum: 1, pageSize: 1 }),
|
||||
listNews({ pageNum: 1, pageSize: 1 }),
|
||||
listMessage({ pageNum: 1, pageSize: 5 }),
|
||||
listJoinApply({ pageNum: 1, pageSize: 5 })
|
||||
listMessage({ pageNum: 1, pageSize: 5 })
|
||||
])
|
||||
if (productRes.status === 'fulfilled') {
|
||||
this.stats.productCount = productRes.value.total || 0
|
||||
}
|
||||
if (newsRes.status === 'fulfilled') {
|
||||
this.stats.newsCount = newsRes.value.total || 0
|
||||
}
|
||||
if (messageRes.status === 'fulfilled') {
|
||||
this.stats.messageCount = messageRes.value.total || 0
|
||||
this.recentMessages = (messageRes.value.rows || []).slice(0, 5)
|
||||
}
|
||||
if (joinRes.status === 'fulfilled') {
|
||||
this.stats.joinApplyCount = joinRes.value.total || 0
|
||||
this.recentJoinApplies = (joinRes.value.rows || []).slice(0, 5)
|
||||
}
|
||||
} catch (e) {
|
||||
// 静默处理,首页不因接口错误而崩溃
|
||||
console.warn('Dashboard stats load error:', e)
|
||||
}
|
||||
},
|
||||
navigateTo(path) {
|
||||
this.$router.push(path).catch(() => {})
|
||||
navigateTo(pathOrPaths) {
|
||||
const targetPath = this.resolveFirstAvailablePath(pathOrPaths)
|
||||
this.$router.push(targetPath).catch(() => {})
|
||||
},
|
||||
resolveFirstAvailablePath(pathOrPaths) {
|
||||
const pathList = Array.isArray(pathOrPaths) ? pathOrPaths : [pathOrPaths]
|
||||
for (const candidatePath of pathList) {
|
||||
const resolved = this.$router.resolve(candidatePath)
|
||||
if (resolved && resolved.route && resolved.route.path !== '/404') {
|
||||
return candidatePath
|
||||
}
|
||||
}
|
||||
// 未找到可匹配路由时,回退到第一个候选,避免点击无响应
|
||||
return pathList[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -285,15 +197,9 @@ export default {
|
||||
.stat-card--blue {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
}
|
||||
.stat-card--green {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
}
|
||||
.stat-card--orange {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
|
||||
}
|
||||
.stat-card--purple {
|
||||
background: linear-gradient(135deg, #722ed1 0%, #9254de 100%);
|
||||
}
|
||||
.stat-icon {
|
||||
font-size: 36px;
|
||||
margin-right: 16px;
|
||||
@ -329,41 +235,6 @@ export default {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* 快捷入口 */
|
||||
.shortcut-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
.shortcut-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 14px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.shortcut-item:hover {
|
||||
background: #ecf5ff;
|
||||
color: #1890ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.shortcut-item i {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.shortcut-item span {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.shortcut-item:hover span {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
/* 最近列表 */
|
||||
.recent-list {
|
||||
max-height: 300px;
|
||||
@ -415,8 +286,5 @@ export default {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.shortcut-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -75,8 +75,8 @@ export default {
|
||||
footerContent: defaultSettings.footerContent,
|
||||
codeUrl: "",
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "admin123",
|
||||
username: "",
|
||||
password: "",
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
@ -124,11 +124,9 @@ export default {
|
||||
const username = Cookies.get("username")
|
||||
const password = Cookies.get("password")
|
||||
const rememberMe = Cookies.get('rememberMe')
|
||||
this.loginForm = {
|
||||
username: username === undefined ? this.loginForm.username : username,
|
||||
password: password === undefined ? this.loginForm.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
}
|
||||
this.loginForm.username = username === undefined ? this.loginForm.username : username
|
||||
this.loginForm.password = password === undefined ? this.loginForm.password : decrypt(password)
|
||||
this.loginForm.rememberMe = rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(valid => {
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
label-width="100px"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="生成类型" prop="type">
|
||||
<el-form-item label="鐢熸垚绫诲瀷" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in typeOptions"
|
||||
@ -30,8 +30,8 @@
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showFileName" label="文件名" prop="fileName">
|
||||
<el-input v-model="formData.fileName" placeholder="请输入文件名" clearable />
|
||||
<el-form-item v-if="showFileName" label="鏂囦欢鍚? prop="fileName">
|
||||
<el-input v-model="formData.fileName" placeholder="" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
@ -39,10 +39,10 @@
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="close">
|
||||
取消
|
||||
鍙栨秷
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">
|
||||
确定
|
||||
纭畾
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@ -61,20 +61,20 @@ export default {
|
||||
rules: {
|
||||
fileName: [{
|
||||
required: true,
|
||||
message: '请输入文件名',
|
||||
message: '璇疯緭鍏ユ枃浠跺悕',
|
||||
trigger: 'blur'
|
||||
}],
|
||||
type: [{
|
||||
required: true,
|
||||
message: '生成类型不能为空',
|
||||
message: '鐢熸垚绫诲瀷涓嶈兘涓虹┖',
|
||||
trigger: 'change'
|
||||
}]
|
||||
},
|
||||
typeOptions: [{
|
||||
label: '页面',
|
||||
label: '椤甸潰',
|
||||
value: 'file'
|
||||
}, {
|
||||
label: '弹窗',
|
||||
label: '寮圭獥',
|
||||
value: 'dialog'
|
||||
}]
|
||||
}
|
||||
|
||||
@ -9,9 +9,9 @@ const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '华阳食品管理系统' // 网页标题
|
||||
|
||||
const baseUrl = 'http://localhost:8080' // 后端接口
|
||||
const baseUrl = 'http://localhost:7003' // 后端接口
|
||||
|
||||
const port = process.env.port || process.env.npm_config_port || 80 // 端口
|
||||
const port = process.env.port || process.env.npm_config_port || 7001 // 端口
|
||||
|
||||
// vue.config.js 配置说明
|
||||
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
|
||||
|
||||
7
backend/Dockerfile
Normal file
7
backend/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
# 使用 JDK8 镜像(RuoYi 默认用 8,如果是 17 请换成 openjdk:17-jdk-alpine)
|
||||
FROM eclipse-temurin:17-jdk-alpine
|
||||
WORKDIR /app
|
||||
# 复制本地已经打包好的 jar 包 (前提是你已经在本地执行过 mvn clean package)
|
||||
COPY ruoyi-admin/target/ruoyi-admin.jar app.jar
|
||||
EXPOSE 7003
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@ -18,6 +18,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
import com.ruoyi.common.utils.file.ImageUploadUtils;
|
||||
import com.ruoyi.framework.config.ServerConfig;
|
||||
|
||||
/**
|
||||
@ -79,7 +80,7 @@ public class CommonController
|
||||
// 上传文件路径
|
||||
String filePath = RuoYiConfig.getUploadPath();
|
||||
// 上传并返回新文件名称
|
||||
String fileName = FileUploadUtils.upload(filePath, file);
|
||||
String fileName = upload(filePath, file);
|
||||
String url = serverConfig.getUrl() + fileName;
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("url", url);
|
||||
@ -111,7 +112,7 @@ public class CommonController
|
||||
for (MultipartFile file : files)
|
||||
{
|
||||
// 上传并返回新文件名称
|
||||
String fileName = FileUploadUtils.upload(filePath, file);
|
||||
String fileName = upload(filePath, file);
|
||||
String url = serverConfig.getUrl() + fileName;
|
||||
urls.add(url);
|
||||
fileNames.add(fileName);
|
||||
@ -159,4 +160,10 @@ public class CommonController
|
||||
log.error("下载文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String upload(String filePath, MultipartFile file) throws Exception
|
||||
{
|
||||
return ImageUploadUtils.isImage(file) ? ImageUploadUtils.upload(filePath, file)
|
||||
: FileUploadUtils.upload(filePath, file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
package com.ruoyi.web.controller.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
|
||||
/**
|
||||
* Handles uploaded portal assets before the generic static resource handler.
|
||||
*/
|
||||
@RestController
|
||||
public class ProfileUploadController
|
||||
{
|
||||
private static final String PROFILE_PREFIX = "/profile/";
|
||||
private static final byte[] MISSING_IMAGE_BYTES = Base64.getDecoder().decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=");
|
||||
|
||||
@RequestMapping(value = "/profile/upload/**", method = { RequestMethod.GET, RequestMethod.HEAD })
|
||||
public ResponseEntity<Resource> getUploadResource(HttpServletRequest request) throws IOException
|
||||
{
|
||||
String requestUri = request.getRequestURI();
|
||||
String relativePath = requestUri.startsWith(PROFILE_PREFIX) ? requestUri.substring(PROFILE_PREFIX.length()) : "";
|
||||
relativePath = URLDecoder.decode(relativePath, StandardCharsets.UTF_8);
|
||||
Path root = Path.of(RuoYiConfig.getProfile()).toAbsolutePath().normalize();
|
||||
Path target = root.resolve(relativePath).normalize();
|
||||
|
||||
if (target.startsWith(root) && Files.isRegularFile(target) && Files.isReadable(target))
|
||||
{
|
||||
MediaType mediaType = resolveMediaType(target);
|
||||
return ResponseEntity.ok()
|
||||
.contentType(mediaType)
|
||||
.cacheControl(CacheControl.noCache())
|
||||
.body(new FileSystemResource(target));
|
||||
}
|
||||
|
||||
if (isImagePath(relativePath))
|
||||
{
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.cacheControl(CacheControl.noStore())
|
||||
.body(new ByteArrayResource(MISSING_IMAGE_BYTES));
|
||||
}
|
||||
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
private static MediaType resolveMediaType(Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
String contentType = Files.probeContentType(path);
|
||||
if (contentType != null && !contentType.isBlank())
|
||||
{
|
||||
return MediaType.parseMediaType(contentType);
|
||||
}
|
||||
}
|
||||
catch (IOException | IllegalArgumentException ignored)
|
||||
{
|
||||
}
|
||||
return MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
private static boolean isImagePath(String path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String lowerPath = path.toLowerCase();
|
||||
return lowerPath.endsWith(".png") || lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")
|
||||
|| lowerPath.endsWith(".gif") || lowerPath.endsWith(".webp") || lowerPath.endsWith(".svg")
|
||||
|| lowerPath.endsWith(".ico");
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.file.ImageUploadUtils;
|
||||
import com.ruoyi.framework.config.ServerConfig;
|
||||
import com.ruoyi.system.domain.HuayangCompanyHonor;
|
||||
import com.ruoyi.system.service.IHuayangCompanyHonorService;
|
||||
@ -113,7 +113,7 @@ public class HuayangCompanyHonorController extends BaseController
|
||||
// 上传文件路径
|
||||
String filePath = RuoYiConfig.getUploadPath() + "/huayang/honor";
|
||||
// 上传并返回新文件名称
|
||||
String fileName = FileUploadUtils.upload(filePath, file);
|
||||
String fileName = ImageUploadUtils.upload(filePath, file);
|
||||
String url = serverConfig.getUrl() + fileName;
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("url", url);
|
||||
@ -125,4 +125,4 @@ public class HuayangCompanyHonorController extends BaseController
|
||||
return error("上传失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ public class HuayangCompanySectionController extends BaseController
|
||||
/**
|
||||
* 按板块标识查询(用于后台快速打开 about_intro 等,须在 /{sectionId} 之前声明)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasAnyPermi('huayang:companySection:query,huayang:joinPageContent:query')")
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companySection:query')")
|
||||
@GetMapping("/byCode/{sectionCode}")
|
||||
public AjaxResult getBySectionCode(@PathVariable("sectionCode") String sectionCode)
|
||||
{
|
||||
@ -67,7 +67,7 @@ public class HuayangCompanySectionController extends BaseController
|
||||
/**
|
||||
* 新增公司简介
|
||||
*/
|
||||
@PreAuthorize("@ss.hasAnyPermi('huayang:companySection:add,huayang:joinPageContent:edit')")
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companySection:add')")
|
||||
@Log(title = "公司简介", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody HuayangCompanySection huayangCompanySection)
|
||||
@ -79,7 +79,7 @@ public class HuayangCompanySectionController extends BaseController
|
||||
/**
|
||||
* 修改公司简介
|
||||
*/
|
||||
@PreAuthorize("@ss.hasAnyPermi('huayang:companySection:edit,huayang:joinPageContent:edit')")
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companySection:edit')")
|
||||
@Log(title = "公司简介", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody HuayangCompanySection huayangCompanySection)
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.system.domain.HuayangCompanyTech;
|
||||
import com.ruoyi.system.service.IHuayangCompanyTechService;
|
||||
|
||||
/**
|
||||
* 品质技术管理Controller
|
||||
*
|
||||
* @author huayang
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/companyTech")
|
||||
public class HuayangCompanyTechController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IHuayangCompanyTechService huayangCompanyTechService;
|
||||
|
||||
/**
|
||||
* 查询品质技术列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companyTech:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(HuayangCompanyTech huayangCompanyTech)
|
||||
{
|
||||
startPage();
|
||||
List<HuayangCompanyTech> list = huayangCompanyTechService.selectHuayangCompanyTechList(huayangCompanyTech);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取品质技术详情
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companyTech:query')")
|
||||
@GetMapping(value = "/{techId}")
|
||||
public AjaxResult getInfo(@PathVariable("techId") Long techId)
|
||||
{
|
||||
return success(huayangCompanyTechService.selectHuayangCompanyTechByTechId(techId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增品质技术
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companyTech:add')")
|
||||
@Log(title = "品质技术", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody HuayangCompanyTech huayangCompanyTech)
|
||||
{
|
||||
huayangCompanyTech.setCreateBy(getUsername());
|
||||
return toAjax(huayangCompanyTechService.insertHuayangCompanyTech(huayangCompanyTech));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改品质技术
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companyTech:edit')")
|
||||
@Log(title = "品质技术", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody HuayangCompanyTech huayangCompanyTech)
|
||||
{
|
||||
huayangCompanyTech.setUpdateBy(getUsername());
|
||||
return toAjax(huayangCompanyTechService.updateHuayangCompanyTech(huayangCompanyTech));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除品质技术
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:companyTech:remove')")
|
||||
@Log(title = "品质技术", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{techIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] techIds)
|
||||
{
|
||||
return toAjax(huayangCompanyTechService.deleteHuayangCompanyTechByTechIds(techIds));
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.system.domain.HuayangJoinApply;
|
||||
import com.ruoyi.system.service.IHuayangJoinApplyService;
|
||||
|
||||
/**
|
||||
* 招商申请管理端Controller
|
||||
*
|
||||
* @author huayang
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/joinApply")
|
||||
public class HuayangJoinApplyController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IHuayangJoinApplyService joinApplyService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:joinApply:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(HuayangJoinApply apply)
|
||||
{
|
||||
startPage();
|
||||
List<HuayangJoinApply> list = joinApplyService.selectHuayangJoinApplyList(apply);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:joinApply:query')")
|
||||
@GetMapping(value = "/{applyId}")
|
||||
public AjaxResult getInfo(@PathVariable("applyId") Long applyId)
|
||||
{
|
||||
return success(joinApplyService.selectHuayangJoinApplyById(applyId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:joinApply:remove')")
|
||||
@Log(title = "招商申请", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{applyIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] applyIds)
|
||||
{
|
||||
return toAjax(joinApplyService.deleteHuayangJoinApplyByIds(applyIds));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:joinApply:handle')")
|
||||
@Log(title = "处理招商申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/handle")
|
||||
public AjaxResult handle(@Validated @RequestBody HuayangJoinApply apply)
|
||||
{
|
||||
apply.setUpdateBy(getUsername());
|
||||
return toAjax(joinApplyService.handleApply(apply));
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,14 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
@ -21,6 +26,8 @@ import com.ruoyi.system.service.IHuayangI18nService;
|
||||
@RequestMapping("/huayang")
|
||||
public class HuayangLanguageController extends BaseController
|
||||
{
|
||||
private static final Pattern I18N_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9_.-]{2,100}$");
|
||||
|
||||
@Autowired
|
||||
private IHuayangLanguageService languageService;
|
||||
|
||||
@ -84,4 +91,277 @@ public class HuayangLanguageController extends BaseController
|
||||
{
|
||||
return toAjax(i18nService.deleteHuayangI18nByIds(i18nIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入翻译(支持草稿模式、统计结果、逐条错误信息)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:add')")
|
||||
@PostMapping("/translation/import")
|
||||
public AjaxResult importTranslation(@RequestBody TranslationImportRequest request)
|
||||
{
|
||||
if (request == null || request.getItems() == null || request.getItems().isEmpty())
|
||||
{
|
||||
return error("导入数据不能为空");
|
||||
}
|
||||
String defaultStatus = StringUtils.defaultIfBlank(request.getDefaultStatus(), "0");
|
||||
if (!"0".equals(defaultStatus) && !"1".equals(defaultStatus))
|
||||
{
|
||||
return error("默认发布状态仅支持 0(草稿) 或 1(已发布)");
|
||||
}
|
||||
|
||||
int total = request.getItems().size();
|
||||
int inserted = 0;
|
||||
int updated = 0;
|
||||
int skipped = 0;
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
int index = 0;
|
||||
for (TranslationImportItem item : request.getItems())
|
||||
{
|
||||
index++;
|
||||
String rowPrefix = "第" + index + "行";
|
||||
if (item == null)
|
||||
{
|
||||
skipped++;
|
||||
errors.add(rowPrefix + ":空记录已跳过");
|
||||
continue;
|
||||
}
|
||||
String langCode = StringUtils.lowerCase(StringUtils.trimToEmpty(item.getLangCode()));
|
||||
String pageModule = StringUtils.trimToEmpty(item.getPageModule());
|
||||
String i18nKey = StringUtils.trimToEmpty(item.getI18nKey());
|
||||
String i18nValue = item.getI18nValue() == null ? "" : item.getI18nValue();
|
||||
String fallbackLangCode = StringUtils.lowerCase(StringUtils.trimToEmpty(item.getFallbackLangCode()));
|
||||
String translateStatus = StringUtils.trimToEmpty(item.getTranslateStatus());
|
||||
|
||||
if (StringUtils.isBlank(langCode) || StringUtils.isBlank(i18nKey))
|
||||
{
|
||||
skipped++;
|
||||
errors.add(rowPrefix + ":langCode / i18nKey 不能为空");
|
||||
continue;
|
||||
}
|
||||
if (!I18N_KEY_PATTERN.matcher(i18nKey).matches())
|
||||
{
|
||||
skipped++;
|
||||
errors.add(rowPrefix + ":i18nKey 格式非法(仅支持字母数字._-,长度2-100)");
|
||||
continue;
|
||||
}
|
||||
if (StringUtils.isBlank(pageModule))
|
||||
{
|
||||
pageModule = "common";
|
||||
}
|
||||
if (StringUtils.isBlank(fallbackLangCode))
|
||||
{
|
||||
fallbackLangCode = "zh_cn";
|
||||
}
|
||||
if (StringUtils.isBlank(translateStatus))
|
||||
{
|
||||
translateStatus = defaultStatus;
|
||||
}
|
||||
if (!"0".equals(translateStatus) && !"1".equals(translateStatus))
|
||||
{
|
||||
skipped++;
|
||||
errors.add(rowPrefix + ":translateStatus 仅支持 0/1");
|
||||
continue;
|
||||
}
|
||||
|
||||
HuayangI18n exists = i18nService.selectHuayangI18nByKey(langCode, i18nKey);
|
||||
if (exists != null)
|
||||
{
|
||||
exists.setI18nValue(i18nValue);
|
||||
exists.setPageModule(pageModule);
|
||||
exists.setFallbackLangCode(fallbackLangCode);
|
||||
exists.setTranslateStatus(translateStatus);
|
||||
exists.setUpdateBy(getUsername());
|
||||
i18nService.updateHuayangI18n(exists);
|
||||
updated++;
|
||||
}
|
||||
else
|
||||
{
|
||||
HuayangI18n record = new HuayangI18n();
|
||||
record.setLangCode(langCode);
|
||||
record.setPageModule(pageModule);
|
||||
record.setI18nKey(i18nKey);
|
||||
record.setI18nValue(i18nValue);
|
||||
record.setFallbackLangCode(fallbackLangCode);
|
||||
record.setTranslateStatus(translateStatus);
|
||||
record.setCreateBy(getUsername());
|
||||
i18nService.insertHuayangI18n(record);
|
||||
inserted++;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("total", total);
|
||||
result.put("inserted", inserted);
|
||||
result.put("updated", updated);
|
||||
result.put("skipped", skipped);
|
||||
result.put("errors", errors);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按筛选条件一键发布(将草稿改为已发布)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:edit')")
|
||||
@PostMapping("/translation/publish")
|
||||
public AjaxResult publishTranslation(@RequestBody TranslationPublishRequest request)
|
||||
{
|
||||
HuayangI18n query = new HuayangI18n();
|
||||
if (request != null)
|
||||
{
|
||||
query.setLangCode(request.getLangCode());
|
||||
query.setPageModule(request.getPageModule());
|
||||
query.setI18nKey(request.getI18nKey());
|
||||
}
|
||||
List<HuayangI18n> rows = i18nService.selectHuayangI18nList(query);
|
||||
int affected = 0;
|
||||
for (HuayangI18n row : rows)
|
||||
{
|
||||
if ("1".equals(row.getTranslateStatus()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
row.setTranslateStatus("1");
|
||||
row.setUpdateBy(getUsername());
|
||||
i18nService.updateHuayangI18n(row);
|
||||
affected++;
|
||||
}
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("matched", rows.size());
|
||||
result.put("published", affected);
|
||||
return success(result);
|
||||
}
|
||||
|
||||
public static class TranslationImportRequest
|
||||
{
|
||||
private String defaultStatus;
|
||||
private List<TranslationImportItem> items;
|
||||
|
||||
public String getDefaultStatus()
|
||||
{
|
||||
return defaultStatus;
|
||||
}
|
||||
|
||||
public void setDefaultStatus(String defaultStatus)
|
||||
{
|
||||
this.defaultStatus = defaultStatus;
|
||||
}
|
||||
|
||||
public List<TranslationImportItem> getItems()
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<TranslationImportItem> items)
|
||||
{
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TranslationImportItem
|
||||
{
|
||||
private String langCode;
|
||||
private String pageModule;
|
||||
private String i18nKey;
|
||||
private String i18nValue;
|
||||
private String fallbackLangCode;
|
||||
private String translateStatus;
|
||||
|
||||
public String getLangCode()
|
||||
{
|
||||
return langCode;
|
||||
}
|
||||
|
||||
public void setLangCode(String langCode)
|
||||
{
|
||||
this.langCode = langCode;
|
||||
}
|
||||
|
||||
public String getPageModule()
|
||||
{
|
||||
return pageModule;
|
||||
}
|
||||
|
||||
public void setPageModule(String pageModule)
|
||||
{
|
||||
this.pageModule = pageModule;
|
||||
}
|
||||
|
||||
public String getI18nKey()
|
||||
{
|
||||
return i18nKey;
|
||||
}
|
||||
|
||||
public void setI18nKey(String i18nKey)
|
||||
{
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
public String getI18nValue()
|
||||
{
|
||||
return i18nValue;
|
||||
}
|
||||
|
||||
public void setI18nValue(String i18nValue)
|
||||
{
|
||||
this.i18nValue = i18nValue;
|
||||
}
|
||||
|
||||
public String getFallbackLangCode()
|
||||
{
|
||||
return fallbackLangCode;
|
||||
}
|
||||
|
||||
public void setFallbackLangCode(String fallbackLangCode)
|
||||
{
|
||||
this.fallbackLangCode = fallbackLangCode;
|
||||
}
|
||||
|
||||
public String getTranslateStatus()
|
||||
{
|
||||
return translateStatus;
|
||||
}
|
||||
|
||||
public void setTranslateStatus(String translateStatus)
|
||||
{
|
||||
this.translateStatus = translateStatus;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TranslationPublishRequest
|
||||
{
|
||||
private String langCode;
|
||||
private String pageModule;
|
||||
private String i18nKey;
|
||||
|
||||
public String getLangCode()
|
||||
{
|
||||
return langCode;
|
||||
}
|
||||
|
||||
public void setLangCode(String langCode)
|
||||
{
|
||||
this.langCode = langCode;
|
||||
}
|
||||
|
||||
public String getPageModule()
|
||||
{
|
||||
return pageModule;
|
||||
}
|
||||
|
||||
public void setPageModule(String pageModule)
|
||||
{
|
||||
this.pageModule = pageModule;
|
||||
}
|
||||
|
||||
public String getI18nKey()
|
||||
{
|
||||
return i18nKey;
|
||||
}
|
||||
|
||||
public void setI18nKey(String i18nKey)
|
||||
{
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.service.ISysDictDataService;
|
||||
|
||||
/**
|
||||
* 新闻类型(字典 huayang_news_type)管理,供「新闻分类」子菜单使用
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/newsCategory")
|
||||
public class HuayangNewsCategoryController extends BaseController
|
||||
{
|
||||
public static final String NEWS_TYPE_DICT = "huayang_news_type";
|
||||
|
||||
@Autowired
|
||||
private ISysDictDataService dictDataService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsCategory:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(SysDictData query)
|
||||
{
|
||||
query.setDictType(NEWS_TYPE_DICT);
|
||||
if (StringUtils.isNotEmpty(query.getDictLabel()))
|
||||
{
|
||||
query.setDictLabel(query.getDictLabel().trim());
|
||||
}
|
||||
startPage();
|
||||
List<SysDictData> list = dictDataService.selectDictDataList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsCategory:query')")
|
||||
@GetMapping(value = "/{dictCode}")
|
||||
public AjaxResult getInfo(@PathVariable Long dictCode)
|
||||
{
|
||||
SysDictData data = dictDataService.selectDictDataById(dictCode);
|
||||
if (data == null || !NEWS_TYPE_DICT.equals(data.getDictType()))
|
||||
{
|
||||
return error("数据不存在或无权访问");
|
||||
}
|
||||
return success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsCategory:add')")
|
||||
@Log(title = "新闻类型字典", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody SysDictData data)
|
||||
{
|
||||
data.setDictType(NEWS_TYPE_DICT);
|
||||
data.setCreateBy(getUsername());
|
||||
return toAjax(dictDataService.insertDictData(data));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsCategory:edit')")
|
||||
@Log(title = "新闻类型字典", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody SysDictData data)
|
||||
{
|
||||
SysDictData existing = dictDataService.selectDictDataById(data.getDictCode());
|
||||
if (existing == null || !NEWS_TYPE_DICT.equals(existing.getDictType()))
|
||||
{
|
||||
return error("数据不存在或无权修改");
|
||||
}
|
||||
data.setDictType(NEWS_TYPE_DICT);
|
||||
data.setUpdateBy(getUsername());
|
||||
return toAjax(dictDataService.updateDictData(data));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsCategory:remove')")
|
||||
@Log(title = "新闻类型字典", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{dictCodes}")
|
||||
public AjaxResult remove(@PathVariable Long[] dictCodes)
|
||||
{
|
||||
for (Long dictCode : dictCodes)
|
||||
{
|
||||
SysDictData d = dictDataService.selectDictDataById(dictCode);
|
||||
if (d == null || !NEWS_TYPE_DICT.equals(d.getDictType()))
|
||||
{
|
||||
return error("包含非法数据,已中止删除");
|
||||
}
|
||||
}
|
||||
dictDataService.deleteDictDataByIds(dictCodes);
|
||||
return success();
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.system.domain.HuayangNews;
|
||||
import com.ruoyi.system.service.IHuayangNewsService;
|
||||
|
||||
/**
|
||||
* 新闻动态管理端Controller
|
||||
*
|
||||
* @author huayang
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/news")
|
||||
public class HuayangNewsController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IHuayangNewsService newsService;
|
||||
|
||||
/**
|
||||
* 查询新闻动态列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(HuayangNews news)
|
||||
{
|
||||
startPage();
|
||||
List<HuayangNews> list = newsService.selectHuayangNewsList(news);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出新闻动态列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:export')")
|
||||
@Log(title = "新闻动态", businessType = BusinessType.EXPORT)
|
||||
@GetMapping("/export")
|
||||
public AjaxResult export(HuayangNews news)
|
||||
{
|
||||
List<HuayangNews> list = newsService.selectHuayangNewsList(news);
|
||||
ExcelUtil<HuayangNews> util = new ExcelUtil<HuayangNews>(HuayangNews.class);
|
||||
return util.exportExcel(list, "新闻数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新闻动态详情
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:query')")
|
||||
@GetMapping(value = "/{newsId}")
|
||||
public AjaxResult getInfo(@PathVariable("newsId") Long newsId)
|
||||
{
|
||||
return success(newsService.selectHuayangNewsById(newsId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增新闻动态
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:add')")
|
||||
@Log(title = "新闻动态", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody HuayangNews news)
|
||||
{
|
||||
news.setCreateBy(getUsername());
|
||||
return toAjax(newsService.insertHuayangNews(news));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改新闻动态
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:edit')")
|
||||
@Log(title = "新闻动态", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody HuayangNews news)
|
||||
{
|
||||
news.setUpdateBy(getUsername());
|
||||
return toAjax(newsService.updateHuayangNews(news));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除新闻动态
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:news:remove')")
|
||||
@Log(title = "新闻动态", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{newsIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] newsIds)
|
||||
{
|
||||
return toAjax(newsService.deleteHuayangNewsByIds(newsIds));
|
||||
}
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.system.domain.HuayangFixedSection;
|
||||
import com.ruoyi.system.domain.HuayangFixedSectionItem;
|
||||
import com.ruoyi.system.service.IHuayangFixedSectionService;
|
||||
|
||||
/**
|
||||
* 官网「新闻动态」页通栏横幅图(固定板块 news_banner,section_id=7)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/newsPage")
|
||||
public class HuayangNewsPageBannerController extends BaseController
|
||||
{
|
||||
private static final Long NEWS_BANNER_SECTION_ID = 8L;
|
||||
private static final String NEWS_BANNER_SECTION_CODE = "news_banner";
|
||||
private static final String HOME_BANNER_SECTION_CODE = "section1";
|
||||
private static final String NEWS_BANNER_ITEM_TYPE_HOME_NEWS_BG = "home_news_bg";
|
||||
|
||||
@Autowired
|
||||
private IHuayangFixedSectionService huayangFixedSectionService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsBanner:query')")
|
||||
@GetMapping("/banner")
|
||||
public AjaxResult getBanner()
|
||||
{
|
||||
HuayangFixedSection section = huayangFixedSectionService.selectHuayangFixedSectionBySectionId(NEWS_BANNER_SECTION_ID);
|
||||
HuayangFixedSection full = huayangFixedSectionService.selectHuayangFixedSectionByCode(NEWS_BANNER_SECTION_CODE);
|
||||
Map<String, Object> data = new HashMap<>(4);
|
||||
data.put("backgroundImage", section != null ? section.getBackgroundImage() : null);
|
||||
data.put("sectionText", section != null ? section.getSectionText() : null);
|
||||
data.put("homeBackgroundImage", getHomeFirstBannerImage());
|
||||
data.put("homeNewsBackgroundImage", getHomeNewsBackgroundImage(full));
|
||||
return AjaxResult.success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('huayang:newsBanner:edit')")
|
||||
@Log(title = "新闻动态页顶横幅", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/banner")
|
||||
public AjaxResult saveBanner(@RequestBody NewsPageBannerSaveRequest body)
|
||||
{
|
||||
HuayangFixedSection existing = huayangFixedSectionService.selectHuayangFixedSectionBySectionId(NEWS_BANNER_SECTION_ID);
|
||||
if (existing == null || !NEWS_BANNER_SECTION_CODE.equals(existing.getSectionCode()))
|
||||
{
|
||||
return AjaxResult.error("新闻动态顶图未初始化:请在数据库执行 backend/sql/huayang_news_dynamic_submenus.sql(含 fixed_section section_id=8)");
|
||||
}
|
||||
existing.setBackgroundImage(body != null ? body.getBackgroundImage() : null);
|
||||
existing.setSectionText(body != null ? body.getSectionText() : null);
|
||||
existing.setUpdateBy(getUsername());
|
||||
int updated = huayangFixedSectionService.updateHuayangFixedSection(existing);
|
||||
if (updated <= 0)
|
||||
{
|
||||
return toAjax(updated);
|
||||
}
|
||||
// 与首页顶部轮播图同步:保存新闻页横幅时,同时更新首页 section1 第一张轮播图
|
||||
if (existing.getBackgroundImage() != null && !existing.getBackgroundImage().trim().isEmpty())
|
||||
{
|
||||
upsertHomeFirstBannerImage(existing.getBackgroundImage().trim());
|
||||
}
|
||||
if (body != null && body.getHomeNewsBackgroundImage() != null)
|
||||
{
|
||||
upsertHomeNewsBackgroundImage(body.getHomeNewsBackgroundImage());
|
||||
}
|
||||
return toAjax(updated);
|
||||
}
|
||||
|
||||
private String getHomeFirstBannerImage()
|
||||
{
|
||||
HuayangFixedSection homeSection = huayangFixedSectionService.selectHuayangFixedSectionByCode(HOME_BANNER_SECTION_CODE);
|
||||
if (homeSection == null || homeSection.getItemList() == null || homeSection.getItemList().isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
HuayangFixedSectionItem first = homeSection.getItemList().get(0);
|
||||
return first != null ? first.getItemImage() : null;
|
||||
}
|
||||
|
||||
private void upsertHomeFirstBannerImage(String image)
|
||||
{
|
||||
HuayangFixedSection homeSection = huayangFixedSectionService.selectHuayangFixedSectionByCode(HOME_BANNER_SECTION_CODE);
|
||||
if (homeSection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<HuayangFixedSectionItem> items = homeSection.getItemList();
|
||||
HuayangFixedSectionItem first = (items != null && !items.isEmpty()) ? items.get(0) : null;
|
||||
if (first != null && first.getItemId() != null)
|
||||
{
|
||||
HuayangFixedSectionItem update = new HuayangFixedSectionItem();
|
||||
update.setItemId(first.getItemId());
|
||||
update.setItemImage(image);
|
||||
update.setUpdateBy(getUsername());
|
||||
huayangFixedSectionService.updateHuayangFixedSectionItem(update);
|
||||
return;
|
||||
}
|
||||
HuayangFixedSectionItem create = new HuayangFixedSectionItem();
|
||||
create.setSectionId(homeSection.getSectionId());
|
||||
create.setItemTitle("首页轮播图1");
|
||||
create.setItemType("slide");
|
||||
create.setSortOrder(1);
|
||||
create.setStatus("0");
|
||||
create.setItemImage(image);
|
||||
create.setCreateBy(getUsername());
|
||||
huayangFixedSectionService.insertHuayangFixedSectionItem(create);
|
||||
}
|
||||
|
||||
private String getHomeNewsBackgroundImage(HuayangFixedSection newsBanner)
|
||||
{
|
||||
if (newsBanner == null || newsBanner.getItemList() == null || newsBanner.getItemList().isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
HuayangFixedSectionItem target = newsBanner.getItemList().stream()
|
||||
.filter(it -> it != null && NEWS_BANNER_ITEM_TYPE_HOME_NEWS_BG.equals(it.getItemType()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return target != null ? target.getItemImage() : null;
|
||||
}
|
||||
|
||||
private void upsertHomeNewsBackgroundImage(String image)
|
||||
{
|
||||
HuayangFixedSection banner = huayangFixedSectionService.selectHuayangFixedSectionByCode(NEWS_BANNER_SECTION_CODE);
|
||||
if (banner == null || banner.getSectionId() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<HuayangFixedSectionItem> items = banner.getItemList();
|
||||
HuayangFixedSectionItem existing = null;
|
||||
if (items != null)
|
||||
{
|
||||
for (HuayangFixedSectionItem it : items)
|
||||
{
|
||||
if (it != null && NEWS_BANNER_ITEM_TYPE_HOME_NEWS_BG.equals(it.getItemType()))
|
||||
{
|
||||
existing = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existing != null && existing.getItemId() != null)
|
||||
{
|
||||
HuayangFixedSectionItem update = new HuayangFixedSectionItem();
|
||||
update.setItemId(existing.getItemId());
|
||||
update.setItemImage(image);
|
||||
update.setUpdateBy(getUsername());
|
||||
huayangFixedSectionService.updateHuayangFixedSectionItem(update);
|
||||
return;
|
||||
}
|
||||
HuayangFixedSectionItem create = new HuayangFixedSectionItem();
|
||||
create.setSectionId(banner.getSectionId());
|
||||
create.setItemTitle("首页华阳新闻背景图");
|
||||
create.setItemType(NEWS_BANNER_ITEM_TYPE_HOME_NEWS_BG);
|
||||
create.setSortOrder(999);
|
||||
create.setStatus("0");
|
||||
create.setItemImage(image);
|
||||
create.setCreateBy(getUsername());
|
||||
huayangFixedSectionService.insertHuayangFixedSectionItem(create);
|
||||
}
|
||||
|
||||
public static class NewsPageBannerSaveRequest
|
||||
{
|
||||
private String backgroundImage;
|
||||
private String sectionText;
|
||||
private String homeBackgroundImage;
|
||||
private String homeNewsBackgroundImage;
|
||||
|
||||
public String getBackgroundImage()
|
||||
{
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage)
|
||||
{
|
||||
this.backgroundImage = backgroundImage;
|
||||
}
|
||||
|
||||
public String getSectionText()
|
||||
{
|
||||
return sectionText;
|
||||
}
|
||||
|
||||
public void setSectionText(String sectionText)
|
||||
{
|
||||
this.sectionText = sectionText;
|
||||
}
|
||||
|
||||
public String getHomeBackgroundImage()
|
||||
{
|
||||
return homeBackgroundImage;
|
||||
}
|
||||
|
||||
public void setHomeBackgroundImage(String homeBackgroundImage)
|
||||
{
|
||||
this.homeBackgroundImage = homeBackgroundImage;
|
||||
}
|
||||
|
||||
public String getHomeNewsBackgroundImage()
|
||||
{
|
||||
return homeNewsBackgroundImage;
|
||||
}
|
||||
|
||||
public void setHomeNewsBackgroundImage(String homeNewsBackgroundImage)
|
||||
{
|
||||
this.homeNewsBackgroundImage = homeNewsBackgroundImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,675 @@
|
||||
package com.ruoyi.web.controller.huayang;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysMenu;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.system.domain.HuayangI18n;
|
||||
import com.ruoyi.system.domain.HuayangLanguage;
|
||||
import com.ruoyi.system.service.IHuayangSiteConfigService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
import com.ruoyi.system.service.IHuayangLanguageService;
|
||||
import com.ruoyi.system.service.ISysMenuService;
|
||||
import com.ruoyi.system.service.translation.ITranslationScanner;
|
||||
import com.ruoyi.system.service.translation.TranslationScannerRegistry;
|
||||
import com.ruoyi.system.service.translation.vo.PageInfoVO;
|
||||
import com.ruoyi.system.service.translation.vo.PageTranslationVO;
|
||||
import com.ruoyi.system.service.translation.vo.TranslationSectionVO;
|
||||
|
||||
/**
|
||||
* 翻译管理(按页面维度)
|
||||
*
|
||||
* <p>提供"翻译管理"菜单的核心能力:</p>
|
||||
* <ul>
|
||||
* <li>列出所有可翻译页面</li>
|
||||
* <li>实时扫描某页面下所有可翻译条目(中文原文 + 已存在翻译)</li>
|
||||
* <li>批量保存翻译</li>
|
||||
* <li>全局完成度统计</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author huayang
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/huayang/translation")
|
||||
public class HuayangTranslationController extends BaseController
|
||||
{
|
||||
/** 默认语种(不参与翻译,只作为原文兜底) */
|
||||
private static final String DEFAULT_LANG = "zh_cn";
|
||||
/** 翻译菜单选择配置 key(保存 sys_menu.menu_id 列表,逗号分隔) */
|
||||
private static final String MENU_SELECTION_CONFIG_KEY = "translation_menu_selected_ids";
|
||||
|
||||
@Autowired
|
||||
private TranslationScannerRegistry scannerRegistry;
|
||||
|
||||
@Autowired
|
||||
private IHuayangLanguageService languageService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
@Autowired
|
||||
private ISysMenuService sysMenuService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangSiteConfigService siteConfigService;
|
||||
|
||||
/**
|
||||
* 列出可翻译页面(用于左侧页面树)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/pages")
|
||||
public AjaxResult listPages()
|
||||
{
|
||||
Set<String> selectedModules = resolveSelectedModules();
|
||||
List<PageInfoVO> pages = new ArrayList<>();
|
||||
for (PageInfoVO page : scannerRegistry.listAllPages())
|
||||
{
|
||||
if (selectedModules.isEmpty() || selectedModules.contains(page.getPageModule()))
|
||||
{
|
||||
pages.add(page);
|
||||
}
|
||||
}
|
||||
return success(pages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出当前启用的目标语种(不含默认语种 zh_cn)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/targetLangs")
|
||||
public AjaxResult listTargetLangs()
|
||||
{
|
||||
List<String> langs = resolveTargetLangCodes();
|
||||
List<HuayangLanguage> all = languageService.selectEnabledLanguageList();
|
||||
// 返回完整对象(含 langName),便于前端展示
|
||||
List<HuayangLanguage> result = new ArrayList<>();
|
||||
if (all != null)
|
||||
{
|
||||
for (HuayangLanguage l : all)
|
||||
{
|
||||
if (l != null && langs.contains(l.getLangCode()))
|
||||
{
|
||||
result.add(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描某页面,返回完整翻译条目(含中文原文与已存在翻译)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/scan")
|
||||
public AjaxResult scan(@RequestParam("pageModule") String pageModule)
|
||||
{
|
||||
if (StringUtils.isBlank(pageModule))
|
||||
{
|
||||
return error("参数 pageModule 必填");
|
||||
}
|
||||
ITranslationScanner scanner = scannerRegistry.getScanner(pageModule);
|
||||
if (scanner == null)
|
||||
{
|
||||
return error("未知页面模块:" + pageModule);
|
||||
}
|
||||
List<String> targetLangs = resolveTargetLangCodes();
|
||||
if (targetLangs.isEmpty())
|
||||
{
|
||||
return error("未配置除默认语种外的可翻译语言,请先在「语言配置」中启用语种");
|
||||
}
|
||||
PageTranslationVO data = scanner.scan(targetLangs);
|
||||
Set<String> allowedSections = resolveAllowedSections(pageModule);
|
||||
if (!allowedSections.isEmpty())
|
||||
{
|
||||
List<TranslationSectionVO> sections = new ArrayList<>();
|
||||
for (TranslationSectionVO section : data.getSections())
|
||||
{
|
||||
if (section != null && allowedSections.contains(section.getSectionKey()))
|
||||
{
|
||||
sections.add(section);
|
||||
}
|
||||
}
|
||||
data.setSections(sections);
|
||||
recalculatePageStats(data, targetLangs);
|
||||
}
|
||||
return success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树选项(一级+二级)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/menuOptions")
|
||||
public AjaxResult menuOptions()
|
||||
{
|
||||
List<SysMenu> rawMenus = sysMenuService.selectMenuList(new SysMenu(), getUserId());
|
||||
List<SysMenu> candidateMenus = filterTranslationCandidateMenus(rawMenus);
|
||||
List<SysMenu> tree = sysMenuService.buildMenuTree(candidateMenus);
|
||||
List<MenuTreeNode> nodes = toMenuTreeNodes(tree);
|
||||
return success(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前已选择的菜单 ID 列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/menuSelection")
|
||||
public AjaxResult menuSelection()
|
||||
{
|
||||
return success(new ArrayList<>(resolveSelectedMenuIds()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存菜单选择
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:edit')")
|
||||
@Log(title = "翻译管理-菜单选择", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/menuSelection")
|
||||
public AjaxResult saveMenuSelection(@RequestBody MenuSelectionRequest request)
|
||||
{
|
||||
List<Long> menuIds = request == null ? Collections.emptyList() : request.getMenuIds();
|
||||
String value = joinMenuIds(menuIds);
|
||||
siteConfigService.upsertConfigValueByKey(
|
||||
MENU_SELECTION_CONFIG_KEY,
|
||||
value,
|
||||
"翻译管理-菜单选择",
|
||||
"翻译管理左侧菜单显示控制(sys_menu.menu_id 列表)"
|
||||
);
|
||||
return success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存翻译条目
|
||||
* <p>请求体示例:</p>
|
||||
* <pre>
|
||||
* {
|
||||
* "pageModule": "home",
|
||||
* "items": [
|
||||
* { "langCode": "en_us", "key": "home.banner.1.title", "value": "Ocean Food" },
|
||||
* { "langCode": "ja_jp", "key": "home.banner.1.title", "value": "" } // 空字符串=删除
|
||||
* ]
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:edit')")
|
||||
@Log(title = "翻译管理-批量保存", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/batch")
|
||||
public AjaxResult batchSave(@RequestBody BatchSaveRequest request)
|
||||
{
|
||||
if (request == null || request.getItems() == null || request.getItems().isEmpty())
|
||||
{
|
||||
return error("无可保存的条目");
|
||||
}
|
||||
if (StringUtils.isBlank(request.getPageModule()))
|
||||
{
|
||||
return error("参数 pageModule 必填");
|
||||
}
|
||||
|
||||
List<String> validLangs = resolveTargetLangCodes();
|
||||
List<HuayangI18n> rows = new ArrayList<>();
|
||||
for (BatchSaveItem item : request.getItems())
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (StringUtils.isBlank(item.getLangCode()) || StringUtils.isBlank(item.getKey()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 安全校验:禁止写入未启用的语种或默认语种
|
||||
if (DEFAULT_LANG.equals(item.getLangCode()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!validLangs.contains(item.getLangCode()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
HuayangI18n row = new HuayangI18n();
|
||||
row.setLangCode(item.getLangCode());
|
||||
row.setI18nKey(item.getKey());
|
||||
row.setI18nValue(item.getValue());
|
||||
row.setPageModule(request.getPageModule());
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
if (rows.isEmpty())
|
||||
{
|
||||
return error("没有有效的条目可保存(语种未启用或缺失 key)");
|
||||
}
|
||||
|
||||
IHuayangI18nService.BatchUpsertStat stat =
|
||||
i18nService.batchUpsertTranslations(rows, getUsername());
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("upserted", stat.getUpserted());
|
||||
data.put("deleted", stat.getDeleted());
|
||||
return AjaxResult.success("保存成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局完成度统计:每个页面 × 每个语种 → 已翻译数 / 总数
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('huayang:translation:list')")
|
||||
@GetMapping("/stats")
|
||||
public AjaxResult stats()
|
||||
{
|
||||
List<PageInfoVO> pages = scannerRegistry.listAllPages();
|
||||
List<String> targetLangs = resolveTargetLangCodes();
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
|
||||
for (PageInfoVO page : pages)
|
||||
{
|
||||
ITranslationScanner scanner = scannerRegistry.getScanner(page.getPageModule());
|
||||
if (scanner == null) continue;
|
||||
// 实时扫描以获取总 key 数(数据量小,性能可接受;若数据量增长后可改为缓存)
|
||||
PageTranslationVO pageVO = scanner.scan(targetLangs);
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("pageModule", page.getPageModule());
|
||||
row.put("pageName", page.getPageName());
|
||||
row.put("totalKeys", pageVO.getTotalKeys());
|
||||
row.put("translated", pageVO.getTranslatedByLang());
|
||||
result.add(row);
|
||||
}
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("targetLangs", targetLangs);
|
||||
data.put("pages", result);
|
||||
return success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前启用、且非默认语种的目标语种列表
|
||||
*/
|
||||
private List<String> resolveTargetLangCodes()
|
||||
{
|
||||
List<HuayangLanguage> langs = languageService.selectEnabledLanguageList();
|
||||
if (langs == null) return Collections.emptyList();
|
||||
List<String> result = new ArrayList<>();
|
||||
for (HuayangLanguage l : langs)
|
||||
{
|
||||
if (l == null) continue;
|
||||
String code = l.getLangCode();
|
||||
if (StringUtils.isBlank(code) || DEFAULT_LANG.equals(code)) continue;
|
||||
result.add(code);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<Long> resolveSelectedMenuIds()
|
||||
{
|
||||
String value = siteConfigService.selectConfigValueByKey(MENU_SELECTION_CONFIG_KEY);
|
||||
if (StringUtils.isBlank(value))
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Long> result = new LinkedHashSet<>();
|
||||
for (String part : value.split(","))
|
||||
{
|
||||
if (StringUtils.isBlank(part))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String trimmed = part.trim();
|
||||
if (!trimmed.matches("\\d+"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
result.add(Long.valueOf(trimmed));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Set<String> resolveSelectedModules()
|
||||
{
|
||||
Set<Long> selectedIds = resolveSelectedMenuIds();
|
||||
if (selectedIds.isEmpty())
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> modules = new HashSet<>();
|
||||
for (SysMenu menu : sysMenuService.selectMenuList(new SysMenu(), getUserId()))
|
||||
{
|
||||
if (menu == null || !selectedIds.contains(menu.getMenuId()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String module = resolveMenuModule(menu);
|
||||
if (StringUtils.isNotBlank(module))
|
||||
{
|
||||
modules.add(module);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
private Set<String> resolveAllowedSections(String pageModule)
|
||||
{
|
||||
Set<Long> selectedIds = resolveSelectedMenuIds();
|
||||
if (selectedIds.isEmpty())
|
||||
{
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<String> sections = new LinkedHashSet<>();
|
||||
for (SysMenu menu : sysMenuService.selectMenuList(new SysMenu(), getUserId()))
|
||||
{
|
||||
if (menu == null || !selectedIds.contains(menu.getMenuId()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String module = resolveMenuModule(menu);
|
||||
if (module == null || !module.equals(pageModule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String section = resolveSectionKey(pageModule, menu);
|
||||
if (StringUtils.isNotBlank(section))
|
||||
{
|
||||
sections.add(section);
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
private String resolveMenuModule(SysMenu menu)
|
||||
{
|
||||
String component = lower(menu.getComponent());
|
||||
String path = lower(menu.getPath());
|
||||
String perms = lower(menu.getPerms());
|
||||
if (component.contains("homeworkbench")
|
||||
|| component.contains("huayang/home")
|
||||
|| component.contains("huayang/company")
|
||||
|| component.contains("huayang/fixedsection")
|
||||
|| path.contains("home")
|
||||
|| perms.startsWith("huayang:home")
|
||||
|| perms.startsWith("huayang:company")
|
||||
|| perms.startsWith("huayang:fixedsection"))
|
||||
{
|
||||
return "home";
|
||||
}
|
||||
if (component.contains("huayang/product")
|
||||
|| path.contains("product")
|
||||
|| perms.startsWith("huayang:product")
|
||||
|| perms.startsWith("huayang:productcategory")
|
||||
|| perms.startsWith("huayang:productinquiry")
|
||||
|| perms.startsWith("huayang:productbanner"))
|
||||
{
|
||||
return "product";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveSectionKey(String pageModule, SysMenu menu)
|
||||
{
|
||||
String perms = lower(menu.getPerms());
|
||||
String path = lower(menu.getPath());
|
||||
String component = lower(menu.getComponent());
|
||||
|
||||
if ("product".equals(pageModule))
|
||||
{
|
||||
if (perms.equals("huayang:productcategory:list") || path.equals("productcategory") || component.contains("huayang/productcategory/index"))
|
||||
{
|
||||
return "category";
|
||||
}
|
||||
if (perms.equals("huayang:product:list") || path.equals("product") || component.contains("huayang/product/index"))
|
||||
{
|
||||
return "item";
|
||||
}
|
||||
}
|
||||
|
||||
// home 目前不细分到 section,避免误过滤
|
||||
return null;
|
||||
}
|
||||
|
||||
private void recalculatePageStats(PageTranslationVO page, List<String> targetLangs)
|
||||
{
|
||||
int totalKeys = 0;
|
||||
Map<String, Integer> translated = new LinkedHashMap<>();
|
||||
for (String lang : targetLangs)
|
||||
{
|
||||
translated.put(lang, 0);
|
||||
}
|
||||
for (TranslationSectionVO section : page.getSections())
|
||||
{
|
||||
if (section == null || section.getItems() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (var item : section.getItems())
|
||||
{
|
||||
if (item == null || item.getFields() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (var field : item.getFields())
|
||||
{
|
||||
if (field == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
totalKeys++;
|
||||
for (String lang : targetLangs)
|
||||
{
|
||||
if (field.getTranslations() != null
|
||||
&& field.getTranslations().get(lang) != null
|
||||
&& StringUtils.isNotBlank(field.getTranslations().get(lang).getValue()))
|
||||
{
|
||||
translated.put(lang, translated.get(lang) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
page.setTotalKeys(totalKeys);
|
||||
page.setTranslatedByLang(translated);
|
||||
}
|
||||
|
||||
private List<SysMenu> filterTranslationCandidateMenus(List<SysMenu> menus)
|
||||
{
|
||||
if (menus == null)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<Long, SysMenu> byId = new HashMap<>();
|
||||
for (SysMenu m : menus)
|
||||
{
|
||||
if (m != null && m.getMenuId() != null)
|
||||
{
|
||||
byId.put(m.getMenuId(), m);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Long> includeIds = new HashSet<>();
|
||||
for (SysMenu m : menus)
|
||||
{
|
||||
if (m == null || m.getMenuId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!isTranslationRelatedMenu(m))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
includeIds.add(m.getMenuId());
|
||||
Long parentId = m.getParentId();
|
||||
while (parentId != null && parentId > 0 && byId.containsKey(parentId))
|
||||
{
|
||||
includeIds.add(parentId);
|
||||
parentId = byId.get(parentId).getParentId();
|
||||
}
|
||||
}
|
||||
|
||||
List<SysMenu> result = new ArrayList<>();
|
||||
for (SysMenu m : menus)
|
||||
{
|
||||
if (m != null && includeIds.contains(m.getMenuId()))
|
||||
{
|
||||
result.add(m);
|
||||
}
|
||||
}
|
||||
result.sort(Comparator.comparing(SysMenu::getParentId, Comparator.nullsLast(Long::compareTo))
|
||||
.thenComparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(SysMenu::getMenuId, Comparator.nullsLast(Long::compareTo)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isTranslationRelatedMenu(SysMenu menu)
|
||||
{
|
||||
if (menu == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ("底部导航栏显示".equals(menu.getMenuName()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String menuType = lower(menu.getMenuType());
|
||||
if ("f".equals(menuType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String perms = lower(menu.getPerms());
|
||||
String component = lower(menu.getComponent());
|
||||
String path = lower(menu.getPath());
|
||||
return perms.startsWith("huayang:")
|
||||
|| component.startsWith("huayang/")
|
||||
|| path.startsWith("huayangsite/");
|
||||
}
|
||||
|
||||
private List<MenuTreeNode> toMenuTreeNodes(List<SysMenu> menus)
|
||||
{
|
||||
List<MenuTreeNode> result = new ArrayList<>();
|
||||
if (menus == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
for (SysMenu m : menus)
|
||||
{
|
||||
if (m == null || m.getMenuId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
MenuTreeNode node = new MenuTreeNode();
|
||||
node.setId(m.getMenuId());
|
||||
node.setLabel(m.getMenuName());
|
||||
node.setPageModule(resolveMenuModule(m));
|
||||
node.setSectionKey(resolveSectionKey(resolveMenuModule(m), m));
|
||||
node.setPath(m.getPath());
|
||||
node.setComponent(m.getComponent());
|
||||
node.setPerms(m.getPerms());
|
||||
node.setRemark(m.getRemark());
|
||||
node.setChildren(toMenuTreeNodes(m.getChildren()));
|
||||
result.add(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String joinMenuIds(List<Long> menuIds)
|
||||
{
|
||||
if (menuIds == null || menuIds.isEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
Set<Long> unique = new LinkedHashSet<>(menuIds);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Long id : unique)
|
||||
{
|
||||
if (id == null || id <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sb.length() > 0)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(id);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String lower(String value)
|
||||
{
|
||||
return value == null ? "" : value.toLowerCase();
|
||||
}
|
||||
|
||||
// ============== 请求体 DTO ==============
|
||||
|
||||
public static class MenuTreeNode
|
||||
{
|
||||
private Long id;
|
||||
private String label;
|
||||
private String pageModule;
|
||||
private String sectionKey;
|
||||
private String path;
|
||||
private String component;
|
||||
private String perms;
|
||||
private String remark;
|
||||
private List<MenuTreeNode> children = new ArrayList<>();
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getLabel() { return label; }
|
||||
public void setLabel(String label) { this.label = label; }
|
||||
public String getPageModule() { return pageModule; }
|
||||
public void setPageModule(String pageModule) { this.pageModule = pageModule; }
|
||||
public String getSectionKey() { return sectionKey; }
|
||||
public void setSectionKey(String sectionKey) { this.sectionKey = sectionKey; }
|
||||
public String getPath() { return path; }
|
||||
public void setPath(String path) { this.path = path; }
|
||||
public String getComponent() { return component; }
|
||||
public void setComponent(String component) { this.component = component; }
|
||||
public String getPerms() { return perms; }
|
||||
public void setPerms(String perms) { this.perms = perms; }
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String remark) { this.remark = remark; }
|
||||
public List<MenuTreeNode> getChildren() { return children; }
|
||||
public void setChildren(List<MenuTreeNode> children) { this.children = children; }
|
||||
}
|
||||
|
||||
public static class MenuSelectionRequest
|
||||
{
|
||||
private List<Long> menuIds = new ArrayList<>();
|
||||
public List<Long> getMenuIds() { return menuIds; }
|
||||
public void setMenuIds(List<Long> menuIds) { this.menuIds = menuIds; }
|
||||
}
|
||||
|
||||
public static class BatchSaveRequest
|
||||
{
|
||||
private String pageModule;
|
||||
private List<BatchSaveItem> items;
|
||||
public String getPageModule() { return pageModule; }
|
||||
public void setPageModule(String pageModule) { this.pageModule = pageModule; }
|
||||
public List<BatchSaveItem> getItems() { return items; }
|
||||
public void setItems(List<BatchSaveItem> items) { this.items = items; }
|
||||
}
|
||||
|
||||
public static class BatchSaveItem
|
||||
{
|
||||
private String langCode;
|
||||
private String key;
|
||||
private String value;
|
||||
public String getLangCode() { return langCode; }
|
||||
public void setLangCode(String langCode) { this.langCode = langCode; }
|
||||
public String getKey() { return key; }
|
||||
public void setKey(String key) { this.key = key; }
|
||||
public String getValue() { return value; }
|
||||
public void setValue(String value) { this.value = value; }
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,12 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -10,6 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
@ -48,10 +54,31 @@ public class SiteCompanyController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/** 占位串完整匹配表(逗号分隔初始化),调整时请改此类静态代码块 */
|
||||
private static final int PLACEHOLDER_PLAIN_MAX_LEN = 200;
|
||||
|
||||
private static final int PLACEHOLDER_SINGLE_CHAR_REPEAT_MAX_LEN = 16;
|
||||
|
||||
private static final Set<String> PLACEHOLDER_EXACT_MATCH_SET;
|
||||
|
||||
static
|
||||
{
|
||||
Set<String> s = new HashSet<>();
|
||||
for (String part : "qq,qw,qwe,qwer,qweqwe,qweqeqe,qweqweqweqwe,qqq,qqqq,ww,www,wwww,vv,vvv,vvvv,test,xxx,asd,asdf".split(","))
|
||||
{
|
||||
String t = part.trim().toLowerCase(Locale.ROOT);
|
||||
if (!t.isEmpty())
|
||||
{
|
||||
s.add(t);
|
||||
}
|
||||
}
|
||||
PLACEHOLDER_EXACT_MATCH_SET = Collections.unmodifiableSet(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公司简介分块。
|
||||
* <p>可选 {@code lang}:非 zh_cn 时,用 i18n key {@code company.section.{sectionCode}.title}、
|
||||
* {@code company.section.{sectionCode}.content} 覆盖(content 受库表 i18n_value 长度限制,长文建议后续扩展字段)。</p>
|
||||
* <p>可选 {@code lang}:非 zh_cn 时,优先 {@code extra_data.langContent}(翻译管理),缺省再用词典
|
||||
* {@code company.section.{sectionCode}.title|content}。</p>
|
||||
*/
|
||||
@GetMapping("/sections")
|
||||
public AjaxResult sections(@RequestParam(value = "lang", required = false) String lang)
|
||||
@ -63,11 +90,22 @@ public class SiteCompanyController extends BaseController
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/** 与门户 {@code normalizeLangCode} 对齐:query 可为 ja-JP、JA_JP、jp 等 */
|
||||
private static String normalizeSiteLang(String lang) {
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
String s = lang.trim().toLowerCase(Locale.ROOT).replace('-', '_');
|
||||
if (s.startsWith("zh")) {
|
||||
return "zh_cn";
|
||||
}
|
||||
if (s.startsWith("en")) {
|
||||
return "en_us";
|
||||
}
|
||||
if (s.startsWith("ja") || s.startsWith("jp")) {
|
||||
return "ja_jp";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private void applySectionLocale(String langNorm, List<HuayangCompanySection> list) {
|
||||
@ -83,25 +121,207 @@ public class SiteCompanyController extends BaseController
|
||||
keys.add("company.section." + s.getSectionCode() + ".content");
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
if (dict.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (HuayangCompanySection s : list) {
|
||||
if (StringUtils.isBlank(s.getSectionCode())) {
|
||||
continue;
|
||||
}
|
||||
String prefix = "company.section." + s.getSectionCode() + ".";
|
||||
String t = dict.get(prefix + "title");
|
||||
// 翻译管理写入的 extra_data.langContent 优先,但测试占位串不会下发到门户。
|
||||
// 无有效手工译文时再尝试 huayang_i18n 词典。
|
||||
String manualTitle = getManualLangTitleFromExtraData(s.getExtraData(), langNorm);
|
||||
String dictTitle = dict.get(prefix + "title");
|
||||
String t = pickBestLocalizedSectionText(manualTitle, dictTitle);
|
||||
if (StringUtils.isNotEmpty(t)) {
|
||||
s.setSectionTitle(t);
|
||||
}
|
||||
String c = dict.get(prefix + "content");
|
||||
String manualContent = getManualLangContentFromExtraData(s.getExtraData(), langNorm);
|
||||
String dictContent = dict.get(prefix + "content");
|
||||
String c = pickBestLocalizedSectionText(manualContent, dictContent);
|
||||
if (StringUtils.isNotEmpty(c)) {
|
||||
s.setSectionContent(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工译文(翻译管理)优先;无手工时再尝试词典。
|
||||
*/
|
||||
private static String pickBestLocalizedSectionText(String manual, String dict) {
|
||||
if (StringUtils.isNotBlank(manual) && !looksLikePlaceholderTranslation(manual)) {
|
||||
return manual.trim();
|
||||
}
|
||||
if (StringUtils.isNotBlank(dict) && !looksLikePlaceholderTranslation(dict)) {
|
||||
return dict;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译后台误填的测试文案(完整匹配见 {@link #PLACEHOLDER_EXACT_MATCH_SET})。
|
||||
*/
|
||||
private static boolean looksLikePlaceholderTranslation(String raw) {
|
||||
if (StringUtils.isBlank(raw)) {
|
||||
return true;
|
||||
}
|
||||
String trimmed = raw.trim();
|
||||
String plain =
|
||||
trimmed.replaceAll("(?i)<br\\s*/?>", " ")
|
||||
.replaceAll("<[^>]+>", " ")
|
||||
.replaceAll(" ", " ")
|
||||
.replaceAll("\\s+", " ")
|
||||
.trim();
|
||||
if (StringUtils.isBlank(plain)) {
|
||||
return true;
|
||||
}
|
||||
if (plain.length() > PLACEHOLDER_PLAIN_MAX_LEN) {
|
||||
return false;
|
||||
}
|
||||
String lower = plain.toLowerCase(Locale.ROOT);
|
||||
if (PLACEHOLDER_EXACT_MATCH_SET.contains(lower)) {
|
||||
return true;
|
||||
}
|
||||
if (lower.matches("^(qwe|wer|asd|zxc|www|vvv|xxx)+$")) {
|
||||
return true;
|
||||
}
|
||||
if (plain.length() <= PLACEHOLDER_SINGLE_CHAR_REPEAT_MAX_LEN && plain.codePoints().distinct().count() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 section.extra_data 解析手动翻译(标题)。
|
||||
* <p>约定:{@code langContent.{lang}} 可为 {@code {"title":"...","content":"..."}},
|
||||
* 标题从此处读取;旧版仅有正文 JSON 字符串时不返回标题。</p>
|
||||
*/
|
||||
private String getManualLangTitleFromExtraData(String extraData, String langNorm) {
|
||||
Object entry = getLangContentEntry(extraData, langNorm);
|
||||
if (!(entry instanceof JSONObject)) {
|
||||
return null;
|
||||
}
|
||||
String title = ((JSONObject) entry).getString("title");
|
||||
return StringUtils.isNotBlank(title) ? title : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 section.extra_data 解析手动翻译(正文)。
|
||||
* <p>约定:
|
||||
* <ul>
|
||||
* <li>新结构:{@code langContent.en_us} = {@code {"title":"...","content":"..."}}</li>
|
||||
* <li>旧结构:{@code langContent.en_us} = 整段 HTML 字符串(兼容)</li>
|
||||
* </ul>
|
||||
*/
|
||||
private String getManualLangContentFromExtraData(String extraData, String langNorm) {
|
||||
Object entry = getLangContentEntry(extraData, langNorm);
|
||||
if (entry instanceof JSONObject) {
|
||||
String content = ((JSONObject) entry).getString("content");
|
||||
return StringUtils.isNotBlank(content) ? content : null;
|
||||
}
|
||||
if (entry instanceof String) {
|
||||
String value = (String) entry;
|
||||
return StringUtils.isNotBlank(value) ? value : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getLangContentEntry(String extraData, String langNorm) {
|
||||
if (StringUtils.isBlank(extraData) || StringUtils.isBlank(langNorm)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(extraData);
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject langContent = root.getJSONObject("langContent");
|
||||
if (langContent == null) {
|
||||
langContent = root.getJSONObject("lang_content");
|
||||
}
|
||||
if (langContent == null) {
|
||||
return null;
|
||||
}
|
||||
String canonical = normalizeSiteLang(langNorm);
|
||||
Object direct = langContent.get(canonical);
|
||||
if (direct != null) {
|
||||
return direct;
|
||||
}
|
||||
for (String k : langContent.keySet()) {
|
||||
if (canonical.equals(normalizeSiteLang(k))) {
|
||||
return langContent.get(k);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String storeExtraJsonField(String extraData, String langNorm, String field) {
|
||||
Object entry = getLangContentEntry(extraData, langNorm);
|
||||
if (!(entry instanceof JSONObject)) {
|
||||
return null;
|
||||
}
|
||||
String v = ((JSONObject) entry).getString(field);
|
||||
return StringUtils.isNotBlank(v) && !looksLikePlaceholderTranslation(v) ? v : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 店面展示页(about_showcase)条目的多语言:i18n 或 extra_data.langContent 的 name/address/phone。
|
||||
*/
|
||||
private void applyAboutShowcaseStoreLocale(String langNorm, List<HuayangCompanyStore> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCompanyStore s : list) {
|
||||
if (s.getStoreId() == null) {
|
||||
continue;
|
||||
}
|
||||
String sid = String.valueOf(s.getStoreId());
|
||||
keys.add("company.store." + sid + ".name");
|
||||
keys.add("company.store." + sid + ".address");
|
||||
keys.add("company.store." + sid + ".phone");
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCompanyStore s : list) {
|
||||
if (s.getStoreId() == null) {
|
||||
continue;
|
||||
}
|
||||
String sid = String.valueOf(s.getStoreId());
|
||||
String p = "company.store." + sid + ".";
|
||||
|
||||
String name = dict.get(p + "name");
|
||||
if (StringUtils.isBlank(name)) {
|
||||
name = storeExtraJsonField(s.getExtraData(), langNorm, "name");
|
||||
}
|
||||
if (StringUtils.isBlank(name)) {
|
||||
name = storeExtraJsonField(s.getExtraData(), langNorm, "title");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(name)) {
|
||||
s.setStoreName(name);
|
||||
}
|
||||
|
||||
String address = dict.get(p + "address");
|
||||
if (StringUtils.isBlank(address)) {
|
||||
address = storeExtraJsonField(s.getExtraData(), langNorm, "address");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(address)) {
|
||||
s.setStoreAddress(address);
|
||||
}
|
||||
|
||||
String phone = dict.get(p + "phone");
|
||||
if (StringUtils.isBlank(phone)) {
|
||||
phone = storeExtraJsonField(s.getExtraData(), langNorm, "phone");
|
||||
}
|
||||
if (StringUtils.isBlank(phone)) {
|
||||
phone = storeExtraJsonField(s.getExtraData(), langNorm, "tel");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(phone)) {
|
||||
s.setStorePhone(phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取店面展示列表(首页等通用场景,不含页面专用/地图专表类型)
|
||||
*/
|
||||
@ -117,29 +337,18 @@ public class SiteCompanyController extends BaseController
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 门户「发展历程」页底部店面轮播:仅 {@code store_type = history}
|
||||
*/
|
||||
@GetMapping("/stores/history")
|
||||
public AjaxResult storesForHistoryPage()
|
||||
{
|
||||
HuayangCompanyStore query = new HuayangCompanyStore();
|
||||
query.setStatus("0");
|
||||
query.setStoreType("history");
|
||||
List<HuayangCompanyStore> list = storeService.selectHuayangCompanyStoreList(query);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 门户「店面展示页」(关于我们模块):仅 {@code store_type = about_showcase}
|
||||
* <p>可选 {@code lang}:详见 {@link #applyAboutShowcaseStoreLocale}。</p>
|
||||
*/
|
||||
@GetMapping("/stores/about-showcase")
|
||||
public AjaxResult storesForAboutPage()
|
||||
public AjaxResult storesForAboutPage(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
HuayangCompanyStore query = new HuayangCompanyStore();
|
||||
query.setStatus("0");
|
||||
query.setStoreType("about_showcase");
|
||||
List<HuayangCompanyStore> list = storeService.selectHuayangCompanyStoreList(query);
|
||||
applyAboutShowcaseStoreLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@ -147,20 +356,20 @@ public class SiteCompanyController extends BaseController
|
||||
* 门户「全国门店」页左侧「店面位置」列表:仅 {@code store_type = map_location}
|
||||
*/
|
||||
@GetMapping("/stores/map-locations")
|
||||
public AjaxResult storesForMapPage()
|
||||
public AjaxResult storesForMapPage(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
HuayangCompanyStore query = new HuayangCompanyStore();
|
||||
query.setStatus("0");
|
||||
query.setStoreType("map_location");
|
||||
List<HuayangCompanyStore> list = storeService.selectHuayangCompanyStoreList(query);
|
||||
applyAboutShowcaseStoreLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/** 不参与通用 {@link #stores()} 列表的类型(专页/专块数据) */
|
||||
private static boolean isExcludedFromGeneralStoresList(String storeType)
|
||||
{
|
||||
return "history".equals(storeType) || "about_showcase".equals(storeType)
|
||||
|| "map_location".equals(storeType);
|
||||
return "about_showcase".equals(storeType) || "map_location".equals(storeType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,14 +385,53 @@ public class SiteCompanyController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取品质技术列表
|
||||
* 获取品质技术列表。
|
||||
* <p>可选 {@code lang}:非 zh_cn 时,优先使用 {@code extra_data.langContent}(翻译管理手工保存),
|
||||
* 缺省再使用词典 {@code company.tech.{techId}.title|.content}。</p>
|
||||
*/
|
||||
@GetMapping("/tech")
|
||||
public AjaxResult techList()
|
||||
public AjaxResult techList(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
HuayangCompanyTech query = new HuayangCompanyTech();
|
||||
query.setStatus("0");
|
||||
List<HuayangCompanyTech> list = techService.selectHuayangCompanyTechList(query);
|
||||
applyTechLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
private void applyTechLocale(String langNorm, List<HuayangCompanyTech> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCompanyTech t : list) {
|
||||
if (t.getTechId() == null) {
|
||||
continue;
|
||||
}
|
||||
String id = String.valueOf(t.getTechId());
|
||||
keys.add("company.tech." + id + ".title");
|
||||
keys.add("company.tech." + id + ".content");
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCompanyTech t : list) {
|
||||
if (t.getTechId() == null) {
|
||||
continue;
|
||||
}
|
||||
String id = String.valueOf(t.getTechId());
|
||||
String prefix = "company.tech." + id + ".";
|
||||
// 翻译管理写入的 extra_data.langContent 优先于词典:避免扫描任务把中文误写入 i18n 后盖住手工英/日文
|
||||
String manualTitle = getManualLangTitleFromExtraData(t.getExtraData(), langNorm);
|
||||
String dictTitle = dict.get(prefix + "title");
|
||||
String title = pickBestLocalizedSectionText(manualTitle, dictTitle);
|
||||
if (StringUtils.isNotEmpty(title)) {
|
||||
t.setTechTitle(title);
|
||||
}
|
||||
String manualContent = getManualLangContentFromExtraData(t.getExtraData(), langNorm);
|
||||
String dictContent = dict.get(prefix + "content");
|
||||
String content = pickBestLocalizedSectionText(manualContent, dictContent);
|
||||
if (StringUtils.isNotEmpty(content)) {
|
||||
t.setTechContent(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -13,7 +15,9 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangCultureCategory;
|
||||
import com.ruoyi.system.domain.HuayangCultureTag;
|
||||
import com.ruoyi.system.service.IHuayangCultureCategoryService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
import com.ruoyi.system.service.IHuayangCultureTagService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 门户:华阳文化页公开数据
|
||||
@ -30,17 +34,22 @@ public class SiteCultureController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangCultureTagService cultureTagService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
@Anonymous
|
||||
@GetMapping("/categories/public")
|
||||
public AjaxResult categoriesPublic()
|
||||
public AjaxResult categoriesPublic(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
List<HuayangCultureCategory> list = cultureCategoryService.selectHuayangCultureCategoryPublicList();
|
||||
applyCultureCategoryLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@Anonymous
|
||||
@GetMapping("/tags/public")
|
||||
public AjaxResult tagsPublic(@RequestParam("categoryId") Long categoryId)
|
||||
public AjaxResult tagsPublic(@RequestParam("categoryId") Long categoryId,
|
||||
@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
if (categoryId == null || categoryId <= 0)
|
||||
{
|
||||
@ -52,12 +61,14 @@ public class SiteCultureController extends BaseController
|
||||
return error("分类不存在或已停用");
|
||||
}
|
||||
List<HuayangCultureTag> list = cultureTagService.selectHuayangCultureTagPublicListByCategoryId(categoryId);
|
||||
applyCultureTagLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@Anonymous
|
||||
@GetMapping("/tags/{tagId}/public")
|
||||
public AjaxResult tagDetailPublic(@PathVariable("tagId") Long tagId)
|
||||
public AjaxResult tagDetailPublic(@PathVariable("tagId") Long tagId,
|
||||
@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
HuayangCultureTag tag = cultureTagService.selectHuayangCultureTagPublicById(tagId);
|
||||
if (tag == null)
|
||||
@ -70,6 +81,64 @@ public class SiteCultureController extends BaseController
|
||||
return error("内容不存在或已下架");
|
||||
}
|
||||
cultureTagService.incrementReadCount(tagId);
|
||||
return success(cultureTagService.selectHuayangCultureTagById(tagId));
|
||||
HuayangCultureTag detail = cultureTagService.selectHuayangCultureTagById(tagId);
|
||||
applyCultureTagDetailLocale(normalizeSiteLang(lang), detail);
|
||||
return success(detail);
|
||||
}
|
||||
|
||||
private static String normalizeSiteLang(String lang) {
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void applyCultureCategoryLocale(String langNorm, List<HuayangCultureCategory> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) return;
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCultureCategory item : list) {
|
||||
if (item.getCategoryId() != null) {
|
||||
keys.add("culture.category." + item.getCategoryId() + ".name");
|
||||
}
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCultureCategory item : list) {
|
||||
if (item.getCategoryId() == null) continue;
|
||||
String v = dict.get("culture.category." + item.getCategoryId() + ".name");
|
||||
if (StringUtils.isNotEmpty(v)) item.setCategoryName(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCultureTagLocale(String langNorm, List<HuayangCultureTag> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) return;
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCultureTag item : list) {
|
||||
if (item.getTagId() != null) {
|
||||
keys.add("culture.tag." + item.getTagId() + ".title");
|
||||
keys.add("culture.tag." + item.getTagId() + ".content");
|
||||
}
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCultureTag item : list) {
|
||||
if (item.getTagId() == null) continue;
|
||||
String prefix = "culture.tag." + item.getTagId() + ".";
|
||||
String t = dict.get(prefix + "title");
|
||||
if (StringUtils.isNotEmpty(t)) item.setTagTitle(t);
|
||||
String c = dict.get(prefix + "content");
|
||||
if (StringUtils.isNotEmpty(c)) item.setTagContent(c);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCultureTagDetailLocale(String langNorm, HuayangCultureTag detail) {
|
||||
if ("zh_cn".equals(langNorm) || detail == null || detail.getTagId() == null) return;
|
||||
String prefix = "culture.tag." + detail.getTagId() + ".";
|
||||
List<String> keys = new ArrayList<>(2);
|
||||
keys.add(prefix + "title");
|
||||
keys.add(prefix + "content");
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
String t = dict.get(prefix + "title");
|
||||
if (StringUtils.isNotEmpty(t)) detail.setTagTitle(t);
|
||||
String c = dict.get(prefix + "content");
|
||||
if (StringUtils.isNotEmpty(c)) detail.setTagContent(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package com.ruoyi.web.controller.site;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
@ -10,10 +11,9 @@ import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.domain.HuayangContact;
|
||||
import com.ruoyi.system.domain.HuayangJoinApply;
|
||||
import com.ruoyi.system.domain.HuayangMessage;
|
||||
import com.ruoyi.system.service.IHuayangContactService;
|
||||
import com.ruoyi.system.service.IHuayangJoinApplyService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
import com.ruoyi.system.service.IHuayangMessageService;
|
||||
import com.ruoyi.system.service.IHuayangSiteConfigService;
|
||||
|
||||
@ -33,34 +33,69 @@ public class SiteCustomerController extends BaseController
|
||||
private IHuayangMessageService messageService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangJoinApplyService joinApplyService;
|
||||
private IHuayangSiteConfigService siteConfigService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangSiteConfigService siteConfigService;
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
* 获取联系信息(公开)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/contact/list")
|
||||
public AjaxResult contactList()
|
||||
public AjaxResult contactList(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
List<HuayangContact> list = contactService.selectEnabledContactList();
|
||||
Map<String, String> siteValueByType = new HashMap<>(3);
|
||||
siteValueByType.put("phone", siteConfigService.selectConfigValueByKey("contact_phone"));
|
||||
siteValueByType.put("email", siteConfigService.selectConfigValueByKey("contact_email"));
|
||||
siteValueByType.put("address", siteConfigService.selectConfigValueByKey("contact_address"));
|
||||
for (HuayangContact item : list)
|
||||
if (list == null || list.isEmpty())
|
||||
{
|
||||
String siteValue = siteValueByType.get(item.getContactType());
|
||||
if (StringUtils.isNotBlank(siteValue))
|
||||
list = buildFallbackContacts();
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, String> siteValueByType = new HashMap<>(3);
|
||||
siteValueByType.put("phone", siteConfigService.selectConfigValueByKey("contact_phone"));
|
||||
siteValueByType.put("email", siteConfigService.selectConfigValueByKey("contact_email"));
|
||||
siteValueByType.put("address", siteConfigService.selectConfigValueByKey("contact_address"));
|
||||
for (HuayangContact item : list)
|
||||
{
|
||||
item.setContactValue(siteValue);
|
||||
if (item == null || StringUtils.isNotBlank(item.getContactValue()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String siteValue = siteValueByType.get(item.getContactType());
|
||||
if (StringUtils.isNotBlank(siteValue))
|
||||
{
|
||||
item.setContactValue(siteValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
applyContactLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
private List<HuayangContact> buildFallbackContacts()
|
||||
{
|
||||
List<HuayangContact> list = new ArrayList<>();
|
||||
appendConfigGroup(list, siteConfigService.selectConfigValueByKey("contact_phone"), "phone", "商务热线", 0);
|
||||
appendConfigGroup(list, siteConfigService.selectConfigValueByKey("contact_email"), "email", "E-mail", 1);
|
||||
appendConfigGroup(list, siteConfigService.selectConfigValueByKey("contact_address"), "address", "地址", 2);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void appendConfigGroup(List<HuayangContact> list, String raw, String type, String defaultLabel, int sortOrder)
|
||||
{
|
||||
if (StringUtils.isBlank(raw))
|
||||
{
|
||||
return;
|
||||
}
|
||||
HuayangContact item = new HuayangContact();
|
||||
item.setContactType(type);
|
||||
item.setContactLabel(defaultLabel);
|
||||
item.setContactValue(raw.trim());
|
||||
item.setSortOrder(sortOrder);
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交在线留言(公开)
|
||||
*/
|
||||
@ -78,20 +113,31 @@ public class SiteCustomerController extends BaseController
|
||||
return error("提交失败,请稍后重试");
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交招商申请(公开)
|
||||
*/
|
||||
@Anonymous
|
||||
@PostMapping("/contact/join")
|
||||
public AjaxResult submitJoinApply(@RequestBody HuayangJoinApply apply)
|
||||
{
|
||||
apply.setCreateBy("anonymous");
|
||||
apply.setApplyStatus("0");
|
||||
int rows = joinApplyService.insertHuayangJoinApply(apply);
|
||||
if (rows > 0)
|
||||
{
|
||||
return success("申请提交成功,我们的招商经理会尽快与您联系!");
|
||||
private static String normalizeSiteLang(String lang) {
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void applyContactLocale(String langNorm, List<HuayangContact> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> keys = new ArrayList<>(3);
|
||||
keys.add("contact.phone.label");
|
||||
keys.add("contact.email.label");
|
||||
keys.add("contact.address.label");
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangContact item : list) {
|
||||
if (item == null || StringUtils.isBlank(item.getContactType())) {
|
||||
continue;
|
||||
}
|
||||
String labelKey = "contact." + item.getContactType() + ".label";
|
||||
String translatedLabel = dict.get(labelKey);
|
||||
if (StringUtils.isNotBlank(translatedLabel)) {
|
||||
item.setContactLabel(translatedLabel);
|
||||
}
|
||||
}
|
||||
return error("提交失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,26 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.service.IHuayangFixedSectionService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangFixedSection;
|
||||
import com.ruoyi.system.domain.HuayangFixedSectionItem;
|
||||
import com.ruoyi.system.domain.HuayangI18n;
|
||||
import com.ruoyi.system.mapper.HuayangI18nMapper;
|
||||
import com.ruoyi.system.service.IHuayangFixedSectionService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
|
||||
/**
|
||||
* 门户固定板块公开接口
|
||||
@ -18,15 +30,166 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/site/fixedSection")
|
||||
public class SiteFixedSectionController extends BaseController
|
||||
{
|
||||
private static final String DEFAULT_LANG = "zh_cn";
|
||||
|
||||
@Autowired
|
||||
private IHuayangFixedSectionService fixedSectionService;
|
||||
|
||||
@Autowired
|
||||
private HuayangI18nMapper i18nMapper;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
* 按编码获取固定板块(包含启用子项)
|
||||
*/
|
||||
@GetMapping("/code/{sectionCode}")
|
||||
public AjaxResult getByCode(@PathVariable("sectionCode") String sectionCode)
|
||||
public AjaxResult getByCode(@PathVariable("sectionCode") String sectionCode,
|
||||
@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
return success(fixedSectionService.selectHuayangFixedSectionByCode(sectionCode));
|
||||
HuayangFixedSection section = fixedSectionService.selectHuayangFixedSectionByCode(sectionCode);
|
||||
applyTranslations(section, normalizeLang(lang));
|
||||
return success(section);
|
||||
}
|
||||
|
||||
private void applyTranslations(HuayangFixedSection section, String lang)
|
||||
{
|
||||
if (section == null || DEFAULT_LANG.equals(lang))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String code = section.getSectionCode();
|
||||
if (StringUtils.isBlank(code))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.add("home." + code + ".main.title");
|
||||
keys.add("home." + code + ".main.text");
|
||||
if (section.getItemList() != null)
|
||||
{
|
||||
for (HuayangFixedSectionItem item : section.getItemList())
|
||||
{
|
||||
if (item == null || item.getItemId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.add("home." + code + ".item." + item.getItemId() + ".title");
|
||||
keys.add("home." + code + ".item." + item.getItemId() + ".desc");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> translations = selectRawTranslations(lang, keys);
|
||||
if (translations == null || translations.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String mainTitle = translations.get("home." + code + ".main.title");
|
||||
if (StringUtils.isNotBlank(mainTitle))
|
||||
{
|
||||
section.setSectionName(mainTitle);
|
||||
}
|
||||
String mainText = translations.get("home." + code + ".main.text");
|
||||
if (StringUtils.isNotBlank(mainText))
|
||||
{
|
||||
section.setSectionText(mainText);
|
||||
}
|
||||
|
||||
if (section.getItemList() != null)
|
||||
{
|
||||
for (HuayangFixedSectionItem item : section.getItemList())
|
||||
{
|
||||
if (item == null || item.getItemId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String prefix = "home." + code + ".item." + item.getItemId();
|
||||
String title = translations.get(prefix + ".title");
|
||||
if (StringUtils.isNotBlank(title))
|
||||
{
|
||||
item.setItemTitle(title);
|
||||
}
|
||||
String desc = translations.get(prefix + ".desc");
|
||||
if (StringUtils.isNotBlank(desc))
|
||||
{
|
||||
item.setItemDesc(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> selectRawTranslations(String lang, List<String> keys)
|
||||
{
|
||||
Map<String, String> translations = new HashMap<>();
|
||||
if (StringUtils.isBlank(lang) || keys == null || keys.isEmpty())
|
||||
{
|
||||
return translations;
|
||||
}
|
||||
try
|
||||
{
|
||||
List<HuayangI18n> rows = i18nMapper.selectHuayangI18nByLangAndKeyList(lang, keys);
|
||||
if (rows != null)
|
||||
{
|
||||
for (HuayangI18n row : rows)
|
||||
{
|
||||
if (row != null && StringUtils.isNotBlank(row.getI18nKey()) && row.getI18nValue() != null)
|
||||
{
|
||||
translations.put(row.getI18nKey(), row.getI18nValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RuntimeException | LinkageError ignored)
|
||||
{
|
||||
List<Map<String, String>> rows = i18nService.selectTranslationByLang(lang);
|
||||
if (rows == null)
|
||||
{
|
||||
return translations;
|
||||
}
|
||||
for (Map<String, String> row : rows)
|
||||
{
|
||||
String key = row.get("i18nKey");
|
||||
if (StringUtils.isBlank(key))
|
||||
{
|
||||
key = row.get("i18n_key");
|
||||
}
|
||||
if (!keys.contains(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String value = row.get("i18nValue");
|
||||
if (value == null)
|
||||
{
|
||||
value = row.get("i18n_value");
|
||||
}
|
||||
if (value != null)
|
||||
{
|
||||
translations.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
|
||||
private String normalizeLang(String raw)
|
||||
{
|
||||
String value = StringUtils.defaultString(raw).trim().toLowerCase(Locale.ROOT).replace('-', '_');
|
||||
if (StringUtils.isBlank(value) || value.startsWith("zh"))
|
||||
{
|
||||
return DEFAULT_LANG;
|
||||
}
|
||||
if (value.startsWith("en"))
|
||||
{
|
||||
return "en_us";
|
||||
}
|
||||
if (value.startsWith("ja") || value.startsWith("jp"))
|
||||
{
|
||||
return "ja_jp";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,12 +6,10 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangHomeBanner;
|
||||
import com.ruoyi.system.domain.HuayangHomeFeature;
|
||||
import com.ruoyi.system.domain.HuayangProduct;
|
||||
import com.ruoyi.system.domain.HuayangNews;
|
||||
import com.ruoyi.system.service.IHuayangHomeBannerService;
|
||||
import com.ruoyi.system.service.IHuayangHomeFeatureService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
import com.ruoyi.system.service.IHuayangProductService;
|
||||
import com.ruoyi.system.service.IHuayangNewsService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@ -34,8 +32,6 @@ public class SiteHomeController extends BaseController {
|
||||
@Autowired
|
||||
private IHuayangProductService productService;
|
||||
@Autowired
|
||||
private IHuayangNewsService newsService;
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
@ -45,7 +41,6 @@ public class SiteHomeController extends BaseController {
|
||||
* <li>轮播标题:{@code home.banner.{bannerId}.title}</li>
|
||||
* <li>特色标题/描述:{@code home.feature.{featureId}.title}、{@code home.feature.{featureId}.desc}</li>
|
||||
* <li>推荐产品名称:{@code home.product.{productId}.name}</li>
|
||||
* <li>推荐新闻标题:{@code home.news.{newsId}.title}</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Anonymous
|
||||
@ -59,25 +54,17 @@ public class SiteHomeController extends BaseController {
|
||||
queryFeature.setStatus("0");
|
||||
List<HuayangHomeFeature> features = featureService.selectHuayangHomeFeatureList(queryFeature);
|
||||
|
||||
// 首页推荐产品
|
||||
HuayangProduct queryProduct = new HuayangProduct();
|
||||
queryProduct.setStatus("0");
|
||||
queryProduct.setIsHome("1"); // 推荐到首页
|
||||
queryProduct.setIsHome("1");
|
||||
List<HuayangProduct> products = productService.selectHuayangProductList(queryProduct);
|
||||
|
||||
// 首页推荐新闻
|
||||
HuayangNews queryNews = new HuayangNews();
|
||||
queryNews.setStatus("0");
|
||||
queryNews.setIsHome("1"); // 推荐到首页
|
||||
List<HuayangNews> news = newsService.selectHuayangNewsList(queryNews);
|
||||
|
||||
applyHomePublicLocale(normalizeSiteLang(lang), banners, features, products, news);
|
||||
applyHomePublicLocale(normalizeSiteLang(lang), banners, features, products);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("banners", banners);
|
||||
data.put("features", features);
|
||||
data.put("products", products);
|
||||
data.put("news", news);
|
||||
return success(data);
|
||||
}
|
||||
|
||||
@ -88,11 +75,8 @@ public class SiteHomeController extends BaseController {
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文库表为基准;其它语言在 huayang_i18n 中维护对应 key 后在此覆盖。
|
||||
*/
|
||||
private void applyHomePublicLocale(String langNorm, List<HuayangHomeBanner> banners,
|
||||
List<HuayangHomeFeature> features, List<HuayangProduct> products, List<HuayangNews> news) {
|
||||
List<HuayangHomeFeature> features, List<HuayangProduct> products) {
|
||||
if ("zh_cn".equals(langNorm)) {
|
||||
return;
|
||||
}
|
||||
@ -119,13 +103,6 @@ public class SiteHomeController extends BaseController {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (news != null) {
|
||||
for (HuayangNews n : news) {
|
||||
if (n.getNewsId() != null) {
|
||||
keys.add("home.news." + n.getNewsId() + ".title");
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
if (dict.isEmpty()) {
|
||||
return;
|
||||
@ -167,16 +144,5 @@ public class SiteHomeController extends BaseController {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (news != null) {
|
||||
for (HuayangNews n : news) {
|
||||
if (n.getNewsId() == null) {
|
||||
continue;
|
||||
}
|
||||
String v = dict.get("home.news." + n.getNewsId() + ".title");
|
||||
if (StringUtils.isNotEmpty(v)) {
|
||||
n.setNewsTitle(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
@ -10,6 +14,7 @@ import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangCompanyHonor;
|
||||
import com.ruoyi.system.service.IHuayangCompanyHonorService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
|
||||
/**
|
||||
* 门户站点公开接口:资质荣誉
|
||||
@ -23,14 +28,69 @@ public class SiteHonorController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangCompanyHonorService huayangCompanyHonorService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
* 对外公开:正常状态的资质荣誉列表(无需登录)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/public")
|
||||
public AjaxResult publicList()
|
||||
public AjaxResult publicList(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
List<HuayangCompanyHonor> list = huayangCompanyHonorService.selectHuayangCompanyHonorPublicList();
|
||||
applyHonorLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
private static String normalizeSiteLang(String lang)
|
||||
{
|
||||
if (StringUtils.isBlank(lang))
|
||||
{
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void applyHonorLocale(String langNorm, List<HuayangCompanyHonor> list)
|
||||
{
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCompanyHonor item : list)
|
||||
{
|
||||
if (item == null || item.getHonorId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.add("company.honor." + item.getHonorId() + ".title");
|
||||
keys.add("company.honor." + item.getHonorId() + ".desc");
|
||||
}
|
||||
if (keys.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCompanyHonor item : list)
|
||||
{
|
||||
if (item == null || item.getHonorId() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String titleKey = "company.honor." + item.getHonorId() + ".title";
|
||||
String descKey = "company.honor." + item.getHonorId() + ".desc";
|
||||
String title = dict.get(titleKey);
|
||||
String desc = dict.get(descKey);
|
||||
if (StringUtils.isNotBlank(title))
|
||||
{
|
||||
item.setHonorTitle(title);
|
||||
}
|
||||
if (StringUtils.isNotBlank(desc))
|
||||
{
|
||||
item.setHonorDesc(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,148 +0,0 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.system.domain.HuayangNews;
|
||||
import com.ruoyi.system.service.IHuayangNewsService;
|
||||
import com.ruoyi.system.service.ISysDictDataService;
|
||||
|
||||
/**
|
||||
* 门户站点公开接口:新闻动态
|
||||
*
|
||||
* @author huayang
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/site/news")
|
||||
public class SiteNewsController extends BaseController
|
||||
{
|
||||
private static final String NEWS_TYPE_DICT = "huayang_news_type";
|
||||
|
||||
@Autowired
|
||||
private IHuayangNewsService newsService;
|
||||
|
||||
@Autowired
|
||||
private ISysDictDataService dictDataService;
|
||||
|
||||
/**
|
||||
* 对外公开:新闻列表(支持类型筛选、分页)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/public")
|
||||
public AjaxResult publicList(
|
||||
@RequestParam(required = false) String newsType,
|
||||
@RequestParam(required = false) Integer pageNum,
|
||||
@RequestParam(required = false) Integer pageSize)
|
||||
{
|
||||
// 默认返回10条
|
||||
if (pageSize == null || pageSize <= 0) {
|
||||
pageSize = 10;
|
||||
}
|
||||
if (pageNum == null || pageNum <= 0) {
|
||||
pageNum = 1;
|
||||
}
|
||||
int offset = (pageNum - 1) * pageSize;
|
||||
long total = newsService.countNewsPublic(newsType);
|
||||
List<HuayangNews> list = newsService.selectNewsForHome(newsType, offset, pageSize);
|
||||
Map<String, Object> data = new HashMap<>(4);
|
||||
data.put("rows", list);
|
||||
data.put("total", total);
|
||||
return success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外公开:新闻详情
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/{newsId}/public")
|
||||
public AjaxResult publicDetail(@PathVariable("newsId") Long newsId)
|
||||
{
|
||||
HuayangNews news = newsService.selectHuayangNewsById(newsId);
|
||||
if (news == null || !"0".equals(news.getStatus()))
|
||||
{
|
||||
return error("新闻不存在或已下架");
|
||||
}
|
||||
// 增加阅读次数
|
||||
newsService.incrementReadCount(newsId);
|
||||
return success(news);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外公开:首页推荐新闻(与 /site/home/public 中 news 列表一致,供首页聚合接口失败时单独拉取)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/homeRecommend")
|
||||
public AjaxResult homeRecommend(@RequestParam(required = false) Integer limit)
|
||||
{
|
||||
if (limit == null || limit <= 0)
|
||||
{
|
||||
limit = 3;
|
||||
}
|
||||
if (limit > 20)
|
||||
{
|
||||
limit = 20;
|
||||
}
|
||||
HuayangNews query = new HuayangNews();
|
||||
query.setStatus("0");
|
||||
query.setIsHome("1");
|
||||
List<HuayangNews> list = newsService.selectHuayangNewsList(query);
|
||||
if (list == null)
|
||||
{
|
||||
list = List.of();
|
||||
}
|
||||
else if (list.size() > limit)
|
||||
{
|
||||
list = new ArrayList<>(list.subList(0, limit));
|
||||
}
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外公开:获取最新新闻(用于首页展示)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/latest")
|
||||
public AjaxResult latestNews(
|
||||
@RequestParam(required = false) String newsType,
|
||||
@RequestParam(required = false) Integer limit)
|
||||
{
|
||||
if (limit == null || limit <= 0) {
|
||||
limit = 5;
|
||||
}
|
||||
List<HuayangNews> list = newsService.selectNewsForHome(newsType, null, limit);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外公开:新闻类型(来源 sys_dict_data,dict_type=huayang_news_type)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/types")
|
||||
public AjaxResult types()
|
||||
{
|
||||
SysDictData query = new SysDictData();
|
||||
query.setDictType(NEWS_TYPE_DICT);
|
||||
query.setStatus("0");
|
||||
List<SysDictData> list = dictDataService.selectDictDataList(query);
|
||||
List<Map<String, Object>> data = list.stream().map(item -> {
|
||||
Map<String, Object> row = new HashMap<>(4);
|
||||
row.put("label", item.getDictLabel());
|
||||
row.put("value", item.getDictValue());
|
||||
row.put("sort", item.getDictSort());
|
||||
return row;
|
||||
}).collect(Collectors.toList());
|
||||
return success(data);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -17,7 +19,9 @@ import com.ruoyi.system.domain.HuayangProductCategory;
|
||||
import com.ruoyi.system.domain.HuayangProductInquiry;
|
||||
import com.ruoyi.system.service.IHuayangProductCategoryService;
|
||||
import com.ruoyi.system.service.IHuayangProductInquiryService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
import com.ruoyi.system.service.IHuayangProductService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 门户站点公开接口:产品中心
|
||||
@ -37,14 +41,18 @@ public class SiteProductController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangProductInquiryService productInquiryService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
* 对外公开:产品分类列表(无需登录)
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/category/public")
|
||||
public AjaxResult publicCategoryList()
|
||||
public AjaxResult publicCategoryList(@RequestParam(required = false) String lang)
|
||||
{
|
||||
List<HuayangProductCategory> list = productCategoryService.selectHuayangProductCategoryPublicList();
|
||||
applyProductCategoryLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@ -55,9 +63,11 @@ public class SiteProductController extends BaseController
|
||||
@GetMapping("/public")
|
||||
public AjaxResult publicList(
|
||||
@RequestParam(required = false) Long categoryId,
|
||||
@RequestParam(required = false) String keyword)
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String lang)
|
||||
{
|
||||
List<HuayangProduct> list = productService.selectHuayangProductPublicList(categoryId, keyword);
|
||||
applyProductLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
@ -66,13 +76,15 @@ public class SiteProductController extends BaseController
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/{productId}/public")
|
||||
public AjaxResult publicDetail(@PathVariable("productId") Long productId)
|
||||
public AjaxResult publicDetail(@PathVariable("productId") Long productId,
|
||||
@RequestParam(required = false) String lang)
|
||||
{
|
||||
HuayangProduct product = productService.selectHuayangProductById(productId);
|
||||
if (product == null || !"0".equals(product.getStatus()))
|
||||
{
|
||||
return error("产品不存在或已下架");
|
||||
}
|
||||
applyProductDetailLocale(normalizeSiteLang(lang), product);
|
||||
return success(product);
|
||||
}
|
||||
|
||||
@ -93,4 +105,62 @@ public class SiteProductController extends BaseController
|
||||
}
|
||||
return error("提交失败,请稍后重试");
|
||||
}
|
||||
|
||||
private static String normalizeSiteLang(String lang) {
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void applyProductCategoryLocale(String langNorm, List<HuayangProductCategory> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) return;
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangProductCategory item : list) {
|
||||
if (item.getCategoryId() != null) {
|
||||
keys.add("product.category." + item.getCategoryId() + ".name");
|
||||
}
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangProductCategory item : list) {
|
||||
if (item.getCategoryId() == null) continue;
|
||||
String v = dict.get("product.category." + item.getCategoryId() + ".name");
|
||||
if (StringUtils.isNotEmpty(v)) {
|
||||
item.setCategoryName(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProductLocale(String langNorm, List<HuayangProduct> list) {
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) return;
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangProduct item : list) {
|
||||
if (item.getProductId() != null) {
|
||||
keys.add("product.item." + item.getProductId() + ".name");
|
||||
keys.add("product.item." + item.getProductId() + ".desc");
|
||||
}
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangProduct item : list) {
|
||||
if (item.getProductId() == null) continue;
|
||||
String prefix = "product.item." + item.getProductId() + ".";
|
||||
String n = dict.get(prefix + "name");
|
||||
if (StringUtils.isNotEmpty(n)) item.setProductName(n);
|
||||
String d = dict.get(prefix + "desc");
|
||||
if (StringUtils.isNotEmpty(d)) item.setProductDesc(d);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProductDetailLocale(String langNorm, HuayangProduct product) {
|
||||
if ("zh_cn".equals(langNorm) || product == null || product.getProductId() == null) return;
|
||||
List<String> keys = new ArrayList<>();
|
||||
String prefix = "product.item." + product.getProductId() + ".";
|
||||
keys.add(prefix + "name");
|
||||
keys.add(prefix + "desc");
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
String n = dict.get(prefix + "name");
|
||||
if (StringUtils.isNotEmpty(n)) product.setProductName(n);
|
||||
String d = dict.get(prefix + "desc");
|
||||
if (StringUtils.isNotEmpty(d)) product.setProductDesc(d);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,7 @@ import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangProduct;
|
||||
import com.ruoyi.system.domain.HuayangNews;
|
||||
import com.ruoyi.system.service.IHuayangProductService;
|
||||
import com.ruoyi.system.service.IHuayangNewsService;
|
||||
|
||||
/**
|
||||
* 全站搜索接口
|
||||
@ -29,11 +27,8 @@ public class SiteSearchController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangProductService productService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangNewsService newsService;
|
||||
|
||||
/**
|
||||
* 根据关键词搜索产品和新闻
|
||||
* 根据关键词搜索产品
|
||||
*/
|
||||
@GetMapping("/all")
|
||||
public AjaxResult search(@RequestParam(value = "keyword", required = false) String keyword)
|
||||
@ -41,17 +36,10 @@ public class SiteSearchController extends BaseController
|
||||
Map<String, Object> results = new HashMap<>();
|
||||
|
||||
if (keyword != null && !keyword.trim().isEmpty()) {
|
||||
// 搜索产品
|
||||
HuayangProduct productQuery = new HuayangProduct();
|
||||
productQuery.setProductName(keyword);
|
||||
List<HuayangProduct> products = productService.selectHuayangProductList(productQuery);
|
||||
results.put("products", products);
|
||||
|
||||
// 搜索新闻
|
||||
HuayangNews newsQuery = new HuayangNews();
|
||||
newsQuery.setNewsTitle(keyword);
|
||||
List<HuayangNews> news = newsService.selectHuayangNewsList(newsQuery);
|
||||
results.put("news", news);
|
||||
}
|
||||
|
||||
return success(results);
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
package com.ruoyi.web.controller.site;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.system.domain.HuayangCompanyTimeline;
|
||||
import com.ruoyi.system.service.IHuayangCompanyTimelineService;
|
||||
import com.ruoyi.system.service.IHuayangI18nService;
|
||||
|
||||
/**
|
||||
* 门户站点公开接口:发展历程
|
||||
@ -23,14 +30,131 @@ public class SiteTimelineController extends BaseController
|
||||
@Autowired
|
||||
private IHuayangCompanyTimelineService huayangCompanyTimelineService;
|
||||
|
||||
@Autowired
|
||||
private IHuayangI18nService i18nService;
|
||||
|
||||
/**
|
||||
* 对外公开:正常状态的发展历程列表(无需登录)
|
||||
* 对外公开:正常状态的发展历程列表(无需登录)。
|
||||
* <p>可选 {@code lang}:非 zh_cn 时用 i18n key {@code company.timeline.{timelineId}.title|summary|detail},
|
||||
* 暂无则读 {@code extra_data.langContent}(对象字段 title、summary、detail;旧版整段字符串视为 detail)。</p>
|
||||
*/
|
||||
@Anonymous
|
||||
@GetMapping("/public")
|
||||
public AjaxResult publicList()
|
||||
public AjaxResult publicList(@RequestParam(value = "lang", required = false) String lang)
|
||||
{
|
||||
List<HuayangCompanyTimeline> list = huayangCompanyTimelineService.selectHuayangCompanyTimelinePublicList();
|
||||
applyTimelineLocale(normalizeSiteLang(lang), list);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
private static String normalizeSiteLang(String lang) {
|
||||
if (StringUtils.isBlank(lang)) {
|
||||
return "zh_cn";
|
||||
}
|
||||
return lang.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private void applyTimelineLocale(String langNorm, List<HuayangCompanyTimeline> list)
|
||||
{
|
||||
if ("zh_cn".equals(langNorm) || list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> keys = new ArrayList<>();
|
||||
for (HuayangCompanyTimeline t : list) {
|
||||
if (t.getTimelineId() == null) {
|
||||
continue;
|
||||
}
|
||||
String id = String.valueOf(t.getTimelineId());
|
||||
keys.add("company.timeline." + id + ".title");
|
||||
keys.add("company.timeline." + id + ".summary");
|
||||
keys.add("company.timeline." + id + ".detail");
|
||||
}
|
||||
Map<String, String> dict = i18nService.selectI18nValueMapByLangAndKeys(langNorm, keys);
|
||||
for (HuayangCompanyTimeline t : list) {
|
||||
if (t.getTimelineId() == null) {
|
||||
continue;
|
||||
}
|
||||
String id = String.valueOf(t.getTimelineId());
|
||||
String p = "company.timeline." + id + ".";
|
||||
|
||||
String title = dict.get(p + "title");
|
||||
if (StringUtils.isBlank(title)) {
|
||||
title = timelineManualField(t.getExtraData(), langNorm, "title");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(title)) {
|
||||
t.setEventTitle(title);
|
||||
}
|
||||
|
||||
String summary = dict.get(p + "summary");
|
||||
if (StringUtils.isBlank(summary)) {
|
||||
summary = timelineManualField(t.getExtraData(), langNorm, "summary");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(summary)) {
|
||||
t.setEventContent(summary);
|
||||
}
|
||||
|
||||
String detail = dict.get(p + "detail");
|
||||
if (StringUtils.isBlank(detail)) {
|
||||
detail = timelineManualDetailFromExtra(t.getExtraData(), langNorm);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(detail)) {
|
||||
t.setEventDetail(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String timelineManualField(String extraData, String langNorm, String field)
|
||||
{
|
||||
Object entry = getLangContentEntry(extraData, langNorm);
|
||||
if (entry instanceof JSONObject) {
|
||||
JSONObject o = (JSONObject) entry;
|
||||
if ("summary".equals(field)) {
|
||||
String s = o.getString("summary");
|
||||
if (StringUtils.isBlank(s)) {
|
||||
s = o.getString("content");
|
||||
}
|
||||
return StringUtils.isNotBlank(s) ? s : null;
|
||||
}
|
||||
if ("title".equals(field)) {
|
||||
String s = o.getString("title");
|
||||
return StringUtils.isNotBlank(s) ? s : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** summary/detail 落空时:旧版仅存一段字符串 → 视作 detail */
|
||||
private String timelineManualDetailFromExtra(String extraData, String langNorm)
|
||||
{
|
||||
Object entry = getLangContentEntry(extraData, langNorm);
|
||||
if (entry instanceof JSONObject) {
|
||||
String detail = ((JSONObject) entry).getString("detail");
|
||||
return StringUtils.isNotBlank(detail) ? detail : null;
|
||||
}
|
||||
if (entry instanceof String) {
|
||||
String s = (String) entry;
|
||||
return StringUtils.isNotBlank(s) ? s : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getLangContentEntry(String extraData, String langNorm)
|
||||
{
|
||||
if (StringUtils.isBlank(extraData) || StringUtils.isBlank(langNorm)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JSONObject root = JSON.parseObject(extraData);
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
JSONObject langContent = root.getJSONObject("langContent");
|
||||
if (langContent == null) {
|
||||
return null;
|
||||
}
|
||||
return langContent.get(langNorm);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,9 +20,8 @@ import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
import com.ruoyi.common.utils.file.MimeTypeUtils;
|
||||
import com.ruoyi.common.utils.file.ImageUploadUtils;
|
||||
import com.ruoyi.framework.web.service.TokenService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
|
||||
@ -128,7 +127,7 @@ public class SysProfileController extends BaseController
|
||||
if (!file.isEmpty())
|
||||
{
|
||||
LoginUser loginUser = getLoginUser();
|
||||
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true);
|
||||
String avatar = ImageUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, true);
|
||||
if (userService.updateUserAvatar(loginUser.getUserId(), avatar))
|
||||
{
|
||||
String oldAvatar = loginUser.getUser().getAvatar();
|
||||
|
||||
@ -6,9 +6,9 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://localhost:3306/hauyang?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: root
|
||||
url: ${DB_URL:jdbc:mysql://localhost:3306/huayang?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true}
|
||||
username: ${DB_USERNAME:huayang}
|
||||
password: ${DB_PASSWORD:}
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
@ -58,4 +58,4 @@ spring:
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
multi-statement-allow: true
|
||||
|
||||
17
backend/ruoyi-admin/src/main/resources/application-prod.yml
Normal file
17
backend/ruoyi-admin/src/main/resources/application-prod.yml
Normal file
@ -0,0 +1,17 @@
|
||||
# 生产环境(与 druid 数据源一起启用,例如:--spring.profiles.active=prod,druid)
|
||||
# 请在服务器用环境变量或独立配置覆盖:数据源、Redis、ruoyi.profile 上传目录、token.secret 等
|
||||
spring:
|
||||
devtools:
|
||||
restart:
|
||||
enabled: false
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.ruoyi: info
|
||||
org.springframework: warn
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: false
|
||||
swagger-ui:
|
||||
enabled: false
|
||||
@ -7,7 +7,8 @@ ruoyi:
|
||||
# 版权年份
|
||||
copyrightYear: 2026
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 开发环境使用项目内目录,避免本机不存在 D:/ruoyi/uploadPath 时上传图全部 404
|
||||
profile: ./uploadPath
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
@ -16,7 +17,7 @@ ruoyi:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
port: 7003
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
@ -135,8 +136,7 @@ referer:
|
||||
# 防盗链开关
|
||||
enabled: false
|
||||
# 允许的域名列表
|
||||
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
|
||||
|
||||
allowed-domains: localhost,127.0.0.1,192.168.1.159,ruoyi.vip,www.ruoyi.vip
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 日志存放路径 -->
|
||||
<property name="log.path" value="/home/ruoyi/logs" />
|
||||
<property name="log.path" value="./logs" />
|
||||
<!-- 日志输出格式 -->
|
||||
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
restart.include.json=/com.alibaba.fastjson2.*.jar
|
||||
@ -1,61 +0,0 @@
|
||||
# 数据源配置
|
||||
spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://localhost:3306/hauyang?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: root
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
enabled: false
|
||||
url:
|
||||
username:
|
||||
password:
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 10
|
||||
# 最大连接池数量
|
||||
maxActive: 20
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
# 配置连接超时时间
|
||||
connectTimeout: 30000
|
||||
# 配置网络超时时间
|
||||
socketTimeout: 60000
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||
maxEvictableIdleTimeMillis: 900000
|
||||
# 配置检测连接是否有效
|
||||
validationQuery: SELECT 1 FROM DUAL
|
||||
testWhileIdle: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
webStatFilter:
|
||||
enabled: true
|
||||
statViewServlet:
|
||||
enabled: true
|
||||
# 设置白名单,不填则允许所有访问
|
||||
allow:
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: 123456
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
# 慢SQL记录
|
||||
log-slow-sql: true
|
||||
slow-sql-millis: 1000
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
@ -1,147 +0,0 @@
|
||||
# 项目相关配置
|
||||
ruoyi:
|
||||
# 名称
|
||||
name: RuoYi
|
||||
# 版本
|
||||
version: 3.9.2
|
||||
# 版权年份
|
||||
copyrightYear: 2026
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
tomcat:
|
||||
# tomcat的URI编码
|
||||
uri-encoding: UTF-8
|
||||
# 连接数满后的排队数,默认为100
|
||||
accept-count: 1000
|
||||
threads:
|
||||
# tomcat最大线程数,默认为200
|
||||
max: 800
|
||||
# Tomcat启动初始化的线程数,默认值10
|
||||
min-spare: 100
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.ruoyi: debug
|
||||
org.springframework: warn
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
profiles:
|
||||
active: druid
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
jackson:
|
||||
time-zone: GMT+8
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: true
|
||||
data:
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
|
||||
# token配置
|
||||
token:
|
||||
# 令牌自定义标识
|
||||
header: Authorization
|
||||
# 令牌密钥
|
||||
secret: abcdefghijklmnopqrstuvwxyz
|
||||
# 令牌有效期(默认30分钟)
|
||||
expireTime: 30
|
||||
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.ruoyi.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 加载全局的配置文件
|
||||
configLocation: classpath:mybatis/mybatis-config.xml
|
||||
|
||||
# PageHelper分页插件
|
||||
pagehelper:
|
||||
helperDialect: mysql
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
|
||||
# Springdoc配置
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
display-name: '测试模块'
|
||||
paths-to-match: '/**'
|
||||
packages-to-scan: com.ruoyi.web.controller.tool
|
||||
|
||||
# 防盗链配置
|
||||
referer:
|
||||
# 防盗链开关
|
||||
enabled: false
|
||||
# 允许的域名列表
|
||||
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
@ -1,24 +0,0 @@
|
||||
Application Version: ${ruoyi.version}
|
||||
Spring Boot Version: ${spring-boot.version}
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// _ooOoo_ //
|
||||
// o8888888o //
|
||||
// 88" . "88 //
|
||||
// (| ^_^ |) //
|
||||
// O\ = /O //
|
||||
// ____/`---'\____ //
|
||||
// .' \\| |// `. //
|
||||
// / \\||| : |||// \ //
|
||||
// / _||||| -:- |||||- \ //
|
||||
// | | \\\ - /// | | //
|
||||
// | \_| ''\---/'' | | //
|
||||
// \ .-\__ `-` ___/-. / //
|
||||
// ___`. .' /--.--\ `. . ___ //
|
||||
// ."" '< `.___\_<|>_/___.' >'"". //
|
||||
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
|
||||
// \ \ `-. \_ __\ /__ _/ .-` / / //
|
||||
// ========`-.____`-.___\_____/___.-`____.-'======== //
|
||||
// `=---=' //
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
|
||||
// 佛祖保佑 永不宕机 永无BUG //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user