Commit cd8d442fe0e5ef5a96873e9c9534d81c1bacbdf9
1 parent
5760f7b9
开始处理水电抄表功能
Showing
25 changed files
with
3212 additions
and
2301 deletions
public/js/jessibuca/jessibuca.d.ts deleted
| 1 | -declare namespace Jessibuca { | ||
| 2 | - | ||
| 3 | - /** 超时信息 */ | ||
| 4 | - enum TIMEOUT { | ||
| 5 | - /** 当play()的时候,如果没有数据返回 */ | ||
| 6 | - loadingTimeout = 'loadingTimeout', | ||
| 7 | - /** 当播放过程中,如果超过timeout之后没有数据渲染 */ | ||
| 8 | - delayTimeout = 'delayTimeout', | ||
| 9 | - } | ||
| 10 | - | ||
| 11 | - /** 错误信息 */ | ||
| 12 | - enum ERROR { | ||
| 13 | - /** 播放错误,url 为空的时候,调用 play 方法 */ | ||
| 14 | - playError = 'playError', | ||
| 15 | - /** http 请求失败 */ | ||
| 16 | - fetchError = 'fetchError', | ||
| 17 | - /** websocket 请求失败 */ | ||
| 18 | - websocketError = 'websocketError', | ||
| 19 | - /** webcodecs 解码 h265 失败 */ | ||
| 20 | - webcodecsH265NotSupport = 'webcodecsH265NotSupport', | ||
| 21 | - /** mediaSource 解码 h265 失败 */ | ||
| 22 | - mediaSourceH265NotSupport = 'mediaSourceH265NotSupport', | ||
| 23 | - /** wasm 解码失败 */ | ||
| 24 | - wasmDecodeError = 'wasmDecodeError', | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | - interface Config { | ||
| 28 | - /** | ||
| 29 | - * 播放器容器 | ||
| 30 | - * * 若为 string ,则底层调用的是 document.getElementById('id') | ||
| 31 | - * */ | ||
| 32 | - container: HTMLElement | string; | ||
| 33 | - /** | ||
| 34 | - * 设置最大缓冲时长,单位秒,播放器会自动消除延迟 | ||
| 35 | - */ | ||
| 36 | - videoBuffer?: number; | ||
| 37 | - /** | ||
| 38 | - * worker地址 | ||
| 39 | - * * 默认引用的是根目录下面的decoder.js文件 ,decoder.js 与 decoder.wasm文件必须是放在同一个目录下面。 */ | ||
| 40 | - decoder?: string; | ||
| 41 | - /** | ||
| 42 | - * 是否不使用离屏模式(提升渲染能力) | ||
| 43 | - */ | ||
| 44 | - forceNoOffscreen?: boolean; | ||
| 45 | - /** | ||
| 46 | - * 是否开启当页面的'visibilityState'变为'hidden'的时候,自动暂停播放。 | ||
| 47 | - */ | ||
| 48 | - hiddenAutoPause?: boolean; | ||
| 49 | - /** | ||
| 50 | - * 是否有音频,如果设置`false`,则不对音频数据解码,提升性能。 | ||
| 51 | - */ | ||
| 52 | - hasAudio?: boolean; | ||
| 53 | - /** | ||
| 54 | - * 设置旋转角度,只支持,0(默认),180,270 三个值 | ||
| 55 | - */ | ||
| 56 | - rotate?: boolean; | ||
| 57 | - /** | ||
| 58 | - * 1. 当为`true`的时候:视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边。 等同于 `setScaleMode(1)` | ||
| 59 | - * 2. 当为`false`的时候:视频画面完全填充canvas区域,画面会被拉伸。等同于 `setScaleMode(0)` | ||
| 60 | - */ | ||
| 61 | - isResize?: boolean; | ||
| 62 | - /** | ||
| 63 | - * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)` | ||
| 64 | - */ | ||
| 65 | - isFullResize?: boolean; | ||
| 66 | - /** | ||
| 67 | - * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。 | ||
| 68 | - */ | ||
| 69 | - isFlv?: boolean; | ||
| 70 | - /** | ||
| 71 | - * 是否开启控制台调试打 | ||
| 72 | - */ | ||
| 73 | - debug?: boolean; | ||
| 74 | - /** | ||
| 75 | - * 1. 设置超时时长, 单位秒 | ||
| 76 | - * 2. 在连接成功之前(loading)和播放中途(heart),如果超过设定时长无数据返回,则回调timeout事件 | ||
| 77 | - */ | ||
| 78 | - timeout?: number; | ||
| 79 | - /** | ||
| 80 | - * 1. 设置超时时长, 单位秒 | ||
| 81 | - * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件 | ||
| 82 | - */ | ||
| 83 | - heartTimeout?: number; | ||
| 84 | - /** | ||
| 85 | - * 1. 设置超时时长, 单位秒 | ||
| 86 | - * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件 | ||
| 87 | - */ | ||
| 88 | - loadingTimeout?: number; | ||
| 89 | - /** | ||
| 90 | - * 是否支持屏幕的双击事件,触发全屏,取消全屏事件 | ||
| 91 | - */ | ||
| 92 | - supportDblclickFullscreen?: boolean; | ||
| 93 | - /** | ||
| 94 | - * 是否显示网 | ||
| 95 | - */ | ||
| 96 | - showBandwidth?: boolean; | ||
| 97 | - /** | ||
| 98 | - * 配置操作按钮 | ||
| 99 | - */ | ||
| 100 | - operateBtns?: { | ||
| 101 | - /** 是否显示全屏按钮 */ | ||
| 102 | - fullscreen?: boolean; | ||
| 103 | - /** 是否显示截图按钮 */ | ||
| 104 | - screenshot?: boolean; | ||
| 105 | - /** 是否显示播放暂停按钮 */ | ||
| 106 | - play?: boolean; | ||
| 107 | - /** 是否显示声音按钮 */ | ||
| 108 | - audio?: boolean; | ||
| 109 | - /** 是否显示录制按 */ | ||
| 110 | - record?: boolean; | ||
| 111 | - }; | ||
| 112 | - /** | ||
| 113 | - * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮 | ||
| 114 | - */ | ||
| 115 | - keepScreenOn?: boolean; | ||
| 116 | - /** | ||
| 117 | - * 是否开启声音,默认是关闭声音播放的 | ||
| 118 | - */ | ||
| 119 | - isNotMute?: boolean; | ||
| 120 | - /** | ||
| 121 | - * 加载过程中文案 | ||
| 122 | - */ | ||
| 123 | - loadingText?: string; | ||
| 124 | - /** | ||
| 125 | - * 背景图片 | ||
| 126 | - */ | ||
| 127 | - background?: string; | ||
| 128 | - /** | ||
| 129 | - * 是否开启MediaSource硬解码 | ||
| 130 | - * * 视频编码只支持H.264视频(Safari on iOS不支持) | ||
| 131 | - * * 不支持 forceNoOffscreen 为 false (开启离屏渲染) | ||
| 132 | - */ | ||
| 133 | - useMSE?: boolean; | ||
| 134 | - /** | ||
| 135 | - * 是否开启Webcodecs硬解码 | ||
| 136 | - * * 视频编码只支持H.264视频 (需在chrome 94版本以上,需要https或者localhost环境) | ||
| 137 | - * * 支持 forceNoOffscreen 为 false (开启离屏渲染) | ||
| 138 | - * */ | ||
| 139 | - useWCS?: boolean; | ||
| 140 | - /** | ||
| 141 | - * 是否开启键盘快捷键 | ||
| 142 | - * 目前支持的键盘快捷键有:esc -> 退出全屏;arrowUp -> 声音增加;arrowDown -> 声音减少; | ||
| 143 | - */ | ||
| 144 | - hotKey?: boolean; | ||
| 145 | - /** | ||
| 146 | - * 在使用MSE或者Webcodecs 播放H265的时候,是否自动降级到wasm模式。 | ||
| 147 | - * 设置为false 则直接关闭播放,抛出Error 异常,设置为true 则会自动切换成wasm模式播放。 | ||
| 148 | - */ | ||
| 149 | - autoWasm?: boolean; | ||
| 150 | - /** | ||
| 151 | - * heartTimeout 心跳超时之后自动再播放,不再抛出异常,而直接重新播放视频地址。 | ||
| 152 | - */ | ||
| 153 | - heartTimeoutReplay?: boolean, | ||
| 154 | - /** | ||
| 155 | - * heartTimeoutReplay 从试次数,超过之后,不再自动播放 | ||
| 156 | - */ | ||
| 157 | - heartTimeoutReplayTimes?: number, | ||
| 158 | - /** | ||
| 159 | - * loadingTimeout loading之后自动再播放,不再抛出异常,而直接重新播放视频地址。 | ||
| 160 | - */ | ||
| 161 | - loadingTimeoutReplay?: boolean, | ||
| 162 | - /** | ||
| 163 | - * heartTimeoutReplay 从试次数,超过之后,不再自动播放 | ||
| 164 | - */ | ||
| 165 | - loadingTimeoutReplayTimes?: number | ||
| 166 | - /** | ||
| 167 | - * wasm解码报错之后,不再抛出异常,而是直接重新播放视频地址。 | ||
| 168 | - */ | ||
| 169 | - wasmDecodeErrorReplay?: boolean, | ||
| 170 | - /** | ||
| 171 | - * https://github.com/langhuihui/jessibuca/issues/152 解决方案 | ||
| 172 | - * 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。 | ||
| 173 | - */ | ||
| 174 | - openWebglAlignment?: boolean | ||
| 175 | - } | ||
| 176 | -} | ||
| 177 | - | ||
| 178 | - | ||
| 179 | -declare class Jessibuca { | ||
| 180 | - | ||
| 181 | - constructor(config?: Jessibuca.Config); | ||
| 182 | - | ||
| 183 | - /** | ||
| 184 | - * 是否开启控制台调试打印 | ||
| 185 | - @example | ||
| 186 | - // 开启 | ||
| 187 | - jessibuca.setDebug(true) | ||
| 188 | - // 关闭 | ||
| 189 | - jessibuca.setDebug(false) | ||
| 190 | - */ | ||
| 191 | - setDebug(flag: boolean): void; | ||
| 192 | - | ||
| 193 | - /** | ||
| 194 | - * 静音 | ||
| 195 | - @example | ||
| 196 | - jessibuca.mute() | ||
| 197 | - */ | ||
| 198 | - mute(): void; | ||
| 199 | - | ||
| 200 | - /** | ||
| 201 | - * 取消静音 | ||
| 202 | - @example | ||
| 203 | - jessibuca.cancelMute() | ||
| 204 | - */ | ||
| 205 | - cancelMute(): void; | ||
| 206 | - | ||
| 207 | - /** | ||
| 208 | - * 留给上层用户操作来触发音频恢复的方法。 | ||
| 209 | - * | ||
| 210 | - * iPhone,chrome等要求自动播放时,音频必须静音,需要由一个真实的用户交互操作来恢复,不能使用代码。 | ||
| 211 | - * | ||
| 212 | - * https://developers.google.com/web/updates/2017/09/autoplay-policy-changes | ||
| 213 | - */ | ||
| 214 | - audioResume(): void; | ||
| 215 | - | ||
| 216 | - /** | ||
| 217 | - * | ||
| 218 | - * 设置超时时长, 单位秒 | ||
| 219 | - * 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件 | ||
| 220 | - | ||
| 221 | - @example | ||
| 222 | - jessibuca.setTimeout(10) | ||
| 223 | - | ||
| 224 | - jessibuca.on('timeout',function(){ | ||
| 225 | - // | ||
| 226 | - }); | ||
| 227 | - */ | ||
| 228 | - setTimeout(): void; | ||
| 229 | - | ||
| 230 | - /** | ||
| 231 | - * @param mode | ||
| 232 | - * 0 视频画面完全填充canvas区域,画面会被拉伸 等同于参数 `isResize` 为false | ||
| 233 | - * | ||
| 234 | - * 1 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 等同于参数 `isResize` 为true | ||
| 235 | - * | ||
| 236 | - * 2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 等同于参数 `isFullResize` 为true | ||
| 237 | - @example | ||
| 238 | - jessibuca.setScaleMode(0) | ||
| 239 | - | ||
| 240 | - jessibuca.setScaleMode(1) | ||
| 241 | - | ||
| 242 | - jessibuca.setScaleMode(2) | ||
| 243 | - */ | ||
| 244 | - setScaleMode(mode: number): void; | ||
| 245 | - | ||
| 246 | - /** | ||
| 247 | - * 暂停播放 | ||
| 248 | - * | ||
| 249 | - * 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。 | ||
| 250 | - @example | ||
| 251 | - jessibuca.pause().then(()=>{ | ||
| 252 | - console.log('pause success') | ||
| 253 | - | ||
| 254 | - jessibuca.play().then(()=>{ | ||
| 255 | - | ||
| 256 | - }).catch((e)=>{ | ||
| 257 | - | ||
| 258 | - }) | ||
| 259 | - | ||
| 260 | - }).catch((e)=>{ | ||
| 261 | - console.log('pause error',e); | ||
| 262 | - }) | ||
| 263 | - */ | ||
| 264 | - pause(): Promise<void>; | ||
| 265 | - | ||
| 266 | - /** | ||
| 267 | - * 关闭视频,不释放底层资源 | ||
| 268 | - @example | ||
| 269 | - jessibuca.close(); | ||
| 270 | - */ | ||
| 271 | - close(): void; | ||
| 272 | - | ||
| 273 | - /** | ||
| 274 | - * 关闭视频,释放底层资源 | ||
| 275 | - @example | ||
| 276 | - jessibuca.destroy() | ||
| 277 | - */ | ||
| 278 | - destroy(): void; | ||
| 279 | - | ||
| 280 | - /** | ||
| 281 | - * 清理画布为黑色背景 | ||
| 282 | - @example | ||
| 283 | - jessibuca.clearView() | ||
| 284 | - */ | ||
| 285 | - clearView(): void; | ||
| 286 | - | ||
| 287 | - /** | ||
| 288 | - * 播放视频 | ||
| 289 | - @example | ||
| 290 | - | ||
| 291 | - jessibuca.play('url').then(()=>{ | ||
| 292 | - console.log('play success') | ||
| 293 | - }).catch((e)=>{ | ||
| 294 | - console.log('play error',e) | ||
| 295 | - }) | ||
| 296 | - // | ||
| 297 | - jessibuca.play() | ||
| 298 | - */ | ||
| 299 | - play(url?: string): Promise<void>; | ||
| 300 | - | ||
| 301 | - /** | ||
| 302 | - * 重新调整视图大小 | ||
| 303 | - */ | ||
| 304 | - resize(): void; | ||
| 305 | - | ||
| 306 | - /** | ||
| 307 | - * 设置最大缓冲时长,单位秒,播放器会自动消除延迟。 | ||
| 308 | - * | ||
| 309 | - * 等同于 `videoBuffer` 参数。 | ||
| 310 | - * | ||
| 311 | - @example | ||
| 312 | - // 设置 200ms 缓冲 | ||
| 313 | - jessibuca.setBufferTime(0.2) | ||
| 314 | - */ | ||
| 315 | - setBufferTime(time: number): void; | ||
| 316 | - | ||
| 317 | - /** | ||
| 318 | - * 设置旋转角度,只支持,0(默认) ,180,270 三个值。 | ||
| 319 | - * | ||
| 320 | - * > 可用于实现监控画面小窗和全屏效果,由于iOS没有全屏API,此方法可以模拟页面内全屏效果而且多端效果一致。 * | ||
| 321 | - @example | ||
| 322 | - jessibuca.setRotate(0) | ||
| 323 | - | ||
| 324 | - jessibuca.setRotate(90) | ||
| 325 | - | ||
| 326 | - jessibuca.setRotate(270) | ||
| 327 | - */ | ||
| 328 | - setRotate(deg: number): void; | ||
| 329 | - | ||
| 330 | - /** | ||
| 331 | - * | ||
| 332 | - * 设置音量大小,取值0 — 1 | ||
| 333 | - * | ||
| 334 | - * > 区别于 mute 和 cancelMute 方法,虽然设置setVolume(0) 也能达到 mute方法,但是mute 方法是不调用底层播放音频的,能提高性能。而setVolume(0)只是把声音设置为0 ,以达到效果。 | ||
| 335 | - * @param volume 当为0时,完全无声;当为1时,最大音量,默认值 | ||
| 336 | - @example | ||
| 337 | - jessibuca.setVolume(0.2) | ||
| 338 | - | ||
| 339 | - jessibuca.setVolume(0) | ||
| 340 | - | ||
| 341 | - jessibuca.setVolume(1) | ||
| 342 | - */ | ||
| 343 | - setVolume(volume: number): void; | ||
| 344 | - | ||
| 345 | - /** | ||
| 346 | - * 返回是否加载完毕 | ||
| 347 | - @example | ||
| 348 | - var result = jessibuca.hasLoaded() | ||
| 349 | - console.log(result) // true | ||
| 350 | - */ | ||
| 351 | - hasLoaded(): boolean; | ||
| 352 | - | ||
| 353 | - /** | ||
| 354 | - * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮。 | ||
| 355 | - * H5目前在chrome\edge 84, android chrome 84及以上有原生亮屏API, 需要是https页面 | ||
| 356 | - * 其余平台为模拟实现,此时为兼容实现,并不保证所有浏览器都支持 | ||
| 357 | - @example | ||
| 358 | - jessibuca.setKeepScreenOn() | ||
| 359 | - */ | ||
| 360 | - setKeepScreenOn(): boolean; | ||
| 361 | - | ||
| 362 | - /** | ||
| 363 | - * 全屏(取消全屏)播放视频 | ||
| 364 | - @example | ||
| 365 | - jessibuca.setFullscreen(true) | ||
| 366 | - // | ||
| 367 | - jessibuca.setFullscreen(false) | ||
| 368 | - */ | ||
| 369 | - setFullscreen(flag: boolean): void; | ||
| 370 | - | ||
| 371 | - /** | ||
| 372 | - * | ||
| 373 | - * 截图,调用后弹出下载框保存截图 | ||
| 374 | - * @param filename 可选参数, 保存的文件名, 默认 `时间戳` | ||
| 375 | - * @param format 可选参数, 截图的格式,可选png或jpeg或者webp ,默认 `png` | ||
| 376 | - * @param quality 可选参数, 当格式是jpeg或者webp时,压缩质量,取值0 ~ 1 ,默认 `0.92` | ||
| 377 | - * @param type 可选参数, 可选download或者base64或者blob,默认`download` | ||
| 378 | - | ||
| 379 | - @example | ||
| 380 | - | ||
| 381 | - jessibuca.screenshot("test","png",0.5) | ||
| 382 | - | ||
| 383 | - const base64 = jessibuca.screenshot("test","png",0.5,'base64') | ||
| 384 | - | ||
| 385 | - const fileBlob = jessibuca.screenshot("test",'blob') | ||
| 386 | - */ | ||
| 387 | - screenshot(filename?: string, format?: string, quality?: number, type?: string): void; | ||
| 388 | - | ||
| 389 | - /** | ||
| 390 | - * 开始录制。 | ||
| 391 | - * @param fileName 可选,默认时间戳 | ||
| 392 | - * @param fileType 可选,默认webm,支持webm 和mp4 格式 | ||
| 393 | - | ||
| 394 | - @example | ||
| 395 | - jessibuca.startRecord('xxx','webm') | ||
| 396 | - */ | ||
| 397 | - startRecord(fileName: string, fileType: string): void; | ||
| 398 | - | ||
| 399 | - /** | ||
| 400 | - * 暂停录制并下载。 | ||
| 401 | - @example | ||
| 402 | - jessibuca.stopRecordAndSave() | ||
| 403 | - */ | ||
| 404 | - stopRecordAndSave(): void; | ||
| 405 | - | ||
| 406 | - /** | ||
| 407 | - * 返回是否正在播放中状态。 | ||
| 408 | - @example | ||
| 409 | - var result = jessibuca.isPlaying() | ||
| 410 | - console.log(result) // true | ||
| 411 | - */ | ||
| 412 | - isPlaying(): boolean; | ||
| 413 | - | ||
| 414 | - /** | ||
| 415 | - * 返回是否静音。 | ||
| 416 | - @example | ||
| 417 | - var result = jessibuca.isMute() | ||
| 418 | - console.log(result) // true | ||
| 419 | - */ | ||
| 420 | - isMute(): boolean; | ||
| 421 | - | ||
| 422 | - /** | ||
| 423 | - * 返回是否正在录制。 | ||
| 424 | - @example | ||
| 425 | - var result = jessibuca.isRecording() | ||
| 426 | - console.log(result) // true | ||
| 427 | - */ | ||
| 428 | - isRecording(): boolean; | ||
| 429 | - | ||
| 430 | - | ||
| 431 | - /** | ||
| 432 | - * 监听 jessibuca 初始化事件 | ||
| 433 | - * @example | ||
| 434 | - * jessibuca.on("load",function(){console.log('load')}) | ||
| 435 | - */ | ||
| 436 | - on(event: 'load', callback: () => void): void; | ||
| 437 | - | ||
| 438 | - /** | ||
| 439 | - * 视频播放持续时间,单位ms | ||
| 440 | - * @example | ||
| 441 | - * jessibuca.on('timeUpdate',function (ts) {console.log('timeUpdate',ts);}) | ||
| 442 | - */ | ||
| 443 | - on(event: 'timeUpdate', callback: () => void): void; | ||
| 444 | - | ||
| 445 | - /** | ||
| 446 | - * 当解析出视频信息时回调,2个回调参数 | ||
| 447 | - * @example | ||
| 448 | - * jessibuca.on("videoInfo",function(data){console.log('width:',data.width,'height:',data.width)}) | ||
| 449 | - */ | ||
| 450 | - on(event: 'videoInfo', callback: (data: { | ||
| 451 | - /** 视频宽 */ | ||
| 452 | - width: number; | ||
| 453 | - /** 视频高 */ | ||
| 454 | - height: number; | ||
| 455 | - }) => void): void; | ||
| 456 | - | ||
| 457 | - /** | ||
| 458 | - * 当解析出音频信息时回调,2个回调参数 | ||
| 459 | - * @example | ||
| 460 | - * jessibuca.on("audioInfo",function(data){console.log('numOfChannels:',data.numOfChannels,'sampleRate',data.sampleRate)}) | ||
| 461 | - */ | ||
| 462 | - on(event: 'audioInfo', callback: (data: { | ||
| 463 | - /** 声频通道 */ | ||
| 464 | - numOfChannels: number; | ||
| 465 | - /** 采样率 */ | ||
| 466 | - sampleRate: number; | ||
| 467 | - }) => void): void; | ||
| 468 | - | ||
| 469 | - /** | ||
| 470 | - * 信息,包含错误信息 | ||
| 471 | - * @example | ||
| 472 | - * jessibuca.on("log",function(data){console.log('data:',data)}) | ||
| 473 | - */ | ||
| 474 | - on(event: 'log', callback: () => void): void; | ||
| 475 | - | ||
| 476 | - /** | ||
| 477 | - * 错误信息 | ||
| 478 | - * @example | ||
| 479 | - * jessibuca.on("error",function(error){ | ||
| 480 | - if(error === Jessibuca.ERROR.fetchError){ | ||
| 481 | - // | ||
| 482 | - } | ||
| 483 | - else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){ | ||
| 484 | - // | ||
| 485 | - } | ||
| 486 | - console.log('error:',error) | ||
| 487 | - }) | ||
| 488 | - */ | ||
| 489 | - on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void; | ||
| 490 | - | ||
| 491 | - /** | ||
| 492 | - * 当前网速, 单位KB 每秒1次, | ||
| 493 | - * @example | ||
| 494 | - * jessibuca.on("kBps",function(data){console.log('kBps:',data)}) | ||
| 495 | - */ | ||
| 496 | - on(event: 'kBps', callback: (value: number) => void): void; | ||
| 497 | - | ||
| 498 | - /** | ||
| 499 | - * 渲染开始 | ||
| 500 | - * @example | ||
| 501 | - * jessibuca.on("start",function(){console.log('start render')}) | ||
| 502 | - */ | ||
| 503 | - on(event: 'start', callback: () => void): void; | ||
| 504 | - | ||
| 505 | - /** | ||
| 506 | - * 当设定的超时时间内无数据返回,则回调 | ||
| 507 | - * @example | ||
| 508 | - * jessibuca.on("timeout",function(error){console.log('timeout:',error)}) | ||
| 509 | - */ | ||
| 510 | - on(event: 'timeout', callback: (error: Jessibuca.TIMEOUT) => void): void; | ||
| 511 | - | ||
| 512 | - /** | ||
| 513 | - * 当play()的时候,如果没有数据返回,则回调 | ||
| 514 | - * @example | ||
| 515 | - * jessibuca.on("loadingTimeout",function(){console.log('timeout')}) | ||
| 516 | - */ | ||
| 517 | - on(event: 'loadingTimeout', callback: () => void): void; | ||
| 518 | - | ||
| 519 | - /** | ||
| 520 | - * 当播放过程中,如果超过timeout之后没有数据渲染,则抛出异常。 | ||
| 521 | - * @example | ||
| 522 | - * jessibuca.on("delayTimeout",function(){console.log('timeout')}) | ||
| 523 | - */ | ||
| 524 | - on(event: 'delayTimeout', callback: () => void): void; | ||
| 525 | - | ||
| 526 | - /** | ||
| 527 | - * 当前是否全屏 | ||
| 528 | - * @example | ||
| 529 | - * jessibuca.on("fullscreen",function(flag){console.log('is fullscreen',flag)}) | ||
| 530 | - */ | ||
| 531 | - on(event: 'fullscreen', callback: () => void): void; | ||
| 532 | - | ||
| 533 | - /** | ||
| 534 | - * 触发播放事件 | ||
| 535 | - * @example | ||
| 536 | - * jessibuca.on("play",function(flag){console.log('play')}) | ||
| 537 | - */ | ||
| 538 | - on(event: 'play', callback: () => void): void; | ||
| 539 | - | ||
| 540 | - /** | ||
| 541 | - * 触发暂停事件 | ||
| 542 | - * @example | ||
| 543 | - * jessibuca.on("pause",function(flag){console.log('pause')}) | ||
| 544 | - */ | ||
| 545 | - on(event: 'pause', callback: () => void): void; | ||
| 546 | - | ||
| 547 | - /** | ||
| 548 | - * 触发声音事件,返回boolean值 | ||
| 549 | - * @example | ||
| 550 | - * jessibuca.on("mute",function(flag){console.log('is mute',flag)}) | ||
| 551 | - */ | ||
| 552 | - on(event: 'mute', callback: () => void): void; | ||
| 553 | - | ||
| 554 | - /** | ||
| 555 | - * 流状态统计,流开始播放后回调,每秒1次。 | ||
| 556 | - * @example | ||
| 557 | - * jessibuca.on("stats",function(s){console.log("stats is",s)}) | ||
| 558 | - */ | ||
| 559 | - on(event: 'stats', callback: (stats: { | ||
| 560 | - /** 当前缓冲区时长,单位毫秒 */ | ||
| 561 | - buf: number; | ||
| 562 | - /** 当前视频帧率 */ | ||
| 563 | - fps: number; | ||
| 564 | - /** 当前音频码率,单位byte */ | ||
| 565 | - abps: number; | ||
| 566 | - /** 当前视频码率,单位byte */ | ||
| 567 | - vbps: number; | ||
| 568 | - /** 当前视频帧pts,单位毫秒 */ | ||
| 569 | - ts: number; | ||
| 570 | - }) => void): void; | ||
| 571 | - | ||
| 572 | - /** | ||
| 573 | - * 渲染性能统计,流开始播放后回调,每秒1次。 | ||
| 574 | - * @param performance 0: 表示卡顿,1: 表示流畅,2: 表示非常流程 | ||
| 575 | - * @example | ||
| 576 | - * jessibuca.on("performance",function(performance){console.log("performance is",performance)}) | ||
| 577 | - */ | ||
| 578 | - on(event: 'performance', callback: (performance: 0 | 1 | 2) => void): void; | ||
| 579 | - | ||
| 580 | - /** | ||
| 581 | - * 录制开始的事件 | ||
| 582 | - | ||
| 583 | - * @example | ||
| 584 | - * jessibuca.on("recordStart",function(){console.log("record start")}) | ||
| 585 | - */ | ||
| 586 | - on(event: 'recordStart', callback: () => void): void; | ||
| 587 | - | ||
| 588 | - /** | ||
| 589 | - * 录制结束的事件 | ||
| 590 | - | ||
| 591 | - * @example | ||
| 592 | - * jessibuca.on("recordEnd",function(){console.log("record end")}) | ||
| 593 | - */ | ||
| 594 | - on(event: 'recordEnd', callback: () => void): void; | ||
| 595 | - | ||
| 596 | - /** | ||
| 597 | - * 录制的时候,返回的录制时长,1s一次 | ||
| 598 | - | ||
| 599 | - * @example | ||
| 600 | - * jessibuca.on("recordingTimestamp",function(timestamp){console.log("recordingTimestamp is",timestamp)}) | ||
| 601 | - */ | ||
| 602 | - on(event: 'recordingTimestamp', callback: (timestamp: number) => void): void; | ||
| 603 | - | ||
| 604 | - /** | ||
| 605 | - * 监听调用play方法 经过 初始化-> 网络请求-> 解封装 -> 解码 -> 渲染 一系列过程的时间消耗 | ||
| 606 | - * @param event | ||
| 607 | - * @param callback | ||
| 608 | - */ | ||
| 609 | - on(event: 'playToRenderTimes', callback: (times: { | ||
| 610 | - playInitStart: number, // 1 初始化 | ||
| 611 | - playStart: number, // 2 初始化 | ||
| 612 | - streamStart: number, // 3 网络请求 | ||
| 613 | - streamResponse: number, // 4 网络请求 | ||
| 614 | - demuxStart: number, // 5 解封装 | ||
| 615 | - decodeStart: number, // 6 解码 | ||
| 616 | - videoStart: number, // 7 渲染 | ||
| 617 | - playTimestamp: number,// playStart- playInitStart | ||
| 618 | - streamTimestamp: number,// streamStart - playStart | ||
| 619 | - streamResponseTimestamp: number,// streamResponse - streamStart | ||
| 620 | - demuxTimestamp: number, // demuxStart - streamResponse | ||
| 621 | - decodeTimestamp: number, // decodeStart - demuxStart | ||
| 622 | - videoTimestamp: number,// videoStart - decodeStart | ||
| 623 | - allTimestamp: number // videoStart - playInitStart | ||
| 624 | - }) => void): void | ||
| 625 | - | ||
| 626 | - /** | ||
| 627 | - * 监听方法 | ||
| 628 | - * | ||
| 629 | - @example | ||
| 630 | - | ||
| 631 | - jessibuca.on("load",function(){console.log('load')}) | ||
| 632 | - */ | ||
| 633 | - on(event: string, callback: Function): void; | ||
| 634 | - | ||
| 635 | -} | ||
| 636 | - | ||
| 637 | -export default Jessibuca; |
public/js/jessibuca/jessibuca.js deleted
No preview for this file type
public/js/jessibuca/renderer.js deleted
| 1 | -!(function () { | ||
| 2 | - /** | ||
| 3 | - * @param opt | ||
| 4 | - * container: DOM 容器 | ||
| 5 | - * contextOptions: | ||
| 6 | - * videoBuffer: | ||
| 7 | - * forceNoGL: | ||
| 8 | - * isNotMute: | ||
| 9 | - * decoder: | ||
| 10 | - * @constructor | ||
| 11 | - */ | ||
| 12 | - function Jessibuca(opt) { | ||
| 13 | - this._opt = opt; | ||
| 14 | - | ||
| 15 | - if (typeof opt.container === "string") { | ||
| 16 | - this._opt.container = document.getElementById(opt.container); | ||
| 17 | - } | ||
| 18 | - if (!this._opt.container) { | ||
| 19 | - throw new Error('Jessibuca need container option'); | ||
| 20 | - return; | ||
| 21 | - } | ||
| 22 | - | ||
| 23 | - this._canvasElement = document.createElement("canvas"); | ||
| 24 | - this._canvasElement.style.position = "absolute"; | ||
| 25 | - this._canvasElement.style.top = 0; | ||
| 26 | - this._canvasElement.style.left = 0; | ||
| 27 | - this._opt.container.appendChild(this._canvasElement); | ||
| 28 | - this._container = this._opt.container; | ||
| 29 | - this._container.style.overflow = "hidden"; | ||
| 30 | - this._containerOldPostion = { | ||
| 31 | - position: this._container.style.position, | ||
| 32 | - top: this._container.style.top, | ||
| 33 | - left: this._container.style.left, | ||
| 34 | - width: this._container.style.width, | ||
| 35 | - height: this._container.style.height | ||
| 36 | - } | ||
| 37 | - if (this._containerOldPostion.position != "absolute") { | ||
| 38 | - this._container.style.position = "relative" | ||
| 39 | - } | ||
| 40 | - this._opt.videoBuffer = opt.videoBuffer || 0; | ||
| 41 | - this._opt.text = opt.text || ''; | ||
| 42 | - // | ||
| 43 | - this._opt.isResize = opt.isResize === false ? opt.isResize : true; | ||
| 44 | - this._opt.isFullResize = opt.isFullResize === true ? opt.isFullResize : false; | ||
| 45 | - this._opt.isDebug = opt.debug === true; | ||
| 46 | - this._opt.timeout = typeof opt.timeout === 'number' ? opt.timeout : 30; | ||
| 47 | - this._opt.supportDblclickFullscreen = opt.supportDblclickFullscreen === true; | ||
| 48 | - this._opt.showBandwidth = opt.showBandwidth === true; | ||
| 49 | - this._opt.operateBtns = Object.assign({ | ||
| 50 | - fullscreen: false, | ||
| 51 | - screenshot: false, | ||
| 52 | - play: false, | ||
| 53 | - audio: false | ||
| 54 | - }, opt.operateBtns || {}); | ||
| 55 | - this._opt.keepScreenOn = opt.keepScreenOn === true; | ||
| 56 | - | ||
| 57 | - if (!opt.forceNoGL) this._initContextGL(); | ||
| 58 | - this._audioContext = new (window.AudioContext || window.webkitAudioContext)(); | ||
| 59 | - this._audioEnabled(true); | ||
| 60 | - if (!opt.isNotMute) this._audioEnabled(false); | ||
| 61 | - if (this._contextGL) { | ||
| 62 | - this._initProgram(); | ||
| 63 | - this._initBuffers(); | ||
| 64 | - this._initTextures(); | ||
| 65 | - } | ||
| 66 | - this._onresize = () => this.resize(); | ||
| 67 | - this._onfullscreenchange = () => this._fullscreenchange(); | ||
| 68 | - window.addEventListener("resize", this._onresize); | ||
| 69 | - document.addEventListener('fullscreenchange', this._onfullscreenchange); | ||
| 70 | - this._decoderWorker = new Worker(opt.decoder || 'ff.js') | ||
| 71 | - var _this = this; | ||
| 72 | - this._hasLoaded = false; | ||
| 73 | - this._stats = { | ||
| 74 | - buf: 0, | ||
| 75 | - fps: 0, | ||
| 76 | - abps: '', | ||
| 77 | - vbps: '', | ||
| 78 | - ts: '' | ||
| 79 | - }; | ||
| 80 | - | ||
| 81 | - if (this._opt.supportDblclickFullscreen) { | ||
| 82 | - this._canvasElement.addEventListener('dblclick', function () { | ||
| 83 | - _this.fullscreen = !_this.fullscreen; | ||
| 84 | - }, false); | ||
| 85 | - } | ||
| 86 | - this.onPlay = noop; | ||
| 87 | - this.onPause = noop; | ||
| 88 | - this.onRecord = noop; | ||
| 89 | - this.onFullscreen = noop; | ||
| 90 | - this.onMute = noop; | ||
| 91 | - this.onLoad = noop; | ||
| 92 | - this.onLog = noop; | ||
| 93 | - this.onError = noop; | ||
| 94 | - this.onTimeUpdate = noop; | ||
| 95 | - this.onInitSize = noop; | ||
| 96 | - this._onMessage(); | ||
| 97 | - this._initDom(); | ||
| 98 | - this._initStatus(); | ||
| 99 | - this._initEventListener(); | ||
| 100 | - this._hideBtns(); | ||
| 101 | - // | ||
| 102 | - this._initWakeLock(); | ||
| 103 | - this._enableWakeLock(); | ||
| 104 | - }; | ||
| 105 | - | ||
| 106 | - function noop() { | ||
| 107 | - | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - Jessibuca.prototype._initDom = function () { | ||
| 111 | - var playBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAARVJREFUSMe9laEOglAUhs+5k9lJFpsJ5QWMJoNGbEY0mEy+gr6GNo0a3SiQCegMRILzGdw4hl+Cd27KxPuXb2zA/91z2YXoGRERkX4fvN3A2QxUiv4dFM3n8jZRBLbbVfd+ubJuF4xjiCyXkksueb1uSKCIZYGLBTEx8ekEoV7PkICeVgs8HiGyXoO2bUigCDM4HoPnM7bI8wwJ6Gk0sEXbLSay30Oo2TQkoGcwgFCSQMhxDAvoETEscDiQkJC4LjMz8+XyZ4HrFYWjEQqHQ1asWGWZfmdFAsVINxuw00HhbvfpydpvxWkKTqdYaRCUfUPJCdzv4Gr1uqfli0tOIAzByUT/iCrL6+84y3Bw+D6ui5Ou+jwA8FnIO++FACgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMDhUMTY6NDI6NTMrMDg6MDCKP7wnAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAw+2IEmwAAAEl0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fZ2Y3MDBzN2IzZncvYm9mYW5nLnN2Z8fICi0AAAAASUVORK5CYII='; | ||
| 112 | - var pauseBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAHVJREFUSMftkCESwCAMBEOnCtdXVMKHeC7oInkEeQJXkRoEZWraipxZc8lsQqQZBACAlIS1oqGhhTCdu3oyxyyMcdRf79c5J7SWDBky+z4173rbJvR+VF/e/qwKqIAKqMBDgZyFzAQCoZTpxq7HLDyOrw/9b07l3z4dDnI2IAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wMS0wOFQxNjo0Mjo1MyswODowMIo/vCcAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDEtMDhUMTY6NDI6NTMrMDg6MDD7YgSbAAAASnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9nZjcwMHM3YjNmdy96YW50aW5nLnN2ZxqNZJkAAAAASUVORK5CYII='; | ||
| 113 | - var screenshotBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAaxJREFUSMfNlLFOAkEQhmevAZMjR6OGRBJKsFBzdkYNpYSaWkopIOFRCBWh1ieA+ALGRgutjK0HzV2H5SX7W/zsmY3cnTEhcZovOzcz9+/s7Ir8d4OGht7fBwAgjvEri2OTl1ffSf0xAMBxRIkS1e3Se3+vcszEMe/6OqmT/aN2m1wsNu/o5YVsNHI7BgA4PCRfXzfXCwKy1RLbcXZG9nrkzc12jvT8nPU/PtatOThgAx8fuS4WyZ0de2e+T87n5OcnuVqRsxl5cpImQDnKUc7DA1fVqpimZCu+vCSjiNH9PlmpJNTQ0INBErfeafZRAakC6FWKfH9nwU7H/l6rGdqCOx3y7c3U+aOARsMMp+1vNskwTLjulB23XJL1epqA9OshIiKeJxAIoug7UyA4OuLi6Ynr52deu+NjOy4MSc9Ln8rMDpTLybBpaOjdXbJUIqdTm8a/t2fn/RSQewR24HicTLmGhnbdzcPquvYtGY3+PIR24UKBUXd35v6Sk4lN47+9NXm/FBAEedfGTjw9JYdDm76fm6+hoS8ujGAxT6L9Im7bTKeurvIEb92+AES1b6x283XSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAwij+8JwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0wOFQxNjo0Mjo1MyswODowMPtiBJsAAABJdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2dmNzAwczdiM2Z3L2NhbWVyYS5zdmeyubWEAAAAAElFTkSuQmCC'; | ||
| 114 | - var fullscreenBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAALZJREFUSMftVbsORUAQVSj8DomChvh3lU5CoSVCQq2RObeYu8XG3deVoHCak81kds7Oaz3vxRcAAMwztOg6vX9d6/3XFQQC+b7iAoFhYE7Tvx9EIFAcy/ftO3MQGAQkCfM4MmeZWyajiLnvmYuCeduMAuSzvRBVYNluFHCssSgFp7Sq9ALKkjnPf9ubRtkDL27HNT3QtsY9cAjsNAVheHIKBOwD2wpxFHDbJpwmaHH2L1iWx+2BDy8RbXXtqbRBAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAwij+8JwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0wOFQxNjo0Mjo1MyswODowMPtiBJsAAABTdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2dmNzAwczdiM2Z3L3F1YW5waW5nenVpZGFodWEuc3ZnTBoI7AAAAABJRU5ErkJggg=='; | ||
| 115 | - var minScreenBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAYJJREFUSMfdVbGKwkAQnQn+geAfWBixUTsVgp3YGKxSWflVNmIjARULwc5KO40ipNHWRgs/wGLniucKa+Jd5ODuuGle5u3szGRmd5bor4iIiMhuB3Sc+HXXBdp2/Lpta7v4dccRJUrUdhtNQIkSVa3C8HwG1uumg34f2OnEB+h0tF1Sv5b+YIsttpZLEhKSdhvscPi8IXFF74GJiYnHY7Cex8zMvFgkbInjmJnv98kqoO30vmhLtaRMB60WtEbDNDudgMUiKiQSzfjOMzFxoQAyCPSfw7/nQZ/PUYnpNGV6OR6BmYzJbzYIoBQCzGaRBDQvJCTdLnTLolg5HN5t6f8V1h/oUT4PrVKJWBotmEzQw+vV3J9Ow851P2/BaoX9Yfh0BrJZYKlk8uUyHOpDeLuBHwzMBJtN2PV6IPUhXK9Nf5cLMAxfluanrmGkRBggtRo03wfq66P/6CsJAnOg+f6rgfZI4BGYiYlHIx048eR6krcnq34kkj1GuVz8+jceo9+SD5A8yGh8CTq7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAwij+8JwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0wOFQxNjo0Mjo1MyswODowMPtiBJsAAABNdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2dmNzAwczdiM2Z3L3p1aXhpYW9odWEuc3ZnoCFr0AAAAABJRU5ErkJggg=='; | ||
| 116 | - var quietBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAR9JREFUSMfVlD0LglAYhe9VkwgNihpsjbYQf4JTS7+iuaGxpcGfJjS0NFRLk2NDi6MogafhJGRIX9yEzvJwrx/nvPd9VYh/F3LkyBuN2g3J1QoAgCQhPe/Hxq5Lo+0WlfJ9dYYAgGaTDAIyy/BUnwcwWJlhcLnZkN2ugIBAuy2kkEL2ep8F73S4kjfFcfn6cMj9KLodrWVBiXyf75tMyOOR+4MBOZ8XLXzorboA5UpnM/J0Ivd7+vX7xX2asqGpVKtFXi5sqWmypXefrfIWAACmU/JwKCoun8hu9zA0uk6u13wgirg+n7+bAcsibbt6SB3n9TQXPxwAwHJJpum7M6BcDDQa0SgMaw9QPkJNIxcLMo4ZcDz+eYDqQFLWbqxKV57EtW1WtMbmAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAwij+8JwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0wOFQxNjo0Mjo1MyswODowMPtiBJsAAABKdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2dmNzAwczdiM2Z3L2ppbmd5aW4uc3ZnIlMYaQAAAABJRU5ErkJggg=='; | ||
| 117 | - var playAudioBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAU5JREFUSMftkzGKwlAURf9PULBQwULSCKK1bZAgNuoaFFyAC3AdZg0uQCwshWzAShEEO7Gy0soUCu9Occ3An5nMGCfdzGsO7+Xy3/03iVL/lbAAACiVIBCI77O37Vi9QCDZbEqLm03ycEBUAoHk818v7nYpul5Jz4tf8HBKYa1mcjwmbzd8rG8NFIsU7ffk8UjmcjE3XK+RtB4G2PT75GbDeblMttumfjSKMRCGLxsQCKTReE9KIJDJxDw/SmKxiOZWWh+ntrSlre2WXRAorbTSrZapip7X66kbMKtQUFBQCENznsmQ93vqBhh5r8fO85jAcsnIrcce1yV3uxgD8zl5uZgU+dGBVlrp6GbTKRPwffaDAek45Gz2/M0AAJ0OeTol+w0rFYrOZ3K1MhNJEjEAwHF4cBA8Z8B1zcXV6msv+JMR2yaHQ1LrXx/8Z+sNRxsWcwZeb6UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMDhUMTY6NDI6NTMrMDg6MDCKP7wnAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAw+2IEmwAAAEt0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fZ2Y3MDBzN2IzZncvc2hlbmd5aW4uc3ZnFog1MQAAAABJRU5ErkJggg=='; | ||
| 118 | - var recordBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAPRJREFUSMflVDEOwjAQO0e8gr2sZYVunREbD6ISfAgmkBjpC/hBEQ+AtTWD6QAI0gBlqRfLp+TiXC5n1nXgMUCS5HBoNBqj6IOMMFwuEpsNAABl6d3HihWrOJaBsuRPkGW+c929HAxuYefb6L+R0ZgkMrJYiItCnCT1sl5Y1jwXj0bNniJNJWqujfX7LyrwJh8AYDxWgulU0dPp20IFlxoODm61kpE4VnS9/puBXyPYgH7LbKY3PhwUnUw+NdC4CdW9+71UgyZspwIBB9No3O0klktxUahyx+Pz+lYG0Xzu84lXRqTqwRQAGAzns8R223gUdxZXGcAK5Hp0ClIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMDhUMTY6NDI6NTMrMDg6MDCKP7wnAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTA4VDE2OjQyOjUzKzA4OjAw+2IEmwAAAE50RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fZ2Y3MDBzN2IzZncvbHV6aGlzaGlwaW4uc3Zn5Zd7GQAAAABJRU5ErkJggg=='; | ||
| 119 | - var recordingBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAahJREFUSMdjYBjpgBFd4NZK+f+soQYG//T+yzFuUFUl2cApjEWM/758UZvysPDn3127GBkZGBgY/v4l6ICb9xTWsRbp6/9f9W8N44Jz5xgCGI4wfGFiIttrR/5n/3/U3KyR8rj8t0RdHS5lcAv+//yXzzhZTY1ii2FAmsGZocna+maD3GnWY62tNzbJBbDOffLkxie5eJYwa2uYMhaigzb2/zyGguPH/y9mTGKYYGlJUIMiYxDjHCen/4oMDAxznJzg4k8Z/jP+l5LCCAFCQP30Y5dfXVZWDI7/zzIs8PNjNGJ4/7/r+XNKA4rkoNZ4/lj0V9TmzUxJv0J+F+jrM3YyvPq/acsWujmA2oBkB9y4LifLxhoa+teAzYFtwtWr/8sZxBj9fHxo7oCbprJ72MqOHWNgZGBkYFy1isGGoZahTFSU0hAgOhcQnfph4P7/df9T9u1jPMn4nyHmxIn/bAzLGe7GxTHsZyj+f+zpUwYGBmmG6bQsiMr+L/v/rqlJY9Njm9889fW4lGEUxXCHwAomUgH3vxBG8c+f1WWf9P98sns3oaJ4FAAAbtWqHTT84QYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMDhUMTY6MzU6MjMrMDg6MDBLHbvEAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTA4VDE2OjM1OjIzKzA4OjAwOkADeAAAAE50RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fcTM1YTFhNHBtY2MvbHV6aGlzaGlwaW4uc3Zn6xlv1QAAAABJRU5ErkJggg=='; | ||
| 120 | - var gifBase64 = 'data:image/gif;base64,R0lGODlhgACAAKIAAP///93d3bu7u5mZmQAA/wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAEACwCAAIAfAB8AAAD/0i63P4wygYqmDjrzbtflvWNZGliYXiubKuloivPLlzReD7al+7/Eh5wSFQIi8hHYBkwHUmD6CD5YTJLz49USuVYraRsZ7vtar7XnQ1Kjpoz6LRHvGlz35O4nEPP2O94EnpNc2sef1OBGIOFMId/inB6jSmPdpGScR19EoiYmZobnBCIiZ95k6KGGp6ni4wvqxilrqBfqo6skLW2YBmjDa28r6Eosp27w8Rov8ekycqoqUHODrTRvXsQwArC2NLF29UM19/LtxO5yJd4Au4CK7DUNxPebG4e7+8n8iv2WmQ66BtoYpo/dvfacBjIkITBE9DGlMvAsOIIZjIUAixliv9ixYZVtLUos5GjwI8gzc3iCGghypQqrbFsme8lwZgLZtIcYfNmTJ34WPTUZw5oRxdD9w0z6iOpO15MgTh1BTTJUKos39jE+o/KS64IFVmsFfYT0aU7capdy7at27dw48qdS7eu3bt480I02vUbX2F/JxYNDImw4GiGE/P9qbhxVpWOI/eFKtlNZbWXuzlmG1mv58+gQ4seTbq06dOoU6vGQZJy0FNlMcV+czhQ7SQmYd8eMhPs5BxVdfcGEtV3buDBXQ+fURxx8oM6MT9P+Fh6dOrH2zavc13u9JXVJb520Vp8dvC76wXMuN5Sepm/1WtkEZHDefnzR9Qvsd9+/wi8+en3X0ntYVcSdAE+UN4zs7ln24CaLagghIxBaGF8kFGoIYV+Ybghh841GIyI5ICIFoklJsigihmimJOLEbLYIYwxSgigiZ+8l2KB+Ml4oo/w8dijjcrouCORKwIpnJIjMnkkksalNeR4fuBIm5UEYImhIlsGCeWNNJphpJdSTlkml1jWeOY6TnaRpppUctcmFW9mGSaZceYopH9zkjnjUe59iR5pdapWaGqHopboaYua1qije67GJ6CuJAAAIfkEBQUABAAsCgACAFcAMAAAA/9Iutz+ML5Ag7w46z0r5WAoSp43nihXVmnrdusrv+s332dt4Tyo9yOBUJD6oQBIQGs4RBlHySSKyczVTtHoidocPUNZaZAr9F5FYbGI3PWdQWn1mi36buLKFJvojsHjLnshdhl4L4IqbxqGh4gahBJ4eY1kiX6LgDN7fBmQEJI4jhieD4yhdJ2KkZk8oiSqEaatqBekDLKztBG2CqBACq4wJRi4PZu1sA2+v8C6EJexrBAD1AOBzsLE0g/V1UvYR9sN3eR6lTLi4+TlY1wz6Qzr8u1t6FkY8vNzZTxaGfn6mAkEGFDgL4LrDDJDyE4hEIbdHB6ESE1iD4oVLfLAqPETIsOODwmCDJlv5MSGJklaS6khAQAh+QQFBQAEACwfAAIAVwAwAAAD/0i63P5LSAGrvTjrNuf+YKh1nWieIumhbFupkivPBEzR+GnnfLj3ooFwwPqdAshAazhEGUXJJIrJ1MGOUamJ2jQ9QVltkCv0XqFh5IncBX01afGYnDqD40u2z76JK/N0bnxweC5sRB9vF34zh4gjg4uMjXobihWTlJUZlw9+fzSHlpGYhTminKSepqebF50NmTyor6qxrLO0L7YLn0ALuhCwCrJAjrUqkrjGrsIkGMW/BMEPJcphLgDaABjUKNEh29vdgTLLIOLpF80s5xrp8ORVONgi8PcZ8zlRJvf40tL8/QPYQ+BAgjgMxkPIQ6E6hgkdjoNIQ+JEijMsasNY0RQix4gKP+YIKXKkwJIFF6JMudFEAgAh+QQFBQAEACw8AAIAQgBCAAAD/kg0PPowykmrna3dzXvNmSeOFqiRaGoyaTuujitv8Gx/661HtSv8gt2jlwIChYtc0XjcEUnMpu4pikpv1I71astytkGh9wJGJk3QrXlcKa+VWjeSPZHP4Rtw+I2OW81DeBZ2fCB+UYCBfWRqiQp0CnqOj4J1jZOQkpOUIYx/m4oxg5cuAaYBO4Qop6c6pKusrDevIrG2rkwptrupXB67vKAbwMHCFcTFxhLIt8oUzLHOE9Cy0hHUrdbX2KjaENzey9Dh08jkz8Tnx83q66bt8PHy8/T19vf4+fr6AP3+/wADAjQmsKDBf6AOKjS4aaHDgZMeSgTQcKLDhBYPEswoA1BBAgAh+QQFBQAEACxOAAoAMABXAAAD7Ei6vPOjyUkrhdDqfXHm4OZ9YSmNpKmiqVqykbuysgvX5o2HcLxzup8oKLQQix0UcqhcVo5ORi+aHFEn02sDeuWqBGCBkbYLh5/NmnldxajX7LbPBK+PH7K6narfO/t+SIBwfINmUYaHf4lghYyOhlqJWgqDlAuAlwyBmpVnnaChoqOkpaanqKmqKgGtrq+wsbA1srW2ry63urasu764Jr/CAb3Du7nGt7TJsqvOz9DR0tPU1TIA2ACl2dyi3N/aneDf4uPklObj6OngWuzt7u/d8fLY9PXr9eFX+vv8+PnYlUsXiqC3c6PmUUgAACH5BAUFAAQALE4AHwAwAFcAAAPpSLrc/m7IAau9bU7MO9GgJ0ZgOI5leoqpumKt+1axPJO1dtO5vuM9yi8TlAyBvSMxqES2mo8cFFKb8kzWqzDL7Xq/4LB4TC6bz1yBes1uu9uzt3zOXtHv8xN+Dx/x/wJ6gHt2g3Rxhm9oi4yNjo+QkZKTCgGWAWaXmmOanZhgnp2goaJdpKGmp55cqqusrZuvsJays6mzn1m4uRAAvgAvuBW/v8GwvcTFxqfIycA3zA/OytCl0tPPO7HD2GLYvt7dYd/ZX99j5+Pi6tPh6+bvXuTuzujxXens9fr7YPn+7egRI9PPHrgpCQAAIfkEBQUABAAsPAA8AEIAQgAAA/lIutz+UI1Jq7026h2x/xUncmD5jehjrlnqSmz8vrE8u7V5z/m5/8CgcEgsGo/IpHLJbDqf0Kh0ShBYBdTXdZsdbb/Yrgb8FUfIYLMDTVYz2G13FV6Wz+lX+x0fdvPzdn9WeoJGAYcBN39EiIiKeEONjTt0kZKHQGyWl4mZdREAoQAcnJhBXBqioqSlT6qqG6WmTK+rsa1NtaGsuEu6o7yXubojsrTEIsa+yMm9SL8osp3PzM2cStDRykfZ2tfUtS/bRd3ewtzV5pLo4eLjQuUp70Hx8t9E9eqO5Oku5/ztdkxi90qPg3x2EMpR6IahGocPCxp8AGtigwQAIfkEBQUABAAsHwBOAFcAMAAAA/9Iutz+MMo36pg4682J/V0ojs1nXmSqSqe5vrDXunEdzq2ta3i+/5DeCUh0CGnF5BGULC4tTeUTFQVONYAs4CfoCkZPjFar83rBx8l4XDObSUL1Ott2d1U4yZwcs5/xSBB7dBMBhgEYfncrTBGDW4WHhomKUY+QEZKSE4qLRY8YmoeUfkmXoaKInJ2fgxmpqqulQKCvqRqsP7WooriVO7u8mhu5NacasMTFMMHCm8qzzM2RvdDRK9PUwxzLKdnaz9y/Kt8SyR3dIuXmtyHpHMcd5+jvWK4i8/TXHff47SLjQvQLkU+fG29rUhQ06IkEG4X/Rryp4mwUxSgLL/7IqFETB8eONT6ChCFy5ItqJomES6kgAQAh+QQFBQAEACwKAE4AVwAwAAAD/0i63A4QuEmrvTi3yLX/4MeNUmieITmibEuppCu3sDrfYG3jPKbHveDktxIaF8TOcZmMLI9NyBPanFKJp4A2IBx4B5lkdqvtfb8+HYpMxp3Pl1qLvXW/vWkli16/3dFxTi58ZRcChwIYf3hWBIRchoiHiotWj5AVkpIXi4xLjxiaiJR/T5ehoomcnZ+EGamqq6VGoK+pGqxCtaiiuJVBu7yaHrk4pxqwxMUzwcKbyrPMzZG90NGDrh/JH8t72dq3IN1jfCHb3L/e5ebh4ukmxyDn6O8g08jt7tf26ybz+m/W9GNXzUQ9fm1Q/APoSWAhhfkMAmpEbRhFKwsvCsmosRIHx444PoKcIXKkjIImjTzjkQAAIfkEBQUABAAsAgA8AEIAQgAAA/VIBNz+8KlJq72Yxs1d/uDVjVxogmQqnaylvkArT7A63/V47/m2/8CgcEgsGo/IpHLJbDqf0Kh0Sj0FroGqDMvVmrjgrDcTBo8v5fCZki6vCW33Oq4+0832O/at3+f7fICBdzsChgJGeoWHhkV0P4yMRG1BkYeOeECWl5hXQ5uNIAOjA1KgiKKko1CnqBmqqk+nIbCkTq20taVNs7m1vKAnurtLvb6wTMbHsUq4wrrFwSzDzcrLtknW16tI2tvERt6pv0fi48jh5h/U6Zs77EXSN/BE8jP09ZFA+PmhP/xvJgAMSGBgQINvEK5ReIZhQ3QEMTBLAAAh+QQFBQAEACwCAB8AMABXAAAD50i6DA4syklre87qTbHn4OaNYSmNqKmiqVqyrcvBsazRpH3jmC7yD98OCBF2iEXjBKmsAJsWHDQKmw571l8my+16v+CweEwum8+hgHrNbrvbtrd8znbR73MVfg838f8BeoB7doN0cYZvaIuMjY6PkJGSk2gClgJml5pjmp2YYJ6dX6GeXaShWaeoVqqlU62ir7CXqbOWrLafsrNctjIDwAMWvC7BwRWtNsbGFKc+y8fNsTrQ0dK3QtXAYtrCYd3eYN3c49/a5NVj5eLn5u3s6e7x8NDo9fbL+Mzy9/T5+tvUzdN3Zp+GBAAh+QQJBQAEACwCAAIAfAB8AAAD/0i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdArcQK2TOL7/nl4PSMwIfcUk5YhUOh3M5nNKiOaoWCuWqt1Ou16l9RpOgsvEMdocXbOZ7nQ7DjzTaeq7zq6P5fszfIASAYUBIYKDDoaGIImKC4ySH3OQEJKYHZWWi5iZG0ecEZ6eHEOio6SfqCaqpaytrpOwJLKztCO2jLi1uoW8Ir6/wCHCxMG2x7muysukzb230M6H09bX2Nna29zd3t/g4cAC5OXm5+jn3Ons7eba7vHt2fL16tj2+QL0+vXw/e7WAUwnrqDBgwgTKlzIsKHDh2gGSBwAccHEixAvaqTYcFCjRoYeNyoM6REhyZIHT4o0qPIjy5YTTcKUmHImx5cwE85cmJPnSYckK66sSAAj0aNIkypdyrSp06dQo0qdSrWq1atYs2rdyrWr169gwxZJAAA7'; | ||
| 121 | - var playBigBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwEAYAAAAHkiXEAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAByBJREFUeNrlXFlIVV0U3vsaaINmZoX0YAR6y8oGMkKLoMESSjBoUJEoIogoIggigoryIQoKGqi3Roh6TKGBIkNEe6hMgzTNKLPSUlMrNdvrf/juurlP5zpc7znb+r+X755pn7W+Pe+9zpVimIEUKVKJiUIKKWRqKs5OmwZOTBQkSFBUFK5HR+tPt7WBOzpwX3U1jquqwGVleK6iQkoppSQy7a8xEBERLVwIPnsWXF9PrqCxEXzxInjpUrDH47YO0h2hw8JwtG4deN8+8OzZA0vl7Vt/iZZCCtnUhPPt7fp9o0fjvpgYHHu9uD8+Hsdsh52hggTV1uLg2DHwpUvSIz3S093ttE4hB5qSxYuRAc+f910im5vBFy6As7LALORQ7RgzBullZIBPngQ3NPRt1+vXeH7NGtN69u8oERFFRIDPnQMrZe8YZ0huLhwMDzdjb1gYC4zj4uKAeaFIkbpxAwfWvse48FOngp89s7eeS1p2Nlg63vQF7Y8iRWrlSthZXR2wZhAR0dy55gwlIqI5c8AfPtgbeuUKHIqKMi3soP3z1UzwiRP2NbqtDbxsmXuGacK3tOgG/fwJ3rbNtIDO+J2ZiQzp6ND97uzE+RUrHDaAmxprif/+HQasXm1aKKcBPxcsADc1/VEjFClS8+eH7oXcuSpSpJ480V/Y0wPOyjItjNtgofWmiPHuHa7Hxg79RUT0e1Rjxb/X1ASnDw9vf/3S9bl1K/iEFSlSixbZdz7Xr5t2fLgBuuTn2xfUjRsHmVBYGNg6gWpo+FtHNU4DuowYAZ3Ky+11GzOm/4SIiGjDBvuczM52zAHua4iI6OpVcGEheO1a8PCdP/j9CNRyKFKk9u4doBDWCRXXBOcE0GekgVBUhPuSk00LPTAdCwp0+3n0GBER4AFenbQiJ8cdg7dvpwGB5xunT4PHjTMtuL0/qan29q9fH+AB62jnyxe31moGlwFWNDbCzq1bcez+snLffr14odtrMzrCBet6/Pnz7hoabAZY8fgxT5iGRwbs36/b19kJHjnS49+BEkIIMXmy/vjt26YdCA4pKdgHKC2Fo5cvh2xiFBTu3NGPw8Ox/5CW5tG3/hi8VffokRmDQwUeNOTlwc/KSmRIbq67djx9Cm5p+W2akEKmpfnaSt5zZdTXY8+0udmQcg5h0iQwD3MfPgRPn+7UG6GjUjiqrNSver0eVIWEBP85EiSIN7H/dSxZAuY1roMHHRt02OqamOhrgnoN46SQQn76ZFoad8Hj8kOH4D/PZJOSQvYKW11jYnxNkHWK3NFhWhKz8HrB9+7xaCU06fYKIiBBgiIjfRlgHTf/j+NlNMTFgceOHXJSJEgQ9wXCVyOk9AlvLfEDWDT6X+DAAXSiHz8OOSkppJCRkfrJ9vYR+NHaql8wNV42jVevUFJ37kQ8kHX8PlRMmOD/SYIEtbZ69IAkvsATs38dP36ADx8GJyc7IzyD+xbhqxE1Nb4a8PKlfiE+HsOxyEgYZI1A+9tRUADetQtNTF2dU29CJ84Twhkz9KtVVb4+oKxMvxAWxjM101KFBvX1qNmbNkHwNWucFl4HT/QmTvSfIkGCSks9HC2MsxxzyTekp5uWLjh0dYHz88FeL2ry5ctm7LHq2NMD7rXUg6rC0cKM9+/BfQS1hghDXg1VpEjdvasvLpqHf3VWs/P+/QA3Lltm75jz8T7BZQAvn9tscJgWXpEiNWuWvd2bNwcQwONbnq6p0R8oLnYnA7Zs6Vvw7m7Yd/z4gDe5DQH2Xrum29/SwoObfh7cts1egFWrnDU4Lg785g2Ytx4LC2H4zJmmhe3XD5+dsJsD1xhHjgwwgfBwPFBXpydQXe3uFqXzfU9o7ZUSXFRkX/IHMcENGKXgixY27fBwA8TZudO+5dixY4gJ37xpyQVfvEtmpmnHTQMFMiUFevBeL6OkZMg1GQlER4P5wwTGt29g65bmvw/4HShanD+5mjIlxC+cNw/cKxqYw7RDHZY9TOEXXpEiVVurC8+jtJUrnTNAkSK1fDle2NWlG9DeDs7IMC2UM35zU2Mt8Urhel6eywalp+vCMzhM++hRDlo1LeCg/dNGNdy5Wtt4LvEuCv+HodqHCu/e2Y8Cyss5aNW0sAPzh8fx1uEkgyMGHWxqgjM8NhYGWoNSraMnvm6+89aXDHjmap1AMUpKcD9/+D2MAYNzcsD9fRDNsZMcwsedfehiPJFeUhJ4925wWVnfdvFHiDt2gEM/MXT+rwp47UMKKeT27Ti7Zw+YA6UCgbdKKyr8cTVSSCEbG3Ge/5yDwWtD48fjfv6rAl7C6LUeb4uvX8FnzuD5U6ewjP35s9M6uQaUJP4Qgz8E4SbJ2sk5BV5jevAAvHmzqS9/hs0XJxBi1CgOWtVjVnlHKSEB16Oj/wgoE0L8LsFcM169AldV8Q4UjouKULKtNch9/AdsEf6XQYgIsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wMS0xMlQxMTo1NjowNSswODowMGcMj/QAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDEtMTJUMTE6NTY6MDUrMDg6MDAWUTdIAAAASXRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9wZHMzeWYxNGczYi9ib2Zhbmcuc3Zn11us5wAAAABJRU5ErkJggg=='; | ||
| 122 | - | ||
| 123 | - function _setStyle(dom, cssObj) { | ||
| 124 | - Object.keys(cssObj).forEach(function (key) { | ||
| 125 | - dom.style[key] = cssObj[key]; | ||
| 126 | - }) | ||
| 127 | - } | ||
| 128 | - | ||
| 129 | - var doms = {}; | ||
| 130 | - | ||
| 131 | - var fragment = document.createDocumentFragment(); | ||
| 132 | - var btnWrap = document.createElement('div'); | ||
| 133 | - var control1 = document.createElement('div'); | ||
| 134 | - var control2 = document.createElement('div'); | ||
| 135 | - var textDom = document.createElement('div'); | ||
| 136 | - var speedDom = document.createElement('div'); | ||
| 137 | - var playDom = document.createElement('div'); | ||
| 138 | - var playBigDom = document.createElement('div'); | ||
| 139 | - var pauseDom = document.createElement('div'); | ||
| 140 | - var screenshotsDom = document.createElement('div'); | ||
| 141 | - var fullscreenDom = document.createElement('div'); | ||
| 142 | - var minScreenDom = document.createElement('div'); | ||
| 143 | - var loadingDom = document.createElement('div'); | ||
| 144 | - var loadingTextDom = document.createElement('div'); | ||
| 145 | - var quietAudioDom = document.createElement('div'); | ||
| 146 | - var playAudioDom = document.createElement('div'); | ||
| 147 | - var recordDom = document.createElement('div'); | ||
| 148 | - var recordingDom = document.createElement('div'); | ||
| 149 | - var bgDom = document.createElement('div'); | ||
| 150 | - | ||
| 151 | - loadingTextDom.innerText = this._opt.loadingText || ''; | ||
| 152 | - textDom.innerText = this._opt.text || ''; | ||
| 153 | - speedDom.innerText = ''; | ||
| 154 | - playDom.title = '播放'; | ||
| 155 | - pauseDom.title = '暂停'; | ||
| 156 | - screenshotsDom.title = '截屏'; | ||
| 157 | - fullscreenDom.title = '全屏'; | ||
| 158 | - minScreenDom.title = '退出全屏'; | ||
| 159 | - quietAudioDom.title = '静音'; | ||
| 160 | - playAudioDom.title = '取消静音'; | ||
| 161 | - recordDom.title = '录制'; | ||
| 162 | - recordingDom.title = '取消录制'; | ||
| 163 | - | ||
| 164 | - var wrapStyle = { | ||
| 165 | - height: '38px', | ||
| 166 | - zIndex: 11, | ||
| 167 | - position: 'absolute', | ||
| 168 | - left: 0, | ||
| 169 | - bottom: 0, | ||
| 170 | - width: '100%', | ||
| 171 | - background: 'rgba(0,0,0)' | ||
| 172 | - }; | ||
| 173 | - | ||
| 174 | - var bgStyle = { | ||
| 175 | - position: 'absolute', | ||
| 176 | - width: '100%', | ||
| 177 | - height: '100%', | ||
| 178 | - }; | ||
| 179 | - | ||
| 180 | - if (this._opt.background) { | ||
| 181 | - bgStyle = Object.assign({}, bgStyle, { | ||
| 182 | - backgroundRepeat: "no-repeat", | ||
| 183 | - backgroundPosition: "center", | ||
| 184 | - backgroundSize: '100%', | ||
| 185 | - backgroundImage: "url('" + this._opt.background + "')" | ||
| 186 | - }) | ||
| 187 | - } | ||
| 188 | - | ||
| 189 | - // | ||
| 190 | - var loadingStyle = { | ||
| 191 | - position: 'absolute', | ||
| 192 | - width: '100%', | ||
| 193 | - height: '100%', | ||
| 194 | - textAlign: 'center', | ||
| 195 | - color: "#fff", | ||
| 196 | - display: 'none', | ||
| 197 | - backgroundImage: "url('" + gifBase64 + "')", | ||
| 198 | - backgroundRepeat: "no-repeat", | ||
| 199 | - backgroundPosition: "center", | ||
| 200 | - backgroundSize: "40px 40px", | ||
| 201 | - }; | ||
| 202 | - | ||
| 203 | - var playBigStyle = { | ||
| 204 | - position: 'absolute', | ||
| 205 | - width: '100%', | ||
| 206 | - height: '100%', | ||
| 207 | - display: 'none', | ||
| 208 | - background: 'rgba(0,0,0,0.4)', | ||
| 209 | - backgroundImage: "url('" + playBigBase64 + "')", | ||
| 210 | - backgroundRepeat: "no-repeat", | ||
| 211 | - backgroundPosition: "center", | ||
| 212 | - backgroundSize: "48px 48px", | ||
| 213 | - cursor: "pointer" | ||
| 214 | - }; | ||
| 215 | - | ||
| 216 | - var loadingTextStyle = { | ||
| 217 | - position: 'absolute', | ||
| 218 | - width: "100%", | ||
| 219 | - top: '60%', | ||
| 220 | - textAlign: 'center', | ||
| 221 | - } | ||
| 222 | - var controlStyle = { | ||
| 223 | - position: 'absolute', | ||
| 224 | - top: 0, | ||
| 225 | - height: '100%', | ||
| 226 | - display: 'flex', | ||
| 227 | - alignItems: 'center', | ||
| 228 | - }; | ||
| 229 | - var styleObj = { | ||
| 230 | - display: 'none', | ||
| 231 | - position: 'relative', | ||
| 232 | - fontSize: '13px', | ||
| 233 | - color: '#fff', | ||
| 234 | - lineHeight: '20px', | ||
| 235 | - marginLeft: '5px', | ||
| 236 | - marginRight: '5px', | ||
| 237 | - userSelect: 'none' | ||
| 238 | - }; | ||
| 239 | - var styleObj2 = { | ||
| 240 | - display: 'none', | ||
| 241 | - position: 'relative', | ||
| 242 | - width: '16px', | ||
| 243 | - height: '16px', | ||
| 244 | - marginLeft: '8px', | ||
| 245 | - marginRight: '8px', | ||
| 246 | - backgroundRepeat: "no-repeat", | ||
| 247 | - backgroundPosition: "center", | ||
| 248 | - backgroundSize: '100%', | ||
| 249 | - cursor: 'pointer', | ||
| 250 | - }; | ||
| 251 | - _setStyle(bgDom, bgStyle); | ||
| 252 | - _setStyle(btnWrap, wrapStyle); | ||
| 253 | - _setStyle(loadingDom, loadingStyle); | ||
| 254 | - _setStyle(playBigDom, playBigStyle); | ||
| 255 | - _setStyle(loadingTextDom, loadingTextStyle); | ||
| 256 | - _setStyle(control1, Object.assign({}, controlStyle, { | ||
| 257 | - left: 0 | ||
| 258 | - })); | ||
| 259 | - _setStyle(control2, Object.assign({}, controlStyle, { | ||
| 260 | - right: 0 | ||
| 261 | - })); | ||
| 262 | - _setStyle(textDom, styleObj); | ||
| 263 | - _setStyle(speedDom, styleObj); | ||
| 264 | - _setStyle(playDom, Object.assign({}, styleObj2, { | ||
| 265 | - backgroundImage: "url('" + playBase64 + "')", | ||
| 266 | - })); | ||
| 267 | - | ||
| 268 | - _setStyle(pauseDom, Object.assign({}, styleObj2, { | ||
| 269 | - backgroundImage: "url('" + pauseBase64 + "')" | ||
| 270 | - })); | ||
| 271 | - | ||
| 272 | - _setStyle(screenshotsDom, Object.assign({}, styleObj2, { | ||
| 273 | - backgroundImage: "url('" + screenshotBase64 + "')" | ||
| 274 | - })); | ||
| 275 | - | ||
| 276 | - _setStyle(fullscreenDom, Object.assign({}, styleObj2, { | ||
| 277 | - backgroundImage: "url('" + fullscreenBase64 + "')" | ||
| 278 | - })); | ||
| 279 | - | ||
| 280 | - _setStyle(minScreenDom, Object.assign({}, styleObj2, { | ||
| 281 | - backgroundImage: "url('" + minScreenBase64 + "')" | ||
| 282 | - })); | ||
| 283 | - | ||
| 284 | - _setStyle(quietAudioDom, Object.assign({}, styleObj2, { | ||
| 285 | - backgroundImage: "url('" + quietBase64 + "')" | ||
| 286 | - })); | ||
| 287 | - | ||
| 288 | - _setStyle(playAudioDom, Object.assign({}, styleObj2, { | ||
| 289 | - backgroundImage: "url('" + playAudioBase64 + "')" | ||
| 290 | - })); | ||
| 291 | - | ||
| 292 | - _setStyle(recordDom, Object.assign({}, styleObj2, { | ||
| 293 | - backgroundImage: "url('" + recordBase64 + "')" | ||
| 294 | - })); | ||
| 295 | - | ||
| 296 | - _setStyle(recordingDom, Object.assign({}, styleObj2, { | ||
| 297 | - backgroundImage: "url('" + recordingBase64 + "')" | ||
| 298 | - })); | ||
| 299 | - | ||
| 300 | - loadingDom.appendChild(loadingTextDom); | ||
| 301 | - if (this._opt.text) { | ||
| 302 | - control1.appendChild(textDom); | ||
| 303 | - doms.textDom = textDom; | ||
| 304 | - } | ||
| 305 | - if (this._opt.showBandwidth) { | ||
| 306 | - control1.appendChild(speedDom); | ||
| 307 | - doms.speedDom = speedDom; | ||
| 308 | - } | ||
| 309 | - | ||
| 310 | - // record | ||
| 311 | - //control2.appendChild(recordingDom); | ||
| 312 | - //control2.appendChild(recordDom); | ||
| 313 | - | ||
| 314 | - // screenshots | ||
| 315 | - if (this._opt.operateBtns.screenshot) { | ||
| 316 | - control2.appendChild(screenshotsDom); | ||
| 317 | - doms.screenshotsDom = screenshotsDom; | ||
| 318 | - } | ||
| 319 | - | ||
| 320 | - // play stop | ||
| 321 | - if (this._opt.operateBtns.play) { | ||
| 322 | - control2.appendChild(playDom); | ||
| 323 | - control2.appendChild(pauseDom); | ||
| 324 | - doms.playDom = playDom; | ||
| 325 | - doms.pauseDom = pauseDom; | ||
| 326 | - } | ||
| 327 | - | ||
| 328 | - // audio | ||
| 329 | - if (this._opt.operateBtns.audio) { | ||
| 330 | - control2.appendChild(playAudioDom); | ||
| 331 | - control2.appendChild(quietAudioDom); | ||
| 332 | - doms.playAudioDom = playAudioDom; | ||
| 333 | - doms.quietAudioDom = quietAudioDom; | ||
| 334 | - } | ||
| 335 | - | ||
| 336 | - // fullscreen | ||
| 337 | - if (this._opt.operateBtns.fullscreen) { | ||
| 338 | - control2.appendChild(fullscreenDom); | ||
| 339 | - control2.appendChild(minScreenDom); | ||
| 340 | - doms.fullscreenDom = fullscreenDom; | ||
| 341 | - doms.minScreenDom = minScreenDom; | ||
| 342 | - } | ||
| 343 | - | ||
| 344 | - btnWrap.appendChild(control1); | ||
| 345 | - btnWrap.appendChild(control2); | ||
| 346 | - | ||
| 347 | - fragment.appendChild(bgDom); | ||
| 348 | - doms.bgDom = bgDom; | ||
| 349 | - fragment.appendChild(loadingDom); | ||
| 350 | - doms.loadingDom = loadingDom; | ||
| 351 | - if (this._showControl()) { | ||
| 352 | - fragment.appendChild(btnWrap); | ||
| 353 | - } | ||
| 354 | - if (this._opt.operateBtns.play) { | ||
| 355 | - fragment.appendChild(playBigDom); | ||
| 356 | - doms.playBigDom = playBigDom; | ||
| 357 | - } | ||
| 358 | - this._container.appendChild(fragment); | ||
| 359 | - this._doms = doms; | ||
| 360 | - }; | ||
| 361 | - | ||
| 362 | - Jessibuca.prototype._initWakeLock = function () { | ||
| 363 | - this._wakeLock = null; | ||
| 364 | - var _this = this; | ||
| 365 | - var handleWakeLock = () => { | ||
| 366 | - if (this._wakeLock !== null && "visible" === document.visibilityState) { | ||
| 367 | - _this._enableWakeLock(); | ||
| 368 | - } | ||
| 369 | - }; | ||
| 370 | - | ||
| 371 | - document.addEventListener('visibilitychange', handleWakeLock); | ||
| 372 | - document.addEventListener('fullscreenchange', handleWakeLock); | ||
| 373 | - }; | ||
| 374 | - | ||
| 375 | - Jessibuca.prototype._enableWakeLock = function () { | ||
| 376 | - if (this._opt.keepScreenOn) { | ||
| 377 | - if ("wakeLock" in navigator) { | ||
| 378 | - var _this = this; | ||
| 379 | - navigator.wakeLock.request("screen").then((lock) => { | ||
| 380 | - _this._wakeLock = lock; | ||
| 381 | - _this._wakeLock.addEventListener('release', function () { | ||
| 382 | - }); | ||
| 383 | - }) | ||
| 384 | - } | ||
| 385 | - } | ||
| 386 | - }; | ||
| 387 | - | ||
| 388 | - | ||
| 389 | - Jessibuca.prototype._initGainNode = function () { | ||
| 390 | - var gainNode = this._audioContext.createGain(); | ||
| 391 | - var _this = this; | ||
| 392 | - var source; | ||
| 393 | - if (!navigator.mediaDevices.getUserMedia) { | ||
| 394 | - console.log('getUserMedia not supported on your browser!'); | ||
| 395 | - return; | ||
| 396 | - } | ||
| 397 | - | ||
| 398 | - navigator.mediaDevices.getUserMedia( | ||
| 399 | - // constraints - only audio needed for this app | ||
| 400 | - { | ||
| 401 | - audio: true | ||
| 402 | - }, | ||
| 403 | - | ||
| 404 | - // Success callback | ||
| 405 | - function (stream) { | ||
| 406 | - source = _this._audioContext.createMediaStreamSource(stream); | ||
| 407 | - source.connect(gainNode); | ||
| 408 | - gainNode.connect(_this._audioContext.destination); | ||
| 409 | - _this._gainNode = gainNode; | ||
| 410 | - }, | ||
| 411 | - | ||
| 412 | - // Error callback | ||
| 413 | - function (err) { | ||
| 414 | - console.log('The following gUM error occurred: ' + err); | ||
| 415 | - } | ||
| 416 | - ); | ||
| 417 | - }; | ||
| 418 | - | ||
| 419 | - Jessibuca.prototype._showControl = function () { | ||
| 420 | - var result = false; | ||
| 421 | - | ||
| 422 | - var hasBtnShow = false; | ||
| 423 | - Object.keys(this._opt.operateBtns).forEach((key) => { | ||
| 424 | - if (this._opt.operateBtns[key]) { | ||
| 425 | - hasBtnShow = true; | ||
| 426 | - } | ||
| 427 | - }); | ||
| 428 | - | ||
| 429 | - if (this._opt.showBandwidth || this._opt.text || hasBtnShow) { | ||
| 430 | - result = true; | ||
| 431 | - } | ||
| 432 | - | ||
| 433 | - return result; | ||
| 434 | - }; | ||
| 435 | - | ||
| 436 | - Jessibuca.prototype._onMessage = function () { | ||
| 437 | - var _this = this; | ||
| 438 | - this._decoderWorker.onmessage = function (event) { | ||
| 439 | - var msg = event.data; | ||
| 440 | - switch (msg.cmd) { | ||
| 441 | - case "init": | ||
| 442 | - _this._opt.isDebug && console.log("decoder worker init") | ||
| 443 | - _this.setBufferTime(_this._opt.videoBuffer); | ||
| 444 | - if (!_this._hasLoaded) { | ||
| 445 | - _this._opt.isDebug && console.log("has loaded"); | ||
| 446 | - _this._hasLoaded = true; | ||
| 447 | - _this.onLoad(); | ||
| 448 | - _this._trigger('load'); | ||
| 449 | - } | ||
| 450 | - break | ||
| 451 | - case "initSize": | ||
| 452 | - _this._canvasElement.width = msg.w; | ||
| 453 | - _this._canvasElement.height = msg.h; | ||
| 454 | - _this.onInitSize(); | ||
| 455 | - _this.resize(); | ||
| 456 | - _this._trigger('videoInfo', {w: msg.w, h: msg.h}); | ||
| 457 | - if (_this.isWebGL()) { | ||
| 458 | - | ||
| 459 | - } else { | ||
| 460 | - _this._initRGB(msg.w, msg.h) | ||
| 461 | - } | ||
| 462 | - break | ||
| 463 | - case "render": | ||
| 464 | - if (_this._contextGL) { | ||
| 465 | - _this._drawNextOutputPictureGL(msg.output); | ||
| 466 | - } else { | ||
| 467 | - _this._drawNextOutputPictureRGBA(msg.buffer); | ||
| 468 | - } | ||
| 469 | - if (_this.loading) { | ||
| 470 | - _this.loading = false; | ||
| 471 | - _this.playing = true; | ||
| 472 | - _this._opt.isDebug && console.log("clear check loading timeout"); | ||
| 473 | - _this._clearCheckLoading(); | ||
| 474 | - } | ||
| 475 | - _this._trigger('timeUpdate', msg.ts); | ||
| 476 | - _this.onTimeUpdate(msg.ts); | ||
| 477 | - _this._updateStats({bps: msg.bps, ts: msg.ts}); | ||
| 478 | - _this._checkHeart(); | ||
| 479 | - break | ||
| 480 | - case "initAudio": | ||
| 481 | - _this._initAudioPlay(msg.frameCount, msg.samplerate, msg.channels) | ||
| 482 | - _this._trigger('audioInfo', { | ||
| 483 | - numOfChannels: msg.channels, // 声频通道 | ||
| 484 | - length: msg.frameCount, // 帧数 | ||
| 485 | - sampleRate: msg.samplerate // 采样率 | ||
| 486 | - }); | ||
| 487 | - break | ||
| 488 | - case "playAudio": | ||
| 489 | - _this._playAudio(msg.buffer) | ||
| 490 | - break | ||
| 491 | - case "print": | ||
| 492 | - _this.onLog(msg.text) | ||
| 493 | - this._trigger('log', msg.text); | ||
| 494 | - _this._opt.isDebug && console.log(msg.text); | ||
| 495 | - break | ||
| 496 | - case "printErr": | ||
| 497 | - _this.onLog(msg.text); | ||
| 498 | - this._trigger('log', msg.text); | ||
| 499 | - _this.onError(msg.text); | ||
| 500 | - this._trigger('error', msg.text); | ||
| 501 | - _this._opt.isDebug && console.error(msg.text); | ||
| 502 | - break; | ||
| 503 | - case "initAudioPlanar": | ||
| 504 | - _this._initAudioPlanar(msg); | ||
| 505 | - _this._trigger('audioInfo', { | ||
| 506 | - numOfChannels: msg.channels, // 声频通道 | ||
| 507 | - length: undefined, // 帧数 | ||
| 508 | - sampleRate: msg.samplerate // 采样率 | ||
| 509 | - }); | ||
| 510 | - break; | ||
| 511 | - default: | ||
| 512 | - _this._opt.isDebug && console.log(msg); | ||
| 513 | - _this[msg.cmd](msg) | ||
| 514 | - } | ||
| 515 | - }; | ||
| 516 | - }; | ||
| 517 | - | ||
| 518 | - Jessibuca.prototype._initEventListener = function () { | ||
| 519 | - var _this = this; | ||
| 520 | - | ||
| 521 | - this._doms.playDom && this._doms.playDom.addEventListener('click', function (e) { | ||
| 522 | - e.stopPropagation(); | ||
| 523 | - _this.play(); | ||
| 524 | - }, false); | ||
| 525 | - | ||
| 526 | - this._doms.playBigDom && this._doms.playBigDom.addEventListener('click', function (e) { | ||
| 527 | - e.stopPropagation(); | ||
| 528 | - _this.play(); | ||
| 529 | - }, false); | ||
| 530 | - | ||
| 531 | - this._doms.pauseDom && this._doms.pauseDom.addEventListener('click', function (e) { | ||
| 532 | - e.stopPropagation(); | ||
| 533 | - _this.pause(); | ||
| 534 | - }, false); | ||
| 535 | - | ||
| 536 | - // screenshots | ||
| 537 | - this._doms.screenshotsDom && this._doms.screenshotsDom.addEventListener('click', function (e) { | ||
| 538 | - e.stopPropagation(); | ||
| 539 | - var filename = _this._opt.text + '' + _now(); | ||
| 540 | - _this._screenshot(filename); | ||
| 541 | - }, false); | ||
| 542 | - // | ||
| 543 | - this._doms.fullscreenDom && this._doms.fullscreenDom.addEventListener('click', function (e) { | ||
| 544 | - e.stopPropagation(); | ||
| 545 | - _this.fullscreen = true; | ||
| 546 | - }, false); | ||
| 547 | - // | ||
| 548 | - this._doms.minScreenDom && this._doms.minScreenDom.addEventListener('click', function (e) { | ||
| 549 | - e.stopPropagation(); | ||
| 550 | - _this.fullscreen = false; | ||
| 551 | - }, false); | ||
| 552 | - // | ||
| 553 | - this._doms.recordDom && this._doms.recordDom.addEventListener('click', function (e) { | ||
| 554 | - e.stopPropagation(); | ||
| 555 | - _this.recording = true; | ||
| 556 | - }, false); | ||
| 557 | - // | ||
| 558 | - this._doms.recordingDom && this._doms.recordingDom.addEventListener('click', function (e) { | ||
| 559 | - e.stopPropagation(); | ||
| 560 | - _this.recording = false; | ||
| 561 | - }, false); | ||
| 562 | - | ||
| 563 | - this._doms.quietAudioDom && this._doms.quietAudioDom.addEventListener('click', function (e) { | ||
| 564 | - e.stopPropagation(); | ||
| 565 | - _this.cancelMute(); | ||
| 566 | - }, false); | ||
| 567 | - | ||
| 568 | - this._doms.playAudioDom && this._doms.playAudioDom.addEventListener('click', function (e) { | ||
| 569 | - e.stopPropagation(); | ||
| 570 | - _this.mute(); | ||
| 571 | - }, false); | ||
| 572 | - }; | ||
| 573 | - /** | ||
| 574 | - * set debug | ||
| 575 | - * @param flag | ||
| 576 | - */ | ||
| 577 | - Jessibuca.prototype.setDebug = function (flag) { | ||
| 578 | - this._opt.isDebug = !!flag; | ||
| 579 | - }; | ||
| 580 | - /** | ||
| 581 | - * mute | ||
| 582 | - */ | ||
| 583 | - Jessibuca.prototype.mute = function () { | ||
| 584 | - this._audioEnabled(false); | ||
| 585 | - this.quieting = true; | ||
| 586 | - }; | ||
| 587 | - | ||
| 588 | - /** | ||
| 589 | - * cancel mute | ||
| 590 | - */ | ||
| 591 | - Jessibuca.prototype.cancelMute = function () { | ||
| 592 | - this._audioEnabled(true); | ||
| 593 | - this.quieting = false; | ||
| 594 | - }; | ||
| 595 | - | ||
| 596 | - /** | ||
| 597 | - * 设置旋转角度 | ||
| 598 | - */ | ||
| 599 | - Jessibuca.prototype.setRotate = function (deg) { | ||
| 600 | - | ||
| 601 | - }; | ||
| 602 | - | ||
| 603 | - Jessibuca.prototype._initStatus = function () { | ||
| 604 | - this._loading = true; | ||
| 605 | - this.loading = true; | ||
| 606 | - this._recording = false; | ||
| 607 | - this.recording = false; | ||
| 608 | - this._playing = false; | ||
| 609 | - this.playing = false; | ||
| 610 | - this._quieting = this._opt.isNotMute ? false : true; | ||
| 611 | - this.quieting = this._opt.isNotMute ? false : true; | ||
| 612 | - this._fullscreen = false; | ||
| 613 | - this.fullscreen = false; | ||
| 614 | - } | ||
| 615 | - | ||
| 616 | - Jessibuca.prototype._initBtns = function () { | ||
| 617 | - // show | ||
| 618 | - _domToggle(this._doms.pauseDom, true); | ||
| 619 | - _domToggle(this._doms.screenshotsDom, true); | ||
| 620 | - _domToggle(this._doms.fullscreenDom, true); | ||
| 621 | - _domToggle(this._doms.quietAudioDom, true); | ||
| 622 | - _domToggle(this._doms.textDom, true); | ||
| 623 | - _domToggle(this._doms.speedDom, true); | ||
| 624 | - _domToggle(this._doms.recordDom, true); | ||
| 625 | - // hide | ||
| 626 | - _domToggle(this._doms.loadingDom, false); | ||
| 627 | - _domToggle(this._doms.playDom, false); | ||
| 628 | - _domToggle(this._doms.playBigDom, false); | ||
| 629 | - _domToggle(this._doms.bgDom, false); | ||
| 630 | - }; | ||
| 631 | - | ||
| 632 | - Jessibuca.prototype._hideBtns = function () { | ||
| 633 | - var _this = this; | ||
| 634 | - Object.keys(this._doms).forEach(function (dom) { | ||
| 635 | - if (dom !== 'bgDom') { | ||
| 636 | - _domToggle(_this._doms[dom], false); | ||
| 637 | - } | ||
| 638 | - }) | ||
| 639 | - }; | ||
| 640 | - | ||
| 641 | - function _checkFull() { | ||
| 642 | - var isFull = document.fullscreenElement || window.webkitFullscreenElement || document.msFullscreenElement; | ||
| 643 | - if (isFull === undefined) isFull = false; | ||
| 644 | - return !!isFull; | ||
| 645 | - } | ||
| 646 | - | ||
| 647 | - Jessibuca.prototype._updateStats = function (options) { | ||
| 648 | - options = options || {}; | ||
| 649 | - | ||
| 650 | - if (!this._startBpsTime) { | ||
| 651 | - this._startBpsTime = _now(); | ||
| 652 | - } | ||
| 653 | - var _nowTime = _now(); | ||
| 654 | - var timestamp = _nowTime - this._startBpsTime; | ||
| 655 | - | ||
| 656 | - if (timestamp < 1 * 1000) { | ||
| 657 | - this._bps += (options.bps || 0); | ||
| 658 | - this._stats.fps += 1; | ||
| 659 | - this._stats.vbps += parseInt((options.bps || 0)); | ||
| 660 | - return; | ||
| 661 | - } | ||
| 662 | - this._stats.ts = options.ts; | ||
| 663 | - this._doms.speedDom && (this._doms.speedDom.innerText = _bpsSize(this._bps)); | ||
| 664 | - this._trigger('bps', this._bps); | ||
| 665 | - this._trigger('stats', this._stats); | ||
| 666 | - this._trigger('performance', _fpsStatus(this._stats.fps)); | ||
| 667 | - this._bps = 0; | ||
| 668 | - this._stats.fps = 0; | ||
| 669 | - this._stats.vbps = 0; | ||
| 670 | - this._startBpsTime = _nowTime; | ||
| 671 | - }; | ||
| 672 | - | ||
| 673 | - | ||
| 674 | - Jessibuca.prototype._checkHeart = function () { | ||
| 675 | - if (this._checkHeartTimeout) { | ||
| 676 | - clearTimeout(this._checkHeartTimeout); | ||
| 677 | - this._checkHeartTimeout = null; | ||
| 678 | - } | ||
| 679 | - var _this = this; | ||
| 680 | - this._checkHeartTimeout = setTimeout(function () { | ||
| 681 | - _this._opt.isDebug && console.log('check heart timeout'); | ||
| 682 | - _this._trigger('timeout'); | ||
| 683 | - _this.recording = false; | ||
| 684 | - _this.playing = false; | ||
| 685 | - _this._close(); | ||
| 686 | - }, this._opt.timeout * 1000); | ||
| 687 | - }; | ||
| 688 | - | ||
| 689 | - Jessibuca.prototype._checkLoading = function () { | ||
| 690 | - if (this._checkLoadingTimeout) { | ||
| 691 | - clearTimeout(this._checkLoadingTimeout); | ||
| 692 | - this._checkLoadingTimeout = null; | ||
| 693 | - } | ||
| 694 | - var _this = this; | ||
| 695 | - this._checkLoadingTimeout = setTimeout(function () { | ||
| 696 | - _this._opt.isDebug && console.log('check loading timeout'); | ||
| 697 | - _this._trigger('timeout'); | ||
| 698 | - _this.playing = false; | ||
| 699 | - _this._close(); | ||
| 700 | - _domToggle(_this._doms.loadingDom, false); | ||
| 701 | - }, this._opt.timeout * 1000); | ||
| 702 | - }; | ||
| 703 | - | ||
| 704 | - Jessibuca.prototype._clearCheckLoading = function () { | ||
| 705 | - if (this._checkLoadingTimeout) { | ||
| 706 | - clearTimeout(this._checkLoadingTimeout); | ||
| 707 | - this._checkLoadingTimeout = null; | ||
| 708 | - } | ||
| 709 | - }; | ||
| 710 | - | ||
| 711 | - Jessibuca.prototype._initCheckVariable = function () { | ||
| 712 | - this._startBpsTime = ''; | ||
| 713 | - this._bps = 0; | ||
| 714 | - if (this._checkHeartTimeout) { | ||
| 715 | - clearTimeout(this._checkHeartTimeout); | ||
| 716 | - this._checkHeartTimeout = null; | ||
| 717 | - } | ||
| 718 | - } | ||
| 719 | - // | ||
| 720 | - Jessibuca.prototype._initAudioPlanar = function (msg) { | ||
| 721 | - var channels = msg.channels | ||
| 722 | - var samplerate = msg.samplerate | ||
| 723 | - var context = this._audioContext; | ||
| 724 | - var isPlaying = false; | ||
| 725 | - var audioBuffers = []; | ||
| 726 | - if (!context) return false; | ||
| 727 | - var _this = this | ||
| 728 | - this._playAudio = function (buffer) { | ||
| 729 | - var frameCount = buffer[0][0].length | ||
| 730 | - var audioBuffer = context.createBuffer(channels, frameCount * buffer.length, samplerate); | ||
| 731 | - var copyToCtxBuffer = function (fromBuffer) { | ||
| 732 | - for (var channel = 0; channel < channels; channel++) { | ||
| 733 | - var nowBuffering = audioBuffer.getChannelData(channel); | ||
| 734 | - for (var j = 0; j < buffer.length; j++) { | ||
| 735 | - for (var i = 0; i < frameCount; i++) { | ||
| 736 | - nowBuffering[i + j * frameCount] = fromBuffer[j][channel][i] | ||
| 737 | - } | ||
| 738 | - //postMessage({ cmd: "setBufferA", buffer: fromBuffer[j] }, '*', fromBuffer[j].map(x => x.buffer)) | ||
| 739 | - } | ||
| 740 | - } | ||
| 741 | - } | ||
| 742 | - var playNextBuffer = function () { | ||
| 743 | - isPlaying = false; | ||
| 744 | - //console.log("~", audioBuffers.length) | ||
| 745 | - if (audioBuffers.length) { | ||
| 746 | - playAudio(audioBuffers.shift()); | ||
| 747 | - } | ||
| 748 | - //if (audioBuffers.length > 1) audioBuffers.shift(); | ||
| 749 | - }; | ||
| 750 | - var playAudio = function (fromBuffer) { | ||
| 751 | - if (!fromBuffer) return | ||
| 752 | - if (isPlaying) { | ||
| 753 | - audioBuffers.push(fromBuffer); | ||
| 754 | - //console.log(audioBuffers.length) | ||
| 755 | - return; | ||
| 756 | - } | ||
| 757 | - isPlaying = true; | ||
| 758 | - copyToCtxBuffer(fromBuffer); | ||
| 759 | - var source = context.createBufferSource(); | ||
| 760 | - source.buffer = audioBuffer; | ||
| 761 | - source.connect(context.destination); | ||
| 762 | - // source.onended = playNextBuffer; | ||
| 763 | - source.start(); | ||
| 764 | - }; | ||
| 765 | - _this._playAudio = playAudio | ||
| 766 | - _this.audioInterval = setInterval(playNextBuffer, audioBuffer.duration * 1000); | ||
| 767 | - playAudio(buffer) | ||
| 768 | - }; | ||
| 769 | - } | ||
| 770 | - | ||
| 771 | - function _unlock(context) { | ||
| 772 | - context.resume(); | ||
| 773 | - var source = context.createBufferSource(); | ||
| 774 | - source.buffer = context.createBuffer(1, 1, 22050); | ||
| 775 | - source.connect(context.destination); | ||
| 776 | - if (source.noteOn) | ||
| 777 | - source.noteOn(0); | ||
| 778 | - else | ||
| 779 | - source.start(0); | ||
| 780 | - } | ||
| 781 | - | ||
| 782 | - function _domToggle(dom, toggle) { | ||
| 783 | - if (dom) { | ||
| 784 | - dom.style.display = toggle ? 'block' : "none"; | ||
| 785 | - } | ||
| 786 | - } | ||
| 787 | - | ||
| 788 | - function _dataURLToFile(dataURL) { | ||
| 789 | - const arr = dataURL.split(","); | ||
| 790 | - const bstr = atob(arr[1]); | ||
| 791 | - const type = arr[0].replace("data:", "").replace(";base64", "") | ||
| 792 | - let n = bstr.length, u8arr = new Uint8Array(n); | ||
| 793 | - while (n--) { | ||
| 794 | - u8arr[n] = bstr.charCodeAt(n); | ||
| 795 | - } | ||
| 796 | - return new File([u8arr], 'file', {type}); | ||
| 797 | - } | ||
| 798 | - | ||
| 799 | - function _downloadImg(content, fileName) { | ||
| 800 | - const aLink = document.createElement("a"); | ||
| 801 | - aLink.download = fileName; | ||
| 802 | - aLink.href = URL.createObjectURL(content); | ||
| 803 | - aLink.click(); | ||
| 804 | - URL.revokeObjectURL(content); | ||
| 805 | - } | ||
| 806 | - | ||
| 807 | - function _bpsSize(value) { | ||
| 808 | - if (null == value || value === '') { | ||
| 809 | - return "0 KB/S"; | ||
| 810 | - } | ||
| 811 | - var srcsize = parseFloat(value); | ||
| 812 | - var size = srcsize / 1024; | ||
| 813 | - size = size.toFixed(2); | ||
| 814 | - return size + 'KB/S'; | ||
| 815 | - } | ||
| 816 | - | ||
| 817 | - function _fpsStatus(fps) { | ||
| 818 | - var result = 0; | ||
| 819 | - if (fps >= 24) { | ||
| 820 | - result = 2; | ||
| 821 | - } else if (fps >= 15) { | ||
| 822 | - result = 1; | ||
| 823 | - } | ||
| 824 | - | ||
| 825 | - return result; | ||
| 826 | - } | ||
| 827 | - | ||
| 828 | - /** | ||
| 829 | - * set audio | ||
| 830 | - * @param flag | ||
| 831 | - */ | ||
| 832 | - Jessibuca.prototype._audioEnabled = function (flag) { | ||
| 833 | - if (flag) { | ||
| 834 | - _unlock(this._audioContext) | ||
| 835 | - this._audioEnabled = function (flag) { | ||
| 836 | - if (flag) { | ||
| 837 | - // 恢复 | ||
| 838 | - this._audioContext.resume(); | ||
| 839 | - | ||
| 840 | - } else { | ||
| 841 | - // 暂停 | ||
| 842 | - this._audioContext.suspend(); | ||
| 843 | - } | ||
| 844 | - } | ||
| 845 | - } else { | ||
| 846 | - this._audioContext.suspend(); | ||
| 847 | - } | ||
| 848 | - } | ||
| 849 | - | ||
| 850 | - Jessibuca.prototype._playAudio = function (data) { | ||
| 851 | - var context = this._audioContext; | ||
| 852 | - var isPlaying = false; | ||
| 853 | - var isDecoding = false; | ||
| 854 | - if (!context) return false; | ||
| 855 | - var audioBuffers = []; | ||
| 856 | - var decodeQueue = [] | ||
| 857 | - var _this = this | ||
| 858 | - var playNextBuffer = function (e) { | ||
| 859 | - if (audioBuffers.length) { | ||
| 860 | - playBuffer(audioBuffers.shift()) | ||
| 861 | - } | ||
| 862 | - }; | ||
| 863 | - var playBuffer = function (buffer) { | ||
| 864 | - isPlaying = true; | ||
| 865 | - var audioBufferSouceNode = context.createBufferSource(); | ||
| 866 | - audioBufferSouceNode.buffer = buffer; | ||
| 867 | - audioBufferSouceNode.connect(context.destination); | ||
| 868 | - // audioBufferSouceNode.onended = playNextBuffer; | ||
| 869 | - audioBufferSouceNode.start(); | ||
| 870 | - if (!_this.audioInterval) { | ||
| 871 | - _this.audioInterval = setInterval(playNextBuffer, buffer.duration * 1000 - 1); | ||
| 872 | - } | ||
| 873 | - } | ||
| 874 | - var decodeAudio = function () { | ||
| 875 | - if (decodeQueue.length) { | ||
| 876 | - context.decodeAudioData(decodeQueue.shift(), tryPlay, decodeAudio); | ||
| 877 | - } else { | ||
| 878 | - isDecoding = false | ||
| 879 | - } | ||
| 880 | - } | ||
| 881 | - var tryPlay = function (buffer) { | ||
| 882 | - decodeAudio() | ||
| 883 | - if (isPlaying) { | ||
| 884 | - audioBuffers.push(buffer); | ||
| 885 | - } else { | ||
| 886 | - playBuffer(buffer) | ||
| 887 | - } | ||
| 888 | - } | ||
| 889 | - var playAudio = function (data) { | ||
| 890 | - decodeQueue.push(...data) | ||
| 891 | - if (!isDecoding) { | ||
| 892 | - isDecoding = true | ||
| 893 | - decodeAudio() | ||
| 894 | - } | ||
| 895 | - } | ||
| 896 | - this._playAudio = playAudio | ||
| 897 | - playAudio(data) | ||
| 898 | - } | ||
| 899 | - Jessibuca.prototype._initAudioPlay = function (frameCount, samplerate, channels) { | ||
| 900 | - var context = this._audioContext; | ||
| 901 | - var isPlaying = false; | ||
| 902 | - var audioBuffers = []; | ||
| 903 | - if (!context) return false; | ||
| 904 | - var _this = this | ||
| 905 | - var resampled = samplerate < 22050; | ||
| 906 | - if (resampled) { | ||
| 907 | - _this._opt.isDebug && console.log("resampled!") | ||
| 908 | - } | ||
| 909 | - var audioBuffer = resampled ? context.createBuffer(channels, frameCount << 1, samplerate << 1) : context.createBuffer(channels, frameCount, samplerate); | ||
| 910 | - var playNextBuffer = function () { | ||
| 911 | - isPlaying = false; | ||
| 912 | - //console.log("~", audioBuffers.length) | ||
| 913 | - if (audioBuffers.length) { | ||
| 914 | - playAudio(audioBuffers.shift()); | ||
| 915 | - } | ||
| 916 | - }; | ||
| 917 | - | ||
| 918 | - var copyToCtxBuffer = channels > 1 ? function (fromBuffer) { | ||
| 919 | - for (var channel = 0; channel < channels; channel++) { | ||
| 920 | - var nowBuffering = audioBuffer.getChannelData(channel); | ||
| 921 | - if (resampled) { | ||
| 922 | - for (var i = 0; i < frameCount; i++) { | ||
| 923 | - nowBuffering[i * 2] = nowBuffering[i * 2 + 1] = fromBuffer[i * (channel + 1)] / 32768; | ||
| 924 | - } | ||
| 925 | - } else | ||
| 926 | - for (var i = 0; i < frameCount; i++) { | ||
| 927 | - nowBuffering[i] = fromBuffer[i * (channel + 1)] / 32768; | ||
| 928 | - } | ||
| 929 | - | ||
| 930 | - } | ||
| 931 | - } : function (fromBuffer) { | ||
| 932 | - var nowBuffering = audioBuffer.getChannelData(0); | ||
| 933 | - for (var i = 0; i < nowBuffering.length; i++) { | ||
| 934 | - nowBuffering[i] = fromBuffer[i] / 32768; | ||
| 935 | - } | ||
| 936 | - }; | ||
| 937 | - var playAudio = function (fromBuffer) { | ||
| 938 | - if (isPlaying) { | ||
| 939 | - audioBuffers.push(fromBuffer); | ||
| 940 | - return; | ||
| 941 | - } | ||
| 942 | - isPlaying = true; | ||
| 943 | - copyToCtxBuffer(fromBuffer); | ||
| 944 | - var source = context.createBufferSource(); | ||
| 945 | - source.buffer = audioBuffer; | ||
| 946 | - source.connect(context.destination); | ||
| 947 | - if (!_this.audioInterval) { | ||
| 948 | - _this.audioInterval = setInterval(playNextBuffer, audioBuffer.duration * 1000); | ||
| 949 | - } | ||
| 950 | - source.start(); | ||
| 951 | - }; | ||
| 952 | - this._playAudio = playAudio; | ||
| 953 | - } | ||
| 954 | - /** | ||
| 955 | - * Returns true if the canvas supports WebGL | ||
| 956 | - */ | ||
| 957 | - Jessibuca.prototype.isWebGL = function () { | ||
| 958 | - return !!this._contextGL; | ||
| 959 | - }; | ||
| 960 | - /** | ||
| 961 | - * set timeout | ||
| 962 | - * @param time | ||
| 963 | - */ | ||
| 964 | - Jessibuca.prototype.setTimeout = function (time) { | ||
| 965 | - if (typeof time === 'number') { | ||
| 966 | - this._opt.timeout = Number(time); | ||
| 967 | - } | ||
| 968 | - }; | ||
| 969 | - | ||
| 970 | - /** | ||
| 971 | - * @desc 视频缩放模式, 当视频分辨率比例与canvas显示区域比例不同时,缩放效果不同: | ||
| 972 | - 0 视频画面完全填充canvas区域,画面会被拉伸 | ||
| 973 | - 1 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边(默认) | ||
| 974 | - 2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 | ||
| 975 | - * @param type | ||
| 976 | - * | ||
| 977 | - */ | ||
| 978 | - Jessibuca.prototype.setScaleMode = function (type) { | ||
| 979 | - if (type === 0) { | ||
| 980 | - this._opt.isFullResize = false; | ||
| 981 | - this._opt.isResize = false; | ||
| 982 | - } else if (type === 1) { | ||
| 983 | - this._opt.isFullResize = false; | ||
| 984 | - this._opt.isResize = true; | ||
| 985 | - } else if (type === 2) { | ||
| 986 | - this._opt.isFullResize = true; | ||
| 987 | - } | ||
| 988 | - this.resize(); | ||
| 989 | - }; | ||
| 990 | - | ||
| 991 | - /** | ||
| 992 | - * Create the GL context from the canvas element | ||
| 993 | - */ | ||
| 994 | - Jessibuca.prototype._initContextGL = function () { | ||
| 995 | - var canvas = this._canvasElement; | ||
| 996 | - var gl = null; | ||
| 997 | - | ||
| 998 | - var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"]; | ||
| 999 | - var nameIndex = 0; | ||
| 1000 | - | ||
| 1001 | - while (!gl && nameIndex < validContextNames.length) { | ||
| 1002 | - var contextName = validContextNames[nameIndex]; | ||
| 1003 | - | ||
| 1004 | - try { | ||
| 1005 | - var contextOptions = {preserveDrawingBuffer: true}; | ||
| 1006 | - if (this._opt.contextOptions) { | ||
| 1007 | - contextOptions = Object.assign(contextOptions, this._opt.contextOptions); | ||
| 1008 | - } | ||
| 1009 | - | ||
| 1010 | - gl = canvas.getContext(contextName, contextOptions); | ||
| 1011 | - } catch (e) { | ||
| 1012 | - gl = null; | ||
| 1013 | - } | ||
| 1014 | - | ||
| 1015 | - if (!gl || typeof gl.getParameter !== "function") { | ||
| 1016 | - gl = null; | ||
| 1017 | - } | ||
| 1018 | - | ||
| 1019 | - ++nameIndex; | ||
| 1020 | - } | ||
| 1021 | - ; | ||
| 1022 | - | ||
| 1023 | - this._contextGL = gl; | ||
| 1024 | - }; | ||
| 1025 | - | ||
| 1026 | - /** | ||
| 1027 | - * Initialize GL shader program | ||
| 1028 | - */ | ||
| 1029 | - Jessibuca.prototype._initProgram = function () { | ||
| 1030 | - var gl = this._contextGL; | ||
| 1031 | - | ||
| 1032 | - var vertexShaderScript = [ | ||
| 1033 | - 'attribute vec4 vertexPos;', | ||
| 1034 | - 'attribute vec4 texturePos;', | ||
| 1035 | - 'varying vec2 textureCoord;', | ||
| 1036 | - | ||
| 1037 | - 'void main()', | ||
| 1038 | - '{', | ||
| 1039 | - 'gl_Position = vertexPos;', | ||
| 1040 | - 'textureCoord = texturePos.xy;', | ||
| 1041 | - '}' | ||
| 1042 | - ].join('\n'); | ||
| 1043 | - | ||
| 1044 | - var fragmentShaderScript = [ | ||
| 1045 | - 'precision highp float;', | ||
| 1046 | - 'varying highp vec2 textureCoord;', | ||
| 1047 | - 'uniform sampler2D ySampler;', | ||
| 1048 | - 'uniform sampler2D uSampler;', | ||
| 1049 | - 'uniform sampler2D vSampler;', | ||
| 1050 | - 'const mat4 YUV2RGB = mat4', | ||
| 1051 | - '(', | ||
| 1052 | - '1.1643828125, 0, 1.59602734375, -.87078515625,', | ||
| 1053 | - '1.1643828125, -.39176171875, -.81296875, .52959375,', | ||
| 1054 | - '1.1643828125, 2.017234375, 0, -1.081390625,', | ||
| 1055 | - '0, 0, 0, 1', | ||
| 1056 | - ');', | ||
| 1057 | - | ||
| 1058 | - 'void main(void) {', | ||
| 1059 | - 'highp float y = texture2D(ySampler, textureCoord).r;', | ||
| 1060 | - 'highp float u = texture2D(uSampler, textureCoord).r;', | ||
| 1061 | - 'highp float v = texture2D(vSampler, textureCoord).r;', | ||
| 1062 | - 'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;', | ||
| 1063 | - '}' | ||
| 1064 | - ].join('\n'); | ||
| 1065 | - | ||
| 1066 | - var vertexShader = gl.createShader(gl.VERTEX_SHADER); | ||
| 1067 | - gl.shaderSource(vertexShader, vertexShaderScript); | ||
| 1068 | - gl.compileShader(vertexShader); | ||
| 1069 | - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { | ||
| 1070 | - this._opt.isDebug && console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader)); | ||
| 1071 | - } | ||
| 1072 | - | ||
| 1073 | - var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); | ||
| 1074 | - gl.shaderSource(fragmentShader, fragmentShaderScript); | ||
| 1075 | - gl.compileShader(fragmentShader); | ||
| 1076 | - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { | ||
| 1077 | - this._opt.isDebug && console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader)); | ||
| 1078 | - } | ||
| 1079 | - | ||
| 1080 | - var program = gl.createProgram(); | ||
| 1081 | - gl.attachShader(program, vertexShader); | ||
| 1082 | - gl.attachShader(program, fragmentShader); | ||
| 1083 | - gl.linkProgram(program); | ||
| 1084 | - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | ||
| 1085 | - this._opt.isDebug && console.log('Program failed to compile: ' + gl.getProgramInfoLog(program)); | ||
| 1086 | - } | ||
| 1087 | - | ||
| 1088 | - gl.useProgram(program); | ||
| 1089 | - | ||
| 1090 | - this._shaderProgram = program; | ||
| 1091 | - }; | ||
| 1092 | - | ||
| 1093 | - /** | ||
| 1094 | - * Initialize vertex buffers and attach to shader program | ||
| 1095 | - */ | ||
| 1096 | - Jessibuca.prototype._initBuffers = function () { | ||
| 1097 | - var gl = this._contextGL; | ||
| 1098 | - var program = this._shaderProgram; | ||
| 1099 | - | ||
| 1100 | - var vertexPosBuffer = gl.createBuffer(); | ||
| 1101 | - gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); | ||
| 1102 | - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW); | ||
| 1103 | - | ||
| 1104 | - var vertexPosRef = gl.getAttribLocation(program, 'vertexPos'); | ||
| 1105 | - gl.enableVertexAttribArray(vertexPosRef); | ||
| 1106 | - gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0); | ||
| 1107 | - | ||
| 1108 | - var texturePosBuffer = gl.createBuffer(); | ||
| 1109 | - gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer); | ||
| 1110 | - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW); | ||
| 1111 | - | ||
| 1112 | - var texturePosRef = gl.getAttribLocation(program, 'texturePos'); | ||
| 1113 | - gl.enableVertexAttribArray(texturePosRef); | ||
| 1114 | - gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0); | ||
| 1115 | - | ||
| 1116 | - this._texturePosBuffer = texturePosBuffer; | ||
| 1117 | - }; | ||
| 1118 | - | ||
| 1119 | - /** | ||
| 1120 | - * Initialize GL textures and attach to shader program | ||
| 1121 | - */ | ||
| 1122 | - Jessibuca.prototype._initTextures = function () { | ||
| 1123 | - var gl = this._contextGL; | ||
| 1124 | - var program = this._shaderProgram; | ||
| 1125 | - | ||
| 1126 | - var yTextureRef = this._initTexture(); | ||
| 1127 | - var ySamplerRef = gl.getUniformLocation(program, 'ySampler'); | ||
| 1128 | - gl.uniform1i(ySamplerRef, 0); | ||
| 1129 | - this._yTextureRef = yTextureRef; | ||
| 1130 | - | ||
| 1131 | - var uTextureRef = this._initTexture(); | ||
| 1132 | - var uSamplerRef = gl.getUniformLocation(program, 'uSampler'); | ||
| 1133 | - gl.uniform1i(uSamplerRef, 1); | ||
| 1134 | - this._uTextureRef = uTextureRef; | ||
| 1135 | - | ||
| 1136 | - var vTextureRef = this._initTexture(); | ||
| 1137 | - var vSamplerRef = gl.getUniformLocation(program, 'vSampler'); | ||
| 1138 | - gl.uniform1i(vSamplerRef, 2); | ||
| 1139 | - this._vTextureRef = vTextureRef; | ||
| 1140 | - }; | ||
| 1141 | - | ||
| 1142 | - /** | ||
| 1143 | - * Create and configure a single texture | ||
| 1144 | - */ | ||
| 1145 | - Jessibuca.prototype._initTexture = function () { | ||
| 1146 | - var gl = this._contextGL; | ||
| 1147 | - | ||
| 1148 | - var textureRef = gl.createTexture(); | ||
| 1149 | - gl.bindTexture(gl.TEXTURE_2D, textureRef); | ||
| 1150 | - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | ||
| 1151 | - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | ||
| 1152 | - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | ||
| 1153 | - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | ||
| 1154 | - gl.bindTexture(gl.TEXTURE_2D, null); | ||
| 1155 | - | ||
| 1156 | - return textureRef; | ||
| 1157 | - }; | ||
| 1158 | - | ||
| 1159 | - /** | ||
| 1160 | - * Draw picture data to the canvas. | ||
| 1161 | - * If this object is using WebGL, the data must be an I420 formatted ArrayBuffer, | ||
| 1162 | - * Otherwise, data must be an RGBA formatted ArrayBuffer. | ||
| 1163 | - */ | ||
| 1164 | - Jessibuca.prototype._drawNextOutputPicture = function (data) { | ||
| 1165 | - if (this._contextGL) { | ||
| 1166 | - this._drawNextOutputPictureGL(data); | ||
| 1167 | - } else { | ||
| 1168 | - this._drawNextOutputPictureRGBA(data); | ||
| 1169 | - } | ||
| 1170 | - }; | ||
| 1171 | - | ||
| 1172 | - /** | ||
| 1173 | - * Draw the next output picture using WebGL | ||
| 1174 | - */ | ||
| 1175 | - Jessibuca.prototype._drawNextOutputPictureGL = function (data) { | ||
| 1176 | - var gl = this._contextGL; | ||
| 1177 | - var texturePosBuffer = this._texturePosBuffer; | ||
| 1178 | - var yTextureRef = this._yTextureRef; | ||
| 1179 | - var uTextureRef = this._uTextureRef; | ||
| 1180 | - var vTextureRef = this._vTextureRef; | ||
| 1181 | - var croppingParams = this.croppingParams | ||
| 1182 | - var width = this._canvasElement.width | ||
| 1183 | - var height = this._canvasElement.height | ||
| 1184 | - if (croppingParams) { | ||
| 1185 | - gl.viewport(0, 0, croppingParams.width, croppingParams.height); | ||
| 1186 | - var tTop = croppingParams.top / height; | ||
| 1187 | - var tLeft = croppingParams.left / width; | ||
| 1188 | - var tBottom = croppingParams.height / height; | ||
| 1189 | - var tRight = croppingParams.width / width; | ||
| 1190 | - var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]); | ||
| 1191 | - | ||
| 1192 | - gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer); | ||
| 1193 | - gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW); | ||
| 1194 | - } else { | ||
| 1195 | - gl.viewport(0, 0, this._canvasElement.width, this._canvasElement.height); | ||
| 1196 | - } | ||
| 1197 | - gl.activeTexture(gl.TEXTURE0); | ||
| 1198 | - gl.bindTexture(gl.TEXTURE_2D, yTextureRef); | ||
| 1199 | - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[0]); | ||
| 1200 | - | ||
| 1201 | - gl.activeTexture(gl.TEXTURE1); | ||
| 1202 | - gl.bindTexture(gl.TEXTURE_2D, uTextureRef); | ||
| 1203 | - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[1]); | ||
| 1204 | - | ||
| 1205 | - gl.activeTexture(gl.TEXTURE2); | ||
| 1206 | - gl.bindTexture(gl.TEXTURE_2D, vTextureRef); | ||
| 1207 | - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width / 2, height / 2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data[2]); | ||
| 1208 | - | ||
| 1209 | - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | ||
| 1210 | - }; | ||
| 1211 | - | ||
| 1212 | - /** | ||
| 1213 | - * Draw next output picture using ARGB data on a 2d canvas. | ||
| 1214 | - */ | ||
| 1215 | - Jessibuca.prototype._drawNextOutputPictureRGBA = function (data) { | ||
| 1216 | - this.imageData.data.set(data); | ||
| 1217 | - var croppingParams = this.croppingParams | ||
| 1218 | - if (!croppingParams) { | ||
| 1219 | - this.ctx2d.putImageData(this.imageData, 0, 0); | ||
| 1220 | - } else { | ||
| 1221 | - this.ctx2d.putImageData(this.imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height); | ||
| 1222 | - } | ||
| 1223 | - }; | ||
| 1224 | - Jessibuca.prototype.ctx2d = null; | ||
| 1225 | - Jessibuca.prototype.imageData = null; | ||
| 1226 | - Jessibuca.prototype._initRGB = function (width, height) { | ||
| 1227 | - this.ctx2d = this._canvasElement.getContext('2d'); | ||
| 1228 | - this.imageData = this.ctx2d.getImageData(0, 0, width, height); | ||
| 1229 | - this.clear = function () { | ||
| 1230 | - this.ctx2d.clearRect(0, 0, width, height) | ||
| 1231 | - }; | ||
| 1232 | - }; | ||
| 1233 | - | ||
| 1234 | - Jessibuca.prototype.pause = function () { | ||
| 1235 | - this._close(); | ||
| 1236 | - if (this.loading) { | ||
| 1237 | - _domToggle(this._doms.loadingDom, false); | ||
| 1238 | - } | ||
| 1239 | - this.recording = false; | ||
| 1240 | - this.playing = false; | ||
| 1241 | - }; | ||
| 1242 | - | ||
| 1243 | - Jessibuca.prototype._close = function () { | ||
| 1244 | - if (this.audioInterval) { | ||
| 1245 | - clearInterval(this.audioInterval) | ||
| 1246 | - } | ||
| 1247 | - delete this._playAudio | ||
| 1248 | - this._decoderWorker.postMessage({cmd: "close"}) | ||
| 1249 | - | ||
| 1250 | - if (this._wakeLock) { | ||
| 1251 | - this._wakeLock.release(); | ||
| 1252 | - this._wakeLock = null; | ||
| 1253 | - } | ||
| 1254 | - | ||
| 1255 | - // this._contextGL.clear(this._contextGL.COLOR_BUFFER_BIT); | ||
| 1256 | - this._initCheckVariable(); | ||
| 1257 | - } | ||
| 1258 | - /** | ||
| 1259 | - * destroy | ||
| 1260 | - * @desc delete worker, | ||
| 1261 | - */ | ||
| 1262 | - Jessibuca.prototype.destroy = function () { | ||
| 1263 | - // destroy | ||
| 1264 | - this._decoderWorker.terminate() | ||
| 1265 | - window.removeEventListener("resize", this._onresize); | ||
| 1266 | - window.removeEventListener('fullscreenchange', this._onfullscreenchange); | ||
| 1267 | - this._initCheckVariable(); | ||
| 1268 | - this._clearCheckLoading(); | ||
| 1269 | - this._off(); | ||
| 1270 | - this._hasLoaded = false; | ||
| 1271 | - // remove dom | ||
| 1272 | - while (this._container.firstChild) { | ||
| 1273 | - this._container.removeChild(this._container.firstChild); | ||
| 1274 | - } | ||
| 1275 | - if (this._wakeLock) { | ||
| 1276 | - this._wakeLock.release(); | ||
| 1277 | - } | ||
| 1278 | - } | ||
| 1279 | - | ||
| 1280 | - /** | ||
| 1281 | - * 清理画布为黑色背景 | ||
| 1282 | - * 用于canvas重用进行多个流切换播放时,将上一个画面清理 | ||
| 1283 | - * 避免后一个视频播放之前出现前一个视频最后一个画面 | ||
| 1284 | - */ | ||
| 1285 | - Jessibuca.prototype.clearView = function () { | ||
| 1286 | - this._contextGL.clear(this._contextGL.COLOR_BUFFER_BIT); | ||
| 1287 | - }; | ||
| 1288 | - /** | ||
| 1289 | - * play | ||
| 1290 | - * @param url | ||
| 1291 | - */ | ||
| 1292 | - Jessibuca.prototype.play = function (url) { | ||
| 1293 | - if (!this.playUrl && !url) { | ||
| 1294 | - return; | ||
| 1295 | - } | ||
| 1296 | - var needDelay = false; | ||
| 1297 | - if (url) { | ||
| 1298 | - if (this.playUrl) { | ||
| 1299 | - this._close(); | ||
| 1300 | - needDelay = true; | ||
| 1301 | - this._contextGL.clear(this._contextGL.COLOR_BUFFER_BIT); | ||
| 1302 | - } | ||
| 1303 | - this.loading = true; | ||
| 1304 | - _domToggle(this._doms.bgDom, false); | ||
| 1305 | - this._checkLoading(); | ||
| 1306 | - this.playUrl = url; | ||
| 1307 | - } else if (this.playUrl) { | ||
| 1308 | - // retry | ||
| 1309 | - if (this.loading) { | ||
| 1310 | - this._hideBtns(); | ||
| 1311 | - _domToggle(this._doms.fullscreenDom, true); | ||
| 1312 | - _domToggle(this._doms.pauseDom, true); | ||
| 1313 | - _domToggle(this._doms.loadingDom, true); | ||
| 1314 | - this._checkLoading(); | ||
| 1315 | - } else { | ||
| 1316 | - this.playing = true; | ||
| 1317 | - } | ||
| 1318 | - } | ||
| 1319 | - this._initCheckVariable(); | ||
| 1320 | - | ||
| 1321 | - if (needDelay) { | ||
| 1322 | - var _this = this; | ||
| 1323 | - setTimeout(function () { | ||
| 1324 | - _this._decoderWorker.postMessage({cmd: "play", url: _this.playUrl, isWebGL: _this.isWebGL()}) | ||
| 1325 | - }, 300); | ||
| 1326 | - } else { | ||
| 1327 | - this._decoderWorker.postMessage({cmd: "play", url: this.playUrl, isWebGL: this.isWebGL()}) | ||
| 1328 | - } | ||
| 1329 | - }; | ||
| 1330 | - /** | ||
| 1331 | - * has loaded | ||
| 1332 | - * @returns {boolean} | ||
| 1333 | - */ | ||
| 1334 | - Jessibuca.prototype.hasLoaded = function () { | ||
| 1335 | - return this._hasLoaded; | ||
| 1336 | - }; | ||
| 1337 | - | ||
| 1338 | - Object.defineProperty(Jessibuca.prototype, "fullscreen", { | ||
| 1339 | - set(value) { | ||
| 1340 | - if (value) { | ||
| 1341 | - if (!_checkFull()) { | ||
| 1342 | - this._container.requestFullscreen(); | ||
| 1343 | - } | ||
| 1344 | - _domToggle(this._doms.minScreenDom, true); | ||
| 1345 | - _domToggle(this._doms.fullscreenDom, false); | ||
| 1346 | - } else { | ||
| 1347 | - if (_checkFull()) { | ||
| 1348 | - document.exitFullscreen(); | ||
| 1349 | - } | ||
| 1350 | - _domToggle(this._doms.minScreenDom, false); | ||
| 1351 | - _domToggle(this._doms.fullscreenDom, true); | ||
| 1352 | - } | ||
| 1353 | - | ||
| 1354 | - if (this._fullscreen !== value) { | ||
| 1355 | - this.onFullscreen(value); | ||
| 1356 | - this._trigger('fullscreen', value); | ||
| 1357 | - } | ||
| 1358 | - this._fullscreen = value; | ||
| 1359 | - }, | ||
| 1360 | - get() { | ||
| 1361 | - return this._fullscreen; | ||
| 1362 | - } | ||
| 1363 | - }); | ||
| 1364 | - | ||
| 1365 | - Object.defineProperty(Jessibuca.prototype, 'playing', { | ||
| 1366 | - set(value) { | ||
| 1367 | - if (value) { | ||
| 1368 | - _domToggle(this._doms.playBigDom, false); | ||
| 1369 | - _domToggle(this._doms.playDom, false); | ||
| 1370 | - _domToggle(this._doms.pauseDom, true); | ||
| 1371 | - | ||
| 1372 | - _domToggle(this._doms.screenshotsDom, true); | ||
| 1373 | - _domToggle(this._doms.recordDom, true); | ||
| 1374 | - if (this._quieting) { | ||
| 1375 | - _domToggle(this._doms.quietAudioDom, true); | ||
| 1376 | - _domToggle(this._doms.playAudioDom, false); | ||
| 1377 | - } else { | ||
| 1378 | - _domToggle(this._doms.quietAudioDom, false); | ||
| 1379 | - _domToggle(this._doms.playAudioDom, true); | ||
| 1380 | - } | ||
| 1381 | - } else { | ||
| 1382 | - this._doms.speedDom && (this._doms.speedDom.innerText = ''); | ||
| 1383 | - if (this.playUrl) { | ||
| 1384 | - _domToggle(this._doms.playDom, true); | ||
| 1385 | - _domToggle(this._doms.playBigDom, true); | ||
| 1386 | - _domToggle(this._doms.pauseDom, false); | ||
| 1387 | - } | ||
| 1388 | - | ||
| 1389 | - // 在停止状态下录像,截屏,音量是非激活,只有播放,最大化时可点击 | ||
| 1390 | - _domToggle(this._doms.recordDom, false); | ||
| 1391 | - _domToggle(this._doms.recordingDom, false); | ||
| 1392 | - _domToggle(this._doms.screenshotsDom, false); | ||
| 1393 | - _domToggle(this._doms.quietAudioDom, false); | ||
| 1394 | - _domToggle(this._doms.playAudioDom, false); | ||
| 1395 | - } | ||
| 1396 | - | ||
| 1397 | - if (this._playing !== value) { | ||
| 1398 | - if (value) { | ||
| 1399 | - this.onPlay(); | ||
| 1400 | - this._trigger('play'); | ||
| 1401 | - } else { | ||
| 1402 | - this.onPause(); | ||
| 1403 | - this._trigger('pause'); | ||
| 1404 | - } | ||
| 1405 | - } | ||
| 1406 | - this._playing = value; | ||
| 1407 | - }, | ||
| 1408 | - get() { | ||
| 1409 | - return this._playing; | ||
| 1410 | - } | ||
| 1411 | - }); | ||
| 1412 | - | ||
| 1413 | - Object.defineProperty(Jessibuca.prototype, 'recording', { | ||
| 1414 | - set(value) { | ||
| 1415 | - if (value) { | ||
| 1416 | - _domToggle(this._doms.recordDom, false); | ||
| 1417 | - _domToggle(this._doms.recordingDom, true); | ||
| 1418 | - } else { | ||
| 1419 | - _domToggle(this._doms.recordDom, true); | ||
| 1420 | - _domToggle(this._doms.recordingDom, false); | ||
| 1421 | - | ||
| 1422 | - } | ||
| 1423 | - if (this._recording !== value) { | ||
| 1424 | - this.onRecord(value); | ||
| 1425 | - this._trigger('record', value); | ||
| 1426 | - this._recording = value; | ||
| 1427 | - } | ||
| 1428 | - }, | ||
| 1429 | - get() { | ||
| 1430 | - return this._recording; | ||
| 1431 | - } | ||
| 1432 | - }); | ||
| 1433 | - | ||
| 1434 | - Object.defineProperty(Jessibuca.prototype, 'quieting', { | ||
| 1435 | - set(value) { | ||
| 1436 | - if (value) { | ||
| 1437 | - _domToggle(this._doms.quietAudioDom, true); | ||
| 1438 | - _domToggle(this._doms.playAudioDom, false); | ||
| 1439 | - } else { | ||
| 1440 | - _domToggle(this._doms.quietAudioDom, false); | ||
| 1441 | - _domToggle(this._doms.playAudioDom, true); | ||
| 1442 | - } | ||
| 1443 | - if (this._quieting !== value) { | ||
| 1444 | - this.onMute(value); | ||
| 1445 | - this._trigger('mute', value); | ||
| 1446 | - } | ||
| 1447 | - this._quieting = value; | ||
| 1448 | - }, | ||
| 1449 | - get() { | ||
| 1450 | - return this._quieting; | ||
| 1451 | - } | ||
| 1452 | - }); | ||
| 1453 | - | ||
| 1454 | - Object.defineProperty(Jessibuca.prototype, 'loading', { | ||
| 1455 | - set(value) { | ||
| 1456 | - if (value) { | ||
| 1457 | - this._hideBtns(); | ||
| 1458 | - _domToggle(this._doms.fullscreenDom, true); | ||
| 1459 | - _domToggle(this._doms.pauseDom, true); | ||
| 1460 | - _domToggle(this._doms.loadingDom, true); | ||
| 1461 | - } else { | ||
| 1462 | - this._initBtns(); | ||
| 1463 | - } | ||
| 1464 | - this._loading = value; | ||
| 1465 | - }, | ||
| 1466 | - get() { | ||
| 1467 | - return this._loading; | ||
| 1468 | - } | ||
| 1469 | - }); | ||
| 1470 | - | ||
| 1471 | - /** | ||
| 1472 | - * resize | ||
| 1473 | - */ | ||
| 1474 | - Jessibuca.prototype.resize = function () { | ||
| 1475 | - var width = this._container.clientWidth; | ||
| 1476 | - var height = this._container.clientHeight; | ||
| 1477 | - if (this._showControl()) { | ||
| 1478 | - height -= 38; | ||
| 1479 | - } | ||
| 1480 | - var resizeWidth = this._canvasElement.width; | ||
| 1481 | - var resizeHeight = this._canvasElement.height; | ||
| 1482 | - var wScale = width / resizeWidth; | ||
| 1483 | - var hScale = height / resizeHeight; | ||
| 1484 | - var scale = wScale > hScale ? hScale : wScale; | ||
| 1485 | - if (!this._opt.isResize) { | ||
| 1486 | - if (wScale !== hScale) { | ||
| 1487 | - scale = wScale + ',' + hScale; | ||
| 1488 | - } | ||
| 1489 | - } | ||
| 1490 | - // | ||
| 1491 | - if (this._opt.isFullResize) { | ||
| 1492 | - scale = wScale > hScale ? wScale : hScale; | ||
| 1493 | - } | ||
| 1494 | - | ||
| 1495 | - this._opt.isDebug && console.log('wScale', wScale, 'hScale', hScale, 'scale', scale); | ||
| 1496 | - this._canvasElement.style.transform = "scale(" + scale + ")" | ||
| 1497 | - this._canvasElement.style.left = ((width - resizeWidth) / 2) + "px" | ||
| 1498 | - this._canvasElement.style.top = ((height - resizeHeight) / 2) + "px" | ||
| 1499 | - } | ||
| 1500 | - | ||
| 1501 | - Jessibuca.prototype._fullscreenchange = function () { | ||
| 1502 | - this.fullscreen = _checkFull(); | ||
| 1503 | - } | ||
| 1504 | - | ||
| 1505 | - /** | ||
| 1506 | - * change buffer | ||
| 1507 | - * @param buffer | ||
| 1508 | - */ | ||
| 1509 | - Jessibuca.prototype.changeBuffer = function (buffer) { | ||
| 1510 | - this._stats.buf = Number(buffer) * 1000; | ||
| 1511 | - this._decoderWorker.postMessage({cmd: "setVideoBuffer", time: Number(buffer)}); | ||
| 1512 | - }; | ||
| 1513 | - /** | ||
| 1514 | - * 设置最大缓冲时长,单位秒,播放器会自动消除延迟。 | ||
| 1515 | - * @param buffer | ||
| 1516 | - */ | ||
| 1517 | - Jessibuca.prototype.setBufferTime = function (buffer) { | ||
| 1518 | - this.changeBuffer(buffer); | ||
| 1519 | - }; | ||
| 1520 | - | ||
| 1521 | - /** | ||
| 1522 | - * 设置音量大小,取值0.0 — 1.0 | ||
| 1523 | - * 当为0.0时,完全无声 | ||
| 1524 | - * 当为1.0时,最大音量,默认值 | ||
| 1525 | - * @param volume | ||
| 1526 | - */ | ||
| 1527 | - Jessibuca.prototype.setVolume = function (volume) { | ||
| 1528 | - if (this._gainNode) { | ||
| 1529 | - this._gainNode.gain.setValueAtTime(volume, this._audioContext.currentTime); | ||
| 1530 | - } | ||
| 1531 | - }; | ||
| 1532 | - | ||
| 1533 | - /** | ||
| 1534 | - * 开启屏幕常亮, 在play前调用 | ||
| 1535 | - * 在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮 | ||
| 1536 | - * H5目前在chrome\edge 84, android chrome 84及以上有原生亮屏API, 需要是https页面 | ||
| 1537 | - * 其余平台为模拟实现,此时为兼容实现,并不保证所有浏览器都支持 | ||
| 1538 | - */ | ||
| 1539 | - Jessibuca.prototype.setKeepScreenOn = function () { | ||
| 1540 | - this._opt.keepScreenOn = true; | ||
| 1541 | - }; | ||
| 1542 | - | ||
| 1543 | - | ||
| 1544 | - /** | ||
| 1545 | - * set fullscreen | ||
| 1546 | - * @param flag | ||
| 1547 | - */ | ||
| 1548 | - Jessibuca.prototype.setFullscreen = function (flag) { | ||
| 1549 | - var fullscreen = !!flag; | ||
| 1550 | - if (this.fullscreen !== fullscreen) { | ||
| 1551 | - this.fullscreen = fullscreen; | ||
| 1552 | - } | ||
| 1553 | - }; | ||
| 1554 | - | ||
| 1555 | - function _now() { | ||
| 1556 | - return new Date().getTime(); | ||
| 1557 | - } | ||
| 1558 | - | ||
| 1559 | - Jessibuca.prototype._screenshot = function (filename, format, quality) { | ||
| 1560 | - filename = filename || _now(); | ||
| 1561 | - var formatType = { | ||
| 1562 | - png: 'image/png', | ||
| 1563 | - jpeg: 'image/jpeg', | ||
| 1564 | - webp: 'image/webp' | ||
| 1565 | - }; | ||
| 1566 | - var encoderOptions = 0.92; | ||
| 1567 | - | ||
| 1568 | - if (typeof quality !== 'undefined') { | ||
| 1569 | - encoderOptions = Number(quality); | ||
| 1570 | - } | ||
| 1571 | - | ||
| 1572 | - var dataURL = this._canvasElement.toDataURL(formatType[format] || formatType.png, encoderOptions); | ||
| 1573 | - _downloadImg(_dataURLToFile(dataURL), filename); | ||
| 1574 | - } | ||
| 1575 | - | ||
| 1576 | - /** | ||
| 1577 | - * 截图,调用后弹出下载框保存截图 | ||
| 1578 | - * @param filename 保存的文件名 默认时间戳 | ||
| 1579 | - * @param format 截图的格式,可选png或jpeg或者webp | ||
| 1580 | - * @param quality 可选参数,当格式是jpeg或者webp时,压缩质量,取值0.0 ~ 1.0 | ||
| 1581 | - */ | ||
| 1582 | - Jessibuca.prototype.screenshot = function (filename, format, quality) { | ||
| 1583 | - this._screenshot(filename, format, quality); | ||
| 1584 | - }; | ||
| 1585 | - | ||
| 1586 | - | ||
| 1587 | - var eventSplitter = /\s+/; | ||
| 1588 | - | ||
| 1589 | - // Execute callbacks | ||
| 1590 | - function _callEach(list, args, context) { | ||
| 1591 | - if (list) { | ||
| 1592 | - for (var i = 0, len = list.length; i < len; i += 1) { | ||
| 1593 | - list[i].apply(context, args); | ||
| 1594 | - } | ||
| 1595 | - } | ||
| 1596 | - } | ||
| 1597 | - | ||
| 1598 | - /** | ||
| 1599 | - * | ||
| 1600 | - * @param events | ||
| 1601 | - * @param callback | ||
| 1602 | - * @returns {Jessibuca} | ||
| 1603 | - */ | ||
| 1604 | - Jessibuca.prototype.on = function (events, callback) { | ||
| 1605 | - var cache, event, list; | ||
| 1606 | - if (!callback) return this; | ||
| 1607 | - cache = this.__events || (this.__events = {}); | ||
| 1608 | - events = events.split(eventSplitter); | ||
| 1609 | - while (event = events.shift()) { | ||
| 1610 | - list = cache[event] || (cache[event] = []); | ||
| 1611 | - list.push(callback); | ||
| 1612 | - } | ||
| 1613 | - return this; | ||
| 1614 | - }; | ||
| 1615 | - /** | ||
| 1616 | - * | ||
| 1617 | - * @param events | ||
| 1618 | - * @param callback | ||
| 1619 | - * @returns {Jessibuca} | ||
| 1620 | - * @private | ||
| 1621 | - */ | ||
| 1622 | - Jessibuca.prototype._off = function () { | ||
| 1623 | - var cache; | ||
| 1624 | - if (!(cache = this.__events)) return this; | ||
| 1625 | - delete this.__events; | ||
| 1626 | - return this; | ||
| 1627 | - }; | ||
| 1628 | - | ||
| 1629 | - /** | ||
| 1630 | - * | ||
| 1631 | - * @param events | ||
| 1632 | - * @returns {Jessibuca} | ||
| 1633 | - * @private | ||
| 1634 | - */ | ||
| 1635 | - Jessibuca.prototype._trigger = function (events) { | ||
| 1636 | - var cache, event, all, list, i, len, rest = [], args; | ||
| 1637 | - if (!(cache = this.__events)) return this; | ||
| 1638 | - events = events.split(eventSplitter); | ||
| 1639 | - // Fill up `rest` with the callback arguments. Since we're only copying | ||
| 1640 | - // the tail of `arguments`, a loop is much faster than Array#slice. | ||
| 1641 | - for (i = 1, len = arguments.length; i < len; i++) { | ||
| 1642 | - rest[i - 1] = arguments[i]; | ||
| 1643 | - } | ||
| 1644 | - // For each event, walk through the list of callbacks twice, first to | ||
| 1645 | - // trigger the event, then to trigger any `"all"` callbacks. | ||
| 1646 | - while (event = events.shift()) { | ||
| 1647 | - if (list = cache[event]) list = list.slice(); | ||
| 1648 | - // Execute event callbacks. | ||
| 1649 | - _callEach(list, rest, this); | ||
| 1650 | - } | ||
| 1651 | - return this; | ||
| 1652 | - } | ||
| 1653 | - | ||
| 1654 | - if (typeof define === 'function') { | ||
| 1655 | - define(function () { | ||
| 1656 | - return Jessibuca; | ||
| 1657 | - }); | ||
| 1658 | - } else if (typeof exports !== 'undefined') { | ||
| 1659 | - module.exports = Jessibuca; | ||
| 1660 | - } else { | ||
| 1661 | - window.Jessibuca = Jessibuca; | ||
| 1662 | - } | ||
| 1663 | -})(); |
src/api/fee/contractCreateFeeApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 3 | + | ||
| 4 | +// 查询合同列表 | ||
| 5 | +export function queryContract(params) { | ||
| 6 | + return new Promise((resolve, reject) => { | ||
| 7 | + request({ | ||
| 8 | + url: '/contract/queryContract', | ||
| 9 | + method: 'get', | ||
| 10 | + params: { | ||
| 11 | + ...params, | ||
| 12 | + communityId: getCommunityId() | ||
| 13 | + } | ||
| 14 | + }).then(response => { | ||
| 15 | + const res = response.data | ||
| 16 | + resolve(res) | ||
| 17 | + }).catch(error => { | ||
| 18 | + reject(error) | ||
| 19 | + }) | ||
| 20 | + }) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +// 保存合同创建费用 | ||
| 24 | +export function saveContractCreateFee(data) { | ||
| 25 | + return new Promise((resolve, reject) => { | ||
| 26 | + request({ | ||
| 27 | + url: '/fee.saveContractCreateFee', | ||
| 28 | + method: 'post', | ||
| 29 | + data: { | ||
| 30 | + ...data, | ||
| 31 | + communityId: getCommunityId() | ||
| 32 | + } | ||
| 33 | + }).then(response => { | ||
| 34 | + const res = response.data | ||
| 35 | + resolve(res) | ||
| 36 | + }).catch(error => { | ||
| 37 | + reject(error) | ||
| 38 | + }) | ||
| 39 | + }) | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +// 获取费用配置列表 | ||
| 43 | +export function listFeeConfigs(params) { | ||
| 44 | + return new Promise((resolve, reject) => { | ||
| 45 | + request({ | ||
| 46 | + url: '/feeConfig.listFeeConfigs', | ||
| 47 | + method: 'get', | ||
| 48 | + params: { | ||
| 49 | + ...params, | ||
| 50 | + communityId: getCommunityId() | ||
| 51 | + } | ||
| 52 | + }).then(response => { | ||
| 53 | + const res = response.data | ||
| 54 | + resolve(res) | ||
| 55 | + }).catch(error => { | ||
| 56 | + reject(error) | ||
| 57 | + }) | ||
| 58 | + }) | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +// 获取字典数据 | ||
| 62 | +export function getDict(dictType, dictName) { | ||
| 63 | + return new Promise((resolve, reject) => { | ||
| 64 | + request({ | ||
| 65 | + url: '/dict/getDict', | ||
| 66 | + method: 'get', | ||
| 67 | + params: { | ||
| 68 | + dictType, | ||
| 69 | + dictName, | ||
| 70 | + communityId: getCommunityId() | ||
| 71 | + } | ||
| 72 | + }).then(response => { | ||
| 73 | + const res = response.data | ||
| 74 | + resolve(res) | ||
| 75 | + }).catch(error => { | ||
| 76 | + reject(error) | ||
| 77 | + }) | ||
| 78 | + }) | ||
| 79 | +} | ||
| 0 | \ No newline at end of file | 80 | \ No newline at end of file |
src/api/fee/meterWaterManageApi.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 3 | + | ||
| 4 | +// 查询抄表记录列表 | ||
| 5 | +export function listMeterWaters(params) { | ||
| 6 | + return new Promise((resolve, reject) => { | ||
| 7 | + params.communityId = getCommunityId() | ||
| 8 | + request({ | ||
| 9 | + url: '/meterWater.listMeterWaters', | ||
| 10 | + method: 'get', | ||
| 11 | + params | ||
| 12 | + }).then(response => { | ||
| 13 | + const res = response.data | ||
| 14 | + resolve({ | ||
| 15 | + data: res.data, | ||
| 16 | + total: res.total, | ||
| 17 | + records: res.records | ||
| 18 | + }) | ||
| 19 | + }).catch(error => { | ||
| 20 | + reject(error) | ||
| 21 | + }) | ||
| 22 | + }) | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +// 查询抄表类型列表 | ||
| 26 | +export function listMeterTypes(params) { | ||
| 27 | + return new Promise((resolve, reject) => { | ||
| 28 | + params.communityId = getCommunityId() | ||
| 29 | + request({ | ||
| 30 | + url: '/meterType.listMeterType', | ||
| 31 | + method: 'get', | ||
| 32 | + params | ||
| 33 | + }).then(response => { | ||
| 34 | + const res = response.data | ||
| 35 | + resolve({ | ||
| 36 | + data: res.data, | ||
| 37 | + total: res.total | ||
| 38 | + }) | ||
| 39 | + }).catch(error => { | ||
| 40 | + reject(error) | ||
| 41 | + }) | ||
| 42 | + }) | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +// 查询楼栋列表 | ||
| 46 | +export function queryFloors(params) { | ||
| 47 | + return new Promise((resolve, reject) => { | ||
| 48 | + params.communityId = getCommunityId() | ||
| 49 | + request({ | ||
| 50 | + url: '/floor.queryFloors', | ||
| 51 | + method: 'get', | ||
| 52 | + params | ||
| 53 | + }).then(response => { | ||
| 54 | + const res = response.data | ||
| 55 | + resolve({ | ||
| 56 | + data: res.apiFloorDataVoList, | ||
| 57 | + total: res.total | ||
| 58 | + }) | ||
| 59 | + }).catch(error => { | ||
| 60 | + reject(error) | ||
| 61 | + }) | ||
| 62 | + }) | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +// 查询单元列表 | ||
| 66 | +export function queryUnits(params) { | ||
| 67 | + return new Promise((resolve, reject) => { | ||
| 68 | + params.communityId = getCommunityId() | ||
| 69 | + request({ | ||
| 70 | + url: '/unit.queryUnits', | ||
| 71 | + method: 'get', | ||
| 72 | + params | ||
| 73 | + }).then(response => { | ||
| 74 | + const res = response.data | ||
| 75 | + resolve(res) | ||
| 76 | + }).catch(error => { | ||
| 77 | + reject(error) | ||
| 78 | + }) | ||
| 79 | + }) | ||
| 80 | +} | ||
| 81 | + | ||
| 82 | +// 查询房屋列表 | ||
| 83 | +export function queryRooms(params) { | ||
| 84 | + return new Promise((resolve, reject) => { | ||
| 85 | + params.communityId = getCommunityId() | ||
| 86 | + request({ | ||
| 87 | + url: '/room.queryRooms', | ||
| 88 | + method: 'get', | ||
| 89 | + params | ||
| 90 | + }).then(response => { | ||
| 91 | + const res = response.data | ||
| 92 | + resolve(res) | ||
| 93 | + }).catch(error => { | ||
| 94 | + reject(error) | ||
| 95 | + }) | ||
| 96 | + }) | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +// 查询房屋树形列表 | ||
| 100 | +export function queryRoomsTree(params) { | ||
| 101 | + return new Promise((resolve, reject) => { | ||
| 102 | + params.communityId = getCommunityId() | ||
| 103 | + request({ | ||
| 104 | + url: '/room.queryRoomsTree', | ||
| 105 | + method: 'get', | ||
| 106 | + params | ||
| 107 | + }).then(response => { | ||
| 108 | + const res = response.data | ||
| 109 | + resolve(res) | ||
| 110 | + }).catch(error => { | ||
| 111 | + reject(error) | ||
| 112 | + }) | ||
| 113 | + }) | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +// 查询上期抄表记录 | ||
| 117 | +export function queryPreMeterWater(params) { | ||
| 118 | + return new Promise((resolve, reject) => { | ||
| 119 | + params.communityId = getCommunityId() | ||
| 120 | + request({ | ||
| 121 | + url: '/meterWater/queryPreMeterWater', | ||
| 122 | + method: 'get', | ||
| 123 | + params | ||
| 124 | + }).then(response => { | ||
| 125 | + const res = response.data | ||
| 126 | + resolve(res) | ||
| 127 | + }).catch(error => { | ||
| 128 | + reject(error) | ||
| 129 | + }) | ||
| 130 | + }) | ||
| 131 | +} | ||
| 132 | + | ||
| 133 | +// 添加抄表记录 | ||
| 134 | +export function saveMeterWater(data) { | ||
| 135 | + return new Promise((resolve, reject) => { | ||
| 136 | + data.communityId = getCommunityId() | ||
| 137 | + request({ | ||
| 138 | + url: '/meterWater.saveMeterWater', | ||
| 139 | + method: 'post', | ||
| 140 | + data | ||
| 141 | + }).then(response => { | ||
| 142 | + const res = response.data | ||
| 143 | + resolve(res) | ||
| 144 | + }).catch(error => { | ||
| 145 | + reject(error) | ||
| 146 | + }) | ||
| 147 | + }) | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +// 修改抄表记录 | ||
| 151 | +export function updateMeterWater(data) { | ||
| 152 | + return new Promise((resolve, reject) => { | ||
| 153 | + data.communityId = getCommunityId() | ||
| 154 | + request({ | ||
| 155 | + url: '/meterWater.updateMeterWater', | ||
| 156 | + method: 'post', | ||
| 157 | + data | ||
| 158 | + }).then(response => { | ||
| 159 | + const res = response.data | ||
| 160 | + resolve(res) | ||
| 161 | + }).catch(error => { | ||
| 162 | + reject(error) | ||
| 163 | + }) | ||
| 164 | + }) | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +// 删除抄表记录 | ||
| 168 | +export function deleteMeterWater(data) { | ||
| 169 | + return new Promise((resolve, reject) => { | ||
| 170 | + data.communityId = getCommunityId() | ||
| 171 | + request({ | ||
| 172 | + url: '/meterWater.deleteMeterWater', | ||
| 173 | + method: 'post', | ||
| 174 | + data | ||
| 175 | + }).then(response => { | ||
| 176 | + const res = response.data | ||
| 177 | + resolve(res) | ||
| 178 | + }).catch(error => { | ||
| 179 | + reject(error) | ||
| 180 | + }) | ||
| 181 | + }) | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +// 查询收费项目列表 | ||
| 185 | +export function listFeeConfigs(params) { | ||
| 186 | + return new Promise((resolve, reject) => { | ||
| 187 | + params.communityId = getCommunityId() | ||
| 188 | + request({ | ||
| 189 | + url: '/feeConfig.listFeeConfigs', | ||
| 190 | + method: 'get', | ||
| 191 | + params | ||
| 192 | + }).then(response => { | ||
| 193 | + const res = response.data | ||
| 194 | + resolve({ | ||
| 195 | + data: res.feeConfigs, | ||
| 196 | + total: res.total | ||
| 197 | + }) | ||
| 198 | + }).catch(error => { | ||
| 199 | + reject(error) | ||
| 200 | + }) | ||
| 201 | + }) | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +// 导入抄表数据 | ||
| 205 | +export function importMeterWaterData(data) { | ||
| 206 | + return new Promise((resolve, reject) => { | ||
| 207 | + data.append('communityId', getCommunityId()) | ||
| 208 | + request({ | ||
| 209 | + url: '/assetImport/importData', | ||
| 210 | + method: 'post', | ||
| 211 | + data, | ||
| 212 | + headers: { | ||
| 213 | + 'Content-Type': 'multipart/form-data' | ||
| 214 | + } | ||
| 215 | + }).then(response => { | ||
| 216 | + const res = response.data | ||
| 217 | + resolve(res) | ||
| 218 | + }).catch(error => { | ||
| 219 | + reject(error) | ||
| 220 | + }) | ||
| 221 | + }) | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +// 导出抄表模板 | ||
| 225 | +export function exportMeterWaterTemplate(params) { | ||
| 226 | + return new Promise((resolve, reject) => { | ||
| 227 | + params.communityId = getCommunityId() | ||
| 228 | + request({ | ||
| 229 | + url: '/export.exportData', | ||
| 230 | + method: 'get', | ||
| 231 | + params | ||
| 232 | + }).then(response => { | ||
| 233 | + const res = response.data | ||
| 234 | + resolve(res) | ||
| 235 | + }).catch(error => { | ||
| 236 | + reject(error) | ||
| 237 | + }) | ||
| 238 | + }) | ||
| 239 | +} | ||
| 0 | \ No newline at end of file | 240 | \ No newline at end of file |
src/components/car/floorUnitTree.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-card class="tree-card"> | ||
| 3 | + <el-tree | ||
| 4 | + ref="tree" | ||
| 5 | + :data="treeData" | ||
| 6 | + :props="defaultProps" | ||
| 7 | + node-key="id" | ||
| 8 | + :default-expanded-keys="expandedKeys" | ||
| 9 | + :highlight-current="true" | ||
| 10 | + @node-click="handleNodeClick" | ||
| 11 | + > | ||
| 12 | + <template #default="{ node, data }"> | ||
| 13 | + <span class="custom-tree-node"> | ||
| 14 | + <img :src="data.icon" class="tree-icon" v-if="data.icon"> | ||
| 15 | + <span>{{ node.label }}</span> | ||
| 16 | + </span> | ||
| 17 | + </template> | ||
| 18 | + </el-tree> | ||
| 19 | + <div v-if="!treeData || treeData.length === 0" class="no-data"> | ||
| 20 | + {{ $t('floorUnitTree.noBuilding') }} | ||
| 21 | + </div> | ||
| 22 | + </el-card> | ||
| 23 | +</template> | ||
| 24 | + | ||
| 25 | +<script> | ||
| 26 | +import { queryFloorAndUnits } from '@/api/car/carStructureApi' | ||
| 27 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 28 | + | ||
| 29 | +export default { | ||
| 30 | + name: 'FloorUnitTree', | ||
| 31 | + props: { | ||
| 32 | + floorId: { | ||
| 33 | + type: String, | ||
| 34 | + default: '' | ||
| 35 | + } | ||
| 36 | + }, | ||
| 37 | + data() { | ||
| 38 | + return { | ||
| 39 | + treeData: [], | ||
| 40 | + defaultProps: { | ||
| 41 | + children: 'children', | ||
| 42 | + label: 'text' | ||
| 43 | + }, | ||
| 44 | + expandedKeys: [], | ||
| 45 | + communityId: '' | ||
| 46 | + } | ||
| 47 | + }, | ||
| 48 | + watch: { | ||
| 49 | + floorId(newVal) { | ||
| 50 | + this.$nextTick(() => { | ||
| 51 | + if (newVal) { | ||
| 52 | + const node = this.$refs.tree.getNode('f_' + newVal) | ||
| 53 | + if (node) { | ||
| 54 | + this.$refs.tree.setCurrentKey(node.key) | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + }) | ||
| 58 | + } | ||
| 59 | + }, | ||
| 60 | + created() { | ||
| 61 | + this.communityId = getCommunityId() | ||
| 62 | + this.loadFloorAndUnits() | ||
| 63 | + }, | ||
| 64 | + methods: { | ||
| 65 | + async loadFloorAndUnits() { | ||
| 66 | + try { | ||
| 67 | + const params = { | ||
| 68 | + communityId: this.communityId | ||
| 69 | + } | ||
| 70 | + const data = await queryFloorAndUnits(params) | ||
| 71 | + this.treeData = this.formatTreeData(data) | ||
| 72 | + this.setDefaultExpanded() | ||
| 73 | + } catch (error) { | ||
| 74 | + this.$message.error(this.$t('floorUnitTree.fetchError')) | ||
| 75 | + } | ||
| 76 | + }, | ||
| 77 | + formatTreeData(data) { | ||
| 78 | + const formattedData = [] | ||
| 79 | + const floorMap = {} | ||
| 80 | + | ||
| 81 | + // First pass: create floor nodes | ||
| 82 | + data.forEach(item => { | ||
| 83 | + if (!floorMap[item.floorId]) { | ||
| 84 | + floorMap[item.floorId] = { | ||
| 85 | + id: 'f_' + item.floorId, | ||
| 86 | + floorId: item.floorId, | ||
| 87 | + floorNum: item.floorNum, | ||
| 88 | + icon: require('@/assets/img/floor.png'), | ||
| 89 | + text: `${item.floorNum}${this.$t('floorUnitTree.building')}(${item.floorName})`, | ||
| 90 | + children: [] | ||
| 91 | + } | ||
| 92 | + formattedData.push(floorMap[item.floorId]) | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + // Add unit if it exists and not '0' | ||
| 96 | + if (item.unitId && item.unitNum !== '0') { | ||
| 97 | + floorMap[item.floorId].children.push({ | ||
| 98 | + id: 'u_' + item.unitId, | ||
| 99 | + unitId: item.unitId, | ||
| 100 | + text: `${item.unitNum}${this.$t('floorUnitTree.unit')}`, | ||
| 101 | + icon: require('@/assets/img/unit.png') | ||
| 102 | + }) | ||
| 103 | + } | ||
| 104 | + }) | ||
| 105 | + | ||
| 106 | + return formattedData | ||
| 107 | + }, | ||
| 108 | + setDefaultExpanded() { | ||
| 109 | + if (this.treeData.length > 0) { | ||
| 110 | + this.expandedKeys = [this.treeData[0].id] | ||
| 111 | + } | ||
| 112 | + }, | ||
| 113 | + handleNodeClick(data) { | ||
| 114 | + if (data.id.startsWith('f_')) { | ||
| 115 | + this.$emit('switchFloor', { floorId: data.floorId }) | ||
| 116 | + } else if (data.id.startsWith('u_')) { | ||
| 117 | + this.$emit('switchUnit', { unitId: data.unitId }) | ||
| 118 | + } | ||
| 119 | + }, | ||
| 120 | + refreshTree(params) { | ||
| 121 | + if (params && params.floorId) { | ||
| 122 | + this.floorId = params.floorId | ||
| 123 | + } | ||
| 124 | + this.loadFloorAndUnits() | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | +} | ||
| 128 | +</script> | ||
| 129 | + | ||
| 130 | +<style lang="scss" scoped> | ||
| 131 | +.tree-card { | ||
| 132 | + height: 100%; | ||
| 133 | + | ||
| 134 | + .custom-tree-node { | ||
| 135 | + display: flex; | ||
| 136 | + align-items: center; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + .tree-icon { | ||
| 140 | + width: 16px; | ||
| 141 | + height: 16px; | ||
| 142 | + margin-right: 5px; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + .no-data { | ||
| 146 | + padding: 10px; | ||
| 147 | + text-align: center; | ||
| 148 | + color: #909399; | ||
| 149 | + } | ||
| 150 | +} | ||
| 151 | +</style> | ||
| 0 | \ No newline at end of file | 152 | \ No newline at end of file |
src/components/fee/addMeterWater.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog :title="$t('meterWater.addMeterReading')" :visible.sync="dialogVisible" width="50%" @close="handleClose"> | ||
| 3 | + <el-form ref="form" :model="form" :rules="rules" label-width="120px" label-position="right"> | ||
| 4 | + <el-form-item :label="$t('meterWater.feeType')" prop="feeTypeCd"> | ||
| 5 | + <el-select v-model="form.feeTypeCd" :placeholder="$t('meterWater.selectFeeType')" style="width: 100%" | ||
| 6 | + @change="handleFeeTypeChange"> | ||
| 7 | + <el-option v-for="item in feeTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> | ||
| 8 | + </el-select> | ||
| 9 | + </el-form-item> | ||
| 10 | + | ||
| 11 | + <el-form-item :label="$t('meterWater.feeItem')" prop="configId"> | ||
| 12 | + <el-select v-model="form.configId" :placeholder="$t('meterWater.selectFeeItem')" style="width: 100%" | ||
| 13 | + @change="handleConfigChange"> | ||
| 14 | + <el-option v-for="item in feeConfigs" :key="item.configId" :label="item.feeName" :value="item.configId" /> | ||
| 15 | + </el-select> | ||
| 16 | + <div class="form-tip"> | ||
| 17 | + {{ $t('meterWater.feeItemTip') }} | ||
| 18 | + </div> | ||
| 19 | + </el-form-item> | ||
| 20 | + | ||
| 21 | + <el-form-item :label="$t('meterWater.meterType')" prop="meterType"> | ||
| 22 | + <el-select v-model="form.meterType" :placeholder="$t('meterWater.selectMeterType')" style="width: 100%" | ||
| 23 | + @change="handleMeterTypeChange"> | ||
| 24 | + <el-option v-for="item in meterTypes" :key="item.typeId" :label="item.typeName" :value="item.typeId" /> | ||
| 25 | + </el-select> | ||
| 26 | + </el-form-item> | ||
| 27 | + | ||
| 28 | + <template v-if="!form.hasRoom"> | ||
| 29 | + <el-form-item :label="$t('meterWater.building')"> | ||
| 30 | + <floor-select2 ref="floorSelect" @change="handleFloorChange" /> | ||
| 31 | + </el-form-item> | ||
| 32 | + | ||
| 33 | + <el-form-item :label="$t('meterWater.unit')"> | ||
| 34 | + <unit-select2 ref="unitSelect" @change="handleUnitChange" /> | ||
| 35 | + </el-form-item> | ||
| 36 | + | ||
| 37 | + <el-form-item :label="$t('meterWater.room')"> | ||
| 38 | + <room-select2 ref="roomSelect" @change="handleRoomChange" /> | ||
| 39 | + </el-form-item> | ||
| 40 | + </template> | ||
| 41 | + | ||
| 42 | + <el-form-item v-else :label="$t('meterWater.feeObject')"> | ||
| 43 | + <el-input v-model="form.ownerName" :placeholder="$t('meterWater.inputRoom')" disabled /> | ||
| 44 | + </el-form-item> | ||
| 45 | + | ||
| 46 | + <el-form-item :label="$t('meterWater.preDegrees')" prop="preDegrees"> | ||
| 47 | + <el-input v-model="form.preDegrees" :placeholder="$t('meterWater.inputPreDegrees')" /> | ||
| 48 | + </el-form-item> | ||
| 49 | + | ||
| 50 | + <el-form-item :label="$t('meterWater.curDegrees')" prop="curDegrees"> | ||
| 51 | + <el-input v-model="form.curDegrees" :placeholder="$t('meterWater.inputCurDegrees')" | ||
| 52 | + @change="handleDegreesChange" /> | ||
| 53 | + </el-form-item> | ||
| 54 | + | ||
| 55 | + <el-form-item :label="$t('meterWater.preReadingTime')" prop="preReadingTime"> | ||
| 56 | + <el-date-picker v-model="form.preReadingTime" type="datetime" | ||
| 57 | + :placeholder="$t('meterWater.selectPreReadingTime')" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" /> | ||
| 58 | + </el-form-item> | ||
| 59 | + | ||
| 60 | + <el-form-item :label="$t('meterWater.curReadingTime')" prop="curReadingTime"> | ||
| 61 | + <el-date-picker v-model="form.curReadingTime" type="datetime" | ||
| 62 | + :placeholder="$t('meterWater.selectCurReadingTime')" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" /> | ||
| 63 | + </el-form-item> | ||
| 64 | + | ||
| 65 | + <el-form-item v-if="form.computingFormula === '9009'" :label="$t('meterWater.price')" prop="price"> | ||
| 66 | + <el-input v-model="form.price" :placeholder="$t('meterWater.inputPrice')" /> | ||
| 67 | + </el-form-item> | ||
| 68 | + | ||
| 69 | + <el-form-item :label="$t('meterWater.remark')" prop="remark"> | ||
| 70 | + <el-input v-model="form.remark" type="textarea" :placeholder="$t('meterWater.inputRemark')" :rows="2" /> | ||
| 71 | + </el-form-item> | ||
| 72 | + | ||
| 73 | + <el-form-item> | ||
| 74 | + <div class="form-tip"> | ||
| 75 | + {{ $t('meterWater.unitTip') }} | ||
| 76 | + </div> | ||
| 77 | + </el-form-item> | ||
| 78 | + </el-form> | ||
| 79 | + | ||
| 80 | + <span slot="footer" class="dialog-footer"> | ||
| 81 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 82 | + <el-button type="primary" @click="handleSubmit">{{ $t('common.save') }}</el-button> | ||
| 83 | + </span> | ||
| 84 | + </el-dialog> | ||
| 85 | +</template> | ||
| 86 | + | ||
| 87 | +<script> | ||
| 88 | +import { saveMeterWater, queryPreMeterWater } from '@/api/fee/meterWaterManageApi' | ||
| 89 | +import { listFeeConfigs, listMeterTypes } from '@/api/fee/meterWaterManageApi' | ||
| 90 | +import { getDict } from '@/api/community/communityApi' | ||
| 91 | +import FloorSelect2 from '@/components/fee/floorSelect2' | ||
| 92 | +import UnitSelect2 from '@/components/fee/unitSelect2' | ||
| 93 | +import RoomSelect2 from '@/components/fee/roomSelect2' | ||
| 94 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 95 | + | ||
| 96 | +export default { | ||
| 97 | + name: 'AddMeterWater', | ||
| 98 | + components: { | ||
| 99 | + FloorSelect2, | ||
| 100 | + UnitSelect2, | ||
| 101 | + RoomSelect2 | ||
| 102 | + }, | ||
| 103 | + data() { | ||
| 104 | + return { | ||
| 105 | + dialogVisible: false, | ||
| 106 | + form: { | ||
| 107 | + waterId: '', | ||
| 108 | + meterType: '', | ||
| 109 | + preDegrees: '', | ||
| 110 | + curDegrees: '', | ||
| 111 | + preReadingTime: '', | ||
| 112 | + curReadingTime: '', | ||
| 113 | + remark: '', | ||
| 114 | + roomId: '', | ||
| 115 | + objId: '', | ||
| 116 | + objName: '', | ||
| 117 | + feeTypeCd: '', | ||
| 118 | + feeConfigs: [], | ||
| 119 | + configId: '', | ||
| 120 | + objType: '3333', | ||
| 121 | + hasRoom: false, | ||
| 122 | + ownerName: '', | ||
| 123 | + meterTypes: [], | ||
| 124 | + computingFormula: '', | ||
| 125 | + price: 0, | ||
| 126 | + communityId: '' | ||
| 127 | + }, | ||
| 128 | + rules: { | ||
| 129 | + feeTypeCd: [ | ||
| 130 | + { required: true, message: this.$t('meterWater.feeTypeRequired'), trigger: 'blur' } | ||
| 131 | + ], | ||
| 132 | + configId: [ | ||
| 133 | + { required: true, message: this.$t('meterWater.feeItemRequired'), trigger: 'blur' } | ||
| 134 | + ], | ||
| 135 | + meterType: [ | ||
| 136 | + { required: true, message: this.$t('meterWater.meterTypeRequired'), trigger: 'blur' } | ||
| 137 | + ], | ||
| 138 | + preDegrees: [ | ||
| 139 | + { required: true, message: this.$t('meterWater.preDegreesRequired'), trigger: 'blur' }, | ||
| 140 | + { pattern: /^\d+(\.\d{1,2})?$/, message: this.$t('meterWater.degreesFormatError') } | ||
| 141 | + ], | ||
| 142 | + curDegrees: [ | ||
| 143 | + { required: true, message: this.$t('meterWater.curDegreesRequired'), trigger: 'blur' }, | ||
| 144 | + { pattern: /^\d+(\.\d{1,2})?$/, message: this.$t('meterWater.degreesFormatError') } | ||
| 145 | + ], | ||
| 146 | + preReadingTime: [ | ||
| 147 | + { required: true, message: this.$t('meterWater.preReadingTimeRequired'), trigger: 'blur' } | ||
| 148 | + ], | ||
| 149 | + curReadingTime: [ | ||
| 150 | + { required: true, message: this.$t('meterWater.curReadingTimeRequired'), trigger: 'blur' } | ||
| 151 | + ], | ||
| 152 | + remark: [ | ||
| 153 | + { max: 500, message: this.$t('meterWater.remarkMaxLength'), trigger: 'blur' } | ||
| 154 | + ] | ||
| 155 | + }, | ||
| 156 | + feeTypeOptions: [], | ||
| 157 | + feeConfigs: [], | ||
| 158 | + meterTypes: [] | ||
| 159 | + } | ||
| 160 | + }, | ||
| 161 | + created() { | ||
| 162 | + this.form.communityId = getCommunityId() | ||
| 163 | + this.loadFeeTypes() | ||
| 164 | + this.loadMeterTypes() | ||
| 165 | + }, | ||
| 166 | + methods: { | ||
| 167 | + open(params) { | ||
| 168 | + this.resetForm() | ||
| 169 | + if (params) { | ||
| 170 | + this.form.hasRoom = Object.prototype.hasOwnProperty.call(params, 'roomId') | ||
| 171 | + if (params.roomId) { | ||
| 172 | + this.form.roomId = params.roomId | ||
| 173 | + this.form.objId = params.roomId | ||
| 174 | + this.form.objName = params.roomName | ||
| 175 | + this.form.ownerName = params.ownerName | ||
| 176 | + ? `${params.roomName}(${params.ownerName})` | ||
| 177 | + : params.roomName | ||
| 178 | + } | ||
| 179 | + } | ||
| 180 | + this.dialogVisible = true | ||
| 181 | + }, | ||
| 182 | + resetForm() { | ||
| 183 | + this.form = { | ||
| 184 | + waterId: '', | ||
| 185 | + meterType: '', | ||
| 186 | + preDegrees: '', | ||
| 187 | + curDegrees: '', | ||
| 188 | + preReadingTime: '', | ||
| 189 | + curReadingTime: '', | ||
| 190 | + remark: '', | ||
| 191 | + roomId: '', | ||
| 192 | + objId: '', | ||
| 193 | + objName: '', | ||
| 194 | + feeTypeCd: '', | ||
| 195 | + feeConfigs: [], | ||
| 196 | + configId: '', | ||
| 197 | + objType: '3333', | ||
| 198 | + hasRoom: false, | ||
| 199 | + ownerName: '', | ||
| 200 | + meterTypes: [], | ||
| 201 | + computingFormula: '', | ||
| 202 | + price: 0, | ||
| 203 | + communityId: getCommunityId() | ||
| 204 | + } | ||
| 205 | + this.$refs.form && this.$refs.form.resetFields() | ||
| 206 | + }, | ||
| 207 | + handleClose() { | ||
| 208 | + this.resetForm() | ||
| 209 | + }, | ||
| 210 | + async loadFeeTypes() { | ||
| 211 | + try { | ||
| 212 | + const data = await getDict('pay_fee_config', 'fee_type_cd') | ||
| 213 | + this.feeTypeOptions = data.map(item => ({ | ||
| 214 | + value: item.value, | ||
| 215 | + label: item.label | ||
| 216 | + })) | ||
| 217 | + } catch (error) { | ||
| 218 | + console.error('Failed to load fee types:', error) | ||
| 219 | + } | ||
| 220 | + }, | ||
| 221 | + async loadMeterTypes() { | ||
| 222 | + try { | ||
| 223 | + const { data } = await listMeterTypes({ | ||
| 224 | + communityId: this.form.communityId, | ||
| 225 | + page: 1, | ||
| 226 | + row: 50 | ||
| 227 | + }) | ||
| 228 | + this.meterTypes = data | ||
| 229 | + } catch (error) { | ||
| 230 | + console.error('Failed to load meter types:', error) | ||
| 231 | + } | ||
| 232 | + }, | ||
| 233 | + async handleFeeTypeChange(feeTypeCd) { | ||
| 234 | + try { | ||
| 235 | + const { data } = await listFeeConfigs({ | ||
| 236 | + communityId: this.form.communityId, | ||
| 237 | + feeTypeCd, | ||
| 238 | + isDefault: 'F', | ||
| 239 | + valid: '1', | ||
| 240 | + page: 1, | ||
| 241 | + row: 20 | ||
| 242 | + }) | ||
| 243 | + this.feeConfigs = data | ||
| 244 | + this.form.configId = '' | ||
| 245 | + } catch (error) { | ||
| 246 | + console.error('Failed to load fee configs:', error) | ||
| 247 | + } | ||
| 248 | + }, | ||
| 249 | + handleConfigChange(configId) { | ||
| 250 | + const config = this.feeConfigs.find(item => item.configId === configId) | ||
| 251 | + if (config) { | ||
| 252 | + this.form.computingFormula = config.computingFormula | ||
| 253 | + } | ||
| 254 | + if (this.form.roomId) { | ||
| 255 | + this.queryPreMeterWater() | ||
| 256 | + } | ||
| 257 | + }, | ||
| 258 | + handleMeterTypeChange() { | ||
| 259 | + if (this.form.roomId) { | ||
| 260 | + this.queryPreMeterWater() | ||
| 261 | + } | ||
| 262 | + }, | ||
| 263 | + async queryPreMeterWater() { | ||
| 264 | + try { | ||
| 265 | + const { data } = await queryPreMeterWater({ | ||
| 266 | + communityId: this.form.communityId, | ||
| 267 | + objId: this.form.roomId, | ||
| 268 | + objType: this.form.objType, | ||
| 269 | + meterType: this.form.meterType | ||
| 270 | + }) | ||
| 271 | + | ||
| 272 | + if (data && data.length > 0) { | ||
| 273 | + this.form.preDegrees = data[0].curDegrees | ||
| 274 | + this.form.preReadingTime = data[0].curReadingTime | ||
| 275 | + if (this.form.computingFormula === '9009') { | ||
| 276 | + this.form.price = data[0].price | ||
| 277 | + } | ||
| 278 | + } else { | ||
| 279 | + this.form.preDegrees = '0' | ||
| 280 | + this.form.preReadingTime = new Date().toISOString().slice(0, 19).replace('T', ' ') | ||
| 281 | + } | ||
| 282 | + } catch (error) { | ||
| 283 | + console.error('Failed to query pre meter water:', error) | ||
| 284 | + } | ||
| 285 | + }, | ||
| 286 | + handleDegreesChange() { | ||
| 287 | + const pre = parseFloat(this.form.preDegrees) || 0 | ||
| 288 | + const cur = parseFloat(this.form.curDegrees) || 0 | ||
| 289 | + if (pre > cur) { | ||
| 290 | + this.$message.warning(this.$t('meterWater.degreesCompareError')) | ||
| 291 | + this.form.curDegrees = '' | ||
| 292 | + } | ||
| 293 | + }, | ||
| 294 | + handleFloorChange(floor) { | ||
| 295 | + this.form.floorId = floor.floorId | ||
| 296 | + this.form.floorNum = floor.floorNum | ||
| 297 | + }, | ||
| 298 | + handleUnitChange(unit) { | ||
| 299 | + this.form.unitId = unit.unitId | ||
| 300 | + this.form.unitNum = unit.unitNum | ||
| 301 | + }, | ||
| 302 | + handleRoomChange(room) { | ||
| 303 | + this.form.roomId = room.roomId | ||
| 304 | + this.form.objId = room.roomId | ||
| 305 | + this.form.objName = room.name | ||
| 306 | + this.form.ownerName = room.link | ||
| 307 | + this.queryPreMeterWater() | ||
| 308 | + }, | ||
| 309 | + handleSubmit() { | ||
| 310 | + this.$refs.form.validate(async valid => { | ||
| 311 | + if (!valid) return | ||
| 312 | + | ||
| 313 | + try { | ||
| 314 | + await saveMeterWater(this.form) | ||
| 315 | + this.$message.success(this.$t('common.saveSuccess')) | ||
| 316 | + this.dialogVisible = false | ||
| 317 | + this.$emit('success') | ||
| 318 | + } catch (error) { | ||
| 319 | + console.error('Failed to save meter water:', error) | ||
| 320 | + this.$message.error(error.message || this.$t('common.saveFailed')) | ||
| 321 | + } | ||
| 322 | + }) | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | +} | ||
| 326 | +</script> | ||
| 327 | + | ||
| 328 | +<style lang="scss" scoped> | ||
| 329 | +.form-tip { | ||
| 330 | + font-size: 12px; | ||
| 331 | + color: #999; | ||
| 332 | + margin-top: 5px; | ||
| 333 | +} | ||
| 334 | +</style> | ||
| 0 | \ No newline at end of file | 335 | \ No newline at end of file |
src/components/fee/contractCreateFeeAdd.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog :title="$t('contractCreateFeeAdd.title')" :visible.sync="visible" width="800px" @close="handleClose"> | ||
| 3 | + <el-form ref="form" :model="formData" :rules="rules" label-width="120px" label-position="right"> | ||
| 4 | + <el-form-item :label="$t('contractCreateFeeAdd.feeTypeCd')" prop="feeTypeCd"> | ||
| 5 | + <el-select v-model="formData.feeTypeCd" :placeholder="$t('contractCreateFeeAdd.selectFeeType')" | ||
| 6 | + style="width: 100%" @change="handleFeeTypeChange"> | ||
| 7 | + <el-option v-for="item in feeTypeOptions" :key="item.statusCd" :label="item.name" :value="item.statusCd" | ||
| 8 | + :disabled="item.statusCd === '888800010008' || | ||
| 9 | + item.statusCd === '888800010017' | ||
| 10 | + " /> | ||
| 11 | + </el-select> | ||
| 12 | + </el-form-item> | ||
| 13 | + | ||
| 14 | + <el-form-item :label="$t('contractCreateFeeAdd.configId')" prop="configId"> | ||
| 15 | + <el-select v-model="formData.configId" :placeholder="$t('contractCreateFeeAdd.selectFeeConfig')" | ||
| 16 | + style="width: 100%" @change="handleConfigChange"> | ||
| 17 | + <el-option v-for="item in feeConfigOptions" :key="item.configId" :label="item.feeName" | ||
| 18 | + :value="item.configId" /> | ||
| 19 | + </el-select> | ||
| 20 | + </el-form-item> | ||
| 21 | + | ||
| 22 | + <el-form-item v-if="formData.computingFormula === '4004'" :label="$t('contractCreateFeeAdd.amount')" | ||
| 23 | + prop="amount"> | ||
| 24 | + <el-input v-model.trim="formData.amount" :placeholder="$t('contractCreateFeeAdd.inputAmount')" /> | ||
| 25 | + </el-form-item> | ||
| 26 | + | ||
| 27 | + <el-form-item v-if="isMore" :label="$t('contractCreateFeeAdd.contractState')" prop="contractState"> | ||
| 28 | + <el-checkbox-group v-model="formData.contractState"> | ||
| 29 | + <el-checkbox label="2001"> | ||
| 30 | + {{ $t('contractCreateFeeAdd.state2001') }} | ||
| 31 | + </el-checkbox> | ||
| 32 | + <el-checkbox label="2003"> | ||
| 33 | + {{ $t('contractCreateFeeAdd.state2003') }} | ||
| 34 | + </el-checkbox> | ||
| 35 | + <el-checkbox label="2005"> | ||
| 36 | + {{ $t('contractCreateFeeAdd.state2005') }} | ||
| 37 | + </el-checkbox> | ||
| 38 | + </el-checkbox-group> | ||
| 39 | + </el-form-item> | ||
| 40 | + | ||
| 41 | + <el-form-item :label="$t('contractCreateFeeAdd.startTime')" prop="startTime"> | ||
| 42 | + <el-date-picker v-model="formData.startTime" type="datetime" | ||
| 43 | + :placeholder="$t('contractCreateFeeAdd.selectStartTime')" value-format="yyyy-MM-dd HH:mm:ss" | ||
| 44 | + style="width: 100%" @change="validateStartTime" /> | ||
| 45 | + </el-form-item> | ||
| 46 | + | ||
| 47 | + <el-form-item :label="$t('contractCreateFeeAdd.endTime')" prop="endTime" :rules="formData.feeFlag === '2006012' | ||
| 48 | + ? [ | ||
| 49 | + { | ||
| 50 | + required: true, | ||
| 51 | + message: $t('contractCreateFeeAdd.endTimeRequired'), | ||
| 52 | + trigger: 'blur' | ||
| 53 | + } | ||
| 54 | + ] | ||
| 55 | + : [] | ||
| 56 | + "> | ||
| 57 | + <el-date-picker v-model="formData.endTime" type="datetime" | ||
| 58 | + :placeholder="$t('contractCreateFeeAdd.selectEndTime')" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" | ||
| 59 | + :disabled="!formData.feeFlag" @change="validateEndTime" /> | ||
| 60 | + </el-form-item> | ||
| 61 | + </el-form> | ||
| 62 | + | ||
| 63 | + <div slot="footer" class="dialog-footer"> | ||
| 64 | + <el-button @click="visible = false"> | ||
| 65 | + {{ $t('common.cancel') }} | ||
| 66 | + </el-button> | ||
| 67 | + <el-button type="primary" @click="handleSubmit"> | ||
| 68 | + {{ $t('common.submit') }} | ||
| 69 | + </el-button> | ||
| 70 | + </div> | ||
| 71 | + </el-dialog> | ||
| 72 | +</template> | ||
| 73 | + | ||
| 74 | +<script> | ||
| 75 | +import { saveContractCreateFee,listFeeConfigs } from '@/api/fee/contractCreateFeeApi' | ||
| 76 | +import { getCommunityId,getDict } from '@/api/community/communityApi' | ||
| 77 | + | ||
| 78 | +export default { | ||
| 79 | + name: 'ContractCreateFeeAdd', | ||
| 80 | + data() { | ||
| 81 | + return { | ||
| 82 | + visible: false, | ||
| 83 | + isMore: false, | ||
| 84 | + formData: { | ||
| 85 | + feeTypeCds: [], | ||
| 86 | + feeConfigs: [], | ||
| 87 | + contractId: '', | ||
| 88 | + feeTypeCd: '', | ||
| 89 | + configId: '', | ||
| 90 | + contractState: ['2001'], | ||
| 91 | + startTime: '', | ||
| 92 | + feeFlag: '', | ||
| 93 | + endTime: '', | ||
| 94 | + computingFormula: '', | ||
| 95 | + amount: '' | ||
| 96 | + }, | ||
| 97 | + feeTypeOptions: [], | ||
| 98 | + feeConfigOptions: [], | ||
| 99 | + rules: { | ||
| 100 | + feeTypeCd: [ | ||
| 101 | + { | ||
| 102 | + required: true, | ||
| 103 | + message: this.$t('contractCreateFeeAdd.feeTypeRequired'), | ||
| 104 | + trigger: 'change' | ||
| 105 | + } | ||
| 106 | + ], | ||
| 107 | + configId: [ | ||
| 108 | + { | ||
| 109 | + required: true, | ||
| 110 | + message: this.$t('contractCreateFeeAdd.configRequired'), | ||
| 111 | + trigger: 'change' | ||
| 112 | + } | ||
| 113 | + ], | ||
| 114 | + startTime: [ | ||
| 115 | + { | ||
| 116 | + required: true, | ||
| 117 | + message: this.$t('contractCreateFeeAdd.startTimeRequired'), | ||
| 118 | + trigger: 'change' | ||
| 119 | + } | ||
| 120 | + ], | ||
| 121 | + amount: [ | ||
| 122 | + { | ||
| 123 | + required: true, | ||
| 124 | + message: this.$t('contractCreateFeeAdd.amountRequired'), | ||
| 125 | + trigger: 'blur' | ||
| 126 | + } | ||
| 127 | + ] | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + }, | ||
| 131 | + methods: { | ||
| 132 | + open(contract, isMore) { | ||
| 133 | + this.isMore = isMore | ||
| 134 | + if (contract) { | ||
| 135 | + this.formData.contractId = contract.contractId | ||
| 136 | + } | ||
| 137 | + this.visible = true | ||
| 138 | + this.loadFeeTypes() | ||
| 139 | + }, | ||
| 140 | + async loadFeeTypes() { | ||
| 141 | + try { | ||
| 142 | + const data = await getDict('pay_fee_config', 'fee_type_cd') | ||
| 143 | + this.feeTypeOptions = data.filter( | ||
| 144 | + item => | ||
| 145 | + item.statusCd !== '888800010015' && item.statusCd !== '888800010016' | ||
| 146 | + ) | ||
| 147 | + } catch (error) { | ||
| 148 | + console.error('Failed to load fee types:', error) | ||
| 149 | + } | ||
| 150 | + }, | ||
| 151 | + async handleFeeTypeChange(value) { | ||
| 152 | + this.formData.configId = '' | ||
| 153 | + try { | ||
| 154 | + const params = { | ||
| 155 | + page: 1, | ||
| 156 | + row: 50, | ||
| 157 | + communityId: getCommunityId(), | ||
| 158 | + feeTypeCd: value, | ||
| 159 | + isDefault: 'F', | ||
| 160 | + valid: '1' | ||
| 161 | + } | ||
| 162 | + const { feeConfigs } = await listFeeConfigs(params) | ||
| 163 | + this.feeConfigOptions = feeConfigs | ||
| 164 | + } catch (error) { | ||
| 165 | + console.error('Failed to load fee configs:', error) | ||
| 166 | + } | ||
| 167 | + }, | ||
| 168 | + handleConfigChange(value) { | ||
| 169 | + this.formData.endTime = '' | ||
| 170 | + const config = this.feeConfigOptions.find(item => item.configId === value) | ||
| 171 | + if (config) { | ||
| 172 | + this.formData.feeFlag = config.feeFlag | ||
| 173 | + this.formData.computingFormula = config.computingFormula | ||
| 174 | + } | ||
| 175 | + }, | ||
| 176 | + validateStartTime(value) { | ||
| 177 | + if (value && this.formData.endTime) { | ||
| 178 | + const start = new Date(value).getTime() | ||
| 179 | + const end = new Date(this.formData.endTime).getTime() | ||
| 180 | + if (start >= end) { | ||
| 181 | + this.$message.error(this.$t('contractCreateFeeAdd.timeError')) | ||
| 182 | + this.formData.startTime = '' | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + }, | ||
| 186 | + validateEndTime(value) { | ||
| 187 | + if (value && this.formData.startTime) { | ||
| 188 | + const start = new Date(this.formData.startTime).getTime() | ||
| 189 | + const end = new Date(value).getTime() | ||
| 190 | + if (start >= end) { | ||
| 191 | + this.$message.error(this.$t('contractCreateFeeAdd.timeError')) | ||
| 192 | + this.formData.endTime = '' | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + }, | ||
| 196 | + async handleSubmit() { | ||
| 197 | + try { | ||
| 198 | + await this.$refs.form.validate() | ||
| 199 | + const params = { | ||
| 200 | + ...this.formData, | ||
| 201 | + communityId: getCommunityId(), | ||
| 202 | + contractState: this.formData.contractState.join(',') | ||
| 203 | + } | ||
| 204 | + const { data } = await saveContractCreateFee(params) | ||
| 205 | + this.$message.success( | ||
| 206 | + this.$t('contractCreateFeeAdd.success', { | ||
| 207 | + total: data.totalRoom, | ||
| 208 | + success: data.successRoom, | ||
| 209 | + error: data.errorRoom | ||
| 210 | + }) | ||
| 211 | + ) | ||
| 212 | + this.$emit('success') | ||
| 213 | + this.visible = false | ||
| 214 | + } catch (error) { | ||
| 215 | + console.error('Submit failed:', error) | ||
| 216 | + } | ||
| 217 | + }, | ||
| 218 | + handleClose() { | ||
| 219 | + this.$refs.form.resetFields() | ||
| 220 | + this.formData = { | ||
| 221 | + feeTypeCds: [], | ||
| 222 | + feeConfigs: [], | ||
| 223 | + contractId: '', | ||
| 224 | + feeTypeCd: '', | ||
| 225 | + configId: '', | ||
| 226 | + contractState: ['2001'], | ||
| 227 | + startTime: '', | ||
| 228 | + feeFlag: '', | ||
| 229 | + endTime: '', | ||
| 230 | + computingFormula: '', | ||
| 231 | + amount: '' | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | +} | ||
| 236 | +</script> | ||
| 0 | \ No newline at end of file | 237 | \ No newline at end of file |
src/components/fee/deleteMeterWater.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('meterWater.confirmOperation')" | ||
| 4 | + :visible.sync="dialogVisible" | ||
| 5 | + width="30%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <div class="confirm-content"> | ||
| 9 | + <p>{{ $t('meterWater.confirmDeleteMeterReading') }}</p> | ||
| 10 | + </div> | ||
| 11 | + <span slot="footer" class="dialog-footer"> | ||
| 12 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 13 | + <el-button type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</el-button> | ||
| 14 | + </span> | ||
| 15 | + </el-dialog> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script> | ||
| 19 | +import { deleteMeterWater } from '@/api/fee/meterWaterManageApi' | ||
| 20 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 21 | + | ||
| 22 | +export default { | ||
| 23 | + name: 'DeleteMeterWater', | ||
| 24 | + data() { | ||
| 25 | + return { | ||
| 26 | + dialogVisible: false, | ||
| 27 | + waterId: '', | ||
| 28 | + communityId: '' | ||
| 29 | + } | ||
| 30 | + }, | ||
| 31 | + created() { | ||
| 32 | + this.communityId = getCommunityId() | ||
| 33 | + }, | ||
| 34 | + methods: { | ||
| 35 | + open(data) { | ||
| 36 | + this.waterId = data.waterId | ||
| 37 | + this.dialogVisible = true | ||
| 38 | + }, | ||
| 39 | + handleClose() { | ||
| 40 | + this.waterId = '' | ||
| 41 | + }, | ||
| 42 | + async handleConfirm() { | ||
| 43 | + try { | ||
| 44 | + await deleteMeterWater({ | ||
| 45 | + waterId: this.waterId, | ||
| 46 | + communityId: this.communityId | ||
| 47 | + }) | ||
| 48 | + this.$message.success(this.$t('common.deleteSuccess')) | ||
| 49 | + this.dialogVisible = false | ||
| 50 | + this.$emit('success') | ||
| 51 | + } catch (error) { | ||
| 52 | + console.error('Failed to delete meter water:', error) | ||
| 53 | + this.$message.error(error.message || this.$t('common.deleteFailed')) | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | +</script> | ||
| 59 | + | ||
| 60 | +<style scoped> | ||
| 61 | +.confirm-content { | ||
| 62 | + text-align: center; | ||
| 63 | + font-size: 16px; | ||
| 64 | + padding: 20px 0; | ||
| 65 | +} | ||
| 66 | +</style> | ||
| 0 | \ No newline at end of file | 67 | \ No newline at end of file |
src/components/fee/editMeterWater.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('meterWater.editMeterReading')" | ||
| 4 | + :visible.sync="dialogVisible" | ||
| 5 | + width="50%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-form | ||
| 9 | + ref="form" | ||
| 10 | + :model="form" | ||
| 11 | + :rules="rules" | ||
| 12 | + label-width="120px" | ||
| 13 | + label-position="right" | ||
| 14 | + > | ||
| 15 | + <el-form-item :label="$t('meterWater.preDegrees')"> | ||
| 16 | + <el-input | ||
| 17 | + v-model="form.preDegrees" | ||
| 18 | + :placeholder="$t('meterWater.inputPreDegrees')" | ||
| 19 | + disabled | ||
| 20 | + /> | ||
| 21 | + </el-form-item> | ||
| 22 | + | ||
| 23 | + <el-form-item :label="$t('meterWater.curDegrees')" prop="curDegrees"> | ||
| 24 | + <el-input | ||
| 25 | + v-model="form.curDegrees" | ||
| 26 | + :placeholder="$t('meterWater.inputCurDegrees')" | ||
| 27 | + @change="handleDegreesChange" | ||
| 28 | + /> | ||
| 29 | + </el-form-item> | ||
| 30 | + | ||
| 31 | + <el-form-item :label="$t('meterWater.preReadingTime')"> | ||
| 32 | + <el-input | ||
| 33 | + v-model="form.preReadingTime" | ||
| 34 | + :placeholder="$t('meterWater.selectPreReadingTime')" | ||
| 35 | + disabled | ||
| 36 | + /> | ||
| 37 | + </el-form-item> | ||
| 38 | + | ||
| 39 | + <el-form-item :label="$t('meterWater.curReadingTime')" prop="curReadingTime"> | ||
| 40 | + <el-date-picker | ||
| 41 | + v-model="form.curReadingTime" | ||
| 42 | + type="datetime" | ||
| 43 | + :placeholder="$t('meterWater.selectCurReadingTime')" | ||
| 44 | + value-format="yyyy-MM-dd HH:mm:ss" | ||
| 45 | + style="width: 100%" | ||
| 46 | + /> | ||
| 47 | + </el-form-item> | ||
| 48 | + | ||
| 49 | + <el-form-item :label="$t('meterWater.remark')" prop="remark"> | ||
| 50 | + <el-input | ||
| 51 | + v-model="form.remark" | ||
| 52 | + type="textarea" | ||
| 53 | + :placeholder="$t('meterWater.inputRemark')" | ||
| 54 | + :rows="2" | ||
| 55 | + /> | ||
| 56 | + </el-form-item> | ||
| 57 | + </el-form> | ||
| 58 | + | ||
| 59 | + <span slot="footer" class="dialog-footer"> | ||
| 60 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 61 | + <el-button type="primary" @click="handleSubmit">{{ $t('common.save') }}</el-button> | ||
| 62 | + </span> | ||
| 63 | + </el-dialog> | ||
| 64 | +</template> | ||
| 65 | + | ||
| 66 | +<script> | ||
| 67 | +import { updateMeterWater } from '@/api/fee/meterWaterManageApi' | ||
| 68 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 69 | + | ||
| 70 | +export default { | ||
| 71 | + name: 'EditMeterWater', | ||
| 72 | + data() { | ||
| 73 | + return { | ||
| 74 | + dialogVisible: false, | ||
| 75 | + form: { | ||
| 76 | + waterId: '', | ||
| 77 | + curDegrees: '', | ||
| 78 | + curReadingTime: '', | ||
| 79 | + remark: '', | ||
| 80 | + communityId: '', | ||
| 81 | + preDegrees: '', | ||
| 82 | + preReadingTime: '' | ||
| 83 | + }, | ||
| 84 | + rules: { | ||
| 85 | + curDegrees: [ | ||
| 86 | + { required: true, message: this.$t('meterWater.curDegreesRequired'), trigger: 'blur' }, | ||
| 87 | + { pattern: /^\d+(\.\d{1,2})?$/, message: this.$t('meterWater.degreesFormatError') } | ||
| 88 | + ], | ||
| 89 | + curReadingTime: [ | ||
| 90 | + { required: true, message: this.$t('meterWater.curReadingTimeRequired'), trigger: 'blur' } | ||
| 91 | + ], | ||
| 92 | + remark: [ | ||
| 93 | + { max: 500, message: this.$t('meterWater.remarkMaxLength'), trigger: 'blur' } | ||
| 94 | + ] | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + }, | ||
| 98 | + created() { | ||
| 99 | + this.form.communityId = getCommunityId() | ||
| 100 | + }, | ||
| 101 | + methods: { | ||
| 102 | + open(data) { | ||
| 103 | + this.form = { | ||
| 104 | + waterId: data.waterId, | ||
| 105 | + curDegrees: data.curDegrees, | ||
| 106 | + curReadingTime: data.curReadingTime, | ||
| 107 | + remark: data.remark, | ||
| 108 | + communityId: this.form.communityId, | ||
| 109 | + preDegrees: data.preDegrees, | ||
| 110 | + preReadingTime: data.preReadingTime | ||
| 111 | + } | ||
| 112 | + this.dialogVisible = true | ||
| 113 | + }, | ||
| 114 | + handleClose() { | ||
| 115 | + this.$refs.form.resetFields() | ||
| 116 | + }, | ||
| 117 | + handleDegreesChange() { | ||
| 118 | + const pre = parseFloat(this.form.preDegrees) || 0 | ||
| 119 | + const cur = parseFloat(this.form.curDegrees) || 0 | ||
| 120 | + if (pre > cur) { | ||
| 121 | + this.$message.warning(this.$t('meterWater.degreesCompareError')) | ||
| 122 | + this.form.curDegrees = '' | ||
| 123 | + } | ||
| 124 | + }, | ||
| 125 | + handleSubmit() { | ||
| 126 | + this.$refs.form.validate(async valid => { | ||
| 127 | + if (!valid) return | ||
| 128 | + | ||
| 129 | + try { | ||
| 130 | + await updateMeterWater(this.form) | ||
| 131 | + this.$message.success(this.$t('common.saveSuccess')) | ||
| 132 | + this.dialogVisible = false | ||
| 133 | + this.$emit('success') | ||
| 134 | + } catch (error) { | ||
| 135 | + console.error('Failed to update meter water:', error) | ||
| 136 | + this.$message.error(error.message || this.$t('common.saveFailed')) | ||
| 137 | + } | ||
| 138 | + }) | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | +} | ||
| 142 | +</script> | ||
| 0 | \ No newline at end of file | 143 | \ No newline at end of file |
src/components/fee/floorSelect2.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-select | ||
| 3 | + v-model="selectedFloor" | ||
| 4 | + :placeholder="$t('meterWater.selectBuilding')" | ||
| 5 | + filterable | ||
| 6 | + clearable | ||
| 7 | + style="width: 100%" | ||
| 8 | + @change="handleChange" | ||
| 9 | + > | ||
| 10 | + <el-option | ||
| 11 | + v-for="item in floors" | ||
| 12 | + :key="item.floorId" | ||
| 13 | + :label="`${item.floorNum}栋`" | ||
| 14 | + :value="item.floorId" | ||
| 15 | + /> | ||
| 16 | + </el-select> | ||
| 17 | +</template> | ||
| 18 | + | ||
| 19 | +<script> | ||
| 20 | +import { queryFloors } from '@/api/fee/meterWaterManageApi' | ||
| 21 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 22 | + | ||
| 23 | +export default { | ||
| 24 | + name: 'FloorSelect2', | ||
| 25 | + props: { | ||
| 26 | + value: { | ||
| 27 | + type: [String, Number], | ||
| 28 | + default: '' | ||
| 29 | + } | ||
| 30 | + }, | ||
| 31 | + data() { | ||
| 32 | + return { | ||
| 33 | + floors: [], | ||
| 34 | + selectedFloor: this.value, | ||
| 35 | + communityId: '' | ||
| 36 | + } | ||
| 37 | + }, | ||
| 38 | + created() { | ||
| 39 | + this.communityId = getCommunityId() | ||
| 40 | + this.loadFloors() | ||
| 41 | + }, | ||
| 42 | + methods: { | ||
| 43 | + async loadFloors() { | ||
| 44 | + try { | ||
| 45 | + const { data } = await queryFloors({ | ||
| 46 | + communityId: this.communityId, | ||
| 47 | + page: 1, | ||
| 48 | + row: 500 | ||
| 49 | + }) | ||
| 50 | + this.floors = data | ||
| 51 | + } catch (error) { | ||
| 52 | + console.error('Failed to load floors:', error) | ||
| 53 | + } | ||
| 54 | + }, | ||
| 55 | + handleChange(value) { | ||
| 56 | + const floor = this.floors.find(item => item.floorId === value) | ||
| 57 | + this.$emit('change', { | ||
| 58 | + floorId: value, | ||
| 59 | + floorNum: floor ? floor.floorNum : '' | ||
| 60 | + }) | ||
| 61 | + } | ||
| 62 | + }, | ||
| 63 | + watch: { | ||
| 64 | + value(newVal) { | ||
| 65 | + this.selectedFloor = newVal | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | +} | ||
| 69 | +</script> | ||
| 0 | \ No newline at end of file | 70 | \ No newline at end of file |
src/components/fee/importMeterWaterFee.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('meterWater.meterReadingImport')" | ||
| 4 | + :visible.sync="dialogVisible" | ||
| 5 | + width="50%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-form | ||
| 9 | + ref="form" | ||
| 10 | + :model="form" | ||
| 11 | + :rules="rules" | ||
| 12 | + label-width="120px" | ||
| 13 | + label-position="right" | ||
| 14 | + > | ||
| 15 | + <el-form-item :label="$t('meterWater.feeType')" prop="feeTypeCd"> | ||
| 16 | + <el-select | ||
| 17 | + v-model="form.feeTypeCd" | ||
| 18 | + :placeholder="$t('meterWater.selectFeeType')" | ||
| 19 | + style="width: 100%" | ||
| 20 | + @change="handleFeeTypeChange" | ||
| 21 | + > | ||
| 22 | + <el-option | ||
| 23 | + v-for="item in feeTypeOptions" | ||
| 24 | + :key="item.value" | ||
| 25 | + :label="item.label" | ||
| 26 | + :value="item.value" | ||
| 27 | + /> | ||
| 28 | + </el-select> | ||
| 29 | + </el-form-item> | ||
| 30 | + | ||
| 31 | + <el-form-item :label="$t('meterWater.feeItem')" prop="configId"> | ||
| 32 | + <el-select | ||
| 33 | + v-model="form.configId" | ||
| 34 | + :placeholder="$t('meterWater.selectFeeItem')" | ||
| 35 | + style="width: 100%" | ||
| 36 | + > | ||
| 37 | + <el-option | ||
| 38 | + v-for="item in feeConfigs" | ||
| 39 | + :key="item.configId" | ||
| 40 | + :label="item.feeName" | ||
| 41 | + :value="item.configId" | ||
| 42 | + /> | ||
| 43 | + </el-select> | ||
| 44 | + </el-form-item> | ||
| 45 | + | ||
| 46 | + <el-form-item :label="$t('meterWater.meterType')" prop="meterType"> | ||
| 47 | + <el-select | ||
| 48 | + v-model="form.meterType" | ||
| 49 | + :placeholder="$t('meterWater.selectMeterType')" | ||
| 50 | + style="width: 100%" | ||
| 51 | + > | ||
| 52 | + <el-option | ||
| 53 | + v-for="item in meterTypes" | ||
| 54 | + :key="item.typeId" | ||
| 55 | + :label="item.typeName" | ||
| 56 | + :value="item.typeId" | ||
| 57 | + /> | ||
| 58 | + </el-select> | ||
| 59 | + </el-form-item> | ||
| 60 | + | ||
| 61 | + <el-form-item :label="$t('meterWater.selectFile')" prop="file"> | ||
| 62 | + <el-upload | ||
| 63 | + ref="upload" | ||
| 64 | + :auto-upload="false" | ||
| 65 | + :limit="1" | ||
| 66 | + :on-change="handleFileChange" | ||
| 67 | + :file-list="fileList" | ||
| 68 | + accept=".xls,.xlsx" | ||
| 69 | + > | ||
| 70 | + <el-button size="small" type="primary">{{ $t('meterWater.selectFile') }}</el-button> | ||
| 71 | + <div slot="tip" class="el-upload__tip"> | ||
| 72 | + {{ fileList.length > 0 ? fileList[0].name : $t('meterWater.fileRequired') }} | ||
| 73 | + </div> | ||
| 74 | + </el-upload> | ||
| 75 | + </el-form-item> | ||
| 76 | + | ||
| 77 | + <el-form-item :label="$t('meterWater.downloadTemplate')"> | ||
| 78 | + <div> | ||
| 79 | + {{ $t('meterWater.downloadTip') }} | ||
| 80 | + <el-link type="primary" @click="handleDownloadTemplate"> | ||
| 81 | + {{ $t('meterWater.importTemplate') }} | ||
| 82 | + </el-link> | ||
| 83 | + {{ $t('meterWater.prepareData') }} | ||
| 84 | + </div> | ||
| 85 | + </el-form-item> | ||
| 86 | + </el-form> | ||
| 87 | + | ||
| 88 | + <span slot="footer" class="dialog-footer"> | ||
| 89 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 90 | + <el-button type="primary" @click="handleImport">{{ $t('common.import') }}</el-button> | ||
| 91 | + </span> | ||
| 92 | + </el-dialog> | ||
| 93 | +</template> | ||
| 94 | + | ||
| 95 | +<script> | ||
| 96 | +import { importMeterWaterData, exportMeterWaterTemplate } from '@/api/fee/meterWaterManageApi' | ||
| 97 | +import { listFeeConfigs, listMeterTypes } from '@/api/fee/meterWaterManageApi' | ||
| 98 | +import { getDict } from '@/api/community/communityApi' | ||
| 99 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 100 | + | ||
| 101 | +export default { | ||
| 102 | + name: 'ImportMeterWaterFee', | ||
| 103 | + data() { | ||
| 104 | + return { | ||
| 105 | + dialogVisible: false, | ||
| 106 | + form: { | ||
| 107 | + feeTypeCd: '', | ||
| 108 | + configId: '', | ||
| 109 | + meterType: '', | ||
| 110 | + file: null, | ||
| 111 | + communityId: '' | ||
| 112 | + }, | ||
| 113 | + rules: { | ||
| 114 | + feeTypeCd: [ | ||
| 115 | + { required: true, message: this.$t('meterWater.feeTypeRequired'), trigger: 'blur' } | ||
| 116 | + ], | ||
| 117 | + configId: [ | ||
| 118 | + { required: true, message: this.$t('meterWater.feeItemRequired'), trigger: 'blur' } | ||
| 119 | + ], | ||
| 120 | + meterType: [ | ||
| 121 | + { required: true, message: this.$t('meterWater.meterTypeRequired'), trigger: 'blur' } | ||
| 122 | + ], | ||
| 123 | + file: [ | ||
| 124 | + { required: true, message: this.$t('meterWater.fileRequired'), trigger: 'blur' } | ||
| 125 | + ] | ||
| 126 | + }, | ||
| 127 | + feeTypeOptions: [], | ||
| 128 | + feeConfigs: [], | ||
| 129 | + meterTypes: [], | ||
| 130 | + fileList: [] | ||
| 131 | + } | ||
| 132 | + }, | ||
| 133 | + created() { | ||
| 134 | + this.form.communityId = getCommunityId() | ||
| 135 | + this.loadFeeTypes() | ||
| 136 | + this.loadMeterTypes() | ||
| 137 | + }, | ||
| 138 | + methods: { | ||
| 139 | + open() { | ||
| 140 | + this.dialogVisible = true | ||
| 141 | + }, | ||
| 142 | + handleClose() { | ||
| 143 | + this.$refs.form.resetFields() | ||
| 144 | + this.fileList = [] | ||
| 145 | + }, | ||
| 146 | + async loadFeeTypes() { | ||
| 147 | + try { | ||
| 148 | + const data = await getDict('pay_fee_config', 'fee_type_cd') | ||
| 149 | + this.feeTypeOptions = data.map(item => ({ | ||
| 150 | + value: item.value, | ||
| 151 | + label: item.label | ||
| 152 | + })) | ||
| 153 | + } catch (error) { | ||
| 154 | + console.error('Failed to load fee types:', error) | ||
| 155 | + } | ||
| 156 | + }, | ||
| 157 | + async loadMeterTypes() { | ||
| 158 | + try { | ||
| 159 | + const { data } = await listMeterTypes({ | ||
| 160 | + communityId: this.form.communityId, | ||
| 161 | + page: 1, | ||
| 162 | + row: 50 | ||
| 163 | + }) | ||
| 164 | + this.meterTypes = data | ||
| 165 | + } catch (error) { | ||
| 166 | + console.error('Failed to load meter types:', error) | ||
| 167 | + } | ||
| 168 | + }, | ||
| 169 | + async handleFeeTypeChange(feeTypeCd) { | ||
| 170 | + try { | ||
| 171 | + const { data } = await listFeeConfigs({ | ||
| 172 | + communityId: this.form.communityId, | ||
| 173 | + feeTypeCd, | ||
| 174 | + isDefault: 'F', | ||
| 175 | + valid: '1', | ||
| 176 | + page: 1, | ||
| 177 | + row: 20 | ||
| 178 | + }) | ||
| 179 | + this.feeConfigs = data | ||
| 180 | + this.form.configId = '' | ||
| 181 | + } catch (error) { | ||
| 182 | + console.error('Failed to load fee configs:', error) | ||
| 183 | + } | ||
| 184 | + }, | ||
| 185 | + handleFileChange(file) { | ||
| 186 | + this.form.file = file.raw | ||
| 187 | + this.fileList = [file] | ||
| 188 | + }, | ||
| 189 | + async handleDownloadTemplate() { | ||
| 190 | + if (!this.form.meterType) { | ||
| 191 | + this.$message.warning(this.$t('meterWater.selectMeterTypeFirst')) | ||
| 192 | + return | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + try { | ||
| 196 | + await exportMeterWaterTemplate({ | ||
| 197 | + communityId: this.form.communityId, | ||
| 198 | + meterType: this.form.meterType | ||
| 199 | + }) | ||
| 200 | + this.$message.success(this.$t('meterWater.downloadStarted')) | ||
| 201 | + } catch (error) { | ||
| 202 | + console.error('Failed to download template:', error) | ||
| 203 | + this.$message.error(error.message || this.$t('common.downloadFailed')) | ||
| 204 | + } | ||
| 205 | + }, | ||
| 206 | + async handleImport() { | ||
| 207 | + this.$refs.form.validate(async valid => { | ||
| 208 | + if (!valid) return | ||
| 209 | + | ||
| 210 | + if (!this.form.file) { | ||
| 211 | + this.$message.warning(this.$t('meterWater.fileRequired')) | ||
| 212 | + return | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + const formData = new FormData() | ||
| 216 | + formData.append('uploadFile', this.form.file) | ||
| 217 | + formData.append('communityId', this.form.communityId) | ||
| 218 | + formData.append('feeTypeCd', this.form.feeTypeCd) | ||
| 219 | + formData.append('configId', this.form.configId) | ||
| 220 | + formData.append('meterType', this.form.meterType) | ||
| 221 | + formData.append('importAdapt', 'importMeterWaterFee') | ||
| 222 | + | ||
| 223 | + try { | ||
| 224 | + const { data } = await importMeterWaterData(formData) | ||
| 225 | + this.$message.success(this.$t('meterWater.importSuccess')) | ||
| 226 | + this.dialogVisible = false | ||
| 227 | + this.$emit('success', data.logId) | ||
| 228 | + } catch (error) { | ||
| 229 | + console.error('Failed to import meter water data:', error) | ||
| 230 | + this.$message.error(error.message || this.$t('common.importFailed')) | ||
| 231 | + } | ||
| 232 | + }) | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | +} | ||
| 236 | +</script> | ||
| 0 | \ No newline at end of file | 237 | \ No newline at end of file |
src/components/fee/importMeterWaterFee2.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('meterWater.meterReadingImport2')" | ||
| 4 | + :visible.sync="dialogVisible" | ||
| 5 | + width="50%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-form | ||
| 9 | + ref="form" | ||
| 10 | + :model="form" | ||
| 11 | + :rules="rules" | ||
| 12 | + label-width="120px" | ||
| 13 | + label-position="right" | ||
| 14 | + > | ||
| 15 | + <el-form-item :label="$t('meterWater.feeType')" prop="feeTypeCd"> | ||
| 16 | + <el-select | ||
| 17 | + v-model="form.feeTypeCd" | ||
| 18 | + :placeholder="$t('meterWater.selectFeeType')" | ||
| 19 | + style="width: 100%" | ||
| 20 | + @change="handleFeeTypeChange" | ||
| 21 | + > | ||
| 22 | + <el-option | ||
| 23 | + v-for="item in feeTypeOptions" | ||
| 24 | + :key="item.value" | ||
| 25 | + :label="item.label" | ||
| 26 | + :value="item.value" | ||
| 27 | + /> | ||
| 28 | + </el-select> | ||
| 29 | + </el-form-item> | ||
| 30 | + | ||
| 31 | + <el-form-item :label="$t('meterWater.feeItem')" prop="configId"> | ||
| 32 | + <el-select | ||
| 33 | + v-model="form.configId" | ||
| 34 | + :placeholder="$t('meterWater.selectFeeItem')" | ||
| 35 | + style="width: 100%" | ||
| 36 | + > | ||
| 37 | + <el-option | ||
| 38 | + v-for="item in feeConfigs" | ||
| 39 | + :key="item.configId" | ||
| 40 | + :label="item.feeName" | ||
| 41 | + :value="item.configId" | ||
| 42 | + /> | ||
| 43 | + </el-select> | ||
| 44 | + </el-form-item> | ||
| 45 | + | ||
| 46 | + <el-form-item :label="$t('meterWater.meterType')" prop="meterType"> | ||
| 47 | + <el-select | ||
| 48 | + v-model="form.meterType" | ||
| 49 | + :placeholder="$t('meterWater.selectMeterType')" | ||
| 50 | + style="width: 100%" | ||
| 51 | + > | ||
| 52 | + <el-option | ||
| 53 | + v-for="item in meterTypes" | ||
| 54 | + :key="item.typeId" | ||
| 55 | + :label="item.typeName" | ||
| 56 | + :value="item.typeId" | ||
| 57 | + /> | ||
| 58 | + </el-select> | ||
| 59 | + </el-form-item> | ||
| 60 | + | ||
| 61 | + <el-form-item :label="$t('meterWater.selectFile')" prop="file"> | ||
| 62 | + <el-upload | ||
| 63 | + ref="upload" | ||
| 64 | + :auto-upload="false" | ||
| 65 | + :limit="1" | ||
| 66 | + :on-change="handleFileChange" | ||
| 67 | + :file-list="fileList" | ||
| 68 | + accept=".xls,.xlsx" | ||
| 69 | + > | ||
| 70 | + <el-button size="small" type="primary">{{ $t('meterWater.selectFile') }}</el-button> | ||
| 71 | + <div slot="tip" class="el-upload__tip"> | ||
| 72 | + {{ fileList.length > 0 ? fileList[0].name : $t('meterWater.fileRequired') }} | ||
| 73 | + </div> | ||
| 74 | + </el-upload> | ||
| 75 | + </el-form-item> | ||
| 76 | + | ||
| 77 | + <el-form-item :label="$t('meterWater.downloadTemplate')"> | ||
| 78 | + <div> | ||
| 79 | + {{ $t('meterWater.downloadTip') }} | ||
| 80 | + <el-link type="primary" @click="handleDownloadTemplate"> | ||
| 81 | + {{ $t('meterWater.importTemplate') }} | ||
| 82 | + </el-link> | ||
| 83 | + {{ $t('meterWater.prepareData') }} | ||
| 84 | + {{ $t('meterWater.dynamicFeeTip') }} | ||
| 85 | + </div> | ||
| 86 | + </el-form-item> | ||
| 87 | + </el-form> | ||
| 88 | + | ||
| 89 | + <span slot="footer" class="dialog-footer"> | ||
| 90 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 91 | + <el-button type="primary" @click="handleImport">{{ $t('common.import') }}</el-button> | ||
| 92 | + </span> | ||
| 93 | + </el-dialog> | ||
| 94 | +</template> | ||
| 95 | + | ||
| 96 | +<script> | ||
| 97 | +import { importMeterWaterData, exportMeterWaterTemplate } from '@/api/fee/meterWaterManageApi' | ||
| 98 | +import { listFeeConfigs, listMeterTypes } from '@/api/fee/meterWaterManageApi' | ||
| 99 | +import { getDict } from '@/api/community/communityApi' | ||
| 100 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 101 | + | ||
| 102 | +export default { | ||
| 103 | + name: 'ImportMeterWaterFee2', | ||
| 104 | + data() { | ||
| 105 | + return { | ||
| 106 | + dialogVisible: false, | ||
| 107 | + form: { | ||
| 108 | + feeTypeCd: '', | ||
| 109 | + configId: '', | ||
| 110 | + meterType: '', | ||
| 111 | + file: null, | ||
| 112 | + communityId: '' | ||
| 113 | + }, | ||
| 114 | + rules: { | ||
| 115 | + feeTypeCd: [ | ||
| 116 | + { required: true, message: this.$t('meterWater.feeTypeRequired'), trigger: 'blur' } | ||
| 117 | + ], | ||
| 118 | + configId: [ | ||
| 119 | + { required: true, message: this.$t('meterWater.feeItemRequired'), trigger: 'blur' } | ||
| 120 | + ], | ||
| 121 | + meterType: [ | ||
| 122 | + { required: true, message: this.$t('meterWater.meterTypeRequired'), trigger: 'blur' } | ||
| 123 | + ], | ||
| 124 | + file: [ | ||
| 125 | + { required: true, message: this.$t('meterWater.fileRequired'), trigger: 'blur' } | ||
| 126 | + ] | ||
| 127 | + }, | ||
| 128 | + feeTypeOptions: [], | ||
| 129 | + feeConfigs: [], | ||
| 130 | + meterTypes: [], | ||
| 131 | + fileList: [] | ||
| 132 | + } | ||
| 133 | + }, | ||
| 134 | + created() { | ||
| 135 | + this.form.communityId = getCommunityId() | ||
| 136 | + this.loadFeeTypes() | ||
| 137 | + this.loadMeterTypes() | ||
| 138 | + }, | ||
| 139 | + methods: { | ||
| 140 | + open() { | ||
| 141 | + this.dialogVisible = true | ||
| 142 | + }, | ||
| 143 | + handleClose() { | ||
| 144 | + this.$refs.form.resetFields() | ||
| 145 | + this.fileList = [] | ||
| 146 | + }, | ||
| 147 | + async loadFeeTypes() { | ||
| 148 | + try { | ||
| 149 | + const data = await getDict('pay_fee_config', 'fee_type_cd') | ||
| 150 | + this.feeTypeOptions = data.map(item => ({ | ||
| 151 | + value: item.value, | ||
| 152 | + label: item.label | ||
| 153 | + })) | ||
| 154 | + } catch (error) { | ||
| 155 | + console.error('Failed to load fee types:', error) | ||
| 156 | + } | ||
| 157 | + }, | ||
| 158 | + async loadMeterTypes() { | ||
| 159 | + try { | ||
| 160 | + const { data } = await listMeterTypes({ | ||
| 161 | + communityId: this.form.communityId, | ||
| 162 | + page: 1, | ||
| 163 | + row: 50 | ||
| 164 | + }) | ||
| 165 | + this.meterTypes = data | ||
| 166 | + } catch (error) { | ||
| 167 | + console.error('Failed to load meter types:', error) | ||
| 168 | + } | ||
| 169 | + }, | ||
| 170 | + async handleFeeTypeChange(feeTypeCd) { | ||
| 171 | + try { | ||
| 172 | + const { data } = await listFeeConfigs({ | ||
| 173 | + communityId: this.form.communityId, | ||
| 174 | + feeTypeCd, | ||
| 175 | + isDefault: 'F', | ||
| 176 | + valid: '1', | ||
| 177 | + page: 1, | ||
| 178 | + row: 20 | ||
| 179 | + }) | ||
| 180 | + this.feeConfigs = data | ||
| 181 | + this.form.configId = '' | ||
| 182 | + } catch (error) { | ||
| 183 | + console.error('Failed to load fee configs:', error) | ||
| 184 | + } | ||
| 185 | + }, | ||
| 186 | + handleFileChange(file) { | ||
| 187 | + this.form.file = file.raw | ||
| 188 | + this.fileList = [file] | ||
| 189 | + }, | ||
| 190 | + async handleDownloadTemplate() { | ||
| 191 | + if (!this.form.meterType) { | ||
| 192 | + this.$message.warning(this.$t('meterWater.selectMeterTypeFirst')) | ||
| 193 | + return | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + const meterType = this.meterTypes.find(item => item.typeId === this.form.meterType) | ||
| 197 | + const feeName = meterType ? meterType.typeName : '' | ||
| 198 | + | ||
| 199 | + try { | ||
| 200 | + await exportMeterWaterTemplate({ | ||
| 201 | + communityId: this.form.communityId, | ||
| 202 | + meterType: this.form.meterType, | ||
| 203 | + feeName, | ||
| 204 | + pagePath: 'exportMeterWater2' | ||
| 205 | + }) | ||
| 206 | + this.$message.success(this.$t('meterWater.downloadStarted')) | ||
| 207 | + } catch (error) { | ||
| 208 | + console.error('Failed to download template:', error) | ||
| 209 | + this.$message.error(error.message || this.$t('common.downloadFailed')) | ||
| 210 | + } | ||
| 211 | + }, | ||
| 212 | + async handleImport() { | ||
| 213 | + this.$refs.form.validate(async valid => { | ||
| 214 | + if (!valid) return | ||
| 215 | + | ||
| 216 | + if (!this.form.file) { | ||
| 217 | + this.$message.warning(this.$t('meterWater.fileRequired')) | ||
| 218 | + return | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + const formData = new FormData() | ||
| 222 | + formData.append('uploadFile', this.form.file) | ||
| 223 | + formData.append('communityId', this.form.communityId) | ||
| 224 | + formData.append('feeTypeCd', this.form.feeTypeCd) | ||
| 225 | + formData.append('configId', this.form.configId) | ||
| 226 | + formData.append('meterType', this.form.meterType) | ||
| 227 | + formData.append('importAdapt', 'importMeterWaterFee') | ||
| 228 | + formData.append('importMeterDynamic', 'true') | ||
| 229 | + | ||
| 230 | + try { | ||
| 231 | + const { data } = await importMeterWaterData(formData) | ||
| 232 | + this.$message.success(this.$t('meterWater.importSuccess')) | ||
| 233 | + this.dialogVisible = false | ||
| 234 | + this.$emit('success', data.logId) | ||
| 235 | + } catch (error) { | ||
| 236 | + console.error('Failed to import meter water data:', error) | ||
| 237 | + this.$message.error(error.message || this.$t('common.importFailed')) | ||
| 238 | + } | ||
| 239 | + }) | ||
| 240 | + } | ||
| 241 | + } | ||
| 242 | +} | ||
| 243 | +</script> | ||
| 0 | \ No newline at end of file | 244 | \ No newline at end of file |
src/components/fee/roomMeterQrcode.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-dialog | ||
| 3 | + :title="$t('meterWater.meterQRCode')" | ||
| 4 | + :visible.sync="dialogVisible" | ||
| 5 | + width="50%" | ||
| 6 | + @close="handleClose" | ||
| 7 | + > | ||
| 8 | + <el-form | ||
| 9 | + ref="form" | ||
| 10 | + :model="form" | ||
| 11 | + :rules="rules" | ||
| 12 | + label-width="120px" | ||
| 13 | + label-position="right" | ||
| 14 | + > | ||
| 15 | + <el-form-item :label="$t('meterWater.feeType')" prop="feeTypeCd"> | ||
| 16 | + <el-select | ||
| 17 | + v-model="form.feeTypeCd" | ||
| 18 | + :placeholder="$t('meterWater.selectFeeType')" | ||
| 19 | + style="width: 100%" | ||
| 20 | + @change="handleFeeTypeChange" | ||
| 21 | + > | ||
| 22 | + <el-option | ||
| 23 | + v-for="item in feeTypeOptions" | ||
| 24 | + :key="item.value" | ||
| 25 | + :label="item.label" | ||
| 26 | + :value="item.value" | ||
| 27 | + /> | ||
| 28 | + </el-select> | ||
| 29 | + </el-form-item> | ||
| 30 | + | ||
| 31 | + <el-form-item :label="$t('meterWater.feeItem')" prop="configId"> | ||
| 32 | + <el-select | ||
| 33 | + v-model="form.configId" | ||
| 34 | + :placeholder="$t('meterWater.selectFeeItem')" | ||
| 35 | + style="width: 100%" | ||
| 36 | + @change="generateQRCode" | ||
| 37 | + > | ||
| 38 | + <el-option | ||
| 39 | + v-for="item in feeConfigs" | ||
| 40 | + :key="item.configId" | ||
| 41 | + :label="item.feeName" | ||
| 42 | + :value="item.configId" | ||
| 43 | + /> | ||
| 44 | + </el-select> | ||
| 45 | + <div class="form-tip"> | ||
| 46 | + {{ $t('meterWater.feeItemTip') }} | ||
| 47 | + </div> | ||
| 48 | + </el-form-item> | ||
| 49 | + | ||
| 50 | + <el-form-item :label="$t('meterWater.meterType')" prop="meterType"> | ||
| 51 | + <el-select | ||
| 52 | + v-model="form.meterType" | ||
| 53 | + :placeholder="$t('meterWater.selectMeterType')" | ||
| 54 | + style="width: 100%" | ||
| 55 | + @change="generateQRCode" | ||
| 56 | + > | ||
| 57 | + <el-option | ||
| 58 | + v-for="item in meterTypes" | ||
| 59 | + :key="item.typeId" | ||
| 60 | + :label="item.typeName" | ||
| 61 | + :value="item.typeId" | ||
| 62 | + /> | ||
| 63 | + </el-select> | ||
| 64 | + </el-form-item> | ||
| 65 | + | ||
| 66 | + <el-form-item :label="$t('meterWater.room')"> | ||
| 67 | + <el-input | ||
| 68 | + v-model="form.ownerName" | ||
| 69 | + :placeholder="$t('meterWater.inputRoom')" | ||
| 70 | + disabled | ||
| 71 | + /> | ||
| 72 | + </el-form-item> | ||
| 73 | + | ||
| 74 | + <el-form-item v-if="showQRCode" :label="$t('meterWater.qrCode')"> | ||
| 75 | + <div class="qr-code-container"> | ||
| 76 | + <div id="qrCodeCanvas" class="qr-code-canvas"></div> | ||
| 77 | + <div class="qr-code-title"> | ||
| 78 | + {{ form.ownerName }}-{{ form.typeName }} | ||
| 79 | + </div> | ||
| 80 | + <div class="qr-code-tip"> | ||
| 81 | + {{ $t('meterWater.qrCodeTip') }} | ||
| 82 | + </div> | ||
| 83 | + </div> | ||
| 84 | + </el-form-item> | ||
| 85 | + </el-form> | ||
| 86 | + | ||
| 87 | + <span slot="footer" class="dialog-footer"> | ||
| 88 | + <el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button> | ||
| 89 | + </span> | ||
| 90 | + </el-dialog> | ||
| 91 | +</template> | ||
| 92 | + | ||
| 93 | +<script> | ||
| 94 | +import QRCode from 'qrcodejs2' | ||
| 95 | +import { listFeeConfigs, listMeterTypes } from '@/api/fee/meterWaterManageApi' | ||
| 96 | +import { getDict } from '@/api/community/communityApi' | ||
| 97 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 98 | + | ||
| 99 | +export default { | ||
| 100 | + name: 'RoomMeterQrcode', | ||
| 101 | + data() { | ||
| 102 | + return { | ||
| 103 | + dialogVisible: false, | ||
| 104 | + form: { | ||
| 105 | + feeTypeCd: '', | ||
| 106 | + configId: '', | ||
| 107 | + meterType: '', | ||
| 108 | + roomId: '', | ||
| 109 | + ownerName: '', | ||
| 110 | + typeName: '', | ||
| 111 | + communityId: '' | ||
| 112 | + }, | ||
| 113 | + rules: { | ||
| 114 | + feeTypeCd: [ | ||
| 115 | + { required: true, message: this.$t('meterWater.feeTypeRequired'), trigger: 'blur' } | ||
| 116 | + ], | ||
| 117 | + configId: [ | ||
| 118 | + { required: true, message: this.$t('meterWater.feeItemRequired'), trigger: 'blur' } | ||
| 119 | + ], | ||
| 120 | + meterType: [ | ||
| 121 | + { required: true, message: this.$t('meterWater.meterTypeRequired'), trigger: 'blur' } | ||
| 122 | + ] | ||
| 123 | + }, | ||
| 124 | + feeTypeOptions: [], | ||
| 125 | + feeConfigs: [], | ||
| 126 | + meterTypes: [], | ||
| 127 | + showQRCode: false, | ||
| 128 | + qrCode: null | ||
| 129 | + } | ||
| 130 | + }, | ||
| 131 | + created() { | ||
| 132 | + this.form.communityId = getCommunityId() | ||
| 133 | + this.loadFeeTypes() | ||
| 134 | + this.loadMeterTypes() | ||
| 135 | + }, | ||
| 136 | + methods: { | ||
| 137 | + open(params) { | ||
| 138 | + this.resetForm() | ||
| 139 | + if (params) { | ||
| 140 | + this.form.roomId = params.roomId | ||
| 141 | + this.form.ownerName = params.ownerName | ||
| 142 | + ? `${params.roomName}(${params.ownerName})` | ||
| 143 | + : params.roomName | ||
| 144 | + } | ||
| 145 | + this.dialogVisible = true | ||
| 146 | + }, | ||
| 147 | + resetForm() { | ||
| 148 | + this.form = { | ||
| 149 | + feeTypeCd: '', | ||
| 150 | + configId: '', | ||
| 151 | + meterType: '', | ||
| 152 | + roomId: '', | ||
| 153 | + ownerName: '', | ||
| 154 | + typeName: '', | ||
| 155 | + communityId: getCommunityId() | ||
| 156 | + } | ||
| 157 | + this.showQRCode = false | ||
| 158 | + this.$refs.form && this.$refs.form.resetFields() | ||
| 159 | + this.clearQRCode() | ||
| 160 | + }, | ||
| 161 | + handleClose() { | ||
| 162 | + this.resetForm() | ||
| 163 | + }, | ||
| 164 | + clearQRCode() { | ||
| 165 | + if (this.qrCode) { | ||
| 166 | + document.getElementById('qrCodeCanvas').innerHTML = '' | ||
| 167 | + this.qrCode = null | ||
| 168 | + } | ||
| 169 | + }, | ||
| 170 | + async loadFeeTypes() { | ||
| 171 | + try { | ||
| 172 | + const data = await getDict('pay_fee_config', 'fee_type_cd') | ||
| 173 | + this.feeTypeOptions = data.map(item => ({ | ||
| 174 | + value: item.value, | ||
| 175 | + label: item.label | ||
| 176 | + })) | ||
| 177 | + } catch (error) { | ||
| 178 | + console.error('Failed to load fee types:', error) | ||
| 179 | + } | ||
| 180 | + }, | ||
| 181 | + async loadMeterTypes() { | ||
| 182 | + try { | ||
| 183 | + const { data } = await listMeterTypes({ | ||
| 184 | + communityId: this.form.communityId, | ||
| 185 | + page: 1, | ||
| 186 | + row: 50 | ||
| 187 | + }) | ||
| 188 | + this.meterTypes = data | ||
| 189 | + } catch (error) { | ||
| 190 | + console.error('Failed to load meter types:', error) | ||
| 191 | + } | ||
| 192 | + }, | ||
| 193 | + async handleFeeTypeChange(feeTypeCd) { | ||
| 194 | + try { | ||
| 195 | + const { data } = await listFeeConfigs({ | ||
| 196 | + communityId: this.form.communityId, | ||
| 197 | + feeTypeCd, | ||
| 198 | + isDefault: 'F', | ||
| 199 | + valid: '1', | ||
| 200 | + page: 1, | ||
| 201 | + row: 20 | ||
| 202 | + }) | ||
| 203 | + this.feeConfigs = data | ||
| 204 | + this.form.configId = '' | ||
| 205 | + } catch (error) { | ||
| 206 | + console.error('Failed to load fee configs:', error) | ||
| 207 | + } | ||
| 208 | + }, | ||
| 209 | + generateQRCode() { | ||
| 210 | + if (!this.form.configId || !this.form.meterType || !this.form.roomId) { | ||
| 211 | + return | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + const meterType = this.meterTypes.find(item => item.typeId === this.form.meterType) | ||
| 215 | + this.form.typeName = meterType ? meterType.typeName : '' | ||
| 216 | + | ||
| 217 | + this.clearQRCode() | ||
| 218 | + | ||
| 219 | + const propertyUrl = this.$store.getters.systemInfo.propertyUrl | ||
| 220 | + const qrCodeUrl = `${propertyUrl}pages/meter/qrcodeMeter?configId=${this.form.configId}&meterType=${this.form.meterType}&roomId=${this.form.roomId}&communityId=${this.form.communityId}` | ||
| 221 | + | ||
| 222 | + this.qrCode = new QRCode(document.getElementById('qrCodeCanvas'), { | ||
| 223 | + text: qrCodeUrl, | ||
| 224 | + width: 200, | ||
| 225 | + height: 200, | ||
| 226 | + colorDark: '#000000', | ||
| 227 | + colorLight: '#ffffff', | ||
| 228 | + correctLevel: QRCode.CorrectLevel.L | ||
| 229 | + }) | ||
| 230 | + | ||
| 231 | + this.showQRCode = true | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | +} | ||
| 235 | +</script> | ||
| 236 | + | ||
| 237 | +<style lang="scss" scoped> | ||
| 238 | +.qr-code-container { | ||
| 239 | + display: flex; | ||
| 240 | + flex-direction: column; | ||
| 241 | + align-items: center; | ||
| 242 | + padding: 20px; | ||
| 243 | + | ||
| 244 | + .qr-code-canvas { | ||
| 245 | + margin-bottom: 10px; | ||
| 246 | + } | ||
| 247 | + | ||
| 248 | + .qr-code-title { | ||
| 249 | + font-size: 18px; | ||
| 250 | + font-weight: bold; | ||
| 251 | + margin-bottom: 10px; | ||
| 252 | + text-align: center; | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + .qr-code-tip { | ||
| 256 | + font-size: 14px; | ||
| 257 | + color: #666; | ||
| 258 | + text-align: center; | ||
| 259 | + max-width: 300px; | ||
| 260 | + } | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +.form-tip { | ||
| 264 | + font-size: 12px; | ||
| 265 | + color: #999; | ||
| 266 | + margin-top: 5px; | ||
| 267 | +} | ||
| 268 | +</style> | ||
| 0 | \ No newline at end of file | 269 | \ No newline at end of file |
src/components/fee/roomSelect2.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-select | ||
| 3 | + v-model="selectedRoom" | ||
| 4 | + :placeholder="$t('meterWater.selectRoom')" | ||
| 5 | + filterable | ||
| 6 | + clearable | ||
| 7 | + style="width: 100%" | ||
| 8 | + :disabled="!unitId" | ||
| 9 | + @change="handleChange" | ||
| 10 | + > | ||
| 11 | + <el-option | ||
| 12 | + v-for="item in rooms" | ||
| 13 | + :key="item.roomId" | ||
| 14 | + :label="getRoomLabel(item)" | ||
| 15 | + :value="item.roomId" | ||
| 16 | + /> | ||
| 17 | + </el-select> | ||
| 18 | +</template> | ||
| 19 | + | ||
| 20 | +<script> | ||
| 21 | +import { queryRooms } from '@/api/fee/meterWaterManageApi' | ||
| 22 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 23 | + | ||
| 24 | +export default { | ||
| 25 | + name: 'RoomSelect2', | ||
| 26 | + props: { | ||
| 27 | + value: { | ||
| 28 | + type: [String, Number], | ||
| 29 | + default: '' | ||
| 30 | + }, | ||
| 31 | + unitId: { | ||
| 32 | + type: [String, Number], | ||
| 33 | + default: '' | ||
| 34 | + } | ||
| 35 | + }, | ||
| 36 | + data() { | ||
| 37 | + return { | ||
| 38 | + rooms: [], | ||
| 39 | + selectedRoom: this.value, | ||
| 40 | + communityId: '' | ||
| 41 | + } | ||
| 42 | + }, | ||
| 43 | + created() { | ||
| 44 | + this.communityId = getCommunityId() | ||
| 45 | + }, | ||
| 46 | + watch: { | ||
| 47 | + unitId: { | ||
| 48 | + immediate: true, | ||
| 49 | + handler(newVal) { | ||
| 50 | + if (newVal) { | ||
| 51 | + this.loadRooms(newVal) | ||
| 52 | + } else { | ||
| 53 | + this.rooms = [] | ||
| 54 | + this.selectedRoom = '' | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + }, | ||
| 58 | + value(newVal) { | ||
| 59 | + this.selectedRoom = newVal | ||
| 60 | + } | ||
| 61 | + }, | ||
| 62 | + methods: { | ||
| 63 | + async loadRooms(unitId) { | ||
| 64 | + try { | ||
| 65 | + const { data } = await queryRooms({ | ||
| 66 | + unitId, | ||
| 67 | + communityId: this.communityId, | ||
| 68 | + page: 1, | ||
| 69 | + row: 200 | ||
| 70 | + }) | ||
| 71 | + this.rooms = data.rooms || [] | ||
| 72 | + } catch (error) { | ||
| 73 | + console.error('Failed to load rooms:', error) | ||
| 74 | + } | ||
| 75 | + }, | ||
| 76 | + getRoomLabel(room) { | ||
| 77 | + return room.ownerName | ||
| 78 | + ? `${room.roomNum}室(${room.ownerName})` | ||
| 79 | + : `${room.roomNum}室` | ||
| 80 | + }, | ||
| 81 | + handleChange(value) { | ||
| 82 | + const room = this.rooms.find(item => item.roomId === value) | ||
| 83 | + if (room) { | ||
| 84 | + this.$emit('change', { | ||
| 85 | + roomId: value, | ||
| 86 | + name: `${room.floorNum}-${room.unitNum}-${room.roomNum}`, | ||
| 87 | + link: this.getRoomLabel(room) | ||
| 88 | + }) | ||
| 89 | + } else { | ||
| 90 | + this.$emit('change', null) | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + } | ||
| 94 | +} | ||
| 95 | +</script> | ||
| 0 | \ No newline at end of file | 96 | \ No newline at end of file |
src/components/fee/roomTreeDiv.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="room-tree-container"> | ||
| 3 | + <el-tree ref="tree" :data="treeData" node-key="id" :props="defaultProps" :highlight-current="true" | ||
| 4 | + :expand-on-click-node="false" @node-click="handleNodeClick"> | ||
| 5 | + <span slot-scope="{ node, data }" class="custom-tree-node"> | ||
| 6 | + <span> | ||
| 7 | + <i :class="data.icon" style="margin-right: 5px"></i> | ||
| 8 | + {{ node.label }} | ||
| 9 | + </span> | ||
| 10 | + </span> | ||
| 11 | + </el-tree> | ||
| 12 | + </div> | ||
| 13 | +</template> | ||
| 14 | + | ||
| 15 | +<script> | ||
| 16 | +import { queryUnits, queryRoomsTree } from '@/api/fee/meterWaterManageApi' | ||
| 17 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 18 | + | ||
| 19 | +export default { | ||
| 20 | + name: 'RoomTreeDiv', | ||
| 21 | + data() { | ||
| 22 | + return { | ||
| 23 | + treeData: [], | ||
| 24 | + defaultProps: { | ||
| 25 | + children: 'children', | ||
| 26 | + label: 'text' | ||
| 27 | + }, | ||
| 28 | + communityId: '' | ||
| 29 | + } | ||
| 30 | + }, | ||
| 31 | + created() { | ||
| 32 | + this.communityId = getCommunityId() | ||
| 33 | + this.loadTreeData() | ||
| 34 | + }, | ||
| 35 | + methods: { | ||
| 36 | + async loadTreeData() { | ||
| 37 | + try { | ||
| 38 | + const units = await queryUnits({ | ||
| 39 | + communityId: this.communityId | ||
| 40 | + }) | ||
| 41 | + this.buildTreeData(units) | ||
| 42 | + } catch (error) { | ||
| 43 | + console.error('Failed to load tree data:', error) | ||
| 44 | + } | ||
| 45 | + }, | ||
| 46 | + buildTreeData(units) { | ||
| 47 | + const treeMap = new Map() | ||
| 48 | + | ||
| 49 | + // Build floor nodes | ||
| 50 | + units.forEach(unit => { | ||
| 51 | + if (!treeMap.has(unit.floorId)) { | ||
| 52 | + treeMap.set(unit.floorId, { | ||
| 53 | + id: `f_${unit.floorId}`, | ||
| 54 | + floorId: unit.floorId, | ||
| 55 | + floorNum: unit.floorNum, | ||
| 56 | + icon: 'el-icon-office-building', | ||
| 57 | + text: `${unit.floorNum}栋`, | ||
| 58 | + children: [] | ||
| 59 | + }) | ||
| 60 | + } | ||
| 61 | + }) | ||
| 62 | + | ||
| 63 | + // Build unit nodes | ||
| 64 | + units.forEach(unit => { | ||
| 65 | + const floorNode = treeMap.get(unit.floorId) | ||
| 66 | + if (floorNode) { | ||
| 67 | + floorNode.children.push({ | ||
| 68 | + id: `u_${unit.unitId}`, | ||
| 69 | + unitId: unit.unitId, | ||
| 70 | + unitNum: unit.unitNum, | ||
| 71 | + icon: 'el-icon-connection', | ||
| 72 | + text: `${unit.unitNum}单元`, | ||
| 73 | + children: [] | ||
| 74 | + }) | ||
| 75 | + } | ||
| 76 | + }) | ||
| 77 | + | ||
| 78 | + this.treeData = Array.from(treeMap.values()) | ||
| 79 | + }, | ||
| 80 | + async handleNodeClick(data) { | ||
| 81 | + if (data.id.startsWith('u_')) { | ||
| 82 | + await this.loadRooms(data) | ||
| 83 | + } else if (data.id.startsWith('r_')) { | ||
| 84 | + this.$emit('selectRoom', { | ||
| 85 | + roomId: data.roomId, | ||
| 86 | + roomName: data.roomName | ||
| 87 | + }) | ||
| 88 | + } | ||
| 89 | + }, | ||
| 90 | + async loadRooms(node) { | ||
| 91 | + try { | ||
| 92 | + const {rooms} = await queryRoomsTree({ | ||
| 93 | + unitId: node.unitId, | ||
| 94 | + communityId: this.communityId, | ||
| 95 | + page: 1, | ||
| 96 | + row: 1000 | ||
| 97 | + }) | ||
| 98 | + | ||
| 99 | + const roomNodes = rooms.map(room => ({ | ||
| 100 | + id: `r_${room.roomId}`, | ||
| 101 | + roomId: room.roomId, | ||
| 102 | + roomName: `${room.floorNum}-${room.unitNum}-${room.roomNum}`, | ||
| 103 | + icon: 'el-icon-house', | ||
| 104 | + text: room.ownerName | ||
| 105 | + ? `${room.roomNum}室(${room.ownerName})` | ||
| 106 | + : `${room.roomNum}室` | ||
| 107 | + })) | ||
| 108 | + | ||
| 109 | + // Update tree data | ||
| 110 | + const floorNode = this.treeData.find( | ||
| 111 | + item => item.id === `f_${node.floorId}` | ||
| 112 | + ) | ||
| 113 | + if (floorNode) { | ||
| 114 | + const unitNode = floorNode.children.find( | ||
| 115 | + item => item.id === `u_${node.unitId}` | ||
| 116 | + ) | ||
| 117 | + if (unitNode) { | ||
| 118 | + this.$set(unitNode, 'children', roomNodes) | ||
| 119 | + this.$refs.tree.updateKeyChildren(unitNode.id, roomNodes) | ||
| 120 | + | ||
| 121 | + // Auto select first room | ||
| 122 | + if (roomNodes.length > 0) { | ||
| 123 | + this.$emit('selectRoom', { | ||
| 124 | + roomId: roomNodes[0].roomId, | ||
| 125 | + roomName: roomNodes[0].roomName | ||
| 126 | + }) | ||
| 127 | + } | ||
| 128 | + } | ||
| 129 | + } | ||
| 130 | + } catch (error) { | ||
| 131 | + console.error('Failed to load rooms:', error) | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | +} | ||
| 136 | +</script> | ||
| 137 | + | ||
| 138 | +<style lang="scss" scoped> | ||
| 139 | +.room-tree-container { | ||
| 140 | + height: 100%; | ||
| 141 | + overflow-y: auto; | ||
| 142 | + padding: 10px; | ||
| 143 | + | ||
| 144 | + .custom-tree-node { | ||
| 145 | + flex: 1; | ||
| 146 | + display: flex; | ||
| 147 | + align-items: center; | ||
| 148 | + font-size: 14px; | ||
| 149 | + padding: 5px 0; | ||
| 150 | + } | ||
| 151 | +} | ||
| 152 | +</style> | ||
| 0 | \ No newline at end of file | 153 | \ No newline at end of file |
src/components/fee/unitSelect2.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <el-select | ||
| 3 | + v-model="selectedUnit" | ||
| 4 | + :placeholder="$t('meterWater.selectUnit')" | ||
| 5 | + filterable | ||
| 6 | + clearable | ||
| 7 | + style="width: 100%" | ||
| 8 | + :disabled="!floorId" | ||
| 9 | + @change="handleChange" | ||
| 10 | + > | ||
| 11 | + <el-option | ||
| 12 | + v-for="item in units" | ||
| 13 | + :key="item.unitId" | ||
| 14 | + :label="`${item.unitNum}单元`" | ||
| 15 | + :value="item.unitId" | ||
| 16 | + /> | ||
| 17 | + </el-select> | ||
| 18 | +</template> | ||
| 19 | + | ||
| 20 | +<script> | ||
| 21 | +import { queryUnits } from '@/api/fee/meterWaterManageApi' | ||
| 22 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 23 | + | ||
| 24 | +export default { | ||
| 25 | + name: 'UnitSelect2', | ||
| 26 | + props: { | ||
| 27 | + value: { | ||
| 28 | + type: [String, Number], | ||
| 29 | + default: '' | ||
| 30 | + }, | ||
| 31 | + floorId: { | ||
| 32 | + type: [String, Number], | ||
| 33 | + default: '' | ||
| 34 | + } | ||
| 35 | + }, | ||
| 36 | + data() { | ||
| 37 | + return { | ||
| 38 | + units: [], | ||
| 39 | + selectedUnit: this.value, | ||
| 40 | + communityId: '' | ||
| 41 | + } | ||
| 42 | + }, | ||
| 43 | + created() { | ||
| 44 | + this.communityId = getCommunityId() | ||
| 45 | + }, | ||
| 46 | + watch: { | ||
| 47 | + floorId: { | ||
| 48 | + immediate: true, | ||
| 49 | + handler(newVal) { | ||
| 50 | + if (newVal) { | ||
| 51 | + this.loadUnits(newVal) | ||
| 52 | + } else { | ||
| 53 | + this.units = [] | ||
| 54 | + this.selectedUnit = '' | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + }, | ||
| 58 | + value(newVal) { | ||
| 59 | + this.selectedUnit = newVal | ||
| 60 | + } | ||
| 61 | + }, | ||
| 62 | + methods: { | ||
| 63 | + async loadUnits(floorId) { | ||
| 64 | + try { | ||
| 65 | + const { data } = await queryUnits({ | ||
| 66 | + floorId, | ||
| 67 | + communityId: this.communityId, | ||
| 68 | + page: 1, | ||
| 69 | + row: 50 | ||
| 70 | + }) | ||
| 71 | + this.units = data | ||
| 72 | + } catch (error) { | ||
| 73 | + console.error('Failed to load units:', error) | ||
| 74 | + } | ||
| 75 | + }, | ||
| 76 | + handleChange(value) { | ||
| 77 | + const unit = this.units.find(item => item.unitId === value) | ||
| 78 | + this.$emit('change', { | ||
| 79 | + unitId: value, | ||
| 80 | + unitNum: unit ? unit.unitNum : '' | ||
| 81 | + }) | ||
| 82 | + } | ||
| 83 | + } | ||
| 84 | +} | ||
| 85 | +</script> | ||
| 0 | \ No newline at end of file | 86 | \ No newline at end of file |
src/i18n/feeI18n.js
0 → 100644
| 1 | +import { messages as contractCreateFeeMessages } from '../views/fee/contractCreateFeeLang' | ||
| 2 | +import { messages as meterWaterManageMessages } from '../views/fee/meterWaterManageLang' | ||
| 3 | +export const messages = { | ||
| 4 | + en: { | ||
| 5 | + ...contractCreateFeeMessages.en, | ||
| 6 | + ...meterWaterManageMessages.en, | ||
| 7 | + }, | ||
| 8 | + zh: { | ||
| 9 | + ...contractCreateFeeMessages.zh, | ||
| 10 | + ...meterWaterManageMessages.zh, | ||
| 11 | + } | ||
| 12 | +} | ||
| 0 | \ No newline at end of file | 13 | \ No newline at end of file |
src/i18n/index.js
| @@ -146,6 +146,7 @@ import { messages as userI18n } from './userI18n' | @@ -146,6 +146,7 @@ import { messages as userI18n } from './userI18n' | ||
| 146 | import { messages as systemI18n } from './systemI18n' | 146 | import { messages as systemI18n } from './systemI18n' |
| 147 | import { messages as communityI18n } from './communityI18n' | 147 | import { messages as communityI18n } from './communityI18n' |
| 148 | import { messages as workI18n } from './workI18n' | 148 | import { messages as workI18n } from './workI18n' |
| 149 | +import { messages as feeI18n } from './feeI18n' | ||
| 149 | 150 | ||
| 150 | Vue.use(VueI18n) | 151 | Vue.use(VueI18n) |
| 151 | 152 | ||
| @@ -290,6 +291,7 @@ const messages = { | @@ -290,6 +291,7 @@ const messages = { | ||
| 290 | ...systemI18n.en, | 291 | ...systemI18n.en, |
| 291 | ...communityI18n.en, | 292 | ...communityI18n.en, |
| 292 | ...workI18n.en, | 293 | ...workI18n.en, |
| 294 | + ...feeI18n.en, | ||
| 293 | }, | 295 | }, |
| 294 | zh: { | 296 | zh: { |
| 295 | ...loginMessages.zh, | 297 | ...loginMessages.zh, |
| @@ -428,6 +430,7 @@ const messages = { | @@ -428,6 +430,7 @@ const messages = { | ||
| 428 | ...systemI18n.zh, | 430 | ...systemI18n.zh, |
| 429 | ...communityI18n.zh, | 431 | ...communityI18n.zh, |
| 430 | ...workI18n.zh, | 432 | ...workI18n.zh, |
| 433 | + ...feeI18n.zh, | ||
| 431 | } | 434 | } |
| 432 | } | 435 | } |
| 433 | 436 |
src/router/feeRouter.js
0 → 100644
| 1 | +export default [ | ||
| 2 | + { | ||
| 3 | + path:'/pages/property/contractCreateFee', | ||
| 4 | + name:'/pages/property/contractCreateFee', | ||
| 5 | + component: () => import('@/views/fee/contractCreateFeeList.vue') | ||
| 6 | + }, | ||
| 7 | + { | ||
| 8 | + path:'/pages/property/meterWaterManage', | ||
| 9 | + name:'/pages/property/meterWaterManage', | ||
| 10 | + component: () => import('@/views/fee/meterWaterManageList.vue') | ||
| 11 | + }, | ||
| 12 | +] | ||
| 0 | \ No newline at end of file | 13 | \ No newline at end of file |
src/router/index.js
| @@ -16,6 +16,7 @@ import userRouter from './userRouter' | @@ -16,6 +16,7 @@ import userRouter from './userRouter' | ||
| 16 | import systemRouter from './systemRouter' | 16 | import systemRouter from './systemRouter' |
| 17 | import communityRouter from './communityRouter' | 17 | import communityRouter from './communityRouter' |
| 18 | import workRouter from './workRouter' | 18 | import workRouter from './workRouter' |
| 19 | +import feeRouter from './feeRouter' | ||
| 19 | 20 | ||
| 20 | Vue.use(VueRouter) | 21 | Vue.use(VueRouter) |
| 21 | 22 | ||
| @@ -640,6 +641,7 @@ const routes = [ | @@ -640,6 +641,7 @@ const routes = [ | ||
| 640 | ...systemRouter, | 641 | ...systemRouter, |
| 641 | ...communityRouter, | 642 | ...communityRouter, |
| 642 | ...workRouter, | 643 | ...workRouter, |
| 644 | + ...feeRouter, | ||
| 643 | // 其他子路由可以在这里添加 | 645 | // 其他子路由可以在这里添加 |
| 644 | ] | 646 | ] |
| 645 | }, | 647 | }, |
src/views/fee/contractCreateFeeLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en: { | ||
| 3 | + contractCreateFee: { | ||
| 4 | + search: { | ||
| 5 | + title: 'Search Conditions', | ||
| 6 | + contractCode: 'Contract Code', | ||
| 7 | + contractNameLike: 'Contract Name', | ||
| 8 | + contractType: 'Contract Type' | ||
| 9 | + }, | ||
| 10 | + list: { | ||
| 11 | + title: 'Contract Information', | ||
| 12 | + batchCreate: 'Batch Create' | ||
| 13 | + }, | ||
| 14 | + table: { | ||
| 15 | + contractCode: 'Contract Code', | ||
| 16 | + parentContractCode: 'Parent Contract Code', | ||
| 17 | + contractName: 'Contract Name', | ||
| 18 | + contractType: 'Contract Type', | ||
| 19 | + partyB: 'Party B', | ||
| 20 | + amount: 'Amount', | ||
| 21 | + startTime: 'Start Time', | ||
| 22 | + endTime: 'End Time', | ||
| 23 | + operation: 'Operation', | ||
| 24 | + payFee: 'Pay Fee', | ||
| 25 | + viewDetail: 'Detail', | ||
| 26 | + viewFee: 'View Fee' | ||
| 27 | + }, | ||
| 28 | + fetchError: 'Failed to fetch contract data' | ||
| 29 | + }, | ||
| 30 | + contractCreateFeeAdd: { | ||
| 31 | + title: 'Create Fee', | ||
| 32 | + feeTypeCd: 'Fee Type', | ||
| 33 | + configId: 'Fee Item', | ||
| 34 | + amount: 'Amount', | ||
| 35 | + contractState: 'Contract State', | ||
| 36 | + startTime: 'Start Time', | ||
| 37 | + endTime: 'End Time', | ||
| 38 | + selectFeeType: 'Please select fee type', | ||
| 39 | + selectFeeConfig: 'Please select fee item', | ||
| 40 | + inputAmount: 'Please input amount', | ||
| 41 | + selectStartTime: 'Select start time', | ||
| 42 | + selectEndTime: 'Select end time', | ||
| 43 | + state2001: 'Pending Review', | ||
| 44 | + state2003: 'Under Review', | ||
| 45 | + state2005: 'Review Completed', | ||
| 46 | + feeTypeRequired: 'Fee type is required', | ||
| 47 | + configRequired: 'Fee item is required', | ||
| 48 | + amountRequired: 'Amount is required', | ||
| 49 | + startTimeRequired: 'Start time is required', | ||
| 50 | + endTimeRequired: 'End time is required', | ||
| 51 | + timeError: 'Start time must be earlier than end time', | ||
| 52 | + success: 'Successfully created fees. Total: {total}, Success: {success}, Failed: {error}', | ||
| 53 | + note: 'Note: Batch created fees will be displayed in the contract fee view' | ||
| 54 | + } | ||
| 55 | + }, | ||
| 56 | + zh: { | ||
| 57 | + contractCreateFee: { | ||
| 58 | + search: { | ||
| 59 | + title: '查询条件', | ||
| 60 | + contractCode: '合同编号', | ||
| 61 | + contractNameLike: '合同名称', | ||
| 62 | + contractType: '合同类型' | ||
| 63 | + }, | ||
| 64 | + list: { | ||
| 65 | + title: '合同信息', | ||
| 66 | + batchCreate: '批量创建' | ||
| 67 | + }, | ||
| 68 | + table: { | ||
| 69 | + contractCode: '合同编号', | ||
| 70 | + parentContractCode: '父合同编号', | ||
| 71 | + contractName: '合同名称', | ||
| 72 | + contractType: '合同类型', | ||
| 73 | + partyB: '乙方', | ||
| 74 | + amount: '合同金额', | ||
| 75 | + startTime: '开始时间', | ||
| 76 | + endTime: '结束时间', | ||
| 77 | + operation: '操作', | ||
| 78 | + payFee: '欠费缴费', | ||
| 79 | + viewDetail: '详情', | ||
| 80 | + viewFee: '查看费用' | ||
| 81 | + }, | ||
| 82 | + fetchError: '获取合同数据失败' | ||
| 83 | + }, | ||
| 84 | + contractCreateFeeAdd: { | ||
| 85 | + title: '创建费用', | ||
| 86 | + feeTypeCd: '费用类型', | ||
| 87 | + configId: '收费项目', | ||
| 88 | + amount: '收费金额', | ||
| 89 | + contractState: '合同状态', | ||
| 90 | + startTime: '计费起始时间', | ||
| 91 | + endTime: '计费结束时间', | ||
| 92 | + selectFeeType: '请选择费用类型', | ||
| 93 | + selectFeeConfig: '请选择收费项目', | ||
| 94 | + inputAmount: '请填写收费金额', | ||
| 95 | + selectStartTime: '请填写计费起始时间', | ||
| 96 | + selectEndTime: '请填写计费结束时间', | ||
| 97 | + state2001: '待审核', | ||
| 98 | + state2003: '审核中', | ||
| 99 | + state2005: '审核完成', | ||
| 100 | + feeTypeRequired: '费用类型不能为空', | ||
| 101 | + configRequired: '费用项目不能为空', | ||
| 102 | + amountRequired: '收费金额不能为空', | ||
| 103 | + startTimeRequired: '计费起始时间不能为空', | ||
| 104 | + endTimeRequired: '计费结束时间不能为空', | ||
| 105 | + timeError: '计费起始时间必须小于计费终止时间', | ||
| 106 | + success: '创建收费成功,总共[{total}]合同,成功[{success}],失败[{error}]', | ||
| 107 | + note: '注:批量创建的费用在合同收费的查看费用中显示' | ||
| 108 | + } | ||
| 109 | + } | ||
| 110 | +} | ||
| 0 | \ No newline at end of file | 111 | \ No newline at end of file |
src/views/fee/contractCreateFeeList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="contract-create-fee-container"> | ||
| 3 | + <!-- 查询条件 --> | ||
| 4 | + <el-card class="search-wrapper"> | ||
| 5 | + <div slot="header" class="flex justify-between"> | ||
| 6 | + <span>{{ $t('contractCreateFee.search.title') }}</span> | ||
| 7 | + </div> | ||
| 8 | + <el-row :gutter="20"> | ||
| 9 | + <el-col :span="6"> | ||
| 10 | + <el-input | ||
| 11 | + v-model.trim="searchForm.contractCode" | ||
| 12 | + :placeholder="$t('contractCreateFee.search.contractCode')" | ||
| 13 | + clearable | ||
| 14 | + /> | ||
| 15 | + </el-col> | ||
| 16 | + <el-col :span="6"> | ||
| 17 | + <el-input | ||
| 18 | + v-model.trim="searchForm.contractNameLike" | ||
| 19 | + :placeholder="$t('contractCreateFee.search.contractNameLike')" | ||
| 20 | + clearable | ||
| 21 | + /> | ||
| 22 | + </el-col> | ||
| 23 | + <el-col :span="6"> | ||
| 24 | + <el-input | ||
| 25 | + v-model.trim="searchForm.contractType" | ||
| 26 | + :placeholder="$t('contractCreateFee.search.contractType')" | ||
| 27 | + clearable | ||
| 28 | + /> | ||
| 29 | + </el-col> | ||
| 30 | + <el-col :span="6"> | ||
| 31 | + <el-button type="primary" @click="handleSearch"> | ||
| 32 | + <i class="el-icon-search"></i> | ||
| 33 | + {{ $t('common.search') }} | ||
| 34 | + </el-button> | ||
| 35 | + <el-button @click="handleReset"> | ||
| 36 | + <i class="el-icon-refresh"></i> | ||
| 37 | + {{ $t('common.reset') }} | ||
| 38 | + </el-button> | ||
| 39 | + </el-col> | ||
| 40 | + </el-row> | ||
| 41 | + </el-card> | ||
| 42 | + | ||
| 43 | + <!-- 合同信息 --> | ||
| 44 | + <el-card class="list-wrapper"> | ||
| 45 | + <div slot="header" class="flex justify-between"> | ||
| 46 | + <span>{{ $t('contractCreateFee.list.title') }}</span> | ||
| 47 | + <el-button | ||
| 48 | + type="primary" | ||
| 49 | + size="small" | ||
| 50 | + @click="handleBatchCreate" | ||
| 51 | + > | ||
| 52 | + <i class="el-icon-plus"></i> | ||
| 53 | + {{ $t('contractCreateFee.list.batchCreate') }} | ||
| 54 | + </el-button> | ||
| 55 | + </div> | ||
| 56 | + | ||
| 57 | + <el-table | ||
| 58 | + v-loading="loading" | ||
| 59 | + :data="tableData" | ||
| 60 | + border | ||
| 61 | + style="width: 100%" | ||
| 62 | + > | ||
| 63 | + <el-table-column | ||
| 64 | + prop="contractCode" | ||
| 65 | + :label="$t('contractCreateFee.table.contractCode')" | ||
| 66 | + align="center" | ||
| 67 | + /> | ||
| 68 | + <el-table-column | ||
| 69 | + prop="parentContractCode" | ||
| 70 | + :label="$t('contractCreateFee.table.parentContractCode')" | ||
| 71 | + align="center" | ||
| 72 | + > | ||
| 73 | + <template slot-scope="scope"> | ||
| 74 | + {{ scope.row.parentContractCode || '-' }} | ||
| 75 | + </template> | ||
| 76 | + </el-table-column> | ||
| 77 | + <el-table-column | ||
| 78 | + prop="contractName" | ||
| 79 | + :label="$t('contractCreateFee.table.contractName')" | ||
| 80 | + align="center" | ||
| 81 | + /> | ||
| 82 | + <el-table-column | ||
| 83 | + prop="contractTypeName" | ||
| 84 | + :label="$t('contractCreateFee.table.contractType')" | ||
| 85 | + align="center" | ||
| 86 | + /> | ||
| 87 | + <el-table-column | ||
| 88 | + prop="bContacts" | ||
| 89 | + :label="$t('contractCreateFee.table.partyB')" | ||
| 90 | + align="center" | ||
| 91 | + /> | ||
| 92 | + <el-table-column | ||
| 93 | + prop="amount" | ||
| 94 | + :label="$t('contractCreateFee.table.amount')" | ||
| 95 | + align="center" | ||
| 96 | + /> | ||
| 97 | + <el-table-column | ||
| 98 | + prop="startTime" | ||
| 99 | + :label="$t('contractCreateFee.table.startTime')" | ||
| 100 | + align="center" | ||
| 101 | + /> | ||
| 102 | + <el-table-column | ||
| 103 | + prop="endTime" | ||
| 104 | + :label="$t('contractCreateFee.table.endTime')" | ||
| 105 | + align="center" | ||
| 106 | + /> | ||
| 107 | + <el-table-column | ||
| 108 | + :label="$t('common.operation')" | ||
| 109 | + align="center" | ||
| 110 | + width="220" | ||
| 111 | + > | ||
| 112 | + <template slot-scope="scope"> | ||
| 113 | + <el-button | ||
| 114 | + v-if="scope.row.state !== '2002'" | ||
| 115 | + size="mini" | ||
| 116 | + @click="handlePayFee(scope.row)" | ||
| 117 | + > | ||
| 118 | + {{ $t('contractCreateFee.table.payFee') }} | ||
| 119 | + </el-button> | ||
| 120 | + <el-button size="mini" @click="handleViewDetail(scope.row)"> | ||
| 121 | + {{ $t('contractCreateFee.table.viewDetail') }} | ||
| 122 | + </el-button> | ||
| 123 | + <el-button size="mini" @click="handleViewFee(scope.row)"> | ||
| 124 | + {{ $t('contractCreateFee.table.viewFee') }} | ||
| 125 | + </el-button> | ||
| 126 | + </template> | ||
| 127 | + </el-table-column> | ||
| 128 | + </el-table> | ||
| 129 | + | ||
| 130 | + <el-pagination | ||
| 131 | + :current-page.sync="pagination.current" | ||
| 132 | + :page-sizes="[10, 20, 30, 50]" | ||
| 133 | + :page-size="pagination.size" | ||
| 134 | + :total="pagination.total" | ||
| 135 | + layout="total, sizes, prev, pager, next, jumper" | ||
| 136 | + @size-change="handleSizeChange" | ||
| 137 | + @current-change="handleCurrentChange" | ||
| 138 | + /> | ||
| 139 | + </el-card> | ||
| 140 | + | ||
| 141 | + <!-- 创建费用弹窗 --> | ||
| 142 | + <contract-create-fee-add ref="createFeeAdd" @success="handleSuccess" /> | ||
| 143 | + </div> | ||
| 144 | +</template> | ||
| 145 | + | ||
| 146 | +<script> | ||
| 147 | +import { queryContract } from '@/api/fee/contractCreateFeeApi' | ||
| 148 | +import ContractCreateFeeAdd from '@/components/fee/contractCreateFeeAdd' | ||
| 149 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 150 | + | ||
| 151 | +export default { | ||
| 152 | + name: 'ContractCreateFeeList', | ||
| 153 | + components: { | ||
| 154 | + ContractCreateFeeAdd | ||
| 155 | + }, | ||
| 156 | + data() { | ||
| 157 | + return { | ||
| 158 | + loading: false, | ||
| 159 | + searchForm: { | ||
| 160 | + contractCode: '', | ||
| 161 | + contractNameLike: '', | ||
| 162 | + contractType: '' | ||
| 163 | + }, | ||
| 164 | + tableData: [], | ||
| 165 | + pagination: { | ||
| 166 | + current: 1, | ||
| 167 | + size: 10, | ||
| 168 | + total: 0 | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | + }, | ||
| 172 | + created() { | ||
| 173 | + this.getList() | ||
| 174 | + }, | ||
| 175 | + methods: { | ||
| 176 | + async getList() { | ||
| 177 | + try { | ||
| 178 | + this.loading = true | ||
| 179 | + const params = { | ||
| 180 | + page: this.pagination.current, | ||
| 181 | + row: this.pagination.size, | ||
| 182 | + ...this.searchForm, | ||
| 183 | + communityId: getCommunityId() | ||
| 184 | + } | ||
| 185 | + const { data, total } = await queryContract(params) | ||
| 186 | + this.tableData = data | ||
| 187 | + this.pagination.total = total | ||
| 188 | + } catch (error) { | ||
| 189 | + this.$message.error(this.$t('contractCreateFee.fetchError')) | ||
| 190 | + } finally { | ||
| 191 | + this.loading = false | ||
| 192 | + } | ||
| 193 | + }, | ||
| 194 | + handleSearch() { | ||
| 195 | + this.pagination.current = 1 | ||
| 196 | + this.getList() | ||
| 197 | + }, | ||
| 198 | + handleReset() { | ||
| 199 | + this.searchForm = { | ||
| 200 | + contractCode: '', | ||
| 201 | + contractNameLike: '', | ||
| 202 | + contractType: '' | ||
| 203 | + } | ||
| 204 | + this.handleSearch() | ||
| 205 | + }, | ||
| 206 | + handleSizeChange(val) { | ||
| 207 | + this.pagination.size = val | ||
| 208 | + this.getList() | ||
| 209 | + }, | ||
| 210 | + handleCurrentChange(val) { | ||
| 211 | + this.pagination.current = val | ||
| 212 | + this.getList() | ||
| 213 | + }, | ||
| 214 | + handleBatchCreate() { | ||
| 215 | + this.$refs.createFeeAdd.open(null, true) | ||
| 216 | + }, | ||
| 217 | + handlePayFee(row) { | ||
| 218 | + this.$router.push({ | ||
| 219 | + path: '/property/owePayFeeOrder', | ||
| 220 | + query: { | ||
| 221 | + payObjId: row.contractId, | ||
| 222 | + payObjType: '7777', | ||
| 223 | + contractName: row.contractName | ||
| 224 | + } | ||
| 225 | + }) | ||
| 226 | + }, | ||
| 227 | + handleViewDetail(row) { | ||
| 228 | + this.$router.push({ | ||
| 229 | + path: '/common/contractApplyDetail', | ||
| 230 | + query: { contractId: row.contractId } | ||
| 231 | + }) | ||
| 232 | + }, | ||
| 233 | + handleViewFee(row) { | ||
| 234 | + this.$router.push({ | ||
| 235 | + path: '/contract/contractDetail', | ||
| 236 | + query: { | ||
| 237 | + contractId: row.contractId, | ||
| 238 | + contractCode: row.contractCode | ||
| 239 | + } | ||
| 240 | + }) | ||
| 241 | + }, | ||
| 242 | + handleSuccess() { | ||
| 243 | + this.getList() | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | +} | ||
| 247 | +</script> | ||
| 248 | + | ||
| 249 | +<style lang="scss" scoped> | ||
| 250 | +.contract-create-fee-container { | ||
| 251 | + padding: 20px; | ||
| 252 | + | ||
| 253 | + .search-wrapper { | ||
| 254 | + margin-bottom: 20px; | ||
| 255 | + | ||
| 256 | + .el-row { | ||
| 257 | + margin-bottom: -20px; | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + .el-col { | ||
| 261 | + margin-bottom: 20px; | ||
| 262 | + } | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + .list-wrapper { | ||
| 266 | + .el-pagination { | ||
| 267 | + margin-top: 20px; | ||
| 268 | + text-align: right; | ||
| 269 | + } | ||
| 270 | + } | ||
| 271 | +} | ||
| 272 | +</style> | ||
| 0 | \ No newline at end of file | 273 | \ No newline at end of file |
src/views/fee/meterWaterManageLang.js
0 → 100644
| 1 | +export const messages = { | ||
| 2 | + en:{ | ||
| 3 | + meterWater: { | ||
| 4 | + queryConditions: 'Query Conditions', | ||
| 5 | + meterReadingInfo: 'Meter Reading Info', | ||
| 6 | + meterId: 'Meter ID', | ||
| 7 | + meterType: 'Meter Type', | ||
| 8 | + objectName: 'Object Name', | ||
| 9 | + preDegrees: 'Previous Degrees', | ||
| 10 | + curDegrees: 'Current Degrees', | ||
| 11 | + preReadingTime: 'Previous Reading Time', | ||
| 12 | + curReadingTime: 'Current Reading Time', | ||
| 13 | + createTime: 'Create Time', | ||
| 14 | + operation: 'Operation', | ||
| 15 | + readMeter: 'Read Meter', | ||
| 16 | + qrCodeMeter: 'QR Code Meter', | ||
| 17 | + import1: 'Import 1', | ||
| 18 | + import2: 'Import 2', | ||
| 19 | + selectMeterType: 'Please select meter type', | ||
| 20 | + inputMeterId: 'Please input meter ID', | ||
| 21 | + query: 'Query', | ||
| 22 | + reset: 'Reset', | ||
| 23 | + addMeterReading: 'Add Meter Reading', | ||
| 24 | + editMeterReading: 'Edit Meter Reading', | ||
| 25 | + confirmOperation: 'Please confirm your operation', | ||
| 26 | + confirmDeleteMeterReading: 'Confirm to delete meter reading', | ||
| 27 | + cancel: 'Cancel', | ||
| 28 | + confirmDelete: 'Confirm Delete', | ||
| 29 | + meterReadingImport: 'Meter Reading Import', | ||
| 30 | + meterReadingImport2: 'Meter Reading Import 2', | ||
| 31 | + feeType: 'Fee Type', | ||
| 32 | + selectFeeType: 'Please select fee type', | ||
| 33 | + feeItem: 'Fee Item', | ||
| 34 | + selectFeeItem: 'Please select fee item', | ||
| 35 | + feeItemTip: 'Note: Fee items with formula [(Current Degrees - Previous Degrees) * Price + Additional Fee]', | ||
| 36 | + meterTypeRequired: 'Please select meter type', | ||
| 37 | + building: 'Building', | ||
| 38 | + unit: 'Unit', | ||
| 39 | + room: 'Room', | ||
| 40 | + feeObject: 'Fee Object', | ||
| 41 | + inputRoom: 'Please input room', | ||
| 42 | + inputPreDegrees: 'Please input previous degrees', | ||
| 43 | + inputCurDegrees: 'Please input current degrees', | ||
| 44 | + selectPreReadingTime: 'Please select previous reading time', | ||
| 45 | + selectCurReadingTime: 'Please select current reading time', | ||
| 46 | + price: 'Price', | ||
| 47 | + inputPrice: 'Please input price', | ||
| 48 | + remark: 'Remark', | ||
| 49 | + inputRemark: 'Please input remark', | ||
| 50 | + unitTip: 'Note: Unit 0 means shop', | ||
| 51 | + save: 'Save', | ||
| 52 | + selectFile: 'Select File', | ||
| 53 | + fileRequired: 'Please select data file', | ||
| 54 | + downloadTemplate: 'Download Template', | ||
| 55 | + downloadTip: 'Please download', | ||
| 56 | + importTemplate: 'Import Template', | ||
| 57 | + prepareData: 'prepare data first, then upload to import', | ||
| 58 | + dynamicFeeTip: 'Used when fee item is dynamic fee', | ||
| 59 | + import: 'Import', | ||
| 60 | + meterQRCode: 'Meter QR Code', | ||
| 61 | + qrCode: 'QR Code', | ||
| 62 | + qrCodeTip: 'Please screenshot and print, paste it to the meter, meter reader can scan quickly', | ||
| 63 | + selectBuilding: 'Please select building', | ||
| 64 | + selectUnit: 'Please select unit', | ||
| 65 | + selectRoom: 'Please select room', | ||
| 66 | + feeTypeRequired: 'Please select fee type', | ||
| 67 | + feeItemRequired: 'Please select fee item', | ||
| 68 | + preDegreesRequired: 'Please input previous degrees', | ||
| 69 | + curDegreesRequired: 'Please input current degrees', | ||
| 70 | + preReadingTimeRequired: 'Please select previous reading time', | ||
| 71 | + curReadingTimeRequired: 'Please select current reading time', | ||
| 72 | + degreesFormatError: 'Degrees format error', | ||
| 73 | + degreesCompareError: 'Current degrees cannot be less than previous degrees', | ||
| 74 | + remarkMaxLength: 'Remark cannot exceed 500 characters', | ||
| 75 | + selectMeterTypeFirst: 'Please select meter type first', | ||
| 76 | + downloadStarted: 'Download started', | ||
| 77 | + importSuccess: 'Import success' | ||
| 78 | + } | ||
| 79 | + }, | ||
| 80 | + zh:{ | ||
| 81 | + meterWater: { | ||
| 82 | + queryConditions: '查询条件', | ||
| 83 | + meterReadingInfo: '抄表信息', | ||
| 84 | + meterId: '表ID', | ||
| 85 | + meterType: '表类型', | ||
| 86 | + objectName: '对象名称', | ||
| 87 | + preDegrees: '上期度数', | ||
| 88 | + curDegrees: '本期度数', | ||
| 89 | + preReadingTime: '上期读表时间', | ||
| 90 | + curReadingTime: '本期读表时间', | ||
| 91 | + createTime: '创建时间', | ||
| 92 | + operation: '操作', | ||
| 93 | + readMeter: '抄表', | ||
| 94 | + qrCodeMeter: '二维码抄表', | ||
| 95 | + import1: '抄表导入1', | ||
| 96 | + import2: '抄表导入2', | ||
| 97 | + selectMeterType: '请选择表类型', | ||
| 98 | + inputMeterId: '请输入表ID', | ||
| 99 | + query: '查询', | ||
| 100 | + reset: '重置', | ||
| 101 | + addMeterReading: '添加抄表', | ||
| 102 | + editMeterReading: '修改抄表', | ||
| 103 | + confirmOperation: '请确认您的操作', | ||
| 104 | + confirmDeleteMeterReading: '确定删除抄表', | ||
| 105 | + cancel: '点错了', | ||
| 106 | + confirmDelete: '确认删除', | ||
| 107 | + meterReadingImport: '抄表导入', | ||
| 108 | + meterReadingImport2: '抄表导入2', | ||
| 109 | + feeType: '费用类型', | ||
| 110 | + selectFeeType: '请选择费用类型', | ||
| 111 | + feeItem: '收费项目', | ||
| 112 | + selectFeeItem: '请选择收费项目', | ||
| 113 | + feeItemTip: '说明:显示公式为【(本期度数-上期度数)*单价+附加费】的费用项', | ||
| 114 | + meterTypeRequired: '请选择抄表类型', | ||
| 115 | + building: '楼栋', | ||
| 116 | + unit: '单元', | ||
| 117 | + room: '房屋', | ||
| 118 | + feeObject: '收费对象', | ||
| 119 | + inputRoom: '请填写房屋', | ||
| 120 | + inputPreDegrees: '请输入上期度数', | ||
| 121 | + inputCurDegrees: '请输入本期度数', | ||
| 122 | + selectPreReadingTime: '请选择上期读表时间', | ||
| 123 | + selectCurReadingTime: '请选择本期读表时间', | ||
| 124 | + price: '单价', | ||
| 125 | + inputPrice: '请输入单价', | ||
| 126 | + remark: '备注', | ||
| 127 | + inputRemark: '请输入备注', | ||
| 128 | + unitTip: '注:单元选择为0表示为商铺', | ||
| 129 | + save: '保存', | ||
| 130 | + selectFile: '选择文件', | ||
| 131 | + fileRequired: '请选择数据文件', | ||
| 132 | + downloadTemplate: '下载模板', | ||
| 133 | + downloadTip: '请先下载', | ||
| 134 | + importTemplate: '导入模板', | ||
| 135 | + prepareData: '准备数据后,上传导入', | ||
| 136 | + dynamicFeeTip: '当收费项目为动态费用时使用', | ||
| 137 | + import: '导入', | ||
| 138 | + meterQRCode: '抄表二维码', | ||
| 139 | + qrCode: '二维码', | ||
| 140 | + qrCodeTip: '请截图打印后,粘贴到电表处,抄表人员扫码快速抄表', | ||
| 141 | + selectBuilding: '请选择楼栋', | ||
| 142 | + selectUnit: '请选择单元', | ||
| 143 | + selectRoom: '请选择房屋', | ||
| 144 | + feeTypeRequired: '请选择费用类型', | ||
| 145 | + feeItemRequired: '请选择收费项目', | ||
| 146 | + preDegreesRequired: '请输入上期度数', | ||
| 147 | + curDegreesRequired: '请输入本期度数', | ||
| 148 | + preReadingTimeRequired: '请选择上期读表时间', | ||
| 149 | + curReadingTimeRequired: '请选择本期读表时间', | ||
| 150 | + degreesFormatError: '度数格式错误', | ||
| 151 | + degreesCompareError: '本期度数不能小于上期度数', | ||
| 152 | + remarkMaxLength: '备注不能超过500个字符', | ||
| 153 | + selectMeterTypeFirst: '请先选择抄表类型', | ||
| 154 | + downloadStarted: '下载已开始', | ||
| 155 | + importSuccess: '导入成功' | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | +} | ||
| 0 | \ No newline at end of file | 160 | \ No newline at end of file |
src/views/fee/meterWaterManageList.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="meter-water-manage-container"> | ||
| 3 | + <el-row :gutter="20"> | ||
| 4 | + <el-col :span="4" class="tree-container"> | ||
| 5 | + <room-tree-div ref="roomTree" @selectRoom="handleSelectRoom" /> | ||
| 6 | + </el-col> | ||
| 7 | + <el-col :span="20"> | ||
| 8 | + <el-card class="box-card"> | ||
| 9 | + <div slot="header" class="clearfix"> | ||
| 10 | + <span>{{ $t('meterWater.queryConditions') }}</span> | ||
| 11 | + </div> | ||
| 12 | + <el-form :inline="true" :model="conditions" class="demo-form-inline"> | ||
| 13 | + <el-form-item :label="$t('meterWater.meterType')"> | ||
| 14 | + <el-select v-model="conditions.meterType" :placeholder="$t('meterWater.selectMeterType')" clearable | ||
| 15 | + style="width: 100%"> | ||
| 16 | + <el-option v-for="item in meterTypes" :key="item.typeId" :label="item.typeName" :value="item.typeId" /> | ||
| 17 | + </el-select> | ||
| 18 | + </el-form-item> | ||
| 19 | + <el-form-item :label="$t('meterWater.meterId')"> | ||
| 20 | + <el-input v-model="conditions.waterId" :placeholder="$t('meterWater.inputMeterId')" clearable /> | ||
| 21 | + </el-form-item> | ||
| 22 | + <el-form-item> | ||
| 23 | + <el-button type="primary" @click="handleQuery">{{ | ||
| 24 | + $t('common.query') | ||
| 25 | + }}</el-button> | ||
| 26 | + <el-button @click="handleReset">{{ $t('common.reset') }}</el-button> | ||
| 27 | + </el-form-item> | ||
| 28 | + </el-form> | ||
| 29 | + </el-card> | ||
| 30 | + | ||
| 31 | + <el-card class="box-card margin-top"> | ||
| 32 | + <div slot="header" class="clearfix"> | ||
| 33 | + <span>{{ $t('meterWater.meterReadingInfo') }}</span> | ||
| 34 | + <div style="float: right"> | ||
| 35 | + <el-button v-if="conditions.objId" type="primary" size="small" @click="handleOpenAddMeter"> | ||
| 36 | + {{ $t('meterWater.readMeter') }} | ||
| 37 | + </el-button> | ||
| 38 | + <el-button type="text" size="small" @click="handleOpenMeterType"> | ||
| 39 | + {{ $t('meterWater.meterType') }} | ||
| 40 | + </el-button> | ||
| 41 | + <el-button v-if="conditions.objId" type="text" size="small" @click="handleOpenQrCode"> | ||
| 42 | + {{ $t('meterWater.qrCodeMeter') }} | ||
| 43 | + </el-button> | ||
| 44 | + <el-button type="text" size="small" style="margin-left: 10px" @click="handleOpenImport1"> | ||
| 45 | + <i class="el-icon-plus"></i>{{ $t('meterWater.import1') }} | ||
| 46 | + </el-button> | ||
| 47 | + <el-button type="text" size="small" style="margin-left: 10px" @click="handleOpenImport2"> | ||
| 48 | + <i class="el-icon-plus"></i>{{ $t('meterWater.import2') }} | ||
| 49 | + </el-button> | ||
| 50 | + </div> | ||
| 51 | + </div> | ||
| 52 | + | ||
| 53 | + <el-table :data="meterWaters" border style="width: 100%"> | ||
| 54 | + <el-table-column prop="waterId" :label="$t('meterWater.meterId')" align="center" /> | ||
| 55 | + <el-table-column prop="meterTypeName" :label="$t('meterWater.meterType')" align="center" /> | ||
| 56 | + <el-table-column prop="objName" :label="$t('meterWater.objectName')" align="center" /> | ||
| 57 | + <el-table-column prop="preDegrees" :label="$t('meterWater.preDegrees')" align="center" /> | ||
| 58 | + <el-table-column prop="curDegrees" :label="$t('meterWater.curDegrees')" align="center" /> | ||
| 59 | + <el-table-column prop="preReadingTime" :label="$t('meterWater.preReadingTime')" align="center" /> | ||
| 60 | + <el-table-column prop="curReadingTime" :label="$t('meterWater.curReadingTime')" align="center" /> | ||
| 61 | + <el-table-column prop="createTime" :label="$t('meterWater.createTime')" align="center" /> | ||
| 62 | + <el-table-column :label="$t('common.operation')" align="center" width="180"> | ||
| 63 | + <template slot-scope="scope"> | ||
| 64 | + <el-button type="text" size="small" @click="handleEdit(scope.row)"> | ||
| 65 | + {{ $t('common.edit') }} | ||
| 66 | + </el-button> | ||
| 67 | + <el-button type="text" size="small" @click="handleDelete(scope.row)"> | ||
| 68 | + {{ $t('common.delete') }} | ||
| 69 | + </el-button> | ||
| 70 | + </template> | ||
| 71 | + </el-table-column> | ||
| 72 | + </el-table> | ||
| 73 | + | ||
| 74 | + <el-pagination :current-page="pagination.current" :page-sizes="[10, 20, 30, 50]" :page-size="pagination.size" | ||
| 75 | + :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" | ||
| 76 | + @current-change="handleCurrentChange" /> | ||
| 77 | + </el-card> | ||
| 78 | + </el-col> | ||
| 79 | + </el-row> | ||
| 80 | + | ||
| 81 | + <!-- 组件 --> | ||
| 82 | + <add-meter-water ref="addMeterWater" @success="handleSuccess" /> | ||
| 83 | + <edit-meter-water ref="editMeterWater" @success="handleSuccess" /> | ||
| 84 | + <delete-meter-water ref="deleteMeterWater" @success="handleSuccess" /> | ||
| 85 | + <import-meter-water-fee ref="importMeterWaterFee" /> | ||
| 86 | + <import-meter-water-fee2 ref="importMeterWaterFee2" /> | ||
| 87 | + <room-meter-qrcode ref="roomMeterQrcode" /> | ||
| 88 | + </div> | ||
| 89 | +</template> | ||
| 90 | + | ||
| 91 | +<script> | ||
| 92 | +import { | ||
| 93 | + listMeterWaters, | ||
| 94 | + listMeterTypes | ||
| 95 | +} from '@/api/fee/meterWaterManageApi' | ||
| 96 | +import { getCommunityId } from '@/api/community/communityApi' | ||
| 97 | +import RoomTreeDiv from '@/components/fee/roomTreeDiv' | ||
| 98 | +import AddMeterWater from '@/components/fee/addMeterWater' | ||
| 99 | +import EditMeterWater from '@/components/fee/editMeterWater' | ||
| 100 | +import DeleteMeterWater from '@/components/fee/deleteMeterWater' | ||
| 101 | +import ImportMeterWaterFee from '@/components/fee/importMeterWaterFee' | ||
| 102 | +import ImportMeterWaterFee2 from '@/components/fee/importMeterWaterFee2' | ||
| 103 | +import RoomMeterQrcode from '@/components/fee/roomMeterQrcode' | ||
| 104 | + | ||
| 105 | +export default { | ||
| 106 | + name: 'MeterWaterManageList', | ||
| 107 | + components: { | ||
| 108 | + RoomTreeDiv, | ||
| 109 | + AddMeterWater, | ||
| 110 | + EditMeterWater, | ||
| 111 | + DeleteMeterWater, | ||
| 112 | + ImportMeterWaterFee, | ||
| 113 | + ImportMeterWaterFee2, | ||
| 114 | + RoomMeterQrcode | ||
| 115 | + }, | ||
| 116 | + data() { | ||
| 117 | + return { | ||
| 118 | + conditions: { | ||
| 119 | + waterId: '', | ||
| 120 | + meterType: '', | ||
| 121 | + roomNum: '', | ||
| 122 | + objId: '', | ||
| 123 | + page: 1, | ||
| 124 | + row: 10, | ||
| 125 | + communityId: '' | ||
| 126 | + }, | ||
| 127 | + meterWaters: [], | ||
| 128 | + meterTypes: [], | ||
| 129 | + pagination: { | ||
| 130 | + current: 1, | ||
| 131 | + size: 10, | ||
| 132 | + total: 0 | ||
| 133 | + }, | ||
| 134 | + roomName: '' | ||
| 135 | + } | ||
| 136 | + }, | ||
| 137 | + created() { | ||
| 138 | + this.conditions.communityId = getCommunityId() | ||
| 139 | + this.loadData() | ||
| 140 | + this.loadMeterTypes() | ||
| 141 | + }, | ||
| 142 | + methods: { | ||
| 143 | + async loadData() { | ||
| 144 | + try { | ||
| 145 | + const { data, total } = await listMeterWaters(this.conditions) | ||
| 146 | + this.meterWaters = data | ||
| 147 | + this.pagination.total = total | ||
| 148 | + } catch (error) { | ||
| 149 | + console.error('Failed to load meter waters:', error) | ||
| 150 | + } | ||
| 151 | + }, | ||
| 152 | + async loadMeterTypes() { | ||
| 153 | + try { | ||
| 154 | + const { data } = await listMeterTypes({ | ||
| 155 | + communityId: this.conditions.communityId, | ||
| 156 | + page: 1, | ||
| 157 | + row: 100 | ||
| 158 | + }) | ||
| 159 | + this.meterTypes = data | ||
| 160 | + } catch (error) { | ||
| 161 | + console.error('Failed to load meter types:', error) | ||
| 162 | + } | ||
| 163 | + }, | ||
| 164 | + handleQuery() { | ||
| 165 | + this.conditions.page = 1 | ||
| 166 | + this.loadData() | ||
| 167 | + }, | ||
| 168 | + handleReset() { | ||
| 169 | + this.conditions.waterId = '' | ||
| 170 | + this.conditions.meterType = '' | ||
| 171 | + this.conditions.roomNum = '' | ||
| 172 | + this.loadData() | ||
| 173 | + }, | ||
| 174 | + handleSizeChange(val) { | ||
| 175 | + this.conditions.row = val | ||
| 176 | + this.loadData() | ||
| 177 | + }, | ||
| 178 | + handleCurrentChange(val) { | ||
| 179 | + this.conditions.page = val | ||
| 180 | + this.loadData() | ||
| 181 | + }, | ||
| 182 | + handleSelectRoom({ roomId, roomName }) { | ||
| 183 | + this.conditions.objId = roomId | ||
| 184 | + this.roomName = roomName | ||
| 185 | + this.loadData() | ||
| 186 | + }, | ||
| 187 | + handleOpenAddMeter() { | ||
| 188 | + this.$refs.addMeterWater.open({ | ||
| 189 | + roomId: this.conditions.objId, | ||
| 190 | + roomName: this.roomName | ||
| 191 | + }) | ||
| 192 | + }, | ||
| 193 | + handleEdit(row) { | ||
| 194 | + this.$refs.editMeterWater.open(row) | ||
| 195 | + }, | ||
| 196 | + handleDelete(row) { | ||
| 197 | + this.$refs.deleteMeterWater.open(row) | ||
| 198 | + }, | ||
| 199 | + handleOpenImport1() { | ||
| 200 | + this.$refs.importMeterWaterFee.open() | ||
| 201 | + }, | ||
| 202 | + handleOpenImport2() { | ||
| 203 | + this.$refs.importMeterWaterFee2.open() | ||
| 204 | + }, | ||
| 205 | + handleOpenQrCode() { | ||
| 206 | + this.$refs.roomMeterQrcode.open({ | ||
| 207 | + roomId: this.conditions.objId, | ||
| 208 | + roomName: this.roomName | ||
| 209 | + }) | ||
| 210 | + }, | ||
| 211 | + handleOpenMeterType() { | ||
| 212 | + this.$router.push('/fee/meterTypeManage') | ||
| 213 | + }, | ||
| 214 | + handleSuccess() { | ||
| 215 | + this.loadData() | ||
| 216 | + } | ||
| 217 | + } | ||
| 218 | +} | ||
| 219 | +</script> | ||
| 220 | + | ||
| 221 | +<style lang="scss" scoped> | ||
| 222 | +.meter-water-manage-container { | ||
| 223 | + padding: 20px; | ||
| 224 | + | ||
| 225 | + .tree-container { | ||
| 226 | + height: calc(100vh - 120px); | ||
| 227 | + overflow-y: auto; | ||
| 228 | + background: #fff; | ||
| 229 | + padding: 10px; | ||
| 230 | + border-radius: 4px; | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + .margin-top { | ||
| 234 | + margin-top: 20px; | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + .clearfix:before, | ||
| 238 | + .clearfix:after { | ||
| 239 | + display: table; | ||
| 240 | + content: ''; | ||
| 241 | + } | ||
| 242 | + | ||
| 243 | + .clearfix:after { | ||
| 244 | + clear: both; | ||
| 245 | + } | ||
| 246 | +} | ||
| 247 | +</style> | ||
| 0 | \ No newline at end of file | 248 | \ No newline at end of file |