chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
<script setup lang="ts">
|
||||
import type { MessageGroup, MessageItem } from '../data'
|
||||
|
||||
defineProps<{
|
||||
group: MessageGroup
|
||||
list: MessageItem[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
open: [item: MessageItem]
|
||||
}>()
|
||||
|
||||
function itemTone(item: MessageItem) {
|
||||
if (item.tagType === 'danger')
|
||||
return 'danger'
|
||||
if (item.tagType === 'warning')
|
||||
return 'warning'
|
||||
if (item.tagType === 'success')
|
||||
return 'success'
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
function iconClass(item: MessageItem) {
|
||||
switch (Number(item.messageType)) {
|
||||
case 1:
|
||||
return 'i-carbon-notification'
|
||||
case 2:
|
||||
return 'i-carbon-warning-alt'
|
||||
case 3:
|
||||
return 'i-carbon-receipt'
|
||||
case 4:
|
||||
return 'i-carbon-wallet'
|
||||
default:
|
||||
return 'i-carbon-notification'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="msg-list">
|
||||
<view class="mlist">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.messageId"
|
||||
class="mitem"
|
||||
@click="emit('open', item)"
|
||||
>
|
||||
<view class="micon" :class="`micon--${itemTone(item)}`">
|
||||
<view class="micon-glyph" :class="iconClass(item)" />
|
||||
</view>
|
||||
<view class="mmain">
|
||||
<view class="mt-row">
|
||||
<text class="mt">{{ item.title }}</text>
|
||||
<view v-if="item.unread" class="udot" />
|
||||
</view>
|
||||
<text class="ms">{{ item.summary }}</text>
|
||||
</view>
|
||||
<view class="mside">
|
||||
<text class="mtime">{{ item.time || '暂无时间' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.msg-list {
|
||||
overflow: hidden;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 6rpx 18rpx rgba(11, 43, 172, 0.06);
|
||||
}
|
||||
|
||||
.mlist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mitem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
min-height: 122rpx;
|
||||
padding: 22rpx 22rpx;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
transition: background 0.16s ease;
|
||||
}
|
||||
|
||||
.mitem:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.mitem:active {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.micon {
|
||||
display: flex;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
border-radius: 18rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.micon--primary {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.micon--success {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.micon--warning {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.micon--danger {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.micon-glyph {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.mmain {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mt-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.mt {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
line-height: 38rpx;
|
||||
color: #111827;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.udot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background: #dc2626;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 0 4rpx rgba(220, 38, 38, 0.18);
|
||||
}
|
||||
|
||||
.ms {
|
||||
display: -webkit-box;
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
line-height: 34rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.mside {
|
||||
display: flex;
|
||||
width: 96rpx;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.mtime {
|
||||
max-width: 96rpx;
|
||||
color: #94a3b8;
|
||||
font-size: 22rpx;
|
||||
line-height: 30rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
.mitem {
|
||||
gap: 14rpx;
|
||||
padding: 20rpx 18rpx;
|
||||
}
|
||||
|
||||
.micon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.mside {
|
||||
width: 96rpx;
|
||||
}
|
||||
|
||||
.mtime {
|
||||
max-width: 96rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import type { MessageGroup, MessageGroupKey } from '../data'
|
||||
|
||||
defineProps<{
|
||||
activeKey: MessageGroupKey
|
||||
groups: MessageGroup[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [key: MessageGroupKey]
|
||||
}>()
|
||||
|
||||
function handleChange(key: MessageGroupKey) {
|
||||
emit('change', key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<scroll-view scroll-x class="mtabs" :show-scrollbar="false">
|
||||
<view
|
||||
v-for="g in groups"
|
||||
:key="g.key"
|
||||
class="mtab"
|
||||
:class="{ 'is-active': activeKey === g.key }"
|
||||
@click="handleChange(g.key)"
|
||||
>
|
||||
<text class="mtxt">{{ g.label }}</text>
|
||||
<view v-if="g.unreadCount" class="mbadge">
|
||||
{{ g.unreadCount > 99 ? '99+' : g.unreadCount }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mtabs {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mtab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
height: 50rpx;
|
||||
margin-right: 12rpx;
|
||||
padding: 0 22rpx;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 999rpx;
|
||||
background: #fff;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.mtab:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.mtab.is-active {
|
||||
border-color: #bfdbfe;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.mtxt {
|
||||
font-size: 23rpx;
|
||||
font-weight: 700;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.is-active .mtxt {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.mbadge {
|
||||
min-width: 26rpx;
|
||||
height: 26rpx;
|
||||
padding: 0 7rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #f43f5e;
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.is-active .mbadge {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import type { MessageFilterKey } from '../data'
|
||||
import { messageFilterOptions } from '../data'
|
||||
|
||||
const props = defineProps<{
|
||||
placeholder: string
|
||||
modelValue: string
|
||||
filterKey: MessageFilterKey
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'update:filterKey', value: MessageFilterKey): void
|
||||
}>()
|
||||
|
||||
const keyword = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
})
|
||||
|
||||
const activeFilterKey = computed({
|
||||
get: () => props.filterKey,
|
||||
set: value => emit('update:filterKey', value),
|
||||
})
|
||||
|
||||
const dotColorMap: Record<MessageFilterKey, string> = {
|
||||
all: '#2563eb',
|
||||
recent30: '#2563eb',
|
||||
asc: '#64748b',
|
||||
}
|
||||
|
||||
function dotColor(key: MessageFilterKey) {
|
||||
return dotColorMap[key]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="toolbar">
|
||||
<view class="search-row">
|
||||
<wd-search
|
||||
v-model="keyword"
|
||||
hide-cancel
|
||||
placeholder-left
|
||||
variant="filled"
|
||||
:placeholder="props.placeholder"
|
||||
custom-class="min-w-0 flex-1 !p-0"
|
||||
/>
|
||||
<wd-button
|
||||
class="refresh-btn"
|
||||
icon="i-carbon-renew"
|
||||
type="primary"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@click="emit('refresh')"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="tag-row">
|
||||
<view
|
||||
v-for="option in messageFilterOptions"
|
||||
:key="option.key"
|
||||
class="chip"
|
||||
:class="{ 'is-active': activeFilterKey === option.key }"
|
||||
@click="activeFilterKey = option.key"
|
||||
>
|
||||
<view class="dot" :style="{ background: dotColor(option.key) }" />
|
||||
<text class="chip-txt">{{ option.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7rpx;
|
||||
height: 44rpx;
|
||||
padding: 0 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.chip:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.chip.is-active {
|
||||
background: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.chip-txt {
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chip.is-active .chip-txt {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chip.is-active .dot {
|
||||
box-shadow: 0 0 0 4rpx rgba(37, 99, 235, 0.14);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user