Compare commits

32 Commits
test ... dev

Author SHA1 Message Date
houjunxiang
4d1e709be9 fix:键盘输入bug 2025-12-04 10:15:32 +08:00
houjunxiang
182fd11399 feat:分析录入支持录入负数 2025-12-04 10:03:19 +08:00
houjunxiang
ee29bdfcc8 feat:pdf预览接口更改 2025-12-03 11:17:02 +08:00
houjunxiang
b7a94c91fe feat:部门选择 2025-12-01 17:02:40 +08:00
houjunxiang
298503fec8 feat:登出清空数据 2025-12-01 10:54:45 +08:00
houjunxiang
1bcfde72ee feat:样品库管理 2025-11-27 18:04:49 +08:00
houjunxiang
6c86dc0760 feat:菜单可配置 2025-11-27 16:12:25 +08:00
houjunxiang
f3c15b3692 style:部门选择样式 2025-11-27 09:19:08 +08:00
houjunxiang
fcc04865f9 feat:部门选择 2025-11-26 18:11:13 +08:00
houjunxiang
96cc747150 feat:打印消息缓存 2025-11-25 14:36:18 +08:00
houjunxiang
44398c6004 style:样式调整 2025-11-25 11:24:16 +08:00
houjunxiang
f1efe8ee61 feat:打印模板提示 2025-11-24 16:57:54 +08:00
houjunxiang
6255d6ae3f feat:样品归库打印 2025-11-24 16:55:04 +08:00
houjunxiang
c504c9b2a7 feat:样品标签打印 2025-11-24 16:48:57 +08:00
houjunxiang
c3630e74ec feat:图标名称修改 2025-11-24 10:46:21 +08:00
houjunxiang
8a3e48d856 feat:node-modules 2025-11-24 10:26:18 +08:00
houjunxiang
753766893b feat:样品库管理 2025-11-21 17:56:33 +08:00
houjunxiang
7ee3df9ab9 feat:样品库管理 2025-11-20 17:23:48 +08:00
houjunxiang
0494d224be feat:样品库管理 2025-11-19 11:02:11 +08:00
houjunxiang
06210e79fd feat:分析赋值 2025-11-18 10:17:46 +08:00
houjunxiang
00e21aebd3 feat:样品分析 2025-11-15 18:09:04 +08:00
houjunxiang
d3f6ad4bf6 feat:查询条件调整 2025-11-14 17:46:43 +08:00
houjunxiang
45ba9b4899 style:文字调整 2025-11-13 18:06:23 +08:00
houjunxiang
4fb5eecdb2 feat:分析管理 2025-11-13 17:00:50 +08:00
houjunxiang
3a5acea5a3 feat:元素校验范围 2025-11-12 10:19:30 +08:00
houjunxiang
970a8b8eae feat:待审数据 2025-11-11 20:51:08 +08:00
houjunxiang
fb41fa9a03 feat:分析 2025-11-11 09:58:12 +08:00
houjunxiang
5916b8c833 feat:样品分析 2025-10-14 18:16:51 +08:00
houjunxiang
b5aed8573a style:样式调整 2025-10-11 16:25:24 +08:00
houjunxiang
c8b2d8683e feat:天平连接 2025-10-10 18:16:14 +08:00
houjunxiang
2b50debcd3 feat:页面修改 2025-10-10 10:45:25 +08:00
houjunxiang
386f1e7466 1 2025-10-09 18:19:55 +08:00
10398 changed files with 853560 additions and 35405 deletions

42
.env
View File

@@ -1,36 +1,16 @@
# 版本号
SHOPRO_VERSION=v2.4.1
# 正式环境接口域名 已经改为可配置的,不在此配置
NX_BASE_URL = http://192.168.26.247:9999
# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development
SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development
; SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080
SHOPRO_DEV_BASE_URL=http://172.16.46.63:30081
### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080
### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
SHOPRO_UPLOAD_TYPE=server
# 后端接口前缀(一般不建议调整)
SHOPRO_API_PATH=/admin-api
# 后端 websocket 接口前缀
SHOPRO_WEBSOCKET_PATH=/infra/ws
# 开发环境接口域名
# NX_DEV_BASE_URL = http://192.168.26.247:9999
NX_DEV_BASE_URL = http://192.168.26.105:888/api
# 开发环境运行端口
SHOPRO_DEV_PORT=3000
NX_DEV_PORT = 3000
# 接口地址前缀
NX_API_PATH = /api/
NX_VERSION = v1.0
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn
### SHOPRO_STATIC_URL = https://file.sheepjs.com
# 前端 H5 访问域名
SHOPRO_H5_URL=http://127.0.0.1:3000
# 是否开启直播 1 开启直播 | 0 关闭直播
SHOPRO_MPLIVE_ON=0
# 租户ID 默认 1
SHOPRO_TENANT_ID=1
NX_STATIC_URL = https://file.nx.com

30
.gitignore vendored
View File

@@ -1,11 +1,19 @@
unpackage/*
node_modules/*
.idea/*
deploy.sh
.hbuilderx/
.vscode/
**/.DS_Store
yarn.lock
package-lock.json
*.keystore
pnpm-lock.yaml
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
/unpackage/cache
/unpackage/debug
/unpackage/dist
/.hbuilderx
/unpackage/release
/unpackage/resources
/.guiplan_cache
.idea

View File

