Skip to content

趋势标记-图标的组合使用实现上升下降趋势

components/trend/src/index.vue

html
<template>
  <div class="trend">
    <div class="text">{{ text }}</div>
    <div class="icon">
      <el-icon-arrowup
        v-if="type === 'up'"
        :style="{ color: upIconColor }"
      ></el-icon-arrowup>
      <el-icon-arrowdown
        v-else
        :style="{ color: downIconColor }"
      ></el-icon-arrowdown>
    </div>
  </div>
</template>

<script lang="ts" setup>
defineProps({
  type: {
    type: String,
    default: "up",
  },
  text: {
    type: String,
    default: "文字",
  },
  upIconColor: {
    type: String,
    default: "#f5222d",
  },
  downIconColor: {
    type: String,
    default: "#52c41a",
  },
});
</script>

<style lang="scss" scoped>
.trend {
  display: flex;
  align-items: center;
}

.text {
  font-size: 12px;
  margin-right: 4px;
}

.icon {
  svg {
    width: 0.8em;
    height: 0.8em;
  }
}
</style>

views/trend/index.vue

html
<template>
  <div class="flex">
    <m-trend text="营业额"></m-trend>
    <m-trend text="销售额" type="down"></m-trend>
  </div>
  <div class="flex">
    <m-trend text="营业额" upIconColor="blue"></m-trend>
    <m-trend text="销售额" type="down" downIconColor="#123456"></m-trend>
  </div>
</template>

<script lang="ts" setup></script>

<style lang="scss" scoped>
.flex {
  display: flex;
  div {
    margin-right: 10px;
  }
}
</style>

趋势标记-动态绑定class的妙用实现颜色反转

子组件接收的text可以是插槽,也可以是父组件通过props传递过去的,可以使用useSlots进行区分

颜色反转只有是默认颜色的时候才生效

html
<template>
  <div class="trend">
    <div class="text">
      <slot v-if="slots.default"></slot>
      <div v-else>{{ text }}</div>
    </div>
    <div class="icon">
      <el-icon-arrowup
        v-if="type === 'up'"
        :style="{ color: !reverseColor ? upIconColor : '#52c41a' }"
      ></el-icon-arrowup>
      <el-icon-arrowdown
        v-else
        :style="{ color: !reverseColor ? downIconColor : '#f5222d' }"
      ></el-icon-arrowdown>
    </div>
  </div>
</template>

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

defineProps({
  type: {
    type: String,
    default: "up",
  },
  text: {
    type: String,
    default: "文字",
  },
  upIconColor: {
    type: String,
    default: "#f5222d",
  },
  downIconColor: {
    type: String,
    default: "#52c41a",
  },
  reverseColor: {
    type: Boolean,
    default: false,
  },
});

const slots = useSlots();
</script>

<style lang="scss" scoped>
.trend {
  display: flex;
  align-items: center;
}

.text {
  font-size: 12px;
  margin-right: 4px;
}

.icon {
  svg {
    width: 0.8em;
    height: 0.8em;
  }
}
</style>
html
<template>
  <!-- <div class="flex">
    <m-trend text="营业额"></m-trend>
    <m-trend text="销售额" type="down"></m-trend>
  </div>
  <div class="flex">
    <m-trend text="营业额" upIconColor="blue"></m-trend>
    <m-trend text="销售额" type="down" downIconColor="#123456"></m-trend>
  </div> -->
  <!-- <m-trend>营业额</m-trend> -->
  <m-trend text="营业额" reverseColor></m-trend>
  <m-trend text="销售额" type="down" downIconColor="purple"></m-trend>
</template>

<script lang="ts" setup></script>

<style lang="scss" scoped>
.flex {
  display: flex;
  div {
    margin-right: 10px;
  }
}
</style>

趋势标记-计算属性的妙用实现文字颜色

html
<template>
  <div class="trend">
    <div class="text" :style="{ color: textColor }">
      <slot v-if="slots.default"></slot>
      <div v-else>{{ text }}</div>
    </div>
    <div class="icon">
      <component
        v-if="type === 'up'"
        :is="`el-icon-${toLine(upIcon)}`"
        :style="{ color: !reverseColor ? upIconColor : '#52c41a' }"
      ></component>
      <component
        v-else
        :is="`el-icon-${toLine(downIcon)}`"
        :style="{ color: !reverseColor ? downIconColor : '#f5222d' }"
      ></component>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { useSlots, computed } from "vue";
import { toLine } from "../../../utils";

const props = defineProps({
  type: {
    type: String,
    default: "up",
  },
  text: {
    type: String,
    default: "文字",
  },
  upIconColor: {
    type: String,
    default: "#f5222d",
  },
  downIconColor: {
    type: String,
    default: "#52c41a",
  },
  reverseColor: {
    type: Boolean,
    default: false,
  },
  upTextColor: {
    type: String,
    default: "rgb(0,0,0)",
  },
  downTextColor: {
    type: String,
    default: "rgb(0,0,0)",
  },
  upIcon: {
    type: String,
    default: "ArrowUp",
  },
  downIcon: {
    type: String,
    default: "ArrowDown",
  },
});

