diff --git a/core/core-backend/src/main/java/io/dataease/visualization/server/VisualizationWatermarkService.java b/core/core-backend/src/main/java/io/dataease/visualization/server/VisualizationWatermarkService.java new file mode 100644 index 0000000000..b1f880c99c --- /dev/null +++ b/core/core-backend/src/main/java/io/dataease/visualization/server/VisualizationWatermarkService.java @@ -0,0 +1,41 @@ +package io.dataease.visualization.server; + +import io.dataease.api.visualization.VisualizationWatermarkApi; +import io.dataease.api.visualization.request.VisualizationWatermarkRequest; +import io.dataease.api.visualization.vo.VisualizationWatermarkVO; +import io.dataease.utils.BeanUtils; +import io.dataease.visualization.dao.auto.entity.VisualizationWatermark; +import io.dataease.visualization.dao.auto.mapper.VisualizationWatermarkMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author : WangJiaHao + * @date : 2024/1/10 16:59 + */ +@RestController +@RequestMapping("/watermark") +public class VisualizationWatermarkService implements VisualizationWatermarkApi { + + private final static String DEFAULT_ID ="system_default"; + + @Resource + private VisualizationWatermarkMapper watermarkMapper; + + @Override + public VisualizationWatermarkVO getWatermarkInfo() { + VisualizationWatermark watermark = watermarkMapper.selectById(DEFAULT_ID); + VisualizationWatermarkVO watermarkVO = new VisualizationWatermarkVO(); + return BeanUtils.copyBean(watermarkVO,watermark); + } + + @Override + public void saveWatermarkInfo(VisualizationWatermarkRequest watermarkRequest) { + VisualizationWatermark watermark = new VisualizationWatermark(); + BeanUtils.copyBean(watermark,watermarkRequest); + watermark.setId(DEFAULT_ID); + watermarkMapper.updateById(watermark); + } +} diff --git a/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql b/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql new file mode 100644 index 0000000000..f8a9c19a0e --- /dev/null +++ b/core/core-backend/src/main/resources/db/desktop/V2.3__ddl.sql @@ -0,0 +1,12 @@ + +DROP TABLE IF EXISTS `visualization_watermark`; +CREATE TABLE `visualization_watermark` ( + `id` varchar(50) NOT NULL COMMENT '主键', + `version` varchar(255) DEFAULT NULL COMMENT '版本号', + `setting_content` longtext COMMENT '设置内容', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建人', + `create_time` bigint(13) DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) COMMENT='仪表板水印设置表'; + +INSERT INTO `visualization_watermark` (`id`, `version`, `setting_content`, `create_by`, `create_time`) VALUES ('system_default', '1.0', '{\"enable\":false,\"enablePanelCustom\":true,\"type\":\"custom\",\"content\":\"水印\",\"watermark_color\":\"#DD1010\",\"watermark_x_space\":12,\"watermark_y_space\":36,\"watermark_fontsize\":15}', 'admin', NULL); diff --git a/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql b/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql new file mode 100644 index 0000000000..f8a9c19a0e --- /dev/null +++ b/core/core-backend/src/main/resources/db/migration/V2.3__ddl.sql @@ -0,0 +1,12 @@ + +DROP TABLE IF EXISTS `visualization_watermark`; +CREATE TABLE `visualization_watermark` ( + `id` varchar(50) NOT NULL COMMENT '主键', + `version` varchar(255) DEFAULT NULL COMMENT '版本号', + `setting_content` longtext COMMENT '设置内容', + `create_by` varchar(255) DEFAULT NULL COMMENT '创建人', + `create_time` bigint(13) DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) +) COMMENT='仪表板水印设置表'; + +INSERT INTO `visualization_watermark` (`id`, `version`, `setting_content`, `create_by`, `create_time`) VALUES ('system_default', '1.0', '{\"enable\":false,\"enablePanelCustom\":true,\"type\":\"custom\",\"content\":\"水印\",\"watermark_color\":\"#DD1010\",\"watermark_x_space\":12,\"watermark_y_space\":36,\"watermark_fontsize\":15}', 'admin', NULL); diff --git a/core/core-frontend/src/api/watermark.ts b/core/core-frontend/src/api/watermark.ts index 6c78457973..dacfd4c8c3 100644 --- a/core/core-frontend/src/api/watermark.ts +++ b/core/core-frontend/src/api/watermark.ts @@ -1,4 +1,5 @@ import request from '@/config/axios' -export const searchRoleApi = (keyword: string) => - request.post({ url: '/role/query', data: { keyword } }) +export const watermarkSave = params => request.post({ url: '/watermark/save', data: params }) + +export const watermarkFind = () => request.get({ url: 'watermark/find' }) diff --git a/core/core-frontend/src/assets/img/watermark-demo-dark.png b/core/core-frontend/src/assets/img/watermark-demo-dark.png new file mode 100644 index 0000000000..b4a48c7334 Binary files /dev/null and b/core/core-frontend/src/assets/img/watermark-demo-dark.png differ diff --git a/core/core-frontend/src/assets/img/watermark-demo-light.png b/core/core-frontend/src/assets/img/watermark-demo-light.png new file mode 100644 index 0000000000..78b5b725a2 Binary files /dev/null and b/core/core-frontend/src/assets/img/watermark-demo-light.png differ diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index 7c29dca6a3..605dfb1fe3 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -2131,5 +2131,24 @@ export default { template_manage: { name_already_exists_type: '分类名称已存在', the_same_category: '同一分类下,该模板名称已存在' + }, + watermark: { + support_params: '当前支持的参数:', + enable: '启用水印', + enable_panel_custom: '允许仪表板单独打开或者关闭水印', + content: '水印内容', + custom_content: '自定义公式', + account: '账号', + nick_name: '昵称', + ip: 'IP', + now: '当前时间', + watermark_color: '水印颜色', + watermark_font_size: '水印字号', + watermark_space: '水印间距', + horizontal: '横向间距', + vertical: '纵向间距', + reset: '重置', + preview: '预览', + save: '保存' } } diff --git a/core/core-frontend/src/views/watermark/ParamsTips.vue b/core/core-frontend/src/views/watermark/ParamsTips.vue new file mode 100644 index 0000000000..c77e6c90cd --- /dev/null +++ b/core/core-frontend/src/views/watermark/ParamsTips.vue @@ -0,0 +1,33 @@ +<template> + <div class="tips-class"> + <el-tooltip class="item" effect="dark" placement="bottom"> + <template #content> + <div> + <span>{{ t('watermark.support_params') }}</span + ><br /> + <span>${username}-{{ t('watermark.account') }}</span + ><br /> + <span>${nickName}-{{ t('watermark.nick_name') }}</span + ><br /> + <span>${time}-{{ t('watermark.now') }}</span + ><br /> + <span>${ip}-IP</span><br /> + </div> + </template> + <span><i class="el-icon-warning" /></span> + </el-tooltip> + </div> +</template> + +<script setup lang="ts"> +import { useI18n } from '@/hooks/web/useI18n' +const { t } = useI18n() +</script> + +<style scoped lang="less"> +.tips-class { + position: absolute; + right: 10px; + z-index: 10; +} +</style> diff --git a/core/core-frontend/src/views/watermark/index.vue b/core/core-frontend/src/views/watermark/index.vue new file mode 100644 index 0000000000..0d023a5b47 --- /dev/null +++ b/core/core-frontend/src/views/watermark/index.vue @@ -0,0 +1,304 @@ +<template xmlns:el-col="http://www.w3.org/1999/html"> + <p class="router-title">水印管理</p> + <el-row class="watermark-table__content"> + <el-row class="watermark-main-outer"> + <el-col class="main-col-left" :span="12"> + <el-form ref="watermarkForm" :model="state.watermarkForm" label-width="120px" size="middle"> + <el-form-item :label="t('watermark.enable')" style="text-align: left"> + <el-switch + size="middle" + v-model="state.watermarkForm.enable" + @change="enableChange" + ></el-switch> + </el-form-item> + <el-form-item label="" style="text-align: left"> + <el-checkbox + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.enablePanelCustom" + ></el-checkbox> + {{ t('watermark.enable_panel_custom') }} + </el-form-item> + <el-form-item :label="t('watermark.content')" style="text-align: left"> + <el-select :disabled="!state.watermarkForm.enable" v-model="state.watermarkForm.type"> + <el-option :label="t('watermark.custom_content')" value="custom" /> + <el-option :label="t('watermark.account')" value="userName" /> + <el-option :label="t('watermark.nick_name')" value="nickName" /> + <el-option :label="t('watermark.ip')" value="ip" /> + <el-option :label="t('watermark.now')" value="time" /> + </el-select> + </el-form-item> + + <el-form-item + label="" + v-show="state.watermarkForm.type === 'custom'" + style="text-align: left" + > + <params-tips></params-tips> + <el-input + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.content" + type="textarea" + :autosize="{ minRows: 4, maxRows: 4 }" + /> + </el-form-item> + + <el-form-item :label="t('watermark.watermark_color')" style="text-align: left"> + <el-color-picker + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.watermark_color" + :predefine="state.predefineColors" + size="middle" + /> + </el-form-item> + <el-form-item :label="t('watermark.watermark_font_size')" style="text-align: left"> + <el-input-number + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.watermark_fontsize" + :min="12" + :max="32" + size="middle" + /> + px + </el-form-item> + <el-form-item :label="t('watermark.horizontal')" style="text-align: left"> + <el-input-number + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.watermark_x_space" + :min="10" + :max="400" + /> + px + </el-form-item> + <el-form-item :label="t('watermark.vertical')" style="text-align: left"> + <el-input-number + :disabled="!state.watermarkForm.enable" + v-model="state.watermarkForm.watermark_y_space" + :min="10" + :max="400" + /> + px + </el-form-item> + </el-form> + <el-row style="margin-left: 53px; text-align: left"> + <el-button size="middle" type="i" @click="cancel">{{ t('watermark.reset') }} </el-button> + <el-button size="middle" type="info" @click="preview" + >{{ t('watermark.preview') }} + </el-button> + <el-button type="primary" size="middle" @click="save" + >{{ t('watermark.save') }} + </el-button> + </el-row> + </el-col> + <el-col :span="12" style="height: 100%"> + <div + id="watermark-demo" + style="position: relative; width: 100%; height: 100%; padding: 20px" + > + <div class="demo-preview"> + <img style="height: 100%" src="@/assets/img/watermark-demo-light.png" /> + </div> + <div class="demo-preview" style="margin-top: 15px"> + <img style="height: 100%" src="@/assets/img/watermark-demo-dark.png" /> + </div> + </div> + </el-col> + </el-row> + </el-row> +</template> + +<script setup> +import { onMounted, reactive, ref } from 'vue' +import { watermarkFind, watermarkSave } from '@/api/watermark' +import { ElMessage } from 'element-plus-secondary/es' +import { personInfoApi } from '@/api/user' +import { getNow, watermark } from '@/components/watermark/watermark' +import { useI18n } from '@/hooks/web/useI18n' +const { t } = useI18n() + +const state = reactive({ + userLoginInfo: { + username: '', + nickName: '', + ip: '' + }, + cmOption: { + tabSize: 2, + styleActiveLine: true, + lineNumbers: true, + line: true, + mode: 'text/x-textile', + theme: 'solarized', + hintOptions: { + // 自定义提示选项 + completeSingle: false // 当匹配只有一项的时候是否自动补全 + } + }, + watermarkFormSource: null, + predefineColors: [ + '#ff4500', + '#ff8c00', + '#ffd700', + '#90ee90', + '#00ced1', + '#1e90ff', + '#c71585', + '#999999', + '#000000', + '#FFFFFF' + ], + watermarkForm: { + enable: false, + enablePanelCustom: false, + type: 'userName', + content: '${time}-${ip}-${nickName}', + watermark_color: '#999999', + watermark_x_space: 100, + watermark_y_space: 100, + watermark_fontsize: 20 + } +}) + +const enableChange = val => { + initWatermark() +} + +const preview = () => { + initWatermark() +} + +const cancel = () => { + state.watermarkForm = { ...state.watermarkFormSource } + const params = { + settingContent: JSON.stringify(state.watermarkForm) + } + watermarkSave(params).then(rsp => { + //ignore + }) + initWatermark() +} + +const save = () => { + const params = { + settingContent: JSON.stringify(state.watermarkForm) + } + watermarkSave(params).then(rsp => { + ElMessage.success('保存成功') + }) +} + +const findData = callback => { + watermarkFind().then(rsp => { + callback(rsp) + }) +} + +const findUserData = callback => { + personInfoApi().then(rsp => { + callback(rsp) + }) +} + +const initData = () => { + findData(res => { + state.watermarkForm = JSON.parse(res.data.settingContent) + state.watermarkFormSource = { ...state.watermarkForm } + initWatermark() + }) +} + +const initWatermark = () => { + let watermark_txt + let watermark_width = 120 + if (state.watermarkForm.type === 'custom') { + watermark_txt = state.watermarkForm.content + watermark_txt = watermark_txt.replaceAll('${ip}', state.userLoginInfo.ip) + watermark_txt = watermark_txt.replaceAll('${username}', state.userLoginInfo.name) + watermark_txt = watermark_txt.replaceAll('${nickName}', state.userLoginInfo.account) + watermark_txt = watermark_txt.replaceAll('${time}', getNow()) + watermark_width = watermark_txt.length * state.watermarkForm.watermark_fontsize * 0.75 + watermark_width = watermark_width > 350 ? 350 : watermark_width + } else if (state.watermarkForm.type === 'nickName') { + watermark_txt = state.userLoginInfo.account + } else if (state.watermarkForm.type === 'ip') { + watermark_txt = state.userLoginInfo.ip + watermark_width = watermark_txt.length * state.watermarkForm.watermark_fontsize + 30 + } else if (state.watermarkForm.type === 'time') { + watermark_txt = getNow() + watermark_width = 200 + } else { + watermark_txt = state.userLoginInfo.name + } + const settings = { + watermark_enable: state.watermarkForm.enable, + watermark_txt: watermark_txt, + watermark_width: watermark_width, + watermark_color: state.watermarkForm.watermark_color, + watermark_x_space: state.watermarkForm.watermark_x_space, + watermark_y_space: state.watermarkForm.watermark_y_space, + watermark_fontsize: state.watermarkForm.watermark_fontsize + 'px' + } + // 清理历史水印 + const historyWatermarkDom = document.getElementById('de-watermark-server') + if (historyWatermarkDom) { + historyWatermarkDom.remove() + } + if (state.watermarkForm.enable) { + watermark(settings, 'watermark-demo') + } +} + +onMounted(() => { + findUserData(res => { + state.userLoginInfo = res.data + initData() + }) +}) +</script> + +<style lang="less" scoped> +.demo-preview { + height: calc(50vh - 130px); + width: 100%; + float: left; + overflow-x: auto; + overflow-y: hidden; + text-align: center; +} + +.main-col-left { + overflow-y: auto; + min-width: 240px; + padding-right: 20px; + padding-top: 20px; + height: 100%; + border-right: 1px solid gainsboro; +} + +.watermark-main-outer { + border: 1px solid gainsboro; + height: calc(100vh - 180px); + min-width: 800px; + min-height: 500px; + overflow-y: hidden; +} + +.router-title { + color: #1f2329; + font-feature-settings: 'clig' off, 'liga' off; + font-family: PingFang SC; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: 28px; +} +.watermark-table__content { + padding: 24px; + width: 100%; + background: var(--ContentBG, #ffffff); + height: calc(100% - 40px) !important; + box-sizing: border-box; + margin-top: 12px; + overflow-x: auto; + border-radius: 4px; +} +</style> diff --git a/sdk/api/api-base/src/main/java/io/dataease/api/visualization/VisualizationWatermarkApi.java b/sdk/api/api-base/src/main/java/io/dataease/api/visualization/VisualizationWatermarkApi.java new file mode 100644 index 0000000000..8714d4056e --- /dev/null +++ b/sdk/api/api-base/src/main/java/io/dataease/api/visualization/VisualizationWatermarkApi.java @@ -0,0 +1,27 @@ +package io.dataease.api.visualization; + +import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import io.dataease.api.visualization.request.VisualizationSubjectRequest; +import io.dataease.api.visualization.request.VisualizationWatermarkRequest; +import io.dataease.api.visualization.vo.VisualizationSubjectVO; +import io.dataease.api.visualization.vo.VisualizationWatermarkVO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "可视化管理:水印") +@ApiSupport(order = 994) +public interface VisualizationWatermarkApi { + + + @ResponseBody + @GetMapping("/find") + VisualizationWatermarkVO getWatermarkInfo(); + + @ResponseBody + @PostMapping("/save") + void saveWatermarkInfo(@RequestBody VisualizationWatermarkRequest watermarkRequest); + +} diff --git a/sdk/api/api-base/src/main/java/io/dataease/api/visualization/request/VisualizationWatermarkRequest.java b/sdk/api/api-base/src/main/java/io/dataease/api/visualization/request/VisualizationWatermarkRequest.java new file mode 100644 index 0000000000..608c61adb8 --- /dev/null +++ b/sdk/api/api-base/src/main/java/io/dataease/api/visualization/request/VisualizationWatermarkRequest.java @@ -0,0 +1,10 @@ +package io.dataease.api.visualization.request; + +import io.dataease.api.visualization.vo.VisualizationWatermarkVO; + +/** + * @author : WangJiaHao + * @date : 2024/1/10 17:02 + */ +public class VisualizationWatermarkRequest extends VisualizationWatermarkVO { +}