@@ -1,6 +0,0 @@
/unpackage/*
/node_modules/**
/uni_modules/**
/public/*
**/*.svg
**/*.sh

View File

@@ -1,10 +0,0 @@
{
"printWidth": 100,
"semi": true,
"vueIndentScriptAndStyle": true,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"htmlWhitespaceSensitivity": "strict",
"endOfLine": "auto"
}

74
App.vue
View File

@@ -1,32 +1,58 @@
<script setup>
import { onLaunch, onShow, onError } from '@dcloudio/uni-app';
import { ShoproInit } from './sheep';
import { onLaunch, onShow, onError } from '@dcloudio/uni-app'
onLaunch(() => {
// 隐藏原生导航栏 使用自定义底部导航
uni.hideTabBar({
fail: () => {},
});
// 加载Shopro底层依赖
ShoproInit();
});
onShow(() => {
import { NxInit } from './nx'
import $store from '@/nx/store'
// #ifdef APP-PLUS
// 获取urlSchemes参数
const args = plus.runtime.arguments;
if (args) {
}
// 获取剪贴板
uni.getClipboardData({
success: (res) => {},
});
import * as urovo from './uni_modules/zzjc-urovo'
// #endif
});
onLaunch(async () => {
// 加载nx底层依赖
await NxInit()
// #ifdef APP-PLUS
let sysInfo = uni.getSystemInfoSync()
let brand = sysInfo.brand.toLowerCase()
if (brand === 'huawei') {
plus.screen.lockOrientation('landscape')
}
if (brand === 'urovo') {
urovo.scanRegister(data => {
console.log('优博讯扫码结果:' + data)
$store('biz').scanQRInfo = data
})
}
// #endif
})
onError(err => {
console.log('AppOnError:', err)
})
onShow(() => {})
</script>
<style lang="scss">
@import '@/sheep/scss/index.scss';
@import '@/uview-plus/index.scss';
@import '@/nx/scss/index.scss';
#u-a-p > div {
z-index: 99999 !important;
}
body {
font-size: 16px;
}
@font-face {
font-family: 'zzjc-lcd';
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAQQAA0AAAAADNgAAAO6AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GYACCWhEIColEhk4LIAABNgIkAyoEIAWILgdAG1sKAI7RS+dwknLKwRCOFCJ62I82+05UkhDNk0toJKgnoWEW8UYotAuZLN4ogW/0ul/7EucnQS2Yf8HMQ1ALgRPXku5MpmeuEm86VZ/nmOmibc+Hf8ET2dCiAmfjYWSBbhR9kVjhi+cndnx0eOZncg/xTozVeOFDCPDF/mEXfL++dWY6NAG9YBwUglAQDCjQkgf5og3qXj3Gg2SS/GQLBUY0iDe/gHyqjBb1fzrUZzh7nqiQF4VbQAuYZXNVT+q3M9JBNwGFAosSZTT/GYjW9Zq/zZvmNWIZTjuQUSmAbekt0dCKbeefjfoTgPmMaRoFGADMg92QI0BeAloBBSA4FBaLweIweFwrw/Acy7E8z3NiScPiOYkamVPoS1uVTKZzfL6vj5HyZFZq64bllpZPwXRsu1PzqxR5MI0r1ETvndkWy2DFNJGBHfSVPc2AMxCVjboO5ch5uR0SUkEHRAEol8eOumGDytORSX162TbqIaWFybLSpkU7nVAe8DOGLs4CXuz+/t2Hd01+UT5OX6hnfhvOL70IrDPP+jjq5A8XAs9uxsva9uOdTM/dOPNsXeD7PJFxPN4++tPhc3YzsO4FislD9YrTj9cNxlgX8xQS4pVxzepk0GLTJlbyrD+Vo6dCHY+/5XmT8gU+rSjtco+V1VNq5zWu3YVXu6q1bc7ZaoKZ+1XmTk6O8e0BAYFdi1Whfc7ZXwULU71WfZ0H5F89A50oQGBN6M9/IIYgmOvb84QHkDsgC3x0PP9mwoVk1fEJGBCLBxQIAo4BYDlXBMCEcjmCN4vC2YMWLjbQytkPtJETjnZ8CtGBSj06IbfRBdlANypb0cMrN4i91K/ow5QX+nlVBQbxNUscun2fFqRVBByHBISiHxTJCmjpDoMR5B1YfhHAUWIHj0klBD5pgkjINJwwMWERnx1EiboBmVASFPHlDxdK9RNd97OiyIxZpnljRoxapFq3SGVnZZMhQfZTdjbq+eNNbX1vt7GrPwddzOeD20bEMxcbJpt+sG23rS/eA6NLOBY+FLBl1TjNJM0QbG2Na5PaEHCObsiYJVPwjsDyYMRODqhhUpWaMW2RqpFuxJJJA+bhwDnzpSvK9yBWBKTkReh1unkLxupglVUiqy8Ycf5kTGui1Wpz1SyXhcLysktcaNUBKDr4+3WLZmVKksRgBRMZxkzelcAwwJRIMw1Hsj7DMP7a5D9IMAbMRG2a0BU4bp5Oh5u2c083WVio/b4owkSddVAY08AB5DO5cufBkxdvPnz58RcAAAA=')
format('woff2');
src: url('~@/static/font/zzjc-lcd.ttf') format('truetype');
}
.zzjc-lcd {
font-family: 'zzjc-lcd' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 lidongtony
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,25 @@
## 技术栈
uni-app、ES6、Vue3、Vite、Pinia;
- 登录,注册
- 全局路由守卫(路由配置)
- Request封装请求封装
- api集中管理
- flex常用布局css,
- utils常用工具函数
- 配置pinia(store)

View File

@@ -1,3 +0,0 @@
{
"prompt" : "template"
}

View File

@@ -0,0 +1,189 @@
<template>
<view v-if="dialogStore.show" class="company-dept-dialog">
<view class="company-dept-dialog__mask" />
<view class="company-dept-dialog__panel">
<view class="company-dept-dialog__header">
<text class="company-dept-dialog__title">{{ dialogStore.title }}</text>
</view>
<view class="company-dept-dialog__body">
<!-- 公司选择 -->
<view class="company-dept-dialog__field">
<text class="company-dept-dialog__label">公司</text>
<uni-data-select
v-model="selectedCompanyModel"
:localdata="companyOptionsForSelect"
:clear="false"
@change="onCompanyChangeUni"
/>
</view>
<!-- 部门选择 -->
<view class="company-dept-dialog__field">
<text class="company-dept-dialog__label">部门</text>
<uni-data-select
v-model="selectedDeptModel"
:localdata="deptOptionsForSelect"
:clear="false"
@change="onDeptChangeUni"
/>
</view>
</view>
<view class="company-dept-dialog__footer">
<u-button v-if="showCancel" style="width: 40%" @tap="handleCancel">取消</u-button>
<u-button style="width: 40%" type="primary" :disabled="!isConfirmEnabled" @tap="handleConfirm"> 确定 </u-button>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import nx from '@/nx'
const dialogStore = nx.$store('company-dept')
const { companyList, selectedCompanyId, selectedDeptId, showCancel } = storeToRefs(dialogStore)
const deptOptions = computed(() => dialogStore.getDeptsByCompanyId(selectedCompanyModel.value))
// 用于 uni-data-select 的格式:必须包含 value 和 text 字段
const companyOptionsForSelect = computed(() =>
companyList.value.map(item => ({
value: item.companyId,
text: item.companyName
}))
)
const deptOptionsForSelect = computed(() =>
deptOptions.value.map(item => ({
value: item.deptId,
text: item.deptName
}))
)
// 双向绑定模型uni-data-select 使用 value 绑定)
const selectedCompanyModel = ref(selectedCompanyId.value)
const selectedDeptModel = ref(selectedDeptId.value)
// 监听 store 变化,同步到 model应对外部触发更新
watch(
() => selectedCompanyId.value,
newVal => {
if (newVal !== selectedCompanyModel.value) {
selectedCompanyModel.value = newVal
}
}
)
watch(
() => selectedDeptId.value,
newVal => {
if (newVal !== selectedDeptModel.value) {
selectedDeptModel.value = newVal
}
}
)
// uni-data-select 的 change 回调
const onCompanyChangeUni = e => {
const companyId = e
if (companyId !== undefined && companyId !== null) {
dialogStore.setSelectedCompany(companyId)
// 重置部门选择
dialogStore.setSelectedDept('')
selectedDeptModel.value = ''
}
}
const onDeptChangeUni = e => {
const deptId = e
if (deptId !== undefined && deptId !== null) {
dialogStore.setSelectedDept(deptId)
}
}
// 按钮状态
const isConfirmEnabled = computed(() => !!dialogStore.selectedCompanyId && !!dialogStore.selectedDeptId)
// 操作
const handleCancel = () => {
dialogStore.cancel()
}
const handleConfirm = () => {
if (!isConfirmEnabled.value) return
dialogStore.confirm()
}
</script>
<style scoped lang="scss">
.company-dept-dialog {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 15000;
display: flex;
align-items: center;
justify-content: center;
&__mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.45);
}
&__panel {
position: relative;
width: 45vw;
background: #ffffff;
border-radius: 12px;
padding: 24px 20px 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}
@media (max-width: 700px) {
&__panel {
width: 70vw;
}
}
&__header {
margin-bottom: 20px;
}
&__title {
display: block;
font-size: 18px;
font-weight: 600;
color: #1f1f1f;
text-align: center;
line-height: 1.4;
}
&__body {
display: flex;
flex-direction: column;
gap: 16px;
}
&__field {
display: flex;
flex-direction: column;
gap: 8px;
}
&__label {
font-size: 14px;
color: #606266;
}
&__footer {
display: flex;
justify-content: space-between;
margin-top: 24px;
}
}
</style>

View File

@@ -0,0 +1,196 @@
# 1.4.2
新增
1. 新增`filterValue`属性,支持通过此关键词来搜索并筛选树结构的内容
# 1.4.1
修复
1. 修复单选 onlyRadioLeaf 时末级节点无法选中的 bug
# 1.4.0
版本调整
建议更新,但需要注意,异步数据的时候,后台需返回 leaf 字段来判断是否末项数据
1. **调整数据项格式,新增 `leaf` 字段,来判断是否为末节点**
2. **调整数据项格式,新增 `sort` 字段,来排序节点位置**
3. **注意:异步加载数据,当为末项的时候,需要服务端数据返回 `leaf` 字段**
4. 新增 `alwaysFirstLoad` ,即异步数据总会在第一次展开节点时,拉取一次后台数据,来比对是否一致
5. 拆分 `field` 属性,**注意: 1.5.0 版本后将移除 `field` 属性**
6. 新增 `labelField``field.label`,指定节点对象中某个属性为**标签**字段,默认`label`
7. 新增 `valueField``field.key`,指定节点对象中某个属性为**值**字段,默认`value`
8. 新增 `childrenField``field.children`,指定节点对象中某个属性为**子树节点**字段,默认`children`
9. 新增 `disabledField``field.disabled`,指定节点对象中某个属性为**禁用**字段,默认`disabled`
10. 新增 `appendField``field.append`,指定节点对象中某个属性为**副标签**字段,默认`append`
11. 新增 `leafField``field.label`,指定节点对象中某个属性为**末级节点**字段,默认`leaf`
12. 新增 `sortField``field.label`,指定节点对象中某个属性为**排序**字段,默认`sort`
13. 新增 `isLeafFn` ,用来自定义控制数据项的末项
14. 更多的项目示例
15. 支持单选取消选中
16. 修复节点展开时可能存在的 bug
17. 修复节点选择可能存在的 bug
18. 调整为子节点默认继承父节点禁用属性
19. `setExpandedKeys` 添加参数一为 `all` 即可支持一键展开/收起全部节点
20. 其它更多优化
# 1.3.4
优化
1. 优化图标字体命名
# 1.3.3
优化
1. 新增方法调用
> - 新增`getUncheckedKeys`,返回未选的 key
> - 新增`getUncheckedNodes`,返回未选的节点
> - 新增`getUnexpandedKeys`,返回未展开的 key
> - 新增`getUnexpandedNodes`,返回未展开的节点
2. 优化示例项目
# 1.3.2
修复
1. 修复在 APP 真机环境中的报错
# 1.3.1
修复
1. 修复方法`setExpandedKeys`没联动展开上级父子节点
# 1.3.0
优化
1. `field`新增字段 `append` 用于在标签后面显示小提示
2. 新增支持点击标签也能选中节点
3. 方法`setExpandedKeys`支持加载动态数据
4. 修复父节点禁用,则不能展开及图标展开显示
5. 修复动态加载数据时,末级节点的 `children``null` 时仍显示展开图标
# 1.2.6
新增
1. 新增支持主题换色
2. 支持单选的`onlyRadioLeaf``true`时可点父节点展开/收起
3. 优化`expandChecked`调整为不展开无子节点的节点
# 1.2.5
新增
1. 新增 `expandChecked`,控制选择时是否展开当前已选的所有下级节点
# 1.2.4
修复
1. 修复动态数据展开状态异常问题
# 1.2.3
新增
1. 新增 `checkedDisabled`,是否渲染禁用值
2. 新增 `packDisabledkey`,是否返回已禁用并选中的 key
3. 修复选择父级时,子级已禁用但仍被选中的问题
# 1.2.2
优化
1. 调整动态数据载入处理方式
2. 修复节点数据因动态数据引起的状态异常
3. 修复初始节点数据默认选中
# 1.2.1
修复
1. 修复切换`选中状态`被重复选中问题
2. 修复动态数据引起的重复选择问题
# 1.2.0
新增
1. 新增方法调用
> - 新增`setCheckedKeys`,方法设置指定 key 的节点选中状态
> - 新增`setExpandedKeys`,方法设置指定 key 的节点展开状态
2. 修复小程序重复插槽一直刷报错问题
3. 优化展开时,会展开子级所以下级节点
# 1.1.1
新增
1. 新增`data``disabled`,支持节点禁用状态
2. 新增`field``disabled`,可自定`disabled`字段值
# 1.1.0
新增
1. 新增`loadMode``loadApi`,支持展开时加载异步数据
2. 新增方法调用
> - 新增`getCheckedKeys`,方法返回已选的 key
> - 新增`getHalfCheckedKeys`,方法返回半选的 key
> - 新增`getExpandedKeys`,方法返回已展开的 key
> - 新增`getCheckedNodes`,方法返回已选的节点
> - 新增`getHalfCheckedNodes`,方法返回半选的节点
> - 新增`getExpandedNodes`,方法返回已展开的节点
3. 对代码进行重构,更易于后期拓展
4. 此次更新后,页面多个的 DaTee 组件间的数据不再关联
# 1.0.6
新增
1. 新增`checkStrictly`,多选模式下选中时是否父子不关联
# 1.0.5
修复
1. 修复多选时已选数据重复问题
# 1.0.4
修复
1. 修复 `change` 事件回调数据的问题
# 1.0.3
优化
1. 优化文档及示例说明
# 1.0.2
新增
1. 新增 `onlyRadioLeaf` ,单选时只允许选中末级
2. 优化默认展开及默认选择的展开问题
# 1.0.1
新增
1. 支持展开/收起回调事件`@expand`
# 1.0.0
初始版本 1.0.0,基于 Vue3 进行开发,支持单选、多选,兼容各大平台
1. 支持单选
2. 支持多选

1181
components/da-tree/index.vue Normal file

File diff suppressed because it is too large Load Diff

197
components/da-tree/props.ts Normal file
View File

@@ -0,0 +1,197 @@
export default {
/**
* 树的数据
*/
data: {
type: Array,
default: () => []
},
/**
* 主题色
*/
themeColor: {
type: String,
default: '#007aff'
},
/**
* 是否开启多选,默认单选
*/
showCheckbox: {
type: Boolean,
default: false
},
/**
* 默认选中的节点注意单选时为单个key多选时为key的数组
*/
defaultCheckedKeys: {
type: [Array, String, Number],
default: null
},
/**
* 是否默认展开全部
*/
defaultExpandAll: {
type: Boolean,
default: false
},
/**
* 默认展开的节点
*/
defaultExpandedKeys: {
type: Array,
default: null
},
/**
* 筛选关键词
*/
filterValue: {
type: String,
default: ''
},
/**
* 是否自动展开到选中的节点,默认不展开
*/
expandChecked: {
type: Boolean,
default: false
},
/**
* (旧)字段对应内容,默认为 {label: 'label',key: 'key', children: 'children', disabled: 'disabled', append: 'append'}
* 注意1.5.0版本后不再兼容
*/
field: {
type: Object,
default: null
},
/**
* 标签字段(新,拆分了)
*/
labelField: {
type: String,
default: 'label'
},
/**
* 值字段(新,拆分了)
*/
valueField: {
type: String,
default: 'value'
},
/**
* 下级字段(新,拆分了)
*/
childrenField: {
type: String,
default: 'children'
},
/**
* 禁用字段(新,拆分了)
*/
disabledField: {
type: String,
default: 'disabled'
},
/**
* 末级节点字段(新,拆分了)
*/
leafField: {
type: String,
default: 'leaf'
},
/**
* 副标签字段(新,拆分了)
*/
appendField: {
type: String,
default: 'append'
},
/**
* 排序字段(新,拆分了)
*/
sortField: {
type: String,
default: 'sort'
},
/**
* Api数据返回后的结果路径支持嵌套如`data.list`
*/
resultField: {
type: String,
default: ''
},
isLeafFn: {
type: Function,
default: null
},
/**
* 是否显示单选图标,默认显示
*/
showRadioIcon: {
type: Boolean,
default: true
},
/**
* 单选时只允许选中末级,默认可随意选中
*/
onlyRadioLeaf: {
type: Boolean,
default: false
},
/**
* 多选时,是否执行父子不关联的任意勾选,默认父子关联
*/
checkStrictly: {
type: Boolean,
default: false
},
/**
* 为 true 时,空的 children 数组会显示展开图标
*/
loadMode: {
type: Boolean,
default: false
},
/**
* 异步加载接口
*/
loadApi: {
type: Function,
default: null
},
/**
* 是否总在首次的时候加载一下内容,来比对是否一致
*/
alwaysFirstLoad: {
type: Boolean,
default: false
},
/**
* 是否渲染(操作)禁用值
*/
checkedDisabled: {
type: Boolean,
default: false
},
/**
* 是否返回已禁用的但已选中的key
*/
packDisabledkey: {
type: Boolean,
default: true
},
/**
* 选择框的位置,可选 left/right
*/
checkboxPlacement: {
type: String,
default: 'left'
},
/**
* 子项缩进距离默认40单位rpx
*/
indent: {
type: Number,
default: 20
}
}

