8
0

2 コミット d605a0640f ... bdb4e24a64

作者 SHA1 メッセージ 日付
  xd111 bdb4e24a64 Merge branch 'main' of http://39.108.216.210:3000/web/admin into main 2 週間 前
  xd111 33531f3435 修改 2 週間 前
1 ファイル変更268 行追加266 行削除
  1. 268 266
      src/views/project/list/edit-element.vue

+ 268 - 266
src/views/project/list/edit-element.vue

@@ -16,15 +16,9 @@
                 >
                     <template #search>
                         <div class="text-13px color-#f0720a">
-                            <span
-                                >提示:鼠标右键功能:更换匹配元素字段、新增元素字段、删除匹配元素字段、公式配置</span
-                            >
-                            <span class="ml-14px text-red-6"
-                                >红色:代表匹配不成功、</span
-                            >
-                            <span class="text-blue-6"
-                                >蓝色代表推荐匹配元素字段、</span
-                            >
+                            <span>提示:鼠标右键功能:更换匹配元素字段、新增元素字段、删除匹配元素字段、公式配置</span>
+                            <span class="ml-14px text-red-6">红色:代表匹配不成功、</span>
+                            <span class="text-blue-6">蓝色代表推荐匹配元素字段、</span>
                             <span class="text-green-6">绿色代表匹配成功</span>
                         </div>
                     </template>
@@ -38,12 +32,12 @@
             </template>
             <hc-card scrollbar>
                 <template #header>
-                    <el-button type="warning" @click="formAdjustmentsClick"
-                        >表单调整</el-button
-                    >
-                    <el-button type="primary" @click="formulaConfigClick"
-                        >公式配置</el-button
-                    >
+                    <el-button type="warning" @click="formAdjustmentsClick">
+                        表单调整
+                    </el-button>
+                    <el-button type="primary" @click="formulaConfigClick">
+                        公式配置
+                    </el-button>
                 </template>
                 <template #extra>
                     <el-button @click="drawerClose">返回上一级</el-button>
@@ -84,15 +78,17 @@
                         type="primary"
                         :loading="submitLoading"
                         @click="savingClick"
-                        >临时保存</el-button
                     >
+                        临时保存
+                    </el-button>
                     <el-button
                         type="danger"
                         style="margin-left: 50px"
                         :loading="submitLoading"
                         @click="delElement"
-                        >删除元素</el-button
                     >
+                        删除元素
+                    </el-button>
                 </div>
                 <div class="hc-edit-element-collapse">
                     <el-collapse v-model="activeNames">
@@ -108,8 +104,9 @@
                                             size="small"
                                             :loading="submitLoading"
                                             @click.stop="dialogSubmit"
-                                            >全部提交保存</el-button
                                         >
+                                            全部提交保存
+                                        </el-button>
                                     </div>
                                 </div>
                             </template>
@@ -124,8 +121,9 @@
                                         v-loading="submitLoading"
                                         type="danger"
                                         @click="rowDelClick(index)"
-                                        >删除</el-link
                                     >
+                                        删除
+                                    </el-link>
                                 </template>
                             </hc-table>
                         </el-collapse-item>
@@ -141,8 +139,9 @@
                                             size="small"
                                             :loading="submitLoading"
                                             @click.stop="addElementClick"
-                                            >新增元素</el-button
                                         >
+                                            新增元素
+                                        </el-button>
                                     </div>
                                 </div>
                             </template>
@@ -156,8 +155,9 @@
                                         size="small"
                                         plain
                                         @click="matchElementClick(item)"
-                                        >{{ item.eName }}</el-button
                                     >
+                                        {{ item.eName }}
+                                    </el-button>
                                 </template>
                                 <hc-empty v-if="matchElement.length <= 0" />
                             </div>
@@ -181,8 +181,9 @@
                         type="danger"
                         size="small"
                         @click="addElementData"
-                        >新增元素</el-button
                     >
+                        新增元素
+                    </el-button>
                 </template>
                 <hc-table
                     :column="addElementColumn"
@@ -206,8 +207,9 @@
                         <el-link
                             type="danger"
                             @click="addElementDelClick(index)"
-                            >删除</el-link
                         >
+                            删除
+                        </el-link>
                     </template>
                 </hc-table>
                 <template #footer>
