mini mvvm

自用,bug较多,if和for指令不能使用

目前为 2022-05-05 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/444466/1047420/mini%20mvvm.js

作者
ayan0312
版本
0.0.2
创建于
2022-05-04
更新于
2022-05-05
大小
23.4 KB
许可证
暂无

指令

<div v-on:click="myClick"></div>
<div v-style="myStyle"></div>
<div v-show="display"></div>
<div v-show:display></div>
<div v-class="myClass"></div>
<div v-model="myModel"></div>
<div v-ref="myRef"></div>
<div v-text="myText"></div>
<div v-html="myHTML"></div>

全局

const state=observe({
  count:GM_getValue('count',1),
})

new Watcher(null,()=>{
  return state.count
},(newVal)=>{
  GM_setValue('count',newVal)
})

const Counter = {
  template: `
    <button v-on:click="decrease">-</button>
    <span>{{global.count}}</span>
    <button v-on:click="increase">+</button>
  `,
  data(){
    return {
      global:state,
    }
  },
  methods:{
    increase(){
      this.global.count+=1
    },
    decrease(){
      this.global.count-=1
    },
  }
}

组件

const MyTitle = {
    data() {
        return {
            title: '标题',
        }
    },
    template: `
        <div class="test">
            <h1 v-ref="title1"> {{ title }}1 </h2>
            <h2> {{ title }}2 </h2>
            <slot></slot>
            <label>
                标题:
                <input v-model="title" type="text" placeholder="修改标题" />
            </label>
        </div>
    `
}

const MyFooter = {
    data() {
        return {
            title: '尾部',
        }
    },
    template: `
        <div class="test">
            <h1 v-ref="title1"> {{ title }} </h2>
        </div>
    `,
    mounted() {
        this.$refs.title1.textContent = '尾部:ref取element改变文字'
        console.log('before nextTick')
        this.$nextTick().then(() => {
            console.log('nextTick(micro)')
        })
        console.log('after nextTick')
    }
}

const MyVM = {
    element: '#app',
    components: {
        'my-title': MyTitle,
        'my-footer': MyFooter
    },
    data: {
        number: {
            input: 0,
            click: 0
        },
        p: '这里是内容',
        display: true,
        tempObject: {},
        fontSizeStyle: { 'font-size': '14px' },
        inputNumberOver100Times: 0,
        numberInputClass: 'red',
        inputClassesIndex: 0,
        inputClasses: ['red', 'blue', 'yellow', 'grey', 'gold', 'black', 'white']
    },
    computed: {
        getInformation() {
            return `点击次数:${this.number.click} 增量:${this.number.input}`
        },
        getInputNumberOver100TimesInformation() {
            return `增量超过100的次数:${this.inputNumberOver100Times}`
        },
        getNewObject() {
            const a = this.tempObject.test
            return a ? `${a.name}` : a
        }
    },
    watch: {
        "number.input": function (newVal, oldVal) {
            if (newVal >= 100 && oldVal < 100)
                this.inputNumberOver100Times += 1
        },
        "number.click": function (newVal, oldVal) {
            this.fontSizeStyle = {
                'font-size': 14 + newVal + 'px'
            }
        },
        "inputNumberOver100Times": function () {
            this.$emit('over100Times')
        }
    },
    methods: {
        setNewObject(e) {
            this.tempObject.test = {
                name: 'test'
            }
            console.log('create test', this.tempObject)
        },

        deleteNewObject(e) {
            if ('test' in this.tempObject) {
                delete this.tempObject.test
            }
            console.log('delete test', this.tempObject)
        },
        displayController(e) {
            this.display = !this.display
        },
        increaseClickNumber() {
            this.number.click += 1
            this.$emit('click')
        },
        increase1(e) {
            this.number.input += 1
            this.increaseClickNumber()
        },
        increase10(e) {
            this.number.input += 10
            this.increaseClickNumber()
        },
        decrease10(e) {
            this.number.input -= 10
            this.increaseClickNumber()
        },
        switchInputTextColor(e) {
            this.inputClassesIndex += 1
            if (this.inputClassesIndex === this.inputClasses.length)
                this.inputClassesIndex = 0
            this.numberInputClass = this.inputClasses[this.inputClassesIndex]
            this.increaseClickNumber()
        }
    }
}

const vm = createVM(MyVM);
vm.$on('click', function () {
  console.log('clickNumber:', this.number.click)
})

vm.$once('over100Times', function () {
  console.log('over 100!')
})

const cVM = createComponent(MyTitle)
cVM.$mount('#app2')

用例

