| New file |
| | |
| | | <script> |
| | | export default { |
| | | onLaunch: function() { |
| | | console.log('App Launch') |
| | | |
| | | if(uni.getStorageSync('token')){ |
| | | console.log('已登陆------------') |
| | | // uni.switchTab({ |
| | | // url: '/pages/index/index' |
| | | // }); |
| | | } |
| | | else{ |
| | | console.log('未登陆------------') |
| | | // uni.reLaunch({ |
| | | // url: '/pages/login/login' |
| | | // }); |
| | | } |
| | | uni.onBLECharacteristicValueChange(res => { |
| | | uni.$emit('BLECharacteristic', res) |
| | | }) |
| | | |
| | | |
| | | }, |
| | | onShow: function() { |
| | | console.log('App Show') |
| | | }, |
| | | onHide: function() { |
| | | console.log('App Hide') |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | /*每个页面公共css */ |
| | | page { |
| | | font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif; |
| | | |
| | | } |
| | | </style> |
| New file |
| | |
| | | // post请求,获取菜单 |
| | | export const postMenu = (params, config = {}) => uni.$uv.http.post('/ebapi/public_api/index', params, config) |
| | | |
| | | // get请求,获取菜单,注意:get请求的配置等,都在第二个参数中,详见前面解释 |
| | | export const getMenu = (data) => uni.$uv.http.get('/ebapi/public_api/index', data) |
| | | |
| | | // 登录 |
| | | export const login = (data) => uni.$uv.http.post("/login", data); |
| | | |
| | | // 退出登录 |
| | | export const logout = (data) => uni.$uv.http.post("/logout", data); |
| | | |
| | | // 注册 |
| | | export const register = (data) => uni.$uv.http.post("/mobile/register", data); |
| | | |
| | | // 注销账户 |
| | | export const unRegistry = () => uni.$uv.http.post("/unRegistry"); |
| | | |
| | | // 获取用户信息 |
| | | export const userInfo = (data) => uni.$uv.http.get('/getInfo', data) |
| | | |
| | | // 修改编辑用户信息 |
| | | export const userProfile = (data) => uni.$uv.http.put("/system/user/profile", data); |
| | | |
| | | // 新建设备 |
| | | export const equipmentAdd = (data) => uni.$uv.http.post("/system/equipment", data); |
| | | |
| | | // 设备列表 |
| | | export const equipmentList = (data) => uni.$uv.http.get('/system/equipment/list', {params:data}) |
| | | |
| | | // 删除列表 |
| | | export const equipmentDel = (id) => uni.$uv.http.delete("/system/equipment/"+id); |
| | | |
| | | |
| | | // 模板列表 |
| | | export const templeteList = (data) => uni.$uv.http.get('/system/templete/list', {params:data}) |
| | | |
| | | // 新增模板 |
| | | export const templeteAdd = (data) => uni.$uv.http.post("/system/templete", data); |
| | | |
| | | // 删除模版 |
| | | export const templeteDel = (id) => uni.$uv.http.delete("/system/templete/"+id); |
| | | |
| | | // 隐私协议/关于我们 1/2 |
| | | export const content = (id) => uni.$uv.http.get('/system/content/'+id) |
| | | |
| | | // 意见反馈 |
| | | export const feekback = (data) => uni.$uv.http.post("/system/opinion", data); |
| | | |
| | | // 客服电话 |
| | | export const configPhone = () => uni.$uv.http.get('/system/config/configKey/biz.customer.phone') |
| | | |
| | | // app下载 |
| | | export const appDownload = () => uni.$uv.http.get('/system/config/configKey/biz.appDownload') |
| | | |
| | | // 模版详情 |
| | | export const templeteInfo = (id) => uni.$uv.http.get("/system/templete/"+id); |
| | | |
| | | // 修改模板 |
| | | export const templeteEdit = (data) => uni.$uv.http.put("/system/templete", data); |
| New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="en"> |
| | | <head> |
| | | <meta charset="UTF-8" /> |
| | | <script> |
| | | var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || |
| | | CSS.supports('top: constant(a)')) |
| | | document.write( |
| | | '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + |
| | | (coverSupport ? ', viewport-fit=cover' : '') + '" />') |
| | | </script> |
| | | <title></title> |
| | | <!--preload-links--> |
| | | <!--app-context--> |
| | | </head> |
| | | <body> |
| | | <div id="app"><!--app-html--></div> |
| | | <script type="module" src="/main.js"></script> |
| | | </body> |
| | | </html> |
| New file |
| | |
| | | import App from './App' |
| | | import uvUI from '@/uni_modules/uv-ui-tools' |
| | | import { Request } from '@/util/request' |
| | | |
| | | // #ifndef VUE3 |
| | | import Vue from 'vue' |
| | | import './uni.promisify.adaptor' |
| | | Vue.config.productionTip = false |
| | | Vue.use(uvUI); |
| | | App.mpType = 'app' |
| | | const app = new Vue({ |
| | | ...App |
| | | }) |
| | | app.$mount() |
| | | Request(app) |
| | | // #endif |
| | | |
| | | // #ifdef VUE3 |
| | | import { |
| | | createSSRApp |
| | | } from 'vue' |
| | | export function createApp() { |
| | | const app = createSSRApp(App) |
| | | app.use(uvUI); |
| | | Request(app) |
| | | return { |
| | | app |
| | | } |
| | | } |
| | | // #endif |
| New file |
| | |
| | | { |
| | | "name" : "ledApp", |
| | | "appid" : "__UNI__8C00436", |
| | | "description" : "", |
| | | "versionName" : "1.0.6", |
| | | "versionCode" : 106, |
| | | "transformPx" : false, |
| | | /* 5+App特有相关 */ |
| | | "app-plus" : { |
| | | "usingComponents" : true, |
| | | "nvueStyleCompiler" : "uni-app", |
| | | "compilerVersion" : 3, |
| | | "splashscreen" : { |
| | | "alwaysShowBeforeRender" : true, |
| | | "waiting" : true, |
| | | "autoclose" : true, |
| | | "delay" : 0 |
| | | }, |
| | | "screenOrientation" : [ "portrait-primary", "landscape-primary" ], |
| | | /* 模块配置 */ |
| | | "modules" : { |
| | | "Barcode" : {}, |
| | | "Geolocation" : {}, |
| | | "Bluetooth" : {}, |
| | | "Camera" : {} |
| | | }, |
| | | /* 应用发布信息 */ |
| | | "distribute" : { |
| | | /* android打包配置 */ |
| | | "android" : { |
| | | "permissions" : [ |
| | | "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
| | | "<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CAMERA\"/>", |
| | | "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
| | | "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
| | | "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
| | | "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
| | | "<uses-feature android:name=\"android.hardware.camera\"/>", |
| | | "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />", |
| | | "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />", |
| | | "<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" />", |
| | | "<uses-permission android:name=\"android.permission.BLUETOOTH\" />", |
| | | "<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" />", |
| | | "<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />", |
| | | "<uses-permission android:name=\"android.permission.BLUETOOTH_ADVERTISE\" />" |
| | | ], |
| | | "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], |
| | | "minSdkVersion" : 21, |
| | | "targetSdkVersion" : 31 |
| | | }, |
| | | /* ios打包配置 */ |
| | | "ios" : { |
| | | "privacyDescription" : { |
| | | "NSBluetoothAlwaysUsageDescription" : "本应用需要始终使用蓝牙功能", |
| | | "NSBluetoothPeripheralUsageDescription" : "需要使用蓝牙连接外设设备", |
| | | "NSLocationWhenInUseUsageDescription" : "蓝牙连接外设设备需要定位服务", |
| | | "NSLocationAlwaysUsageDescription" : "", |
| | | "NSLocationAlwaysAndWhenInUseUsageDescription" : "蓝牙连接外设设备需要定位服务" |
| | | }, |
| | | "dSYMs" : false, |
| | | "idfa" : false |
| | | }, |
| | | /* SDK配置 */ |
| | | "sdkConfigs" : { |
| | | "geolocation" : { |
| | | "system" : { |
| | | "__platform__" : [ "ios", "android" ] |
| | | } |
| | | } |
| | | }, |
| | | "icons" : { |
| | | "android" : { |
| | | "hdpi" : "unpackage/res/icons/72x72.png", |
| | | "xhdpi" : "unpackage/res/icons/96x96.png", |
| | | "xxhdpi" : "unpackage/res/icons/144x144.png", |
| | | "xxxhdpi" : "unpackage/res/icons/192x192.png" |
| | | }, |
| | | "ios" : { |
| | | "appstore" : "unpackage/res/icons/1024x1024.png", |
| | | "ipad" : { |
| | | "app" : "unpackage/res/icons/76x76.png", |
| | | "app@2x" : "unpackage/res/icons/152x152.png", |
| | | "notification" : "unpackage/res/icons/20x20.png", |
| | | "notification@2x" : "unpackage/res/icons/40x40.png", |
| | | "proapp@2x" : "unpackage/res/icons/167x167.png", |
| | | "settings" : "unpackage/res/icons/29x29.png", |
| | | "settings@2x" : "unpackage/res/icons/58x58.png", |
| | | "spotlight" : "unpackage/res/icons/40x40.png", |
| | | "spotlight@2x" : "unpackage/res/icons/80x80.png" |
| | | }, |
| | | "iphone" : { |
| | | "app@2x" : "unpackage/res/icons/120x120.png", |
| | | "app@3x" : "unpackage/res/icons/180x180.png", |
| | | "notification@2x" : "unpackage/res/icons/40x40.png", |
| | | "notification@3x" : "unpackage/res/icons/60x60.png", |
| | | "settings@2x" : "unpackage/res/icons/58x58.png", |
| | | "settings@3x" : "unpackage/res/icons/87x87.png", |
| | | "spotlight@2x" : "unpackage/res/icons/80x80.png", |
| | | "spotlight@3x" : "unpackage/res/icons/120x120.png" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }, |
| | | /* 快应用特有相关 */ |
| | | "quickapp" : {}, |
| | | /* 小程序特有相关 */ |
| | | "mp-weixin" : { |
| | | "appid" : "wx835fdaefed9491a4", |
| | | "setting" : { |
| | | "urlCheck" : false, |
| | | "minified" : true, |
| | | "postcss" : true, |
| | | "es6" : true, |
| | | "bluetooth" : true, |
| | | "request" : [ "scope.userLocation", "scope.userInfo" ] |
| | | }, |
| | | "usingComponents" : true, |
| | | "permission" : { |
| | | "scope.userLocation" : { |
| | | "desc" : "你的位置信息将用于蓝牙设备扫描" |
| | | } |
| | | } |
| | | }, |
| | | "mp-alipay" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-baidu" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "mp-toutiao" : { |
| | | "usingComponents" : true |
| | | }, |
| | | "uniStatistics" : { |
| | | "enable" : false |
| | | }, |
| | | "vueVersion" : "3" |
| | | } |
| New file |
| | |
| | | { |
| | | "pages": [{ |
| | | "path": "pages/index/index", |
| | | "style": { |
| | | "navigationBarTitleText": "设备" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/login/login", |
| | | "style": { |
| | | "navigationBarTitleText": "登陆" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/my/my", |
| | | "style": { |
| | | "navigationBarTitleText": "我的" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/template/template", |
| | | "style": { |
| | | "navigationBarTitleText": "模版" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/addDevice/addDevice", |
| | | "style": { |
| | | "navigationBarTitleText": "添加设备" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/invite/invite", |
| | | "style": { |
| | | "navigationBarTitleText": "" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/about/about", |
| | | "style": { |
| | | "navigationBarTitleText": "关于我们" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/feedback/feedback", |
| | | "style": { |
| | | "navigationBarTitleText": "反馈问题" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/set/set", |
| | | "style": { |
| | | "navigationBarTitleText": "设置" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/policy/policy", |
| | | "style": { |
| | | "navigationBarTitleText": "" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/addTemplate/addTemplate", |
| | | "style": { |
| | | "navigationBarTitleText": "添加模版" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/animation/animation", |
| | | "style": { |
| | | "navigationBarTitleText": "编辑动画", |
| | | "orientation": "landscape" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/register/register", |
| | | "style": { |
| | | "navigationBarTitleText": "" |
| | | } |
| | | }, |
| | | |
| | | { |
| | | "path": "pages/personal/personal", |
| | | "style": { |
| | | "navigationBarTitleText": "" |
| | | } |
| | | } |
| | | ], |
| | | "globalStyle": { |
| | | "navigationBarTextStyle": "black", |
| | | "navigationBarTitleText": "LEDAPP", |
| | | "navigationBarBackgroundColor": "#F8F8F8", |
| | | "backgroundColor": "#F8F8F8", |
| | | "navigationStyle": "custom" |
| | | }, |
| | | "tabBar": { |
| | | "color": "#757575", |
| | | "selectedColor": "#3086F3", |
| | | "borderStyle": "black", |
| | | "backgroundColor": "#ffffff", |
| | | "list": [{ |
| | | "pagePath": "pages/index/index", |
| | | "iconPath": "/static/img/ico_m1.png", |
| | | "selectedIconPath": "/static/img/ico_m1a.png", |
| | | "text": "设备" |
| | | }, |
| | | { |
| | | "pagePath": "pages/template/template", |
| | | "iconPath": "/static/img/ico_m2.png", |
| | | "selectedIconPath": "/static/img/ico_m2a.png", |
| | | "text": "模版" |
| | | }, |
| | | { |
| | | "pagePath": "pages/my/my", |
| | | "iconPath": "/static/img/ico_m3.png", |
| | | "selectedIconPath": "/static/img/ico_m3a.png", |
| | | "text": "我的" |
| | | } |
| | | ] |
| | | }, |
| | | "uniIdRouter": {} |
| | | } |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="关于我们" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="about"> |
| | | <rich-text :nodes="aboutData"></rich-text> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | content |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | aboutData: "" |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.getContent() |
| | | }, |
| | | methods: { |
| | | getContent() { |
| | | content(2).then(res => { |
| | | console.log(res.data) |
| | | this.aboutData = res.data.content.replace(/(?<=src=")\/prod-api/g, uni.$uv.http.config.baseURL) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | <style> |
| | | /deep/ rich-text img { |
| | | max-width: 100%; |
| | | height: auto; |
| | | } |
| | | </style> |
| | | <style lang="scss" scoped> |
| | | .about { |
| | | margin-top: 360rpx; |
| | | padding: 30rpx; |
| | | font-weight: 400; |
| | | font-size: 27rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 46rpx; |
| | | text-align: justified; |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_4.png') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view> |
| | | <uv-navbar title="添加设备" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="#3B74EE" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="page_bg" v-if="searchIng"> |
| | | <view class="searching_ico"> |
| | | <image src="/static/img/ico_1.png" mode="widthFix"></image> |
| | | </view> |
| | | |
| | | <view class="searching_text" v-if="isBluetooth && isLocation"> |
| | | <view class="name"> |
| | | 搜索中 |
| | | </view> |
| | | <view class="text"> |
| | | 持续自动搜索中... |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="searching_text" v-if="!isBluetooth"> |
| | | <view class="name"> |
| | | 蓝牙未开启 |
| | | </view> |
| | | <view class="text"> |
| | | 请手动开启系统蓝牙 |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="searching_text" v-if="!isLocation"> |
| | | <view class="name"> |
| | | 定位服务未开启 |
| | | </view> |
| | | <view class="text"> |
| | | 请手动开启系统定位服务 |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | |
| | | <view class="bluetooth" v-if="!searchIng"> |
| | | <view class="bluetooth_title"> |
| | | <image class="ico" src="/static/img/ico_2.png" mode="widthFix"></image> |
| | | <text>已找到设备</text> |
| | | </view> |
| | | <view class="bluetooth_list"> |
| | | <view class="item" v-for="(item,index) in bluetoothList" :key="index"> |
| | | <image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | {{item.name}} |
| | | </view> |
| | | <view class="btn" v-if="!isLink"> |
| | | <uv-button type="primary" shape="circle" text="连接" size="small" |
| | | @click="createBLEConnection(item)"></uv-button> |
| | | </view> |
| | | <!-- <view class="btn" v-if="!isLink"> |
| | | <uv-button type="primary" shape="circle" text="连接" size="small" |
| | | @click="createBLEConnection(item.deviceId)"></uv-button> |
| | | </view> --> |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="LIST" size="small" |
| | | @click="commandLIST()"></uv-button> |
| | | </view> --> |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="播放LED" size="small" |
| | | @click="this.$refs.popupLED.open();"></uv-button> |
| | | </view> --> |
| | | <view class="btn" v-if="isLink"> |
| | | <uv-button type="primary" shape="circle" text="发送LED" size="small" |
| | | @click="this.$refs.popupTemplete.open();"></uv-button> |
| | | </view> |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="获取画板数据" size="small" |
| | | @click="getDrawData()"></uv-button> |
| | | </view> --> |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="删除全部" size="small" |
| | | @click="commandDALL()"></uv-button> |
| | | </view> --> |
| | | <!-- <view class="btn" v-if="isLink"> |
| | | <uv-button type="primary" shape="circle" text="调节亮度" size="small" |
| | | @click="this.$refs.popupBRIG.open();"></uv-button> |
| | | </view> --> |
| | | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <!-- 设置密码弹出框 --> |
| | | <uv-popup ref="popupPwd" @change="changePopupPwd" mode="center" round="30rpx" :closeable="false" |
| | | :closeOnClickOverlay="false"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">请输入设备绑定的密码</view> |
| | | <view class="cont"> |
| | | <uv-input v-model="password" type="number" maxlength="4" placeholder="请输入4位数密码" /> |
| | | </view> |
| | | <view class="btns"> |
| | | <uv-button type="primary" :disabled="password.length !== 4" @click="confirmPwd">确定</uv-button> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 设置名称出框 --> |
| | | <uv-popup ref="popupName" @change="changePopupName" mode="center" round="30rpx" :closeable="false" |
| | | :closeOnClickOverlay="false"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">请输入设备名称</view> |
| | | <view class="cont"> |
| | | <uv-input v-model="deviceName" placeholder="请输入设备名称" /> |
| | | </view> |
| | | <view class="btns"> |
| | | <uv-button type="primary" :disabled="deviceName == ''" @click="confirmName">确定</uv-button> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 播放设备中文件列表 --> |
| | | <uv-popup ref="popupLED" mode="center" round="30rpx"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">发送LED</view> |
| | | <view class="cont cont_led"> |
| | | <view class="led_item" v-for="(item,index) in fileNames" :key="index"> |
| | | <view class="name"> |
| | | {{item.fileName}} |
| | | </view> |
| | | <!-- <view class="name"> |
| | | {{item.nameHex}} |
| | | </view> --> |
| | | <uv-button type="primary" @click="commandDELL(item.nameHex)">删除</uv-button> |
| | | <uv-button type="primary" @click="commandPLAY(item.nameHex)">播放</uv-button> |
| | | </view> |
| | | </view> |
| | | <view class="btns"> |
| | | <uv-button type="primary" plain @click="this.$refs.popupLED.close();">关闭</uv-button> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 调节亮度 --> |
| | | <uv-popup ref="popupBRIG" mode="center" round="30rpx"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">调节亮度</view> |
| | | <view class="cont"> |
| | | <uv-slider v-model="brightness" :min="0" :max="9" :step="1" block-size="24" block-color="#2979ff" |
| | | @changing="changeBrightness"></uv-slider> |
| | | </view> |
| | | <view class="btns_two"> |
| | | <view class="btn2"> |
| | | <uv-button type="primary" plain @click="this.$refs.popupBRIG.close();">关闭</uv-button> |
| | | </view> |
| | | <view class="btn2"> |
| | | <uv-button type="primary" @click="commandBRIG()">确定</uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 模版列表 --> |
| | | <uv-popup ref="popupTemplete" mode="center" round="30rpx"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">发送LED</view> |
| | | <view class="cont cont_led"> |
| | | <view class="led_item" v-for="(item,index) in templeteData" :key="index"> |
| | | <view class="name"> |
| | | {{item.name}} |
| | | </view> |
| | | <view class="name"> |
| | | {{item.screen}} |
| | | </view> |
| | | <uv-button type="primary" shape="circle" size="small" |
| | | @click="commandSendTemplete(item)">播放</uv-button> |
| | | </view> |
| | | </view> |
| | | <view class="btns"> |
| | | <uv-button type="primary" plain @click="this.$refs.popupTemplete.close();">关闭</uv-button> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | equipmentAdd, |
| | | templeteList |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | searchIng: true, |
| | | bluetoothList: [], //搜索到的蓝牙设备列表 |
| | | deviceId: '', //设备id |
| | | serviceId: '', //服务uuid |
| | | characteristicId1: '', //特征uuid |
| | | characteristicId2: '', |
| | | characteristicId3: '', |
| | | showPwdPopup: false, |
| | | password: '', //设置的密码 |
| | | checId: '', //智能壳的ID |
| | | batchNumber: '', //生产顺序号 |
| | | fileNames: [], //文件名称列表 |
| | | brightness: 3, //亮度 |
| | | nameIndex: 1, |
| | | name: '', |
| | | |
| | | ledName: '', // 名称 |
| | | screen: '', |
| | | ledTotal: 3, // 总帧数 |
| | | ledCurrent: 0, // 当前帧数 |
| | | ledData: [], |
| | | isLink: false, |
| | | deviceName: '', |
| | | versions: '', |
| | | isBluetooth: true, |
| | | lastBluetooth: null, |
| | | isLocation: true, |
| | | platform: '' |
| | | // ledData: [ |
| | | // '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', |
| | | // '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', |
| | | // '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF0000FF0000FF0000FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' |
| | | // ] |
| | | } |
| | | }, |
| | | onLoad() { |
| | | console.log('onLoad-----') |
| | | |
| | | }, |
| | | onShow() { |
| | | const systemInfo = uni.getSystemInfoSync(); |
| | | if (systemInfo.platform === 'ios') { |
| | | // iOS 设备 |
| | | this.platform = 'ios' |
| | | } else if (systemInfo.platform === 'android') { |
| | | // Android 设备 |
| | | this.platform = 'android' |
| | | } |
| | | console.log('onShow-----') |
| | | this.openBluetooth() |
| | | this.getTempleteList() |
| | | //this.checkBluetoothEnabled() |
| | | }, |
| | | onHide() { |
| | | console.log('onHide----aaa-') |
| | | uni.$off('BLECharacteristic') |
| | | this.closeBLEConnection() |
| | | this.closeBluetooth() |
| | | this.stopBluetoothDevices() |
| | | }, |
| | | onUnload() { |
| | | uni.$off('BLECharacteristic') |
| | | console.log('onUnload-----aaa') |
| | | this.closeBLEConnection() |
| | | this.closeBluetooth() |
| | | this.stopBluetoothDevices() |
| | | }, |
| | | methods: { |
| | | // 获取模版列表 |
| | | getTempleteList() { |
| | | templeteList({ |
| | | userId: uni.getStorageSync('userId'), |
| | | pageNum: 1, |
| | | pageSize: 10 |
| | | }).then(res => { |
| | | //console.log('模版列表-------', res.rows) |
| | | this.templeteData = res.rows |
| | | }) |
| | | }, |
| | | // 添加设备 |
| | | postEquipmentAdd() { |
| | | |
| | | //console.log('this.checId---', this.checId) |
| | | equipmentAdd({ |
| | | userId: uni.getStorageSync('userId'), |
| | | code: this.platform, |
| | | password: this.password, |
| | | name: this.deviceName, //设备名称 |
| | | checId: this.checId, //智能壳的ID |
| | | deviceId: this.deviceId, //设备id |
| | | serviceId: this.serviceId, //服务id |
| | | characteristicId1: this.characteristicId1, //特征id |
| | | characteristicId2: this.characteristicId2, |
| | | characteristicId3: this.characteristicId3, |
| | | versions: this.versions |
| | | }).then(res => { |
| | | console.log(res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if (res.code == 200) { |
| | | this.closeBLEConnection() |
| | | setTimeout(() => { |
| | | uni.switchTab({ |
| | | url: '/pages/index/index' |
| | | }); |
| | | }, 300); |
| | | } |
| | | }) |
| | | }, |
| | | changeBrightness(value) { |
| | | //console.log('changeBrightness----', value) |
| | | //this.brightness = value |
| | | }, |
| | | // 设置名称 |
| | | confirmName() { |
| | | this.$refs.popupName.close(); |
| | | this.commandQVER() |
| | | uni.showLoading({ |
| | | title: '添加中...', |
| | | mask: true |
| | | }); |
| | | //this.postEquipmentAdd() |
| | | }, |
| | | changePopupName(e) { |
| | | console.log(e) |
| | | }, |
| | | changePopupPwd(e) { |
| | | console.log(e.show) |
| | | if (e.show) { |
| | | this.password = '' |
| | | } else { |
| | | //this.closeBLEConnection() |
| | | } |
| | | }, |
| | | // 初始化蓝牙 |
| | | openBluetooth() { |
| | | uni.openBluetoothAdapter({ |
| | | success: (res) => { |
| | | console.log('蓝牙初始化成功'); |
| | | // uni.onBluetoothAdapterStateChange(function (res) { |
| | | // console.log('监听蓝牙适配器状态变化事件------', res) |
| | | // }) |
| | | this.isBluetooth = true |
| | | this.startBluetoothDevices() |
| | | }, |
| | | fail: (err) => { |
| | | //console.log('蓝牙初始化失败----', err); |
| | | if (err.code == 10001) { |
| | | console.log('蓝牙开关未开启----', err); |
| | | this.isBluetooth = false |
| | | |
| | | this.lastBluetooth = false |
| | | uni.onBluetoothAdapterStateChange(res => { |
| | | console.log('蓝牙状态变化:', res.available) |
| | | |
| | | // 仅当状态从 false 变成 true 时执行一次 |
| | | if (this.lastBluetooth == false && res.available == true) { |
| | | console.log('蓝牙开启,执行 openBluetooth()') |
| | | this.openBluetooth() |
| | | } |
| | | this.lastBluetooth = res.available |
| | | this.isBluetooth = res.available |
| | | }) |
| | | } |
| | | }, |
| | | complete: () => { |
| | | |
| | | } |
| | | }); |
| | | }, |
| | | // 关闭蓝牙 |
| | | closeBluetooth() { |
| | | uni.closeBluetoothAdapter({ |
| | | success: (res) => { |
| | | console.log(res) |
| | | } |
| | | }) |
| | | }, |
| | | //开始搜寻附近的蓝牙外围设备 |
| | | startBluetoothDevices() { |
| | | const that = this |
| | | uni.startBluetoothDevicesDiscovery({ |
| | | success: (res) => { |
| | | console.log('开始扫描设备', res); |
| | | that.onBluetoothDeviceFound(); |
| | | }, |
| | | fail: (err) => { |
| | | console.log('扫描失败', err); |
| | | if(err.code == 10016){ |
| | | //that.isLocation = false |
| | | const timerLocation = setInterval(function() { |
| | | const systemSetting = uni.getSystemSetting() |
| | | that.isLocation = systemSetting.locationEnabled |
| | | console.log("执行一次",this.isLocation,systemSetting.locationEnabled); |
| | | if(that.isLocation){ |
| | | clearInterval(timerLocation); |
| | | that.openBluetooth() |
| | | } |
| | | }, 500); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | // 停止搜寻附近的蓝牙外围设备 |
| | | stopBluetoothDevices() { |
| | | uni.stopBluetoothDevicesDiscovery({ |
| | | success: (res) => { |
| | | console.log(res) |
| | | }, |
| | | fail: (err) => { |
| | | console.log(err) |
| | | } |
| | | }); |
| | | }, |
| | | // 监听寻找到新设备的事件 |
| | | onBluetoothDeviceFound() { |
| | | uni.onBluetoothDeviceFound(res => { |
| | | //console.log('找到新设备', res); |
| | | res.devices.forEach((device) => { |
| | | if (device.name.includes('CH9140')) { |
| | | this.bluetoothList.push(device); |
| | | console.log('找到新设备----', this.bluetoothList); |
| | | this.stopBluetoothDevices() |
| | | setTimeout(() => { |
| | | this.searchIng = false |
| | | }, 500); |
| | | } |
| | | }); |
| | | }); |
| | | }, |
| | | // 连接低功耗蓝牙设备 |
| | | createBLEConnection(item) { |
| | | console.log('连接de蓝牙设备--------', item) |
| | | const that = this |
| | | uni.showLoading({ |
| | | title: '连接中...', |
| | | mask: true |
| | | }); |
| | | that.name = item.name |
| | | that.deviceId = item.deviceId |
| | | uni.createBLEConnection({ |
| | | deviceId: that.deviceId, |
| | | success: (res) => { |
| | | console.log('连接低功耗蓝牙设备', res); |
| | | setTimeout(() => { |
| | | //that.setBLEMTU() |
| | | that.getServices(); |
| | | }, 500); |
| | | }, |
| | | fail: (err) => { |
| | | console.log('连接失败', err); |
| | | this.closeBLEConnection() |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '连接失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | // 断开与低功耗蓝牙设备的连接 |
| | | closeBLEConnection() { |
| | | uni.closeBLEConnection({ |
| | | deviceId: this.deviceId, |
| | | success(res) { |
| | | console.log(res) |
| | | } |
| | | }) |
| | | }, |
| | | setBLEMTU() { |
| | | uni.setBLEMTU({ |
| | | deviceId: this.deviceId, |
| | | mtu: 80, // 最大值一般 512,蓝牙 4.2 通常支持 247 |
| | | success: (res) => { |
| | | console.log('设置MTU成功:', res); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('设置MTU失败:', err); |
| | | } |
| | | }); |
| | | }, |
| | | // 获取蓝牙设备所有服务 |
| | | getServices() { |
| | | console.log(this.deviceId) |
| | | uni.getBLEDeviceServices({ |
| | | deviceId: this.deviceId, |
| | | success: (res) => { |
| | | console.log('服务列表成功', res); |
| | | this.serviceId = res.services.find(item => item.uuid.toLowerCase().includes('fff0'))?.uuid; |
| | | this.getCharacteristics(); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('服务列表失败:', err); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '服务列表失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | // 获取蓝牙设备某个服务中所有特征值(characteristic) |
| | | getCharacteristics() { |
| | | uni.getBLEDeviceCharacteristics({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | success: (res) => { |
| | | console.log('特征列表', res); |
| | | this.characteristicId1 = res.characteristics[0].uuid |
| | | this.characteristicId2 = res.characteristics[1].uuid |
| | | this.characteristicId3 = res.characteristics[2].uuid |
| | | this.startNotify() |
| | | setTimeout(() => { |
| | | this.setBaud() |
| | | }, 500); |
| | | } |
| | | }); |
| | | }, |
| | | // 设置波特率 |
| | | setBaud() { |
| | | var that = this |
| | | var hexStr = "06000900009600000801009F" |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId3, |
| | | value: this.hexStringToArrayBuffer(hexStr), |
| | | writeType: 'write', |
| | | success: (res) => { |
| | | console.log('设置波特率成功----', res); |
| | | that.setBLEMTU() |
| | | that.commandCHEC() |
| | | }, |
| | | fail: (err) => { |
| | | console.log('设置波特率失败----', err); |
| | | } |
| | | }); |
| | | }, |
| | | // 开始监听 |
| | | startNotify() { |
| | | const that = this |
| | | uni.notifyBLECharacteristicValueChange({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId1, |
| | | state: true, |
| | | success: () => { |
| | | console.log('开始监听------'); |
| | | uni.$off('BLECharacteristic'); |
| | | uni.$on('BLECharacteristic',function(res){ |
| | | console.log('BLECharacteristic---------------cc', res) |
| | | const resData = that.ab2hex(res.value); |
| | | const result = that.removeStartBytes(resData) |
| | | const resName = that.getCommandType(result); |
| | | console.log("回调返回数据-------add", resData); |
| | | console.log('result-----------add', result) |
| | | console.log('resName----------add', resName) |
| | | uni.hideLoading(); |
| | | if (resName == 'CHEC') { |
| | | that.parseBLECHECResponse(result) |
| | | } else if (resName == 'BIND') { |
| | | that.$refs.popupName.open(); |
| | | } else if (resName == 'LIST') { |
| | | that.parseFileList(result) |
| | | } else if (resName == 'BRIG') { |
| | | |
| | | } else if (resName == 'PLAY') { |
| | | |
| | | } else if (resName == 'INST') { |
| | | // 添加动画帧 |
| | | that.ledCurrent++ |
| | | if (that.ledCurrent < that.ledTotal) { |
| | | uni.showLoading({ |
| | | title: '发送中' + that.ledCurrent, |
| | | mask: true |
| | | }); |
| | | that.commandINST(that.ledData[that.ledCurrent]); |
| | | } else if (that.ledCurrent == that.ledTotal) { |
| | | uni.showLoading({ |
| | | title: '发送结束帧', |
| | | mask: true |
| | | }); |
| | | that.commandEnd() |
| | | } |
| | | |
| | | } else if (resName == 'DALL') { |
| | | |
| | | } else if (resName == 'QVER') { |
| | | // 查询版本信息 |
| | | that.versions = parseInt(result.slice(-8, -6), 16); |
| | | that.postEquipmentAdd() |
| | | } else { |
| | | that.isLink == false |
| | | uni.showToast({ |
| | | title: '返回数据错误', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | // 关闭蓝牙设备 |
| | | that.closeBLEConnection() |
| | | } |
| | | }) |
| | | //uni.onBLECharacteristicValueChange(res => { |
| | | // const resData = that.ab2hex(res.value); |
| | | // const result = that.removeStartBytes(resData) |
| | | // const resName = that.getCommandType(result); |
| | | // console.log("回调返回数据-------add", resData); |
| | | // console.log('result-----------add', result) |
| | | // console.log('resName----------add', resName) |
| | | // uni.hideLoading(); |
| | | // if (resName == 'CHEC') { |
| | | // that.parseBLECHECResponse(result) |
| | | // } else if (resName == 'BIND') { |
| | | // that.$refs.popupName.open(); |
| | | // } else if (resName == 'LIST') { |
| | | // that.parseFileList(result) |
| | | // } else if (resName == 'BRIG') { |
| | | |
| | | // } else if (resName == 'PLAY') { |
| | | |
| | | // } else if (resName == 'INST') { |
| | | // // 添加动画帧 |
| | | // that.ledCurrent++ |
| | | // if (that.ledCurrent < that.ledTotal) { |
| | | // uni.showLoading({ |
| | | // title: '发送中' + that.ledCurrent, |
| | | // mask: true |
| | | // }); |
| | | // that.commandINST(that.ledData[that.ledCurrent]); |
| | | // } else if (that.ledCurrent == that.ledTotal) { |
| | | // uni.showLoading({ |
| | | // title: '发送结束帧', |
| | | // mask: true |
| | | // }); |
| | | // that.commandEnd() |
| | | // } |
| | | |
| | | // } else if (resName == 'DALL') { |
| | | |
| | | // } else if (resName == 'QVER') { |
| | | // // 查询版本信息 |
| | | // that.versions = parseInt(result.slice(-8, -6), 16); |
| | | // that.postEquipmentAdd() |
| | | // } else { |
| | | // that.isLink == false |
| | | // uni.showToast({ |
| | | // title: '返回数据错误', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // // 关闭蓝牙设备 |
| | | // that.closeBLEConnection() |
| | | // } |
| | | //}); |
| | | } |
| | | }); |
| | | }, |
| | | // 判断并去除10个0x55启动字节(20个hex字符) |
| | | removeStartBytes(hexStr) { |
| | | // 10个0x55 = 20个hex字符 |
| | | const startBytes = '55'.repeat(10); // '55555555555555555555' |
| | | if (hexStr.startsWith(startBytes)) { |
| | | return hexStr.slice(20); // 去掉前20个字符 |
| | | } |
| | | return hexStr; |
| | | }, |
| | | // 返回指令类型 判断 |
| | | getCommandType(hexString) { |
| | | // 判断是否有 返回标志 单字节。0(0x30),工作无异常;1(0x31)工作有异常 |
| | | // 判断是否包含返回标志 30 或 31 |
| | | // 查找30或31的位置 |
| | | let returnFlag = ''; |
| | | let cmd = ''; |
| | | let packageType = hexString.slice(2, 4); |
| | | |
| | | if (packageType == '41') { |
| | | returnFlag = hexString.slice(12, 14); |
| | | const before8Chars = hexString.slice(4, 12); |
| | | cmd = before8Chars.match(/.{2}/g) |
| | | .map(h => String.fromCharCode(parseInt(h, 16))) |
| | | .join(''); |
| | | return cmd; |
| | | } else if (packageType == '43') { |
| | | returnFlag = hexString.slice(28, 30); |
| | | const before8Chars = hexString.slice(20, 28); |
| | | cmd = before8Chars.match(/.{2}/g) |
| | | .map(h => String.fromCharCode(parseInt(h, 16))) |
| | | .join(''); |
| | | return cmd; |
| | | } |
| | | console.log('returnFlag------', returnFlag) |
| | | }, |
| | | // 查询智能壳ID |
| | | // 构建指令数据 |
| | | commandCHEC() { |
| | | //555555555555555555557e41434845430095977e |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x43, 0x48, 0x45, 0x43]; // "CHEC"的ASCII码 |
| | | const reserved = 0x00; // 保留字节 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | reserved |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('CHEC data----AAAA--------', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | // 加密绑定号 发送绑定号 |
| | | commandBIND() { |
| | | console.log('checId---', this.checId) |
| | | // 59 5C 37 39 71 4D 56 65 5A 37 12 18 07 06 2A 18 36 1E 1E 06 |
| | | // 7e 41 42 49 4e 44 6d 71 41 45 95 4e 61 78 9e 39 49 12 15 13 71 42 94 50 50 17 7c c9 7e |
| | | //帧头+0x41+ BIND +加密绑定号+CRC校验码+帧尾 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x42, 0x49, 0x4E, 0x44]; // "BIND"的ASCII码 |
| | | const reserved = this.replaceScreenSizeInID(this.checId, this.password); // 密码 |
| | | console.log('reserved----111', reserved) |
| | | const reservedId = this.encryptSmartShellID(reserved) |
| | | console.log('reservedid--222', reservedId) |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | ...this.hexStringToArray(reservedId) |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('BIND data----BBBB--------', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | //关屏指令(BLAC) |
| | | commandBLAC() { |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x42, 0x49, 0x4E, 0x44]; // "BLAC"的ASCII码 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('data----', data) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | // 查询版本信息 |
| | | commandQVER() { |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x51, 0x56, 0x45, 0x52]; // "QVER"的ASCII码 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('QVER----', data) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | //查询智能壳已存信息序列(LIST) |
| | | commandLIST() { |
| | | //1.手机发指令:启动字节+帧头+包头段(0x43+简易绑定号+屏大小)+LIST+信息属性+CRC校验码+帧尾。 |
| | | //2.智能壳指令:启动字节+帧头+包头段+LIST+返回标志+加密地址+文件数+[信息名1+信息名2+...+信息名n]+CRC校验码+帧尾。 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x4C, 0x49, 0x53, 0x54]; // "LIST"的ASCII码 |
| | | const infoAttribute = 0x00; // 信息属性: 0=一般信息 |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('LIST data----', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | //指定节目显示(PLAY) |
| | | commandPLAY(nameHex) { |
| | | console.log('nameHex----', nameHex, typeof nameHex) |
| | | //1.手机发指令:启动字节+帧头+0x41+ PLAY +信息属性+文件名+CRC校验码+帧尾。 |
| | | //2.智能壳指令:启动字节+帧头+0x41+ PLAY +返回标志+CRC校验码+帧尾 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x50, 0x4C, 0x41, 0x59]; // "PLAY"的ASCII码 |
| | | const infoAttribute = 0x00; // 信息属性: 0=一般信息 |
| | | //const fileName = [0x42, 0x48] //文件名称 |
| | | const fileName = this.hexStringToArray(nameHex) |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('PLAY data----', data) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | // 调节亮度 |
| | | commandBRIG() { |
| | | console.log('brightness----', this.brightness) |
| | | //1.手机发指令:启动字节+帧头+0x41+ BRIG +亮度+CRC校验码+帧尾。 |
| | | //2.智能壳回指令:启动字节+帧头+0x41+ BRIG +返回标志+CRC校验码+帧尾。 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x42, 0x52, 0x49, 0x47]; // "BRIG"的ASCII码 |
| | | |
| | | //亮度:单字节,0,1,2,3。。。ASIC码 |
| | | const brightness = this.stringToASCIIByteArray(this.brightness.toString()) |
| | | //const brightness = 0x32 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | brightness |
| | | ]; |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('BRIG data----', data) |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | |
| | | }, |
| | | commandSendTemplete(item) { |
| | | console.log('--------------', item) |
| | | uni.showLoading({ |
| | | title: '发送中...', |
| | | mask: true |
| | | }); |
| | | const name = 'A' + this.nameIndex++ |
| | | this.ledName = name |
| | | this.screen = item.screen |
| | | this.ledTotal = item.fpsTotal |
| | | this.ledCurrent = 0 |
| | | this.ledData = JSON.parse(item.fpsData) |
| | | this.commandINST(this.ledData[this.ledCurrent]); |
| | | }, |
| | | // 获取画板数据 |
| | | getDrawData() { |
| | | uni.showLoading({ |
| | | title: '发送中...', |
| | | mask: true |
| | | }); |
| | | const name = 'A' + this.nameIndex++ |
| | | this.ledName = name |
| | | console.log('name--------', name) |
| | | // uni.getStorage({ |
| | | // key: 'pixelData', |
| | | // success: (res) => { |
| | | // this.commandINST(res.data) |
| | | // } |
| | | // }); |
| | | this.commandINST(this.ledData[this.ledCurrent]); |
| | | }, |
| | | //添加信息指令(INST) |
| | | commandINST(hexString) { |
| | | // 1.手机发指令:启动+帧头+包头段(0x43+简易绑定号+屏大小)+INST+信息属性+文件名+总帧数+当前帧*+块描述+块数据+CRC校验码+帧尾 |
| | | //注意:当前帧如果是0xff,则表示是结束帧,后面四个字节,第一个字节(表示速度),第二个字节(播放次数,0表示一直播放),后面二个字节保留;其他数字表示表示当前是第几帧。 |
| | | //2.智能壳指令:启动+帧头+包头段+INST+返回标志+加密地址+CRC校验码+帧尾 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码 |
| | | const infoAttribute = 0x02; // 信息属性: 0=一般信息 |
| | | |
| | | const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称 |
| | | //console.log('fileName----', this.ledName) |
| | | // 总帧数 |
| | | const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0') |
| | | .toUpperCase()) //[0x01]; //this.hexStringToArray('01') |
| | | //console.log('totalFrame----', totalFrame) |
| | | // 当前帧 |
| | | const currentFrame = this.hexStringToArray(this.ledCurrent.toString(16).padStart(2, '0') |
| | | .toUpperCase()) //0x00 |
| | | |
| | | // 块描述 |
| | | const tblockDesc = 0x00 |
| | | |
| | | // 块数据 |
| | | const blockDescription = this.hexStringToArray(hexString) |
| | | //const blockString = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF0000FF000000000000000000000000000000000000000000000000000000000000FF0000000000000000000000000000000000000000000000FF0000000000000000FF0000FF00000000000000000000000000000000FF0000FF000000000000000000FF0000000000FF0000FF00FF0000FF0000FF000000000000000000FF00FF0000FF000000000000000000FF00000000000000000000FF000000FF0000FF0000FF0000FF0000000000FF0000000000000000000000FF0000000000000000000000FF0000FF0000000000000000FF0000FF0000000000000000000000000000FF00FF000000000000000000000000FF00FF0000FF000000000000000000FF00000000000000FF0000FF0000FF0000FF0000FF0000000000000000FF0000FF000000000000000000000000FF0000000000FF0000000000000000000000000000000000000000FF0000000000000000000000000000000000FF0000000000000000000000000000000000FF00000000000000000000000000FF0000FF000000FF0000FF0000000000000000000000FF00000000000000FF0000FF000000FF00FF0000FF000000000000FF0000000000000000000000000000000000000000000000FF0000000000FF0000000000000000000000FF0000000000000000000000000000000000000000FF0000000000000000FF0000000000000000FF0000000000000000FF00000000000000FF0000FF0000FF0000FF000000000000000000FF00000000000000FF0000FF0000FF0000FF0000FF000000000000000000000000000000000000FF0000FF0000000000000000FF0000000000000000FF0000000000000000FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| | | //const blockDescription = this.hexStringToArray(blockString) |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName, |
| | | ...totalFrame, |
| | | ...currentFrame, |
| | | tblockDesc, |
| | | ...blockDescription |
| | | ]; |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log(this.ledCurrent, '----INST data----', this.hexArrayToHexString(data)) |
| | | //const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | //this.writeBLECharacteristicValue(buffer) |
| | | this.sendBLEDataInChunks( |
| | | this.hexArrayToHexString(data) |
| | | ) |
| | | }, 1000); |
| | | }, |
| | | // 发送结束针 |
| | | commandEnd() { |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码 |
| | | const infoAttribute = 0x02; // 信息属性: 0=一般信息 |
| | | |
| | | const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称 |
| | | //console.log('fileName----', this.ledName) |
| | | // 总帧数 |
| | | const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0') |
| | | .toUpperCase()) //[0x01]; //this.hexStringToArray('01') |
| | | //console.log('totalFrame----', totalFrame) |
| | | |
| | | //结束幕: |
| | | const ffdata = this.hexStringToArray('ff10000000') |
| | | const endData = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName, |
| | | ...totalFrame, |
| | | ...ffdata |
| | | ] |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const endcrc = this.calcCRC16(endData.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | endData.push((endcrc >> 8) & 0xFF); // CRC高8位 |
| | | endData.push(endcrc & 0xFF); // CRC低8位 |
| | | endData.push(frameHeader); // 帧尾 |
| | | console.log('INST endData----', this.hexArrayToHexString(endData)) |
| | | |
| | | const endbuffer = new Uint8Array(endData).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(endbuffer) |
| | | }, 1000); |
| | | }, |
| | | async sendBLEDataInChunks(hexStr) { |
| | | const maxBytes = 60 |
| | | const interval = 50 |
| | | const chunks = this.splitHexData(hexStr, maxBytes) |
| | | |
| | | console.log(`📦 总共需要发送 ${chunks.length} 包,每包 ${maxBytes} 字节,间隔 ${interval}ms`) |
| | | |
| | | for (let i = 0; i < chunks.length; i++) { |
| | | const buffer = this.hexToBuffer(chunks[i]) |
| | | try { |
| | | await new Promise((resolve, reject) => { |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId2, |
| | | value: buffer, |
| | | success: (res) => { |
| | | console.log(`✅ 第 ${i + 1} 包发送成功`, res) |
| | | resolve() |
| | | }, |
| | | fail: (err) => { |
| | | console.error(`❌ 第 ${i + 1} 包发送失败`, err) |
| | | reject(err) |
| | | } |
| | | }) |
| | | }) |
| | | } catch (err) { |
| | | console.error(`🚫 发送中断,第 ${i + 1} 包发送失败`) |
| | | break |
| | | } |
| | | // 延迟下一包发送 |
| | | await new Promise(r => setTimeout(r, interval)) |
| | | } |
| | | console.log("🎉 数据发送完成") |
| | | // setTimeout(() => { |
| | | // uni.showLoading({ |
| | | // title: '发送中...', |
| | | // mask: true |
| | | // }); |
| | | // this.commandEnd() |
| | | // }, 500) |
| | | }, |
| | | // 删除单个 |
| | | commandDELL(nameHex) { |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x44, 0x45, 0x4C, 0x4C]; // "DELL"的ASCII码 |
| | | const infoAttribute = 0x00; // 信息属性: 0=一般信息 |
| | | //const fileName = [0x42, 0x48] //文件名称 |
| | | const fileName = this.hexStringToArray(nameHex) |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('DELL data----', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | // 删除全部 |
| | | commandDALL() { |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x44, 0x41, 0x4C, 0x4C]; // "DALL"的ASCII码 |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('DALL data----', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 1000); |
| | | }, |
| | | |
| | | // 生成简易绑定号 |
| | | generateSimpleBindId() { |
| | | if (!this.checId || !this.password) { |
| | | //console.error("生成简易绑定号失败:checId或password不可用"); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '请先连接设备', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | return; |
| | | } |
| | | // 从checId中提取生产顺序号(最后4字节,即8个字符) |
| | | const productionNumber = this.checId.slice(-8); // 生产顺序号 |
| | | // 生产顺序号+密码 |
| | | const simpleBindData = productionNumber + this.password; |
| | | // 用加密数字加密 |
| | | const encryptedSimpleBind = this.encryptSmartShellID(simpleBindData); |
| | | // 取前6字节作为简易绑定号 |
| | | const simpleBindId = this.hexStringToArray(encryptedSimpleBind.slice(0, 12)); |
| | | //console.log('简易绑定号生成----', simpleBindId); |
| | | return simpleBindId; |
| | | }, |
| | | |
| | | //向低功耗蓝牙设备特征值中写入二进制数据 |
| | | writeBLECharacteristicValue(buffer) { |
| | | const that = this |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId2, |
| | | value: buffer, |
| | | writeType: 'write', |
| | | success: (res) => { |
| | | console.log('指令发送成功2----', res); |
| | | }, |
| | | fail: (err) => { |
| | | console.log('指令发送失败2----', err); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '指令发送失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | //向低功耗蓝牙设备特征值中写入二进制数据2222222 |
| | | writeBLEValue(buffer) { |
| | | const that = this |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId3, |
| | | value: buffer, |
| | | writeType: 'write', |
| | | success: (res) => { |
| | | console.log('指令发送成功----3', res); |
| | | |
| | | }, |
| | | fail: (err) => { |
| | | console.log('指令发送失败----3', err); |
| | | } |
| | | }); |
| | | }, |
| | | // 将十六进制字符串转为 ArrayBuffer |
| | | hexToBuffer(hexStr) { |
| | | const typedArray = new Uint8Array(hexStr.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))) |
| | | return typedArray.buffer |
| | | }, |
| | | |
| | | // 将大十六进制数据切分为每段 maxBytes 大小(单位:字节) |
| | | splitHexData(hexStr, maxBytes = 100) { |
| | | const result = [] |
| | | for (let i = 0; i < hexStr.length; i += maxBytes * 2) { |
| | | result.push(hexStr.slice(i, i + maxBytes * 2)) |
| | | } |
| | | return result |
| | | }, |
| | | |
| | | // 获取文件名称文件数量 |
| | | parseFileList(hexString) { |
| | | // 文件数占1字节(2个十六进制字符 |
| | | const fileCountHex = hexString.slice(32, 34); |
| | | const fileCount = parseInt(fileCountHex, 16); |
| | | console.log('文件数:', fileCount); |
| | | |
| | | // 获取文件名称列表 |
| | | const fileNames = []; |
| | | let nameStartIndex = 34; // 文件数后开始 |
| | | |
| | | for (let i = 0; i < fileCount; i++) { |
| | | // 每个信息名占2个字节(4个十六进制字符) |
| | | const nameHex = hexString.slice(nameStartIndex, nameStartIndex + 4); |
| | | const nameBytes = nameHex.match(/.{2}/g); |
| | | if (nameBytes && nameBytes.length === 2) { |
| | | // 将两个字节转换为ASCII字符 |
| | | const char1 = String.fromCharCode(parseInt(nameBytes[0], 16)); |
| | | const char2 = String.fromCharCode(parseInt(nameBytes[1], 16)); |
| | | const fileName = char1 + char2; |
| | | fileNames.push({ |
| | | fileName: fileName, |
| | | nameHex: nameHex |
| | | }); |
| | | } |
| | | |
| | | nameStartIndex += 4; // 移动到下一个文件名 |
| | | } |
| | | console.log('文件名称列表:', fileNames); |
| | | this.fileNames = fileNames |
| | | }, |
| | | // 指令ASCII码转换 |
| | | stringToASCIIByteArray(str) { |
| | | return str.split('').map(char => char.charCodeAt(0)); |
| | | }, |
| | | //16进制数组 转 16进制字符串 |
| | | hexArrayToHexString(hexArray) { |
| | | return hexArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); |
| | | }, |
| | | // 16进制字符串转16进制数组 |
| | | hexStringToArray(hexStr) { |
| | | if (!hexStr) return []; |
| | | return hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16)); |
| | | }, |
| | | // 16进制数据转二进数据 |
| | | hexStringToArrayBuffer(hexStr) { |
| | | const bytes = new Uint8Array(hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16))); |
| | | return bytes.buffer; |
| | | }, |
| | | // 二进数据转16进制数据 |
| | | ab2hex(buffer) { |
| | | const hexArr = Array.prototype.map.call( |
| | | new Uint8Array(buffer), |
| | | function(bit) { |
| | | return ('00' + bit.toString(16)).slice(-2) |
| | | } |
| | | ) |
| | | return hexArr.join('') |
| | | }, |
| | | calcCRC16(data) { |
| | | // 创建查表 |
| | | const table = this.buildCRC16Table(); |
| | | let crc = 0; |
| | | // 计算CRC |
| | | for (let i = 0; i < data.length; i++) { |
| | | crc = ((crc << 8) & 0xFF00) ^ table[((crc >> 8) ^ data[i]) & 0xFF]; |
| | | } |
| | | return crc & 0xFFFF; |
| | | }, |
| | | buildCRC16Table() { |
| | | const table = new Array(256); |
| | | const poly = 0x1021; // CRC-CCITT多项式 |
| | | for (let i = 0; i < 256; i++) { |
| | | let nData = i << 8; |
| | | let nAccum = 0; |
| | | for (let j = 0; j < 8; j++) { |
| | | if ((nData ^ nAccum) & 0x8000) { |
| | | nAccum = (nAccum << 1) ^ poly; |
| | | } else { |
| | | nAccum <<= 1; |
| | | } |
| | | nData <<= 1; |
| | | } |
| | | table[i] = nAccum & 0xFFFF; |
| | | } |
| | | return table; |
| | | }, |
| | | |
| | | // 设置密码 |
| | | confirmPwd() { |
| | | console.log("✅ 用户密码:", this.password); |
| | | this.$refs.popupPwd.close(); |
| | | this.commandBIND() |
| | | uni.showLoading({ |
| | | title: '绑定中...', |
| | | mask: true |
| | | }); |
| | | }, |
| | | // 密码转16进制数组 |
| | | passwordToHexArray(pwd) { |
| | | if (!/^\d{4}$/.test(pwd)) throw new Error('密码必须是4位数字'); |
| | | return [ |
| | | parseInt(pwd.slice(0, 2), 10), |
| | | parseInt(pwd.slice(2, 4), 10) |
| | | ]; |
| | | }, |
| | | // 密码置换掉ID号中"屏的大小",加密下发 |
| | | replaceScreenSizeInID(originalHexString, password4Digit) { |
| | | // 密码4个数字,每二个合起来一个字节,如3472变成 0x34 0x72 |
| | | const high = password4Digit.slice(0, 2); // "34" |
| | | const low = password4Digit.slice(2, 4); // "72" |
| | | |
| | | // 替换屏幕大小字段(第 10、11 个字节,即第20-23个字符) |
| | | const before = originalHexString.slice(0, 20); |
| | | const after = originalHexString.slice(24); |
| | | |
| | | return before + high + low + after; |
| | | }, |
| | | // 提取智能壳加密ID |
| | | parseBLECHECResponse(hexStr) { |
| | | const encryptedID = hexStr.slice(14, -6); |
| | | console.log('加密id-------', typeof encryptedID, encryptedID); |
| | | if(!encryptedID){ |
| | | // uni.showToast({ |
| | | // title: '请设置手电筒模式并在手电筒闪烁时点击确认', |
| | | // icon: 'none', |
| | | // mask: true, |
| | | // duration: 2000 |
| | | // }); |
| | | this.closeBLEConnection() |
| | | //this.openBluetooth() |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: '请将设备设置手电筒模式并在手电筒闪烁时点击确认', |
| | | showCancel: false, |
| | | confirmText: '关闭', |
| | | success: function (res) { |
| | | if (res.confirm) { |
| | | console.log('用户点击确定'); |
| | | } |
| | | } |
| | | }); |
| | | return |
| | | } |
| | | const decryptId = this.decryptSmartShellID(encryptedID) |
| | | console.log('解密id-------', typeof decryptId, decryptId); |
| | | this.checId = decryptId |
| | | this.$refs.popupPwd.open(); |
| | | }, |
| | | // 解密函数 |
| | | decryptSmartShellID(encryptedHex) { |
| | | const key = '2021101236011119680215001413714294505017'; |
| | | const decrypted = []; |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = encryptedHex.substr(i * 2, 2); |
| | | const encryptedByte = parseInt(byteHex, 16); |
| | | const keyHex = key.substr(i * 2, 2) |
| | | const keyByte = parseInt(keyHex, 16); |
| | | const decryptedByte = (encryptedByte - keyByte); |
| | | decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | return decrypted.join(''); |
| | | }, |
| | | // 加密函数 |
| | | encryptSmartShellID(encryptedHex) { |
| | | const key = '2021101236011119680215001413714294505017'; |
| | | const decrypted = []; |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = encryptedHex.substr(i * 2, 2); |
| | | const encryptedByte = parseInt(byteHex, 16); |
| | | const keyHex = key.substr(i * 2, 2) |
| | | const keyByte = parseInt(keyHex, 16); |
| | | const decryptedByte = (encryptedByte + keyByte); |
| | | decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | return decrypted.join(''); |
| | | }, |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .btns_two { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 0 40rpx; |
| | | margin-top: 50rpx; |
| | | |
| | | .btn2 { |
| | | width: 47%; |
| | | } |
| | | } |
| | | |
| | | .cont_led { |
| | | max-height: 500rpx; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .popup_pwd { |
| | | width: 660rpx; |
| | | box-sizing: border-box; |
| | | padding: 0 50rpx 50rpx; |
| | | |
| | | .led_item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 10rpx 0; |
| | | |
| | | .name { |
| | | font-size: 30rpx; |
| | | color: #000000; |
| | | line-height: 50rpx; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | padding-right: 40rpx; |
| | | } |
| | | |
| | | .btn { |
| | | flex-shrink: 0; |
| | | width: 120rpx; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | text-align: center; |
| | | font-weight: 500; |
| | | font-size: 32rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 42rpx; |
| | | padding: 50rpx 0 40rpx; |
| | | } |
| | | |
| | | .btns { |
| | | margin-top: 40rpx; |
| | | } |
| | | } |
| | | |
| | | .bluetooth { |
| | | padding: 20rpx 30rpx 0; |
| | | |
| | | .bluetooth_list { |
| | | .item { |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.2), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 22rpx; |
| | | display: flex; |
| | | align-items: stretch; |
| | | justify-content: space-between; |
| | | margin-top: 24rpx; |
| | | |
| | | .ph { |
| | | display: block; |
| | | flex-shrink: 0; |
| | | width: 230rpx; |
| | | height: 170rpx; |
| | | border-radius: 20rpx; |
| | | margin-right: 24rpx; |
| | | } |
| | | |
| | | .info { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 27rpx; |
| | | color: #000000; |
| | | line-height: 50rpx; |
| | | } |
| | | |
| | | .btn { |
| | | width: 200rpx; |
| | | margin-top: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .bluetooth_title { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20rpx; |
| | | |
| | | text { |
| | | display: block; |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 34rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 38rpx; |
| | | } |
| | | |
| | | .ico { |
| | | flex-shrink: 0; |
| | | width: 40rpx; |
| | | margin-right: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .searching_text { |
| | | text-align: center; |
| | | background: url('/static/img/bg_3.png') no-repeat center top; |
| | | background-size: 100% auto; |
| | | box-sizing: border-box; |
| | | padding-top: 140rpx; |
| | | margin-top: -100rpx; |
| | | height: 300rpx; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 36rpx; |
| | | color: #000000; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .text { |
| | | font-size: 26rpx; |
| | | color: rgba(0, 0, 0, 0.5); |
| | | line-height: 34rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | } |
| | | |
| | | .searching_ico { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding-top: 100rpx; |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | background: linear-gradient(3deg, #72E5FF 0%, #3B74EE 98%, #3B74EE 100%); |
| | | } |
| | | |
| | | .page_bg1 { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view> |
| | | <uv-navbar title="添加设备" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="#3B74EE" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="page_bg"> |
| | | <view class="searching_ico" @click="getBluetoothDevices"> |
| | | <image src="/static/img/ico_1.png" mode="widthFix"></image> |
| | | </view> |
| | | |
| | | <view class="searching_text"> |
| | | <view class="name"> |
| | | 搜索中 |
| | | </view> |
| | | <view class="text"> |
| | | 持续自动搜索中... |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="bluetooth" v-if="bluetoothList.length != 0"> |
| | | <view class="bluetooth_title"> |
| | | <image class="ico" src="/static/img/ico_2.png" mode="widthFix"></image> |
| | | <text>已找到设备</text> |
| | | </view> |
| | | <view class="bluetooth_list"> |
| | | <view class="item" v-for="(item,index) in bluetoothList" :key="index"> |
| | | <image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | {{item.device.name}} |
| | | </view> |
| | | <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="连接" size="small" |
| | | @click="createBLEConnection(item.device.address)"></uv-button> |
| | | |
| | | </view> |
| | | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | BleLib |
| | | } from '@/uni_modules/android-ble' |
| | | var lib = new BleLib() |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | searchIng: true, |
| | | bluetoothList: [], |
| | | deviceId: '', //设备id |
| | | serviceId: '', //服务uuid |
| | | characteristicId: '', //特征uuid |
| | | characteristicId1: '', |
| | | characteristicId2: '', |
| | | characteristicId3: '', |
| | | bluetooth: false |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | onShow() { |
| | | this.bluetooth = lib.isEnabled(); |
| | | if (this.bluetooth) { |
| | | console.log('蓝牙开启', this.bluetooth) |
| | | this.getBluetoothDevices() |
| | | } else { |
| | | console.log('蓝牙关闭', this.bluetooth) |
| | | } |
| | | }, |
| | | onUnload() { |
| | | //lib.close(); |
| | | }, |
| | | methods: { |
| | | // 蓝牙扫描 |
| | | getBluetoothDevices() { |
| | | console.log('蓝牙扫描----') |
| | | const that = this |
| | | var isconnect = lib.isConnected(); |
| | | console.log('蓝牙是否连接----',isconnect) |
| | | if(isconnect){ |
| | | console.log('蓝牙是否连接----',isconnect) |
| | | var device = lib.getConnectMac(); |
| | | console.log('device----',device) |
| | | } |
| | | lib.startScanBleDevice(15000, function(res) { |
| | | if (res.data.device.name === 'CH9140BLE2U') { |
| | | that.bluetoothList.push(res.data); |
| | | console.log(res.data) |
| | | } |
| | | }) |
| | | }, |
| | | // 连接蓝牙 |
| | | createBLEConnection(address) { |
| | | console.log('address-----',address) |
| | | const that = this |
| | | const isconnect = lib.isConnected(); |
| | | console.log('蓝牙是否连接----',isconnect) |
| | | // if(isconnect){ |
| | | // const device = lib.getConnectMac(); |
| | | // console.log('获取已连接设备id----',device) |
| | | // that.commandCHEC() |
| | | // } |
| | | // else{ |
| | | lib.connect(address, false, function(res) { |
| | | console.log('连接', res) |
| | | // type==0 表示连接成功 |
| | | if (res.type == 0) { |
| | | // 扫描蓝牙与特征值 |
| | | lib.scanServices(function(resSevice) { |
| | | console.log('特征值--------', resSevice) |
| | | that.characteristicId1 = resSevice.data[2].characteristics[0].uuid; |
| | | that.characteristicId2 = resSevice.data[2].characteristics[1].uuid; |
| | | that.characteristicId3 = resSevice.data[2].characteristics[2].uuid; |
| | | that.onNotityReadBleData() |
| | | setTimeout(() => { |
| | | console.log("延迟执行这段代码"); |
| | | that.setBaud() |
| | | //that.commandCHEC() |
| | | }, 2000); |
| | | }); |
| | | // lib.setMtu(512, function(resMtu) { |
| | | // console.log('设置mtu(仅安卓)', resMtu) |
| | | // }) |
| | | |
| | | |
| | | } else { |
| | | if(res.message == 'bluetooth is connected'){ |
| | | //that.commandCHEC() |
| | | } |
| | | else{ |
| | | uni.showToast({ |
| | | title: '蓝牙连接失败', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | // } |
| | | }, |
| | | // 开启服务消息通知 |
| | | onNotityReadBleData() { |
| | | const that = this |
| | | console.log(that.characteristicId1) |
| | | lib.onNotityReadBleData(lib.getSericUUID(), |
| | | that.characteristicId1, |
| | | true, |
| | | function(res) { |
| | | console.log('开启服务消息通知', res.data) |
| | | |
| | | uni.$uv.debounce(that.extractEncryptedIDWithCheck(res.data), 2000) |
| | | } |
| | | ) |
| | | }, |
| | | // 构建指令数据 |
| | | commandCHEC() { |
| | | //555555555555555555557e41434845430095977e |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x43, 0x48, 0x45, 0x43]; // "CHEC"的ASCII码 |
| | | const reserved = 0x00; // 保留字节 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | reserved |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | |
| | | setTimeout(() => { |
| | | this.writeDataToBle(data) |
| | | }, 2000); |
| | | }, |
| | | // 设置波特率 |
| | | setBaud() { |
| | | var that = this |
| | | var d = "06000900009600000801009F" |
| | | lib.writeStringDataToBle( |
| | | lib.getSericUUID(), |
| | | that.characteristicId2, |
| | | d, |
| | | function(res) { |
| | | console.log('设置波特率', res) |
| | | that.commandCHEC() |
| | | } |
| | | ) |
| | | }, |
| | | |
| | | // 发送写入指令 |
| | | writeDataToBle(b) { |
| | | var that = this |
| | | lib.writeDataToBle( |
| | | lib.getSericUUID(), |
| | | that.characteristicId2, |
| | | b, |
| | | function(res) { |
| | | console.log('发送16进制数组', res) |
| | | |
| | | } |
| | | ) |
| | | }, |
| | | writeStringDataToBle() { |
| | | var that = this |
| | | var d = "555555555555555555557e41434845430095977e" |
| | | lib.writeStringDataToBle( |
| | | lib.getSericUUID(), |
| | | that.characteristicId2, |
| | | d, |
| | | function(res) { |
| | | console.log('发送16进制字符数据', res) |
| | | } |
| | | ) |
| | | }, |
| | | // 16进制数据转二位数据 |
| | | hexStringToArrayBuffer(hexStr) { |
| | | const bytes = new Uint8Array(hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16))); |
| | | return bytes.buffer; |
| | | }, |
| | | // 二位数据转16进制数据 |
| | | ab2hex(buffer) { |
| | | const hexArr = Array.prototype.map.call( |
| | | new Uint8Array(buffer), |
| | | function(bit) { |
| | | return ('00' + bit.toString(16)).slice(-2) |
| | | } |
| | | ) |
| | | return hexArr.join('') |
| | | }, |
| | | calcCRC16(data) { |
| | | // 创建查表 |
| | | const table = this.buildCRC16Table(); |
| | | let crc = 0; |
| | | |
| | | // 计算CRC |
| | | for (let i = 0; i < data.length; i++) { |
| | | crc = ((crc << 8) & 0xFF00) ^ table[((crc >> 8) ^ data[i]) & 0xFF]; |
| | | } |
| | | |
| | | return crc & 0xFFFF; |
| | | }, |
| | | buildCRC16Table() { |
| | | const table = new Array(256); |
| | | const poly = 0x1021; // CRC-CCITT多项式 |
| | | |
| | | for (let i = 0; i < 256; i++) { |
| | | let nData = i << 8; |
| | | let nAccum = 0; |
| | | |
| | | for (let j = 0; j < 8; j++) { |
| | | if ((nData ^ nAccum) & 0x8000) { |
| | | nAccum = (nAccum << 1) ^ poly; |
| | | } else { |
| | | nAccum <<= 1; |
| | | } |
| | | nData <<= 1; |
| | | } |
| | | table[i] = nAccum & 0xFFFF; |
| | | } |
| | | return table; |
| | | }, |
| | | // 判断返回数据是否正确 并提取加密id |
| | | extractEncryptedIDWithCheck(buffer) { |
| | | const hexArr = Array.prototype.map.call( |
| | | new Uint8Array(buffer), |
| | | function(bit) { |
| | | return ('00' + bit.toString(16)).slice(-2) |
| | | } |
| | | ) |
| | | const responseHex = hexArr.join('').slice(20) |
| | | console.log('responseHex---',responseHex) |
| | | // 1. 提取返回标志位置(第11字节,即下标:30~31) |
| | | const returnFlagHex = responseHex.substring(12, 14).toUpperCase(); |
| | | console.log('returnFlagHex---',returnFlagHex) |
| | | if (returnFlagHex === "30") { |
| | | // 正常,提取加密ID(从下标 32 开始,取 40 位 |
| | | const encryptedID = responseHex.substring(14, 55).toUpperCase(); |
| | | console.log('encryptedID---',encryptedID) |
| | | uni.showToast({ |
| | | title: encryptedID, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } else if (returnFlagHex === "31") { |
| | | // 异常,无加密ID |
| | | uni.showToast({ |
| | | title: '智能壳工作异常,返回标志0x31', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | lib.close(); |
| | | } else { |
| | | // 未知标志 |
| | | uni.showToast({ |
| | | title: '智能壳工作异常,未知返回标志', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | lib.close(); |
| | | } |
| | | }, |
| | | // 解密函数 |
| | | decryptSmartShellID(encryptedHex) { |
| | | if (encryptedHex.length !== 40) { |
| | | console.error('加密ID长度必须为40个十六进制字符(20字节)'); |
| | | return null; |
| | | } |
| | | |
| | | const key = [ |
| | | 0x14, 0x15, 0x0A, 0x0C, 0x24, 0x01, 0x0B, 0x13, 0x44, 0x02, |
| | | 0x0F, 0x00, 0x0E, 0x0D, 0x47, 0x2A, 0x5E, 0x32, 0x32, 0x11 |
| | | ]; |
| | | |
| | | const decrypted = []; |
| | | |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = encryptedHex.substr(i * 2, 2); |
| | | const encryptedByte = parseInt(byteHex, 16); |
| | | const decryptedByte = (encryptedByte - key[i] + 256) % 256; |
| | | decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | |
| | | return decrypted.join(' '); |
| | | }, |
| | | // 加密函数 |
| | | encryptSmartShellID(originalHex) { |
| | | if (originalHex.length !== 40) { |
| | | console.error('原始ID长度必须为40位十六进制字符串(20字节)'); |
| | | return null; |
| | | } |
| | | |
| | | // 加密密钥(20 字节) |
| | | const key = [ |
| | | 0x14, 0x15, 0x0A, 0x0C, 0x24, 0x01, 0x0B, 0x13, 0x44, 0x02, |
| | | 0x0F, 0x00, 0x0E, 0x0D, 0x47, 0x2A, 0x5E, 0x32, 0x32, 0x11 |
| | | ]; |
| | | |
| | | const encrypted = []; |
| | | |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = originalHex.substr(i * 2, 2); |
| | | const originalByte = parseInt(byteHex, 16); |
| | | const encryptedByte = (originalByte + key[i]) % 256; // 忽略进位 |
| | | encrypted.push(encryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | |
| | | return encrypted.join(''); |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .bluetooth { |
| | | padding: 0 30rpx; |
| | | |
| | | .bluetooth_list { |
| | | .item { |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 22rpx; |
| | | display: flex; |
| | | align-items: stretch; |
| | | justify-content: space-between; |
| | | margin-top: 24rpx; |
| | | |
| | | .ph { |
| | | display: block; |
| | | flex-shrink: 0; |
| | | width: 230rpx; |
| | | height: 170rpx; |
| | | border-radius: 20rpx; |
| | | margin-right: 24rpx; |
| | | } |
| | | |
| | | .info { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 27rpx; |
| | | color: #000000; |
| | | line-height: 50rpx; |
| | | } |
| | | |
| | | .btn { |
| | | width: 160rpx; |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .bluetooth_title { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-top: 20rpx; |
| | | |
| | | text { |
| | | display: block; |
| | | width: 100%; |
| | | font-weight: 500; |
| | | font-size: 34rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 38rpx; |
| | | } |
| | | |
| | | .ico { |
| | | flex-shrink: 0; |
| | | width: 40rpx; |
| | | margin-right: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .searching_text { |
| | | text-align: center; |
| | | background: url('/static/img/bg_3.png') no-repeat center top; |
| | | background-size: 100% auto; |
| | | box-sizing: border-box; |
| | | padding-top: 140rpx; |
| | | margin-top: -100rpx; |
| | | height: 300rpx; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 36rpx; |
| | | color: #000000; |
| | | line-height: 34rpx; |
| | | } |
| | | |
| | | .text { |
| | | font-size: 26rpx; |
| | | color: rgba(0, 0, 0, 0.5); |
| | | line-height: 34rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | } |
| | | |
| | | .searching_ico { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding-top: 100rpx; |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | background: linear-gradient(3deg, #72E5FF 0%, #3B74EE 98%, #3B74EE 100%); |
| | | } |
| | | |
| | | .page_bg1 { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="新增模版" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 封面图片 |
| | | </view> |
| | | <view class="item item_pic"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | :previewFullImage="false" @afterRead="afterRead" @delete="deletePic"> |
| | | </uv-upload> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column" @change="changeSpec"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="clickGoAnim(index)" shape="circle" size="small" |
| | | color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | |
| | | |
| | | <!-- <view class="temp_btn" @click="clickGoAnim"> |
| | | 编辑动画 |
| | | </view> --> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" |
| | | @click="clickTempleteAdd()"></uv-button> |
| | | <uv-button text="测试" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" |
| | | style="margin-top: 30rpx;" @click="clickTempleteAdd2()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | templeteAdd |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1 |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | }, |
| | | onShow() { |
| | | console.log('------onShow') |
| | | //const fpsParams = uni.getStorageSync('fpsParams'); |
| | | // if (fpsParams) { |
| | | // console.log('收到回传参数:', fpsParams); |
| | | // this.fpsData[fpsParams.fpsIndex] = fpsParams.data; |
| | | // uni.removeStorageSync('fpsParams'); |
| | | // } |
| | | const that = this |
| | | uni.getStorage({ |
| | | key: 'fpsParams', |
| | | success: (res) => { |
| | | console.log('获取成功:', res.data); |
| | | that.fpsData[res.data.fpsIndex] = res.data.data; |
| | | }, |
| | | fail: (err) => { |
| | | console.error('获取失败:', err); |
| | | } |
| | | }); |
| | | }, |
| | | methods: { |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 24) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | clickTempleteAdd2() { |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | |
| | | templeteAdd({ |
| | | name: '', |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: '', //屏幕 |
| | | fpsTotal: '', //总帧数 |
| | | speed: '', //速度 |
| | | pic: '', |
| | | fpsData: '' |
| | | }).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | }) |
| | | |
| | | }, |
| | | clickTempleteAdd() { |
| | | if (!this.tempName) { |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fileList1.length == 0) { |
| | | uni.showToast({ |
| | | title: '请上传封面图片', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fpsData.length == 0) { |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | //console.log('fpsData------------', this.fpsData) |
| | | // 循环校验每一帧是否完整 |
| | | // let missingIndex = -1; |
| | | // for (let i = 0; i < this.total; i++) { |
| | | // if (this.fpsData[i] === null || this.fpsData[i] === undefined) { |
| | | // missingIndex = i; |
| | | // break; |
| | | // } |
| | | // } |
| | | // if (missingIndex !== -1) { |
| | | // uni.showToast({ |
| | | // title: '请编辑第' + (missingIndex + 1) + '帧', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // return; |
| | | // } |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | // 动画方式 |
| | | if (this.modeVal == 0) { //一帧动画 |
| | | let frames = this.generateFrames(this.fpsData[0], 24); |
| | | const parsedData = frames.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = 24 |
| | | } else if (this.modeVal == 1) { |
| | | const parsedData = this.fpsData.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = this.total |
| | | } |
| | | |
| | | // setTimeout(() => { |
| | | |
| | | // }, 1000) |
| | | const addData = { |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData) |
| | | } |
| | | console.log('addData-----', addData) |
| | | templeteAdd(addData).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | |
| | | }, |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | // this.fpsData = [] |
| | | // for (let i = 0; i < this.total; i++) { |
| | | // this.fpsData.push(null); |
| | | // } |
| | | this.adjustFpsData() |
| | | console.log('fpsData------------', this.fpsData) |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | clickGoAnim(index) { |
| | | uni.navigateTo({ |
| | | url: '/pages/animation/animation?fps=' + index |
| | | }) |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data).fileName) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .footer_none { |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .footer_btn { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | } |
| | | |
| | | .temp_btn { |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | |
| | | .spec_radio { |
| | | margin-top: 30rpx; |
| | | |
| | | &:first-child { |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .temp { |
| | | padding: 0 30rpx; |
| | | |
| | | .temp_item1 { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | |
| | | .item {} |
| | | } |
| | | |
| | | .temp_item { |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .item { |
| | | margin-top: 20rpx; |
| | | |
| | | &.item_pic { |
| | | margin-top: 25rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="新增模版" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 封面图片 |
| | | </view> |
| | | <view class="item item_pic"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | :previewFullImage="false" @afterRead="afterRead" @delete="deletePic"> |
| | | </uv-upload> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column" @change="changeSpec"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="clickGoAnim(index)" shape="circle" size="small" |
| | | color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | |
| | | |
| | | <!-- <view class="temp_btn" @click="clickGoAnim"> |
| | | 编辑动画 |
| | | </view> --> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" |
| | | @click="clickTempleteAdd()"></uv-button> |
| | | <uv-button text="测试" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" |
| | | style="margin-top: 30rpx;" @click="clickTempleteAdd2()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | templeteAdd |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1 |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | }, |
| | | onShow() { |
| | | console.log('------onShow') |
| | | //const fpsParams = uni.getStorageSync('fpsParams'); |
| | | // if (fpsParams) { |
| | | // console.log('收到回传参数:', fpsParams); |
| | | // this.fpsData[fpsParams.fpsIndex] = fpsParams.data; |
| | | // uni.removeStorageSync('fpsParams'); |
| | | // } |
| | | const that = this |
| | | uni.getStorage({ |
| | | key: 'fpsParams', |
| | | success: (res) => { |
| | | console.log('获取成功:', res.data); |
| | | that.fpsData[res.data.fpsIndex] = res.data.data; |
| | | }, |
| | | fail: (err) => { |
| | | console.error('获取失败:', err); |
| | | } |
| | | }); |
| | | }, |
| | | methods: { |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 24) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | clickTempleteAdd2() { |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | |
| | | templeteAdd({ |
| | | name: '', |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: '', //屏幕 |
| | | fpsTotal: '', //总帧数 |
| | | speed: '', //速度 |
| | | pic: '', |
| | | fpsData: '' |
| | | }).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | }) |
| | | |
| | | }, |
| | | clickTempleteAdd() { |
| | | if (!this.tempName) { |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fileList1.length == 0) { |
| | | uni.showToast({ |
| | | title: '请上传封面图片', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fpsData.length == 0) { |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | //console.log('fpsData------------', this.fpsData) |
| | | // 循环校验每一帧是否完整 |
| | | // let missingIndex = -1; |
| | | // for (let i = 0; i < this.total; i++) { |
| | | // if (this.fpsData[i] === null || this.fpsData[i] === undefined) { |
| | | // missingIndex = i; |
| | | // break; |
| | | // } |
| | | // } |
| | | // if (missingIndex !== -1) { |
| | | // uni.showToast({ |
| | | // title: '请编辑第' + (missingIndex + 1) + '帧', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // return; |
| | | // } |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | // 动画方式 |
| | | if (this.modeVal == 0) { //一帧动画 |
| | | let frames = this.generateFrames(this.fpsData[0], 24); |
| | | const parsedData = frames.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = 24 |
| | | } else if (this.modeVal == 1) { |
| | | const parsedData = this.fpsData.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = this.total |
| | | } |
| | | |
| | | // setTimeout(() => { |
| | | |
| | | // }, 1000) |
| | | const addData = { |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData) |
| | | } |
| | | console.log('addData-----', addData) |
| | | templeteAdd(addData).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | |
| | | }, |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | // this.fpsData = [] |
| | | // for (let i = 0; i < this.total; i++) { |
| | | // this.fpsData.push(null); |
| | | // } |
| | | this.adjustFpsData() |
| | | console.log('fpsData------------', this.fpsData) |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | clickGoAnim(index) { |
| | | uni.navigateTo({ |
| | | url: '/pages/animation/animation?fps=' + index |
| | | }) |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data).fileName) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .footer_none { |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .footer_btn { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | } |
| | | |
| | | .temp_btn { |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | |
| | | .spec_radio { |
| | | margin-top: 30rpx; |
| | | |
| | | &:first-child { |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .temp { |
| | | padding: 0 30rpx; |
| | | |
| | | .temp_item1 { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | |
| | | .item {} |
| | | } |
| | | |
| | | .temp_item { |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .item { |
| | | margin-top: 20rpx; |
| | | |
| | | &.item_pic { |
| | | margin-top: 25rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | |
| | | <uv-navbar :title="titleName" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 封面图片 |
| | | </view> |
| | | <view class="item item_pic"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | :previewFullImage="false" @afterRead="afterRead" @delete="deletePic"> |
| | | <!-- <image v-if="fileList1.length != 0" |
| | | :src="baseURL + fileList1[0]?.url" |
| | | mode="aspectFill" |
| | | style="width: 200rpx;height: 200rpx;" |
| | | ></image> |
| | | <view class="item_pic_btn" v-else> |
| | | <uv-icon name="camera-fill" color="#ccc" size="60rpx"></uv-icon> |
| | | </view> --> |
| | | </uv-upload> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column" @change="changeSpec"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="popupAnimationOpen(index)" shape="circle" size="small" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" @click="clickTempleteAdd()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | <!-- 动画弹出框 --> |
| | | <uv-popup ref="popupAnimation" mode="center" @change="changeAnimation" :safeAreaInsetTop="false" :safeAreaInsetBottom="false" :zoom="false" duration="0"> |
| | | <view class="animation_pixel"> |
| | | <view class="back_btn" @click="popupAnimationClose"> |
| | | <uv-icon name="arrow-leftward" color="#333" size="30"></uv-icon> |
| | | </view> |
| | | <!-- 动画按钮 --> |
| | | <view class="controls"> |
| | | <uv-button type="primary" :plain="true" text="分辨率" @click="changeRatio()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="颜色" @click="changeColor()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="添加文字" @click="addText()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="清除" @click="clear()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="保存" @click="clickSaveData"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 文字生成图形 --> |
| | | <canvas canvas-id="myCanvas" id="myCanvas" class="canvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas> |
| | | |
| | | <view class="pixel_grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove" |
| | | @touchend.prevent="handleTouchEnd" @touchcancel.prevent="handleTouchEnd" |
| | | :style="{ |
| | | gridTemplateRows: `repeat(${height}, 1fr)`, |
| | | gridTemplateColumns: `repeat(${width}, 1fr)` |
| | | }"> |
| | | <view class="pixel_cell" v-for="(cell, cellIndex) in pixelData" :key="cellIndex"> |
| | | <view class="pixel_cell_item" :style="{ backgroundColor: cell }"></view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 分辨率 --> |
| | | <uv-picker ref="pickerRatio" :columns="columnsRatio" @confirm="confirmRatio"></uv-picker> |
| | | |
| | | <!-- 颜色 --> |
| | | <uv-popup ref="pickerColor" mode="bottom" round="10" :closeable="true"> |
| | | <view class="color_list"> |
| | | <view class="color_item" v-for="(item, index) in colorList" :class="{ hover: color === item }" :key="index" :style="{ backgroundColor: item }" @click="clickColor(item)"></view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | templeteAdd, |
| | | templeteInfo, |
| | | templeteEdit |
| | | } from '@/api/api.js' |
| | | let ctx = null |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1, |
| | | |
| | | |
| | | |
| | | // 模版动画 |
| | | pixelData: [], |
| | | canvasData: [], |
| | | canvasWidth: 240, |
| | | canvasHeight: 120, |
| | | width: 24, |
| | | height: 12, |
| | | color: '#FF0000', |
| | | gridRect: null, // 存储像素网格的位置信息 |
| | | lastCell: null, // 防止重复绘制 |
| | | columnsRatio: [ |
| | | ['12*24', '16*32', '24*48', '32*64', '40*80'] |
| | | ], |
| | | // columnsRatio: [ |
| | | // ['12*24'] |
| | | // ], |
| | | colorList: [ |
| | | '#FF0000', |
| | | '#00FF00', |
| | | '#0000FF', |
| | | '#FFFF00', |
| | | '#FFA500', |
| | | '#800080', |
| | | '#00FFFF', |
| | | '#FFC0CB' |
| | | ], |
| | | fpsIndex: 0, |
| | | fpsList: [], |
| | | tId: '', |
| | | tempInfo:{}, |
| | | titleName: '新增模版' |
| | | |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | if(opt.id){ |
| | | this.tId = opt.id |
| | | this.getTempleteInfo() |
| | | this.titleName = '编辑模版' |
| | | } |
| | | else{ |
| | | this.titleName = '新增模版' |
| | | } |
| | | }, |
| | | onShow() { |
| | | |
| | | }, |
| | | onHide() { |
| | | this.$refs.popupAnimation.close(); |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | onUnload() { |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | methods: { |
| | | // 获取编辑详情 |
| | | getTempleteInfo(){ |
| | | templeteInfo(this.tId).then(res=>{ |
| | | console.log('获取编辑详情-------',res) |
| | | if(res.code == 200){ |
| | | this.tempInfo = res.data |
| | | this.tempName = this.tempInfo.name |
| | | this.fileList1.push({ |
| | | url: this.baseURL + this.tempInfo.pic |
| | | }) |
| | | |
| | | this.specVal = this.tempInfo.screen |
| | | this.modeVal = this.tempInfo.mode |
| | | |
| | | this.total = this.tempInfo.cycle |
| | | this.fpsTotal = this.tempInfo.fpsTotal |
| | | this.speed = this.tempInfo.speed |
| | | |
| | | this.fpsList = JSON.parse(this.tempInfo.fpsList) |
| | | |
| | | } |
| | | else{ |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | // 添加模版提交接口 |
| | | clickTempleteAdd() { |
| | | |
| | | if (!this.tempName) { |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fileList1.length == 0) { |
| | | uni.showToast({ |
| | | title: '请上传封面图片', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | if (this.fpsList.length == 0) { |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } else { |
| | | for (let i = 0; i < this.fpsList.length; i++) { |
| | | if (this.fpsList[i] === null) { |
| | | uni.showToast({ |
| | | title: `请编辑第 ${i+1} 个动画帧`, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | } |
| | | } |
| | | console.log('this.fpsList=========',this.fpsList) |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | |
| | | |
| | | // 遍历二维数组 |
| | | this.fpsData = this.fpsList.map(inner => |
| | | inner.map(item => { |
| | | if (item === "") { |
| | | return "000000"; // 空的改成000000 |
| | | } else if (item.includes("#")) { |
| | | return item.replace("#", ""); // 去掉# |
| | | } else { |
| | | return item; // 保持原样 |
| | | } |
| | | }) |
| | | ); |
| | | |
| | | console.log('this.fpsData=========',this.fpsData) |
| | | |
| | | // 动画方式 |
| | | if (this.modeVal == 0) { //一帧动画 |
| | | let frames = this.generateFrames(this.fpsData[0], 24); |
| | | const parsedData = frames.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = 24 |
| | | } else if (this.modeVal == 1) { |
| | | console.log('modeVal--------1') |
| | | const parsedData = this.fpsData.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | console.log('modeVal--------1',parsedData) |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = this.total |
| | | } |
| | | console.log('animationData--------', this.animationData) |
| | | |
| | | // 判断this.fileList1[0].url里是否有 this.baseURL |
| | | if (this.fileList1[0].url.includes(this.baseURL)) { |
| | | this.fileList1[0].url = this.fileList1[0].url.replace(this.baseURL, ""); |
| | | } |
| | | |
| | | console.log('url----',this.fileList1[0].url); |
| | | |
| | | // 判断如果有id就编辑 |
| | | if(this.tId){ |
| | | const putData = { |
| | | id: this.tId, |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('putData-----', putData) |
| | | templeteEdit(putData).then(res => { |
| | | console.log('编辑动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | else{ |
| | | const addData = { |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('addData-----', addData) |
| | | templeteAdd(addData).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | }, |
| | | // 保存动画 |
| | | clickSaveData() { |
| | | |
| | | this.fpsList[this.fpsIndex] = this.pixelData; |
| | | |
| | | |
| | | |
| | | |
| | | this.$refs.popupAnimation.close(); |
| | | }, |
| | | changeColor() { |
| | | this.$refs.pickerColor.open(); |
| | | }, |
| | | clickColor(item) { |
| | | console.log('confirmColor---', item); |
| | | this.color = item; |
| | | this.$refs.pickerColor.close(); |
| | | }, |
| | | changeRatio() { |
| | | this.$refs.pickerRatio.open(); |
| | | }, |
| | | // 分辨率 |
| | | confirmRatio(e) { |
| | | console.log('confirmRatio---', e.value[0]); |
| | | const ratio = e.value[0].split('*'); |
| | | console.log('ratio---', ratio[0]); |
| | | this.height = Number(ratio[0]); |
| | | this.width = Number(ratio[1]); |
| | | this.canvasWidth = this.width * 10; |
| | | this.canvasHeight = this.height * 10; |
| | | // 重新初始化一维数组 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | console.log('pixelData---', this.pixelData); |
| | | }, |
| | | // 预先获取像素网格的位置信息 |
| | | getGridRect(cb) { |
| | | if (this.gridRect) { |
| | | cb(this.gridRect) |
| | | } else { |
| | | uni.createSelectorQuery().in(this).select('.pixel_grid').boundingClientRect(rect => { |
| | | this.gridRect = rect |
| | | cb(rect) |
| | | }).exec() |
| | | } |
| | | }, |
| | | // 计算触摸点对应的格子 |
| | | getCellByTouch(touch, cb) { |
| | | this.getGridRect(rect => { |
| | | if (!rect) return |
| | | const x = touch.clientX - rect.left |
| | | const y = touch.clientY - rect.top |
| | | const cellWidth = rect.width / this.width |
| | | const cellHeight = rect.height / this.height |
| | | const colIndex = Math.floor(x / cellWidth) |
| | | const rowIndex = Math.floor(y / cellHeight) |
| | | if ( |
| | | colIndex >= 0 && colIndex < this.width && |
| | | rowIndex >= 0 && rowIndex < this.height |
| | | ) { |
| | | cb({ |
| | | rowIndex, |
| | | colIndex |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchStart(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | }) |
| | | }, |
| | | handleTouchMove(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | // 只在格子变化时才触发 |
| | | if (!this.lastCell || this.lastCell.rowIndex !== cell.rowIndex || this.lastCell.colIndex !== |
| | | cell.colIndex) { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchEnd() { |
| | | this.lastCell = null |
| | | }, |
| | | handleClick(rowIndex, colIndex) { |
| | | console.log(rowIndex, colIndex) |
| | | // 计算一维数组的索引 |
| | | const index = rowIndex * this.width + colIndex |
| | | // 如果当前像素有颜色,则清除;如果没有颜色,则添加当前选择的颜色 |
| | | const currentColor = this.pixelData[index] |
| | | const newColor = currentColor ? '' : this.color |
| | | this.$set(this.pixelData, index, newColor) |
| | | }, |
| | | clear() { |
| | | ctx && ctx.clearRect(0, 0, 1000, 1000) |
| | | // 将所有像素点设置为空字符串 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 模版动画弹出框 |
| | | changeAnimation(e){ |
| | | //console.log('打开关闭模版动画弹窗触发---',e) |
| | | if(e.show){ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('landscape-primary') |
| | | } |
| | | } |
| | | else{ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | } |
| | | }, |
| | | // 打开模版动画 |
| | | popupAnimationOpen(index){ |
| | | this.$refs.popupAnimation.open(); |
| | | this.fpsIndex = index |
| | | |
| | | if(this.fpsList[this.fpsIndex] == null){ |
| | | console.log('第一次编辑-----null') |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }else{ |
| | | console.log('已编辑过-----') |
| | | this.pixelData = this.fpsList[this.fpsIndex] |
| | | } |
| | | }, |
| | | // 关闭模版动画 |
| | | popupAnimationClose(){ |
| | | this.$refs.popupAnimation.close(); |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 24) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | this.adjustFpsData() |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsList = [] |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsList.length > this.total) { |
| | | this.fpsList = this.fpsList.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsList.length < this.total) { |
| | | const missing = this.total - this.fpsList.length; |
| | | this.fpsList = this.fpsList.concat(new Array(missing).fill(null)); |
| | | } |
| | | |
| | | // 如果 fpsList 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsList 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data)) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | // 生成点阵数据 |
| | | generateDotMatrix(char) { |
| | | return new Promise((resolve, reject) => { |
| | | |
| | | // 使用 uni-app 的 canvas context |
| | | ctx = uni.createCanvasContext('myCanvas', this); |
| | | |
| | | // 设置字体样式 |
| | | ctx.setFontSize(this.canvasHeight * 0.9); |
| | | ctx.setFillStyle('#000') |
| | | ctx.setTextAlign('center') |
| | | ctx.setTextBaseline('middle') |
| | | // 绘制文字 |
| | | // 计算文字位置 |
| | | // 水平居中 |
| | | const x = this.canvasWidth / 2; |
| | | // 垂直居中,稍微向上偏移一点 |
| | | const y = this.canvasHeight / 2; |
| | | |
| | | // 绘制文字 |
| | | ctx.fillText(char, x, y); |
| | | |
| | | // 绘制到画布 |
| | | ctx.draw(false, () => { |
| | | // 获取画布数据 |
| | | uni.canvasGetImageData({ |
| | | canvasId: 'myCanvas', |
| | | x: 0, |
| | | y: 0, |
| | | width: this.canvasWidth, |
| | | height: this.canvasHeight, |
| | | success: (res) => { |
| | | console.log(res) |
| | | const imageData = res.data; |
| | | // 生成一维数组 |
| | | const pixelArray = new Array(this.height * this.width).fill('') |
| | | |
| | | // 计算每个格子的像素大小 |
| | | const cellWidth = this.canvasWidth / this.width; |
| | | const cellHeight = this.canvasHeight / this.height; |
| | | |
| | | // 生成一维数组 |
| | | for (let y = 0; y < this.height; y++) { |
| | | for (let x = 0; x < this.width; x++) { |
| | | let blackPixels = 0; |
| | | let totalPixels = 0; |
| | | |
| | | // 对每个格子进行采样 |
| | | for (let py = 0; py < cellHeight; py++) { |
| | | for (let px = 0; px < cellWidth; px++) { |
| | | const pixelY = Math.floor(y * cellHeight + py); |
| | | const pixelX = Math.floor(x * cellWidth + px); |
| | | |
| | | const idx = (pixelY * this.canvasWidth + pixelX) * 4; |
| | | const r = imageData[idx]; |
| | | const g = imageData[idx + 1]; |
| | | const b = imageData[idx + 2]; |
| | | const a = imageData[idx + 3]; |
| | | |
| | | // 计算亮度 |
| | | const brightness = 0.3 * r + 0.59 * g + 0.11 * b; |
| | | |
| | | // 如果亮度小于阈值且透明度大于阈值,则认为是黑色 |
| | | if (brightness < 128 && a > 128) { |
| | | blackPixels++; |
| | | } |
| | | totalPixels++; |
| | | } |
| | | } |
| | | |
| | | // 如果黑色像素占比超过阈值,则设置为当前选择的颜色 |
| | | const isBlack = blackPixels / totalPixels > 0.3; |
| | | const arrayIndex = y * this.width + x; |
| | | pixelArray[arrayIndex] = isBlack ? this.color : ''; // 改为颜色值或空字符串 |
| | | } |
| | | } |
| | | |
| | | resolve(pixelArray); |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 修改添加文字方法 |
| | | async addText() { |
| | | uni.showModal({ |
| | | title: '添加文字', |
| | | content: '', |
| | | editable: true, |
| | | placeholderText: '请输入文字', |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | const text = res.content; |
| | | if (text.length > 0) { |
| | | try { |
| | | // 生成点阵数据 |
| | | const dotMatrix = await this.generateDotMatrix(text); |
| | | console.log('点阵数据:', dotMatrix); |
| | | this.pixelData = dotMatrix |
| | | } catch (error) { |
| | | console.error('生成点阵失败:', error); |
| | | uni.showToast({ |
| | | title: '生成点阵失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请输入文字', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | page { |
| | | -webkit-text-size-adjust: 100%; |
| | | text-size-adjust: 100%; |
| | | } |
| | | .item_pic_btn{ |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | background: #eee; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15rpx; |
| | | } |
| | | .color_list { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | gap: 30rpx; |
| | | box-sizing: border-box; |
| | | padding: 50rpx; |
| | | |
| | | .color_item { |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | border-radius: 5rpx; |
| | | } |
| | | } |
| | | |
| | | .controls { |
| | | position: absolute; |
| | | bottom: 10px; |
| | | left: 50px; |
| | | display: flex; |
| | | gap: 20px; |
| | | z-index: 9; |
| | | } |
| | | .animation_pixel{ |
| | | width: 100vw; |
| | | height: 100vh; |
| | | position: relative; |
| | | .back_btn{ |
| | | position: absolute; |
| | | left: 30px; |
| | | top: 20px; |
| | | z-index: 9999; |
| | | } |
| | | .canvas { |
| | | margin: 100rpx auto 0; |
| | | background: #ccc; |
| | | position: absolute; |
| | | z-index: 1; |
| | | left: -9999px; |
| | | top: -9999px; |
| | | } |
| | | .pixel_grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: var(--status-bar-height); |
| | | right: 0; |
| | | bottom: 0; |
| | | display: grid; |
| | | gap: 1px; |
| | | box-sizing: border-box; |
| | | background: #eee; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel_cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | |
| | | .pixel_cell_item { |
| | | width: 50%; |
| | | height: 50%; |
| | | aspect-ratio: 1/1; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | // ------------- |
| | | .footer_none { |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .footer_btn { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | } |
| | | .footer_btn2 { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .temp_btn { |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | |
| | | .spec_radio { |
| | | margin-top: 30rpx; |
| | | |
| | | &:first-child { |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .temp { |
| | | padding: 0 30rpx; |
| | | |
| | | .temp_item1 { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | |
| | | .item {} |
| | | } |
| | | |
| | | .temp_item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: flex-start; |
| | | flex-direction: column; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .item { |
| | | margin-top: 20rpx; |
| | | width: 100%; |
| | | &.item_pic { |
| | | margin-top: 25rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | |
| | | <uv-navbar :title="titleName" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 封面图片 |
| | | </view> |
| | | <view class="item item_pic"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | :previewFullImage="false" @afterRead="afterRead" @delete="deletePic"> |
| | | <!-- <image v-if="fileList1.length != 0" |
| | | :src="baseURL + fileList1[0]?.url" |
| | | mode="aspectFill" |
| | | style="width: 200rpx;height: 200rpx;" |
| | | ></image> |
| | | <view class="item_pic_btn" v-else> |
| | | <uv-icon name="camera-fill" color="#ccc" size="60rpx"></uv-icon> |
| | | </view> --> |
| | | </uv-upload> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column" @change="changeSpec"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="popupAnimationOpen(index)" shape="circle" size="small" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" @click="clickTempleteAdd()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | <!-- 动画弹出框 --> |
| | | <uv-popup ref="popupAnimation" mode="center" @change="changeAnimation" :safeAreaInsetTop="false" :safeAreaInsetBottom="false" :zoom="false" duration="0"> |
| | | <view class="animation_pixel"> |
| | | <view class="back_btn" @click="popupAnimationClose"> |
| | | <uv-icon name="arrow-leftward" color="#333" size="30"></uv-icon> |
| | | </view> |
| | | <!-- 动画按钮 --> |
| | | <view class="controls"> |
| | | <uv-button type="primary" :plain="true" text="分辨率" @click="changeRatio()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="颜色" @click="changeColor()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="添加文字" @click="addText()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="清除" @click="clear()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="保存" @click="clickSaveData"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 文字生成图形 --> |
| | | <canvas canvas-id="myCanvas" id="myCanvas" class="canvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas> |
| | | |
| | | <view class="pixel_grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove" |
| | | @touchend.prevent="handleTouchEnd" @touchcancel.prevent="handleTouchEnd" |
| | | :style="{ |
| | | gridTemplateRows: `repeat(${height}, 1fr)`, |
| | | gridTemplateColumns: `repeat(${width}, 1fr)` |
| | | }"> |
| | | <view class="pixel_cell" v-for="(cell, cellIndex) in pixelData" :key="cellIndex"> |
| | | <view class="pixel_cell_item" :style="{ backgroundColor: cell }"></view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 分辨率 --> |
| | | <uv-picker ref="pickerRatio" :columns="columnsRatio" @confirm="confirmRatio"></uv-picker> |
| | | |
| | | <!-- 颜色 --> |
| | | <uv-popup ref="pickerColor" mode="bottom" round="10" :closeable="true"> |
| | | <view class="color_list"> |
| | | <view class="color_item" v-for="(item, index) in colorList" :class="{ hover: color === item }" :key="index" :style="{ backgroundColor: item }" @click="clickColor(item)"></view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | templeteAdd, |
| | | templeteInfo, |
| | | templeteEdit |
| | | } from '@/api/api.js' |
| | | let ctx = null |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1, |
| | | |
| | | |
| | | |
| | | // 模版动画 |
| | | pixelData: [], |
| | | canvasData: [], |
| | | canvasWidth: 240, |
| | | canvasHeight: 120, |
| | | width: 24, |
| | | height: 12, |
| | | color: '#FF0000', |
| | | gridRect: null, // 存储像素网格的位置信息 |
| | | lastCell: null, // 防止重复绘制 |
| | | columnsRatio: [ |
| | | ['12*24', '16*32', '24*48', '32*64', '40*80'] |
| | | ], |
| | | // columnsRatio: [ |
| | | // ['12*24'] |
| | | // ], |
| | | colorList: [ |
| | | '#FF0000', |
| | | '#00FF00', |
| | | '#0000FF', |
| | | '#FFFF00', |
| | | '#FFA500', |
| | | '#800080', |
| | | '#00FFFF', |
| | | '#FFC0CB' |
| | | ], |
| | | fpsIndex: 0, |
| | | fpsList: [], |
| | | tId: '', |
| | | tempInfo:{}, |
| | | titleName: '新增模版' |
| | | |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | if(opt.id){ |
| | | this.tId = opt.id |
| | | this.getTempleteInfo() |
| | | this.titleName = '编辑模版' |
| | | } |
| | | else{ |
| | | this.titleName = '新增模版' |
| | | } |
| | | }, |
| | | onShow() { |
| | | |
| | | }, |
| | | onHide() { |
| | | this.$refs.popupAnimation.close(); |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | onUnload() { |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | methods: { |
| | | // 获取编辑详情 |
| | | getTempleteInfo(){ |
| | | templeteInfo(this.tId).then(res=>{ |
| | | console.log('获取编辑详情-------',res) |
| | | if(res.code == 200){ |
| | | this.tempInfo = res.data |
| | | this.tempName = this.tempInfo.name |
| | | this.fileList1.push({ |
| | | url: this.baseURL + this.tempInfo.pic |
| | | }) |
| | | |
| | | this.specVal = this.tempInfo.screen |
| | | this.modeVal = this.tempInfo.mode |
| | | |
| | | this.total = this.tempInfo.cycle |
| | | this.fpsTotal = this.tempInfo.fpsTotal |
| | | this.speed = this.tempInfo.speed |
| | | |
| | | this.fpsList = JSON.parse(this.tempInfo.fpsList) |
| | | |
| | | } |
| | | else{ |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | // 添加模版提交接口 |
| | | clickTempleteAdd() { |
| | | |
| | | if (!this.tempName) { |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fileList1.length == 0) { |
| | | uni.showToast({ |
| | | title: '请上传封面图片', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | if (this.fpsList.length == 0) { |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } else { |
| | | for (let i = 0; i < this.fpsList.length; i++) { |
| | | if (this.fpsList[i] === null) { |
| | | uni.showToast({ |
| | | title: `请编辑第 ${i+1} 个动画帧`, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | } |
| | | } |
| | | console.log('this.fpsList=========',this.fpsList) |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | |
| | | |
| | | // 遍历二维数组 |
| | | this.fpsData = this.fpsList.map(inner => |
| | | inner.map(item => { |
| | | if (item === "") { |
| | | return "000000"; // 空的改成000000 |
| | | } else if (item.includes("#")) { |
| | | return item.replace("#", ""); // 去掉# |
| | | } else { |
| | | return item; // 保持原样 |
| | | } |
| | | }) |
| | | ); |
| | | |
| | | console.log('this.fpsData=========',this.fpsData) |
| | | |
| | | // 动画方式 |
| | | if (this.modeVal == 0) { //一帧动画 |
| | | let frames = this.generateFrames(this.fpsData[0], 24); |
| | | const parsedData = frames.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = 24 |
| | | } else if (this.modeVal == 1) { |
| | | console.log('modeVal--------1') |
| | | const parsedData = this.fpsData.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | console.log('modeVal--------1',parsedData) |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = this.total |
| | | } |
| | | console.log('animationData--------', this.animationData) |
| | | |
| | | // 判断this.fileList1[0].url里是否有 this.baseURL |
| | | if (this.fileList1[0].url.includes(this.baseURL)) { |
| | | this.fileList1[0].url = this.fileList1[0].url.replace(this.baseURL, ""); |
| | | } |
| | | |
| | | console.log('url----',this.fileList1[0].url); |
| | | |
| | | // 判断如果有id就编辑 |
| | | if(this.tId){ |
| | | const putData = { |
| | | id: this.tId, |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('putData-----', putData) |
| | | templeteEdit(putData).then(res => { |
| | | console.log('编辑动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | else{ |
| | | const addData = { |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('addData-----', addData) |
| | | templeteAdd(addData).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | }, |
| | | // 保存动画 |
| | | clickSaveData() { |
| | | |
| | | this.fpsList[this.fpsIndex] = this.pixelData; |
| | | |
| | | |
| | | |
| | | |
| | | this.$refs.popupAnimation.close(); |
| | | }, |
| | | changeColor() { |
| | | this.$refs.pickerColor.open(); |
| | | }, |
| | | clickColor(item) { |
| | | console.log('confirmColor---', item); |
| | | this.color = item; |
| | | this.$refs.pickerColor.close(); |
| | | }, |
| | | changeRatio() { |
| | | this.$refs.pickerRatio.open(); |
| | | }, |
| | | // 分辨率 |
| | | confirmRatio(e) { |
| | | console.log('confirmRatio---', e.value[0]); |
| | | const ratio = e.value[0].split('*'); |
| | | console.log('ratio---', ratio[0]); |
| | | this.height = Number(ratio[0]); |
| | | this.width = Number(ratio[1]); |
| | | this.canvasWidth = this.width * 10; |
| | | this.canvasHeight = this.height * 10; |
| | | // 重新初始化一维数组 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | console.log('pixelData---', this.pixelData); |
| | | }, |
| | | // 预先获取像素网格的位置信息 |
| | | getGridRect(cb) { |
| | | if (this.gridRect) { |
| | | cb(this.gridRect) |
| | | } else { |
| | | uni.createSelectorQuery().in(this).select('.pixel_grid').boundingClientRect(rect => { |
| | | this.gridRect = rect |
| | | cb(rect) |
| | | }).exec() |
| | | } |
| | | }, |
| | | // 计算触摸点对应的格子 |
| | | getCellByTouch(touch, cb) { |
| | | this.getGridRect(rect => { |
| | | if (!rect) return |
| | | const x = touch.clientX - rect.left |
| | | const y = touch.clientY - rect.top |
| | | const cellWidth = rect.width / this.width |
| | | const cellHeight = rect.height / this.height |
| | | const colIndex = Math.floor(x / cellWidth) |
| | | const rowIndex = Math.floor(y / cellHeight) |
| | | if ( |
| | | colIndex >= 0 && colIndex < this.width && |
| | | rowIndex >= 0 && rowIndex < this.height |
| | | ) { |
| | | cb({ |
| | | rowIndex, |
| | | colIndex |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchStart(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | }) |
| | | }, |
| | | handleTouchMove(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | // 只在格子变化时才触发 |
| | | if (!this.lastCell || this.lastCell.rowIndex !== cell.rowIndex || this.lastCell.colIndex !== |
| | | cell.colIndex) { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchEnd() { |
| | | this.lastCell = null |
| | | }, |
| | | handleClick(rowIndex, colIndex) { |
| | | console.log(rowIndex, colIndex) |
| | | // 计算一维数组的索引 |
| | | const index = rowIndex * this.width + colIndex |
| | | // 如果当前像素有颜色,则清除;如果没有颜色,则添加当前选择的颜色 |
| | | const currentColor = this.pixelData[index] |
| | | const newColor = currentColor ? '' : this.color |
| | | this.$set(this.pixelData, index, newColor) |
| | | }, |
| | | clear() { |
| | | ctx && ctx.clearRect(0, 0, 1000, 1000) |
| | | // 将所有像素点设置为空字符串 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 模版动画弹出框 |
| | | changeAnimation(e){ |
| | | //console.log('打开关闭模版动画弹窗触发---',e) |
| | | if(e.show){ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('landscape-primary') |
| | | } |
| | | } |
| | | else{ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | } |
| | | }, |
| | | // 打开模版动画 |
| | | popupAnimationOpen(index){ |
| | | this.$refs.popupAnimation.open(); |
| | | this.fpsIndex = index |
| | | |
| | | if(this.fpsList[this.fpsIndex] == null){ |
| | | console.log('第一次编辑-----null') |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }else{ |
| | | console.log('已编辑过-----') |
| | | this.pixelData = this.fpsList[this.fpsIndex] |
| | | } |
| | | }, |
| | | // 关闭模版动画 |
| | | popupAnimationClose(){ |
| | | this.$refs.popupAnimation.close(); |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 24) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | this.adjustFpsData() |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsList = [] |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsList.length > this.total) { |
| | | this.fpsList = this.fpsList.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsList.length < this.total) { |
| | | const missing = this.total - this.fpsList.length; |
| | | this.fpsList = this.fpsList.concat(new Array(missing).fill(null)); |
| | | } |
| | | |
| | | // 如果 fpsList 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsList 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data)) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | // 生成点阵数据 |
| | | generateDotMatrix(char) { |
| | | return new Promise((resolve, reject) => { |
| | | |
| | | // 使用 uni-app 的 canvas context |
| | | ctx = uni.createCanvasContext('myCanvas', this); |
| | | |
| | | // 设置字体样式 |
| | | ctx.setFontSize(this.canvasHeight * 0.9); |
| | | ctx.setFillStyle('#000') |
| | | ctx.setTextAlign('center') |
| | | ctx.setTextBaseline('middle') |
| | | // 绘制文字 |
| | | // 计算文字位置 |
| | | // 水平居中 |
| | | const x = this.canvasWidth / 2; |
| | | // 垂直居中,稍微向上偏移一点 |
| | | const y = this.canvasHeight / 2; |
| | | |
| | | // 绘制文字 |
| | | ctx.fillText(char, x, y); |
| | | |
| | | // 绘制到画布 |
| | | ctx.draw(false, () => { |
| | | // 获取画布数据 |
| | | uni.canvasGetImageData({ |
| | | canvasId: 'myCanvas', |
| | | x: 0, |
| | | y: 0, |
| | | width: this.canvasWidth, |
| | | height: this.canvasHeight, |
| | | success: (res) => { |
| | | console.log(res) |
| | | const imageData = res.data; |
| | | // 生成一维数组 |
| | | const pixelArray = new Array(this.height * this.width).fill('') |
| | | |
| | | // 计算每个格子的像素大小 |
| | | const cellWidth = this.canvasWidth / this.width; |
| | | const cellHeight = this.canvasHeight / this.height; |
| | | |
| | | // 生成一维数组 |
| | | for (let y = 0; y < this.height; y++) { |
| | | for (let x = 0; x < this.width; x++) { |
| | | let blackPixels = 0; |
| | | let totalPixels = 0; |
| | | |
| | | // 对每个格子进行采样 |
| | | for (let py = 0; py < cellHeight; py++) { |
| | | for (let px = 0; px < cellWidth; px++) { |
| | | const pixelY = Math.floor(y * cellHeight + py); |
| | | const pixelX = Math.floor(x * cellWidth + px); |
| | | |
| | | const idx = (pixelY * this.canvasWidth + pixelX) * 4; |
| | | const r = imageData[idx]; |
| | | const g = imageData[idx + 1]; |
| | | const b = imageData[idx + 2]; |
| | | const a = imageData[idx + 3]; |
| | | |
| | | // 计算亮度 |
| | | const brightness = 0.3 * r + 0.59 * g + 0.11 * b; |
| | | |
| | | // 如果亮度小于阈值且透明度大于阈值,则认为是黑色 |
| | | if (brightness < 128 && a > 128) { |
| | | blackPixels++; |
| | | } |
| | | totalPixels++; |
| | | } |
| | | } |
| | | |
| | | // 如果黑色像素占比超过阈值,则设置为当前选择的颜色 |
| | | const isBlack = blackPixels / totalPixels > 0.3; |
| | | const arrayIndex = y * this.width + x; |
| | | pixelArray[arrayIndex] = isBlack ? this.color : ''; // 改为颜色值或空字符串 |
| | | } |
| | | } |
| | | |
| | | resolve(pixelArray); |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 修改添加文字方法 |
| | | async addText() { |
| | | uni.showModal({ |
| | | title: '添加文字', |
| | | content: '', |
| | | editable: true, |
| | | placeholderText: '请输入文字', |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | const text = res.content; |
| | | if (text.length > 0) { |
| | | try { |
| | | // 生成点阵数据 |
| | | const dotMatrix = await this.generateDotMatrix(text); |
| | | console.log('点阵数据:', dotMatrix); |
| | | this.pixelData = dotMatrix |
| | | } catch (error) { |
| | | console.error('生成点阵失败:', error); |
| | | uni.showToast({ |
| | | title: '生成点阵失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请输入文字', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | page { |
| | | -webkit-text-size-adjust: 100%; |
| | | text-size-adjust: 100%; |
| | | } |
| | | .item_pic_btn{ |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | background: #eee; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15rpx; |
| | | } |
| | | .color_list { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | gap: 30rpx; |
| | | box-sizing: border-box; |
| | | padding: 50rpx; |
| | | |
| | | .color_item { |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | border-radius: 5rpx; |
| | | } |
| | | } |
| | | |
| | | .controls { |
| | | position: absolute; |
| | | bottom: 10px; |
| | | left: 50px; |
| | | display: flex; |
| | | gap: 20px; |
| | | z-index: 9; |
| | | } |
| | | .animation_pixel{ |
| | | width: 100vw; |
| | | height: 100vh; |
| | | position: relative; |
| | | .back_btn{ |
| | | position: absolute; |
| | | left: 30px; |
| | | top: 20px; |
| | | z-index: 9999; |
| | | } |
| | | .canvas { |
| | | margin: 100rpx auto 0; |
| | | background: #ccc; |
| | | position: absolute; |
| | | z-index: 1; |
| | | left: -9999px; |
| | | top: -9999px; |
| | | } |
| | | .pixel_grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: var(--status-bar-height); |
| | | right: 0; |
| | | bottom: 0; |
| | | display: grid; |
| | | gap: 1px; |
| | | box-sizing: border-box; |
| | | background: #eee; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel_cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | |
| | | .pixel_cell_item { |
| | | width: 50%; |
| | | height: 50%; |
| | | aspect-ratio: 1/1; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | // ------------- |
| | | .footer_none { |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .footer_btn { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | } |
| | | .footer_btn2 { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .temp_btn { |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | |
| | | .spec_radio { |
| | | margin-top: 30rpx; |
| | | |
| | | &:first-child { |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .temp { |
| | | padding: 0 30rpx; |
| | | |
| | | .temp_item1 { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | |
| | | .item {} |
| | | } |
| | | |
| | | .temp_item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: flex-start; |
| | | flex-direction: column; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .item { |
| | | margin-top: 20rpx; |
| | | width: 100%; |
| | | &.item_pic { |
| | | margin-top: 25rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="新增模版" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column"> |
| | | <uv-radio |
| | | class="spec_radio" |
| | | v-for="(item, index) in specList" |
| | | :key="index" |
| | | :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode"> |
| | | <uv-radio |
| | | class="spec_radio" |
| | | v-for="(item, index) in modeList" |
| | | :key="index" |
| | | :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="clickGoAnim(item)" shape="circle" size="small" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | |
| | | |
| | | <!-- <view class="temp_btn" @click="clickGoAnim"> |
| | | 编辑动画 |
| | | </view> --> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" @click="clickTempleteAdd()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { templeteAdd } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | modeList: [ |
| | | { |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2,3,4,5,6,7,8,9] |
| | | ], |
| | | total: 1, |
| | | columnsSpeed: [ |
| | | [10,20,30,40,50,60,70,80,90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, |
| | | fpsIds: [] |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | onShow() { |
| | | console.log('------onShow') |
| | | const fpsParams = uni.getStorageSync('fpsParams'); |
| | | // if (fpsParams) { |
| | | // console.log('收到回传参数:', fpsParams); |
| | | // this.fpsIndex = fpsParams.fpsIndex+1 |
| | | // this.fpsData[fpsParams.fpsIndex] = fpsParams.data; |
| | | // this.fpsIds.push(fpsParams.fpsIndex) |
| | | // console.log(this.fpsIndex, this.fpsData); |
| | | // uni.removeStorageSync('fpsParams'); // 清理掉 |
| | | // } |
| | | if (fpsParams) { |
| | | console.log('收到回传参数:', fpsParams); |
| | | // 用 splice 代替下标赋值 |
| | | this.fpsIndex = fpsParams.fpsIndex+1 |
| | | this.fpsData.splice(fpsParams.fpsIndex, 1, fpsParams.data); |
| | | |
| | | if (!this.fpsIds.includes(fpsParams.fpsIndex + 1)) { |
| | | this.fpsIds.push(fpsParams.fpsIndex + 1); |
| | | } |
| | | console.log(this.fpsIndex, this.fpsData); |
| | | uni.removeStorageSync('fpsParams'); // 清理掉 |
| | | } |
| | | }, |
| | | methods: { |
| | | clickTempleteAdd(){ |
| | | if(!this.tempName){ |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(this.fpsIds.length == 0){ |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | let totalArr = new Array(this.total).fill(0).map((_, i) => i + 1); |
| | | let diffBA = totalArr.filter(v => !this.fpsIds.includes(v)); |
| | | console.log(totalArr, diffBA) |
| | | if(diffBA.length != 0){ |
| | | uni.showToast({ |
| | | title: '请编辑第'+ Math.min(...diffBA) +'帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | templeteAdd({ |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | fpsTotal: this.total, //总帧数 |
| | | speed: this.speed, //速度 |
| | | |
| | | fpsData: JSON.stringify(this.fpsData) |
| | | }).then(res=>{ |
| | | console.log('添加动画模版',res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if(res.code == 200){ |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | clickSpeed(){ |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal(){ |
| | | if(this.total != 1){ |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | }, |
| | | changeMode(e){ |
| | | if(e == 0){ |
| | | this.total = 1 |
| | | } |
| | | else{ |
| | | this.total = 2 |
| | | } |
| | | this.fpsIndex = 1 |
| | | this.fpsData = [] |
| | | }, |
| | | clickGoAnim(index){ |
| | | console.log(this.fpsIndex, index) |
| | | // if(this.fpsIndex > index){ |
| | | // uni.showToast({ |
| | | // title: '请先编辑第'+this.fpsIndex+'帧', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // return |
| | | // } |
| | | uni.navigateTo({ |
| | | url: '/pages/animation/animation?fps='+index |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .footer_none{ |
| | | height: 200rpx; |
| | | } |
| | | .footer_btn{ |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding:30rpx 40rpx; |
| | | } |
| | | .temp_btn{ |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | .spec_radio{ |
| | | margin-top: 30rpx; |
| | | &:first-child{ |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | .temp{ |
| | | padding:0 30rpx; |
| | | .temp_item1{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient( 180deg, #FFFFFF 0%, rgba(255,255,255,0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67,147,248,0.1), -8rpx -8rpx 23rpx 0rpx rgba(255,255,255,0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding:30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | .title{ |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | .item{ |
| | | |
| | | } |
| | | } |
| | | .temp_item{ |
| | | background: linear-gradient( 180deg, #FFFFFF 0%, rgba(255,255,255,0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67,147,248,0.1), -8rpx -8rpx 23rpx 0rpx rgba(255,255,255,0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding:30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | .title{ |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | .item{ |
| | | margin-top: 15rpx; |
| | | } |
| | | } |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | |
| | | <uv-navbar :title="titleName" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp"> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 模版名称 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入名称" border="none" v-model="tempName" fontSize="27rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 封面图片 |
| | | </view> |
| | | <view class="item item_pic"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | :previewFullImage="false" @afterRead="afterRead" @delete="deletePic"> |
| | | <!-- <image v-if="fileList1.length != 0" |
| | | :src="baseURL + fileList1[0]?.url" |
| | | mode="aspectFill" |
| | | style="width: 200rpx;height: 200rpx;" |
| | | ></image> |
| | | <view class="item_pic_btn" v-else> |
| | | <uv-icon name="camera-fill" color="#ccc" size="60rpx"></uv-icon> |
| | | </view> --> |
| | | </uv-upload> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 屏幕规格 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="specVal" placement="column" @change="changeSpec" labelSize="26rpx"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in specList" :key="index" :label="item.name" |
| | | :name="item.name"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画方式 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-radio-group v-model="modeVal" placement="column" @change="changeMode" labelSize="26rpx"> |
| | | <uv-radio class="spec_radio" v-for="(item, index) in modeList" :key="index" :label="item.name" |
| | | :name="item.val"> |
| | | </uv-radio> |
| | | </uv-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画帧数 |
| | | </view> |
| | | <view class="item" @click="clickTotal"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="total" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="temp_item"> |
| | | <view class="title"> |
| | | 动画速度 |
| | | </view> |
| | | <view class="item" @click="clickSpeed"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="speed" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" |
| | | customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="temp_item" v-if="modeVal == 1"> |
| | | <view class="title"> |
| | | 循环次数 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入" readonly border="none" v-model="cycle" suffixIcon="arrow-right" |
| | | suffixIconStyle="color: #bbb" fontSize="26rpx" customStyle="background: #ECEFF6;padding:18rpx 20rpx;border-radius: 10rpx;"></uv-input> |
| | | </view> |
| | | </view> --> |
| | | <view class="temp_item1" v-for="(item,index) in total" :key="index"> |
| | | <view class="title"> |
| | | 动画第{{item}}帧 |
| | | </view> |
| | | <view class="item"> |
| | | <uv-button text="编辑" @click="popupAnimationOpen(index)" shape="circle" size="small" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)"></uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="footer_none"></view> |
| | | <view class="footer_btn"> |
| | | <uv-button text="保存" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" @click="clickTempleteAdd()"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 动画帧数 --> |
| | | <uv-picker ref="pickerTotal" :columns="columnsTotal" @confirm="confirmTotal"></uv-picker> |
| | | |
| | | <!-- 动画速度 --> |
| | | <uv-picker ref="pickerSpeed" :columns="columnsSpeed" @confirm="confirmSpeed"></uv-picker> |
| | | |
| | | <!-- 动画弹出框 --> |
| | | <uv-popup ref="popupAnimation" mode="center" @change="changeAnimation" :safeAreaInsetTop="false" :safeAreaInsetBottom="false" :zoom="false" duration="0"> |
| | | <view class="animation_pixel"> |
| | | <view class="back_btn" @click="popupAnimationClose"> |
| | | <uv-icon name="arrow-leftward" color="#333" size="30"></uv-icon> |
| | | </view> |
| | | <!-- 动画按钮 --> |
| | | <view class="controls"> |
| | | <uv-button type="primary" :plain="true" text="分辨率" @click="changeRatio()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="颜色" @click="changeColor()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="添加文字" @click="addText()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="清除" @click="clear()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="保存" @click="clickSaveData"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 文字生成图形 --> |
| | | <canvas canvas-id="myCanvas" id="myCanvas" class="canvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas> |
| | | |
| | | <view class="pixel_grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove" |
| | | @touchend.prevent="handleTouchEnd" @touchcancel.prevent="handleTouchEnd" |
| | | :style="{ |
| | | gridTemplateRows: `repeat(${height}, 1fr)`, |
| | | gridTemplateColumns: `repeat(${width}, 1fr)` |
| | | }"> |
| | | <view class="pixel_cell" v-for="(cell, cellIndex) in pixelData" :key="cellIndex"> |
| | | <view class="pixel_cell_item" :style="{ backgroundColor: cell }"></view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 分辨率 --> |
| | | <uv-picker ref="pickerRatio" :columns="columnsRatio" @confirm="confirmRatio"></uv-picker> |
| | | |
| | | <!-- 颜色 --> |
| | | <uv-popup ref="pickerColor" mode="bottom" round="10" :closeable="true"> |
| | | <view class="color_list"> |
| | | <view class="color_item" v-for="(item, index) in colorList" :class="{ hover: color === item }" :key="index" :style="{ backgroundColor: item }" @click="clickColor(item)"></view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | templeteAdd, |
| | | templeteInfo, |
| | | templeteEdit |
| | | } from '@/api/api.js' |
| | | let ctx = null |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1, |
| | | |
| | | |
| | | |
| | | // 模版动画 |
| | | pixelData: [], |
| | | canvasData: [], |
| | | canvasWidth: 240, |
| | | canvasHeight: 120, |
| | | width: 24, |
| | | height: 12, |
| | | color: '#FF0000', |
| | | gridRect: null, // 存储像素网格的位置信息 |
| | | lastCell: null, // 防止重复绘制 |
| | | columnsRatio: [ |
| | | ['12*24', '16*32', '24*48', '32*64', '40*80'] |
| | | ], |
| | | // columnsRatio: [ |
| | | // ['12*24'] |
| | | // ], |
| | | colorList: [ |
| | | '#FF0000', |
| | | '#00FF00', |
| | | '#0000FF', |
| | | '#FFFF00', |
| | | '#FFA500', |
| | | '#800080', |
| | | '#00FFFF', |
| | | '#FFC0CB' |
| | | ], |
| | | fpsIndex: 0, |
| | | fpsList: [], |
| | | tId: '', |
| | | tempInfo:{}, |
| | | titleName: '新增模版' |
| | | |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | if(opt.id){ |
| | | this.tId = opt.id |
| | | this.getTempleteInfo() |
| | | this.titleName = '编辑模版' |
| | | } |
| | | else{ |
| | | this.titleName = '新增模版' |
| | | } |
| | | }, |
| | | onShow() { |
| | | |
| | | }, |
| | | onHide() { |
| | | this.$refs.popupAnimation.close(); |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | onUnload() { |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | methods: { |
| | | // 跳转到编辑动画页面 |
| | | goToAnimaiton(index){ |
| | | this.fpsIndex = index |
| | | uni.navigateTo({ |
| | | url: '/pages/animation/animation?index='+index |
| | | }) |
| | | }, |
| | | // 获取编辑详情 |
| | | getTempleteInfo(){ |
| | | templeteInfo(this.tId).then(res=>{ |
| | | console.log('获取编辑详情-------',res) |
| | | if(res.code == 200){ |
| | | this.tempInfo = res.data |
| | | this.tempName = this.tempInfo.name |
| | | this.fileList1.push({ |
| | | url: this.baseURL + this.tempInfo.pic |
| | | }) |
| | | |
| | | this.specVal = this.tempInfo.screen |
| | | this.modeVal = this.tempInfo.mode |
| | | |
| | | this.total = this.tempInfo.cycle |
| | | this.fpsTotal = this.tempInfo.fpsTotal |
| | | this.speed = this.tempInfo.speed |
| | | |
| | | this.fpsList = JSON.parse(this.tempInfo.fpsList) |
| | | |
| | | } |
| | | else{ |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | // 添加模版提交接口 |
| | | clickTempleteAdd() { |
| | | |
| | | if (!this.tempName) { |
| | | uni.showToast({ |
| | | title: '请输入模版名称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (this.fileList1.length == 0) { |
| | | uni.showToast({ |
| | | title: '请上传封面图片', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | if (this.fpsList.length == 0) { |
| | | uni.showToast({ |
| | | title: '请编辑动画帧', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } else { |
| | | for (let i = 0; i < this.fpsList.length; i++) { |
| | | if (this.fpsList[i] === null) { |
| | | uni.showToast({ |
| | | title: `请编辑第 ${i+1} 个动画帧`, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | } |
| | | } |
| | | console.log('this.fpsList=========',this.fpsList) |
| | | |
| | | uni.showLoading({ |
| | | title: '添加中', |
| | | mask: true |
| | | }); |
| | | |
| | | |
| | | // 遍历二维数组 |
| | | this.fpsData = this.fpsList.map(inner => |
| | | inner.map(item => { |
| | | if (item === "") { |
| | | return "000000"; // 空的改成000000 |
| | | } else if (item.includes("#")) { |
| | | return item.replace("#", ""); // 去掉# |
| | | } else { |
| | | return item; // 保持原样 |
| | | } |
| | | }) |
| | | ); |
| | | |
| | | console.log('this.fpsData=========',this.fpsData) |
| | | |
| | | // 动画方式 |
| | | if (this.modeVal == 0) { //一帧动画 |
| | | let frames = this.generateFrames(this.fpsData[0], 24); |
| | | const parsedData = frames.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = 24 |
| | | } else if (this.modeVal == 1) { |
| | | console.log('modeVal--------1') |
| | | const parsedData = this.fpsData.map(item => { |
| | | return this.toSnakeData(item, this.specWidth) |
| | | }); |
| | | console.log('modeVal--------1',parsedData) |
| | | this.animationData = parsedData.map(item => { |
| | | return this.mergeRowsToString(item) |
| | | }); |
| | | this.fpsTotal = this.total |
| | | } |
| | | console.log('animationData--------', this.animationData) |
| | | |
| | | // 判断this.fileList1[0].url里是否有 this.baseURL |
| | | if (this.fileList1[0].url.includes(this.baseURL)) { |
| | | this.fileList1[0].url = this.fileList1[0].url.replace(this.baseURL, ""); |
| | | } |
| | | |
| | | console.log('url----',this.fileList1[0].url); |
| | | |
| | | // 判断如果有id就编辑 |
| | | if(this.tId){ |
| | | const putData = { |
| | | id: this.tId, |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('putData-----', putData) |
| | | templeteEdit(putData).then(res => { |
| | | console.log('编辑动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | else{ |
| | | const addData = { |
| | | name: this.tempName, |
| | | userId: uni.getStorageSync('userId'), |
| | | screen: this.specVal, //屏幕 |
| | | mode: this.modeVal, |
| | | cycle: this.total, |
| | | fpsTotal: this.fpsTotal, //总帧数 |
| | | speed: this.speed, //速度 |
| | | pic: this.fileList1[0].url, |
| | | fpsData: JSON.stringify(this.animationData), |
| | | fpsList: JSON.stringify(this.fpsList) |
| | | } |
| | | console.log('addData-----', addData) |
| | | templeteAdd(addData).then(res => { |
| | | console.log('添加动画模版', res) |
| | | uni.hideLoading(); |
| | | if (res.code == 200) { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 1500, |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }) |
| | | } |
| | | |
| | | }, |
| | | // 保存动画 |
| | | clickSaveData() { |
| | | this.fpsList[this.fpsIndex] = this.pixelData; |
| | | this.$refs.popupAnimation.close(); |
| | | }, |
| | | changeColor() { |
| | | this.$refs.pickerColor.open(); |
| | | }, |
| | | clickColor(item) { |
| | | console.log('confirmColor---', item); |
| | | this.color = item; |
| | | this.$refs.pickerColor.close(); |
| | | }, |
| | | changeRatio() { |
| | | this.$refs.pickerRatio.open(); |
| | | }, |
| | | // 分辨率 |
| | | confirmRatio(e) { |
| | | console.log('confirmRatio---', e.value[0]); |
| | | const ratio = e.value[0].split('*'); |
| | | console.log('ratio---', ratio[0]); |
| | | this.height = Number(ratio[0]); |
| | | this.width = Number(ratio[1]); |
| | | this.canvasWidth = this.width * 10; |
| | | this.canvasHeight = this.height * 10; |
| | | // 重新初始化一维数组 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | console.log('pixelData---', this.pixelData); |
| | | }, |
| | | // 预先获取像素网格的位置信息 |
| | | getGridRect(cb) { |
| | | if (this.gridRect) { |
| | | cb(this.gridRect) |
| | | } else { |
| | | uni.createSelectorQuery().in(this).select('.pixel_grid').boundingClientRect(rect => { |
| | | this.gridRect = rect |
| | | cb(rect) |
| | | }).exec() |
| | | } |
| | | }, |
| | | // 计算触摸点对应的格子 |
| | | getCellByTouch(touch, cb) { |
| | | this.getGridRect(rect => { |
| | | if (!rect) return |
| | | const x = touch.clientX - rect.left |
| | | const y = touch.clientY - rect.top |
| | | const cellWidth = rect.width / this.width |
| | | const cellHeight = rect.height / this.height |
| | | const colIndex = Math.floor(x / cellWidth) |
| | | const rowIndex = Math.floor(y / cellHeight) |
| | | if ( |
| | | colIndex >= 0 && colIndex < this.width && |
| | | rowIndex >= 0 && rowIndex < this.height |
| | | ) { |
| | | cb({ |
| | | rowIndex, |
| | | colIndex |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchStart(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | }) |
| | | }, |
| | | handleTouchMove(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | // 只在格子变化时才触发 |
| | | if (!this.lastCell || this.lastCell.rowIndex !== cell.rowIndex || this.lastCell.colIndex !== |
| | | cell.colIndex) { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchEnd() { |
| | | this.lastCell = null |
| | | }, |
| | | handleClick(rowIndex, colIndex) { |
| | | console.log(rowIndex, colIndex) |
| | | // 计算一维数组的索引 |
| | | const index = rowIndex * this.width + colIndex |
| | | // 如果当前像素有颜色,则清除;如果没有颜色,则添加当前选择的颜色 |
| | | const currentColor = this.pixelData[index] |
| | | const newColor = currentColor ? '' : this.color |
| | | this.$set(this.pixelData, index, newColor) |
| | | }, |
| | | clear() { |
| | | ctx && ctx.clearRect(0, 0, 1000, 1000) |
| | | // 将所有像素点设置为空字符串 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 模版动画弹出框 |
| | | changeAnimation(e){ |
| | | //console.log('打开关闭模版动画弹窗触发---',e) |
| | | // ios设备不支持弹出框横屏切换 |
| | | if(e.show){ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('landscape-primary') |
| | | } |
| | | } |
| | | else{ |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait-primary') |
| | | } |
| | | } |
| | | }, |
| | | // 打开模版动画 |
| | | popupAnimationOpen(index){ |
| | | this.$refs.popupAnimation.open(); |
| | | this.fpsIndex = index |
| | | |
| | | if(this.fpsList[this.fpsIndex] == null){ |
| | | console.log('第一次编辑-----null') |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }else{ |
| | | console.log('已编辑过-----') |
| | | this.pixelData = this.fpsList[this.fpsIndex] |
| | | } |
| | | }, |
| | | // 关闭模版动画 |
| | | popupAnimationClose(){ |
| | | this.$refs.popupAnimation.close(); |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 24) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | this.adjustFpsData() |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsList = [] |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsList.length > this.total) { |
| | | this.fpsList = this.fpsList.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsList.length < this.total) { |
| | | const missing = this.total - this.fpsList.length; |
| | | this.fpsList = this.fpsList.concat(new Array(missing).fill(null)); |
| | | } |
| | | |
| | | // 如果 fpsList 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsList 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data)) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | // 生成点阵数据 |
| | | generateDotMatrix(char) { |
| | | return new Promise((resolve, reject) => { |
| | | |
| | | // 使用 uni-app 的 canvas context |
| | | ctx = uni.createCanvasContext('myCanvas', this); |
| | | |
| | | // 设置字体样式 |
| | | ctx.setFontSize(this.canvasHeight * 0.9); |
| | | ctx.setFillStyle('#000') |
| | | ctx.setTextAlign('center') |
| | | ctx.setTextBaseline('middle') |
| | | // 绘制文字 |
| | | // 计算文字位置 |
| | | // 水平居中 |
| | | const x = this.canvasWidth / 2; |
| | | // 垂直居中,稍微向上偏移一点 |
| | | const y = this.canvasHeight / 2; |
| | | |
| | | // 绘制文字 |
| | | ctx.fillText(char, x, y); |
| | | |
| | | // 绘制到画布 |
| | | ctx.draw(false, () => { |
| | | // 获取画布数据 |
| | | uni.canvasGetImageData({ |
| | | canvasId: 'myCanvas', |
| | | x: 0, |
| | | y: 0, |
| | | width: this.canvasWidth, |
| | | height: this.canvasHeight, |
| | | success: (res) => { |
| | | console.log(res) |
| | | const imageData = res.data; |
| | | // 生成一维数组 |
| | | const pixelArray = new Array(this.height * this.width).fill('') |
| | | |
| | | // 计算每个格子的像素大小 |
| | | const cellWidth = this.canvasWidth / this.width; |
| | | const cellHeight = this.canvasHeight / this.height; |
| | | |
| | | // 生成一维数组 |
| | | for (let y = 0; y < this.height; y++) { |
| | | for (let x = 0; x < this.width; x++) { |
| | | let blackPixels = 0; |
| | | let totalPixels = 0; |
| | | |
| | | // 对每个格子进行采样 |
| | | for (let py = 0; py < cellHeight; py++) { |
| | | for (let px = 0; px < cellWidth; px++) { |
| | | const pixelY = Math.floor(y * cellHeight + py); |
| | | const pixelX = Math.floor(x * cellWidth + px); |
| | | |
| | | const idx = (pixelY * this.canvasWidth + pixelX) * 4; |
| | | const r = imageData[idx]; |
| | | const g = imageData[idx + 1]; |
| | | const b = imageData[idx + 2]; |
| | | const a = imageData[idx + 3]; |
| | | |
| | | // 计算亮度 |
| | | const brightness = 0.3 * r + 0.59 * g + 0.11 * b; |
| | | |
| | | // 如果亮度小于阈值且透明度大于阈值,则认为是黑色 |
| | | if (brightness < 128 && a > 128) { |
| | | blackPixels++; |
| | | } |
| | | totalPixels++; |
| | | } |
| | | } |
| | | |
| | | // 如果黑色像素占比超过阈值,则设置为当前选择的颜色 |
| | | const isBlack = blackPixels / totalPixels > 0.3; |
| | | const arrayIndex = y * this.width + x; |
| | | pixelArray[arrayIndex] = isBlack ? this.color : ''; // 改为颜色值或空字符串 |
| | | } |
| | | } |
| | | |
| | | resolve(pixelArray); |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 修改添加文字方法 |
| | | async addText() { |
| | | uni.showModal({ |
| | | title: '添加文字', |
| | | content: '', |
| | | editable: true, |
| | | placeholderText: '请输入文字', |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | const text = res.content; |
| | | if (text.length > 0) { |
| | | try { |
| | | // 生成点阵数据 |
| | | const dotMatrix = await this.generateDotMatrix(text); |
| | | console.log('点阵数据:', dotMatrix); |
| | | this.pixelData = dotMatrix |
| | | } catch (error) { |
| | | console.error('生成点阵失败:', error); |
| | | uni.showToast({ |
| | | title: '生成点阵失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请输入文字', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | page { |
| | | -webkit-text-size-adjust: 100%; |
| | | text-size-adjust: 100%; |
| | | } |
| | | .item_pic_btn{ |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | background: #eee; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15rpx; |
| | | } |
| | | .color_list { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | gap: 30rpx; |
| | | box-sizing: border-box; |
| | | padding: 50rpx; |
| | | |
| | | .color_item { |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | border-radius: 5rpx; |
| | | } |
| | | } |
| | | |
| | | .controls { |
| | | position: absolute; |
| | | bottom: 10px; |
| | | left: 50px; |
| | | display: flex; |
| | | gap: 20px; |
| | | z-index: 9; |
| | | } |
| | | .animation_pixel{ |
| | | width: 100vw; |
| | | height: 100vh; |
| | | position: relative; |
| | | .back_btn{ |
| | | position: absolute; |
| | | left: 30px; |
| | | top: 20px; |
| | | z-index: 9999; |
| | | } |
| | | .canvas { |
| | | margin: 100rpx auto 0; |
| | | background: #ccc; |
| | | position: absolute; |
| | | z-index: 1; |
| | | left: -9999px; |
| | | top: -9999px; |
| | | } |
| | | .pixel_grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: var(--status-bar-height); |
| | | right: 0; |
| | | bottom: 0; |
| | | display: grid; |
| | | gap: 1px; |
| | | box-sizing: border-box; |
| | | background: #eee; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel_cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | |
| | | .pixel_cell_item { |
| | | width: 50%; |
| | | height: 50%; |
| | | aspect-ratio: 1/1; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | // ------------- |
| | | .footer_none { |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .footer_btn { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | } |
| | | .footer_btn2 { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | padding: 30rpx 40rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .temp_btn { |
| | | position: fixed; |
| | | left: 30rpx; |
| | | bottom: 60rpx; |
| | | right: 30rpx; |
| | | box-sizing: border-box; |
| | | height: 96rpx; |
| | | background: linear-gradient(142deg, #6FD2FF 0%, #268DFF 100%); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | } |
| | | |
| | | .spec_radio { |
| | | margin-top: 30rpx; |
| | | |
| | | &:first-child { |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .temp { |
| | | padding: 0 30rpx; |
| | | |
| | | .temp_item1 { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | |
| | | } |
| | | |
| | | .item {} |
| | | } |
| | | |
| | | .temp_item { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: flex-start; |
| | | flex-direction: column; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 30rpx; |
| | | box-sizing: border-box; |
| | | margin-top: 24rpx; |
| | | |
| | | .title { |
| | | flex-shrink: 0; |
| | | font-size: 30rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .item { |
| | | margin-top: 20rpx; |
| | | width: 100%; |
| | | font-size: 30rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | &.item_pic { |
| | | margin-top: 25rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view> |
| | | <!-- <uv-navbar title="动画编辑" :autoBack="true" bgColor="rgba(0,0,0,0)" :placeholder="false"></uv-navbar> --> |
| | | <view class="back_btn" @click="clickBack"> |
| | | <uv-icon name="arrow-leftward" color="#333" size="26rpx"></uv-icon> |
| | | </view> |
| | | <view class="controls"> |
| | | <uv-button type="primary" :plain="true" text="分辨率" @click="changeRatio()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="颜色" @click="changeColor()"></uv-button> |
| | | |
| | | <uv-button type="primary" :plain="true" text="添加文字" @click="addText()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="清除" @click="clear()"></uv-button> |
| | | |
| | | <uv-button type="primary" :plain="true" text="保存" @click="clickSaveData"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 文字生成图形 --> |
| | | <canvas canvas-id="myCanvas" id="myCanvas" class="canvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas> |
| | | |
| | | <!-- 修改像素网格显示 --> |
| | | <!-- <view class="pixel-grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove"> |
| | | <view class="pixel-row" v-for="(row, rowIndex) in Math.ceil(pixelData.length / width)" :key="rowIndex"> |
| | | <view class="pixel-cell" v-for="(item, colIndex) in width" :key="colIndex"> |
| | | <view v-if="pixelData[rowIndex * width + colIndex]" class="pixel-cell-item" :style="{ backgroundColor: pixelData[rowIndex * width + colIndex] }"> |
| | | {{rowIndex,colIndex}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <view class="pixel_grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove" |
| | | @touchend.prevent="handleTouchEnd" @touchcancel.prevent="handleTouchEnd" |
| | | :style="{ |
| | | gridTemplateRows: `repeat(${height}, 1fr)`, |
| | | gridTemplateColumns: `repeat(${width}, 1fr)` |
| | | }"> |
| | | <view class="pixel_cell" v-for="(cell, cellIndex) in pixelData" :key="cellIndex"> |
| | | <view class="pixel_cell_item" :style="{ backgroundColor: cell }"> |
| | | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 分辨率 --> |
| | | <uv-picker ref="pickerRatio" :columns="columnsRatio" @confirm="confirmRatio"></uv-picker> |
| | | |
| | | <!-- 颜色 --> |
| | | <uv-popup ref="pickerColor" mode="bottom" round="10" :closeable="true"> |
| | | <view class="color_list"> |
| | | <view class="color_item" v-for="(item, index) in colorList" :class="{ hover: color === item }" |
| | | :key="index" :style="{ backgroundColor: item }" @click="clickColor(item)"></view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- <uv-pick-color ref="pickerColor" @confirm="confirm"></uv-pick-color> --> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | let ctx = null |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | pixelData: [], |
| | | canvasData: [], |
| | | canvasWidth: 240, |
| | | canvasHeight: 120, |
| | | width: 24, |
| | | height: 12, |
| | | color: '#FF0000', |
| | | gridRect: null, // 存储像素网格的位置信息 |
| | | lastCell: null, // 防止重复绘制 |
| | | columnsRatio: [ |
| | | ['12*24', '16*32', '24*48', '32*64', '40*80'] |
| | | ], |
| | | // columnsRatio: [ |
| | | // ['12*24'] |
| | | // ], |
| | | colorList: [ |
| | | '#FF0000', |
| | | '#00FF00', |
| | | '#0000FF', |
| | | '#FFFF00', |
| | | '#FFA500', |
| | | '#800080', |
| | | '#00FFFF', |
| | | '#FFC0CB' |
| | | ], |
| | | fpsIndex: 0 |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | // App端强制横屏 |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('landscape-primary') |
| | | } |
| | | if (opt.fps) { |
| | | this.fpsIndex = opt.fps |
| | | } |
| | | |
| | | }, |
| | | onShow() { |
| | | // 改为一维数组初始化 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | onHide() { |
| | | console.log('离开页面恢复竖屏------onHide') |
| | | // 离开页面恢复竖屏 |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | onUnload() { |
| | | console.log('离开页面恢复竖屏------onUnload') |
| | | // 离开页面恢复竖屏 |
| | | if (typeof plus !== 'undefined') { |
| | | plus.screen.lockOrientation('portrait') |
| | | } |
| | | }, |
| | | methods: { |
| | | clickBack() { |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | }, |
| | | clickSaveData() { |
| | | const parsed = this.pixelData.map(item => { |
| | | if (item) { |
| | | return item.replace("#", ""); |
| | | } else { |
| | | return "000000"; |
| | | } |
| | | }); |
| | | console.log('parsed-------------', parsed.join('')) |
| | | //const snakeRows = this.toSnakeData(parsed, this.width) |
| | | //console.log('snakeRows-------------', snakeRows) |
| | | //const snakeString = this.mergeRowsToString(snakeRows) |
| | | //console.log('snakeString-------------', snakeString) |
| | | |
| | | // uni.setStorageSync('fpsParams', { |
| | | // shouldRefresh: true, |
| | | // fpsIndex: Number(this.fpsIndex), |
| | | // //data: snakeString |
| | | // data: parsed |
| | | // }); |
| | | |
| | | uni.setStorage({ |
| | | key: 'fpsParams', |
| | | data: { |
| | | shouldRefresh: true, |
| | | fpsIndex: Number(this.fpsIndex), |
| | | data: parsed |
| | | }, |
| | | success: () => { |
| | | console.log('存储成功'); |
| | | uni.navigateBack({ |
| | | delta: 1, |
| | | }); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('存储失败:', err); |
| | | } |
| | | }); |
| | | |
| | | |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | getData() { |
| | | uni.switchTab({ |
| | | url: '/pages/template/template', |
| | | success() { |
| | | console.log('跳转成功'); |
| | | }, |
| | | fail(err) { |
| | | console.error('跳转失败:', err); |
| | | } |
| | | }); |
| | | }, |
| | | changeColor() { |
| | | this.$refs.pickerColor.open(); |
| | | }, |
| | | clickColor(item) { |
| | | console.log('confirmColor---', item); |
| | | this.color = item; |
| | | this.$refs.pickerColor.close(); |
| | | }, |
| | | changeRatio() { |
| | | this.$refs.pickerRatio.open(); |
| | | }, |
| | | // 分辨率 |
| | | confirmRatio(e) { |
| | | console.log('confirmRatio---', e.value[0]); |
| | | const ratio = e.value[0].split('*'); |
| | | console.log('ratio---', ratio[0]); |
| | | this.height = Number(ratio[0]); |
| | | this.width = Number(ratio[1]); |
| | | this.canvasWidth = this.width * 10; |
| | | this.canvasHeight = this.height * 10; |
| | | // 重新初始化一维数组 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | console.log('pixelData---', this.pixelData); |
| | | }, |
| | | // 预先获取像素网格的位置信息 |
| | | getGridRect(cb) { |
| | | if (this.gridRect) { |
| | | cb(this.gridRect) |
| | | } else { |
| | | uni.createSelectorQuery().in(this).select('.pixel_grid').boundingClientRect(rect => { |
| | | this.gridRect = rect |
| | | cb(rect) |
| | | }).exec() |
| | | } |
| | | }, |
| | | // 计算触摸点对应的格子 |
| | | getCellByTouch(touch, cb) { |
| | | this.getGridRect(rect => { |
| | | if (!rect) return |
| | | const x = touch.clientX - rect.left |
| | | const y = touch.clientY - rect.top |
| | | const cellWidth = rect.width / this.width |
| | | const cellHeight = rect.height / this.height |
| | | const colIndex = Math.floor(x / cellWidth) |
| | | const rowIndex = Math.floor(y / cellHeight) |
| | | if ( |
| | | colIndex >= 0 && colIndex < this.width && |
| | | rowIndex >= 0 && rowIndex < this.height |
| | | ) { |
| | | cb({ |
| | | rowIndex, |
| | | colIndex |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchStart(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | }) |
| | | }, |
| | | handleTouchMove(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | // 只在格子变化时才触发 |
| | | if (!this.lastCell || this.lastCell.rowIndex !== cell.rowIndex || this.lastCell.colIndex !== |
| | | cell.colIndex) { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchEnd() { |
| | | this.lastCell = null |
| | | }, |
| | | handleClick(rowIndex, colIndex) { |
| | | console.log(rowIndex, colIndex) |
| | | // 计算一维数组的索引 |
| | | const index = rowIndex * this.width + colIndex |
| | | // 如果当前像素有颜色,则清除;如果没有颜色,则添加当前选择的颜色 |
| | | const currentColor = this.pixelData[index] |
| | | const newColor = currentColor ? '' : this.color |
| | | this.$set(this.pixelData, index, newColor) |
| | | }, |
| | | clear() { |
| | | ctx && ctx.clearRect(0, 0, 1000, 1000) |
| | | // 将所有像素点设置为空字符串 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 生成点阵数据 |
| | | generateDotMatrix(char) { |
| | | return new Promise((resolve, reject) => { |
| | | |
| | | // 使用 uni-app 的 canvas context |
| | | ctx = uni.createCanvasContext('myCanvas', this); |
| | | |
| | | // 设置字体样式 |
| | | ctx.setFontSize(this.canvasHeight * 0.9); |
| | | ctx.setFillStyle('#000') |
| | | ctx.setTextAlign('center') |
| | | ctx.setTextBaseline('middle') |
| | | // 绘制文字 |
| | | // 计算文字位置 |
| | | // 水平居中 |
| | | const x = this.canvasWidth / 2; |
| | | // 垂直居中,稍微向上偏移一点 |
| | | const y = this.canvasHeight / 2; |
| | | |
| | | // 绘制文字 |
| | | ctx.fillText(char, x, y); |
| | | |
| | | // 绘制到画布 |
| | | ctx.draw(false, () => { |
| | | // 获取画布数据 |
| | | uni.canvasGetImageData({ |
| | | canvasId: 'myCanvas', |
| | | x: 0, |
| | | y: 0, |
| | | width: this.canvasWidth, |
| | | height: this.canvasHeight, |
| | | success: (res) => { |
| | | console.log(res) |
| | | const imageData = res.data; |
| | | // 生成一维数组 |
| | | const pixelArray = new Array(this.height * this.width).fill('') |
| | | |
| | | // 计算每个格子的像素大小 |
| | | const cellWidth = this.canvasWidth / this.width; |
| | | const cellHeight = this.canvasHeight / this.height; |
| | | |
| | | // 生成一维数组 |
| | | for (let y = 0; y < this.height; y++) { |
| | | for (let x = 0; x < this.width; x++) { |
| | | let blackPixels = 0; |
| | | let totalPixels = 0; |
| | | |
| | | // 对每个格子进行采样 |
| | | for (let py = 0; py < cellHeight; py++) { |
| | | for (let px = 0; px < cellWidth; px++) { |
| | | const pixelY = Math.floor(y * cellHeight + py); |
| | | const pixelX = Math.floor(x * cellWidth + px); |
| | | |
| | | const idx = (pixelY * this.canvasWidth + |
| | | pixelX) * 4; |
| | | const r = imageData[idx]; |
| | | const g = imageData[idx + 1]; |
| | | const b = imageData[idx + 2]; |
| | | const a = imageData[idx + 3]; |
| | | |
| | | // 计算亮度 |
| | | const brightness = 0.3 * r + 0.59 * g + 0.11 * |
| | | b; |
| | | |
| | | // 如果亮度小于阈值且透明度大于阈值,则认为是黑色 |
| | | if (brightness < 128 && a > 128) { |
| | | blackPixels++; |
| | | } |
| | | totalPixels++; |
| | | } |
| | | } |
| | | |
| | | // 如果黑色像素占比超过阈值,则设置为当前选择的颜色 |
| | | const isBlack = blackPixels / totalPixels > 0.3; |
| | | const arrayIndex = y * this.width + x; |
| | | pixelArray[arrayIndex] = isBlack ? this.color : |
| | | ''; // 改为颜色值或空字符串 |
| | | } |
| | | } |
| | | |
| | | resolve(pixelArray); |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 修改添加文字方法 |
| | | async addText() { |
| | | uni.showModal({ |
| | | title: '添加文字', |
| | | content: '', |
| | | editable: true, |
| | | placeholderText: '请输入文字', |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | const text = res.content; |
| | | if (text.length > 0) { |
| | | try { |
| | | // 生成点阵数据 |
| | | const dotMatrix = await this.generateDotMatrix(text); |
| | | console.log('点阵数据:', dotMatrix); |
| | | this.pixelData = dotMatrix |
| | | } catch (error) { |
| | | console.error('生成点阵失败:', error); |
| | | uni.showToast({ |
| | | title: '生成点阵失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请输入文字', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .back_btn { |
| | | position: fixed; |
| | | left: 10rpx; |
| | | top: 26rpx; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .pixel_grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: var(--status-bar-height); |
| | | right: 0; |
| | | bottom: 0; |
| | | display: grid; |
| | | gap: 1px; |
| | | box-sizing: border-box; |
| | | background: #eee; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel_cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | |
| | | .pixel_cell_item { |
| | | width: 50%; |
| | | height: 50%; |
| | | aspect-ratio: 1/1; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .color_list { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | gap: 30rpx; |
| | | box-sizing: border-box; |
| | | padding: 50rpx; |
| | | |
| | | .color_item { |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | border-radius: 5rpx; |
| | | } |
| | | } |
| | | |
| | | .controls { |
| | | position: absolute; |
| | | bottom: 15rpx; |
| | | left: 20rpx; |
| | | display: flex; |
| | | gap: 20rpx; |
| | | z-index: 9; |
| | | } |
| | | |
| | | .canvas { |
| | | margin: 100rpx auto 0; |
| | | background: #ccc; |
| | | position: absolute; |
| | | z-index: 1; |
| | | left: -9999px; |
| | | top: -9999px; |
| | | } |
| | | |
| | | .pixel-grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | z-index: 8; |
| | | background: #fff; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel-row { |
| | | display: flex; |
| | | flex: 1; |
| | | |
| | | .pixel-cell { |
| | | flex: 1; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | border: 1rpx solid #eee; |
| | | box-sizing: border-box; |
| | | |
| | | .pixel-cell-item { |
| | | width: 50%; |
| | | aspect-ratio: 1/1; |
| | | color: #333 !important; |
| | | ; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view> |
| | | <view class="animation_pixel"> |
| | | <view class="back_btn" @click="popupAnimationClose"> |
| | | <uv-icon name="arrow-leftward" color="#333" size="30"></uv-icon> |
| | | </view> |
| | | <!-- 动画按钮 --> |
| | | <view class="controls"> |
| | | <uv-button type="primary" :plain="true" text="分辨率" @click="changeRatio()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="颜色" @click="changeColor()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="添加文字" @click="addText()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="清除" @click="clear()"></uv-button> |
| | | <uv-button type="primary" :plain="true" text="保存" @click="clickSaveData"></uv-button> |
| | | </view> |
| | | |
| | | <!-- 文字生成图形 --> |
| | | <canvas canvas-id="myCanvas" id="myCanvas" class="canvas" |
| | | :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas> |
| | | |
| | | <view class="pixel_grid" @touchstart.prevent="handleTouchStart" @touchmove.prevent="handleTouchMove" |
| | | @touchend.prevent="handleTouchEnd" @touchcancel.prevent="handleTouchEnd" :style="{ |
| | | gridTemplateRows: `repeat(${height}, 1fr)`, |
| | | gridTemplateColumns: `repeat(${width}, 1fr)` |
| | | }"> |
| | | <view class="pixel_cell" v-for="(cell, cellIndex) in pixelData" :key="cellIndex"> |
| | | <view class="pixel_cell_item" :style="{ backgroundColor: cell }"></view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 分辨率 --> |
| | | <uv-picker ref="pickerRatio" :columns="columnsRatio" @confirm="confirmRatio"></uv-picker> |
| | | |
| | | <!-- 颜色 --> |
| | | <uv-popup ref="pickerColor" mode="bottom" round="10" :closeable="true"> |
| | | <view class="color_list"> |
| | | <view class="color_item" v-for="(item, index) in colorList" :class="{ hover: color === item }" |
| | | :key="index" :style="{ backgroundColor: item }" @click="clickColor(item)"></view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | let ctx = null |
| | | export default { |
| | | data() { |
| | | return { |
| | | tempName: '', |
| | | pic: '', |
| | | specList: [{ |
| | | name: '12*24' |
| | | }, { |
| | | name: '16*24' |
| | | }, { |
| | | name: '18*24' |
| | | }], |
| | | specVal: '12*24', |
| | | specWidth: 24, |
| | | specHeight: 12, |
| | | modeList: [{ |
| | | name: '单个动画帧', |
| | | val: 0 |
| | | }, |
| | | { |
| | | name: '动画帧切换', |
| | | val: 1 |
| | | } |
| | | ], |
| | | modeVal: 0, |
| | | columnsTotal: [ |
| | | [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, |
| | | 28, 29, 30 |
| | | ] |
| | | ], |
| | | total: 1, //动画总帧数 |
| | | columnsSpeed: [ |
| | | [10, 20, 30, 40, 50, 60, 70, 80, 90] |
| | | ], |
| | | speed: 10, |
| | | cycle: 1, |
| | | fpsData: [], |
| | | fpsIndex: 1, //第几帧 |
| | | fpsIds: [], |
| | | |
| | | baseURL: '', |
| | | fileList1: [], |
| | | animationData: [], |
| | | fpsTotal: 1, |
| | | |
| | | |
| | | |
| | | // 模版动画 |
| | | pixelData: [], |
| | | canvasData: [], |
| | | canvasWidth: 240, |
| | | canvasHeight: 120, |
| | | width: 24, |
| | | height: 12, |
| | | color: '#FF0000', |
| | | gridRect: null, // 存储像素网格的位置信息 |
| | | lastCell: null, // 防止重复绘制 |
| | | columnsRatio: [ |
| | | ['12*24', '16*32', '24*48', '32*64', '40*80'] |
| | | ], |
| | | // columnsRatio: [ |
| | | // ['12*24'] |
| | | // ], |
| | | colorList: [ |
| | | '#FF0000', |
| | | '#00FF00', |
| | | '#0000FF', |
| | | '#FFFF00', |
| | | '#FFA500', |
| | | '#800080', |
| | | '#00FFFF', |
| | | '#FFC0CB' |
| | | ], |
| | | fpsIndex: 0, |
| | | fpsList: [], |
| | | tId: '', |
| | | tempInfo: {}, |
| | | titleName: '新增模版', |
| | | |
| | | |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | if (opt.index) { |
| | | this.fpsIndex = opt.index |
| | | console.log('fpsIndex--', this.fpsIndex) |
| | | } |
| | | }, |
| | | onShow() { |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | onHide() { |
| | | |
| | | }, |
| | | onUnload() { |
| | | |
| | | }, |
| | | methods: { |
| | | // 保存动画 |
| | | clickSaveData() { |
| | | this.fpsList[this.fpsIndex] = this.pixelData; |
| | | this.$refs.popupAnimation.close(); |
| | | }, |
| | | changeColor() { |
| | | this.$refs.pickerColor.open(); |
| | | }, |
| | | clickColor(item) { |
| | | console.log('confirmColor---', item); |
| | | this.color = item; |
| | | this.$refs.pickerColor.close(); |
| | | }, |
| | | changeRatio() { |
| | | this.$refs.pickerRatio.open(); |
| | | }, |
| | | // 分辨率 |
| | | confirmRatio(e) { |
| | | console.log('confirmRatio---', e.value[0]); |
| | | const ratio = e.value[0].split('*'); |
| | | console.log('ratio---', ratio[0]); |
| | | this.height = Number(ratio[0]); |
| | | this.width = Number(ratio[1]); |
| | | this.canvasWidth = this.width * 10; |
| | | this.canvasHeight = this.height * 10; |
| | | // 重新初始化一维数组 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | console.log('pixelData---', this.pixelData); |
| | | }, |
| | | // 预先获取像素网格的位置信息 |
| | | getGridRect(cb) { |
| | | if (this.gridRect) { |
| | | cb(this.gridRect) |
| | | } else { |
| | | uni.createSelectorQuery().in(this).select('.pixel_grid').boundingClientRect(rect => { |
| | | this.gridRect = rect |
| | | cb(rect) |
| | | }).exec() |
| | | } |
| | | }, |
| | | // 计算触摸点对应的格子 |
| | | getCellByTouch(touch, cb) { |
| | | this.getGridRect(rect => { |
| | | if (!rect) return |
| | | const x = touch.clientX - rect.left |
| | | const y = touch.clientY - rect.top |
| | | const cellWidth = rect.width / this.width |
| | | const cellHeight = rect.height / this.height |
| | | const colIndex = Math.floor(x / cellWidth) |
| | | const rowIndex = Math.floor(y / cellHeight) |
| | | if ( |
| | | colIndex >= 0 && colIndex < this.width && |
| | | rowIndex >= 0 && rowIndex < this.height |
| | | ) { |
| | | cb({ |
| | | rowIndex, |
| | | colIndex |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchStart(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | }) |
| | | }, |
| | | handleTouchMove(e) { |
| | | // 阻止默认行为 |
| | | e.preventDefault && e.preventDefault(); |
| | | |
| | | if (!e.touches.length) return |
| | | this.getCellByTouch(e.touches[0], cell => { |
| | | // 只在格子变化时才触发 |
| | | if (!this.lastCell || this.lastCell.rowIndex !== cell.rowIndex || this.lastCell.colIndex !== |
| | | cell.colIndex) { |
| | | this.lastCell = cell |
| | | this.handleClick(cell.rowIndex, cell.colIndex) |
| | | } |
| | | }) |
| | | }, |
| | | handleTouchEnd() { |
| | | this.lastCell = null |
| | | }, |
| | | handleClick(rowIndex, colIndex) { |
| | | console.log(rowIndex, colIndex) |
| | | // 计算一维数组的索引 |
| | | const index = rowIndex * this.width + colIndex |
| | | // 如果当前像素有颜色,则清除;如果没有颜色,则添加当前选择的颜色 |
| | | const currentColor = this.pixelData[index] |
| | | const newColor = currentColor ? '' : this.color |
| | | this.$set(this.pixelData, index, newColor) |
| | | }, |
| | | clear() { |
| | | ctx && ctx.clearRect(0, 0, 1000, 1000) |
| | | // 将所有像素点设置为空字符串 |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 模版动画弹出框 |
| | | changeAnimation(e) { |
| | | //console.log('打开关闭模版动画弹窗触发---',e) |
| | | // ios设备不支持弹出框横屏切换 |
| | | // if(e.show){ |
| | | // if (typeof plus !== 'undefined') { |
| | | // plus.screen.lockOrientation('landscape-primary') |
| | | // } |
| | | // } |
| | | // else{ |
| | | // if (typeof plus !== 'undefined') { |
| | | // plus.screen.lockOrientation('portrait') |
| | | // } |
| | | // } |
| | | }, |
| | | // 打开模版动画 |
| | | popupAnimationOpen(index) { |
| | | this.fpsIndex = index |
| | | if (this.fpsList[this.fpsIndex] == null) { |
| | | console.log('第一次编辑-----null') |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | } else { |
| | | console.log('已编辑过-----') |
| | | this.pixelData = this.fpsList[this.fpsIndex] |
| | | } |
| | | }, |
| | | // 关闭模版动画 |
| | | popupAnimationClose() { |
| | | this.$refs.popupAnimation.close(); |
| | | this.pixelData = new Array(this.height * this.width).fill('') |
| | | }, |
| | | // 屏幕规格 |
| | | changeSpec(e) { |
| | | const ratio = e.split('*'); |
| | | this.specHeight = Number(ratio[0]); |
| | | this.specWidth = Number(ratio[1]); |
| | | }, |
| | | // 偶数行数据反转 |
| | | toSnakeData(pixelData, cols = 24) { |
| | | const rows = [] |
| | | for (let i = 0; i < pixelData.length; i += cols) { |
| | | let row = pixelData.slice(i, i + cols) |
| | | if ((rows.length + 1) % 2 === 0) { |
| | | row.reverse() // 偶数行反转 |
| | | } |
| | | rows.push(row) |
| | | } |
| | | return rows |
| | | }, |
| | | // 二维数组转字符串 |
| | | mergeRowsToString(rows) { |
| | | const rowStrings = rows.map(r => r.join('')) // 每行合并 |
| | | return rowStrings.join('') // 所有行合并 |
| | | }, |
| | | // 生成一帧动画其他帧数据 |
| | | generateFrames(data, frameCount = 12) { |
| | | const cols = this.specWidth; // 每行 24 个 |
| | | const rows = this.specHeight; // 共 12 行 |
| | | const frames = []; |
| | | // 拆分成 12 行 |
| | | let grid = []; |
| | | for (let r = 0; r < rows; r++) { |
| | | grid.push(data.slice(r * cols, (r + 1) * cols)); |
| | | } |
| | | // 生成帧 |
| | | for (let f = 0; f < frameCount; f++) { |
| | | let frame = []; |
| | | // 每行左移 f 次 |
| | | for (let r = 0; r < rows; r++) { |
| | | let row = grid[r].slice(); // 拷贝一行 |
| | | let shifted = row.slice(f % cols).concat(row.slice(0, f % cols)); |
| | | frame = frame.concat(shifted); |
| | | } |
| | | frames.push(frame); |
| | | } |
| | | return frames; |
| | | }, |
| | | |
| | | clickSpeed() { |
| | | this.$refs.pickerSpeed.open(); |
| | | }, |
| | | confirmSpeed(e) { |
| | | console.log('confirmSpeed', e.value[0]); |
| | | this.speed = e.value[0] |
| | | }, |
| | | clickTotal() { |
| | | if (this.total != 1) { |
| | | this.$refs.pickerTotal.open(); |
| | | } |
| | | }, |
| | | confirmTotal(e) { |
| | | console.log('confirmTotal', e.value[0]); |
| | | this.total = e.value[0] |
| | | this.adjustFpsData() |
| | | }, |
| | | changeMode(e) { |
| | | this.fpsList = [] |
| | | this.fpsData = [] |
| | | if (e == 0) { |
| | | this.total = 1 |
| | | } else { |
| | | this.total = 2 |
| | | } |
| | | this.adjustFpsData() |
| | | }, |
| | | adjustFpsData() { |
| | | // 如果 fpsData 太长 → 截取 |
| | | if (this.fpsList.length > this.total) { |
| | | this.fpsList = this.fpsList.slice(0, this.total); |
| | | } |
| | | // 如果 fpsData 太短 → 补齐 null |
| | | else if (this.fpsList.length < this.total) { |
| | | const missing = this.total - this.fpsList.length; |
| | | this.fpsList = this.fpsList.concat(new Array(missing).fill(null)); |
| | | } |
| | | |
| | | // 如果 fpsList 太长 → 截取 |
| | | if (this.fpsData.length > this.total) { |
| | | this.fpsData = this.fpsData.slice(0, this.total); |
| | | } |
| | | // 如果 fpsList 太短 → 补齐 null |
| | | else if (this.fpsData.length < this.total) { |
| | | const missing = this.total - this.fpsData.length; |
| | | this.fpsData = this.fpsData.concat(new Array(missing).fill(null)); |
| | | } |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---', event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL + '/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----', JSON.parse(res.data)) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | // 生成点阵数据 |
| | | generateDotMatrix(char) { |
| | | return new Promise((resolve, reject) => { |
| | | |
| | | // 使用 uni-app 的 canvas context |
| | | ctx = uni.createCanvasContext('myCanvas', this); |
| | | |
| | | // 设置字体样式 |
| | | ctx.setFontSize(this.canvasHeight * 0.9); |
| | | ctx.setFillStyle('#000') |
| | | ctx.setTextAlign('center') |
| | | ctx.setTextBaseline('middle') |
| | | // 绘制文字 |
| | | // 计算文字位置 |
| | | // 水平居中 |
| | | const x = this.canvasWidth / 2; |
| | | // 垂直居中,稍微向上偏移一点 |
| | | const y = this.canvasHeight / 2; |
| | | |
| | | // 绘制文字 |
| | | ctx.fillText(char, x, y); |
| | | |
| | | // 绘制到画布 |
| | | ctx.draw(false, () => { |
| | | // 获取画布数据 |
| | | uni.canvasGetImageData({ |
| | | canvasId: 'myCanvas', |
| | | x: 0, |
| | | y: 0, |
| | | width: this.canvasWidth, |
| | | height: this.canvasHeight, |
| | | success: (res) => { |
| | | console.log(res) |
| | | const imageData = res.data; |
| | | // 生成一维数组 |
| | | const pixelArray = new Array(this.height * this.width).fill('') |
| | | |
| | | // 计算每个格子的像素大小 |
| | | const cellWidth = this.canvasWidth / this.width; |
| | | const cellHeight = this.canvasHeight / this.height; |
| | | |
| | | // 生成一维数组 |
| | | for (let y = 0; y < this.height; y++) { |
| | | for (let x = 0; x < this.width; x++) { |
| | | let blackPixels = 0; |
| | | let totalPixels = 0; |
| | | |
| | | // 对每个格子进行采样 |
| | | for (let py = 0; py < cellHeight; py++) { |
| | | for (let px = 0; px < cellWidth; px++) { |
| | | const pixelY = Math.floor(y * cellHeight + py); |
| | | const pixelX = Math.floor(x * cellWidth + px); |
| | | |
| | | const idx = (pixelY * this.canvasWidth + |
| | | pixelX) * 4; |
| | | const r = imageData[idx]; |
| | | const g = imageData[idx + 1]; |
| | | const b = imageData[idx + 2]; |
| | | const a = imageData[idx + 3]; |
| | | |
| | | // 计算亮度 |
| | | const brightness = 0.3 * r + 0.59 * g + 0.11 * |
| | | b; |
| | | |
| | | // 如果亮度小于阈值且透明度大于阈值,则认为是黑色 |
| | | if (brightness < 128 && a > 128) { |
| | | blackPixels++; |
| | | } |
| | | totalPixels++; |
| | | } |
| | | } |
| | | |
| | | // 如果黑色像素占比超过阈值,则设置为当前选择的颜色 |
| | | const isBlack = blackPixels / totalPixels > 0.3; |
| | | const arrayIndex = y * this.width + x; |
| | | pixelArray[arrayIndex] = isBlack ? this.color : |
| | | ''; // 改为颜色值或空字符串 |
| | | } |
| | | } |
| | | |
| | | resolve(pixelArray); |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | }, |
| | | |
| | | // 修改添加文字方法 |
| | | async addText() { |
| | | uni.showModal({ |
| | | title: '添加文字', |
| | | content: '', |
| | | editable: true, |
| | | placeholderText: '请输入文字', |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | const text = res.content; |
| | | if (text.length > 0) { |
| | | try { |
| | | // 生成点阵数据 |
| | | const dotMatrix = await this.generateDotMatrix(text); |
| | | console.log('点阵数据:', dotMatrix); |
| | | this.pixelData = dotMatrix |
| | | } catch (error) { |
| | | console.error('生成点阵失败:', error); |
| | | uni.showToast({ |
| | | title: '生成点阵失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请输入文字', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .color_list { |
| | | width: 100%; |
| | | display: flex; |
| | | justify-content: center; |
| | | flex-wrap: wrap; |
| | | gap: 30rpx; |
| | | box-sizing: border-box; |
| | | padding: 50rpx; |
| | | |
| | | .color_item { |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | border-radius: 5rpx; |
| | | } |
| | | } |
| | | |
| | | .controls { |
| | | position: absolute; |
| | | bottom: 10px; |
| | | left: 50px; |
| | | display: flex; |
| | | gap: 20px; |
| | | z-index: 9; |
| | | } |
| | | |
| | | .animation_pixel { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | position: relative; |
| | | |
| | | .back_btn { |
| | | position: absolute; |
| | | left: 30px; |
| | | top: 20px; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .canvas { |
| | | margin: 100rpx auto 0; |
| | | background: #ccc; |
| | | position: absolute; |
| | | z-index: 1; |
| | | left: -9999px; |
| | | top: -9999px; |
| | | } |
| | | |
| | | .pixel_grid { |
| | | position: fixed; |
| | | left: 0; |
| | | top: var(--status-bar-height); |
| | | right: 0; |
| | | bottom: 0; |
| | | display: grid; |
| | | gap: 1px; |
| | | box-sizing: border-box; |
| | | background: #eee; |
| | | touch-action: none; |
| | | -webkit-touch-callout: none; |
| | | -webkit-user-select: none; |
| | | user-select: none; |
| | | |
| | | .pixel_cell { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | background: #fff; |
| | | |
| | | .pixel_cell_item { |
| | | width: 50%; |
| | | height: 50%; |
| | | aspect-ratio: 1/1; |
| | | font-size: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="意见反馈" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="feedback"> |
| | | <uv-textarea v-model="content" placeholder="请输入反馈内容" height="400rpx" customStyle="background:#ECEFF6" textStyle="font-size:27rpx"></uv-textarea> |
| | | </view> |
| | | |
| | | <view class="feedback_btn" @click="clickFeedback"> |
| | | 提交反馈 |
| | | </view> |
| | | |
| | | <view class="foot_tips"> |
| | | 客服电话:{{phone}} |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { feekback,configPhone } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | content: '', |
| | | userId: '', |
| | | phone: '' |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.userId = uni.getStorageSync('userId'); |
| | | this.getConfigPhone() |
| | | }, |
| | | methods: { |
| | | getConfigPhone(){ |
| | | configPhone().then(res=>{ |
| | | console.log(res.msg) |
| | | this.phone = res.msg |
| | | }) |
| | | }, |
| | | clickFeedback(){ |
| | | if(!this.content || this.content.trim() == ''){ |
| | | uni.showToast({ |
| | | title: '请输入反馈内容', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | this.content = '' |
| | | return |
| | | } |
| | | feekback({ |
| | | content: this.content, |
| | | userId: this.userId |
| | | }).then(res=>{ |
| | | console.log(res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if(res.code == 200){ |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } |
| | | }) |
| | | |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .foot_tips{ |
| | | position: fixed; |
| | | left: 0; |
| | | bottom: 100rpx; |
| | | right: 0; |
| | | text-align: center; |
| | | font-size: 30rpx; |
| | | color: rgba(0,0,0,0.7); |
| | | line-height: 38rpx; |
| | | } |
| | | .feedback_btn{ |
| | | width: 423rpx; |
| | | height: 96rpx; |
| | | background: linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%); |
| | | box-shadow: 0rpx 12rpx 27rpx 0rpx rgba(56,151,243,0.38); |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | font-size: 30rpx; |
| | | color: #FFFFFF; |
| | | line-height: 96rpx; |
| | | text-align: center; |
| | | margin: 60rpx auto 0; |
| | | } |
| | | .feedback{ |
| | | margin: 20rpx 30rpx 0; |
| | | background: linear-gradient( 180deg, #FFFFFF 0%, rgba(255,255,255,0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67,147,248,0.1), -8rpx -8rpx 23rpx 0rpx rgba(255,255,255,0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | box-sizing: border-box; |
| | | padding:30rpx; |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view :class="deviceList.length == 0?'page_bg':'page_bg1'"> |
| | | <uv-navbar title="我的设备" :placeholder="true" leftIcon="" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="hi" v-if="deviceList.length == 0"> |
| | | HI,你来了~ |
| | | </view> |
| | | <view class="add_device" v-if="deviceList.length == 0"> |
| | | <navigator url="/pages/addDevice/addDevice" hover-class="none"> |
| | | <image class="ico_add" src="/static/img/btn_1.png" mode="widthFix"></image> |
| | | <view class="name"> |
| | | 添加设备 |
| | | </view> |
| | | </navigator> |
| | | </view> |
| | | |
| | | <view class="device_list" v-if="deviceList.length != 0"> |
| | | <view class="device_item" v-for="(item,index) in deviceList" :key="index"> |
| | | <view class="item"> |
| | | <image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | {{item.name}} |
| | | </view> |
| | | <view class="version"> |
| | | v {{item.versions}} |
| | | </view> |
| | | <view class="power"> |
| | | <view v-if="devKwh && dId == item.id"> |
| | | 电量{{devKwh * 10}}% |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="state" v-if="dId != item.id"> |
| | | 未连接 |
| | | </view> |
| | | |
| | | <view class="state green" v-if="dId == item.id"> |
| | | 已连接 |
| | | </view> |
| | | <view class="btn" v-if="dId != item.id"> |
| | | <uv-button type="primary" shape="circle" text="连接" size="small" |
| | | @click="createBLEConnection(item,index)"></uv-button> |
| | | <view class="btn_none"></view> |
| | | <uv-button type="primary" shape="circle" text="删除" size="small" |
| | | @click="clickEquipmentDel(item.id, index)"></uv-button> |
| | | </view> |
| | | <view class="btn" v-if="dId == item.id"> |
| | | <uv-button type="primary" shape="circle" text="关闭" size="small" |
| | | @click="clickCloseBLEConnection(index)"></uv-button> |
| | | </view> |
| | | |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="设备升级" size="small"></uv-button> |
| | | </view> --> |
| | | </view> |
| | | </view> |
| | | <view class="item_btn" v-if="dId == item.id"> |
| | | <uv-button type="primary" shape="circle" text="调节亮度" size="small" :plain="true" |
| | | @click="this.$refs.popupBRIG.open();"></uv-button> |
| | | <uv-button type="primary" shape="circle" text="发送LED" size="small" :plain="true" |
| | | @click="getTempleteList"></uv-button> |
| | | </view> |
| | | </view> |
| | | <!-- <view class="device_item"> |
| | | <view class="item"> |
| | | <image class="ph" src="/static/img/ph_1.png" mode="aspectFill"></image> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | 设备名称占位文字 |
| | | </view> |
| | | <view class="version"> |
| | | v2.21 |
| | | </view> |
| | | <view class="power"> |
| | | |
| | | </view> |
| | | <view class="state"> |
| | | 未连接 |
| | | </view> |
| | | <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="设备升级" size="small"></uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> --> |
| | | |
| | | <navigator class="add_device_small" url="/pages/addDevice/addDevice" hover-class="none" |
| | | :render-link="false"> |
| | | <image class="ico_add" src="/static/img/btn_1.png" mode="widthFix"></image> |
| | | <view class="name"> |
| | | 添加设备 |
| | | </view> |
| | | </navigator> |
| | | |
| | | </view> |
| | | |
| | | |
| | | <!-- 调节亮度 --> |
| | | <uv-popup ref="popupBRIG" mode="center" round="30rpx"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">调节亮度</view> |
| | | <view class="cont"> |
| | | <uv-slider v-model="brightness" :min="0" :max="9" :step="1" block-size="24" |
| | | block-color="#2979ff"></uv-slider> |
| | | </view> |
| | | <view class="btns_two"> |
| | | <view class="btn2"> |
| | | <uv-button type="primary" plain @click="this.$refs.popupBRIG.close();">关闭</uv-button> |
| | | </view> |
| | | <view class="btn2"> |
| | | <uv-button type="primary" @click="commandBRIG()">确定</uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | <!-- 模版列表 --> |
| | | <uv-popup ref="popupTemplete" mode="center" round="30rpx"> |
| | | <view class="popup_pwd"> |
| | | <view class="title">发送LED</view> |
| | | <view class="cont cont_led"> |
| | | |
| | | <view class="led_item" v-for="(item,index) in templeteData" :key="index"> |
| | | <view class="led_item_pic"> |
| | | <uv-image :src="baseURL+item.pic" width="160rpx" height="120rpx" :radius="5"></uv-image> |
| | | </view> |
| | | <view class="led_item_text"> |
| | | <view class="name"> |
| | | {{item.name}} |
| | | </view> |
| | | <view class="text"> |
| | | {{item.screen}} |
| | | </view> |
| | | </view> |
| | | <uv-button type="primary" shape="circle" size="small" @click="commandSendTemplate(item)">播放</uv-button> |
| | | </view> |
| | | |
| | | </view> |
| | | <view class="btns"> |
| | | <uv-button type="primary" plain @click="this.$refs.popupTemplete.close();">关闭</uv-button> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | equipmentList, |
| | | equipmentDel, |
| | | templeteList |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | searchIng: true, |
| | | userId: '', |
| | | deviceList: [], |
| | | |
| | | bluetoothList: [], //搜索到的蓝牙设备列表 |
| | | deviceId: '', //设备id |
| | | serviceId: '', //服务uuid |
| | | characteristicId1: '', //特征uuid |
| | | characteristicId2: '', |
| | | characteristicId3: '', |
| | | showPwdPopup: false, |
| | | password: '', //设置的密码 |
| | | checId: '', //智能壳的ID |
| | | batchNumber: '', //生产顺序号 |
| | | fileNames: [], //文件名称列表 |
| | | brightness: 3, //亮度 |
| | | nameIndex: 1, |
| | | name: '', |
| | | |
| | | ledName: '', // 名称 |
| | | screen: '', |
| | | ledTotal: 3, // 总帧数 |
| | | ledCurrent: 0, // 当前帧数 |
| | | ledData: [], |
| | | ledSpeed: '', //速度 |
| | | isLink: false, |
| | | listIndex: 0, |
| | | templeteData: [], |
| | | baseURL: '', |
| | | dId: '' ,//已连接的设备id, |
| | | devKwh: '', |
| | | platform: '' |
| | | } |
| | | }, |
| | | onLoad() { |
| | | //uni.setStorageSync('dId', ''); |
| | | this.userId = uni.getStorageSync('userId'); |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | const systemInfo = uni.getSystemInfoSync(); |
| | | if (systemInfo.platform === 'ios') { |
| | | // iOS 设备 |
| | | this.platform = 'ios' |
| | | } else if (systemInfo.platform === 'android') { |
| | | // Android 设备 |
| | | this.platform = 'android' |
| | | } |
| | | else{ |
| | | this.platform = '' |
| | | } |
| | | }, |
| | | onShow() { |
| | | this.getEquipmentList() |
| | | this.openBluetooth() |
| | | |
| | | const that = this |
| | | this.userId = uni.getStorageSync('userId'); |
| | | |
| | | this.dId = uni.getStorageSync('dId'); |
| | | if(this.dId){ |
| | | this.deviceId = uni.getStorageSync('deviceId'); |
| | | this.serviceId = uni.getStorageSync('serviceId'); |
| | | this.characteristicId1 = uni.getStorageSync('characteristicId1'); |
| | | this.characteristicId2 = uni.getStorageSync('characteristicId2'); |
| | | this.characteristicId3 = uni.getStorageSync('characteristicId3'); |
| | | this.checId = uni.getStorageSync('checId'); |
| | | this.password = uni.getStorageSync('password'); |
| | | this.startNotify() |
| | | } |
| | | |
| | | console.log('dId---',this.dId) |
| | | |
| | | //this.getTempleteList() |
| | | }, |
| | | onHide() { |
| | | console.log('onHide-----') |
| | | uni.$off('BLECharacteristic') |
| | | |
| | | uni.setStorageSync('dId', this.dId); |
| | | uni.setStorageSync('deviceId', this.deviceId); |
| | | uni.setStorageSync('serviceId', this.serviceId); |
| | | uni.setStorageSync('characteristicId1', this.characteristicId1); |
| | | uni.setStorageSync('characteristicId2', this.characteristicId2); |
| | | uni.setStorageSync('characteristicId3', this.characteristicId3); |
| | | uni.setStorageSync('checId', this.checId); |
| | | uni.setStorageSync('password', this.password); |
| | | |
| | | //this.clickCloseBLEConnection() |
| | | |
| | | //this.closeBluetooth() |
| | | //this.closeBLEConnection() |
| | | }, |
| | | onUnload() { |
| | | console.log('onUnload-----') |
| | | uni.$off('BLECharacteristic') |
| | | |
| | | //this.closeBluetooth() |
| | | //this.closeBLEConnection() |
| | | }, |
| | | methods: { |
| | | // 获取模版列表 |
| | | getTempleteList() { |
| | | uni.showLoading({ |
| | | title: '加载中...', |
| | | mask: true |
| | | }); |
| | | templeteList({ |
| | | userId: this.userId, |
| | | pageNum: 1, |
| | | pageSize: 99 |
| | | }).then(res => { |
| | | console.log('模版列表-------', res.rows) |
| | | uni.hideLoading(); |
| | | this.templeteData = res.rows |
| | | if (this.templeteData.length != 0) { |
| | | this.$refs.popupTemplete.open(); |
| | | } else { |
| | | uni.showToast({ |
| | | title: '暂无数据,请先添加模版!', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | |
| | | }) |
| | | }, |
| | | // 删除设备 |
| | | clickEquipmentDel(id, index) { |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: '删除后无法恢复,确定删除吗?', |
| | | success: (resa) => { |
| | | if (resa.confirm) { |
| | | console.log('用户点击确定'); |
| | | equipmentDel(id).then(res=>{ |
| | | console.log(res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if(res.code == 200){ |
| | | //this.getTempleteList() |
| | | this.deviceList.splice(index, 1) |
| | | } |
| | | }) |
| | | } else if (resa.cancel) { |
| | | console.log('用户点击取消'); |
| | | } |
| | | } |
| | | }); |
| | | }, |
| | | // 设备列表 |
| | | getEquipmentList() { |
| | | this.deviceList = [] |
| | | equipmentList({ |
| | | userId: this.userId, |
| | | code: this.platform, |
| | | pageNum: 1, |
| | | pageSize: 99 |
| | | }).then(res => { |
| | | console.log('设备列表------', res.rows) |
| | | this.deviceList = res.rows |
| | | }) |
| | | }, |
| | | // 初始化蓝牙 |
| | | openBluetooth() { |
| | | uni.openBluetoothAdapter({ |
| | | success: (res) => { |
| | | console.log('蓝牙初始化成功'); |
| | | this.onBLEStateChange() |
| | | }, |
| | | fail: (err) => { |
| | | console.log('蓝牙初始化失败', err); |
| | | } |
| | | }); |
| | | }, |
| | | // 关闭蓝牙 |
| | | closeBluetooth() { |
| | | uni.closeBluetoothAdapter({ |
| | | success: (res) => { |
| | | console.log(res) |
| | | } |
| | | }) |
| | | }, |
| | | // 断开与低功耗蓝牙设备的连接 |
| | | closeBLEConnection() { |
| | | const that = this |
| | | uni.closeBLEConnection({ |
| | | deviceId: this.deviceId, |
| | | success(res) { |
| | | console.log('断开与低功耗蓝牙设备的连接', res) |
| | | that.dId = '' |
| | | uni.setStorageSync('dId', ''); |
| | | } |
| | | }) |
| | | }, |
| | | // 关闭连接 |
| | | clickCloseBLEConnection() { |
| | | const that = this |
| | | console.log('that.deviceId-----', that.deviceId) |
| | | uni.closeBLEConnection({ |
| | | deviceId: that.deviceId, |
| | | success(res) { |
| | | console.log('关闭连接', res) |
| | | // 关闭连接清空已连接的设备id |
| | | that.dId = '' |
| | | uni.setStorageSync('dId', ''); |
| | | //that.isLink = false |
| | | //that.deviceList[that.listIndex].isLink = false |
| | | } |
| | | }) |
| | | }, |
| | | // 发送LED |
| | | commandSendTemplate(item) { |
| | | console.log('--------------', item); |
| | | |
| | | uni.showLoading({ |
| | | title: '发送中...', |
| | | mask: true |
| | | }); |
| | | |
| | | const name = 'A' + this.nameIndex++; |
| | | this.ledName = name; |
| | | this.screen = item.screen; |
| | | this.ledTotal = item.fpsTotal; |
| | | this.ledSpeed = item.speed |
| | | this.ledCurrent = 0; |
| | | |
| | | try { |
| | | this.ledData = JSON.parse(item.fpsData); |
| | | console.log('this.ledData------',this.ledData) |
| | | } catch (e) { |
| | | uni.hideLoading(); |
| | | console.error('fpsData 解析失败:', e); |
| | | uni.showToast({ |
| | | title: 'fpsData 解析失败', |
| | | duration: 2000 |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (this.ledData.length > 0) { |
| | | this.commandINST(this.ledData[this.ledCurrent]); |
| | | } else { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '无可发送的帧数据', |
| | | duration: 2000 |
| | | }); |
| | | console.warn('无可发送的帧数据'); |
| | | } |
| | | }, |
| | | // 连接 |
| | | createBLEConnection(item, index) { |
| | | const that = this |
| | | if (this.dId) { |
| | | uni.showToast({ |
| | | title: '请先关闭已连接的设备', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | return |
| | | } |
| | | this.deviceId = item.deviceId |
| | | this.serviceId = item.serviceId |
| | | this.characteristicId1 = item.characteristicId1 |
| | | this.characteristicId2 = item.characteristicId2 |
| | | this.characteristicId3 = item.characteristicId3 |
| | | this.checId = item.checId |
| | | this.password = item.password |
| | | |
| | | |
| | | uni.showLoading({ |
| | | title: '连接中...', |
| | | mask: true |
| | | }); |
| | | uni.createBLEConnection({ |
| | | deviceId: this.deviceId, |
| | | success: (res) => { |
| | | console.log('连接低功耗蓝牙设备', res); |
| | | this.startNotify() |
| | | // setTimeout(() => { |
| | | // that.setBaud() |
| | | // that.setBLEMTU() |
| | | // }, 500); |
| | | this.dId = item.id |
| | | |
| | | setTimeout(() => { |
| | | this.commandPOWR() |
| | | }, 1500); |
| | | }, |
| | | fail: (err) => { |
| | | //console.log('连接失败', err); |
| | | this.closeBLEConnection() |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '连接失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | // 设置波特率 |
| | | setBaud() { |
| | | var that = this |
| | | var hexStr = "06000900009600000801009F" |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId3, |
| | | value: this.hexStringToArrayBuffer(hexStr), |
| | | writeType: 'write', |
| | | success: (res) => { |
| | | console.log('设置波特率成功----', res); |
| | | //that.commandBIND() |
| | | uni.hideLoading(); |
| | | }, |
| | | fail: (err) => { |
| | | console.log('设置波特率失败----', err); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '设置波特率失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | // 设置蓝牙最大传输单元 |
| | | setBLEMTU() { |
| | | var that = this |
| | | uni.setBLEMTU({ |
| | | deviceId: this.deviceId, |
| | | mtu: 80, // 最大值一般 512,蓝牙 4.2 通常支持 247 |
| | | success: (res) => { |
| | | console.log('设置MTU成功:', res); |
| | | uni.hideLoading(); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('设置MTU失败:', err); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '设置MTU失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | commandCHEC() { |
| | | //555555555555555555557e41434845430095977e |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x43, 0x48, 0x45, 0x43]; // "CHEC"的ASCII码 |
| | | const reserved = 0x00; // 保留字节 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | reserved |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('CHEC data----AAAA--------', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 500); |
| | | }, |
| | | // 加密绑定号 发送绑定号 |
| | | commandBIND() { |
| | | // uni.showToast({ |
| | | // title: '发送绑定号', |
| | | // icon: 'none', |
| | | // mask: true |
| | | // }); |
| | | //console.log('checId---', this.checId) |
| | | // 59 5C 37 39 71 4D 56 65 5A 37 12 18 07 06 2A 18 36 1E 1E 06 |
| | | // 7e 41 42 49 4e 44 6d 71 41 45 95 4e 61 78 9e 39 49 12 15 13 71 42 94 50 50 17 7c c9 7e |
| | | //帧头+0x41+ BIND +加密绑定号+CRC校验码+帧尾 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x42, 0x49, 0x4E, 0x44]; // "BIND"的ASCII码 |
| | | const reserved = this.replaceScreenSizeInID(this.checId, this.password); // 密码 |
| | | //console.log('reserved----111', reserved) |
| | | const reservedId = this.encryptSmartShellID(reserved) |
| | | //console.log('reservedid--222', reservedId) |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | ...this.hexStringToArray(reservedId) |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | //console.log('BIND data----BBBB--------', this.hexArrayToHexString(data)) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 500); |
| | | }, |
| | | |
| | | //向低功耗蓝牙设备特征值中写入二进制数据 |
| | | writeBLECharacteristicValue(buffer) { |
| | | const that = this |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId2, |
| | | value: buffer, |
| | | writeType: 'write', |
| | | success: (res) => { |
| | | //console.log('写入指令发送成功----', res); |
| | | uni.hideLoading(); |
| | | }, |
| | | fail: (err) => { |
| | | //console.log('写入指令发送失败----', err); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '写入指令发送失败', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | // 调节亮度 |
| | | commandBRIG() { |
| | | console.log('brightness----', this.brightness) |
| | | //1.手机发指令:启动字节+帧头+0x41+ BRIG +亮度+CRC校验码+帧尾。 |
| | | //2.智能壳回指令:启动字节+帧头+0x41+ BRIG +返回标志+CRC校验码+帧尾。 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x42, 0x52, 0x49, 0x47]; // "BRIG"的ASCII码 |
| | | |
| | | //亮度:单字节,0,1,2,3。。。ASIC码 |
| | | const brightness = this.stringToASCIIByteArray(this.brightness.toString()) |
| | | //const brightness = 0x32 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command, |
| | | brightness |
| | | ]; |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | console.log('BRIG data----', data) |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 500); |
| | | |
| | | }, |
| | | //添加信息指令(INST) |
| | | commandINST(hexString) { |
| | | // 1.手机发指令:启动+帧头+包头段(0x43+简易绑定号+屏大小)+INST+信息属性+文件名+总帧数+当前帧*+块描述+块数据+CRC校验码+帧尾 |
| | | //注意:当前帧如果是0xff,则表示是结束帧,后面四个字节,第一个字节(表示速度),第二个字节(播放次数,0表示一直播放),后面二个字节保留;其他数字表示表示当前是第几帧。 |
| | | //2.智能壳指令:启动+帧头+包头段+INST+返回标志+加密地址+CRC校验码+帧尾 |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | //const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | //console.log('屏大小ID----',this.checId) |
| | | //console.log('屏大小----',screenSize,this.checId.slice(20, 24)) |
| | | const screenSize = [0x0C,0x18] |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码 |
| | | const infoAttribute = 0x02; // 信息属性: 0=一般信息 |
| | | |
| | | const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称 |
| | | //console.log('fileName----', this.ledName) |
| | | // 总帧数 |
| | | const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0') |
| | | .toUpperCase()) //[0x01]; //this.hexStringToArray('01') |
| | | //console.log('totalFrame----', totalFrame) |
| | | // 当前帧 |
| | | const currentFrame = this.hexStringToArray(this.ledCurrent.toString(16).padStart(2, '0').toUpperCase()) //0x00 |
| | | |
| | | // 块描述 |
| | | const tblockDesc = 0x00 |
| | | |
| | | // 块数据 |
| | | const blockDescription = this.hexStringToArray(hexString) |
| | | //const blockString = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF0000FF000000000000000000000000000000000000000000000000000000000000FF0000000000000000000000000000000000000000000000FF0000000000000000FF0000FF00000000000000000000000000000000FF0000FF000000000000000000FF0000000000FF0000FF00FF0000FF0000FF000000000000000000FF00FF0000FF000000000000000000FF00000000000000000000FF000000FF0000FF0000FF0000FF0000000000FF0000000000000000000000FF0000000000000000000000FF0000FF0000000000000000FF0000FF0000000000000000000000000000FF00FF000000000000000000000000FF00FF0000FF000000000000000000FF00000000000000FF0000FF0000FF0000FF0000FF0000000000000000FF0000FF000000000000000000000000FF0000000000FF0000000000000000000000000000000000000000FF0000000000000000000000000000000000FF0000000000000000000000000000000000FF00000000000000000000000000FF0000FF000000FF0000FF0000000000000000000000FF00000000000000FF0000FF000000FF00FF0000FF000000000000FF0000000000000000000000000000000000000000000000FF0000000000FF0000000000000000000000FF0000000000000000000000000000000000000000FF0000000000000000FF0000000000000000FF0000000000000000FF00000000000000FF0000FF0000FF0000FF000000000000000000FF00000000000000FF0000FF0000FF0000FF0000FF000000000000000000000000000000000000FF0000FF0000000000000000FF0000000000000000FF0000000000000000FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" |
| | | //const blockDescription = this.hexStringToArray(blockString) |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName, |
| | | ...totalFrame, |
| | | ...currentFrame, |
| | | tblockDesc, |
| | | ...blockDescription |
| | | ]; |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | //console.log(this.ledCurrent, '----INST data----', this.hexArrayToHexString(data)) |
| | | //const buffer = new Uint8Array(data).buffer; |
| | | console.log('第几帧-------',this.ledCurrent) |
| | | setTimeout(() => { |
| | | //this.writeBLECharacteristicValue(buffer) |
| | | this.sendBLEDataInChunks( |
| | | this.hexArrayToHexString(data) |
| | | ) |
| | | }, 50); |
| | | }, |
| | | // 发送结束针 |
| | | commandEnd() { |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x43; // 包类型 |
| | | // 使用新的简易绑定号生成函数 |
| | | const simpleBindId = this.generateSimpleBindId(); |
| | | |
| | | // 屏大小 (2 bytes): 从ID中获取 |
| | | const screenSize = this.hexStringToArray(this.checId.slice(20, 24)); |
| | | const packageHeader = [packageType, ...simpleBindId, ...screenSize]; |
| | | const command = [0x49, 0x4E, 0x53, 0x54]; // "INST"的ASCII码 |
| | | const infoAttribute = 0x02; // 信息属性: 0=一般信息 |
| | | |
| | | const fileName = this.stringToASCIIByteArray(this.ledName) //文件名称 |
| | | //console.log('fileName----', this.ledName) |
| | | // 总帧数 |
| | | const totalFrame = this.hexStringToArray(this.ledTotal.toString(16).padStart(2, '0').toUpperCase()) //[0x01]; //this.hexStringToArray('01') |
| | | //console.log('totalFrame----', totalFrame) |
| | | |
| | | //结束幕: |
| | | const ffdata = this.hexStringToArray('ff') |
| | | const speedData = this.hexStringToArray(this.ledSpeed.toString(16).padStart(2, '0').toUpperCase()) |
| | | |
| | | console.log('speedData------',speedData) |
| | | const ffend = this.hexStringToArray('000000') |
| | | |
| | | const endData = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | ...packageHeader, |
| | | ...command, |
| | | infoAttribute, |
| | | ...fileName, |
| | | ...totalFrame, |
| | | ...ffdata, |
| | | ...speedData, |
| | | ...ffend |
| | | ] |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const endcrc = this.calcCRC16(endData.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | endData.push((endcrc >> 8) & 0xFF); // CRC高8位 |
| | | endData.push(endcrc & 0xFF); // CRC低8位 |
| | | endData.push(frameHeader); // 帧尾 |
| | | //console.log('INST endData----', this.hexArrayToHexString(endData)) |
| | | |
| | | const endbuffer = new Uint8Array(endData).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(endbuffer) |
| | | }, 50); |
| | | }, |
| | | async sendBLEDataInChunks(hexStr) { |
| | | const maxBytes = 76 |
| | | const interval = 20 |
| | | const chunks = this.splitHexData(hexStr, maxBytes) |
| | | |
| | | //console.log(`📦 总共需要发送 ${chunks.length} 包,每包 ${maxBytes} 字节,间隔 ${interval}ms`) |
| | | |
| | | for (let i = 0; i < chunks.length; i++) { |
| | | const buffer = this.hexToBuffer(chunks[i]) |
| | | try { |
| | | await new Promise((resolve, reject) => { |
| | | uni.writeBLECharacteristicValue({ |
| | | deviceId: this.deviceId, |
| | | serviceId: this.serviceId, |
| | | characteristicId: this.characteristicId2, |
| | | value: buffer, |
| | | success: (res) => { |
| | | //console.log(`✅ 第 ${i + 1} 包发送成功`, res) |
| | | resolve() |
| | | }, |
| | | fail: (err) => { |
| | | //console.error(`❌ 第 ${i + 1} 包发送失败`, err) |
| | | uni.hideLoading(); |
| | | reject(err) |
| | | } |
| | | }) |
| | | }) |
| | | } catch (err) { |
| | | console.error(`🚫 发送中断,第 ${i + 1} 包发送失败`) |
| | | uni.hideLoading(); |
| | | break |
| | | } |
| | | // 延迟下一包发送 |
| | | await new Promise(r => setTimeout(r, interval)) |
| | | } |
| | | //console.log("🎉 数据发送完成") |
| | | }, |
| | | // 读取电量 |
| | | commandPOWR() { |
| | | console.log('读取电量POWR----', ) |
| | | // 构建指令数据 |
| | | const startBytes = new Array(10).fill(0x55); // 10个启动字节 |
| | | const frameHeader = 0x7E; // 帧头 |
| | | const packageType = 0x41; // 包类型 |
| | | const command = [0x50, 0x4F, 0x57, 0x52]; // "POWR"的ASCII码 |
| | | |
| | | // 组合数据 |
| | | const data = [ |
| | | ...startBytes, |
| | | frameHeader, |
| | | packageType, |
| | | ...command |
| | | ]; |
| | | |
| | | // 计算CRC校验码(从包头段开始计算) |
| | | const crc = this.calcCRC16(data.slice(11)); |
| | | |
| | | // 添加CRC和帧尾 |
| | | data.push((crc >> 8) & 0xFF); // CRC高8位 |
| | | data.push(crc & 0xFF); // CRC低8位 |
| | | data.push(frameHeader); // 帧尾 |
| | | //console.log('POWR----', data) |
| | | |
| | | const buffer = new Uint8Array(data).buffer; |
| | | setTimeout(() => { |
| | | this.writeBLECharacteristicValue(buffer) |
| | | }, 500); |
| | | }, |
| | | // 监听蓝牙连接状态 |
| | | onBLEStateChange(){ |
| | | const that = this |
| | | uni.onBLEConnectionStateChange(res => { |
| | | //console.log('监听蓝牙连接状态------------',res) |
| | | if (!res.connected) { |
| | | // uni.showToast({ |
| | | // title: '蓝牙设备已断开', |
| | | // icon: 'none', |
| | | // mask: true |
| | | // }); |
| | | that.dId = '' |
| | | uni.setStorageSync('dId', ''); |
| | | //console.log('蓝牙设备已断开------------',res) |
| | | //that.isLink = false |
| | | // for(let i = 0;i<that.deviceList.length;i++){ |
| | | // if(that.deviceList[i].isLink){ |
| | | // that.deviceList[i].isLink = false |
| | | // } |
| | | // } |
| | | } |
| | | }) |
| | | }, |
| | | // 开始监听 |
| | | startNotify() { |
| | | const that = this |
| | | uni.notifyBLECharacteristicValueChange({ |
| | | deviceId: that.deviceId, |
| | | serviceId: that.serviceId, |
| | | characteristicId: that.characteristicId1, |
| | | state: true, |
| | | success: () => { |
| | | console.log('开始监听------'); |
| | | setTimeout(() => { |
| | | that.setBaud() |
| | | if (uni.getSystemInfoSync().platform === 'android') { |
| | | that.setBLEMTU(); |
| | | } |
| | | else{ |
| | | uni.hideLoading(); |
| | | } |
| | | |
| | | }, 500); |
| | | |
| | | uni.$off('BLECharacteristic'); |
| | | uni.$on('BLECharacteristic', function(res) { |
| | | |
| | | //console.log('BLECharacteristic---------------index', res) |
| | | const resData = that.ab2hex(res.value); |
| | | const result = that.removeStartBytes(resData) |
| | | const resName = that.getCommandType(result); |
| | | //console.log("回调返回数据-------", resData); |
| | | //console.log('result-----------', result) |
| | | //console.log('resName----------', resName) |
| | | //uni.hideLoading(); |
| | | if (resName == 'CHEC') { |
| | | |
| | | } else if (resName == 'BIND') { |
| | | //that.isLink = true; |
| | | //that.deviceList[that.listIndex].isLink = true; |
| | | //that.commandPOWR() |
| | | } else if (resName == 'LIST') { |
| | | //that.parseFileList(result) |
| | | } else if (resName == 'BRIG') { |
| | | |
| | | } else if (resName == 'PLAY') { |
| | | |
| | | } else if (resName == 'INST') { |
| | | // 添加动画帧 |
| | | that.ledCurrent++ |
| | | if (that.ledCurrent < that.ledTotal) { |
| | | // uni.showLoading({ |
| | | // title: '发送中',// + that.ledCurrent, |
| | | // mask: true |
| | | // }); |
| | | that.commandINST(that.ledData[that.ledCurrent]); |
| | | } else if (that.ledCurrent == that.ledTotal) { |
| | | // uni.showLoading({ |
| | | // title: '发送结束帧', |
| | | // mask: true |
| | | // }); |
| | | that.commandEnd() |
| | | } |
| | | |
| | | } else if (resName == 'DALL') { |
| | | |
| | | } else if (resName == 'POWR') { |
| | | // 查询电量 |
| | | let capacityHex = result.slice(-8, -6); // "0a" |
| | | let capacity = parseInt(capacityHex, 16); // 转十进制 |
| | | that.devKwh = capacity |
| | | } else { |
| | | //that.isLink == false |
| | | uni.showToast({ |
| | | title: '返回数据错误', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | // 关闭蓝牙设备 |
| | | that.closeBLEConnection() |
| | | } |
| | | }) |
| | | }, |
| | | fail: (err) => { |
| | | //console.log('监听失败----', err); |
| | | uni.hideLoading(); |
| | | // uni.showToast({ |
| | | // title: '监听失败', |
| | | // icon: 'none', |
| | | // mask: true |
| | | // }); |
| | | that.dId = '' |
| | | uni.setStorageSync('dId', ''); |
| | | } |
| | | }); |
| | | }, |
| | | // 生成简易绑定号 |
| | | generateSimpleBindId() { |
| | | if (!this.checId || !this.password) { |
| | | //console.error("生成简易绑定号失败:checId或password不可用"); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '请先连接设备', |
| | | icon: 'none', |
| | | mask: true |
| | | }); |
| | | return; |
| | | } |
| | | // 从checId中提取生产顺序号(最后4字节,即8个字符) |
| | | const productionNumber = this.checId.slice(-8); // 生产顺序号 |
| | | // 生产顺序号+密码 |
| | | const simpleBindData = productionNumber + this.password; |
| | | // 用加密数字加密 |
| | | const encryptedSimpleBind = this.encryptSmartShellID(simpleBindData); |
| | | // 取前6字节作为简易绑定号 |
| | | const simpleBindId = this.hexStringToArray(encryptedSimpleBind.slice(0, 12)); |
| | | //console.log('简易绑定号生成----', simpleBindId); |
| | | return simpleBindId; |
| | | }, |
| | | // 判断并去除10个0x55启动字节(20个hex字符) |
| | | removeStartBytes(hexStr) { |
| | | // 10个0x55 = 20个hex字符 |
| | | const startBytes = '55'.repeat(10); // '55555555555555555555' |
| | | if (hexStr.startsWith(startBytes)) { |
| | | return hexStr.slice(20); // 去掉前20个字符 |
| | | } |
| | | return hexStr; |
| | | }, |
| | | // 返回指令类型 判断 |
| | | getCommandType(hexString) { |
| | | // 判断是否有 返回标志 单字节。0(0x30),工作无异常;1(0x31)工作有异常 |
| | | // 判断是否包含返回标志 30 或 31 |
| | | // 查找30或31的位置 |
| | | let returnFlag = ''; |
| | | let cmd = ''; |
| | | let packageType = hexString.slice(2, 4); |
| | | |
| | | if (packageType == '41') { |
| | | returnFlag = hexString.slice(12, 14); |
| | | const before8Chars = hexString.slice(4, 12); |
| | | cmd = before8Chars.match(/.{2}/g) |
| | | .map(h => String.fromCharCode(parseInt(h, 16))) |
| | | .join(''); |
| | | return cmd; |
| | | } else if (packageType == '43') { |
| | | returnFlag = hexString.slice(28, 30); |
| | | const before8Chars = hexString.slice(20, 28); |
| | | cmd = before8Chars.match(/.{2}/g) |
| | | .map(h => String.fromCharCode(parseInt(h, 16))) |
| | | .join(''); |
| | | return cmd; |
| | | } |
| | | //console.log('returnFlag------', returnFlag) |
| | | }, |
| | | // 将十六进制字符串转为 ArrayBuffer |
| | | hexToBuffer(hexStr) { |
| | | const typedArray = new Uint8Array(hexStr.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))) |
| | | return typedArray.buffer |
| | | }, |
| | | // 将大十六进制数据切分为每段 maxBytes 大小(单位:字节) |
| | | splitHexData(hexStr, maxBytes = 100) { |
| | | const result = [] |
| | | for (let i = 0; i < hexStr.length; i += maxBytes * 2) { |
| | | result.push(hexStr.slice(i, i + maxBytes * 2)) |
| | | } |
| | | return result |
| | | }, |
| | | // 指令ASCII码转换 |
| | | stringToASCIIByteArray(str) { |
| | | return str.split('').map(char => char.charCodeAt(0)); |
| | | }, |
| | | //16进制数组 转 16进制字符串 |
| | | hexArrayToHexString(hexArray) { |
| | | return hexArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); |
| | | }, |
| | | // 16进制字符串转16进制数组 |
| | | hexStringToArray(hexStr) { |
| | | if (!hexStr) return []; |
| | | return hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16)); |
| | | }, |
| | | // 16进制数据转二进数据 |
| | | hexStringToArrayBuffer(hexStr) { |
| | | const bytes = new Uint8Array(hexStr.match(/.{2}/g).map(byte => parseInt(byte, 16))); |
| | | return bytes.buffer; |
| | | }, |
| | | // 二进数据转16进制数据 |
| | | ab2hex(buffer) { |
| | | const hexArr = Array.prototype.map.call( |
| | | new Uint8Array(buffer), |
| | | function(bit) { |
| | | return ('00' + bit.toString(16)).slice(-2) |
| | | } |
| | | ) |
| | | return hexArr.join('') |
| | | }, |
| | | calcCRC16(data) { |
| | | // 创建查表 |
| | | const table = this.buildCRC16Table(); |
| | | let crc = 0; |
| | | // 计算CRC |
| | | for (let i = 0; i < data.length; i++) { |
| | | crc = ((crc << 8) & 0xFF00) ^ table[((crc >> 8) ^ data[i]) & 0xFF]; |
| | | } |
| | | return crc & 0xFFFF; |
| | | }, |
| | | buildCRC16Table() { |
| | | const table = new Array(256); |
| | | const poly = 0x1021; // CRC-CCITT多项式 |
| | | for (let i = 0; i < 256; i++) { |
| | | let nData = i << 8; |
| | | let nAccum = 0; |
| | | for (let j = 0; j < 8; j++) { |
| | | if ((nData ^ nAccum) & 0x8000) { |
| | | nAccum = (nAccum << 1) ^ poly; |
| | | } else { |
| | | nAccum <<= 1; |
| | | } |
| | | nData <<= 1; |
| | | } |
| | | table[i] = nAccum & 0xFFFF; |
| | | } |
| | | return table; |
| | | }, |
| | | // 密码置换掉ID号中"屏的大小",加密下发 |
| | | replaceScreenSizeInID(originalHexString, password4Digit) { |
| | | // 密码4个数字,每二个合起来一个字节,如3472变成 0x34 0x72 |
| | | const high = password4Digit.slice(0, 2); // "34" |
| | | const low = password4Digit.slice(2, 4); // "72" |
| | | |
| | | // 替换屏幕大小字段(第 10、11 个字节,即第20-23个字符) |
| | | const before = originalHexString.slice(0, 20); |
| | | const after = originalHexString.slice(24); |
| | | |
| | | return before + high + low + after; |
| | | }, |
| | | // 解密函数 |
| | | decryptSmartShellID(encryptedHex) { |
| | | const key = '2021101236011119680215001413714294505017'; |
| | | const decrypted = []; |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = encryptedHex.substr(i * 2, 2); |
| | | const encryptedByte = parseInt(byteHex, 16); |
| | | const keyHex = key.substr(i * 2, 2) |
| | | const keyByte = parseInt(keyHex, 16); |
| | | const decryptedByte = (encryptedByte - keyByte); |
| | | decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | return decrypted.join(''); |
| | | }, |
| | | // 加密函数 |
| | | encryptSmartShellID(encryptedHex) { |
| | | const key = '2021101236011119680215001413714294505017'; |
| | | const decrypted = []; |
| | | for (let i = 0; i < 20; i++) { |
| | | const byteHex = encryptedHex.substr(i * 2, 2); |
| | | const encryptedByte = parseInt(byteHex, 16); |
| | | const keyHex = key.substr(i * 2, 2) |
| | | const keyByte = parseInt(keyHex, 16); |
| | | const decryptedByte = (encryptedByte + keyByte); |
| | | decrypted.push(decryptedByte.toString(16).padStart(2, '0').toUpperCase()); |
| | | } |
| | | return decrypted.join(''); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .btns_two { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 0 40rpx; |
| | | margin-top: 50rpx; |
| | | |
| | | .btn2 { |
| | | width: 47%; |
| | | } |
| | | } |
| | | |
| | | .cont_led { |
| | | max-height: 700rpx; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .popup_pwd { |
| | | width: 680rpx; |
| | | box-sizing: border-box; |
| | | padding: 0 40rpx 50rpx; |
| | | |
| | | .led_item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 15rpx 0; |
| | | .led_item_text{ |
| | | width: 100%; |
| | | padding:0 20rpx; |
| | | } |
| | | .name { |
| | | font-size: 30rpx; |
| | | color: #333; |
| | | line-height: 50rpx; |
| | | } |
| | | .text{ |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | line-height: 50rpx; |
| | | } |
| | | |
| | | .btn { |
| | | flex-shrink: 0; |
| | | width: 120rpx; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | text-align: center; |
| | | font-weight: 500; |
| | | font-size: 32rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 42rpx; |
| | | padding: 50rpx 0 40rpx; |
| | | } |
| | | |
| | | .btns { |
| | | margin-top: 40rpx; |
| | | } |
| | | } |
| | | |
| | | .device_list { |
| | | padding: 0 30rpx; |
| | | |
| | | .device_item { |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | padding: 22rpx; |
| | | margin-top: 24rpx; |
| | | position: relative; |
| | | |
| | | .item_btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | margin-top: 20rpx; |
| | | border-top: 1rpx dashed #ddd; |
| | | padding-top: 22rpx; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | .item { |
| | | display: flex; |
| | | align-items: stretch; |
| | | justify-content: space-between; |
| | | position: relative; |
| | | } |
| | | |
| | | .ph { |
| | | display: block; |
| | | flex-shrink: 0; |
| | | width: 230rpx; |
| | | height: 170rpx; |
| | | border-radius: 20rpx; |
| | | margin-right: 24rpx; |
| | | } |
| | | |
| | | .info { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | position: relative; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 27rpx; |
| | | color: #000000; |
| | | line-height: 50rpx; |
| | | box-sizing: border-box; |
| | | padding-right: 80rpx; |
| | | } |
| | | |
| | | .version { |
| | | font-size: 26rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .power { |
| | | font-size: 26rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | .state { |
| | | position: absolute; |
| | | top: 10rpx; |
| | | right: 0; |
| | | font-size: 22rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 38rpx; |
| | | |
| | | &.green { |
| | | color: #2BA471; |
| | | } |
| | | } |
| | | |
| | | .btn { |
| | | position: absolute; |
| | | right: 0; |
| | | bottom: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: end; |
| | | |
| | | .btn_none { |
| | | flex-shrink: 0; |
| | | width: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .add_device_small { |
| | | margin: 30rpx auto 0; |
| | | background: rgba(255, 255, 255, 0.95) url('/static/img/bg_1.png') no-repeat center bottom; |
| | | background-size: 100% auto; |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.2), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20rpx 30rpx 10rpx; |
| | | |
| | | .ico_add { |
| | | width: 100rpx; |
| | | } |
| | | |
| | | .name { |
| | | font-weight: 400; |
| | | font-size: 31rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 40rpx; |
| | | margin-bottom: 10rpx; |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | .add_device { |
| | | margin: 60rpx auto 0; |
| | | width: 688rpx; |
| | | height: 319rpx; |
| | | background: rgba(255, 255, 255, 0.95) url('/static/img/bg_1.png') no-repeat center bottom; |
| | | background-size: 100% auto; |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | |
| | | .ico_add { |
| | | width: 220rpx; |
| | | } |
| | | |
| | | .name { |
| | | font-weight: 400; |
| | | font-size: 31rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 40rpx; |
| | | margin-bottom: 10rpx; |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | .hi { |
| | | text-align: center; |
| | | color: #fff; |
| | | font-weight: 500; |
| | | font-size: 42rpx; |
| | | line-height: 42rpx; |
| | | padding: 40rpx 0 0; |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: linear-gradient(3deg, rgba(114, 229, 255, 0) 14%, rgba(114, 229, 255, 0.2) 33%, rgba(114, 229, 255, 0.4) 48%, #72E5FF 62%, #3B74EE 100%); |
| | | } |
| | | |
| | | .page_bg1 { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="邀请好友" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="invite"> |
| | | <view class="name"> |
| | | app下载链接 |
| | | </view> |
| | | <view class="link"> |
| | | {{appDown}} |
| | | </view> |
| | | <view class="btn"> |
| | | <uv-button type="primary" text="复制" :plain="true" size="small" @click="copyText"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { |
| | | appDownload |
| | | } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | appDown: '' |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.getAppDownload() |
| | | }, |
| | | methods: { |
| | | getAppDownload() { |
| | | appDownload().then(res => { |
| | | console.log(res) |
| | | this.appDown = res.msg |
| | | }) |
| | | }, |
| | | copyText() { |
| | | uni.setClipboardData({ |
| | | data: this.appDown, |
| | | success: () => { |
| | | uni.showToast({ |
| | | title: '复制成功', |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .invite { |
| | | margin: 30rpx; |
| | | background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67, 147, 248, 0.1), -8rpx -8rpx 23rpx 0rpx rgba(255, 255, 255, 0.4); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | box-sizing: border-box; |
| | | padding: 30rpx; |
| | | position: relative; |
| | | |
| | | .name { |
| | | font-weight: 500; |
| | | font-size: 27rpx; |
| | | color: rgba(0, 0, 0, 0.9); |
| | | line-height: 46rpx; |
| | | } |
| | | |
| | | .link { |
| | | font-size: 27rpx; |
| | | color: rgba(0, 0, 0, 0.7); |
| | | line-height: 46rpx; |
| | | width: 500%; |
| | | margin-top: 20rpx; |
| | | } |
| | | |
| | | .btn { |
| | | position: absolute; |
| | | right: 30rpx; |
| | | bottom: 30rpx; |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title=" " :placeholder="true" leftIcon="" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="login_logo"> |
| | | <image class="ph" src="/static/img/logo.png" mode="widthFix"></image> |
| | | <view class="name"> |
| | | 欢迎,请登录 |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="register"> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入手机号/邮箱" border="none" v-model="userName"></uv-input> |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入密码" :password="isEye" border="none" v-model="password"></uv-input> |
| | | <view class="eye" @click="isEye = !isEye"> |
| | | <uv-icon v-if="isEye" name="eye-off" color="#666" size="40rpx"></uv-icon> |
| | | <uv-icon v-else name="eye" color="#666" size="40rpx"></uv-icon> |
| | | </view> |
| | | </view> |
| | | <view class="item_login"> |
| | | <navigator class="link" url="/pages/register/register" hover-class="none"> |
| | | 还没有账号?去注册 |
| | | </navigator> |
| | | </view> |
| | | <view class="item_agree"> |
| | | <uv-checkbox-group v-model="agree" size="28rpx" labelSize="28rpx"> |
| | | <uv-checkbox activeColor="#268DFF" name="agree" label="我已阅读并同意" labelColor="#666"></uv-checkbox> |
| | | <navigator class="link" url="/pages/policy/policy?id=1" hover-class="none">《用户协议》</navigator> |
| | | 和 |
| | | <navigator class="link" url="/pages/policy/policy?id=2" hover-class="none">《隐私政策》</navigator> |
| | | </uv-checkbox-group> |
| | | </view> |
| | | <view class="item_btn"> |
| | | <uv-button text="登录" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" size="large" @click="clickLogin()"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 协议同意弹出框 --> |
| | | <uv-popup ref="popupAgree" round="15" :safeAreaInsetBottom="false"> |
| | | <view class="popup_agree"> |
| | | <view class="title"> |
| | | 请阅读并同意以下条款 |
| | | </view> |
| | | <view class="text"> |
| | | 我已阅读并同意 |
| | | <navigator class="link" url="/myPages/agreement/agreement" hover-class="none">《服务协议》</navigator>和 |
| | | <navigator class="link" url="/myPages/privacy/privacy" hover-class="none">《隐私政策》</navigator> |
| | | </view> |
| | | <view class="btn"> |
| | | <view class="btn_item"> |
| | | <uv-button type="primary" shape="circle" text="取消" :plain="true" :custom-style="customStyle" @click="clickAgreeClose"></uv-button> |
| | | </view> |
| | | <view class="btn_item"> |
| | | <uv-button type="primary" shape="circle" text="同意" color="#268DFF" @click="clickAgreeOk"></uv-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uv-popup> |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { login,userInfo } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | userName: '18646195977', |
| | | password: '123456', |
| | | agree: [], |
| | | isEye: true |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | methods: { |
| | | clickAgreeClose(){ |
| | | this.$refs.popupAgree.close(); |
| | | }, |
| | | clickAgreeOk(){ |
| | | this.agree = ['agree'] |
| | | this.$refs.popupAgree.close(); |
| | | }, |
| | | clickLogin(){ |
| | | if(!this.userName){ |
| | | uni.showToast({ |
| | | title: '请输入账号', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.password){ |
| | | uni.showToast({ |
| | | title: '请输入密码', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(this.agree.length == 0){ |
| | | this.$refs.popupAgree.open(); |
| | | return |
| | | } |
| | | uni.showLoading({ |
| | | title: '登陆中' |
| | | }); |
| | | login({ |
| | | username: this.userName, |
| | | password: this.password |
| | | }).then(res=>{ |
| | | console.log('登陆----',res) |
| | | if(res.code == 200){ |
| | | uni.setStorageSync('token', res.token) |
| | | userInfo().then(res=>{ |
| | | uni.setStorageSync('userId', res.user.userId) |
| | | uni.hideLoading(); |
| | | uni.reLaunch({ |
| | | url: '/pages/index/index' |
| | | }); |
| | | }) |
| | | } |
| | | else{ |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | changeAgree(e){ |
| | | //console.log(e.detail.value.length) |
| | | this.agree = e.detail.value.length |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .popup_agree{ |
| | | width: 650rpx; |
| | | box-sizing: border-box; |
| | | padding:50rpx 40rpx; |
| | | .title{ |
| | | text-align: center; |
| | | font-size: 34rpx; |
| | | color:#000; |
| | | font-weight: bold; |
| | | line-height: 50rpx; |
| | | } |
| | | .text{ |
| | | color: #333; |
| | | font-size: 28rpx; |
| | | line-height: 40rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding:30rpx 0 50rpx; |
| | | .link{ |
| | | color: #3086F3; |
| | | font-size: 28rpx; |
| | | line-height: 40rpx; |
| | | } |
| | | } |
| | | .btn{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | .btn_item{ |
| | | width: 47%; |
| | | } |
| | | } |
| | | } |
| | | .register{ |
| | | padding:0 60rpx; |
| | | .item_agree{ |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.85); |
| | | line-height: 30rpx; |
| | | margin-top: 80rpx; |
| | | .link{ |
| | | font-size: 27rpx; |
| | | color: #3086F3; |
| | | } |
| | | .item_agree_info{ |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | .item_btn{ |
| | | margin-top: 40rpx; |
| | | } |
| | | .item{ |
| | | margin-top: 46rpx; |
| | | height: 100rpx; |
| | | background: #ECEFF6; |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-sizing: border-box; |
| | | padding:0 20rpx; |
| | | } |
| | | .item_login{ |
| | | padding:20rpx 0; |
| | | .link{ |
| | | font-size: 27rpx; |
| | | color: #3086F3; |
| | | line-height: 40rpx; |
| | | } |
| | | } |
| | | } |
| | | .login_logo{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | padding-top: 100rpx; |
| | | .ph{ |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | .name{ |
| | | font-weight: 600; |
| | | font-size: 38rpx; |
| | | color: rgba(0,0,0,0.65); |
| | | line-height: 50rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | } |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title=" " :placeholder="true" leftIcon="" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="my_info"> |
| | | <image class="ph" :src="baseURL + user.avatar" mode="aspectFill"></image> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | {{user.nickName}} |
| | | </view> |
| | | <view class="text"> |
| | | 微信号: {{user.wx||'-'}} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="my_nav"> |
| | | <navigator class="item" url="/pages/personal/personal" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 用户信息编辑 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="/pages/invite/invite" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 邀请好友 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="/pages/about/about" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 关于我们 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="/pages/feedback/feedback" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 意见反馈 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="/pages/set/set" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 设置 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | </view> |
| | | |
| | | <view class="logout"> |
| | | <uv-button type="primary" :hairline="true" :plain="true" text="退出登录" shape="circle" color="#999" @click="postLogout"></uv-button> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { userInfo,logout } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | baseURL: '', |
| | | user: {} |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | onShow() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | this.getUserInfo() |
| | | }, |
| | | methods: { |
| | | postLogout(){ |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: '确定退出登录吗?', |
| | | success: function (res) { |
| | | if (res.confirm) { |
| | | console.log('用户点击确定'); |
| | | logout().then(res=>{ |
| | | console.log('退出----',res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | uni.removeStorageSync('token'); |
| | | uni.removeStorageSync('userId'); |
| | | uni.reLaunch({ |
| | | url: '/pages/login/login' |
| | | }); |
| | | }) |
| | | } else if (res.cancel) { |
| | | console.log('用户点击取消'); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | }, |
| | | getUserInfo(){ |
| | | userInfo().then(res=>{ |
| | | console.log('用户信息----',res) |
| | | this.user = res.user |
| | | uni.setStorageSync('userId', res.user.userId) |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .logout{ |
| | | width: 380rpx; |
| | | margin: 60rpx auto 0; |
| | | } |
| | | .my_nav{ |
| | | margin-top: 70rpx; |
| | | padding:0 30rpx; |
| | | .item{ |
| | | width: 100%; |
| | | height: 120rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-sizing: border-box; |
| | | border-bottom: 1rpx solid #ddd; |
| | | box-sizing: border-box; |
| | | padding-right: 10rpx; |
| | | .name{ |
| | | font-size: 30rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 46rpx; |
| | | } |
| | | } |
| | | } |
| | | .my_info{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding:20rpx 30rpx 0; |
| | | .info{ |
| | | width: 100%; |
| | | .name{ |
| | | font-weight: 500; |
| | | font-size: 35rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 48rpx; |
| | | } |
| | | .text{ |
| | | font-weight: 400; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.7); |
| | | line-height: 46rpx; |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | .ph{ |
| | | flex-shrink: 0; |
| | | width: 140rpx; |
| | | height: 140rpx; |
| | | border-radius: 50%; |
| | | border:4rpx solid #fff; |
| | | margin-right: 30rpx; |
| | | } |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="个人资料" :autoBack="true" :placeholder="true" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff" leftIconColor="#fff"></uv-navbar> |
| | | |
| | | <view class="head_ph"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" :previewFullImage="false" |
| | | @afterRead="afterRead" @delete="deletePic"> |
| | | <!-- <image class="ico_head" :src="baseURL+avatar" mode="widthFix" |
| | | style="width: 200rpx;height: 200rpx;"></image> --> |
| | | </uv-upload> |
| | | <!-- <image class="camera" src="/static/img/ico_3.png" mode="widthFix"></image> --> |
| | | </view> |
| | | |
| | | <view class="user_info"> |
| | | <view class="item"> |
| | | <view class="name"> |
| | | 电话 |
| | | </view> |
| | | <view class="info"> |
| | | <uv-input placeholder="请输入电话" border="none" v-model="phonenumber" :clearable="true" ></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="item"> |
| | | <view class="name"> |
| | | 密码 |
| | | </view> |
| | | <view class="info_eye"> |
| | | <uv-input placeholder="请输入密码" :password="isEye" border="none" v-model="password" :clearable="true"></uv-input> |
| | | <view class="eye" @click="isEye = !isEye"> |
| | | <uv-icon v-if="isEye" name="eye-off" color="#666" size="40rpx"></uv-icon> |
| | | <uv-icon v-else name="eye" color="#666" size="40rpx"></uv-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="item"> |
| | | <view class="name"> |
| | | 昵称 |
| | | </view> |
| | | <view class="info"> |
| | | <uv-input placeholder="请输入昵称" border="none" v-model="nickName" :clearable="true"></uv-input> |
| | | </view> |
| | | </view> |
| | | <view class="item"> |
| | | <view class="name"> |
| | | 微信号 |
| | | </view> |
| | | <view class="info"> |
| | | <uv-input placeholder="请输入微信号" border="none" v-model="wx" :clearable="true"></uv-input> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | |
| | | <view class="footer_btn"> |
| | | <uv-button text="确认修改" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" size="large" @click="postUserProfile()"></uv-button> |
| | | </view> |
| | | |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { userInfo, userProfile } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | userId: '', |
| | | userName: '', |
| | | password: '', |
| | | nickName: '', |
| | | wx: '', |
| | | avatar: '', |
| | | phonenumber: '', |
| | | fileList1: [], |
| | | baseURL: '', |
| | | agree: 0, |
| | | isEye: true |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | this.getUserInfo() |
| | | }, |
| | | methods: { |
| | | postUserProfile(){ |
| | | if(this.fileList1.length == 0){ |
| | | uni.showToast({ |
| | | title: '请上传头像', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | // if(!this.avatar){ |
| | | // uni.showToast({ |
| | | // title: '请上传头像', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // return |
| | | // } |
| | | if(!this.phonenumber){ |
| | | uni.showToast({ |
| | | title: '请输入手机号码', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if (!/^1[3-9]\d{9}$/.test(this.phonenumber)) { |
| | | uni.showToast({ |
| | | title: '手机号码格式不正确', |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.password){ |
| | | uni.showToast({ |
| | | title: '请输入密码', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.nickName){ |
| | | uni.showToast({ |
| | | title: '请输入昵称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.wx){ |
| | | uni.showToast({ |
| | | title: '请输入微信号', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | userProfile({ |
| | | userId: this.userId, |
| | | phonenumber: this.phonenumber, |
| | | password: this.password, |
| | | nickName: this.nickName, |
| | | wx: this.wx, |
| | | avatar: this.avatar |
| | | }).then(res=>{ |
| | | console.log('修改用户信息---',res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | this.getUserInfo() |
| | | if(res.code == 200){ |
| | | uni.navigateBack({ |
| | | delta: 1 |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | getUserInfo(){ |
| | | userInfo().then(res=>{ |
| | | console.log(res.user) |
| | | this.fileList1 = [] |
| | | this.userId = res.user.userId |
| | | this.phonenumber = res.user.phonenumber |
| | | this.password = res.user.password |
| | | this.nickName = res.user.nickName |
| | | this.wx = res.user.wx |
| | | this.avatar = res.user.avatar |
| | | this.fileList1.push({url:this.baseURL+res.user.avatar}) |
| | | }) |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---',event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this.avatar = result |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL+'/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----',JSON.parse(res.data).fileName) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | ::v-deep .uv-upload__wrap__preview,::v-deep .uv-upload__button{ |
| | | margin: 0 !important; |
| | | } |
| | | ::v-deep .uv-upload__wrap__preview__image{ |
| | | border-radius: 50%; |
| | | } |
| | | .footer_btn{ |
| | | position: fixed; |
| | | left:0; |
| | | right: 0; |
| | | bottom: 0; |
| | | padding:35rpx; |
| | | } |
| | | .user_info{ |
| | | .item{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | border-bottom: 1rpx solid #ddd; |
| | | padding:0 35rpx; |
| | | height: 120rpx; |
| | | .name{ |
| | | width: 140rpx; |
| | | flex-shrink: 0; |
| | | font-size: 28rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 46rpx; |
| | | } |
| | | .info{ |
| | | width: 100%; |
| | | } |
| | | .info_eye{ |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | .eye{ |
| | | flex-shrink: 0; |
| | | margin-left: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .head_ph { |
| | | flex-shrink: 0; |
| | | width: 200rpx; |
| | | margin: 50rpx auto; |
| | | position: relative; |
| | | .camera{ |
| | | position: absolute; |
| | | right: 0; |
| | | bottom: 0; |
| | | width: 50rpx; |
| | | z-index: 9; |
| | | } |
| | | .name{ |
| | | text-align: center; |
| | | font-size: 28rpx; |
| | | line-height: 40rpx; |
| | | margin-top: 20rpx; |
| | | } |
| | | .ico_head { |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar :title="title" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="policy" v-if="id == 1"> |
| | | 请仔细阅读以下条款,为获得聚合包装提供的服务,服务使用人(以下称“用户”)应当审慎阅读、充分理解各条款内容,并按照页面上的提示完成全部的注册程序。用户在进行注册程序过程中点击“同意”按钮即表示用户完全接受本协议项下的全部条款。 |
| | | 请仔细阅读以下条款,为获得聚合包装提供的服务,服务使用人(以下称“用户”)应当审慎阅读、充分理解各条款内容,并按照页面上的提示完成全部的注册程序。用户在进行注册程序过程中点击“同意”按钮即表示用户完全接受本协议项下的全部条款。 |
| | | </view> |
| | | |
| | | <view class="policy" v-if="id == 2"> |
| | | <uv-parse :content="infoData"></uv-parse> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { content } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | infoData: '', |
| | | id: 0, |
| | | title: '' |
| | | } |
| | | }, |
| | | onLoad(opt) { |
| | | if(opt.id){ |
| | | this.id = opt.id |
| | | const titleMap = { |
| | | 1: '用户协议', |
| | | 2: '隐私协议' |
| | | } |
| | | this.title = titleMap[this.id] |
| | | } |
| | | this.getContent() |
| | | }, |
| | | methods: { |
| | | getContent(){ |
| | | content(1).then(res=>{ |
| | | console.log(res.data.content) |
| | | this.infoData = res.data.content |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | .policy{ |
| | | padding:20rpx 30rpx; |
| | | font-size: 28rpx; |
| | | line-height: 40rpx; |
| | | background: #fff; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title=" " :placeholder="true" :autoBack="true" bgColor="rgba(0,0,0,0)" |
| | | titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="head_ph"> |
| | | <uv-upload :fileList="fileList1" name="1" :maxCount="1" width="200rpx" height="200rpx" |
| | | @afterRead="afterRead" @delete="deletePic"> |
| | | <image class="ico_head" src="/static/img/ico_head.png" mode="widthFix" |
| | | style="width: 200rpx;height: 200rpx;"></image> |
| | | </uv-upload> |
| | | <view class="name"> |
| | | 上传头像 |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="register"> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入手机号/邮箱" border="none" v-model="userName"></uv-input> |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入密码" :password="isEye" border="none" v-model="password"></uv-input> |
| | | <view class="eye" @click="isEye = !isEye"> |
| | | <uv-icon v-if="isEye" name="eye-off" color="#666" size="40rpx"></uv-icon> |
| | | <uv-icon v-else name="eye" color="#666" size="40rpx"></uv-icon> |
| | | </view> |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入昵称" border="none" v-model="nickName"></uv-input> |
| | | </view> |
| | | <view class="item"> |
| | | <uv-input placeholder="请输入微信号" border="none" v-model="wx"></uv-input> |
| | | </view> |
| | | |
| | | <view class="item_login"> |
| | | <navigator class="link" url="/pages/login/login" hover-class="none"> |
| | | 已有账号?去登录 |
| | | </navigator> |
| | | </view> |
| | | <view class="item_agree"> |
| | | <checkbox-group @change="changeAgree"> |
| | | <label class="item_agree_info"> |
| | | <checkbox value="agree" :checked="false" style="transform:scale(0.75)" ></checkbox> |
| | | 同意本应用 |
| | | <navigator class="link" url="/pages/policy/policy?id=1" hover-class="none">《用户协议》</navigator> |
| | | 和 |
| | | <navigator class="link" url="/pages/policy/policy?id=2" hover-class="none">《隐私政策》</navigator> |
| | | </label> |
| | | </checkbox-group> |
| | | </view> |
| | | <view class="item_btn"> |
| | | <uv-button text="注册" color="linear-gradient( 142deg, #6FD2FF 0%, #268DFF 100%)" shape="circle" size="large" @click="clickRegister()"></uv-button> |
| | | </view> |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { register } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | userName: '', |
| | | password: '', |
| | | nickName: '', |
| | | wx: '', |
| | | avatar: '', |
| | | fileList1: [], |
| | | baseURL: '', |
| | | agree: 0, |
| | | isEye: true |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | console.log(this.baseURL) |
| | | |
| | | }, |
| | | methods: { |
| | | clickRegister(){ |
| | | if(this.fileList1.length == 0){ |
| | | uni.showToast({ |
| | | title: '请上传头像', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.userName){ |
| | | uni.showToast({ |
| | | title: '请输入账号', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.password){ |
| | | uni.showToast({ |
| | | title: '请输入密码', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.nickName){ |
| | | uni.showToast({ |
| | | title: '请输入昵称', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | if(!this.agree){ |
| | | uni.showToast({ |
| | | title: '请勾选用户协议和隐私政策', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | |
| | | register({ |
| | | userName: this.userName, |
| | | password: this.password, |
| | | nickName: this.nickName, |
| | | wx: this.wx, |
| | | avatar: this.fileList1[0].url, |
| | | }).then(res=>{ |
| | | console.log('注册----',res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if(res.code == 200){ |
| | | uni.reLaunch({ |
| | | url: '/pages/login/login' |
| | | }); |
| | | } |
| | | else{ |
| | | |
| | | } |
| | | }) |
| | | }, |
| | | changeAgree(e){ |
| | | //console.log(e.detail.value.length) |
| | | this.agree = e.detail.value.length |
| | | }, |
| | | // 删除图片 |
| | | deletePic(event) { |
| | | this[`fileList${event.name}`].splice(event.index, 1) |
| | | }, |
| | | // 新增图片 |
| | | async afterRead(event) { |
| | | console.log('event---',event) |
| | | // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式 |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[`fileList${event.name}`].length |
| | | lists.map((item) => { |
| | | this[`fileList${event.name}`].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | let item = this[`fileList${event.name}`][fileListLen] |
| | | this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, { |
| | | status: 'success', |
| | | message: '', |
| | | url: result |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | }, |
| | | uploadFilePromise(url) { |
| | | return new Promise((resolve, reject) => { |
| | | let a = uni.uploadFile({ |
| | | url: this.baseURL+'/common/uploadFileByAnny', // 仅为示例,非真实的接口地址 |
| | | filePath: url, |
| | | name: 'file', |
| | | success: (res) => { |
| | | console.log('图片-----',JSON.parse(res.data).fileName) |
| | | setTimeout(() => { |
| | | resolve(JSON.parse(res.data).fileName) |
| | | }, 1000) |
| | | } |
| | | }); |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | |
| | | .register{ |
| | | padding:0 60rpx; |
| | | .item_agree{ |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.85); |
| | | line-height: 30rpx; |
| | | margin-top: 80rpx; |
| | | .link{ |
| | | font-size: 27rpx; |
| | | color: #3086F3; |
| | | } |
| | | .item_agree_info{ |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | .item_btn{ |
| | | margin-top: 40rpx; |
| | | } |
| | | .item{ |
| | | margin-top: 46rpx; |
| | | height: 100rpx; |
| | | background: #ECEFF6; |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | box-sizing: border-box; |
| | | padding:0 20rpx; |
| | | } |
| | | .item_login{ |
| | | padding:20rpx 0; |
| | | .link{ |
| | | font-size: 27rpx; |
| | | color: #3086F3; |
| | | line-height: 40rpx; |
| | | } |
| | | } |
| | | } |
| | | .head_ph { |
| | | flex-shrink: 0; |
| | | width: 200rpx; |
| | | margin: 50rpx auto; |
| | | .name{ |
| | | text-align: center; |
| | | font-size: 28rpx; |
| | | line-height: 40rpx; |
| | | margin-top: 20rpx; |
| | | } |
| | | .ico_head { |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | border-radius: 50%; |
| | | |
| | | } |
| | | } |
| | | |
| | | .page_bg { |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background: url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="设置" :autoBack="true" :placeholder="true" leftIconColor="#fff" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="set_nav"> |
| | | <navigator class="item" url="/pages/policy/policy?id=1" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 用户协议 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="/pages/policy/policy?id=2" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | 隐私政策 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | <navigator class="item" url="#" hover-class="none" :render-link="false"> |
| | | <view class="name"> |
| | | APP升级 |
| | | </view> |
| | | <uv-icon name="arrow-right" color="rgba(0, 0, 0, 0.40)" size="36rpx"></uv-icon> |
| | | </navigator> |
| | | </view> |
| | | <view class="set_btn" @click="postUnRegistry"> |
| | | 注销用户 |
| | | </view> |
| | | |
| | | <view class="foot_tips"> |
| | | APP版本:V1.2 |
| | | </view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { unRegistry } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | |
| | | } |
| | | }, |
| | | onLoad() { |
| | | |
| | | }, |
| | | methods: { |
| | | postUnRegistry(){ |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: '确定注销用户吗?', |
| | | success: function (res) { |
| | | if (res.confirm) { |
| | | console.log('用户点击确定'); |
| | | unRegistry().then(res=>{ |
| | | console.log('注销----',res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | uni.removeStorageSync('token'); |
| | | uni.removeStorageSync('userId'); |
| | | uni.reLaunch({ |
| | | url: '/pages/login/login' |
| | | }); |
| | | }) |
| | | } else if (res.cancel) { |
| | | console.log('用户点击取消'); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .set_btn{ |
| | | width: 386rpx; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 96rpx; |
| | | height: 96rpx; |
| | | background: #FFFFFF; |
| | | border-radius: 385rpx 385rpx 385rpx 385rpx; |
| | | border: 2rpx solid rgba(0,0,0,0.15); |
| | | text-align: center; |
| | | margin: 60rpx auto; |
| | | } |
| | | .set_nav{ |
| | | padding:0 30rpx; |
| | | .item{ |
| | | height: 138rpx; |
| | | background: linear-gradient( 180deg, #FFFFFF 0%, rgba(255,255,255,0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67,147,248,0.1), -8rpx -8rpx 23rpx 0rpx rgba(255,255,255,0.4); |
| | | border-radius: 23rpx 23rpx 23rpx 23rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding:0 30rpx; |
| | | margin-top: 24rpx; |
| | | .name{ |
| | | font-weight: 500; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 46rpx; |
| | | } |
| | | } |
| | | } |
| | | .foot_tips{ |
| | | position: fixed; |
| | | left: 0; |
| | | bottom: 100rpx; |
| | | right: 0; |
| | | text-align: center; |
| | | font-size: 30rpx; |
| | | color: rgba(0,0,0,0.7); |
| | | line-height: 38rpx; |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | min-height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="page_bg"> |
| | | <uv-navbar title="模版" :placeholder="true" leftIcon="" bgColor="rgba(0,0,0,0)" titleStyle="color:#fff"></uv-navbar> |
| | | |
| | | <view class="temp_add" @click="gotoTemp"> |
| | | <uv-icon name="plus" color="#3086F3" size="30rpx"></uv-icon> |
| | | <view class="name"> |
| | | 新增动画 |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="temp_list" v-if="listData.length > 0"> |
| | | <view class="item" v-for="(item,index) in listData" :key="index"> |
| | | <view class="pic" > |
| | | <uv-image :src="baseURL+item.pic" width="200rpx" height="150rpx" :radius="5"></uv-image> |
| | | </view> |
| | | <view class="info"> |
| | | <view class="name"> |
| | | {{item.name}} |
| | | </view> |
| | | <view class="text"> |
| | | {{item.screen}} |
| | | </view> |
| | | </view> |
| | | <view class="btn"> |
| | | <uv-button type="primary" :plain="true" shape="circle" text="编辑" size="small" @click="clickTempleteEdit(item.id)"></uv-button> |
| | | <uv-button type="primary" :plain="true" shape="circle" text="删除" size="small" custom-style="marginTop:20rpx" @click="clickTempleteDel(item.id)"></uv-button> |
| | | </view> |
| | | <!-- <view class="btn"> |
| | | <uv-button type="primary" shape="circle" text="编辑" size="small" @click="gotoTemp"></uv-button> |
| | | </view> --> |
| | | </view> |
| | | </view> |
| | | <uv-empty v-if="listData.length == 0" mode="list" marginTop="300rpx" iconColor="#eee" textColor="#ddd"></uv-empty> |
| | | |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { templeteList,templeteDel } from '@/api/api.js' |
| | | export default { |
| | | data() { |
| | | return { |
| | | userId: '', |
| | | listData: [], |
| | | baseURL: '', |
| | | } |
| | | }, |
| | | onLoad() { |
| | | this.baseURL = uni.$uv.http.config.baseURL |
| | | }, |
| | | onShow() { |
| | | this.userId = uni.getStorageSync('userId'); |
| | | this.getTempleteList() |
| | | }, |
| | | methods: { |
| | | // 编辑 |
| | | clickTempleteEdit(id){ |
| | | uni.navigateTo({ |
| | | url: '/pages/addTemplate/addTemplate?id='+id |
| | | }); |
| | | }, |
| | | // 删除 |
| | | clickTempleteDel(id){ |
| | | uni.showModal({ |
| | | title: '提示', |
| | | content: '删除后无法恢复,确定删除吗?', |
| | | success: (resa) => { |
| | | if (resa.confirm) { |
| | | console.log('用户点击确定'); |
| | | templeteDel(id).then(res=>{ |
| | | console.log(res) |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | if(res.code == 200){ |
| | | this.getTempleteList() |
| | | } |
| | | }) |
| | | } else if (resa.cancel) { |
| | | console.log('用户点击取消'); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | }, |
| | | getTempleteList(){ |
| | | templeteList({ |
| | | userId: this.userId, |
| | | pageNum: 1, |
| | | pageSize: 99 |
| | | }).then(res=>{ |
| | | console.log('模版list-----------',res) |
| | | if(res.code == 200){ |
| | | this.listData = res.rows |
| | | } |
| | | else{ |
| | | uni.showToast({ |
| | | title: res.msg, |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }) |
| | | }, |
| | | gotoTemp(){ |
| | | uni.navigateTo({ |
| | | url: '/pages/addTemplate/addTemplate' |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .temp_list{ |
| | | padding:0 30rpx; |
| | | .item{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | min-height: 160rpx; |
| | | background: linear-gradient( 180deg, #FFFFFF 0%, rgba(255,255,255,0.5) 100%); |
| | | box-shadow: 8rpx 8rpx 23rpx 0rpx rgba(67,147,248,0.1), -8rpx -8rpx 23rpx 0rpx rgba(255,255,255,0.4); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #FFFFFF; |
| | | margin-bottom: 20rpx; |
| | | box-sizing: border-box; |
| | | padding:15px 30rpx; |
| | | .pic{ |
| | | width: 200rpx; |
| | | flex-shrink: 0; |
| | | margin-right: 25rpx; |
| | | } |
| | | .info{ |
| | | width: 100%; |
| | | .name{ |
| | | font-weight: 500; |
| | | font-size: 28rpx; |
| | | color: rgba(0,0,0,0.9); |
| | | line-height: 40rpx; |
| | | } |
| | | .text{ |
| | | font-weight: 600; |
| | | font-size: 27rpx; |
| | | color: rgba(0,0,0,0.7); |
| | | line-height: 40rpx; |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | .btn{ |
| | | flex-shrink: 0; |
| | | margin-left: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | .temp_add{ |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin:10rpx 30rpx 20rpx; |
| | | height: 90rpx; |
| | | background: #FFFFFF; |
| | | box-shadow: 0rpx 12rpx 27rpx 0rpx rgba(56,151,243,0.38); |
| | | border-radius: 20rpx; |
| | | border: 2rpx solid #3086F3; |
| | | .name{ |
| | | font-size: 28rpx; |
| | | color: #3086F3; |
| | | line-height: 42rpx; |
| | | margin-left: 6rpx; |
| | | } |
| | | } |
| | | .page_bg{ |
| | | width: 100%; |
| | | height: calc(100vh - var(--window-bottom)); |
| | | background:url('/static/img/bg_2.jpg') no-repeat center top; |
| | | background-size: 100% auto; |
| | | } |
| | | </style> |
| New file |
| | |
| | | uni.addInterceptor({ |
| | | returnValue (res) { |
| | | if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { |
| | | return res; |
| | | } |
| | | return new Promise((resolve, reject) => { |
| | | res.then((res) => { |
| | | if (!res) return resolve(res) |
| | | return res[0] ? reject(res[0]) : resolve(res[1]) |
| | | }); |
| | | }); |
| | | }, |
| | | }); |
| New file |
| | |
| | | /** |
| | | * 这里是uni-app内置的常用样式变量 |
| | | * |
| | | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 |
| | | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App |
| | | * |
| | | */ |
| | | |
| | | /** |
| | | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 |
| | | * |
| | | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 |
| | | */ |
| | | |
| | | /* 颜色变量 */ |
| | | |
| | | /* 行为相关颜色 */ |
| | | $uni-color-primary: #007aff; |
| | | $uni-color-success: #4cd964; |
| | | $uni-color-warning: #f0ad4e; |
| | | $uni-color-error: #dd524d; |
| | | |
| | | /* 文字基本颜色 */ |
| | | $uni-text-color:#333;//基本色 |
| | | $uni-text-color-inverse:#fff;//反色 |
| | | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 |
| | | $uni-text-color-placeholder: #808080; |
| | | $uni-text-color-disable:#c0c0c0; |
| | | |
| | | /* 背景颜色 */ |
| | | $uni-bg-color:#ffffff; |
| | | $uni-bg-color-grey:#f8f8f8; |
| | | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 |
| | | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 |
| | | |
| | | /* 边框颜色 */ |
| | | $uni-border-color:#c8c7cc; |
| | | |
| | | /* 尺寸变量 */ |
| | | |
| | | /* 文字尺寸 */ |
| | | $uni-font-size-sm:12px; |
| | | $uni-font-size-base:14px; |
| | | $uni-font-size-lg:16px; |
| | | |
| | | /* 图片尺寸 */ |
| | | $uni-img-size-sm:20px; |
| | | $uni-img-size-base:26px; |
| | | $uni-img-size-lg:40px; |
| | | |
| | | /* Border Radius */ |
| | | $uni-border-radius-sm: 2px; |
| | | $uni-border-radius-base: 3px; |
| | | $uni-border-radius-lg: 6px; |
| | | $uni-border-radius-circle: 50%; |
| | | |
| | | /* 水平间距 */ |
| | | $uni-spacing-row-sm: 5px; |
| | | $uni-spacing-row-base: 10px; |
| | | $uni-spacing-row-lg: 15px; |
| | | |
| | | /* 垂直间距 */ |
| | | $uni-spacing-col-sm: 4px; |
| | | $uni-spacing-col-base: 8px; |
| | | $uni-spacing-col-lg: 12px; |
| | | |
| | | /* 透明度 */ |
| | | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 |
| | | |
| | | /* 文章场景相关 */ |
| | | $uni-color-title: #2C405A; // 文章标题颜色 |
| | | $uni-font-size-title:20px; |
| | | $uni-color-subtitle: #555555; // 二级标题颜色 |
| | | $uni-font-size-subtitle:26px; |
| | | $uni-color-paragraph: #3F536E; // 文章段落颜色 |
| | | $uni-font-size-paragraph:15px; |
| New file |
| | |
| | | // 此vm参数为页面的实例,可以通过它引用vuex中的变量 |
| | | export const Request = (vm)=>{ |
| | | // 初始化请求配置 |
| | | uni.$uv.http.setConfig((config) => { |
| | | /* config 为默认全局配置*/ |
| | | config.baseURL = 'https://shoujike.shenzhenbenwo.com/prod-api'; /* 根域名 */ |
| | | return config |
| | | }) |
| | | |
| | | // 请求拦截 |
| | | uni.$uv.http.interceptors.request.use((config) => { // 可使用async await 做异步操作 |
| | | // 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{} |
| | | // config.data = config.data || {} |
| | | // // 根据custom参数中配置的是否需要token,添加对应的请求头 |
| | | // if(config?.custom?.auth) { |
| | | // // 可以在此通过vm引用vuex中的变量,具体值在vm.$store.state中 |
| | | // config.header.token = vm.$store.state.userInfo.token |
| | | // } |
| | | const token = uni.getStorageSync('token'); |
| | | //console.log('token----',token) |
| | | if (token) { |
| | | config.header.Authorization = token; // 设置 Authorization 头部 |
| | | } |
| | | return config |
| | | }, config => { // 可使用async await 做异步操作 |
| | | return Promise.reject(config) |
| | | }) |
| | | |
| | | // 响应拦截 |
| | | uni.$uv.http.interceptors.response.use((response) => { /* 对响应成功做点什么 可使用async await 做异步操作*/ |
| | | //return response.data |
| | | const res = response.data |
| | | const token = uni.getStorageSync('token'); |
| | | //console.log('拦截器---',res) |
| | | if(res.code == 401){ //判断token 过期 跳转重新登录 |
| | | //ElMessage.error("请先登录!") |
| | | // uni.showToast({ |
| | | // title: res.msg, |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | uni.removeStorageSync('token'); |
| | | uni.showToast({ |
| | | title: '请重新登录!', |
| | | duration: 2000, |
| | | icon: 'none' |
| | | }); |
| | | uni.reLaunch({ |
| | | url: '/pages/login/login' |
| | | }); |
| | | // setTimeout(() => { |
| | | // //window.location.href = '/'; |
| | | |
| | | // }, 1000); |
| | | return |
| | | } |
| | | // if(response.code == 502){ |
| | | // uni.showToast({ |
| | | // title: '服务器错误,请稍后重试', |
| | | // duration: 2000, |
| | | // icon: 'none' |
| | | // }); |
| | | // return |
| | | // } |
| | | return res; |
| | | }, (response) => { |
| | | // 对响应错误做点什么 (statusCode !== 200) |
| | | return Promise.reject(response) |
| | | }) |
| | | } |