const slots = useSlots();

const textColor = computed(() => {
  return props.type === "up" ? props.upTextColor : props.downTextColor;
});
</script>

<style lang="scss" scoped>
.trend {
  display: flex;
  align-items: center;
}

.text {
  font-size: 12px;
  margin-right: 4px;
}

.icon {
  svg {
    width: 0.8em;
    height: 0.8em;
  }
}
</style>
html
<template>
  <!-- <div class="flex">
    <m-trend text="营业额"></m-trend>
    <m-trend text="销售额" type="down"></m-trend>
  </div>
  <div class="flex">
    <m-trend text="营业额" upIconColor="blue"></m-trend>
    <m-trend text="销售额" type="down" downIconColor="#123456"></m-trend>
  </div> -->
  <!-- <m-trend>营业额</m-trend> -->
  <!-- <m-trend text="营业额" reverseColor></m-trend>
  <m-trend text="销售额" type="down" downIconColor="purple"></m-trend> -->
  <!-- <m-trend text="营业额" upTextColor="blue"></m-trend>
  <m-trend text="销售额" type="down" downTextColor="yellow"></m-trend> -->
  <m-trend text="营业额" upIcon="CaretTop"></m-trend>
  <m-trend text="销售额" type="down" downIcon="CaretBottom"></m-trend>
</template>

<script lang="ts" setup></script>

<style lang="scss" scoped>
.flex {
  display: flex;
  div {
    margin-right: 10px;
  }
}
</style>

通知菜单-icon和badge组件的组合使用

html
<template>
  <el-badge :value="value" :max="max" :is-dot="isDot">
    <component :is="`el-icon-${toLine(icon)}`"></component>
  </el-badge>
</template>

<script lang="ts" setup>
import { toLine } from "../../../utils";

defineProps({
  icon: {
    type: String,
    default: "Bell",
  },
  value: {
    type: [String, Number],
    defalut: "",
  },
  max: {
    type: Number,
  },
  isDot: {
    type: Boolean,
    default: false,
  },
});
</script>

<style lang="scss" scoped></style>
html
<template>
  <m-notification :value="50"></m-notification><br />
  <m-notification :value="50" :max="30"></m-notification><br />
  <m-notification :value="50" isDot></m-notification><br />
  <m-notification :value="50" icon="ChatLineRound"></m-notification>
</template>

<script lang="ts" setup></script>

<style lang="scss" scoped></style>

通知菜单-封装一个列表组件(上)

components/notification/src/index.vue

html
<template>
  <el-popover placement="bottom" :width="300" trigger="hover">
    <template #default>
      <slot></slot>
    </template>
    <template #reference>
      <el-badge :value="value" :max="max" :is-dot="isDot">
        <component :is="`el-icon-${toLine(icon)}`"></component>
      </el-badge>
    </template>
  </el-popover>
</template>

views/notification/index.vue

html
<template>
  <!-- <m-notification :value="50"></m-notification><br />
  <m-notification :value="50" :max="30"></m-notification><br />
  <m-notification :value="50" isDot></m-notification><br />
  <m-notification :value="50" icon="ChatLineRound"></m-notification> -->
  <m-notification :value="1">
    <template #default>
      <m-list></m-list>
    </template>
  </m-notification>
</template>

封装m-list

components/list/src/index.vue

html
<template>
  <div>list</div>
</template>

<script lang="ts" setup>
import { PropType } from "vue";
import { ListOptions, ActionOptions } from "./types";
defineProps({
  list: {
    type: Array as PropType<ListOptions[]>,
    required: true,
  },
  actions: {
    type: Array as PropType<ActionOptions[]>,
    default: () => [],
  },
});
</script>

<style lang="scss" scoped></style>

components/list/src/types.ts

typescript
export interface ListItem {
  avatar?: string;
  title?: string;
  desc?: string;
  time?: string;
  tag?: string;
  tagType?: "" | "success" | "info" | "warning" | "danger";
}

// 列表
export interface ListOptions {
  title: string;
  content: ListItem[];
}

// 操作选项
export interface ActionOptions {
  text: string;
  icon?: string;
}

通知菜单-封装一个列表组件(下)

先准备数据

views/notification/data.ts

