Files
emsfront/src/views/ems/site/sbbh/AddPlan.vue

718 lines
22 KiB
Vue
Raw Normal View History

2025-10-26 22:44:04 +08:00
<template>
<el-dialog
2026-02-15 16:24:29 +08:00
v-loading="loading"
width="92%"
:visible.sync="dialogTableVisible"
class="ems-dialog plan-dialog"
title="保护方案"
:close-on-click-modal="false"
:show-close="false"
2025-10-26 22:44:04 +08:00
>
<el-form
2026-02-15 16:24:29 +08:00
v-loading="loading > 0"
ref="addTempForm"
:model="formData"
:rules="rules"
size="medium"
label-width="120px"
class="plan-form"
2025-10-26 22:44:04 +08:00
>
2026-02-15 16:24:29 +08:00
<div class="base-panel">
<div class="panel-title">基础信息</div>
<div class="base-grid">
<el-form-item label="设备保护名称" prop="faultName" class="span-1">
<el-input v-model="formData.faultName" placeholder="请输入" clearable></el-input>
</el-form-item>
<el-form-item label="是否告警" prop="isAlert" class="span-1">
<el-checkbox v-model="formData.isAlert" :true-label="1" :false-label="0">启用告警</el-checkbox>
</el-form-item>
<el-form-item label="告警等级" prop="faultLevel" class="span-1">
<el-radio-group v-model="formData.faultLevel" :disabled="mode === 'edit'">
<el-radio :label="1">等级1</el-radio>
<el-radio :label="2">等级2</el-radio>
<el-radio :label="3">等级3</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="处理方案描述" prop="description" class="span-2">
<el-input
v-model="formData.description"
type="textarea"
:rows="2"
placeholder="请输入"
clearable
></el-input>
</el-form-item>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
</div>
<div class="plan-section">
<div class="section-head">
<div>
<div class="section-title">保护前提</div>
<div class="section-desc">配置触发故障的判定条件和关系</div>
</div>
<div class="section-actions">
<el-button @click.native.prevent="addRow('protectionSettings')" type="primary" size="mini">
新增保护前提
</el-button>
</div>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<div class="row-card" v-for="(item, index) in protectionSettings" :key="'protectionSettings' + index">
<div class="row-index">{{ index + 1 }}</div>
<div class="row-grid setting-grid">
<div class="field-block field-point">
<div class="field-label">点位</div>
<el-autocomplete
v-model="item.point"
placeholder="请输入点位ID/描述"
clearable
:trigger-on-focus="false"
:debounce="250"
:value-key="'label'"
:fetch-suggestions="(q, c) => querySearchAsync(q, c, index, 'protectionSettings')"
@select="(v) => handleSelect(v, index, 'protectionSettings')"
@blur="() => fillPointMetaByPoint(item.point, index, 'protectionSettings')"
></el-autocomplete>
</div>
<div class="field-block">
<div class="field-label">故障比较符</div>
<el-select v-model="item.faultOperator" placeholder="请选择">
<el-option
v-for="(value, key) in comparisonOperatorOptions"
:key="key + 'faultOperator'"
:label="key"
:value="value"
></el-option>
</el-select>
</div>
<div class="field-block">
<div class="field-label">故障值</div>
<el-input placeholder="请输入故障值" v-model="item.faultValue"></el-input>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<div class="field-block">
<div class="field-label">释放比较符</div>
<el-select v-model="item.releaseOperator" placeholder="请选择">
<el-option
v-for="(value, key) in comparisonOperatorOptions"
:key="key + 'releaseOperator'"
:label="key"
:value="value"
></el-option>
</el-select>
</div>
<div class="field-block">
<div class="field-label">释放值</div>
<el-input placeholder="请输入释放值" v-model="item.releaseValue"></el-input>
</div>
<div class="field-block">
<div class="field-label">关系</div>
<el-select v-model="item.relationNext" placeholder="请选择">
<el-option
v-for="(value, key) in relationWithPoint"
:key="key + 'relation'"
:label="key"
:value="value"
></el-option>
</el-select>
2025-10-26 22:44:04 +08:00
</div>
</div>
2026-02-15 16:24:29 +08:00
<div class="row-action">
<el-button @click.native.prevent="deleteRow(index, 'protectionSettings')" type="warning" size="mini">
删除
</el-button>
</div>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<el-empty v-if="protectionSettings.length === 0" description="暂无保护前提,请先添加"></el-empty>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<div class="plan-section">
<div class="section-head">
<div>
<div class="section-title">保护方案</div>
<div class="section-desc">设置触发后的目标写入点位和值</div>
</div>
<div class="section-actions">
<el-button @click.native.prevent="addRow('protectionPlan')" type="primary" size="mini">
新增保护方案
</el-button>
2025-10-26 22:44:04 +08:00
</div>
</div>
2026-02-15 16:24:29 +08:00
<div class="row-card" v-for="(item, index) in protectionPlan" :key="'protectionPlan' + index">
<div class="row-index">{{ index + 1 }}</div>
<div class="row-grid plan-grid">
<div class="field-block field-point">
<div class="field-label">点位</div>
<el-autocomplete
v-model="item.point"
placeholder="请输入点位ID/描述"
clearable
:trigger-on-focus="false"
:debounce="250"
:value-key="'label'"
:fetch-suggestions="(q, c) => querySearchAsync(q, c, index, 'protectionPlan')"
@select="(v) => handleSelect(v, index, 'protectionPlan')"
@blur="() => fillPointMetaByPoint(item.point, index, 'protectionPlan')"
></el-autocomplete>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<div class="field-block eq-block">
<div class="field-label">比较</div>
<div class="eq-text">=</div>
2025-10-26 22:44:04 +08:00
</div>
2026-02-15 16:24:29 +08:00
<div class="field-block">
<div class="field-label">故障值</div>
<el-input placeholder="请输入故障值" v-model="item.value"></el-input>
</div>
</div>
<div class="row-action">
<el-button @click.native.prevent="deleteRow(index, 'protectionPlan')" type="warning" size="mini">
删除
</el-button>
2025-10-26 22:44:04 +08:00
</div>
</div>
2026-02-15 16:24:29 +08:00
<el-empty v-if="protectionPlan.length === 0" description="暂无保护方案,请先添加"></el-empty>
2025-10-26 22:44:04 +08:00
</div>
</el-form>
2026-02-15 16:24:29 +08:00
<div slot="footer" class="dialog-footer">
2025-10-26 22:44:04 +08:00
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveDialog">确定</el-button>
</div>
</el-dialog>
</template>
2026-02-15 16:24:29 +08:00
2025-10-26 22:44:04 +08:00
<script>
2026-02-15 16:24:29 +08:00
import { mapState } from "vuex";
import { validText } from "@/utils/validate";
import { addProtectPlan, getPointMatchList, getProtectPlan, updateProtectPlan } from "@/api/ems/site";
2025-12-19 21:49:19 +08:00
2025-10-26 22:44:04 +08:00
export default {
data() {
const validateText = (rule, value, callback) => {
if (value !== "" && !validText(value)) {
callback(new Error("只能输入中文、英文、数字和特殊字符!"));
} else {
callback();
}
};
return {
2026-02-15 16:24:29 +08:00
mode: "",
2025-10-26 22:44:04 +08:00
loading: 0,
protectionSettings: [],
protectionPlan: [],
dialogTableVisible: false,
formData: {
2026-02-15 16:24:29 +08:00
id: "",
siteId: "",
faultName: "",
isAlert: 0,
faultLevel: 1,
faultDelaySeconds: "",
releaseDelaySeconds: "",
description: "",
2025-10-26 22:44:04 +08:00
},
rules: {
2026-02-15 16:24:29 +08:00
faultName: [{ required: true, message: "请输入设备保护名称", trigger: "blur" }],
isAlert: [{ required: true, message: "请选择是否告警", trigger: "blur" }],
2025-10-26 22:44:04 +08:00
description: [
2026-02-15 16:24:29 +08:00
{ required: true, message: "请输入设备描述", trigger: "blur" },
{ validator: validateText, trigger: "blur" },
2025-10-31 21:34:52 +08:00
],
2025-10-26 22:44:04 +08:00
},
};
},
computed: {
...mapState({
2026-02-15 16:24:29 +08:00
comparisonOperatorOptions: (state) => state?.ems?.comparisonOperatorOptions || {},
2025-10-26 22:44:04 +08:00
relationWithPoint: (state) => state?.ems?.relationWithPoint || {},
}),
},
2025-10-31 21:34:52 +08:00
methods: {
2025-12-19 21:49:19 +08:00
open(id, siteId) {
2026-02-15 16:24:29 +08:00
const selectedSiteId = siteId || "";
this.formData.siteId = selectedSiteId;
this.dialogTableVisible = true;
2025-12-19 21:49:19 +08:00
if (id) {
2026-02-15 16:24:29 +08:00
this.formData.id = id;
this.mode = "edit";
getProtectPlan(id).then((response) => {
const data = response?.data || {};
2025-10-31 21:34:52 +08:00
this.formData = {
id,
2026-02-15 16:24:29 +08:00
siteId: selectedSiteId || data?.siteId || "",
faultName: data?.faultName || "",
isAlert: data?.isAlert || 0,
faultLevel: data?.faultLevel || 1,
faultDelaySeconds: data?.faultDelaySeconds || "",
releaseDelaySeconds: data?.releaseDelaySeconds || "",
description: data?.description || "",
};
const plan = JSON.parse(data?.protectionPlan || "[]");
const settings = JSON.parse(data?.protectionSettings || "[]");
2025-12-19 21:49:19 +08:00
this.$nextTick(() => {
2026-02-15 16:24:29 +08:00
this.protectionPlan.splice(0, 0, ...plan);
this.protectionSettings.splice(0, 0, ...settings);
});
});
2025-12-19 21:49:19 +08:00
} else {
2026-02-15 16:24:29 +08:00
this.mode = "add";
2025-10-31 21:34:52 +08:00
}
2025-10-26 22:44:04 +08:00
},
addRow(type) {
2026-02-15 16:24:29 +08:00
const item =
type === "protectionSettings"
? {
deviceId: "",
deviceName: "",
deviceCategory: "",
categoryName: "",
point: "",
pointName: "",
faultValue: "",
releaseValue: "",
faultOperator: "",
releaseOperator: "",
relationNext: "",
}
: {
deviceId: "",
deviceName: "",
deviceCategory: "",
categoryName: "",
point: "",
pointName: "",
value: "",
};
this[type].splice(this[type].length, 0, item);
2025-10-26 22:44:04 +08:00
},
deleteRow(index, type) {
this[type].splice(index, 1);
},
querySearchAsync(query, cb, index, type) {
2026-02-15 16:24:29 +08:00
if (!this.formData.siteId) {
2025-10-26 22:44:04 +08:00
this.$message({
type: "warning",
2026-02-15 16:24:29 +08:00
message: "请先选择站点",
2025-10-26 22:44:04 +08:00
});
return cb([]);
}
2026-02-15 16:24:29 +08:00
getPointMatchList({
siteId: this.formData.siteId,
pageNum: 1,
pageSize: 100,
pointId: query || "",
pointDesc: query || "",
2025-10-31 21:34:52 +08:00
})
2026-02-15 16:24:29 +08:00
.then((response) => {
const data = response?.rows || [];
cb(
data.map((item) => {
const pointLabel = item.pointId || item.pointName || item.dataKey || "";
const desc = item.pointDesc || "";
return {
value: pointLabel,
pointName: item.pointName || pointLabel,
pointId: item.pointId || pointLabel,
pointDesc: desc,
deviceId: item.deviceId || "",
deviceName: item.deviceName || "",
deviceCategory: item.deviceCategory || "",
categoryName: item.deviceCategory || "",
label: desc ? `${pointLabel}${desc}` : pointLabel,
};
2025-10-28 18:15:10 +08:00
})
2026-02-15 16:24:29 +08:00
);
})
.catch(() => cb([]));
2025-10-28 18:15:10 +08:00
},
2026-02-15 16:24:29 +08:00
fillPointMetaByPoint(point, index, type) {
if (!this.formData.siteId || !point) {
return;
2025-12-19 21:49:19 +08:00
}
2026-02-15 16:24:29 +08:00
getPointMatchList({
2025-12-19 21:49:19 +08:00
siteId: this.formData.siteId,
2026-02-15 16:24:29 +08:00
pageNum: 1,
pageSize: 1,
pointId: point,
2025-12-19 21:49:19 +08:00
}).then((response) => {
2026-02-15 16:24:29 +08:00
const row = (response?.rows || [])[0];
if (!row) return;
const line = Object.assign({}, this[type][index], {
point: row.pointId || point,
pointName: row.pointName || row.pointId || point,
deviceId: row.deviceId || this[type][index].deviceId,
deviceName: row.deviceName || this[type][index].deviceName,
deviceCategory: row.deviceCategory || this[type][index].deviceCategory,
categoryName: row.deviceCategory || this[type][index].categoryName,
});
this[type].splice(index, 1, line);
});
2025-10-28 18:15:10 +08:00
},
2026-02-15 16:24:29 +08:00
handleSelect(data, index, type) {
const line = Object.assign({}, this[type][index], {
point: data.pointId || data.value,
pointName: data.pointName || data.pointId || data.value,
deviceId: data.deviceId || this[type][index].deviceId,
deviceName: data.deviceName || this[type][index].deviceName,
deviceCategory: data.deviceCategory || this[type][index].deviceCategory,
categoryName: data.categoryName || data.deviceCategory || this[type][index].categoryName,
});
this[type].splice(index, 1, line);
2025-10-26 22:44:04 +08:00
},
saveDialog() {
2025-12-19 21:49:19 +08:00
function getToastMsg(name, type, index) {
2025-10-31 21:34:52 +08:00
return {
2025-12-19 21:49:19 +08:00
protectionSettings: {
2026-02-15 16:24:29 +08:00
deviceId: `请选择保护前提第${index}行的设备`,
deviceCategory: `请选择保护前提第${index}行的设备类型`,
categoryName: `请选择保护前提第${index}行的设备类型`,
point: `请选择保护前提第${index}行的点位`,
pointName: `请选择保护前提第${index}行的点位`,
faultValue: `请输入保护前提第${index}行的故障值`,
releaseValue: `请输入保护前提第${index}行的释放值`,
faultOperator: `请选择保护前提第${index}行的故障值比较关系`,
releaseOperator: `请选择保护前提第${index}行的释放值比较关系`,
relationNext: `请选择保护前提第${index}行与下一个点位的关系`,
2025-10-31 21:34:52 +08:00
},
2025-12-19 21:49:19 +08:00
protectionPlan: {
deviceId: `请选择保护方案第${index}行的设备`,
2026-02-15 16:24:29 +08:00
deviceCategory: `请选择保护方案第${index}行的设备类型`,
categoryName: `请选择保护方案第${index}行的设备类型`,
2025-10-31 21:34:52 +08:00
point: `请选择保护方案第${index}行的点位`,
2025-12-19 21:49:19 +08:00
pointName: `请选择保护方案第${index}行的点位`,
2026-02-15 16:24:29 +08:00
value: `请输入保护方案第${index}行的故障值`,
},
}[type][name];
2025-10-31 21:34:52 +08:00
}
2025-12-19 21:49:19 +08:00
2025-10-26 22:44:04 +08:00
this.$refs.addTempForm.validate((valid) => {
if (!valid) return;
const {
2026-02-15 16:24:29 +08:00
id = "",
siteId = "",
faultName = "",
isAlert = 0,
faultLevel = 1,
faultDelaySeconds = "",
releaseDelaySeconds = "",
description = "",
2025-10-26 22:44:04 +08:00
} = this.formData;
2026-02-15 16:24:29 +08:00
const { protectionSettings, protectionPlan } = this;
let protectionSettingsValidateStatus = true;
let protectionPlanValidateStatus = true;
const settingRequiredFields = ["point", "faultOperator", "faultValue", "releaseOperator", "releaseValue"];
const planRequiredFields = ["point", "value"];
2025-12-19 21:49:19 +08:00
for (let i = 0; i < protectionSettings.length; i++) {
2026-02-15 16:24:29 +08:00
const row = protectionSettings[i] || {};
for (let inner = 0; inner < settingRequiredFields.length; inner++) {
const key = settingRequiredFields[inner];
const value = row[key];
if (![0, "0"].includes(value) && !value) {
this.$message.error(getToastMsg(key, "protectionSettings", i + 1));
protectionSettingsValidateStatus = false;
break;
2025-10-31 21:34:52 +08:00
}
}
2026-02-15 16:24:29 +08:00
if (!protectionSettingsValidateStatus) break;
if (!row.deviceId) {
this.$message.error(`请选择保护前提第${i + 1}行的点位(需从点位列表中选择)`);
protectionSettingsValidateStatus = false;
break;
}
if (protectionSettings[i + 1] && !row.relationNext) {
this.$message.error(getToastMsg("relationNext", "protectionSettings", i + 1));
protectionSettingsValidateStatus = false;
break;
}
2025-10-31 21:34:52 +08:00
}
2025-12-19 21:49:19 +08:00
for (let i = 0; i < protectionPlan.length; i++) {
2026-02-15 16:24:29 +08:00
const row = protectionPlan[i] || {};
for (let inner = 0; inner < planRequiredFields.length; inner++) {
const key = planRequiredFields[inner];
const value = row[key];
if (![0, "0"].includes(value) && !value) {
this.$message.error(getToastMsg(key, "protectionPlan", i + 1));
protectionPlanValidateStatus = false;
break;
2025-10-31 21:34:52 +08:00
}
}
2026-02-15 16:24:29 +08:00
if (!protectionPlanValidateStatus) break;
if (!row.deviceId) {
this.$message.error(`请选择保护方案第${i + 1}行的点位(需从点位列表中选择)`);
protectionPlanValidateStatus = false;
break;
}
2025-10-31 21:34:52 +08:00
}
2026-02-15 16:24:29 +08:00
if (!protectionSettingsValidateStatus || !protectionPlanValidateStatus) return;
const settings = protectionSettings.map((item) => Object.assign({}, item));
const plan = protectionPlan.map((item) => Object.assign({}, item));
2025-10-31 21:34:52 +08:00
this.loading += 1;
2026-02-15 16:24:29 +08:00
2025-12-19 21:49:19 +08:00
const params = {
2025-10-31 21:34:52 +08:00
siteId,
faultName,
isAlert,
faultLevel,
faultDelaySeconds,
releaseDelaySeconds,
description,
2025-12-19 21:49:19 +08:00
protectionSettings: JSON.stringify(settings),
protectionPlan: JSON.stringify(plan),
2026-02-15 16:24:29 +08:00
};
2025-10-31 21:34:52 +08:00
if (this.mode === "add") {
addProtectPlan(params)
2026-02-15 16:24:29 +08:00
.then((response) => {
if (response.code === 200) {
this.$emit("update");
this.closeDialog();
}
})
.finally(() => {
this.loading -= 1;
});
2025-10-26 22:44:04 +08:00
} else {
2026-02-15 16:24:29 +08:00
params.id = id;
2025-10-31 21:34:52 +08:00
updateProtectPlan(params)
2026-02-15 16:24:29 +08:00
.then((response) => {
if (response.code === 200) {
this.$emit("update");
this.closeDialog();
}
})
.finally(() => {
this.loading -= 1;
});
2025-10-26 22:44:04 +08:00
}
});
},
closeDialog() {
this.$emit("clear");
2025-12-19 21:49:19 +08:00
for (let key in this.formData) {
2026-02-15 16:24:29 +08:00
this.formData[key] = key === "isAlert" ? 0 : key === "faultLevel" ? 1 : "";
2025-10-28 18:15:10 +08:00
}
2025-10-26 22:44:04 +08:00
this.$refs.addTempForm.resetFields();
2026-02-15 16:24:29 +08:00
this.$set(this, "protectionSettings", []);
this.$set(this, "protectionPlan", []);
2025-10-26 22:44:04 +08:00
this.dialogTableVisible = false;
},
},
};
</script>
2026-02-15 16:24:29 +08:00
2025-10-26 22:44:04 +08:00
<style scoped lang="scss">
2026-02-15 16:24:29 +08:00
.plan-form {
background: #f5f8ff;
border: 1px solid #e8edf7;
border-radius: 14px;
padding: 16px;
}
.base-panel {
background: #fff;
border: 1px solid #e7ecf6;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.panel-title {
font-size: 15px;
font-weight: 600;
color: #1f2d3d;
margin-bottom: 14px;
}
.base-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px 18px;
.span-1 {
grid-column: span 1;
}
.span-2 {
grid-column: span 2;
2025-10-26 22:44:04 +08:00
}
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.plan-section {
background: #fff;
border: 1px solid #e7ecf6;
border-radius: 12px;
padding: 16px;
margin-bottom: 14px;
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.section-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #1f2d3d;
line-height: 1;
margin-bottom: 6px;
}
.section-desc {
color: #7b8999;
font-size: 12px;
}
.section-actions {
display: flex;
align-items: center;
gap: 10px;
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.row-card {
display: flex;
gap: 12px;
align-items: stretch;
border: 1px solid #e9edf5;
border-radius: 12px;
padding: 12px;
margin-bottom: 12px;
transition: all 0.2s ease;
&:hover {
border-color: #d5e2fb;
box-shadow: 0 4px 14px rgba(30, 70, 140, 0.08);
}
}
.row-index {
width: 30px;
height: 30px;
border-radius: 15px;
background: #eaf1ff;
color: #2d5ee8;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.row-grid {
flex: 1;
display: grid;
gap: 10px;
}
.setting-grid {
grid-template-columns: 2fr repeat(5, minmax(120px, 1fr));
}
.plan-grid {
grid-template-columns: 2fr 80px 1fr;
}
.field-block {
.field-label {
color: #66788c;
font-size: 12px;
margin-bottom: 6px;
}
.el-select,
.el-autocomplete,
.el-input {
width: 100%;
}
}
.eq-block {
.eq-text {
height: 36px;
border: 1px solid #dfe5f0;
border-radius: 4px;
2025-10-26 22:44:04 +08:00
display: flex;
2026-02-15 16:24:29 +08:00
align-items: center;
justify-content: center;
color: #4a5b6f;
font-weight: 600;
background: #f8faff;
}
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.row-action {
width: 72px;
display: flex;
align-items: flex-start;
justify-content: center;
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
@media (max-width: 1366px) {
.setting-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.plan-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 992px) {
.base-grid {
grid-template-columns: 1fr;
.span-1,
.span-2 {
grid-column: span 1;
2025-10-26 22:44:04 +08:00
}
}
2025-12-19 21:49:19 +08:00
2026-02-15 16:24:29 +08:00
.section-head {
flex-direction: column;
align-items: stretch;
}
.section-actions {
flex-wrap: wrap;
}
.row-card {
flex-direction: column;
}
.setting-grid,
.plan-grid {
grid-template-columns: 1fr;
}
.row-action {
width: 100%;
justify-content: flex-end;
2025-10-26 22:44:04 +08:00
}
}
</style>