@@ -217,8 +219,9 @@
                         type="primary"
                         :loading="addElementLoading"
                         @click="addElementSubmit"
-                        >确定</el-button
                     >
+                        确定
+                    </el-button>
                 </template>
             </hc-dialog>
         </hc-page-split>
@@ -226,7 +229,7 @@
 </template>
 
 <script setup>
-import { ref, watch } from "vue";
+import { ref, watch } from 'vue'
 import {
     arrIndex,
     deepClone,
@@ -234,11 +237,10 @@ import {
     getArrValue,
     getObjValue,
     isNullES,
-} from "js-fast-way";
-import { getDictionaryData } from "~uti/tools";
-import excelApi from "~api/exctab/exceltab";
-import { useAppStore } from "~src/store";
-const store = useAppStore();
+} from 'js-fast-way'
+import { getDictionaryData } from '~uti/tools'
+import excelApi from '~api/exctab/exceltab'
+import { useAppStore } from '~src/store'
 const props = defineProps({
     info: {
         type: Object,
@@ -250,141 +252,141 @@ const props = defineProps({
     },
     type: {
         type: [String, Number],
-        default: "", //默认没有,独立表单库类型为2
+        default: '', //默认没有,独立表单库类型为2
     },
-});
-
+})
 //事件
-const emit = defineEmits(["close", "toPage"]);
-
+const emit = defineEmits(['close', 'toPage'])
+const store = useAppStore()
 //双向绑定
-const isShow = defineModel("modelValue", {
+const isShow = defineModel('modelValue', {
     default: false,
-});
+})
 
 //监听数据
-const dataInfo = ref(props.info);
-const editData = ref(props.data);
-const type = ref(props.type);
+const dataInfo = ref(props.info)
+const editData = ref(props.data)
+const type = ref(props.type)
 watch(
     () => [props.info, props.data, props.type],
     ([info, data, tpe]) => {
-        dataInfo.value = getObjValue(info);
-        editData.value = getObjValue(data);
-        type.value = tpe;
+        dataInfo.value = getObjValue(info)
+        editData.value = getObjValue(data)
+        type.value = tpe
     },
-    { immediate: true, deep: true }
-);
+    { immediate: true, deep: true },
+)
 
 //监听显示
 watch(isShow, (val) => {
-    if (val) getDataApi();
-    store.setIsShowName(true);
-});
+    if (val) getDataApi()
+    store.setIsShowName(true)
+})
 
 //页面分割
-const splitOptions = { sizes: [70, 30], snapOffset: 0, minSize: [300, 300] };
+const splitOptions = { sizes: [70, 30], snapOffset: 0, minSize: [300, 300] }
 
 //处理相关数据
-const excelRef = ref(null);
-const excelHtml = ref("");
-const tableFormLoading = ref(true);
+const excelRef = ref(null)
+const excelHtml = ref('')
+const tableFormLoading = ref(true)
 const getDataApi = async () => {
     const { pkeyId, excelId, primaryKeyId, excelIds } = getObjValue(
-        dataInfo.value
-    );
+        dataInfo.value,
+    )
     if (type.value !== 2) {
         if (isNullES(pkeyId) || isNullES(excelId)) {
-            tableFormLoading.value = false;
-            window?.$message.warning("表单值异常,请联系管理员");
-            return;
+            tableFormLoading.value = false
+            window?.$message.warning('表单值异常,请联系管理员')
+            return
         }
     } else {
         if (isNullES(primaryKeyId) || isNullES(excelIds)) {
-            tableFormLoading.value = false;
-            window?.$message.warning("表单值异常,请联系管理员");
-            return;
+            tableFormLoading.value = false
+            window?.$message.warning('表单值异常,请联系管理员')
+            return
         }
     }
 
-    tableFormLoading.value = true;
+    tableFormLoading.value = true
     const { data } = await excelApi.getExcelHtml({
         pkeyId: pkeyId ? pkeyId : primaryKeyId, //独立表单库取primaryKeyId
-    });
-    excelHtml.value = data || "";
-    await getElementInfoByTabId();
-    await getUnMatchField();
-    getDataType().then();
-    tableFormLoading.value = false;
-};
+    })
+    excelHtml.value = data || ''
+    await getElementInfoByTabId()
+    await getUnMatchField()
+    getDataType().then()
+    tableFormLoading.value = false
+}
 
 //获取全部元素