typescript
export const list = [
  {
    title: '通知',
    content: [
      {
        title: '蒂姆·库克回复了你的邮件',
        time: '2019-05-08 14:33:18',
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png'
      },
      {
        title: '乔纳森·伊夫邀请你参加会议',
        time: '2019-05-08 14:33:18',
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png'
      },
      {
        title: '斯蒂夫·沃兹尼亚克已批准了你的休假申请',
        time: '2019-05-08 14:33:18',
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png'
      }

    ],
  },
  {
    title: '关注',
    content: [
      {
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
        title: '曲丽丽 评论了你',
        desc: '描述信息描述信息描述信息',
        time: '3小时前'
      },
      {
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
        title: '曲丽丽 评论了你',
        desc: '描述信息描述信息描述信息',
        time: '3小时前'
      },
      {
        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
        title: '曲丽丽 评论了你',
        desc: '描述信息描述信息描述信息',
        time: '3小时前'
      }
    ]
  },
  {
    title: '代办',
    content: [
      {
        title: '任务名称',
        desc: '任务需要在 2017-01-12 20:00 前启动',
        tag: '未开始',
        tagType: ''
      },
      {
        title: '第三方紧急代码变更',
        desc: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
        tag: '马上到期',
        tagType: 'danger'
      },
      {
        title: '信息安全考试',
        desc: '指派竹尔于 2017-01-09 前完成更新并发布',
        tag: '已耗时8天',
        tagType: 'warning'
      }
    ]
  },
]
export const actions = [
  {
    text: '清空代办',
    icon: 'delete'
  },
  {
    text: '查看更多',
    icon: 'edit'
  },
]

components/list/src/index.vue

html
<template>
  <div class="list-tabs__item">
    <el-tabs>
      <el-tab-pane
        v-for="(item, index) in list"
        :key="index"
        :label="item.title"
      >
        <el-scrollbar max-height="300px">
          <div
            class="container"
            @click="clickItem(item1, index1)"
            v-for="(item1, index1) in item.content"
            :key="index1"
          >
            <div class="avatar" v-if="item1.avatar">
              <el-avatar size="small" :src="item1.avatar"></el-avatar>
            </div>
            <div class="content">
              <div v-if="item1.title" class="title">
                <div>{{ item1.title }}</div>
                <el-tag v-if="item1.tag" size="mini" :type="item1.tagType">{{
                  item1.tag
                }}</el-tag>
              </div>
              <div class="time" v-if="item1.desc">{{ item1.desc }}</div>
              <div class="time" v-if="item1.time">{{ item1.time }}</div>
            </div>
          </div>
          <div class="actions">
            <div
              class="a-item"
              :class="{ border: i !== actions.length }"
              v-for="(action, i) in actions"
              :key="i"
              @click="clickAction(action, i)"
            >
              <div class="a-icon" v-if="action.icon">
                <component :is="`el-icon-${toLine(action.icon)}`"></component>
              </div>
              <div class="a-text">{{ action.text }}</div>
            </div>
          </div>
        </el-scrollbar>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

<script lang="ts" setup>
import { PropType } from "vue";
import { ListOptions, ActionOptions, ListItem } from "./types";
import { toLine } from "../../../utils";
defineProps({
  // 列表的内容
  list: {
    type: Array as PropType<ListOptions[]>,
    required: true,
  },
  // 操作的内容
  actions: {
    type: Array as PropType<ActionOptions[]>,
    default: () => [],
  },
});
const emits = defineEmits(["clickItem", "clickAction"]);

const clickItem = (item: ListItem, index: number) => {
  emits("clickItem", { item, index });
};
const clickAction = (item: ActionOptions, index: number) => {
  emits("clickAction", { item, index });
};
</script>

<style lang="scss" scoped>
.container {
  display: flex;
  align-items: center;
  padding: 12px 20px;
  cursor: pointer;
  &:hover {
    background: #e6f6ff;
  }
  .avatar {
    flex: 1;
  }
  .content {
    flex: 3;
    .title {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    .time {
      font-size: 12px;
      color: #999;
      margin-top: 4px;
    }
  }
}
.actions {
  height: 50px;
  display: flex;
  align-items: center;
  border-top: 1px solid #eee;
  .a-item {
    height: 50px;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    .a-icon {
      margin-right: 4px;
      position: relative;
      top: 2px;
    }
  }
}
.border {
  border-right: 1px solid #eee;
}
</style>

style/ui.scss

scss
.list-tabs__item {
  .el-tabs__nav {
    width: 100%;
    display: flex;
  }

  .el-tabs__item {
    flex: 1;
    text-align: center;
  }
}

views/notification/index.vue

html
<template>
  <!-- <m-notification :value="50"></m-notification><br />
  <m-notification :value="50" :max="30"></m-notification><br />
  <m-notification :value="50" isDot></m-notification><br />
  <m-notification :value="50" icon="ChatLineRound"></m-notification> -->
  <m-notification :value="1">
    <template #default>
      <m-list
        @clickItem="clickItem"
        @clickAction="clickAction"
        :list="list"
        :actions="actions"
      ></m-list>
    </template>
  </m-notification>
</template>

<script lang="ts" setup>
import { list, actions } from "./data";

const clickItem = (val: any) => {
  console.log(val);
};
const clickAction = (val: any) => {
  console.log(val);
};
</script>

<style lang="scss" scoped></style>