整合前端
This commit is contained in:
425
ruoyi-ui/src/views/oa/application/index.vue
Normal file
425
ruoyi-ui/src/views/oa/application/index.vue
Normal file
@@ -0,0 +1,425 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="activeTab" style="margin-bottom: 15px;">
|
||||
<el-tab-pane label="Web应用" name="web">
|
||||
<div class="header-bar" style="display: flex; justify-content: flex-end; align-items: center; margin-bottom: 20px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>创建应用</el-button>
|
||||
</div>
|
||||
<div class="search-section" style="margin-bottom: 30px; display: flex; justify-content: center;">
|
||||
<el-input
|
||||
v-model="queryParams.applicationName"
|
||||
placeholder="搜索应用"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
style="width: 50%; max-width: 600px;"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</div>
|
||||
<div class="all-applications-grid">
|
||||
<div v-for="item in applicationList" :key="item.applicationId" class="custom-application-card"
|
||||
@click="goToApplicationDetail(item.applicationId)"
|
||||
>
|
||||
<div class="card-actions" @click.stop>
|
||||
<el-dropdown trigger="hover" @command="handleCardCommand">
|
||||
<span class="el-dropdown-link" @click.stop @mousedown.stop>
|
||||
<i class="el-icon-more" style="font-size: 16px; color: #909399;"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="{ action: 'edit', item: item }">编辑</el-dropdown-item>
|
||||
<el-dropdown-item :command="{ action: 'delete', item: item }">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="open-type-icon">
|
||||
<el-tooltip :content="getOpenTypeText(item.routeType)" placement="right" effect="light">
|
||||
<i :class="getOpenTypeIcon(item.routeType)"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<svg-icon :icon-class="item.icon" class="card-icon" />
|
||||
<span style="font-size: 14px; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">{{ item.applicationName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="小工具" name="tools">
|
||||
<ToolsEntry />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- Add/Modify Application Dialog (keep as is) -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="50%" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="应用图标" prop="icon">
|
||||
<el-popover
|
||||
placement="bottom-start"
|
||||
width="460"
|
||||
trigger="click"
|
||||
@show="$refs['iconSelect'].reset()"
|
||||
>
|
||||
<IconSelect ref="iconSelect" @selected="selected" :active-icon="form.icon" />
|
||||
<el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly>
|
||||
<svg-icon
|
||||
v-if="form.icon"
|
||||
slot="prefix"
|
||||
:icon-class="form.icon"
|
||||
style="width: 25px;"
|
||||
/>
|
||||
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
|
||||
</el-input>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP 地址" prop="ip">
|
||||
<el-input v-model="form.ip" placeholder="请输入服务 IP 地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口号" prop="port">
|
||||
<el-input v-model="form.port" placeholder="请输入服务端口号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用名称" prop="applicationName">
|
||||
<el-input v-model="form.applicationName" placeholder="请输入应用名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路由类型" prop="routeType">
|
||||
<el-radio-group v-model="form.routeType">
|
||||
<el-radio label="0">当前页面打开</el-radio>
|
||||
<el-radio label="1">新标签页打开</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addApplication, delApplication, getApplication, listApplication, updateApplication } from "@/api/oa/application";
|
||||
import IconSelect from "@/components/IconSelect";
|
||||
import { handleApplicationRoute } from "@/utils/application";
|
||||
import ToolsEntry from './tools/index.vue';
|
||||
|
||||
export default {
|
||||
name: "Application",
|
||||
components: { IconSelect, ToolsEntry },
|
||||
data() {
|
||||
return {
|
||||
// 按钮loading
|
||||
buttonLoading: false,
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 应用集成表格数据
|
||||
applicationList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 30,
|
||||
applicationName: undefined,
|
||||
sortField: undefined,
|
||||
sortOrder: undefined,
|
||||
},
|
||||
// 表单参数
|
||||
form: {
|
||||
applicationId: undefined,
|
||||
icon: undefined,
|
||||
ip: undefined,
|
||||
port: undefined,
|
||||
applicationName: undefined,
|
||||
description: undefined,
|
||||
delFlag: undefined,
|
||||
remark: undefined,
|
||||
routeType: 0
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
applicationId: [
|
||||
{ required: true, message: "主键ID不能为空", trigger: "blur" }
|
||||
],
|
||||
ip: [
|
||||
{ required: true, message: "服务 IP 地址不能为空", trigger: "blur" }
|
||||
],
|
||||
port: [
|
||||
{ required: true, message: "服务端口号不能为空", trigger: "blur" }
|
||||
],
|
||||
applicationName: [
|
||||
{ required: true, message: "应用名称不能为空", trigger: "blur" }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: "应用描述不能为空", trigger: "blur" }
|
||||
],
|
||||
remark: [
|
||||
{ required: true, message: "备注不能为空", trigger: "blur" }
|
||||
],
|
||||
routeType: [
|
||||
{ required: true, message: "路由类型不能为空", trigger: "change" }
|
||||
]
|
||||
},
|
||||
activeTab: 'web', // 默认显示Web应用Tab
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
// 选择图标
|
||||
selected(name) {
|
||||
this.form.icon = name;
|
||||
},
|
||||
// 处理卡片操作命令
|
||||
handleCardCommand(command) {
|
||||
if (command.action === 'edit') {
|
||||
this.handleUpdate(command.item);
|
||||
} else if (command.action === 'delete') {
|
||||
this.handleDelete(command.item);
|
||||
}
|
||||
},
|
||||
// 跳转到应用详情页面
|
||||
goToApplicationDetail(id) {
|
||||
const application = this.applicationList.find(item => item.applicationId === id);
|
||||
if (application) {
|
||||
handleApplicationRoute(application);
|
||||
}
|
||||
},
|
||||
/** 查询应用集成列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
const params = { ...this.queryParams };
|
||||
if (params.sortField) {
|
||||
const [field, order] = params.sortField.split(',');
|
||||
params.sortField = field;
|
||||
params.sortOrder = order;
|
||||
}
|
||||
listApplication(params).then(response => {
|
||||
this.applicationList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
applicationId: undefined,
|
||||
icon: undefined,
|
||||
ip: undefined,
|
||||
port: undefined,
|
||||
applicationName: undefined,
|
||||
description: undefined,
|
||||
delFlag: undefined,
|
||||
remark: undefined,
|
||||
routeType: 0
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.queryParams.applicationName = undefined;
|
||||
this.queryParams.sortField = undefined;
|
||||
this.queryParams.sortOrder = undefined;
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加应用集成";
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.loading = true;
|
||||
this.reset();
|
||||
const applicationId = row.applicationId;
|
||||
getApplication(applicationId).then(response => {
|
||||
this.loading = false;
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改应用集成";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.buttonLoading = true;
|
||||
if (this.form.applicationId != null) {
|
||||
updateApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
} else {
|
||||
addApplication(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const applicationIds = row.applicationId;
|
||||
this.$modal.confirm('是否确认删除应用名称为"' + row.applicationName + '"的数据项?').then(() => {
|
||||
this.loading = true;
|
||||
return delApplication(applicationIds);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getOpenTypeIcon(type) {
|
||||
const icons = {
|
||||
'0': 'el-icon-monitor',
|
||||
'1': 'el-icon-position'
|
||||
};
|
||||
return icons[type] || icons['0'];
|
||||
},
|
||||
getOpenTypeText(type) {
|
||||
const texts = {
|
||||
'0': '在页面中打开',
|
||||
'1': '在新标签页打开'
|
||||
};
|
||||
return texts[type] || texts['0'];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.all-applications-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.custom-application-card {
|
||||
position: relative;
|
||||
width: 180px;
|
||||
height: 100px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-application-card:hover {
|
||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
opacity: 0; /* Hidden by default */
|
||||
transition: opacity 0.3s ease; /* Smooth transition for hover */
|
||||
}
|
||||
|
||||
.custom-application-card:hover .card-actions {
|
||||
opacity: 1; /* Show on hover */
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.open-type-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
pointer-events: none;
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-application-card:hover {
|
||||
.open-type-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
166
ruoyi-ui/src/views/oa/application/render.vue
Normal file
166
ruoyi-ui/src/views/oa/application/render.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="app-wrapper">
|
||||
<iframe
|
||||
v-if="appUrl"
|
||||
:src="appUrl"
|
||||
:name="appName"
|
||||
frameborder="0"
|
||||
class="app-iframe"
|
||||
@load="handleIframeLoad"
|
||||
></iframe>
|
||||
<div v-else class="loading-container">
|
||||
<fad-loading></fad-loading>
|
||||
</div>
|
||||
<div v-if="loading" class="loading-overlay">
|
||||
<fad-loading></fad-loading>
|
||||
</div>
|
||||
<div class="app-actions">
|
||||
<el-tooltip content="刷新" placement="bottom" effect="light">
|
||||
<el-button
|
||||
type="text"
|
||||
:icon="loading ? 'el-icon-loading' : 'el-icon-refresh'"
|
||||
@click="refreshApp"
|
||||
:loading="loading"
|
||||
circle
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="在新标签页打开" placement="bottom" effect="light">
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-position"
|
||||
@click="openInNewTab"
|
||||
circle
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getApplication } from "@/api/oa/application";
|
||||
import FadLoading from "@/components/fad-ui/fad-loading";
|
||||
|
||||
export default {
|
||||
name: "ApplicationRender",
|
||||
components: {
|
||||
FadLoading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
appUrl: "",
|
||||
appName: "",
|
||||
applicationId: null,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.applicationId = this.$route.params.applicationId;
|
||||
this.appName = `app-${this.applicationId}`;
|
||||
this.loading = true;
|
||||
this.getApplicationData();
|
||||
},
|
||||
methods: {
|
||||
async getApplicationData() {
|
||||
try {
|
||||
const response = await getApplication(this.applicationId);
|
||||
const { ip, port } = response.data;
|
||||
let url;
|
||||
|
||||
if (ip.startsWith('http://') || ip.startsWith('https://')) {
|
||||
url = ip;
|
||||
} else {
|
||||
url = `http://${ip}`;
|
||||
if (port) {
|
||||
url += `:${port}`;
|
||||
}
|
||||
}
|
||||
this.appUrl = url;
|
||||
} catch (error) {
|
||||
console.error("获取应用信息出错:", error);
|
||||
this.$message.error("获取应用信息出错");
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleIframeLoad() {
|
||||
this.loading = false;
|
||||
},
|
||||
refreshApp() {
|
||||
this.loading = true;
|
||||
const iframe = document.querySelector('.app-iframe');
|
||||
if (iframe) {
|
||||
iframe.src = this.appUrl;
|
||||
}
|
||||
},
|
||||
openInNewTab() {
|
||||
if (this.appUrl) {
|
||||
window.open(this.appUrl, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 84px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.app-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.app-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.app-actions {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
.el-button {
|
||||
padding: 6px;
|
||||
font-size: 16px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
137
ruoyi-ui/src/views/oa/application/tools/index.vue
Normal file
137
ruoyi-ui/src/views/oa/application/tools/index.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="tools-container">
|
||||
<div class="tools-grid">
|
||||
<div
|
||||
v-for="tool in tools"
|
||||
:key="tool.key"
|
||||
class="tool-card"
|
||||
@click="openTool(tool)"
|
||||
>
|
||||
<svg-icon v-if="tool.iconType === 'svg'" :icon-class="tool.icon" class="tool-icon" />
|
||||
<i v-else-if="tool.iconType === 'el'" :class="tool.icon" class="tool-icon"></i>
|
||||
<div class="tool-title">{{ tool.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-show="currentTool && currentTool.openType === 'drawer'"
|
||||
:visible.sync="drawerVisible"
|
||||
:title="currentTool ? currentTool.title : ''"
|
||||
size="40%"
|
||||
direction="rtl"
|
||||
@close="closeDrawer"
|
||||
@closed="afterDrawerClosed"
|
||||
>
|
||||
<component
|
||||
v-if="currentTool && currentTool.component"
|
||||
:is="currentTool.component"
|
||||
/>
|
||||
<div v-else style="text-align:center; color:#999; padding:40px 0;">敬请期待</div>
|
||||
</el-drawer>
|
||||
<el-dialog
|
||||
v-if="currentTool && currentTool.openType === 'dialog'"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="currentTool ? currentTool.title : ''"
|
||||
width="40%"
|
||||
@close="closeDialog"
|
||||
>
|
||||
<component
|
||||
v-if="currentTool && currentTool.component"
|
||||
:is="currentTool.component"
|
||||
/>
|
||||
<div v-else style="text-align:center; color:#999; padding:40px 0;">敬请期待</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import QrGenerator from './qr.vue';
|
||||
|
||||
export default {
|
||||
name: 'ToolsEntry',
|
||||
components: { QrGenerator, SvgIcon },
|
||||
data() {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
key: 'qr',
|
||||
title: '二维码生成器',
|
||||
icon: 'qr', // 只写图标名
|
||||
iconType: 'svg', // 新增
|
||||
component: 'QrGenerator',
|
||||
openType: 'drawer' // 新增
|
||||
},
|
||||
// {
|
||||
// key: 'demo',
|
||||
// title: '演示工具',
|
||||
// icon: 'el-icon-s-tools',
|
||||
// iconType: 'el', // 新增
|
||||
// component: null, // 先占位
|
||||
// openType: 'dialog' // 新增
|
||||
// },
|
||||
// 可继续添加更多工具
|
||||
],
|
||||
drawerVisible: false,
|
||||
dialogVisible: false, // 新增
|
||||
currentTool: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openTool(tool) {
|
||||
this.currentTool = tool;
|
||||
if (tool.openType === 'drawer') {
|
||||
this.drawerVisible = true;
|
||||
} else if (tool.openType === 'dialog') {
|
||||
this.dialogVisible = true;
|
||||
}
|
||||
},
|
||||
closeDrawer() {
|
||||
this.drawerVisible = false;
|
||||
// 不要立即清空 currentTool
|
||||
},
|
||||
afterDrawerClosed() {
|
||||
this.currentTool = null;
|
||||
},
|
||||
closeDialog() {
|
||||
this.dialogVisible = false;
|
||||
this.currentTool = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tools-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.tool-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120px;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.tool-card:hover {
|
||||
box-shadow: 0 4px 16px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
.tool-icon {
|
||||
font-size: 36px;
|
||||
color: #409EFF;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tool-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
122
ruoyi-ui/src/views/oa/application/tools/qr.vue
Normal file
122
ruoyi-ui/src/views/oa/application/tools/qr.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="qr-generator">
|
||||
<el-form :inline="false" @submit.native.prevent class="qr-form">
|
||||
<el-form-item label="二维码内容" class="qr-form-item">
|
||||
<el-input
|
||||
v-model="text"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入要生成二维码的内容,可多行"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="qr-form-item-btns">
|
||||
<el-button type="primary" @click="generate">生成二维码</el-button>
|
||||
<el-button type="success" :disabled="!qrDataUrl" @click="saveQr">保存二维码</el-button>
|
||||
<el-button type="info" :disabled="!qrDataUrl" @click="printQr">打印二维码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<transition name="fade">
|
||||
<div v-if="qrDataUrl" class="qr-result">
|
||||
<img :src="qrDataUrl" alt="二维码" ref="qrImg" />
|
||||
<div style="margin-top: 10px; color: #666; font-size: 13px;">可右键保存或点击“保存二维码”按钮</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
export default {
|
||||
name: 'QrGenerator',
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
qrDataUrl: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
if (!this.text) {
|
||||
this.$message.warning('请输入内容');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.qrDataUrl = await QRCode.toDataURL(this.text, { width: 240 });
|
||||
} catch (e) {
|
||||
this.$message.error('二维码生成失败');
|
||||
}
|
||||
},
|
||||
saveQr() {
|
||||
if (!this.qrDataUrl) return;
|
||||
const a = document.createElement('a');
|
||||
a.href = this.qrDataUrl;
|
||||
a.download = 'qrcode.png';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
},
|
||||
printQr() {
|
||||
if (!this.qrDataUrl) return;
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>打印二维码</title>
|
||||
<style>
|
||||
body { text-align: center; margin: 0; padding: 40px 0; }
|
||||
img { width: 240px; height: 240px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="${this.qrDataUrl}" alt="二维码" />
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.qr-generator {
|
||||
padding: 20px 0;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.qr-form {
|
||||
background: #fafbfc;
|
||||
border-radius: 8px;
|
||||
padding: 20px 20px 10px 20px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.04);
|
||||
}
|
||||
.qr-form-item {
|
||||
width: 100%;
|
||||
}
|
||||
.qr-form-item-btns {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.qr-result {
|
||||
margin-top: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
.qr-result img {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.08);
|
||||
background: #fff;
|
||||
}
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user