-const allElement = ref([]);
+const allElement = ref([])
 const getElementInfoByTabId = async () => {
-    const { initTableId } = getObjValue(dataInfo.value);
+    const { initTableId } = getObjValue(dataInfo.value)
     const { data } = await excelApi.getElementInfoByTabId({
         tabId: initTableId,
-    });
-    allElement.value = getArrValue(data);
-};
+    })
+    allElement.value = getArrValue(data)
+}
 const allElementChange = () => {
-    const { htmlType } = formModel.value;
+    const { htmlType } = formModel.value
     let info = getObjValue(
-        allElement.value.find((item) => item.id === htmlType)
-    );
-    formModel.value.htmlName = info.eName;
-};
+        allElement.value.find((item) => item.id === htmlType),
+    )
+    formModel.value.htmlName = info.eName
+}
 
 //提取未匹配的元素字段
-const matchElement = ref([]);
+const matchElement = ref([])
 const getUnMatchField = async () => {
-    const { pkeyId, initTableId, primaryKeyId } = getObjValue(dataInfo.value);
+    const { pkeyId, initTableId, primaryKeyId } = getObjValue(dataInfo.value)
     const { data } = await excelApi.getUnMatchField({
         pkeyId: pkeyId ? pkeyId : primaryKeyId,
         tabId: initTableId,
-    });
-    matchElement.value = getArrValue(data);
-};
+    })
+    matchElement.value = getArrValue(data)
+}
 
 //未匹配的元素
 const matchElementClick = ({ id, eName }) => {
-    if (submitLoading.value) return;
-    formModel.value.htmlType = id;
-    formModel.value.htmlName = eName;
-};
+    if (submitLoading.value) return
+    formModel.value.htmlType = id
+    formModel.value.htmlName = eName
+}
 
 //元素数据类型
-const dataType = ref([]);
+const dataType = ref([])
 const getDataType = async () => {
-    dataType.value = await getDictionaryData("data_type");
-};
+    dataType.value = await getDictionaryData('data_type')
+}
 
 //基础表单
-const formRef = ref(null);
-const formModel = ref({});
+const formRef = ref(null)
+const formModel = ref({})
 const formRules = {
-    colName: { required: true, trigger: "blur", message: "请先获取元素坐标" },
+    colName: { required: true, trigger: 'blur', message: '请先获取元素坐标' },
     htmlType: {
         required: true,
-        trigger: "blur",
-        message: "请先寻找将元素替换为",
+        trigger: 'blur',
+        message: '请先寻找将元素替换为',
     },
-};
+}
 
 //元素被点击
 const excelClick = async (item) => {
-    const { pkeyId, primaryKeyId } = getObjValue(dataInfo.value);
+    const { pkeyId, primaryKeyId } = getObjValue(dataInfo.value)
     if (isNullES(pkeyId) && isNullES(primaryKeyId)) {
-        window?.$message.warning("表单值异常,请联系管理员");
-        return;
+        window?.$message.warning('表单值异常,请联系管理员')
+        return
     }
-    const data = await getDomAttribute(item);
-    const arr = getArrValue(formDataArr.value);
-    const index = arrIndex(arr, "key", data.key);
-    const colName = data.name || data.text || data.key;
-    const htmlType = index !== -1 ? arr[index].htmlType : "";
-    const htmlName = index !== -1 ? arr[index].htmlName : "";
-    const type = index !== -1 ? arr[index].type : "";
+    const data = await getDomAttribute(item)
+    const arr = getArrValue(formDataArr.value)
+    const index = arrIndex(arr, 'key', data.key)
+    // const colName = data.name || data.text || data.key
+    const colName = data.text 
+    const htmlType = index !== -1 ? arr[index].htmlType : ''
+    const htmlName = index !== -1 ? arr[index].htmlName : ''
+    const type = index !== -1 ? arr[index].type : ''
     formModel.value = {
         tabId: pkeyId ? pkeyId : primaryKeyId,
         colName: colName,
@@ -394,278 +396,278 @@ const excelClick = async (item) => {
         htmlType: htmlType,
         htmlName: htmlName,
         type: type,
-    };
-};
+    }
+}
 
 //获取元素相关信息
 const keys = [
-    "type",
-    "key",
-    "tr",
-    "td",
-    "index",
-    "x1",
-    "y1",
-    "x2",
-    "y2",
-    "name",
-    "text",
-    "rows",
-    "format",
-    "tip",
-    "weighing",
-    "label",
-    "value",
-    "src",
-    "val",
-    "contractid",
-    "pkeyid",
-    "objs",
-    "range",
-    "def",
-    "max",
-];
+    'type',
+    'key',
+    'tr',
+    'td',
+    'index',
+    'x1',
+    'y1',
+    'x2',
+    'y2',
+    'name',
+    'text',
+    'rows',
+    'format',
+    'tip',
+    'weighing',
+    'label',
+    'value',
+    'src',
+    'val',
+    'contractid',
+    'pkeyid',
+    'objs',
+    'range',
+    'def',
+    'max',
+]
 const getDomAttribute = async (item) => {
-    const dom = item?.target;
-    let obj = { zdom: item };
+    const dom = item?.target
+    let obj = { zdom: item }
     for (let i = 0; i < keys.length; i++) {
-        obj[keys[i]] = await getAttribute(dom, keys[i]);
+        obj[keys[i]] = await getAttribute(dom, keys[i])
     }
-    return obj;
-};
+    return obj
+}
 
 //获取属性
 const getAttribute = async (dom, key) => {
     try {
-        return dom?.getAttribute(`data-${key}`);
+        return dom?.getAttribute(`data-${key}`)
     } catch (e) {
-        return null;
+        return null
     }
-};
+}
 
 //临时保存表格
