Skip to content

角色管理

清除表单上次校验

ts
// 创建按钮回调
const handleCreate = () => {
    dialogVisible.value = true
    formRef.value?.resetFields()
    roleInfo.value.name = ''
    roleInfo.value.remark = ''
    roleInfo.value.id = ''
    // 重置选中状态
    setCurrent()
}

使用inport引入图片

ts
import deviceIcon from '@/assets/deviceicon.png';

const labelOptions = ref({
  visible: true,
  markerList: props.dataList,
  icon: {
    image: deviceIcon, // 引用已解析的图片路径
    // anchor: 'bottom-center',
    // size: [25, 34],
    // clipOrigin: [459, 92],
    // clipSize: [50, 68]
  }
});

遍历获取需要属性

ts
// 处理数据-获取根节点数据
const filterMenuData = (original: any[]) => {
    return original
        .filter((item: any) => !item.pid) // 筛选 pid 为空的根节点
        .map((item: any) => ({
            id: item.id,
            name: item.name,
            pid: item.pid,
        })); // 返回只包含 id、name、pid 的对象数组
};

// 示例数据
const originalData = [
    { id: 1, name: "根节点1", pid: null },
    { id: 2, name: "子节点1", pid: 1 },
    { id: 3, name: "根节点2", pid: null },
    { id: 4, name: "子节点2", pid: 3 },
];

// 调用函数
const rootNodes = filterMenuData(originalData);
console.log(rootNodes);

复制按钮动态显示

vue
<template>
  <div class="copy-container">
    <!-- 提示框 -->
    <el-tooltip
      content="点击复制"
      placement="top"
      v-if="!copied"
    >
      <!-- 未复制图标 -->
      <el-icon
        :class="['copy-icon', copied ? 'copied' : '']"
        @click="handleCopy"
      >
        <copy-document />
      </el-icon>
    </el-tooltip>

    <!-- 已复制的提示 -->
    <el-tooltip
      content="已复制!"
      placement="top"
      v-if="copied"
    >
      <el-icon
        :class="['copy-icon', copied ? 'copied' : '']"
      >
        <check />
      </el-icon>
    </el-tooltip>
  </div>
</template>

<script lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { CopyDocument, Check } from "@element-plus/icons-vue";

export default {
  components: {
    CopyDocument,
    Check,
  },
  setup() {
    // 是否已复制的状态
    const copied = ref(false);

    // 处理复制逻辑
    const handleCopy = () => {
      // 模拟复制功能,可以替换为实际逻辑
      const textToCopy = "要复制的内容";
      navigator.clipboard.writeText(textToCopy).then(
        () => {
          copied.value = true;
          ElMessage.success("复制成功!");

          // 2秒后恢复为未复制状态
          setTimeout(() => {
            copied.value = false;
          }, 2000);
        },
        () => {
          ElMessage.error("复制失败,请重试!");
        }
      );
    };

    return {
      copied,
      handleCopy,
    };
  },
};
</script>