View File

@@ -0,0 +1,310 @@
# da-tree
一个基于 Vue3 的 tree(树)组件,同时支持主题换色,可能是最适合你的 tree(树)组件
组件一直在更新,遇到问题可在下方讨论。
`同时更新 Vue2 版本,在此查看 ===>` **[Vue2 版](https://ext.dcloud.net.cn/plugin?id=12692)**
### 关于使用
可在右侧的`使用 HBuilderX 导入插件``下载示例项目ZIP`,方便快速上手。
可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。
插件地址https://ext.dcloud.net.cn/plugin?id=12384
### 组件示例
```jsx
<template>
<view>多选</view>
<view><button @click="doCheckedTree(['2'],true)">全选</button></view>
<view><button @click="doCheckedTree(['2'],false)">取消全选</button></view>
<view><button @click="doCheckedTree(['211','222'],true)">选中指定节点</button></view>
<view><button @click="doCheckedTree(['211','222'],false)">取消选中指定节点</button></view>
<view><button @click="doExpandTree('all',true)">展开全部节点</button></view>
<view><button @click="doExpandTree('all',false)">收起全部节点</button></view>
<view><button @click="doExpandTree(['22','23'],true)">展开节点</button></view>
<view><button @click="doExpandTree(['22','23'],false)">收起节点</button></view>
<DaTree
ref="DaTreeRef"
:data="roomTreeData"
labelField="name"
valueField="id"
defaultExpandAll
showCheckbox
:defaultCheckedKeys="defaultCheckedKeysValue"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>单选</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
defaultExpandAll
:defaultCheckedKeys="defaultCheckedKeysValue2"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>默认展开指定节点</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
showCheckbox
:defaultExpandedKeys="defaultExpandKeysValue3"
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
<view>异步加载数据</view>
<DaTree
:data="roomTreeData"
labelField="name"
valueField="id"
showCheckbox
loadMode
:loadApi="GetApiData"
defaultExpandAll
@change="handleTreeChange"
@expand="handleExpandChange"></DaTree>
</template>
```
```js
import { defineComponent, ref } from 'vue'
/**
* 模拟创建一个接口数据
*/
function GetApiData(currentNode) {
const { key } = currentNode
return new Promise((resolve) => {
setTimeout(() => {
// 模拟返回空数据
if (key.indexOf('-') > -1) {
return resolve(null)
// return resolve([])
}
return resolve([
{
id: `${key}-1`,
name: `行政部X${key}-1`,
},
{
id: `${key}-2`,
name: `财务部X${key}-2`,
append: '定义了末项数据',
leaf: true,
},
{
id: `${key}-3`,
name: `资源部X${key}-3`,
},
{
id: `${key}-4`,
name: `资源部X${key}-3`,
append: '被禁用,无展开图标',
disabled: true,
},
])
}, 2000)
})
}
import DaTree from '@/components/da-tree/index.vue'
export default defineComponent({
components: { DaTree },
setup() {
const DaTreeRef = ref()
// key的类型必须对应树数据key的类型
const defaultCheckedKeysValue = ref(['211', '222'])
const defaultCheckedKeysValue2 = ref('222')
const defaultExpandKeysValue3 = ref(['212', '231'])
const roomTreeData = ref([
{
id: '2',
name: '行政中心',
children: [
{
id: '21',
name: '行政部',
children: [
{
id: '211',
name: '行政一部',
children: null,
},
{
id: '212',
name: '行政二部',
children: [],
disabled: true,
},
],
},
{
id: '22',
name: '财务部',
children: [
{
id: '221',
name: '财务一部',
children: [],
disabled: true,
},
{
id: '222',
name: '财务二部',
children: [],
},
],
},
{
id: '23',
name: '人力资源部',
children: [
{
id: '231',
name: '人力一部',
children: [],
},
{
id: '232',
name: '人力二部',
append: '更多示例,请下载示例项目查看',
},
],
},
],
},
])
function doExpandTree(keys, expand) {
DaTreeRef.value?.setExpandedKeys(keys, expand)
const gek = DaTreeRef.value?.getExpandedKeys()
console.log('当前已展开的KEY ==>', gek)
}
function doCheckedTree(keys, checked) {
DaTreeRef.value?.setCheckedKeys(keys, checked)
const gek = DaTreeRef.value?.getCheckedKeys()
console.log('当前已选中的KEY ==>', gek)
}
function handleTreeChange(allSelectedKeys, currentItem) {
console.log('handleTreeChange ==>', allSelectedKeys, currentItem)
}
function handleExpandChange(expand, currentItem) {
console.log('handleExpandChange ==>', expand, currentItem)
}
return {
DaTreeRef,
roomTreeData,
defaultCheckedKeysValue,
defaultCheckedKeysValue2,
defaultExpandKeysValue3,
handleTreeChange,
handleExpandChange,
GetApiData,
doExpandTree,
doCheckedTree,
}
},
})
```
** 更多示例请下载/导入示例项目 ZIP 查看 **
### 组件参数
| 属性 | 类型 | 默认值 | 必填 | 说明 |
| :------------------ | :------------------------------ | :--------- | :--- | :--------------------------------------------------------------------------- |
| data | `Array` | - | 是 | 树的数据 |
| themeColor | `String` | `#007aff` | 否 | 主题色,十六进制 |
| defaultCheckedKeys | `Array` \| `Number` \| `String` | - | 否 | 默认选中的节点,单选为单个 key多选为 key 的数组 |
| showCheckbox | `Boolean` | `false` | 否 | 是否开启多选,默认单选 |
| checkStrictly | `Boolean` | `false` | 否 | 多选时,是否执行父子不关联的任意勾选,默认父子关联 |
| showRadioIcon | `Boolean` | `true` | 否 | 是否显示单选图标,默认显示 |
| onlyRadioLeaf | `Boolean` | `true` | 否 | 单选时只允许选中末级,默认可随意选中 |
| defaultExpandAll | `Boolean` | `false` | 否 | 是否默认展开全部 |
| defaultExpandedKeys | `Array` | - | 否 | 默认展开的节点 |
| indent | `Number` | `40` | 否 | 子项缩进距离,单位 rpx |
| checkboxPlacement | `String` | `left` | 否 | 选择框的位置,可选 left/right |
| loadMode | `Boolean` | `false` | 否 | 为 true 时,空的 children 数组会显示展开图标 |
| loadApi | `Function` | - | 否 | 选择框的位置,可选 left/right |
| checkedDisabled | `Boolean` | `false` | 否 | 是否渲染禁用值,默认不渲染 |
| packDisabledkey | `Boolean` | `true` | 否 | 是否返回已禁用的但已选中的 key默认返回禁用已选值 |
| expandChecked | `Boolean` | `false` | 否 | 是否自动展开到选中的节点,默认不展开 |
| alwaysFirstLoad | `Boolean` | `false` | 否 | 是否总在首次的时候加载一下内容,默认不加载,否则只有展开末级节点才会加载数据 |
| isLeafFn | `Function` | - | 否 | 自定义函数返回来控制数据项的末项 |
| field | `Object` | - | 否 | 字段对应内容,格式参考下方(1.5.0 后移除,请用单独的字段匹配) |
| labelField | `String` | `label` | 否 | 指定节点对象中某个属性为标签字段,默认`label` |
| valueField | `String` | `value` | 否 | 指定节点对象中某个属性为值字段,默认`value` |
| childrenField | `String` | `children` | 否 | 指定节点对象中某个属性为子树节点字段,默认`children` |
| disabledField | `String` | `disabled` | 否 | 指定节点对象中某个属性为禁用字段,默认`disabled` |
| appendField | `String` | `append` | 否 | 指定节点对象中某个属性为副标签字段,默认`append` |
| leafField | `String` | `leaf` | 否 | 指定节点对象中某个属性为末级节点字段,默认`leaf` |
| sortField | `String` | `sort` | 否 | 指定节点对象中某个属性为排序字段,默认`sort` |
| filterValue | `String` | - | 否 | 搜索筛选的关键词,通过输入关键词筛选内容 |
**field 格式(1.5.0 后移除,请用单独的字段匹配)**
```js
{
label: 'label',
key: 'key',
children: 'children',
disabled: 'disabled',
append: 'append'
}
```
### 组件事件
| 事件名称 | 回调参数 | 说明 |
| :------- | :-------------------------------------- | :-------------- |
| change | `(allCheckedKeys, currentItem) => void` | 选中时回调 |
| expand | `(expandState, currentItem) => void` | 展开/收起时回调 |
### 组件方法
| 方法名称 | 参数 | 说明 |
| :------------------ | :--------------- | :------------------------------------------------------------------------------------------------ |
| setCheckedKeys | `(keys,checked)` | 设置指定 key 的节点选中/取消选中的状态。注: keys 单选时为 key多选时为 key 的数组 |
| setExpandedKeys | `(keys,expand)` | 设置指定 key 的节点展开/收起的状态,当 keys 为 all 时即代表展开/收起全部。注keys 为数组或 `all` |
| getCheckedKeys | - | 返回已选的 key |
| getHalfCheckedKeys | - | 返回半选的 key |
| getUncheckedKeys | - | 返回未选的 key |
| getCheckedNodes | - | 返回已选的节点 |
| getUncheckedNodes | - | 返回未选的节点 |
| getHalfCheckedNodes | - | 返回半选的节点 |
| getExpandedKeys | - | 返回已展开的 key |
| getUnexpandedKeys | - | 返回未展开的 key |
| getExpandedNodes | - | 返回已展开的节点 |
| getUnexpandedNodes | - | 返回未展开的节点 |
### 组件版本
v1.4.2
### 差异化
已通过测试
> - H5 页面
> - 微信小程序
> - 支付宝、钉钉小程序
> - 字节跳动、抖音、今日头条小程序
> - 百度小程序
> - 飞书小程序
> - QQ 小程序
> - 京东小程序
未测试
> - 快手小程序由于非企业用户暂无演示
> - 快应用、360 小程序因 Vue3 支持的原因暂无演示
### 开发组
[@CRLANG](https://crlang.com)

150
components/da-tree/utils.ts Normal file
View File

@@ -0,0 +1,150 @@
/** 未选 */
export const unCheckedStatus = 0
/** 半选 */
export const halfCheckedStatus = 1
/** 选中 */
export const isCheckedStatus = 2
/**
* 深拷贝内容
* @param originData 拷贝对象
* @author crlang(https://crlang.com)
*/
export function deepClone(originData) {
const type = Object.prototype.toString.call(originData)
let data
if (type === '[object Array]') {
data = []
for (let i = 0; i < originData.length; i++) {
data.push(deepClone(originData[i]))
}
} else if (type === '[object Object]') {
data = {}
for (const prop in originData) {
// eslint-disable-next-line no-prototype-builtins
if (originData.hasOwnProperty(prop)) { // 非继承属性
data[prop] = deepClone(originData[prop])
}
}
} else {
data = originData
}
return data
}
/**
* 获取所有指定的节点
* @param type
* @param value
* @author crlang(https://crlang.com)
*/
export function getAllNodes(list, type, value, packDisabledkey = true) {
if (!list || list.length === 0) {
return []
}
const res = []
for (let i = 0; i < list.length; i++) {
const item = list[i]
if (item[type] === value) {
if ((packDisabledkey && item.disabled) || !item.disabled) {
res.push(item)
}
}
}
return res
}
/**
* 获取所有指定的key值
* @param type
* @param value
* @author crlang(https://crlang.com)
*/
export function getAllNodeKeys(list, type, value, packDisabledkey = true) {
if (!list || list.length === 0) {
return null
}
const res = []
for (let i = 0; i < list.length; i++) {
const item = list[i]
if (item[type] === value) {
if ((packDisabledkey && item.disabled) || !item.disabled) {
res.push(item.key)
}
}
}
return res.length ? res : null
}
/**
* 错误输出
*
* @param msg
*/
export function logError(msg, ...args) {
console.error(`DaTree: ${msg}`, ...args)
}
const toString = Object.prototype.toString
export function is(val, type) {
return toString.call(val) === `[object ${type}]`
}
/**
* 是否对象(Object)
* @param val
*/
export function isObject(val) {
return val !== null && is(val, 'Object')
}
/**
* 是否数字(Number)
* @param val
*/
export function isNumber(val) {
return is(val, 'Number')
}
/**
* 是否字符串(String)
* @param val
*/
export function isString(val) {
return is(val, 'String')
}
/**
* 是否函数方法(Function)
* @param val
*/
export function isFunction(val) {
return typeof val === 'function'
}
/**
* 是否布尔(Boolean)
* @param val
*/
export function isBoolean(val) {
return is(val, 'Boolean')
}
/**
* 是否数组(Array)
* @param val
*/
export function isArray(val) {
return val && Array.isArray(val)
}

View File

@@ -0,0 +1,64 @@
//my-tabbar文件
<template>
<view>
<u-tabbar
:value="currentTab"
:fixed="true"
:border="false"
activeColor="#0055a2"
:placeholder="false"
@change="changeTabIndex"
>
<u-tabbar-item
v-for="item in switchTabs"
:key="item.name"
:text="item.text"
:icon="item.iconName"
></u-tabbar-item>
</u-tabbar>
</view>
</template>
<script setup>
import { computed, reactive } from 'vue'
let props = defineProps({
currentTab: {
type: Number,
default: 0
}
})
const switchTabs = reactive([
{
pagePath: '/pages/index/index',
iconName: 'home',
text: '首页',
name: 'home'
},
{
pagePath: '/pages/me/index',
iconName: 'account',
text: '我的',
name: 'account'
}
])
function changeTabIndex(e) {
let pagePath = switchTabs[e].pagePath
uni.switchTab({
url: pagePath
})
}
</script>
<style lang="scss" scoped>
::v-deep .u-tabbar__content {
background-color: #f2f2f2;
padding: 10rpx 0;
.u-icon__icon {
font-size: 54rpx !important;
}
.u-tabbar-item__text {
font-size: 38rpx;
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<view>
<slot />
<company-dept-dialog />
</view>
</template>
<script setup></script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,67 @@
<template>
<view class="content">
<view class="x-c">
<image style="width: 80px; height: 80px" :src="`/static/images/menus/${icon}.png`"></image>
</view>
<view class="pt50 pb50 fs30">
<span>{{ title }}</span>
</view>
<view class="x-c">
<u-icon size="150" color="#0055A2" name="scan"></u-icon>
</view>
<!-- #ifdef H5 -->
<up-search
shape="square"
placeholder="请输入设备id"
actionText="查询"
:clearabled="false"
:showAction="true"
@custom="handleInputSearch"
></up-search>
<!-- #endif -->
</view>
</template>
<script setup>
let props = defineProps({
title: {
type: String,
default: '请扫描设备'
},
icon: {
type: String,
default: 'useRecord'
}
})
const emits = defineEmits(['deviceId'])
const customIconsMap = new Map([
['useRecord', '&#xe66e;'],
['dailyCheck', '&#xe614;'],
['maintain', '&#xe60e;'],
['periodCheck', '&#xe676;'],
['calibration', '&#xe83b;'],
['accept', '&#xe6de;'],
['transfer', '&#xe603;']
])
function iconMap(key) {
return customIconsMap.get(key)
}
function handleInputSearch(e) {
emits('deviceId', e)
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: 'CustomFont';
src: url('@/static/iconfont/iconfont.ttf');
}
.content {
padding-top: 100px;
text-align: center;
background-color: #fff;
box-sizing: border-box;
height: 100%;
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<view>
<up-upload
v-if="isUpdate"
:file-list="fileList"
:accept="accept"
multiple
:size-type="['original', 'compressed']"
:max-count="maxCount"
:width="width"
:height="height"
:preview-full-image="true"
:disabled="disabled"
@delete="deletePic"
@after-read="afterRead"
>
</up-upload>
<u-album v-else multiple-size="85" single-size="85" :urls="imgs" row-count="4" />
</view>
</template>
<script setup>
import { ref, reactive, watch, onMounted } from 'vue'
import { getBaseUrl, getImgBaseUrl } from '@/defaultBaseUrl'
const props = defineProps({
modelValue: { type: [Array, String], default: '' },
isUpdate: { type: Boolean, default: true },
disabled: { type: Boolean, default: false },
accept: { type: String, default: 'image' },
width: { type: String, default: '80px' },
height: { type: String, default: '80px' },
maxCount: { type: Number, default: 20 },
returnAsString: { type: Boolean, default: true }
})
const emit = defineEmits(['update:modelValue'])
const fileList = ref([])
const imgs = ref([])
const uploadConfig = reactive({
baseURL: getBaseUrl(),
imgBaseURL: getImgBaseUrl(),
header: { 'X-Access-Token': uni.getStorageSync('token') }
})
// 同步外部值到组件
watch(
() => props.modelValue,
newVal => {
if (!newVal) return (fileList.value = [])
const urls = Array.isArray(newVal) ? newVal : newVal.split(',')
imgs.value = urls
fileList.value = urls.map(url => ({
url: uploadConfig.imgBaseURL + url,
status: 'success',
message: ''
}))
},
{ immediate: true }
)
const deletePic = event => {
fileList.value.splice(event.index, 1)
updateParent(fileList.value)
}
const afterRead = async event => {
const files = [].concat(event.file)
const initialLength = fileList.value.length
// 添加上传状态
fileList.value.push(
...files.map(file => ({
...file,
status: 'uploading',
message: '上传中'
}))
)
try {
for (let i = 0; i < files.length; i++) {
const result = await uploadFile(files[i])
const index = initialLength + i
fileList.value[index] = {
...files[i],
status: 'success',
url: uploadConfig.imgBaseURL + result.message
}
}
} catch (error) {
console.error('Upload failed:', error)
// 回滚失败的上传
fileList.value.splice(initialLength, files.length)
} finally {
updateParent(fileList.value)
}
}
const uploadFile = file => {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${uploadConfig.baseURL}/sys/common/upload`,
filePath: file.url,
name: 'file',
header: uploadConfig.header,
formData: { biz: 'lims-device' },
success: res => {
if (res.statusCode === 200) {
resolve(JSON.parse(res.data))
} else {
reject(new Error(res.data))
}
},
fail: reject
})
})
}
const formatValue = fileList => {
// 去掉 imgBaseURL 后的部分
const baseUrlLength = uploadConfig.imgBaseURL.length
return props.returnAsString
? fileList.map(item => item.url.slice(baseUrlLength)).join(',')
: fileList.map(item => item.url.slice(baseUrlLength))
}
const updateParent = fileList => {
emit('update:modelValue', formatValue(fileList))
}
</script>
<style lang="scss" scoped>
:deep(.u-upload__wrap__preview) {
margin: 0;
}
</style>

View File

@@ -0,0 +1,566 @@
<template>
<view>
<view v-if="visible" class="s-verify-mask" @touchmove.stop.prevent>
<view class="s-verify-wrapper" @tap.stop>
<view class="s-verify-header">
<text class="s-verify-title">安全验证</text>
<!-- <view class="s-verify-close" @tap="handleClose">×</view> -->
</view>
<view class="s-verify-body">
<view class="s-verify-image" :style="imageStyle">
<image v-if="backgroundImage" class="s-verify-image-bg" :src="backgroundImage" mode="aspectFill" />
<image
v-if="showBlock"
class="s-verify-image-block"
:style="blockStyle"
:src="blockImage"
mode="widthFix"
/>
<view class="s-verify-refresh" @tap="refreshCaptcha">
<text></text>
</view>
<view v-if="loading" class="s-verify-loading">加载中...</view>
</view>
<view v-if="tipMessage" :class="['s-verify-tip', tipClass]">
{{ tipMessage }}
</view>
<view
class="s-verify-slider"
ref="sliderRef"
@touchstart.stop.prevent="onDragStart"
@touchmove.stop.prevent="onDragMove"
@touchend.stop.prevent="onDragEnd"
@mousedown.stop="onMouseDown"
>
<view class="s-verify-slider-track" />
<view class="s-verify-slider-fill" :style="sliderFillStyle" />
<view
class="s-verify-slider-handle"
:class="{
's-verify-slider-success': success,
's-verify-slider-fail': failAnimate
}"
:style="sliderHandleStyle"
>
<text class="s-verify-handle-icon">{{ success ? '✔' : '' }}</text>
</view>
<text class="s-verify-slider-text">{{ sliderHint }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue'
import nx from '@/nx'
import { aesEncrypt } from './utils/aes'
const props = defineProps({
captchaType: {
type: String,
default: 'blockPuzzle'
},
mode: {
type: String,
default: 'pop'
}
})
const emits = defineEmits(['success', 'error'])
const visible = ref(false)
const loading = ref(false)
const success = ref(false)
const failAnimate = ref(false)
const verifying = ref(false)
const tipMessage = ref('')
const sliderRef = ref(null)
const sliderWidth = 310
const sliderHeight = 40
const blockWidth = Math.floor((sliderWidth * 47) / 310)
const imageHeight = 155
const dragState = reactive({
startClientX: 0,
baseOffset: 0,
isDragging: false
})
const captchaState = reactive({
originalImageBase64: '',
jigsawImageBase64: '',
token: '',
secretKey: ''
})
const handleOffset = ref(0)
const maxOffset = computed(() => Math.max(sliderWidth - blockWidth, 0))
const showBlock = computed(() => props.captchaType === 'blockPuzzle' && blockImage.value)
const backgroundImage = computed(() =>
captchaState.originalImageBase64 ? `data:image/png;base64,${captchaState.originalImageBase64}` : ''
)
const blockImage = computed(() =>
captchaState.jigsawImageBase64 ? `data:image/png;base64,${captchaState.jigsawImageBase64}` : ''
)
const imageStyle = computed(() => ({
width: `${sliderWidth}px`,
height: `${imageHeight}px`
}))
const blockStyle = computed(() => ({
width: `${blockWidth}px`,
height: `${imageHeight}px`,
transform: `translateX(${handleOffset.value}px)`
}))
const sliderHandleStyle = computed(() => ({
width: `${blockWidth}px`,
height: `${sliderHeight}px`,
transform: `translateX(${handleOffset.value}px)`
}))
const sliderFillStyle = computed(() => ({
width: `${handleOffset.value > 0 ? Math.min(handleOffset.value + blockWidth, sliderWidth) : 0}px`
}))
const sliderHint = computed(() => {
if (success.value) {
return '验证通过'
}
if (loading.value) {
return '验证码加载中'
}
return '请按住滑块,拖动完成拼图'
})
const tipClass = computed(() => {
if (success.value) return 's-verify-tip-success'
if (failAnimate.value) return 's-verify-tip-error'
return ''
})
let closeTimer = null
let mouseMoveListener = null
let mouseUpListener = null
function show() {
if (visible.value) return
visible.value = true
resetState()
nextTick(() => {
loadCaptcha()
})
}
function handleClose() {
visible.value = false
cleanupMouseListeners()
clearCloseTimer()
}
function refreshCaptcha() {
if (loading.value) return
resetSlider()
loadCaptcha()
}
function resetSlider() {
handleOffset.value = 0
dragState.baseOffset = 0
dragState.startClientX = 0
dragState.isDragging = false
success.value = false
failAnimate.value = false
tipMessage.value = ''
verifying.value = false
}
function resetState() {
captchaState.originalImageBase64 = ''
captchaState.jigsawImageBase64 = ''
captchaState.token = ''
captchaState.secretKey = ''
resetSlider()
}
async function loadCaptcha() {
loading.value = true
try {
const res = await nx.$api.sys.getCaptchaCode({ captchaType: props.captchaType })
if (res && (res.repCode === '0000' || res.code === 0)) {
const data = res.repData || res.data || {}
captchaState.originalImageBase64 = data.originalImageBase64 || ''
captchaState.jigsawImageBase64 = data.jigsawImageBase64 || ''
captchaState.token = data.token || ''
captchaState.secretKey = data.secretKey || ''
} else {
tipMessage.value = res?.repMsg || res?.msg || '验证码获取失败,请重试'
emits('error', res)
}
} catch (error) {
console.error('captcha load error', error)
tipMessage.value = '验证码加载异常,请稍后再试'
emits('error', error)
} finally {
loading.value = false
}
}
function onDragStart(event) {
if (loading.value || success.value) return
dragState.isDragging = true
dragState.startClientX = getClientX(event)
dragState.baseOffset = handleOffset.value
failAnimate.value = false
tipMessage.value = ''
}
function onDragMove(event) {
if (!dragState.isDragging) return
const currentX = getClientX(event)
const delta = currentX - dragState.startClientX
let next = dragState.baseOffset + delta
if (next < 0) next = 0
if (next > maxOffset.value) next = maxOffset.value
handleOffset.value = next
}
function onDragEnd() {
if (!dragState.isDragging) return
dragState.isDragging = false
submitVerification()
}
function onMouseDown(event) {
if (event?.button !== 0) return
if (typeof window !== 'undefined') {
mouseMoveListener = ev => {
onDragMove(ev)
ev.preventDefault()
}
mouseUpListener = ev => {
cleanupMouseListeners()
onDragEnd(ev)
}
window.addEventListener('mousemove', mouseMoveListener)
window.addEventListener('mouseup', mouseUpListener)
}
onDragStart(event)
}
function cleanupMouseListeners() {
if (typeof window === 'undefined') return
if (mouseMoveListener) {
window.removeEventListener('mousemove', mouseMoveListener)
mouseMoveListener = null
}
if (mouseUpListener) {
window.removeEventListener('mouseup', mouseUpListener)
mouseUpListener = null
}
}
function clearCloseTimer() {
if (closeTimer) {
clearTimeout(closeTimer)
closeTimer = null
}
}
async function submitVerification() {
if (verifying.value) return
verifying.value = true
failAnimate.value = false
const point = {
x: Math.round(handleOffset.value),
y: 5.0
}
try {
const payload = captchaState.secretKey
? aesEncrypt(JSON.stringify(point), captchaState.secretKey)
: JSON.stringify(point)
const res = await nx.$api.sys.verifyCaptcha({
captchaType: props.captchaType,
token: captchaState.token,
pointJson: payload
})
if (res && (res.repCode === '0000' || res.code === 0)) {
success.value = true
tipMessage.value = '验证成功'
const captchaVerification = captchaState.secretKey
? aesEncrypt(`${captchaState.token}---${JSON.stringify(point)}`, captchaState.secretKey)
: `${captchaState.token}---${JSON.stringify(point)}`
emits('success', {
captchaVerification,
token: captchaState.token,
point
})
clearCloseTimer()
closeTimer = setTimeout(() => {
handleClose()
}, 800)
} else {
handleFail(res?.repMsg || res?.msg || '验证失败,请重试')
}
} catch (error) {
console.error('captcha verify error', error)
handleFail('网络异常,请重试')
} finally {
verifying.value = false
}
}
function handleFail(message) {
failAnimate.value = true
tipMessage.value = message
emits('error', message)
setTimeout(() => {
failAnimate.value = false
}, 400)
setTimeout(() => {
refreshCaptcha()
}, 500)
}
function getClientX(event) {
if (event?.touches && event.touches.length) {
return event.touches[0].clientX
}
if (event?.changedTouches && event.changedTouches.length) {
return event.changedTouches[0].clientX
}
return event?.clientX || 0
}
onBeforeUnmount(() => {
cleanupMouseListeners()
clearCloseTimer()
})
defineExpose({
show,
close: handleClose,
refresh: refreshCaptcha
})
</script>
<style lang="scss" scoped>
.s-verify-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.s-verify-wrapper {
width: 340px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18);
overflow: hidden;
}
.s-verify-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #f2f3f5;
}
.s-verify-title {
font-size: 16px;
font-weight: 600;
color: #1f2d3d;
}
.s-verify-close {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 50%;
color: #999999;
font-size: 18px;
}
.s-verify-body {
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.s-verify-image {
position: relative;
border-radius: 12px;
overflow: hidden;
background: #f5f5f5;
}
.s-verify-image-bg {
width: 100%;
height: 100%;
display: block;
}
.s-verify-image-block {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: block;
}
.s-verify-refresh {
position: absolute;
top: 10px;
right: 10px;
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.45);
color: #ffffff;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.s-verify-loading {
position: absolute;
top: 0;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.65);
font-size: 14px;
color: #666666;
}
.s-verify-tip {
width: 100%;
text-align: center;
font-size: 14px;
padding: 6px 0;
border-radius: 6px;
}
.s-verify-tip-success {
color: #52c41a;
background: rgba(82, 196, 26, 0.12);
}
.s-verify-tip-error {
color: #ff4d4f;
background: rgba(255, 77, 79, 0.12);
}
.s-verify-slider {
position: relative;
width: 310px;
height: 40px;
border-radius: 20px;
background: #f2f3f5;
overflow: hidden;
}
.s-verify-slider-track {
width: 100%;
height: 100%;
border-radius: 20px;
background: #f2f3f5;
}
.s-verify-slider-fill {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: var(--ui-BG-Main, #409eff);
border-radius: 20px;
transition: width 0.05s linear;
}
.s-verify-slider-handle {
position: absolute;
top: 0;
background: #ffffff;
border-radius: 20px;
box-shadow: 0 6px 14px rgba(25, 87, 170, 0.25);
display: flex;
align-items: center;
justify-content: center;
color: #1f2d3d;
transition: transform 0.05s linear;
}
.s-verify-slider-success {
background: #52c41a;
color: #ffffff;
}
.s-verify-slider-fail {
animation: s-verify-shake 0.3s;
}
.s-verify-handle-icon {
font-size: 22px;
font-weight: 600;
}
.s-verify-slider-text {
position: absolute;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #8c8c8c;
font-size: 14px;
}
@keyframes s-verify-shake {
0% {
transform: translateX(-4px);
}
25% {
transform: translateX(4px);
}
50% {
transform: translateX(-2px);
}
75% {
transform: translateX(2px);
}
100% {
transform: translateX(0);
}
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<up-navbar
v-bind="$attrs"
:bgColor="bgColor"
titleStyle="color:#fff"
leftIconColor="#fff"
:left-text="leftText"
:title="title"
placeholder
:autoBack="autoBack"
:leftIcon="leftIcon"
@rightClick="rightClick"
@leftClick="leftClick"
>
<template #right>
<slot></slot>
</template>
</up-navbar>
</template>
<script setup>
const emits = defineEmits(['leftClick', 'rightClick'])
const props = defineProps({
title: {
type: String,
required: true
},
autoBack: {
type: Boolean,
default: true
},
color: {
type: String,
default: '#fff'
},
leftIcon: {
type: String,
default: 'arrow-left'
},
leftText: {
type: String,
default: '返回'
},
bgColor: {
type: String,
default: '#0055A2'
}
})
function rightClick() {
emits('rightClick')
}
function leftClick() {
emits('leftClick')
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,304 @@
<template>
<view>
<u-popup :show="showAuncelSelector" closeable @close="close" @open="open" mode="right">
<view class="p10">天平选择</view>
<scroll-view scroll-y="true" class="content">
<u-grid :col="3" @click="doSelect" style="gap: 20px">
<u-grid-item v-for="(auncel, index) in auncelList" :index="index" :key="index">
<view class="auncel-item">
<view class="auncel-name">
{{ auncel.deviceCode }}
<view style="text-align: center">{{ auncel.controlRealName }}</view>
</view>
<view class="weight">
<view :class="getWeightClass(auncel)" :style="getWeightStyle(auncel)">
{{ getWeightText(auncel) }}
</view>
<view v-if="auncel.isConnected === 1" class="weight-unit">{{ auncel.weightUnit }}</view>
</view>
</view>
<view v-if="auncel.isConnected != 1" class="shade">天平断开</view>
</u-grid-item>
</u-grid>
</scroll-view>
</u-popup>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import nx from '@/nx' // 假设你的全局状态/工具挂载在 nx
// Props & Emits
const props = defineProps({
showAuncelSelector: {
type: Boolean,
default: false
},
previousAuncelId: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:showAuncelSelector', 'doSelect'])
// 响应式数据
const auncelList = ref([])
// 计算属性:获取用户信息
const userInfo = computed(() => nx.$store('user').userInfo)
// 方法
const open = () => {
getPageData()
listenDeviceData()
}
const close = () => {
auncelList.value = []
closeDeviceListener()
emit('update:showAuncelSelector', false)
}
const getPageData = () => {
nx.$api.laboratory
.getDeviceLaboratoryListBy({
pageNo: 1,
pageSize: 999,
collectDeviceType: 'balance',
deviceStatus: '0',
isEnable: '1'
})
.then(res => {
auncelList.value = res.list
})
}
const doSelect = index => {
const currentAuncel = auncelList.value[index]
if (currentAuncel.isConnected !== 1) {
uni.showToast({ title: '天平设备尚未连接!', icon: 'none' })
return
}
if (currentAuncel.controlRealName && currentAuncel.controlRealName !== userInfo.value.nickname) {
uni.showToast({
title: `当前天平正被“${currentAuncel.controlRealName}”使用,请选择其他天平!`,
icon: 'none'
})
return
}
let delayFlag = false
if (props.previousAuncelId && props.previousAuncelId !== '' && props.previousAuncelId !== currentAuncel.id) {
releaseDeviceControl(props.previousAuncelId)
delayFlag = true
}
const controlDevice = {
msgId: currentAuncel.id,
cmd: 'controlDevice',
clientType: 'caaClient',
data: {
deviceId: currentAuncel.id,
deviceCode: currentAuncel.deviceCode,
deviceName: currentAuncel.productName,
isControl: true,
controlRealName: userInfo.value.nickname
}
}
const sendControl = () => {
const controlData = JSON.stringify(controlDevice)
nx.$measure.send(controlData)
emit('update:showAuncelSelector', false)
emit('doSelect', controlDevice)
}
if (delayFlag) {
setTimeout(sendControl, 300)
} else {
sendControl()
}
}
const releaseDeviceControl = deviceId => {
if (!deviceId) return
const controlDevice = {
msgId: deviceId,
cmd: 'controlDevice',
clientType: 'caaClient',
data: {
deviceId,
isControl: false,
controlRealName: userInfo.value.nickname
}
}
nx.$measure.send(JSON.stringify(controlDevice))
}
const listenDeviceData = () => {
uni.$on('deviceData', handleDeviceData)
uni.$on('deviceStatus', handleDeviceStatus)
uni.$on('connClose', handleConnClose)
}
const handleDeviceData = res => {
if (res.deviceType === 'balance') {
auncelList.value.forEach(item => {
if (item.id === res.deviceId) {
item.weightData = res.weightData
item.weightUnit = res.weightUnit
item.weightStable = res.weightStable
item.isConnected = 1
item.controlRealName = res.controlRealName
}
})
}
}
const handleDeviceStatus = res => {
if (res.deviceType === 'balance') {
auncelList.value.forEach(item => {
if (item.id === res.deviceId) {
item.isConnected = res.connected
if (res.connected == 0) {
item.weightData = ''
item.weightUnit = ''
item.weightStable = 0
}
}
})
}
}
const handleConnClose = () => {
auncelList.value.forEach(item => {
item.weightData = ''
item.weightUnit = ''
item.weightStable = 0
item.controlRealName = ''
item.isConnected = 0
})
}
const closeDeviceListener = () => {
uni.$off('deviceData', handleDeviceData)
uni.$off('deviceStatus', handleDeviceStatus)
uni.$off('connClose', handleConnClose)
}
// 生命周期
onMounted(() => {
// 如果组件在 mounted 时已打开,可考虑自动加载(但通常由父组件控制 show
})
onUnmounted(() => {
closeDeviceListener()
})
function getWeightText(auncel) {
if (auncel.isConnected !== 1) {
return ''
}
return auncel.weightData || ''
}
function getWeightClass(auncel) {
if (auncel.weightStable === 0) return 'weight-data-yellow'
if (auncel.weightStable === 1) return 'weight-data'
if (auncel.weightStable === 2) return 'weight-data-warning'
return 'weight-data-yellow'
}
function getWeightStyle(auncel) {
if (auncel.isConnected !== 1) {
return {
textAlign: 'center',
fontSize: '28px'
}
}
return {
textAlign: 'right',
fontSize: '32px'
}
}
</script>
<style scoped lang="scss">
.content {
width: 60vw;
height: 90vh;
padding: 0 20px 40px 20px;
}
.auncel-item {
height: 180px;
width: 180px;
background-image: url(/static/images/auncel.png);
background-repeat: no-repeat;
background-size: 100%;
display: flex;
flex-direction: column;
}
.shade {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(31, 41, 55, 0.55);
color: #fff;
font-weight: 500;
z-index: 10;
border-radius: 8px;
}
.auncel-name {
flex: 1;
text-align: center;
padding-top: 20px;
}
.weight {
flex: 1;
font-size: 28px;
padding-top: 10px;
padding: 10px 30px 0 30px;
position: relative;
}
.weight-data,
.weight-data-yellow,
.weight-data-warning {
font-family: zzjc-lcd;
}
.weight-data {
color: #4cd964;
}
.weight-data-yellow {
color: #ffff00;
}
.weight-data-warning {
color: #ff3333;
}
.weight-unit {
position: absolute;
right: 10px;
top: 10px;
color: #ffffff;
font-size: 20px;
}
@media (max-width: 700px) {
.auncel-item {
height: 150px;
width: 150px;
}
.auncel-name {
padding-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,88 @@
<!-- 查看样品详情 -->
<template>
<view>
<u-popup :show="showPopup" @close="close" @open="open" mode="left">
<view class="detail_title">
{{ taskDetail.sampleName }}
{{ taskDetail.sampleAssayCode }}
</view>
<view>
<scroll-view scroll-y style="height: 100vh; width: 30vw">
<view style="padding: 10px">
<view class="form-item-my" v-for="(field, index) in fields" :key="index">
<view class="label-my">{{ field.title }}</view>
<view class="value-my">{{ field.value }}</view>
</view>
</view>
<view class="p30"></view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import nx from '@/nx'
// Props
const props = defineProps({
showPopup: {
type: Boolean,
default: false
},
detailId: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:showPopup'])
// Data
const fields = ref([])
const taskDetail = ref({})
// Methods
const getAllIndexes = arr => {
return arr.map((_, index) => index)
}
const close = () => {
emit('update:showPopup', false)
}
const getSampleData = () => {
const businessAssayTaskDataId = props.detailId
nx.$api.assayTask.getSampleAnalysisDataByTaskDataId({ businessAssayTaskDataId }).then(res => {
taskDetail.value = res
fields.value = res.columns
})
}
const open = () => {
getSampleData()
}
</script>
<style scoped lang="scss">
.form-item-my {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding: 8px 0;
}
.label-my {
color: #606266;
}
.detail_title {
text-align: center;
font-size: 18px;
font-weight: 400;
padding: 20px;
padding-top: 35px;
background-color: $uni-color-primary;
color: #fff;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<view>
<up-row justify="space-between" v-for="item in commentWf">
<up-col span="6">
<view>
{{ item.nodeName }}<text class="value">{{ item.comment }}</text>
</view>
</up-col>
<up-col span="6">
<view class="y-end">
<text class="value">{{ item.userName }}</text>
<text class="value">{{ item.time }}</text>
</view>
</up-col>
</up-row>
</view>
</template>
<script setup>
const props = defineProps({
commentWf: {
type: Array,
default: () => []
}
})
</script>
<style lang="scss" scoped>
.u-row {
border-bottom: 1px solid #eee;
padding: 10px 0;
}
.value {
color: #666;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<view class="y-f" style="height: 55vh">
<view class="weight">
<view class="weight-data">{{ nums }}</view>
</view>
<view class="keyboard-container">
<view class="keypad">
<view
class="oner"
:style="getListItemStyle(index)"
v-for="(item, index) in numbers"
:key="'num_' + index"
@click="changeNums(item, index)"
>
{{ item.text }}
</view>
</view>
<view class="func-pad">
<view @click="jianshao()" class="oner flex1">
<u-icon name="arrow-leftward" bold></u-icon>
</view>
<view @click="setNull()" class="oner flex1 mt10 mb10">清空</view>
<view class="oner confirm" @click="ok()">确认</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// Props
const props = defineProps({
numKeyboardParam: {
type: Object,
default: () => null
}
})
// Emits虽然你用的是 uni.$emit但也可以定义 emit 用于规范)
// const emit = defineEmits(['keyboardOK'])
// Data
const nums = ref('')
const numbers = ref([
{ text: '1' },
{ text: '2' },
{ text: '3' },
{ text: '4' },
{ text: '5' },
{ text: '6' },
{ text: '7' },
{ text: '8' },
{ text: '9' },
{ text: '0' },
{ text: '-' },
{ text: '.' }
])
// Methods
const ok = () => {
const val = { val: nums.value }
nums.value = ''
if (val.val) {
uni.$emit('keyboardOK', val)
}
}
const setNull = () => {
nums.value = ''
uni.$emit('keyboardOK', null)
}
const clearNum = () => {
nums.value = ''
}
const jianshao = () => {
if (nums.value) {
nums.value = nums.value.substring(0, nums.value.length - 1)
}
}
const changeNums = (item, index) => {
const inputChar = item.text
// 处理负号 '-'
if (inputChar === '-') {
// 只允许在输入为空时输入负号(即只能作为第一个字符)
if (nums.value === '') {
nums.value = '-'
}
return // 无论是否成功,都不继续处理
}
if (inputChar === '.') {
// 不能以 '.' 开头(除非已有负号)
if (nums.value === '' || nums.value === '-') {
return false
}
// 已存在小数点则不允许再输入
if (nums.value.indexOf('.') !== -1) {
return false
}
}
// 检查小数位数限制
let decimal = props.numKeyboardParam?.decimal ?? -1
const parts = nums.value.split('.')
if (parts.length === 2 && decimal !== -1) {
if (parts[1].length >= decimal) {
return false
}
}
nums.value += inputChar
}
const getListItemStyle = index => {
return {
background: numbers.value[index]?.background || ''
}
}
defineExpose({ clearNum })
</script>
<style lang="scss" scoped>
.keyboard-container {
flex: 5;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(232, 232, 232, 0.98);
}
.keypad {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 1fr;
gap: 10px;
padding: 10px;
flex: 4;
height: 95%;
}
.oner {
width: 95%;
font-size: 20px;
border: none;
border-radius: 5px;
background-color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.func-pad {
height: 100%;
display: flex;
padding: 10px 10px 10px 0;
flex-direction: column;
flex: 1;
box-sizing: border-box;
}
.confirm {
flex: 2;
color: #ffffff;
background-color: #19be6b;
}
.weight {
width: 100%;
background-color: #2c405a;
flex: 1;
padding: 8px;
box-sizing: border-box;
}
.weight-data {
color: #4cd964;
text-align: right;
letter-spacing: 5px;
font-size: 50px;
font-family: zzjc-lcd;
height: 40px;
}
</style>

36
defaultBaseUrl.js Normal file
View File

@@ -0,0 +1,36 @@
// 在此不用配置接口前缀
const isDev = process.env.NODE_ENV === 'development'
const BaseUrl = isDev ? 'http://192.168.26.116:888/admin-api' : 'http://172.16.46.62:30081/admin-api'
// const BaseUrl = isDev ? 'http://192.168.26.163:48080/admin-api' : 'http://192.168.26.116:888/admin-api'
//
// const BaseUrl = isDev ? 'http://localhost:9999' : ''
const upgradeBaseUrl = 'http://192.168.26.116:888'
const tenantId = '1'
export const clientId = 'zgty_lims'
function initDefaultBaseUrl() {
uni.setStorageSync('base_url', BaseUrl)
uni.setStorageSync('tenant_id', tenantId)
uni.setStorageSync('upgradeBaseUrl', upgradeBaseUrl)
}
initDefaultBaseUrl()
export function getBaseUrl() {
return uni.getStorageSync('base_url')
}
export function getImgBaseUrl() {
return uni.getStorageSync('base_url').replace('/api', '')
}
export function getTenantId() {
return uni.getStorageSync('tenant_id')
}
export function getUpgradeBaseUrl() {
return uni.getStorageSync('upgradeBaseUrl')
}
export function getWebSocketUrl() {
// return uni.getStorageSync('base_url').replace('/api', '') + '/ws'
return 'ws://192.168.26.116:888/ws'
}

13421
hybrid/html/build/pdf.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

45929
hybrid/html/build/pdf.worker.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
hybrid/html/build/pdf.worker.js.map vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<03>RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE<53>CNS2-H

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<02>RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE<53> ETen-B5-H` ^

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More