-const formArrRef = ref(null);
+const formArrRef = ref(null)
 const tableColumn = [
-    { key: "colName", name: "元素坐标", minWidth: 120 },
-    { key: "key", name: "元素位置", minWidth: 120 },
-    { key: "htmlName", name: "匹配元素", minWidth: 120 },
-    { key: "tdIndex", name: "td", align: "center", width: 60 },
-    { key: "trIndex", name: "tr", align: "center", width: 60 },
-    { key: "action", name: "操作", align: "center", width: 70, fixed: "right" },
-];
+    { key: 'colName', name: '当前元素', minWidth: 120 },
+    { key: 'key', name: '元素位置', minWidth: 120 },
+    { key: 'htmlName', name: '匹配元素', minWidth: 120 },
+    { key: 'tdIndex', name: 'td(坐标)', align: 'center', width: 60 },
+    { key: 'trIndex', name: 'tr(坐标)', align: 'center', width: 60 },
+    { key: 'action', name: '操作', align: 'center', width: 70, fixed: 'right' },
+]
 
 //临时保存
-const formDataArr = ref([]);
+const formDataArr = ref([])
 const savingClick = async () => {
-    const res = await formValidate(formRef.value);
-    if (!res) return;
-    const form = deepClone(formModel.value);
-    const arr = formDataArr.value;
-    const index = arrIndex(arr, "key", form.key);
-    const obj = { ...form, type: "" };
+    const res = await formValidate(formRef.value)
+    if (!res) return
+    const form = deepClone(formModel.value)
+    const arr = formDataArr.value
+    const index = arrIndex(arr, 'key', form.key)
+    const obj = { ...form, type: '' }
     if (index !== -1) {
-        arr[index] = obj;
+        arr[index] = obj
     } else {
-        arr.push(obj);
+        arr.push(obj)
     }
-    formModel.value = {};
-    window?.$message.success("临时保存成功,记得点击下方的全部提交保存");
-};
+    formModel.value = {}
+    window?.$message.success('临时保存成功,记得点击下方的全部提交保存')
+}
 
 //删除元素
 const delElement = () => {
-    const form = deepClone(formModel.value);
+    const form = deepClone(formModel.value)
     if (isNullES(form.tdIndex) || isNullES(form.trIndex)) {
-        window?.$message.warning("请先在左侧表单,点击要删除的元素");
-        return;
+        window?.$message.warning('请先在左侧表单,点击要删除的元素')
+        return
     }
-    const arr = formDataArr.value;
-    const index = arrIndex(arr, "key", form.key);
+    const arr = formDataArr.value
+    const index = arrIndex(arr, 'key', form.key)
     const obj = {
         ...form,
         htmlType: null,
-        htmlName: "删除元素",
-        type: "del",
-    };
+        htmlName: '删除元素',
+        type: 'del',
+    }
     if (index !== -1) {
-        arr[index] = obj;
+        arr[index] = obj
     } else {
-        arr.push(obj);
+        arr.push(obj)
     }
-    formModel.value = {};
-    window?.$message.success("当前操作临时保存,记得点击下方的全部提交保存");
-};
+    formModel.value = {}
+    window?.$message.success('当前操作临时保存,记得点击下方的全部提交保存')
+}
 
 //删除临时保存
 const rowDelClick = (index) => {
-    formDataArr.value.splice(index, 1);
-};
+    formDataArr.value.splice(index, 1)
+}
 
 //折叠面板
-const activeNames = ref(["key2"]);
+const activeNames = ref(['key2'])
 
 //保存数据
-const submitLoading = ref(false);
+const submitLoading = ref(false)
 const dialogSubmit = async () => {
-    const arr = deepClone(formDataArr.value);
+    const arr = deepClone(formDataArr.value)
     if (arr.length <= 0) {
-        window?.$message.warning("请先添加要操作的元素");
-        return;
+        window?.$message.warning('请先添加要操作的元素')
+        return
     }
     //数据分类
-    submitLoading.value = true;
+    submitLoading.value = true
     let matchArr = [],
-        delArr = [];
+        delArr = []
     for (let i = 0; i < arr.length; i++) {
-        if (arr[i].type === "del") {
+        if (arr[i].type === 'del') {
             delArr.push({
                 ...arr[i],
-                colName: "/",
+                colName: '/',
                 htmlName: null,
                 type: null,
-            });
+            })
         } else {
             matchArr.push({
                 ...arr[i],
-                colName: "",
+                colName: '',
                 htmlName: null,
                 type: null,
-            });
+            })
         }
     }
-    await matchElementApi(matchArr);
-    await delElementApi(delArr);
-    window?.$message.success("操作完成");
-    clearFormData();
-    await getDataApi();
-    submitLoading.value = false;
-};
+    await matchElementApi(matchArr)
+    await delElementApi(delArr)
+    window?.$message.success('操作完成')
+    clearFormData()
+    await getDataApi()
+    submitLoading.value = false
+}
 
 //匹配元素
 const matchElementApi = async (data) => {
-    if (data.length <= 0) return;
-    await excelApi.saveTabColInfo(data);
-};
+    if (data.length <= 0) return
+    await excelApi.saveTabColInfo(data)
+}
 
 //删除元素
 const delElementApi = async (arr, batchSize = 3) => {
-    if (arr.length <= 0) return;
-    const totalBatches = Math.ceil(arr.length / batchSize);
+    if (arr.length <= 0) return
+    const totalBatches = Math.ceil(arr.length / batchSize)
     let totalSuccess = 0,
-        totalFail = 0;
+        totalFail = 0
     for (let i = 0; i < arr.length; i += batchSize) {
-        const batch = arr.slice(i, i + batchSize);
-        const currentBatch = Math.floor(i / batchSize) + 1;
-        console.log(`正在处理第 ${currentBatch}/${totalBatches} 批`);
+        const batch = arr.slice(i, i + batchSize)
+        const currentBatch = Math.floor(i / batchSize) + 1
+        console.log(`正在处理第 ${currentBatch}/${totalBatches} 批`)
 
         const results = await Promise.all(
             batch.map(async (element) => {
                 try {
-                    await excelApi.delExcelElement(element);
-                    return { element, success: true };
+                    await excelApi.delExcelElement(element)
+                    return { element, success: true }
                 } catch (error) {
-                    console.error(`删除元素 ${element} 时发生错误:`, error);
-                    return { element, success: false, error };
+                    console.error(`删除元素 ${element} 时发生错误:`, error)
+                    return { element, success: false, error }
                 }
-            })
-        );
-        const batchSuccess = results.filter((r) => r.success).length;
-        const batchFail = results.filter((r) => !r.success).length;
-        totalSuccess += batchSuccess;
-        totalFail += batchFail;
+            }),
+        )
+        const batchSuccess = results.filter((r) => r.success).length
+        const batchFail = results.filter((r) => !r.success).length
+        totalSuccess += batchSuccess
+        totalFail += batchFail
 
         console.log(
-            `第 ${currentBatch} 批处理结果: 成功 ${batchSuccess}, 失败 ${batchFail}`
-        );
+            `第 ${currentBatch} 批处理结果: 成功 ${batchSuccess}, 失败 ${batchFail}`,
+        )
     }