<style scoped>
.copy-container {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.copy-icon {
  font-size: 24px;
  cursor: pointer;
  transition: transform 0.2s ease;
}

.copy-icon.copied {
  color: green;
  transform: scale(1.2);
}
</style>
ts
功能解析
图标动态切换

使用 copied 状态管理图标的显示。
根据状态,切换显示为未复制或已复制的图标。
复制功能

使用 navigator.clipboard.writeText 将文本写入剪贴板。
成功后,通过 copied 状态触发图标变化。
延迟恢复状态

使用 setTimeout 在 2 秒后将 copied 状态重置为 false
样式动画

使用 CSS 的 transform 和 transition 增加用户交互的视觉效果。
提示信息

使用 ElMessage 显示操作反馈。
运行效果
点击图标时,文本被复制,图标变为已复制的状态,并且显示提示信息。
2 秒后,图标恢复为未复制状态,可以再次点击复制。

三元运算符

ts
简化写法
如果状态较少且逻辑简单,可以直接在模板中通过三元表达式实现:

<span>
    {{ item.off === 0 ? item.property1 : item.off === 1 ? item.property2 : item.property3 }}
</span>

null的判断

ts
在你的代码中,item.off >= 0 表达式会导致 null 被隐式转换为 0,因为在 JavaScript 中,比较运算符 >= 会将 null 转换为数字 0,因此 null >= 0 的结果是 true

在 JavaScript 中,>= 会触发隐式类型转换:

如果 item.off 是 null,它会被转换为数字 0
然后 0 >= 0 评估为 true,所以颜色被设置为绿色。

<span :style="{ color: item.off === null || item.off === undefined ? 'red' : item.off >= 0 ? 'green' : 'red', marginLeft: '1px' }">
    {{ item.off }}
</span>

加载中骨架屏 栅格

<el-card> 之所以会纵向排列,是因为它们默认是以 块级元素(block-level element) 的方式排列的,也就是每个卡片占据一整行。为了让它们横向排列,需要使用 Flex 布局Grid 布局 来控制其排列方式。

Element Plus 提供的栅格系统可以方便地实现横向排列。

vue
<el-skeleton class="skeleton-loading" v-show="deviceListLoading" animated style="width: 100%;">
    <template #template>
        <el-row :gutter="10">
            <el-col :span="6" v-for="(_, index) in 12" :key="index">
                <el-card style="border-radius: 5px;">Card {{ index + 1 }}</el-card>
            </el-col>
        </el-row>
    </template>
</el-skeleton>

pinia

计算属性

你的代码中,Piniagetters 用来计算基于 state 的值。在你的需求中,当 state 中的 sn 不存在时,可以从 localStoragepara 获取数据并更新 state 中的 sn。但你的实现中有些问题,例如直接修改 state.sn 是不被推荐的,因为 getters 是计算属性,不应该直接修改状态。

ts
import { defineStore } from 'pinia';

export const useDeviceStore = defineStore('device', {
  state: () => ({
    sn: '', // 初始化 sn
  }),
  getters: {
    getDeviceInfo(state) {
      // 如果 state.sn 不存在,尝试从 localStorage 中获取 para 并解析
      if (!state.sn) {
        const para = JSON.parse(localStorage.getItem('para') || '{}');
        if (para.sn) {
          state.sn = para.sn; // 更新 state.sn
        }
      }
      return state.sn; // 返回 sn
    },
  },
});

getters 的职责

  • getters 的目的是返回计算后的值,避免直接修改 state
  • 但在你的需求中,需要动态从 localStorage 获取数据并更新 state.sn,因此可以在 getters 中进行条件处理。

localStorage 的解析

  • 使用 JSON.parse 安全地解析 localStorage 中的数据,同时提供默认值 {},以避免解析出错。

Pinia 响应式支持

  • state.sn 更新时,Pinia 会自动触发响应式更新。

更清晰的逻辑分离

ts
export const useDeviceStore = defineStore('device', {
  state: () => ({
    sn: '',
  }),
  actions: {
    loadDeviceInfo() {
      if (!this.sn) {
        const para = JSON.parse(localStorage.getItem('para') || '{}');
        if (para.sn) {
          this.sn = para.sn;
        }
      }
    },
  },
  getters: {
    getDeviceInfo: (state) => state.sn, // 仅返回 sn
  },
});
ts
使用时:

const store = useDeviceStore();
store.loadDeviceInfo(); // 先调用 action 加载数据
console.log(store.getDeviceInfo); // 获取 sn

动态导航栏

ts
在 Vue 3 + TypeScript 项目中,可以通过一个动态组件来实现一个功能性的页面导航栏,类似于浏览器的标签页。以下是具体实现思路和代码示例:

实现功能描述
显示当前页面:展示当前页面的名称。
切换页面:点击某个页面标签,可以切换到对应的页面。
关闭页面:在标签上添加关闭按钮,可以关闭对应的页面。

复制文本

vue
<template>
  <div>
    <!-- 可复制的文本 -->
    <p @dblclick="copyText" ref="textElement">双击我复制这段文本!</p>
    <!-- 显示复制状态 -->
    <p v-if="copySuccess" class="success-message">复制成功!</p>
  </div>
</template>

<script lang="ts">
import { ref } from "vue";

export default {
  name: "CopyText",
  setup() {
    // 引用目标文本元素
    const textElement = ref<HTMLParagraphElement | null>(null);
    // 复制成功状态
    const copySuccess = ref(false);

    // 双击复制文本的函数
    const copyText = async () => {
      if (textElement.value) {
        const text = textElement.value.innerText;

        // 尝试使用 Clipboard API
        if (navigator.clipboard && window.isSecureContext) {
          try {
            await navigator.clipboard.writeText(text);
            showSuccessMessage();
            return;
          } catch (error) {
            console.error("Clipboard API 复制失败:", error);
          }
        }

        // 回退到 execCommand 方法
        try {
          const textarea = document.createElement("textarea");
          textarea.value = text;

          // 隐藏 textarea 并添加到文档中
          textarea.style.position = "absolute";
          textarea.style.left = "-9999px";
          document.body.appendChild(textarea);

          // 选中并复制内容
          textarea.select();
          const successful = document.execCommand("copy");
          document.body.removeChild(textarea);

          if (successful) {
            showSuccessMessage();
          } else {
            console.error("execCommand 复制失败");
          }
        } catch (error) {
          console.error("execCommand 方法复制失败:", error);
        }
      }
    };

    // 显示复制成功提示
    const showSuccessMessage = () => {
      copySuccess.value = true;
      setTimeout(() => {
        copySuccess.value = false;
      }, 2000);
    };

    return {
      textElement,
      copyText,
      copySuccess,
    };
  },
};
</script>

<style scoped>
.success-message {
  color: green;
  font-weight: bold;
}
</style>

svg-icon

ts
从代码来看,你的目标是全局注册 Element Plus 的图标组件,同时集成一个自定义的 SvgIcon 组件来加载自定义的 SVG 图标。下面是代码的详细说明以及可能的优化点:
ts
const icons = svg as any;
for (const i in icons) {
  app.component(`ele-${icons[i].name}`, icons[i]);
}
这段代码会将 @element-plus/icons-vue 中的所有图标组件以 ele- 为前缀注册为全局组件,例如 ele-Edit, ele-Search。
ts
该组件的功能是根据传入的 name 动态显示不同来源的图标,包括:

Element Plus 的内置图标:

图标名称以 ele- 开头,使用 <component> 动态渲染。
用于渲染 @element-plus/icons-vue 中的图标。
外部链接或本地图片图标:

图标名称以 https, http, /src, /assets 等路径开头时,使用 <img> 标签渲染图标。
自定义 CSS 类图标:

默认通过 <i> 标签渲染其他类型的图标(通常用于字体图标,如 Font Awesome)。
支持的样式配置:

支持通过 size 和 color 属性调整图标大小和颜色。
vue
<template>
  <i v-if="isShowIconSvg" class="el-icon" :style="setIconSvgStyle">
    <component :is="getIconName" />
  </i>
  <div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
    <img :src="getIconName" :style="setIconSvgInsStyle" />
  </div>
  <i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue';

export default defineComponent({
  name: 'svgIcon',
  props: {
    name: {
      type: String,
      required: true,
    },
    size: {
      type: Number,
      default: 14,
    },
    color: {
      type: String,
      default: '#000',
    },
  },
  setup(props) {
    // 用于判断图标类型
    const isShowIconSvg = computed(() => props.name?.startsWith('ele-'));
    const isShowIconImg = computed(() => /^(https?:\/\/|\/src|\/assets)/.test(props.name || ''));

    // 获取图标名称
    const getIconName = computed(() => props.name || '');

    // 样式设置
    const setIconSvgStyle = computed(() => `font-size: ${props.size}px; color: ${props.color};`);
    const setIconImgOutStyle = computed(() => `width: ${props.size}px; height: ${props.size}px; display: inline-block;`);
    const setIconSvgInsStyle = computed(() => `width: ${props.size}px; height: ${props.size}px;`);

    return {
      isShowIconSvg,
      isShowIconImg,
      getIconName,
      setIconSvgStyle,
      setIconImgOutStyle,
      setIconSvgInsStyle,
    };
  },
});
</script>

使用方式

ts
使用 Element Plus 图标:

<svg-icon name="ele-Edit" size="24" color="#409EFF" />
使用外部或本地图片:
<svg-icon name="https://example.com/icon.svg" size="32" />
使用自定义类名图标:
<svg-icon name="custom-icon-class" size="16" color="red" />

要在 Element Plus<el-button> 中使用自定义图标,并结合你的 svg-icon 组件,关键在于将 svg-icon 作为按钮的图标插槽来使用。

vue
<template>
  <!-- 使用自定义 svg-icon 作为按钮的图标 -->
  <el-button type="primary" circle>
    <svg-icon name="ele-Edit" size="20" color="#fff" />
  </el-button>

  <el-button type="success" circle>
    <svg-icon name="custom-icon-class" size="20" color="#fff" />
  </el-button>

  <el-button type="warning" circle>
    <svg-icon name="https://example.com/icon.svg" size="20" />
  </el-button>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import SvgIcon from '@/components/SvgIcon/index.vue';

export default defineComponent({
  name: 'CustomButton',
  components: {
    SvgIcon,
  },
});
</script>

<style scoped>
/* 确保图标在按钮中居中显示 */
.el-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
</style>

动态设置图标

vue
<template>
  <el-button type="primary" circle>
    <svg-icon :name="iconName" :size="20" :color="iconColor" />
  </el-button>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';
import SvgIcon from '@/components/SvgIcon/index.vue';

export default defineComponent({
  name: 'DynamicButtonIcon',
  components: {
    SvgIcon,
  },
  setup() {
    const iconName = ref('ele-Edit'); // 动态图标名称
    const iconColor = ref('#fff');   // 动态图标颜色

    return {
      iconName,
      iconColor,
    };
  },
});
</script>

el-drawer隐藏滚动条

方式1

ts
/* 针对 el-Drawer 的滚动条隐藏但保留滚动功能 */
.el-drawer__body {
  overflow-y: scroll; /* 保留滚动功能 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}

.el-drawer__body::-webkit-scrollbar {
  width: 0; /* 隐藏滚动条 */
  height: 0;
}

方式2通过 custom-class 添加自定义样式

如果只想对某个特定的 el-Drawer 生效,可以通过 custom-class 添加自定义样式:

vue
<template>
  <el-drawer
    title="示例"
    :visible.sync="visible"
    custom-class="no-scroll-drawer">
    <p>内容1...</p>
    <p>内容2...</p>
    <p>内容3...</p>
  </el-drawer>
</template>

<style>
/* 自定义样式 */
.no-scroll-drawer .el-drawer__body {
  overflow-y: scroll; /* 保留滚动功能 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
}

.no-scroll-drawer .el-drawer__body::-webkit-scrollbar {
  width: 0; /* 隐藏滚动条 */
  height: 0;
}
</style>

方式3 通过内联样式动态控制

可以在组件中通过 JavaScript 动态设置滚动条样式:

vue
<template>
  <el-drawer
    ref="drawer"
    title="示例"
    :visible.sync="visible">
    <div class="content">
      <p>内容1...</p>
      <p>内容2...</p>
      <p>内容3...</p>
    </div>
  </el-drawer>
</template>

<script>
import { ref, watch } from "vue";

export default {
  setup() {
    const visible = ref(false);
    const drawer = ref(null);

    watch(visible, (newVal) => {
      if (newVal && drawer.value) {
        const drawerBody = drawer.value.$el.querySelector(".el-drawer__body");
        if (drawerBody) {
          drawerBody.style.overflowY = "scroll"; // 保留滚动功能
          drawerBody.style.scrollbarWidth = "none"; // 隐藏 Firefox 滚动条
          drawerBody.style.msOverflowStyle = "none"; // 隐藏 IE/Edge 滚动条
          drawerBody.style.webkitScrollbar = "none"; // 隐藏滚动条(兼容 Webkit)
        }
      }
    });

    return { visible, drawer };
  },
};
</script>

<style>
/* 隐藏滚动条 */
.el-drawer__body::-webkit-scrollbar {
  width: 0;
  height: 0;
}
</style>

linux日志查询

cd /opt/temper/logs/process/info

ts
grep -C 10 '062005000279829' info.log
ts
按行显示匹配关键字并统计行数
grep "linux" example.txt | wc -l

高亮显示关键字并打印最后几行
grep --color=always "关键字" logfile.log | tail -n 5

动态查看日志内容
如果日志文件正在不断更新,并想实时查看包含关键字的最后几行,可以使用以下命令:
tail -f logfile.log | grep --color=always "关键字"

只打印文件末尾的日志并搜索关键字
如果日志文件非常大,可以直接从末尾开始搜索以提高效率:
tail -n 1000 logfile.log | grep --color=always "关键字" | tail -n 5

如果想在日志文件中查找某个错误信息,同时查看前后上下文
grep -C 3 "ERROR" logfile.log

想搜索某段代码中的关键字并查看附近代码片段:
grep -C 5 "function" script.js

索包含关键字(如 IP 地址或端口)的内容,并查看上下几行配置或状态信息
grep -C 2 "192.168.1.1" network.log

末尾几行
grep -C 10 '062005000279829' info.log

绝对值

ts
const validateY = (rule: any, value: any, callback: any) => {
    if (Math.abs(value - RemoteAdjustForm.value.k) < 5) {
        console.log('value',value)
        callback(new Error('角色必须设置'))
    } else {
        callback(); // 验证通过
    }
};

禁用按钮提示

要在按钮处于 disabled 状态时提示用户原因,你可以结合 Element-PlusTooltip 组件来实现。以下是一个完整的示例:

vue
<template>
  <el-tooltip
    v-if="RemoteAdjustForm.overtime === '0'"
    content="当前状态无法提交"
    effect="dark"
    placement="top"
  >
    <el-button
      type="primary"
      @click="onRemoteAdjustSubmit"
      :loading="RemoteAdjustLoading"
      :disabled="RemoteAdjustForm.overtime === '0'"
    >
      {{ RemoteAdjustLoading ? '提交中' : '提交' }}
    </el-button>
  </el-tooltip>
  <el-button
    v-else
    type="primary"
    @click="onRemoteAdjustSubmit"
    :loading="RemoteAdjustLoading"
  >
    {{ RemoteAdjustLoading ? '提交中' : '提交' }}
  </el-button>
</template>

<script>
export default {
  data() {
    return {
      RemoteAdjustLoading: false,
      RemoteAdjustForm: {
        overtime: '0', // 示例数据,表示禁用状态
      },
    };
  },
  methods: {
    onRemoteAdjustSubmit() {
      // 提交逻辑
      console.log('提交表单');
    },
  },
};
</script>

获取当前页面的路由path

vue
<template>
  <div>
    <h1>当前页面路径</h1>
    <p>当前路径: {{ currentPath }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useRoute } from 'vue-router';

export default defineComponent({
  name: 'CurrentPath',
  setup() {
    const route = useRoute();

    // 获取当前路由的 path
    const currentPath = computed(() => route.path);

    return {
      currentPath,
    };
  },
});
</script>

lodash节流操作

vue
<template>
  <div>
    <el-button type="primary" @click="handleClick">节流按钮</el-button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { throttle } from 'lodash';

export default defineComponent({
  name: 'ThrottleButton',
  setup() {
    // 定义节流的点击事件,设置节流间隔为 2 秒
    const handleClick = throttle(() => {
      console.log('按钮点击事件触发!');
    }, 2000);

    return {
      handleClick,
    };
  },
});
</script>