零基础手把手教你如何使用Laf免费玩转Midjourney后续之前端整合

一、前言

上篇讲述了 零基础手把手教你如何使用Laf免费玩转Midjourney,下面将讲解如何结合前端完成终极体验!

二、项目搭建

前端技术栈:vue + element plus

1.创建vue项目

这里使用vue脚手架创建项目,搭建项目步骤可参考官网 创建一个项目 | Vue CLIhttps://cli.vuejs.org/zh/guide/creating-a-project.html

2.安装element plus依赖并引入

安装步骤可参考官网 安装 | Element Plushttps://element-plus.gitee.io/zh-CN/guide/installation.html安装完成后在main.js中引入组件

import ElementPlus from 'element-plus'
import * as ElIcons from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import locale from 'element-plus/lib/locale/lang/zh-cn'

const app = createApp(App)

for (const name in ElIcons) {
    app.component(name, ElIcons[name]);
}
app.use(ElementPlus, { locale })
app.use(store)
app.use(router)
app.mount('#app')

3.代码实现

修改“vue.config.js”文件中的配置,其中“target”为Laf接口访问前缀

module.exports = {
  transpileDependencies: true,
  lintOnSave: false,
  productionSourceMap: false,
  devServer: {
      proxy: {
          '/api': {
              target: 'https://xxx.laf.dev',
              changeOrigin: true,
              ws: false,
              pathRewrite: {
                  '^/api': ''
              }
          }
      }
  }
}

接着,完成页面布局和逻辑业务实现,以“home.vue”为例:

<template>
  <div class="page">
    <el-container>
      <el-header ref="elHeader">
        <p>欢迎体验Midjourney绘画功能~</p>
        <p>绘画指令:[/mj 提示词],例如:/mj a dog</p>
        <p>放大指令:[/mj u1-u4],例如:/mj u2</p>
        <p>变换指令:[/mj v1-v4],例如:/mj v3</p>
        <p class="tips">注意:提示词最好用英文!</p>
      </el-header>
      <el-main :style="{ height: scrollerHeight + 'px' }">
        <el-scrollbar class="msg-list">
          <el-row v-for="(item, key) in msgList" :key="key" class="msg-item">
            <el-col>
              <img v-if="item.type == 1" class="img_1" src="../assets/images/ic_chatgpt.png" />
              <el-card :style="{ 'margin-left': (item.type == 2 ? 'auto' : '0') }">
                <div class="txt">
                  <!-- <img class="img" v-if="item.content.indexOf('http') > -1" :src="item.content" /> -->
                  <el-image v-if="item.content.indexOf('http') > -1" :src="item.content"
                    :preview-src-list="[item.content]" fit="cover" />
                  <text v-else>{{ item.content }}</text>
                </div>
                <div class="time">{{ item.time }}</div>
              </el-card>
              <img v-if="item.type == 2" class="img_2" src="../assets/images/me.png" />
            </el-col>
          </el-row>
        </el-scrollbar>
      </el-main>
      <el-footer ref="elFooter">
        <el-input v-model="queryInfo.param.prompt" placeholder="/mj 提示词" clearable size="large"
          :formatter="(value) => `/mj ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
          :parser="(value) => value.replace(/\/mj\s?|(,*)/g, '')" :disabled="isDisabled">
          <template #append>
            <el-button type="primary" :disabled="isDisabled" @click="sendMsg()">
              <el-icon>
                <Position />
              </el-icon>发送
            </el-button>
          </template>
        </el-input>
      </el-footer>
    </el-container>
  </div>
</template>

<script>
import topBar from '@/components/topBar.vue'

var _this;
export default {
  name: 'home',
  components: {
    topBar
  },
  data() {
    return {
      // 容器高度初始化为0
      scrollerHeight: 0,
      // 查询参数信息
      queryInfo: {
        type: null,
        param: {
          msg_Id: null,
          prompt: '',
          question: null,
          index: null,
          id: null,
          url: null
        }
      },
      // 信息集合
      msgList: [],
      msgIndex: 0,
      isDisabled: false,
      // 图片信息
      imgInfo: null,
      // 操作类型 1-绘图,2-放大,3-变换
      operateType: 0,
      // 是否重试
      isRetry: true
    }
  },
  created() {
    _this = this;

    // 先读取缓存中的会话信息,没有则新创建一条
    const _msgList = localStorage.getItem('msgList');
    if (_msgList) {
      _this.msgList = JSON.parse(_msgList);
    } else {
      _this.insertMsg(1, '亲,欢迎加入Midjourney的绘画体验,请输入指令.....');
    }

    const _imgInfo = localStorage.getItem('imgInfo');
    if (_imgInfo) {
      _this.imgInfo = JSON.parse(_imgInfo);
    }
  },
  mounted: function () {
    _this.calculateHeight();

    // 当浏览器窗口大小发生变化时,重新计算容器高度
    window.addEventListener('resize', () => {
      _this.calculateHeight();
    });
  },
  methods: {
    // 计算容器高度
    calculateHeight() {
      // 获取顶部元素
      let elHeader = _this.$refs.elHeader;
      // 获取底部元素
      let elFooter = _this.$refs.elFooter;
      if (elHeader && elFooter) {
        _this.scrollerHeight = document.documentElement.clientHeight - elHeader.$el.offsetHeight - elFooter.$el.offsetHeight - 1;
      }
    },
    //发送消息
    sendMsg() {
      const _prompt = _this.queryInfo.param.prompt.replace(/\/mj\s?|(,*)/g, '').toLowerCase();
      if (!_prompt) {
        _this.$message.toast({
          message: '请输入提示词!'
        });
        return;
      }

      if (!_this.imgInfo && (_prompt.indexOf('u1') > -1 || _prompt.indexOf('u2') > -1 || _prompt.indexOf('u3') > -1 || _prompt.indexOf('u4') > -1
        || _prompt.indexOf('v1') > -1 || _prompt.indexOf('v2') > -1 || _prompt.indexOf('v3') > -1 || _prompt.indexOf('v4') > -1)) {
        _this.$message.toast({
          message: '请先输入绘图指令!'
        });
        return;
      }

      let optStr = '';
      _this.queryInfo.param.question = _prompt;
      // 放大图片
      if (_prompt.indexOf('u1') > -1 || _prompt.indexOf('u2') > -1 || _prompt.indexOf('u3') > -1 || _prompt.indexOf('u4') > -1) {
        optStr = '放大';
        _this.operateType = 2;
        _this.queryInfo.type = 'upscale';
        if (_prompt.indexOf('u1') > -1) {
          _this.queryInfo.param.index = 1;
        }
        if (_prompt.indexOf('u2') > -1) {
          _this.queryInfo.param.index = 2;
        }
        if (_prompt.indexOf('u3') > -1) {
          _this.queryInfo.param.index = 3;
        }
        if (_prompt.indexOf('u4') > -1) {
          _this.queryInfo.param.index = 4;
        }
        _this.queryInfo.param.id = _this.imgInfo.id;
        _this.queryInfo.param.url = _this.imgInfo.attachments[0].url;
      }
      //变化图片
      else if (_prompt.indexOf('v1') > -1 || _prompt.indexOf('v2') > -1 || _prompt.indexOf('v3') > -1 || _prompt.indexOf('v4') > -1) {
        optStr = '变化';
        _this.operateType = 3;
        _this.queryInfo.type = 'variation';
        if (_prompt.indexOf('v1') > -1) {
          _this.queryInfo.param.index = 1;
        }
        if (_prompt.indexOf('v2') > -1) {
          _this.queryInfo.param.index = 2;
        }
        if (_prompt.indexOf('v3') > -1) {
          _this.queryInfo.param.index = 3;
        }
        if (_prompt.indexOf('v4') > -1) {
          _this.queryInfo.param.index = 4;
        }
        _this.queryInfo.param.id = _this.imgInfo.id;
        _this.queryInfo.param.url = _this.imgInfo.attachments[0].url;
      }
      //生成图片
      else {
        optStr = '生成';
        _this.operateType = 1;
        _this.queryInfo.type = 'imagine';
        _this.queryInfo.param.msg_Id = 'amj_' + Math.ceil(Math.random() * 1000000000000);
      }

      _this.queryInfo.param.prompt = '';
      _this.insertMsg(2, _prompt);
      _this.isDisabled = true;
      _this.isRetry = true;
      console.log('请求参数', JSON.stringify(_this.queryInfo));
      _this.$requests
        .post("/api/mj-func", _this.queryInfo)
        .then((res) => {
          console.log('请求结果', res);
          if (res != null) {
            _this.$message.toast({
              message: res.msg,
              type: "success",
            });

            // 请求成功后,开始倒计时60秒
            let seconds = 60;
            _this.insertMsg(1, '请求成功,' + seconds + ' 秒后将为您获取' + optStr + '后的图片。');
            let timer = setInterval(function () {
              seconds--;
              if (seconds == 0) {
                console.log('执行查询');
                clearInterval(timer);
                _this.msgList[_this.msgList.length - 1].content = '图片获取中,请稍后......';
                localStorage.setItem('msgList', JSON.stringify(_this.msgList));

                _this.retrieveMessages();
                return;
              }
              _this.msgList[_this.msgList.length - 1].content = '请求成功,' + seconds + ' 秒后将为您获取' + optStr + '后的图片。';
              localStorage.setItem('msgList', JSON.stringify(_this.msgList));
            }, 1000);
          }
        });
    },
    // 查询消息
    retrieveMessages() {
      _this.queryInfo.type = 'retrieveMessages';
      console.log('查询参数', JSON.stringify(_this.queryInfo));
      _this.$requests
        .post("/api/mj-func", _this.queryInfo)
        .then((res) => {
          console.log('查询结果', res);
          _this.isDisabled = _this.isRetry ? true : false;
          if (res != null) {
            _this.isDisabled = false;
            _this.$message.toast({
              message: res.msg,
              type: "success",
            });

            if (res.data) {
              _this.insertMsg(1, res.data.pic);

              // 生成图片后,存储变量
              if (_this.operateType == 1) {
                _this.imgInfo = res.data;
                localStorage.setItem('imgInfo', JSON.stringify(_this.imgInfo));
              }
            }
          }
          else {
            if (_this.isRetry) {
              _this.isRetry = false;
              // 重试,开始倒计时180秒
              let seconds = 180;
              let timer = setInterval(function () {
                seconds--;
                if (seconds == 0) {
                  console.log('重试->执行查询');
                  clearInterval(timer);
                  _this.msgList[_this.msgList.length - 1].content = '图片获取中,请稍后......';
                  localStorage.setItem('msgList', JSON.stringify(_this.msgList));

                  _this.retrieveMessages();
                  return;
                }
                _this.msgList[_this.msgList.length - 1].content = '图片获取失败,' + seconds + ' 秒后将为您重新获取。';
                localStorage.setItem('msgList', JSON.stringify(_this.msgList));
              }, 1000);
            }
            else {
              _this.insertMsg(1, '十分抱歉,图片获取失败,请重新输入指令.....');
            }
          }
        });
    },
    // 插入会话
    insertMsg(type, content) {
      _this.msgIndex = _this.msgIndex + 1;
      _this.msgList.push({
        id: _this.msgIndex,
        type: type || 0,//类型 1-机器人,2-自己
        content: content || '',
        time: _this.$date.fmtDateTime(null, 3)
      });
      // 将会话写入缓存
      localStorage.setItem('msgList', JSON.stringify(_this.msgList));
    }
  }
}
</script>

<style lang="scss">
.page {
  .el-header {
    line-height: 20px;
    text-align: left;
    background-color: var(--el-color-primary-light-7);
    color: var(--el-text-color-primary);
    height: auto;
    padding: 8px 15px;

    p {
      font-size: 13px;
      color: #303133;
    }

    .tips {
      color: red;
    }
  }

  .el-main {
    padding: 12px 15px;

    .msg-list {
      .msg-item:last-child {
        margin-bottom: 0;
      }

      .msg-item {
        margin-bottom: 10px;
        text-align: left;

        .el-col {
          display: flex;

        }

        .img {
          width: 100%;
        }

        .img_1 {
          width: 36px;
          height: 36px;
          margin-right: 8px;
        }

        .img_2 {
          width: 36px;
          height: 36px;
          margin-left: 8px;
        }

        .el-card__body {
          padding: 8px 15px;
          font-size: 14px;
          color: #303133;
          min-width: 130px;
        }

        .time {
          margin-top: 5px;
          text-align: right;
          color: #909399;
          font-size: 12px;
        }
      }
    }
  }

  .el-footer {
    line-height: 60px;
    background-color: #ecf5ff;
    color: var(--el-text-color-primary);

    .el-input-group__append {
      background-color: #409EFF;
      color: #ffffff;
      box-shadow: none;
    }
  }
}
</style>

说明:post请求时,第一个url参数需要使用自己在Laf中创建的云函数的名称

在src目录下创建utils文件夹,用于存放以上关联的js,具体代码如下:

日期格式化(date.js)

//时间处理
const date = {
    /**
     * 
     * @param {时间} dateTime 
     * @param {类型} type 默认:年月日
     * type=1 年-月-日
     * type=2 年.月.日
     * type=3 年-月-日 时:分:秒
     * type=4 年.月.日 时:分:秒
     * @returns 
     */
    fmtDateTime(dateTime, type) {
        if (dateTime == '' || dateTime == null) {
            dateTime = new Date();
        } else {
            // dateTime = dateTime.substr(0, 4) + "/" + dateTime.substr(5, 2) + "/" + dateTime.substr(8, 2) + " " + dateTime.substr(11, 8);
            dateTime = new Date(dateTime);
        }

        var y = dateTime.getFullYear();
        var m = dateTime.getMonth() + 1;
        var d = dateTime.getDate();
        var h = dateTime.getHours() > 9 ? dateTime.getHours().toString() : '0' + dateTime.getHours();
        var mm = dateTime.getMinutes() > 9 ? dateTime.getMinutes().toString() : '0' + dateTime.getMinutes();
        var ss = dateTime.getSeconds() > 9 ? dateTime.getSeconds().toString() : '0' + dateTime.getSeconds();

        if (type === 1) {
            return y + '-' + (m < 10 ? ('0' + m) : m) + '-' + (d < 10 ? ('0' + d) : d);
        }
        else if (type === 2) {
            return y + '.' + (m < 10 ? ('0' + m) : m) + '.' + (d < 10 ? ('0' + d) : d);
        }
        else if (type === 3) {
            return y + '-' + (m < 10 ? ('0' + m) : m) + '-' + (d < 10 ? ('0' + d) : d) + " " + h + ":" + mm + ":" + ss;
        }
        else if (type === 4) {
            return y + '.' + (m < 10 ? ('0' + m) : m) + '.' + (d < 10 ? ('0' + d) : d) + " " + h + ":" + mm + ":" + ss;
        }
        return y + '年' + (m < 10 ? ('0' + m) : m) + '月' + (d < 10 ? ('0' + d) : d) + '日';
    }
}

export default date

消息弹框提示(message.js)

import { ElMessage } from "element-plus";

const message = {
    //信息提示
    toast(obj) {
        return ElMessage({
            message: obj.message || 'this is a message.',
            type: obj.type || 'warning',//success warning info error
            duration: obj.duration || 3000,
            showClose: obj.showClose || false
        });
    }
}

export default message

加载动画(loading.js)

import { ElLoading } from 'element-plus';

// 定义一个请求次数的变量,用来记录当前页面总共请求的次数
let loadingRequestCount = 0;
// 初始化loading
let loadingInstance;

// 编写一个显示loading的函数 并且记录请求次数 ++
const showLoading = (target) => {
    if (loadingRequestCount === 0) {
        // element的服务方式 target 我这边取的是表格class
        // 类似整个表格loading和在表格配置v-loading一样的效果,这么做是全局实现了,不用每个页面单独去v-loading
        loadingInstance = ElLoading.service({ target });
    }
    loadingRequestCount++
}

// 编写一个隐藏loading的函数,并且记录请求次数 --
const hideLoading = () => {
    if (loadingRequestCount <= 0) return
    loadingRequestCount--
    if (loadingRequestCount === 0) {
        loadingInstance.close();
    }
}

export {
    showLoading,
    hideLoading
}

http请求(requests.js)

import axios from 'axios'
import message from './message';
import { showLoading, hideLoading } from '@/utils/loading'

// 创建 axios 实例
const requests = axios.create({
    // baseURL: 'http://localhost:8088',
    withCredentials: true,
    headers: {
        // 'Content-Type': 'application/json',
        // 'content-type' : 'application/x-www-form-urlencoded',
        // 'auth': 'test'
    },
    timeout: 30 * 1000 // 请求超时时间 30秒
})

// 错误处理函数
const err = (error) => {
    // 响应拦截进来隐藏loading效果,此处采用延时处理是合并loading请求效果,避免多次请求loading关闭又开启
    setTimeout(() => {
        hideLoading()
    }, 200);

    if (error.response) {
        const data = error.response.data;
        if (error.response.status === 403) {
            message.toast({
                message: data.message || data.msg
            });
        }
        if (error.response.status === 401) {
            message.toast({
                type: 'warning',
                message: '你没有权限。'
            });
        }
    }
    return Promise.reject(error)
}

// requests interceptor(请求拦截器)
requests.interceptors.request.use(config => {
    // 请求拦截进来调用显示loading效果
    showLoading(".el-main");

    const token = localStorage.getItem('token')
    if (token) {
        config.headers['auth'] = token // 让每个请求携带自定义 token
    }
    return config
}, err)

// requests interceptor(接收拦截器)
requests.interceptors.response.use((response) => {
    // 响应拦截进来隐藏loading效果,此处采用延时处理是合并loading请求效果,避免多次请求loading关闭又开启
    setTimeout(() => {
        hideLoading()
    }, 200);

    const res = response.data;
    if (res.code == 200) {
        return Promise.resolve(res).catch((e) => { });
    } else {
        message.toast({
            message: res.msg
        });

        return Promise.reject(res).catch((e) => { });
    }
}, err)

export default {
    requests
}

同时,需要修改“main.js”,进行引入,配置全局变量

import message from './utils/message'
import requests from './utils/requests'
import date from './utils/date'

//配置全局属性
app.config.globalProperties.$requests = requests.requests
app.config.globalProperties.$message = message
app.config.globalProperties.$date = date

“App.vue”页面中样式如下:

<template>
  <router-view />
</template>

<style lang="scss">
body {
  padding: 0;
  margin: 0;
}

#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

p {
  margin: 0;
  padding: 0;
}

.el-message{
  width: max-content;
}
</style>

以为全部完成后,使用命令npm run serve运行项目,界面效果如下:

 在线体验可点击 Midjourney绘画

三、发布项目并部署到Laf云平台

1.打包项目

本地开发完成后,没有服务器部署到线上怎么办?不用担心,Laf都已经为我们考虑好了,使用命令npm run build打包项目

2.项目托管

进入【Laf云开发】-【存储】,新建“Bucket”空间,如下图:

 完成后,点击上方“上传”按钮,将本地打包后生成的dist文件夹下的文件全部上传,如下图:

再点击右上方“开启网站托管”按钮,生成“当前域名”,即可进行外部访问了。

 关于网站托管详细介绍,可访问官网 静态网站托管介绍 | laf 云开发https://doc.laf.run/guide/website-hosting/

以上即为使用Laf接入Midjourney后整合前端的完整演示,如有疑问,欢迎提出!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值