Ver código fonte

文件编辑错误

yanghao 2 dias atrás
pai
commit
2da4479e14

+ 1 - 1
src/layout/components/TagsView/src/helper.ts

@@ -13,7 +13,7 @@ export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') =>
       route.meta.affix = true
     } else if (currentSource === 'spzx' && tagPath === '/video_center/protocol') {
       route.meta.affix = true
-    } else if (currentSource === 'qhse' && tagPath === '/qhse/measure/ledger') {
+    } else if (currentSource === 'qhse' && tagPath === '/qhse/qhse_kanban') {
       route.meta.affix = true
     } else if (currentSource === 'yyhy' && tagPath === '/operation-meeting/fill') {
       route.meta.affix = true

+ 3 - 3
src/permission.ts

@@ -116,7 +116,7 @@ router.beforeEach(async (to, from, next) => {
 
       if (source && source === 'qhse') {
         sessionStorage.setItem('LOGIN_SOURCE', source as string)
-        next({ path: '/qhse/measure/ledger' })
+        next({ path: '/qhse/qhse_kanban' })
       }
 
       if (source && source === 'yyhy') {
@@ -198,7 +198,7 @@ router.beforeEach(async (to, from, next) => {
 
       if (source && source === 'qhse') {
         sessionStorage.setItem('LOGIN_SOURCE', source as string)
-        next({ path: '/qhse/measure/ledger' })
+        next({ path: '/qhse/qhse_kanban' })
       }
 
       if (source && source === 'yyhy') {
@@ -231,7 +231,7 @@ router.beforeEach(async (to, from, next) => {
       } else if (source && source === 'spzx') {
         next(`/login?redirect=/video_center/protocol`)
       } else if (source && source === 'qhse') {
-        next(`/login?redirect=/qhse/measure/ledger`)
+        next(`/login?redirect=/qhse/qhse_kanban`)
       } else if (source && source === 'yyhy') {
         next(`/login?redirect=/operation-meeting/fill`)
       } else next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页

+ 263 - 0
src/views/pms/device/components/dashboard/DeviceClassifyTopCard.vue

@@ -0,0 +1,263 @@
+<script setup lang="ts">
+import * as echarts from 'echarts'
+
+type ClassifyItem = {
+  category?: string
+  name?: string
+  value?: number | string
+}
+
+const props = defineProps<{
+  data: ClassifyItem[]
+  loading?: boolean
+}>()
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+let resizeObserver: ResizeObserver | null = null
+
+const barPalette = [
+  ['#47b5ff', '#147ff3'],
+  ['#5f8dff', '#6a5cff'],
+  ['#34d3b3', '#12a99d'],
+  ['#ffbd59', '#f58b2d'],
+  ['#8c7cff', '#6854ee']
+]
+
+const categories = computed(() => props.data.map((item) => item.category || item.name || '未分类'))
+const values = computed(() => props.data.map((item) => Number(item.value ?? 0)))
+
+const escapeHtml = (value: string) =>
+  value.replace(/[&<>"']/g, (char) => {
+    const entities: Record<string, string> = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;'
+    }
+    return entities[char]
+  })
+
+const formatTooltip = (params: {
+  color?: string
+  marker?: string
+  name?: string
+  value?: number | string
+}) => {
+  const marker =
+    params.marker ||
+    '<span style="display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#1688f5;"></span>'
+  const name = escapeHtml(params.name || '未分类')
+  const value = params.value ?? 0
+
+  return `<div style="line-height:1.8;font-size:14px;">${marker}<span>${name}</span><br/><span style="padding-left:15px;">设备数量&nbsp;&nbsp;<strong>${value}</strong></span></div>`
+}
+
+const seriesData = computed(() =>
+  values.value.map((value, index) => {
+    const colors = barPalette[index % barPalette.length]
+    return {
+      value,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: colors[0] },
+          { offset: 1, color: colors[1] }
+        ])
+      }
+    }
+  })
+)
+
+const chartOption = computed(() => ({
+  grid: {
+    top: 16,
+    right: 18,
+    bottom: 40,
+    left: 38
+  },
+  tooltip: {
+    trigger: 'item',
+    backgroundColor: '#fff',
+    borderColor: '#e8edf5',
+    borderWidth: 1,
+    padding: [10, 13],
+    textStyle: {
+      color: '#172033',
+      fontSize: 14
+    },
+    extraCssText: 'box-shadow: 0 12px 28px rgba(15, 23, 42, 0.14); border-radius: 12px;',
+    formatter: formatTooltip
+  },
+  xAxis: {
+    type: 'category',
+    data: categories.value,
+    axisTick: { show: false },
+    axisLine: { lineStyle: { color: '#e8edf5' } },
+    axisLabel: {
+      color: '#738199',
+      rotate: 30,
+      interval: 0,
+      fontSize: 11,
+      margin: 10
+    }
+  },
+  yAxis: {
+    type: 'value',
+    splitLine: {
+      lineStyle: {
+        color: '#edf1f6',
+        type: 'dashed'
+      }
+    },
+    axisLabel: {
+      color: '#8b98ac',
+      fontSize: 11
+    }
+  },
+  series: [
+    {
+      name: '设备数量',
+      type: 'bar',
+      data: seriesData.value,
+      barWidth: 16,
+      barMinHeight: 6,
+      showBackground: true,
+      backgroundStyle: {
+        color: 'rgba(22, 136, 245, 0.055)'
+      },
+      itemStyle: {},
+      emphasis: {
+        scale: true,
+        itemStyle: {
+          shadowBlur: 12,
+          shadowColor: 'rgba(22, 136, 245, 0.22)'
+        }
+      }
+    }
+  ]
+}))
+
+const renderChart = () => {
+  if (!chartRef.value || props.loading || props.data.length === 0) return
+  if (!chart) {
+    chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+  }
+  chart.setOption(chartOption.value, true)
+}
+
+watch(
+  () => [props.loading, props.data],
+  () => nextTick(renderChart),
+  { deep: true, immediate: true }
+)
+
+onMounted(() => {
+  nextTick(renderChart)
+  if (chartRef.value) {
+    resizeObserver = new ResizeObserver(() => chart?.resize())
+    resizeObserver.observe(chartRef.value)
+  }
+})
+
+onBeforeUnmount(() => {
+  resizeObserver?.disconnect()
+  chart?.dispose()
+  chart = null
+})
+</script>
+
+<template>
+  <section class="device-classify-card">
+    <div class="card-header">
+      <div>
+        <h3>分类 Top</h3>
+      </div>
+      <span class="soft-badge">Top {{ data.length }}</span>
+    </div>
+
+    <div v-show="data.length > 0 && !loading" ref="chartRef" class="chart-el"></div>
+    <div v-if="data.length === 0 || loading" class="empty-state">
+      {{ loading ? '加载中...' : '暂无分类数据' }}
+    </div>
+  </section>
+</template>
+
+<style scoped lang="scss">
+.device-classify-card {
+  position: relative;
+  min-width: 0;
+  min-height: 164px;
+  padding: 12px 14px 6px;
+  overflow: hidden;
+  background: radial-gradient(circle at 92% 18%, rgb(22 136 245 / 9%) 0, transparent 28%),
+    linear-gradient(180deg, #fff 0%, #fbfdff 100%);
+  border: 1px solid #e8edf5;
+  border-radius: 16px;
+  box-shadow: 0 10px 26px rgb(15 23 42 / 8%);
+}
+
+.device-classify-card::before,
+.device-classify-card::after {
+  position: absolute;
+  pointer-events: none;
+  border-radius: 999px;
+  content: '';
+}
+
+.device-classify-card::before {
+  top: -52px;
+  right: 120px;
+  width: 112px;
+  height: 112px;
+  background: rgb(53 205 178 / 8%);
+}
+
+.device-classify-card::after {
+  right: -42px;
+  bottom: -48px;
+  width: 132px;
+  height: 132px;
+  background: rgb(140 124 255 / 7%);
+  border: 1px solid rgb(140 124 255 / 10%);
+}
+
+.card-header {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 700;
+  color: #25324a;
+}
+
+.soft-badge {
+  padding: 5px 11px;
+  font-size: 12px;
+  color: #1688f5;
+  background: #edf7ff;
+  border: 1px solid #d8ecff;
+  border-radius: 999px;
+}
+
+.chart-el,
+.empty-state {
+  position: relative;
+  z-index: 1;
+  height: 168px;
+}
+
+.empty-state {
+  display: flex;
+  font-size: 13px;
+  color: #9aa6b8;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 360 - 0
src/views/pms/device/components/dashboard/DeviceStatusCard.vue

@@ -0,0 +1,360 @@
+<script setup lang="ts">
+import * as echarts from 'echarts'
+
+type StatItem = {
+  name?: string
+  category?: string
+  value?: number | string
+}
+
+const props = defineProps<{
+  data: StatItem[]
+  loading?: boolean
+}>()
+
+const chartRef = ref<HTMLDivElement>()
+let chart: echarts.ECharts | null = null
+let resizeObserver: ResizeObserver | null = null
+
+const palette = [
+  '#1688f5',
+  '#35cdb2',
+  '#ffbd59',
+  '#ff6f91',
+  '#8c7cff',
+  '#4eb3ff',
+  '#52c41a',
+  '#fa8c16',
+  '#13c2c2',
+  '#722ed1',
+  '#eb2f96',
+  '#2f54eb'
+]
+
+const chartData = computed(() =>
+  props.data.map((item) => ({
+    name: item.name || item.category || '未知',
+    value: Number(item.value ?? 0)
+  }))
+)
+
+const total = computed(() => chartData.value.reduce((sum, item) => sum + item.value, 0))
+const legendItems = computed(() => chartData.value)
+
+const escapeHtml = (value: string) =>
+  value.replace(/[&<>"']/g, (char) => {
+    const entities: Record<string, string> = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;'
+    }
+    return entities[char]
+  })
+
+const formatTooltip = (params: {
+  color?: string
+  marker?: string
+  name?: string
+  percent?: number
+  value?: number | string
+}) => {
+  const color = typeof params.color === 'string' ? params.color : palette[0]
+  const marker =
+    params.marker ||
+    `<span style="display:inline-block;width:8px;height:8px;margin-right:6px;border-radius:50%;background:${color};"></span>`
+  const name = escapeHtml(params.name || '未知')
+  const value = params.value ?? 0
+  const percent = typeof params.percent === 'number' ? params.percent.toFixed(2) : '0.00'
+
+  return `<div style="line-height:1.8;font-size:14px;">${marker}<span>${name}</span><br/><span style="padding-left:14px;">${value} 台 (${percent}%)</span></div>`
+}
+
+const chartOption = computed(() => ({
+  color: palette,
+  tooltip: {
+    trigger: 'item',
+    backgroundColor: '#fff',
+    borderColor: '#e8edf5',
+    borderWidth: 1,
+    padding: [8, 10],
+    textStyle: {
+      color: '#172033',
+      fontSize: 14
+    },
+    extraCssText: 'box-shadow: 0 10px 24px rgba(15, 23, 42, 0.12); border-radius: 10px;',
+    formatter: formatTooltip
+  },
+  series: [
+    {
+      type: 'pie',
+      radius: ['56%', '90%'],
+      center: ['50%', '50%'],
+      minAngle: 8,
+      avoidLabelOverlap: true,
+      stillShowZeroSum: false,
+      label: { show: false },
+      labelLine: { show: false },
+      itemStyle: {
+        borderWidth: 0
+      },
+      emphasis: {
+        scale: true,
+        scaleSize: 4,
+        itemStyle: {
+          shadowBlur: 10,
+          shadowColor: 'rgba(15, 23, 42, 0.14)'
+        }
+      },
+      data: chartData.value
+    }
+  ]
+}))
+
+const renderChart = () => {
+  if (!chartRef.value) return
+  if (props.loading || chartData.value.length === 0) {
+    chart?.clear()
+    return
+  }
+  if (!chart) {
+    chart = echarts.init(chartRef.value, undefined, { renderer: 'canvas' })
+  }
+  chart.setOption(chartOption.value, true)
+}
+
+watch(
+  () => [props.loading, props.data],
+  () => nextTick(renderChart),
+  { deep: true, immediate: true }
+)
+
+onMounted(() => {
+  nextTick(renderChart)
+  if (chartRef.value) {
+    resizeObserver = new ResizeObserver(() => chart?.resize())
+    resizeObserver.observe(chartRef.value)
+  }
+})
+
+onBeforeUnmount(() => {
+  resizeObserver?.disconnect()
+  chart?.dispose()
+  chart = null
+})
+</script>
+
+<template>
+  <section class="device-status-card">
+    <div class="card-header">
+      <div>
+        <h3>设备状态</h3>
+      </div>
+      <span class="soft-badge">{{ chartData.length }} 类</span>
+    </div>
+
+    <div class="status-body">
+      <div class="chart-wrap">
+        <div v-show="chartData.length > 0 && !loading" ref="chartRef" class="chart-el"></div>
+        <div v-if="chartData.length === 0 || loading" class="empty-state">
+          {{ loading ? '加载中...' : '暂无状态数据' }}
+        </div>
+        <div v-if="chartData.length > 0 && !loading" class="chart-center">
+          <span>合计</span>
+          <strong>{{ total }}</strong>
+        </div>
+      </div>
+
+      <div class="legend-list">
+        <div v-for="(item, index) in legendItems" :key="item.name" class="legend-item">
+          <span
+            class="legend-dot"
+            :style="{ backgroundColor: palette[index % palette.length] }"></span>
+          <span class="legend-name">{{ item.name }}</span>
+          <strong>{{ item.value }}</strong>
+        </div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<style scoped lang="scss">
+.device-status-card {
+  position: relative;
+  min-width: 0;
+  min-height: 164px;
+  padding: 12px 14px 10px;
+  overflow: hidden;
+  background: #fff;
+  border: 1px solid #e8edf5;
+  border-radius: 16px;
+  box-shadow: 0 10px 26px rgb(15 23 42 / 8%);
+  container-type: inline-size;
+}
+
+.device-status-card::before,
+.device-status-card::after {
+  position: absolute;
+  pointer-events: none;
+  border-radius: 999px;
+  content: '';
+}
+
+.device-status-card::before {
+  top: -50px;
+  left: -34px;
+  width: 118px;
+  height: 118px;
+  background: rgb(53 205 178 / 10%);
+}
+
+.device-status-card::after {
+  right: -38px;
+  bottom: -46px;
+  width: 126px;
+  height: 126px;
+  background: rgb(22 136 245 / 8%);
+  border: 1px solid rgb(22 136 245 / 10%);
+}
+
+.card-header,
+.status-body,
+.legend-item {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  align-items: center;
+}
+
+.card-header {
+  justify-content: space-between;
+}
+
+h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 700;
+  color: #25324a;
+}
+
+.soft-badge {
+  padding: 5px 11px;
+  font-size: 12px;
+  color: #1688f5;
+  background: #edf7ff;
+  border: 1px solid #d8ecff;
+  border-radius: 999px;
+}
+
+.status-body {
+  display: grid;
+  margin-top: 6px;
+  grid-template-columns: minmax(240px, 1fr) minmax(300px, 330px);
+  gap: 10px;
+}
+
+.chart-wrap {
+  position: relative;
+  min-width: 0;
+}
+
+.chart-el,
+.empty-state {
+  height: 168px;
+}
+
+.chart-center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  display: flex;
+  font-size: 12px;
+  color: #8b98ac;
+  pointer-events: none;
+  transform: translate(-50%, -50%);
+  flex-direction: column;
+  align-items: center;
+}
+
+.chart-center strong {
+  margin-top: 3px;
+  font-size: 18px;
+  line-height: 1;
+  color: #25324a;
+}
+
+.legend-list {
+  display: grid;
+  width: min(100%, 330px);
+  max-height: 168px;
+  min-width: 0;
+  padding-right: 4px;
+  overflow: auto;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 7px 8px;
+  justify-self: end;
+}
+
+.legend-item {
+  min-width: 0;
+  padding: 0;
+  font-size: 12px;
+  color: #526178;
+  background: transparent;
+  border: 0;
+}
+
+.legend-dot {
+  width: 9px;
+  height: 9px;
+  margin-right: 6px;
+  border-radius: 50%;
+  flex: none;
+}
+
+.legend-name {
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  flex: 1;
+}
+
+.legend-item strong {
+  min-width: 24px;
+  margin-left: 6px;
+  color: #172033;
+  text-align: right;
+}
+
+.empty-state {
+  display: flex;
+  font-size: 13px;
+  color: #9aa6b8;
+  align-items: center;
+  justify-content: center;
+}
+
+@container (max-width: 620px) {
+  .status-body {
+    grid-template-columns: minmax(220px, 1fr) minmax(150px, 176px);
+  }
+
+  .legend-list {
+    width: min(100%, 176px);
+    grid-template-columns: minmax(0, 1fr);
+  }
+}
+
+@media (width <= 1440px) {
+  .status-body {
+    grid-template-columns: minmax(190px, 1fr) minmax(132px, 156px);
+  }
+
+  .legend-list {
+    width: min(100%, 156px);
+    grid-template-columns: minmax(0, 1fr);
+  }
+}
+</style>

+ 149 - 0
src/views/pms/device/components/dashboard/DeviceTotalCard.vue

@@ -0,0 +1,149 @@
+<script setup lang="ts">
+defineProps<{
+  total: number
+  loading?: boolean
+}>()
+</script>
+
+<template>
+  <section class="device-total-card">
+    <div class="card-top">
+      <div class="metric-icon">
+        <Icon icon="ep:histogram" :size="26" />
+      </div>
+      <div class="metric-meta">
+        <span>设备总览</span>
+        <strong>设备总数</strong>
+      </div>
+    </div>
+
+    <div class="metric-value">
+      <span v-if="!loading">{{ total }}</span>
+      <span v-else class="value-skeleton"></span>
+    </div>
+
+    <div class="metric-footer">
+      <span class="trend-pill">
+        <Icon icon="ep:top" :size="12" />
+        设备台账
+      </span>
+    </div>
+  </section>
+</template>
+
+<style scoped lang="scss">
+.device-total-card {
+  position: relative;
+  display: flex;
+  min-width: 0;
+  min-height: 164px;
+  padding: 12px 14px;
+  overflow: hidden;
+  color: #172033;
+  background: #fff;
+  border: 1px solid #e8edf5;
+  border-radius: 16px;
+  box-shadow: 0 10px 26px rgb(15 23 42 / 8%);
+  flex-direction: column;
+  justify-content: space-between;
+}
+
+.device-total-card::before,
+.device-total-card::after {
+  position: absolute;
+  pointer-events: none;
+  border-radius: 999px;
+  content: '';
+}
+
+.device-total-card::before {
+  right: -36px;
+  bottom: -54px;
+  width: 132px;
+  height: 132px;
+  background: rgb(27 132 255 / 8%);
+  border: 1px solid rgb(27 132 255 / 12%);
+}
+
+.device-total-card::after {
+  top: 12px;
+  right: 14px;
+  width: 56px;
+  height: 56px;
+  background: #f0f7ff;
+  border: 1px solid #dcecff;
+}
+
+.card-top,
+.metric-footer {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.card-top {
+  justify-content: flex-start;
+  gap: 14px;
+}
+
+.metric-icon {
+  display: flex;
+  width: 40px;
+  height: 40px;
+  color: #1288ff;
+  background: #edf7ff;
+  border: 1px solid #d8ecff;
+  border-radius: 16px;
+  align-items: center;
+  justify-content: center;
+}
+
+.metric-meta {
+  display: flex;
+  min-width: 0;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.metric-meta span,
+.metric-footer {
+  font-size: 13px;
+  color: #7b8798;
+}
+
+.metric-meta strong {
+  font-size: 17px;
+  font-weight: 700;
+  color: #172033;
+}
+
+.metric-value {
+  position: relative;
+  z-index: 1;
+  font-size: 40px;
+  font-weight: 800;
+  line-height: 1;
+  letter-spacing: 0;
+}
+
+.trend-pill {
+  display: inline-flex;
+  padding: 4px 9px;
+  font-size: 12px;
+  color: #0d8a59;
+  background: #e8f8ef;
+  border-radius: 999px;
+  align-items: center;
+  gap: 3px;
+}
+
+.value-skeleton {
+  display: block;
+  width: 96px;
+  height: 44px;
+  background: #eef4fb;
+  border-radius: 10px;
+}
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 761 - 833
src/views/pms/device/index.vue


+ 3 - 2
src/views/pms/iotrddailyreport/fillDailyReport.vue

@@ -201,8 +201,9 @@ function realValue(type: any, value: string) {
     <!-- <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2"> </div> -->
     <el-form
       size="default"
-      class="min-h-62px bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-x-8 gap-y-3 flex flex-wrap items-center justify-between">
-      <div class="min-w-0 flex flex-1 flex-wrap items-center gap-x-8 gap-y-3">
+      :model="query"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-6 flex items-center justify-between flex-wrap min-w-0">
+      <div class="flex items-center gap-6 flex-wrap">
         <el-form-item label="项目">
           <el-input
             v-model="query.contractName"

+ 3 - 2
src/views/pms/iotrddailyreport/index.vue

@@ -272,8 +272,9 @@ onMounted(() => {
     <!-- <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2"> </div> -->
     <el-form
       size="default"
-      class="min-h-62px bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-x-8 gap-y-3 flex flex-wrap items-center justify-between">
-      <div class="min-w-0 flex flex-1 flex-wrap items-center gap-x-8 gap-y-3">
+      :model="query"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-6 flex items-center justify-between flex-wrap min-w-0">
+      <div class="flex items-center gap-6 flex-wrap">
         <el-form-item label="项目">
           <el-input
             v-model="query.contractName"

+ 3 - 2
src/views/pms/iotrddailyreport/statistics.vue

@@ -178,8 +178,9 @@ function handleWellNameClick(taskId: number) {
     <!-- <div class="p-4 bg-white dark:bg-[#1d1e1f] shadow rounded-lg row-span-2"> </div> -->
     <el-form
       size="default"
-      class="min-h-62px bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-x-8 gap-y-3 flex flex-wrap items-center justify-between">
-      <div class="min-w-0 flex flex-1 flex-wrap items-center gap-x-8 gap-y-3">
+      :model="query"
+      class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-8 py-3 gap-6 flex items-center justify-between flex-wrap min-w-0">
+      <div class="flex items-center gap-6 flex-wrap">
         <el-form-item label="项目">
           <el-input
             v-model="query.contractName"

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff