添加掌上工厂应用

This commit is contained in:
砂糖
2025-10-27 13:21:43 +08:00
parent f5c313421a
commit 718f0efc76
408 changed files with 64677 additions and 1 deletions

View File

@@ -0,0 +1,26 @@
<template>
<qiun-data-charts :type="type" :chartData="chartData" :opts="opts"/>
</template>
<script>
export default {
// 定义接收的属性(替代 Vue3 的 defineProps
props: {
// 图表类型如line、column、pie等
type: {
type: String,
required: true // 图表类型为必填项
},
// 图表数据包含categories和series等
chartData: {
type: Object,
required: true // 图表数据为必填项
},
// 图表配置项(如样式、坐标轴设置等)
opts: {
type: Object,
default: () => ({}) // 配置项默认为空对象
}
}
}
</script>

View File

@@ -0,0 +1,145 @@
<template>
<view class="collapse-panel">
<view
class="collapse-header"
@click="toggleCollapse"
>
<view class="title">
<uni-icons type="gear-filled" size="18" color="white" />
<text>{{ title }}</text>
</view>
<view class="header-right">
<view class="extra" @click.stop>
<slot name="extra"></slot>
</view>
<view
class="arrow"
:class="{ 'arrow-open': isOpen }"
>
<uni-icons type="arrowright" size="18" color="white" />
</view>
</view>
</view>
<view
class="collapse-content"
:style="contentStyle"
>
<slot />
</view>
</view>
</template>
<script>
export default {
// 定义组件接收的属性
props: {
title: {
type: String,
required: true
},
duration: {
type: Number,
default: 300
},
isBorder: {
type: Boolean,
default: true
}
},
// 响应式数据
data() {
return {
isOpen: false,
contentHeight: 0
};
},
// 计算属性
computed: {
contentStyle() {
return {
'max-height': this.isOpen ? '1000px' : '0',
'transition': `all ${this.duration}ms ease`,
'opacity': this.isOpen ? 1 : 0
};
}
},
// 方法定义
methods: {
toggleCollapse() {
this.isOpen = !this.isOpen;
}
}
};
</script>
<style lang="scss" scoped>
.collapse-panel {
margin: 16rpx 0;
border-radius: 8rpx;
overflow: hidden;
border: 1rpx solid #eee;
.collapse-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background-color: #2bf;
color: white;
transition: background-color 0.2s;
&:active {
background-color: #f1f3f5;
}
.title {
font-size: 32rpx;
font-weight: 500;
color: white;
display: flex;
align-items: center;
gap: 12rpx;
}
.header-right {
display: flex;
align-items: center;
gap: 16rpx;
}
.extra {
display: flex;
align-items: center;
}
.arrow {
transition: transform 0.3s ease;
color: white;
&-open {
transform: rotate(90deg);
}
}
}
.collapse-content {
overflow: hidden;
padding: 0 24rpx;
background-color: #fff;
// 展开动画
&-enter-active,
&-leave-active {
transition: all 0.3s ease;
}
&-enter-from,
&-leave-to {
opacity: 0;
max-height: 0;
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
</template>
<script>
export default {
props: {
formConf: {
require: true,
type: Object
},
},
watch: {
formConf: {
immediate: true,
deep: true,
handler(newVal) {
this.SchemaToJsonForm(newVal)
}
}
}
methods: {
SchemaToJsonForm(schema) {
console.log(schema)
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,124 @@
<template>
<view class="card-container" :style="{ borderRadius: borderRadius }">
<!-- 头部区域 -->
<view class="card-header" :style="headerStyle">
<text class="header-title">{{ title }}</text>
<text class="header-value">{{ value }}</text>
</view>
<!-- 信息容器条件渲染 -->
<view v-if="info && info.length" class="info-container">
<view v-for="(item, rowIndex) in info" :key="rowIndex" class="info-item">
<text class="info-label">{{ item.label }}</text>
<text class="info-value">
{{ item.value }}
<text v-if="item.unit" class="unit-text">{{ item.unit }}</text>
</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'DeviceInfoCard',
props: {
title: {
type: String,
required: true
},
value: {
type: [String, Number],
required: true
},
info: {
type: Array,
default: () => []
},
// 样式相关props
borderRadius: {
type: String,
default: '10rpx'
},
headerStyle: {
type: Object,
default: () => ({
backgroundColor: '#d9edf6',
border: '1px solid #d9edf6'
})
},
itemWidth: {
type: String,
default: '48%' // 控制信息项宽度
}
}
}
</script>
<style scoped>
/* 卡片容器 */
.card-container {
overflow: hidden;
background: #fff;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
}
/* 头部样式 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
}
.header-title {
font-size: 32rpx;
color: #333;
}
.header-value {
font-size: 36rpx;
color: #2a7ae9;
font-weight: 500;
}
/* 信息容器 */
.info-container {
padding: 20rpx;
display: flex;
box-sizing: border-box;
flex-wrap: wrap;
justify-content: space-between;
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 24rpx;
}
.info-item {
display: flex;
align-items: baseline;
}
.info-label {
color: #666;
font-size: 28rpx;
flex-shrink: 0;
margin-right: 16rpx;
}
.info-value {
color: #333;
font-size: 30rpx;
font-weight: 500;
word-break: break-all;
}
.unit-text {
font-size: 0.8em;
color: #999;
margin-left: 6rpx;
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<view class="metric-container" :style="{ gridTemplateColumns: `repeat(${columns}, 1fr)` }">
<view class="metric-item" v-for="(item, index) in items" :key="index">
<view class="metric-value">
{{ item.value }}
</view>
<view class="metric-label">{{ item.label }}</view>
<view v-if="item.unit" class="metric-unit">{{ item.unit }}</view>
</view>
</view>
</template>
<script>
export default {
name: 'MetricCard',
props: {
items: {
type: Array,
required: true
},
columns: {
type: Number,
default: 3
}
}
}
</script>
<style scoped>
.metric-container {
display: grid;
gap: 20rpx;
padding: 20rpx;
box-sizing: border-box;
background-color: #fff;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.metric-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20rpx;
}
.metric-value {
font-size: 48rpx;
font-weight: 600;
margin-bottom: 16rpx;
line-height: 1.2;
color: #333;
}
.metric-unit {
font-size: 0.7em;
color: #999;
margin-left: 8rpx;
}
.metric-label {
font-size: 28rpx;
color: #666;
letter-spacing: 2rpx;
text-align: center;
line-height: 1.4;
}
</style>

View File

@@ -0,0 +1,331 @@
<!-- components/uni-table/uni-table.vue -->
<template>
<view class="uni-table-container">
<!-- 横向表格模式 -->
<scroll-view
v-if="mode === 'horizontal'"
scroll-x
class="horizontal-table"
:style="{ maxHeight: height }"
>
<view class="table-header">
<view
v-for="(col, index) in columns"
:key="index"
class="header-cell"
:style="getHeaderStyle(col)"
>
{{ col.title }}
</view>
</view>
<view
v-for="(row, rowIndex) in data"
:key="rowIndex"
class="table-row"
:class="{ 'row-hover': rowHover }"
@click="handleRowClick(row, rowIndex)"
>
<view
v-for="(col, colIndex) in columns"
:key="colIndex"
class="body-cell"
:style="getCellStyle(col)"
>
<slot
v-if="col.slot"
:name="col.slot"
:row="row"
:column="col"
></slot>
<text v-else>{{ formatCellValue(row[col.key], col) }}</text>
</view>
</view>
<view v-if="data.length === 0" class="empty-tip">
<u-empty mode="data" icon="/static/empty.png"/>
</view>
</scroll-view>
<!-- 纵向表格模式 -->
<scroll-view
v-else
scroll-y
class="vertical-table"
:style="{ maxHeight: height }"
>
<view
v-for="(row, rowIndex) in data"
:key="rowIndex"
class="vertical-row"
:class="{ 'row-hover': rowHover }"
@click="handleRowClick(row, rowIndex)"
>
<view
v-for="(col, colIndex) in columns"
:key="colIndex"
class="vertical-cell"
>
<view class="cell-label">{{ col.title }}</view>
<view class="cell-value">
<slot
v-if="col.slot"
:name="col.slot"
:row="row"
:column="col"
></slot>
<text v-else>{{ formatCellValue(row[col.key], col) }}</text>
</view>
</view>
</view>
<view v-if="data.length === 0" class="empty-tip">
<u-empty mode="data" icon="/static/empty.png"/>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: 'UniTable',
props: {
// 表格模式horizontal横向/vertical纵向
mode: {
type: String,
default: 'horizontal',
validator: value => ['horizontal', 'vertical'].includes(value)
},
// 列配置
columns: {
type: Array,
required: true,
},
// 表格数据
data: {
type: Array,
default: () => []
},
// 固定高度支持px/rpx
height: {
type: String,
default: '1000rpx'
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否显示行hover效果
rowHover: {
type: Boolean,
default: true
},
// 是否显示斑马纹
stripe: {
type: Boolean,
default: false
},
// 是否显示序号列
showIndex: {
type: Boolean,
default: false
},
// 序号列标题
indexLabel: {
type: String,
default: '序号'
}
},
computed: {
// 处理后的列配置
processedColumns() {
let cols = [...this.columns]
if (this.showIndex) {
cols.unshift({
title: this.indexLabel,
key: '_index',
width: '80rpx',
align: 'center'
})
}
return cols
}
},
methods: {
// 获取表头样式
getHeaderStyle(col) {
return {
width: col.width || '200rpx',
textAlign: col.align || 'left',
flex: col.flex ? `0 0 ${col.width}` : 'none',
backgroundColor: this.border ? '#f5f7fa' : 'transparent'
}
},
// 获取单元格样式
getCellStyle(col) {
return {
width: col.width || '200rpx',
textAlign: col.align || 'left',
flex: col.flex ? `0 0 ${col.width}` : 'none'
}
},
// 行点击事件
handleRowClick(row, index) {
this.$emit('row-click', row, index)
},
// 格式化单元格值
formatCellValue(value, col) {
if (value === undefined || value === null) {
return '--'
}
// 如果列定义了format函数使用format函数处理
if (col.format && typeof col.format === 'function') {
return col.format(value)
}
// 如果是日期类型
if (col.type === 'date') {
return this.formatDate(value, col.dateFormat)
}
// 如果是数字类型
if (col.type === 'number') {
return this.formatNumber(value, col.precision)
}
return value
},
// 格式化日期
formatDate(value, format = 'YYYY-MM-DD') {
if (!value) return '--'
// 这里可以使用日期格式化库如dayjs
return value
},
// 格式化数字
formatNumber(value, precision) {
if (typeof value !== 'number') return value
return precision !== undefined ? value.toFixed(precision) : value
}
}
}
</script>
<style lang="scss" scoped>
.uni-table-container {
background-color: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
.horizontal-table {
.table-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 2rpx solid #ebeef5;
position: sticky;
top: 0;
z-index: 1;
.header-cell {
padding: 24rpx;
font-weight: 500;
color: #303133;
white-space: nowrap;
transition: background-color 0.3s;
position: relative;
&::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 50%;
width: 2rpx;
background-color: #ebeef5;
}
}
}
.table-row {
display: flex;
border-bottom: 1rpx solid #ebeef5;
transition: background-color 0.3s;
&:last-child {
border-bottom: none;
}
&.row-hover:active {
background-color: #f5f7fa;
}
.body-cell {
padding: 24rpx;
color: #606266;
word-break: break-all;
display: flex;
align-items: center;
}
}
}
.vertical-table {
.vertical-row {
padding: 24rpx;
border-bottom: 1rpx solid #ebeef5;
transition: background-color 0.3s;
&:last-child {
border-bottom: none;
}
&.row-hover:active {
background-color: #f5f7fa;
}
.vertical-cell {
display: flex;
margin-bottom: 20rpx;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.cell-label {
width: 200rpx;
color: #909399;
flex-shrink: 0;
font-size: 28rpx;
}
.cell-value {
flex: 1;
color: #303133;
word-break: break-all;
font-size: 28rpx;
padding-left: 20rpx;
}
}
}
}
.empty-tip {
padding: 80rpx 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
}
}
</style>