const TIME_OUT = 60 * 1000
const FILTER_URLS = [
  'https://static.myfigurecollection.net/ressources/nsfw.png',
  'https://static.myfigurecollection.net/ressources/spoiler.png'
]

const globalState =observe({
  group:GM_getValue('groupCount',1),
  count:GM_getValue('picCount',1),
  componentDownloadStatus:{},
  downloadStates:{
    normal:0,
    loading:0,
    error:0,
    timeout:0,
    downloaded:0,
  }
})

new Watcher(null,()=>{
  return globalState.count
},(newVal)=>{
  GM_setValue('picCount',newVal)
})

new Watcher(null,()=>{
  return globalState.group
},(newVal)=>{
  GM_setValue('groupCount',newVal)
  globalState.count = 0
})

new Watcher(null,()=>{
  return globalState.componentDownloadStatus
},(newVal)=>{
  Object.assign(globalState.downloadStates,{
    normal:0,
    loading:0,
    error:0,
    timeout:0,
    downloaded:0,
  })
  const states = globalState.downloadStates
  Object.keys(newVal).forEach(key=>{
    const status = newVal[key]
    if(states[status] != null)
        states[status]+=1
  })
},true)

function beforeDownload(){
  if(GM_getValue('groupCount',1) != globalState.group){
    const curCount = GM_getValue('picCount',1) + 1
    globalState.group = GM_getValue('groupCount',1)
    globalState.count = curCount
  }else{
    globalState.group = GM_getValue('groupCount',1)
    globalState.count = GM_getValue('picCount',1) + 1
  }

  return {group:globalState.group,count:globalState.count}
}

const DownloadSequence = {
  template: `
    <span>当前组:</span>
    <button v-on:click="decreaseGroup">-</button>
    <span>{{global.group}}</span>
    <button v-on:click="increaseGroup">+</button>
    <span>当前图片:</span>
    <button v-on:click="decreaseCount">-</button>
    <span>{{global.count}}</span>
    <button v-on:click="increaseCount">+</button>
  `,
  data(){
    return {
      global:globalState,
    }
  },
  methods:{
    increaseCount(){
      this.global.count+=1
    },
    decreaseCount(){
      this.global.count-=1
    },
    increaseGroup(){
      this.global.group+=1
    },
    decreaseGroup(){
      this.global.group-=1
    }
  }
}

const REQEUST_BUTTON_STYLES = {
  normal:{},
  loading: { background: 'white',color:'black',cursor:'wait'},
  error:{background:'red',color:'white'},
  timeout:{background:'yellow',color:'black'},
  downloaded: {background:'green',color:'white'}
}

const DownloadButton = {
    template: `
      <button v-on:click="download" v-style="downloadBtnStyle">
        {{downloadedMsg}}
      </button>
    `,
    data(){
      return {
        oldStatus:'normal',
        downloadStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
      }
    },
    computed:{
      downloadBtnStyle(){
        return REQEUST_BUTTON_STYLES[this.downloadStatus]
      },
      downloadedMsg(){
        const messages = {
          normal:'下载原图' ,
          loading: '正在下载...',
          error:'下载失败',
          timeout:'下载超时',
          downloaded:  '重新下载'
        }
        return messages[this.downloadStatus]
      }
    },
    watch:{
      downloadStatus(newStatus,oldStatus){
        this.oldStatus = oldStatus
        globalState.componentDownloadStatus[this.cid] = newStatus
      }
    },
    created(){
      globalState.componentDownloadStatus[this.cid] = this.downloadStatus
    },
    destoryed(){
      delete globalState.componentDownloadStatus[this.cid]
    },
    methods:{
      download(){
        if(this.downloadStatus === 'loading') return
        this.downloadStatus = 'loading'
        if(this.oldStatus !== 'error' && this.oldStatus !== 'timeout')
          this.value = beforeDownload()
        this.$emit('download',this.value)
      },
    }
}

const DownloadState = {
  template:`
    <div style="display:flex;flex-direction:row;padding:5px">
      <div style="margin-right:5px;color:grey">
        <span style="">未下载:</span>
        <span>{{states.normal}}</span>
      </div>
      <div style="margin-right:5px;color:green">
        <span style="">已下载:</span>
        <span>{{states.downloaded}}</span>
      </div>
      <div style="margin-right:5px;color:black">
        <span style="">正在下载:</span>
        <span>{{states.loading}}</span>
      </div>
      <div style="margin-right:5px;color:brown">
        <span style="">下载超时:</span>
        <span>{{states.timeout}}</span>
      </div>
      <div style="color:red">
        <span>下载报错:</span>
        <span>{{states.error}}</span>
      </div>
    </div>
  `,
  data(){
    return {
      states:globalState.downloadStates
    }
  },
  methods:{
    download(value){
      this.$emit('download',value)
    }
  }
}

const PictureDownload = {
  components:{
    'download-button':DownloadButton,
    'download-sequence':DownloadSequence,
  },
  template:`
    <div>
      <download-sequence></download-sequence>
      <span v-show:downloaded style="margin:0 10px">{{msg}}</span>
      <download-button v-ref="downloadButton" v-on:download="download"></download-button>
    </div>
  `,
  data(){
    return {
      group:0,
      count:0,
      downloaded:false
    }
  },
  computed:{
    msg(){
      return `${this.group}.${this.count}`
    }
  },
  mounted(){
      const value = GM_getValue(window.location.href)
      if(!value) return
      this.$refs.downloadButton.downloadStatus = value.downloadStatus
      this.$refs.downloadButton.value = {
        group: value.group,
        count: value.count
      }
      this.downloaded = true
      this.group = value.group
      this.count = value.count
  },
  methods:{
    download(value){
      this.downloaded = true
      this.group = value.group
      this.count = value.count
      this.$emit('download',value)
    }
  }
}

function insertAfter(targetNode, afterNode){
  const parentNode = afterNode.parentNode
  const beforeNode = afterNode.nextElementSibling
  if(beforeNode == null)
    parentNode.appendChild(targetNode)
  else
    parentNode.insertBefore(targetNode, beforeNode)
}

function mountVM(node,component){
  const vm = new MVVMComponent(component)
  vm.$mount(node)
  return vm
}

function download({vm,picture,group,count,origin}){
  const values = origin.split('/')
  const fileType = values[values.length - 1].split('.')[1]
  const name = `${group}.${count}.${fileType}`
  const end = (status)=>{
    return ()=>{
      vm.downloadStatus = status
      GM_setValue(picture,{origin,group,count,downloadStatus:status})
    }
  }

  GM_download({
    url:origin,
    name,
    timeout:TIME_OUT,
    onload:end('downloaded'),
    onerror:end('error'),
    ontimeout:end('timeout'),
  })
}

function renderPictureExtension(objectMeta,thePicture){
  const origin = thePicture.href
  const div = document.createElement('div')
  objectMeta.appendChild(div)
  const vm = mountVM(div,PictureDownload)
  vm.$on('download',(value)=>{
    download({
      ...value,
      origin,
      picture:window.location.href,
      vm:vm.$refs.downloadButton,
    })
  })
}

const createPictruePreview = ({thumb,origin,picture}) => {
  const commonStyle={
    'margin':'10px 10px',
    'border':'1px solid #000',
    'padding':'10px',
    'border-radius':'5px',
    'background':'#fff',
    'transition':'all 0.5s',
    'box-sizing': 'border-box'
  }

  return {
      components:{
        'download-button':DownloadButton
      },
      template: `
          <div v-style="containerStyle">
            <div style="margin-bottom:10px;display:flex;flex-direction:row;justify-content:center;align-items:center;width:100%;">
              <div style="margin:0 10px;flex-shrink:0"><img style="cursor:pointer" v-on:click="openPicturePage" [src]="thumb" /></div>
              <div style="flex-shrink:1" v-show="originalImage"><img style="width:100%" [src]="src" /></div>
            </div>
            <div style="display:flex;justify-content:center;align-items:center;flex-direction:column">
                <download-button v-ref="downloadButton" v-on:download="download"></download-button>
                <br v-show:downloaded />
                <div v-show:downloaded>
                  <span>组:</span>
                  <span >{{group}}</span>
                  <span>图片:</span>
                  <span >{{count}}</span>
                </div>
                <br />
                <button v-on:click="toggle">{{msg}}</button>
                <br />
                <button v-show:refresh v-on:click="refreshOrigin" v-style="refreshBtnStyle">
                  {{refreshMsg}}
                </button>
            </div>
          </div>
      `,
      data(){
        return {
          thumb,
          origin,
          picture,
          refresh:FILTER_URLS.includes(origin),
          originalImage:false,
          group:0,
          count:0,
          downloaded:false,
          refreshStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
        }
      },
      computed:{
        src(){
          return this.originalImage ? this.origin : this.thumb
        },
        msg(){
          return this.originalImage ? '关闭预览' : '预览原图'
        },
        containerStyle(){
          return Object.assign({},commonStyle,this.originalImage ? {
            width:'100%'
          }:{} ) 
        },
        refreshBtnStyle(){
          return REQEUST_BUTTON_STYLES[this.refreshStatus]
        },
        refreshMsg(){
          const messages = {
            normal:'获取隐藏图片' ,
            loading: '正在获取...',
            error:'获取失败',
            timeout:'获取超时',
            downloaded:  '重新获取'
          }
          return messages[this.refreshStatus]
        }
      },
      mounted(){
        const value = GM_getValue(picture)
        if(!value) return
        this.$refs.downloadButton.downloadStatus = value.downloadStatus
        this.$refs.downloadButton.value = {
          group: value.group,
          count: value.count
        }
        this.downloaded = true
        this.group = value.group
        this.count = value.count
      },
      methods:{
        download(value){
          this.downloaded = true
          this.group = value.group
          this.count = value.count

          if(this.refresh && this.refreshStatus !== 'downloaded')
            this.refreshOrigin()
              .then(()=>{
                this.$emit('download',{...value,origin:this.origin})
              })
          else
            this.$emit('download',{...value,origin:this.origin})
        },
        toggle(){
          if(this.refresh && !this.originalImage && this.refreshStatus !== 'downloaded')
            this.refreshOrigin()

          this.originalImage = !this.originalImage
        },
        refreshOrigin(){
          if(this.refreshStatus === 'loading') return
          this.refreshStatus = 'loading'
          return new Promise((resolve,reject)=>{
            GM_xmlhttpRequest({
              url:this.picture,
              responseType:'document',
              timeout:TIME_OUT,
              onload:(data)=>{
                const doc = data.response
                const a = doc.querySelector('.the-picture>a')
                if(a){
                  this.origin = a.href
                  const thumb = a.href.split('/')
                  thumb.splice(thumb.length - 1,0,'thumbnails')
                  this.thumb = thumb.join('/')
                  this.refreshStatus = 'downloaded'
                  resolve()
                  return                
                }
                this.refreshStatus = 'error'
                reject()
              },
              onerror:()=>{
                this.refreshStatus = 'error'
                reject()
              },
              ontimeout:()=>{
                this.refreshStatus = 'timeout'
                reject()
              }
            })
          })
        },
        openPicturePage(){
          window.open(picture)
        }
      }
  }
}

function parseOriginalImageURL(thumb_url){
  if(thumb_url.indexOf('thumbnails/') > -1){
    url = thumb_url.split('thumbnails/').join('')
  } else {
    const paths = thumb_url.split('pictures/')[1].split('/')
    if(paths.length > 2){
      paths.splice(3,1)
      url = [thumb_url.split('pictures/')[0],paths.join('/')].join('pictures/')
    }
  }

  return url
}

function getImageURLs(node){
  const picture = node.querySelector('a').href
  const viewport = node.querySelector('.viewport')
  const thumb = viewport.style.background.split('"')[1]
  let origin = thumb
  if(FILTER_URLS.includes(origin))
    origin = thumb
  else
    origin = parseOriginalImageURL(origin)

  return {thumb,origin,picture}
}

function renderPicturesExtension(thumbs){
  function _initParentNode(parentNode){
    parentNode.innerHTML = ''
    parentNode.style.setProperty('display','flex')
    parentNode.style.setProperty('flex-direction','row')
    parentNode.style.setProperty('flex-wrap','wrap')
    parentNode.style.setProperty('justify-content','center')
    parentNode.style.setProperty('align-items','center')
    const div1 = document.createElement('div')
    parentNode.parentNode.insertBefore(div1,parentNode)
    mountVM(div1,DownloadSequence)
    const div2 = document.createElement('div')
    const pageCount = document.querySelector('.listing-count-pages') || parentNode
    pageCount.parentNode.insertBefore(div2,pageCount)
    mountVM(div2,DownloadState)
  }

  const parentNode = thumbs[0].parentNode
  _initParentNode(parentNode)
  thumbs.forEach(thumb_node=>{
    const imageURLs = getImageURLs(thumb_node)
    const preview = createPictruePreview(imageURLs)
    const div3 = document.createElement('div')
    parentNode.appendChild(div3)
    const previewVM = mountVM(div3,preview)
    previewVM.$on('download',(value)=>{
      download({
        ...value,
        picture:imageURLs.picture,
        vm:previewVM.$refs.downloadButton,
      })
    })
  })
}

function render(){
  const objectMeta = document.querySelector('.object-meta')
  const thePicture = document.querySelector('.the-picture>a')
  if(objectMeta && thePicture)
    renderPictureExtension(objectMeta,thePicture)

  const thumbs = []
  document.querySelectorAll('.picture-icon.tbx-tooltip').forEach(thumb=>{
    thumbs.push(thumb)  
  })
  if(thumbs.length > 0)
    renderPicturesExtension(thumbs.reverse())
}

render()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址