<template>
|
<div class="task-tree-container">
|
<!-- 任务树操作按钮 -->
|
<div class="tree-actions mb10">
|
<el-button type="primary"
|
size="small"
|
icon="Plus"
|
@click="handleAddTask">添加任务</el-button>
|
<el-button type="success"
|
size="small"
|
icon="RefreshRight"
|
@click="refreshTree">刷新</el-button>
|
<el-button type="info"
|
size="small"
|
icon="Filter"
|
@click="toggleFilter">
|
{{ showFilter ? '隐藏筛选' : '显示筛选' }}
|
</el-button>
|
</div>
|
<!-- 筛选条件 -->
|
<div v-if="showFilter"
|
class="filter-section mb10">
|
<el-form :inline="true"
|
:model="filterParams">
|
<el-form-item label="任务状态">
|
<el-select v-model="filterParams.status"
|
placeholder="全部"
|
clearable
|
style="width: 120px">
|
<el-option label="未开始"
|
value="notStarted" />
|
<el-option label="进行中"
|
value="inProgress" />
|
<el-option label="已完成"
|
value="completed" />
|
<el-option label="已逾期"
|
value="overdue" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="负责人">
|
<el-input v-model="filterParams.assignee"
|
placeholder="输入负责人"
|
clearable
|
style="width: 150px" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary"
|
size="small"
|
@click="filterTree">筛选</el-button>
|
<el-button size="small"
|
@click="resetFilter">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
<!-- 任务树 -->
|
<div class="tree-content">
|
<el-tree v-loading="loading"
|
:data="taskTreeData"
|
:props="defaultProps"
|
:expand-on-click-node="false"
|
node-key="nodeId"
|
ref="treeRef"
|
@node-contextmenu="handleContextMenu"
|
@node-click="handleNodeClick">
|
<template #default="{ node, data }">
|
<!-- 节点内容 -->
|
<div class="tree-node-content"
|
:class="{ 'phase-node': data.type === 'phase', 'task-node': data.type === 'task' }">
|
<!-- 节点图标 -->
|
<div class="node-icon">
|
<i v-if="data.type === 'phase'"
|
class="el-icon-folder text-primary" />
|
<i v-else-if="data.status === 'completed'"
|
class="el-icon-circle-check text-success" />
|
<i v-else-if="data.status === 'inProgress'"
|
class="el-icon-circle-check text-primary" />
|
<i v-else-if="data.status === 'overdue'"
|
class="el-icon-alarm-clock text-danger" />
|
<i v-else
|
class="el-icon-circle-close text-gray-400" />
|
</div>
|
<!-- 节点标题和描述 -->
|
<div class="node-info">
|
<div class="node-title"
|
:class="{ 'overdue-title': data.type === 'task' && data.status === 'overdue' }">
|
{{ node.label }}
|
<span v-if="data.type === 'task' && data.priority === 'high'"
|
class="priority-tag">高优</span>
|
<span v-else-if="data.type === 'task' && data.priority === 'medium'"
|
class="priority-tag medium">中优</span>
|
</div>
|
<div v-if="data.description"
|
class="node-description">{{ data.description }}</div>
|
<!-- 任务元信息 -->
|
<div v-if="data.type === 'task'"
|
class="task-meta">
|
<span class="meta-item">
|
<i class="el-icon-user"></i>
|
{{ data.assigneeName || '未分配' }}
|
</span>
|
<span class="meta-item">
|
<i class="el-icon-calendar"></i>
|
{{ formatDateRange(data.startDate, data.endDate) }}
|
</span>
|
</div>
|
</div>
|
<!-- 任务进度条 -->
|
<div v-if="data.type === 'task'"
|
class="task-progress">
|
<el-progress :percentage="data.progress || 0"
|
:stroke-width="4"
|
:show-text="false" />
|
</div>
|
<!-- 操作按钮 -->
|
<div class="node-actions">
|
<el-button v-if="data.type === 'task'"
|
type="text"
|
size="small"
|
icon="Edit"
|
@click.stop="handleEditTask(data)"
|
v-hasPermi="['oaSystem:task:edit']" />
|
<el-button v-if="data.type === 'phase'"
|
type="text"
|
size="small"
|
icon="Plus"
|
@click.stop="handleAddTaskUnderPhase(data)"
|
v-hasPermi="['oaSystem:task:add']" />
|
<el-button type="text"
|
size="small"
|
icon="Delete"
|
@click.stop="handleDeleteNode(data)"
|
v-hasPermi="['oaSystem:task:remove']" />
|
</div>
|
</div>
|
</template>
|
</el-tree>
|
</div>
|
<!-- 右键菜单 -->
|
<div v-if="showContextMenu"
|
:style="contextMenuStyle"
|
class="context-menu">
|
<el-menu @select="handleContextMenuSelect">
|
<el-menu-item v-if="selectedNode.type === 'task'"
|
index="edit">编辑任务</el-menu-item>
|
<el-menu-item v-if="selectedNode.type === 'phase'"
|
index="addTask">添加子任务</el-menu-item>
|
<el-menu-item index="delete">删除</el-menu-item>
|
<el-menu-item index="expandAll">展开全部</el-menu-item>
|
<el-menu-item index="collapseAll">收起全部</el-menu-item>
|
</el-menu>
|
</div>
|
<!-- 任务表单对话框 -->
|
<el-dialog :title="dialogTitle"
|
v-model="dialogOpen"
|
width="600px"
|
append-to-body>
|
<el-form ref="taskFormRef"
|
:model="taskForm"
|
:rules="taskRules"
|
label-width="80px">
|
<el-form-item label="任务名称"
|
prop="taskName">
|
<el-input v-model="taskForm.taskName"
|
placeholder="请输入任务名称" />
|
</el-form-item>
|
<el-form-item label="负责人"
|
prop="assigneeId">
|
<el-input v-model="taskForm.assigneeId"
|
placeholder="请输入负责人ID" />
|
</el-form-item>
|
<el-form-item label="开始日期"
|
prop="startDate">
|
<el-date-picker v-model="taskForm.startDate"
|
type="date"
|
placeholder="选择开始日期"
|
style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="结束日期"
|
prop="endDate">
|
<el-date-picker v-model="taskForm.endDate"
|
type="date"
|
placeholder="选择结束日期"
|
style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="优先级"
|
prop="priority">
|
<el-select v-model="taskForm.priority"
|
placeholder="选择优先级">
|
<el-option label="低"
|
value="low" />
|
<el-option label="中"
|
value="medium" />
|
<el-option label="高"
|
value="high" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="进度"
|
prop="progress">
|
<el-input-number v-model="taskForm.progress"
|
:min="0"
|
:max="100"
|
style="width: 100%" />
|
</el-form-item>
|
<el-form-item label="描述"
|
prop="description">
|
<el-input v-model="taskForm.description"
|
type="textarea"
|
placeholder="请输入任务描述" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="submitTaskForm">确定</el-button>
|
<el-button @click="dialogOpen = false">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, watch, onMounted } from "vue";
|
import { ElMessage, ElMessageBox, ElMenu, ElMenuItem } from "element-plus";
|
// import { getProject, addTask, updateTask, deleteTask, deletePhase } from '@/api/oaSystem/projectManagement';
|
|
const props = defineProps({
|
projectId: {
|
type: String,
|
required: true,
|
},
|
});
|
|
const emit = defineEmits(["refresh"]);
|
|
// 组件状态
|
const loading = ref(false);
|
const treeRef = ref();
|
const showContextMenu = ref(false);
|
const contextMenuStyle = ref({});
|
const selectedNode = ref({});
|
const showFilter = ref(false);
|
const dialogOpen = ref(false);
|
const dialogTitle = ref("");
|
const taskFormRef = ref();
|
|
// 筛选参数
|
const filterParams = reactive({
|
status: "",
|
assignee: "",
|
});
|
|
// 任务表单数据
|
const taskForm = reactive({
|
taskId: undefined,
|
taskName: "",
|
description: "",
|
startDate: "",
|
endDate: "",
|
assigneeId: "",
|
assigneeName: "",
|
status: "notStarted",
|
progress: 0,
|
priority: "medium",
|
phaseId: "",
|
projectId: props.projectId,
|
});
|
|
// 表单验证规则
|
const taskRules = {
|
taskName: [
|
{ required: true, message: "任务名称不能为空", trigger: "blur" },
|
{
|
min: 2,
|
max: 50,
|
message: "任务名称长度在 2 到 50 个字符",
|
trigger: "blur",
|
},
|
],
|
startDate: [
|
{ required: true, message: "开始日期不能为空", trigger: "change" },
|
],
|
endDate: [{ required: true, message: "结束日期不能为空", trigger: "change" }],
|
assigneeId: [{ required: true, message: "负责人不能为空", trigger: "blur" }],
|
progress: [
|
{ required: true, message: "进度不能为空", trigger: "blur" },
|
{
|
type: "number",
|
min: 0,
|
max: 100,
|
message: "进度必须在 0 到 100 之间",
|
trigger: "blur",
|
},
|
],
|
};
|
|
// 任务树数据
|
const rawTaskTreeData = ref([]);
|
|
// 模拟任务数据
|
const mockTaskData = {
|
PRJ2023001: [
|
{
|
phaseId: "PHASE001",
|
phaseName: "需求分析",
|
startDate: "2023-11-01",
|
endDate: "2023-11-15",
|
status: "completed",
|
tasks: [
|
{
|
taskId: "TASK001",
|
taskName: "需求调研",
|
description: "调研用户需求和业务流程",
|
startDate: "2023-11-01",
|
endDate: "2023-11-05",
|
assigneeId: "USER001",
|
assigneeName: "张三",
|
status: "completed",
|
progress: 100,
|
priority: "medium",
|
},
|
{
|
taskId: "TASK002",
|
taskName: "需求文档编写",
|
description: "编写详细的需求规格说明书",
|
startDate: "2023-11-06",
|
endDate: "2023-11-15",
|
assigneeId: "USER002",
|
assigneeName: "李四",
|
status: "completed",
|
progress: 100,
|
priority: "high",
|
},
|
],
|
},
|
{
|
phaseId: "PHASE002",
|
phaseName: "系统设计",
|
startDate: "2023-11-16",
|
endDate: "2023-12-10",
|
status: "completed",
|
tasks: [
|
{
|
taskId: "TASK003",
|
taskName: "系统架构设计",
|
description: "设计系统整体架构",
|
startDate: "2023-11-16",
|
endDate: "2023-11-25",
|
assigneeId: "USER003",
|
assigneeName: "王五",
|
status: "completed",
|
progress: 100,
|
priority: "high",
|
},
|
{
|
taskId: "TASK004",
|
taskName: "数据库设计",
|
description: "设计数据库表结构和关系",
|
startDate: "2023-11-26",
|
endDate: "2023-12-10",
|
assigneeId: "USER004",
|
assigneeName: "赵六",
|
status: "completed",
|
progress: 100,
|
priority: "medium",
|
},
|
],
|
},
|
{
|
phaseId: "PHASE003",
|
phaseName: "开发实现",
|
startDate: "2023-12-11",
|
endDate: "2024-01-31",
|
status: "inProgress",
|
tasks: [
|
{
|
taskId: "TASK005",
|
taskName: "前端开发",
|
description: "开发用户界面和交互逻辑",
|
startDate: "2023-12-11",
|
endDate: "2024-01-15",
|
assigneeId: "USER005",
|
assigneeName: "钱七",
|
status: "inProgress",
|
progress: 70,
|
priority: "high",
|
},
|
{
|
taskId: "TASK006",
|
taskName: "后端开发",
|
description: "开发业务逻辑和API接口",
|
startDate: "2023-12-11",
|
endDate: "2024-01-20",
|
assigneeId: "USER006",
|
assigneeName: "孙八",
|
status: "inProgress",
|
progress: 60,
|
priority: "high",
|
},
|
],
|
},
|
],
|
// 默认数据
|
default: [
|
{
|
phaseId: "PHASE_DEFAULT1",
|
phaseName: "准备阶段",
|
startDate: "2023-01-01",
|
endDate: "2023-03-31",
|
status: "completed",
|
tasks: [
|
{
|
taskId: "TASK_DEFAULT1",
|
taskName: "项目启动",
|
description: "召开项目启动会议",
|
startDate: "2023-01-01",
|
endDate: "2023-01-05",
|
assigneeId: "USER_DEFAULT1",
|
assigneeName: "负责人A",
|
status: "completed",
|
progress: 100,
|
priority: "high",
|
},
|
],
|
},
|
{
|
phaseId: "PHASE_DEFAULT2",
|
phaseName: "执行阶段",
|
startDate: "2023-04-01",
|
endDate: "2023-09-30",
|
status: "inProgress",
|
tasks: [
|
{
|
taskId: "TASK_DEFAULT2",
|
taskName: "核心功能开发",
|
description: "开发系统核心功能模块",
|
startDate: "2023-04-01",
|
endDate: "2023-06-30",
|
assigneeId: "USER_DEFAULT2",
|
assigneeName: "负责人B",
|
status: "inProgress",
|
progress: 50,
|
priority: "high",
|
},
|
],
|
},
|
],
|
};
|
|
const taskTreeData = computed(() => {
|
// 应用筛选条件
|
if (!showFilter.value || (!filterParams.status && !filterParams.assignee)) {
|
return rawTaskTreeData.value;
|
}
|
|
// 深拷贝原始数据以避免修改
|
const filteredData = JSON.parse(JSON.stringify(rawTaskTreeData.value));
|
|
// 递归筛选节点
|
const filterNodes = nodes => {
|
const result = [];
|
|
nodes.forEach(node => {
|
// 对于阶段节点,检查其子任务是否符合筛选条件
|
if (node.type === "phase" && node.children) {
|
const filteredChildren = filterNodes(node.children);
|
if (filteredChildren.length > 0) {
|
// 保留至少有一个子任务符合条件的阶段
|
node.children = filteredChildren;
|
result.push(node);
|
}
|
}
|
// 对于任务节点,直接应用筛选条件
|
else if (node.type === "task") {
|
const statusMatch =
|
!filterParams.status || node.status === filterParams.status;
|
const assigneeMatch =
|
!filterParams.assignee ||
|
(node.assigneeName &&
|
node.assigneeName.includes(filterParams.assignee));
|
|
if (statusMatch && assigneeMatch) {
|
result.push(node);
|
}
|
}
|
});
|
|
return result;
|
};
|
|
return filterNodes(filteredData);
|
});
|
|
// 树节点配置
|
const defaultProps = {
|
children: "children",
|
label: data => {
|
if (data.type === "phase") {
|
return `${data.phaseName}`;
|
} else {
|
return `${data.taskName}`;
|
}
|
},
|
};
|
|
// 加载任务树数据
|
const loadTaskTree = async () => {
|
loading.value = true;
|
// try {
|
// const { data } = await getProject(props.projectId);
|
// rawTaskTreeData.value = buildTaskTree(data.phases || []);
|
// } catch (error) {
|
// ElMessage.error('加载任务树失败');
|
// console.error('加载任务树失败:', error);
|
// } finally {
|
// loading.value = false;
|
// }
|
try {
|
// 模拟网络延迟
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
// 使用模拟数据替代API请求
|
const phases = mockTaskData[props.projectId] || mockTaskData.default;
|
rawTaskTreeData.value = buildTaskTree(phases);
|
} catch (error) {
|
ElMessage.error("加载任务树失败");
|
console.error("加载任务树失败:", error);
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 构建任务树
|
const buildTaskTree = phases => {
|
return phases.map(phase => ({
|
nodeId: phase.phaseId,
|
phaseId: phase.phaseId,
|
phaseName: phase.phaseName,
|
type: "phase",
|
children: (phase.tasks || []).map(task => ({
|
nodeId: task.taskId,
|
taskId: task.taskId,
|
taskName: task.taskName,
|
description: task.description,
|
startDate: task.startDate,
|
endDate: task.endDate,
|
assigneeId: task.assigneeId,
|
assigneeName: task.assigneeName,
|
status: task.status,
|
progress: task.progress,
|
priority: task.priority,
|
phaseId: task.phaseId,
|
projectId: props.projectId,
|
type: "task",
|
})),
|
}));
|
};
|
|
// 格式化日期范围
|
const formatDateRange = (startDate, endDate) => {
|
if (!startDate || !endDate) return "";
|
return `${startDate} - ${endDate}`;
|
};
|
|
// 刷新树
|
const refreshTree = () => {
|
loadTaskTree();
|
// 通知父组件刷新数据
|
emit("refresh");
|
};
|
|
// 切换筛选面板
|
const toggleFilter = () => {
|
showFilter.value = !showFilter.value;
|
};
|
|
// 应用筛选
|
const filterTree = () => {
|
// 筛选逻辑已经在computed中实现
|
};
|
|
// 重置筛选
|
const resetFilter = () => {
|
filterParams.status = "";
|
filterParams.assignee = "";
|
};
|
|
// 处理节点点击
|
const handleNodeClick = (data, node) => {
|
// 切换展开/收起状态
|
if (data.type === "phase") {
|
node.expanded = !node.expanded;
|
}
|
};
|
|
// 处理右键菜单
|
const handleContextMenu = (event, data) => {
|
event.preventDefault();
|
selectedNode.value = data;
|
contextMenuStyle.value = {
|
position: "fixed",
|
left: `${event.clientX}px`,
|
top: `${event.clientY}px`,
|
zIndex: 1000,
|
};
|
showContextMenu.value = true;
|
};
|
|
// 处理右键菜单选择
|
const handleContextMenuSelect = index => {
|
showContextMenu.value = false;
|
switch (index) {
|
case "edit":
|
if (selectedNode.value.type === "task") {
|
handleEditTask(selectedNode.value);
|
}
|
break;
|
case "addTask":
|
if (selectedNode.value.type === "phase") {
|
handleAddTaskUnderPhase(selectedNode.value);
|
}
|
break;
|
case "delete":
|
handleDeleteNode(selectedNode.value);
|
break;
|
case "expandAll":
|
treeRef.value?.expandAll();
|
break;
|
case "collapseAll":
|
treeRef.value?.collapseAll();
|
break;
|
}
|
};
|
|
// 添加任务
|
const handleAddTask = () => {
|
resetTaskForm();
|
dialogTitle.value = "添加任务";
|
dialogOpen.value = true;
|
};
|
|
// 在指定阶段下添加任务
|
const handleAddTaskUnderPhase = phase => {
|
resetTaskForm();
|
taskForm.phaseId = phase.phaseId;
|
dialogTitle.value = "添加子任务";
|
dialogOpen.value = true;
|
};
|
|
// 编辑任务
|
const handleEditTask = task => {
|
resetTaskForm();
|
Object.assign(taskForm, { ...task });
|
dialogTitle.value = "编辑任务";
|
dialogOpen.value = true;
|
};
|
|
// 删除节点
|
const handleDeleteNode = async node => {
|
const confirmMessage =
|
node.type === "phase"
|
? `确定要删除阶段 "${node.phaseName}" 及其所有子任务吗?`
|
: `确定要删除任务 "${node.taskName}" 吗?`;
|
|
await ElMessageBox.confirm(confirmMessage, "确认操作", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).catch(() => {
|
throw new Error("取消删除");
|
});
|
|
try {
|
if (node.type === "phase") {
|
await deletePhase(node.phaseId);
|
} else {
|
await deleteTask(node.taskId);
|
}
|
ElMessage.success("删除成功");
|
refreshTree();
|
} catch (error) {
|
if (error.message !== "取消删除") {
|
ElMessage.error("删除失败");
|
console.error("删除失败:", error);
|
}
|
}
|
};
|
|
// 重置任务表单
|
const resetTaskForm = () => {
|
taskForm.taskId = undefined;
|
taskForm.taskName = "";
|
taskForm.description = "";
|
taskForm.startDate = "";
|
taskForm.endDate = "";
|
taskForm.assigneeId = "";
|
taskForm.assigneeName = "";
|
taskForm.status = "notStarted";
|
taskForm.progress = 0;
|
taskForm.priority = "medium";
|
taskForm.phaseId = "";
|
taskForm.projectId = props.projectId;
|
|
if (taskFormRef.value) {
|
taskFormRef.value.resetFields();
|
}
|
};
|
|
// 提交任务表单
|
const submitTaskForm = async () => {
|
try {
|
await taskFormRef.value.validate();
|
|
if (taskForm.taskId) {
|
await updateTask(taskForm);
|
ElMessage.success("修改任务成功");
|
} else {
|
await addTask(taskForm);
|
ElMessage.success("添加任务成功");
|
}
|
|
dialogOpen.value = false;
|
refreshTree();
|
} catch (error) {
|
console.error("提交表单失败:", error);
|
}
|
};
|
|
// 点击其他区域关闭右键菜单
|
document.addEventListener("click", () => {
|
if (showContextMenu.value) {
|
showContextMenu.value = false;
|
}
|
});
|
|
// 监听项目ID变化
|
watch(
|
() => props.projectId,
|
newProjectId => {
|
if (newProjectId) {
|
loadTaskTree();
|
}
|
}
|
);
|
|
// 初始化
|
onMounted(() => {
|
loadTaskTree();
|
});
|
</script>
|
|
<style scoped>
|
.task-tree-container {
|
padding: 10px;
|
}
|
|
.tree-actions {
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
}
|
|
.filter-section {
|
background: #f5f7fa;
|
padding: 10px;
|
border-radius: 4px;
|
}
|
|
.tree-content {
|
background: #fff;
|
border: 1px solid #ebeef5;
|
border-radius: 4px;
|
padding: 10px;
|
max-height: 600px;
|
overflow-y: auto;
|
}
|
|
.tree-node-content {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
padding: 5px 0;
|
min-height: 40px;
|
}
|
|
.phase-node {
|
font-weight: bold;
|
color: #409eff;
|
}
|
|
.task-node {
|
color: #606266;
|
}
|
|
.node-icon {
|
display: flex;
|
align-items: center;
|
width: 20px;
|
}
|
|
.node-info {
|
flex: 1;
|
min-width: 0;
|
}
|
|
.node-title {
|
font-weight: 500;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
margin-bottom: 2px;
|
}
|
|
.overdue-title {
|
color: #f56c6c;
|
font-weight: bold;
|
}
|
|
.priority-tag {
|
background: #f56c6c;
|
color: white;
|
font-size: 10px;
|
padding: 1px 4px;
|
border-radius: 2px;
|
margin-left: 5px;
|
}
|
|
.priority-tag.medium {
|
background: #e6a23c;
|
}
|
|
.node-description {
|
font-size: 12px;
|
color: #909399;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.task-meta {
|
display: flex;
|
gap: 15px;
|
font-size: 12px;
|
color: #909399;
|
margin-top: 2px;
|
}
|
|
.meta-item {
|
display: flex;
|
align-items: center;
|
gap: 3px;
|
}
|
|
.task-progress {
|
width: 120px;
|
margin: 0 10px;
|
}
|
|
.node-actions {
|
display: flex;
|
gap: 5px;
|
opacity: 0;
|
transition: opacity 0.3s;
|
}
|
|
.tree-node-content:hover .node-actions {
|
opacity: 1;
|
}
|
|
.context-menu {
|
background: white;
|
border: 1px solid #ebeef5;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
border-radius: 4px;
|
}
|
|
.context-menu .el-menu {
|
min-width: 120px;
|
border: none;
|
}
|
|
.context-menu .el-menu-item {
|
padding: 0 15px;
|
height: 36px;
|
line-height: 36px;
|
}
|
|
.context-menu .el-menu-item:hover {
|
background-color: #f5f7fa;
|
}
|
|
.text-gray-400 {
|
color: #c0c4cc;
|
}
|
</style>
|