-    console.log(`总计处理结果: 成功 ${totalSuccess}, 失败 ${totalFail}`);
-};
+    console.log(`总计处理结果: 成功 ${totalSuccess}, 失败 ${totalFail}`)
+}
 
 //新增元素
-const isAddElementShow = ref(false);
+const isAddElementShow = ref(false)
 const addElementClick = () => {
-    addElementTable.value = [{ eName: "", eType: null }];
-    addElementLoading.value = false;
-    isAddElementShow.value = true;
-};
+    addElementTable.value = [{ eName: '', eType: null }]
+    addElementLoading.value = false
+    isAddElementShow.value = true
+}
 
 //表格数据
 const addElementColumn = [
-    { key: "eName", name: "清表元素名称" },
-    { key: "eType", name: "元素数据类型" },
-    { key: "action", name: "操作", align: "center", width: 70, fixed: "right" },
-];
-const addElementTable = ref([]);
+    { key: 'eName', name: '清表元素名称' },
+    { key: 'eType', name: '元素数据类型' },
+    { key: 'action', name: '操作', align: 'center', width: 70, fixed: 'right' },
+]
+const addElementTable = ref([])
 
 //新增数据
 const addElementData = () => {
-    addElementTable.value.push({ eName: "", eType: null });
-};
+    addElementTable.value.push({ eName: '', eType: null })
+}
 
 //删除
 const addElementDelClick = (index) => {
-    addElementTable.value.splice(index, 1);
-};
+    addElementTable.value.splice(index, 1)
+}
 
 //提交新增
-const addElementLoading = ref(false);
+const addElementLoading = ref(false)
 const addElementSubmit = async () => {
-    const arr = addElementTable.value;
+    const arr = addElementTable.value
     if (arr.length <= 0) {
-        window?.$message.warning("请先添加元素字段");
-        return;
+        window?.$message.warning('请先添加元素字段')
+        return
     }
-    const isValid = arr.every((val) => val.eName && val.eType);
+    const isValid = arr.every((val) => val.eName && val.eType)
     if (!isValid) {
-        window?.$message.warning("请先完善数据");
-        return;
+        window?.$message.warning('请先完善数据')
+        return
     }
-    const { pid } = getObjValue(editData.value);
-    const { pkeyId, initTableName, primaryKeyId } = getObjValue(dataInfo.value);
+    const { pid } = getObjValue(editData.value)
+    const { pkeyId, initTableName, primaryKeyId } = getObjValue(dataInfo.value)
     const { isRes } = await excelApi.submitBatch({
         projectId: pid,
         initTableName,
         id: pkeyId ? pkeyId : primaryKeyId,
         listData: arr,
-    });
-    if (!isRes) return;
-    window?.$message.success("操作完成");
-    addElementClose();
-    await getElementInfoByTabId();
-    await getUnMatchField();
-};
+    })
+    if (!isRes) return
+    window?.$message.success('操作完成')
+    addElementClose()
+    await getElementInfoByTabId()
+    await getUnMatchField()
+}
 
 //关闭新增元素
 const addElementClose = () => {
-    isAddElementShow.value = false;
-    addElementLoading.value = false;
-    addElementTable.value = [];
-};
+    isAddElementShow.value = false
+    addElementLoading.value = false
+    addElementTable.value = []
+}
 
 //表单调整
 const formAdjustmentsClick = () => {
-    drawerClose();
-    emit("toPage", "adjustment");
-};
+    drawerClose()
+    emit('toPage', 'adjustment')
+}
 
 //公式配置
 const formulaConfigClick = () => {
-    drawerClose();
-    emit("toPage", "formula");
-};
+    drawerClose()
+    emit('toPage', 'formula')
+}
 
 //清空数据
 const clearFormData = () => {
-    formModel.value = {};
-    formDataArr.value = [];
-};
+    formModel.value = {}
+    formDataArr.value = []
+}
 
 //关闭抽屉
 const drawerClose = () => {
-    isShow.value = false;
-    clearFormData();
-    emit("close");
-};
+    isShow.value = false
+    clearFormData()
+    emit('close')
+}
 </script>
 
 <style lang="scss">