Commit 5f3e70be by haojie

1

parents
VITE_BASE_API='http://snow.test'
VITE_BASE_API=''
\ No newline at end of file
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
//引入路径模块
import path from 'path';
//引入文件模块
import fs from 'fs';
import { parse } from 'node-html-parser';
import { fileURLToPath } from 'url';
const __filenameNew = fileURLToPath(import.meta.url);
const __dirnameNew = path.dirname(__filenameNew);
// 因打包文件名是获取的当前时间,所以每次修改index.html记得在这修改文件名
let pathName = path.join(
__dirnameNew,
'Snow-mobile-2023-1-12--18.33/index.html'
);
fs.readFile(pathName, 'utf8', function (err, html) {
if (err) {
return console.log('读取index.html文件失败' + err.message);
}
const root = parse(html);
const elList = root.querySelectorAll('script');
for (let i = 0; i < elList.length; i++) {
// 1、移除 <script type=module> 元素
const data = elList[i].getAttribute('type');
const cro = elList[i].hasAttribute('crossorigin');
if (data && data === 'module' && cro) {
elList[i].remove();
}
// 2、移除其他 <script> 的 nomodule 属性
const hasNoModule = elList[i].hasAttribute('nomodule');
if (hasNoModule) {
elList[i].removeAttribute('nomodule');
}
// 3、移除 <script id=vite-legacy-entry> 元素的内容,并把 data-src 属性名改为 src
// const hasDataSrc = elList[i].hasAttribute('data-src');
// if (hasDataSrc) {
// const legacyEle = elList[i];
// const srcData = legacyEle.getAttribute('data-src');
// legacyEle.setAttribute('src', srcData);
// legacyEle.removeAttribute('data-src');
// legacyEle.innerText = '';
// }
}
const ellink = root.querySelectorAll('link');
for (let j = 0; j < ellink.length; j++) {
// 4.移除 link标签上的modulepreload
let rel = ellink[j].getAttribute('rel');
if (rel && rel == 'modulepreload') {
ellink[j].removeAttribute('rel');
}
}
// 将新的组合的内容写入原有index.html
fs.writeFileSync(pathName, root.toString());
// console.log('222'+root.toString())
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>GPT</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "snow-mobile",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --open --mode development",
"dev:app": "vite --mode app",
"build": "vue-tsc && vite build",
"build:app": "vue-tsc && vite build --mode app",
"build:app2": "vite build --mode app",
"preview": "vite preview"
},
"dependencies": {
"@vitejs/plugin-legacy": "^3.0.1",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"dayjs": "^1.11.7",
"event-source-polyfill": "^1.0.31",
"js-cookie": "^3.0.1",
"tdesign-icons-vue-next": "^0.1.7",
"tdesign-vue-next": "^1.0.5",
"uuid": "^9.0.0",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-router": "^4.1.6",
"vuex": "^4.1.0"
},
"devDependencies": {
"@types/event-source-polyfill": "^1.0.1",
"@types/js-cookie": "^3.0.2",
"@types/node": "^18.11.15",
"@types/uuid": "^9.0.0",
"@vitejs/plugin-vue": "^4.0.0",
"axios": "^0.24.0",
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6",
"less": "^4.1.1",
"serve": "^14.2.0",
"terser": "^5.16.1",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vite-plugin-compression": "^0.5.1",
"vite-svg-loader": "^3.1.0",
"vue-tsc": "^1.0.11"
}
}
192.168.1.1:3000
\ No newline at end of file
<svg width="23" height="26" viewBox="0 0 23 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5919 0.5C11.9687 0.5 12.2741 0.800513 12.2741 1.17121V5.57183L14.6679 3.2164C14.9342 2.95427 15.3662 2.95427 15.6325 3.2164C15.8989 3.47853 15.8989 3.90352 15.6325 4.16564L12.2741 7.47031V11.8362L16.1225 9.64986L17.3502 5.14146C17.4477 4.78339 17.8218 4.5709 18.1857 4.66684C18.5496 4.76279 18.7655 5.13084 18.668 5.48891L17.7934 8.70062L21.6627 6.50246C21.989 6.31711 22.4062 6.4271 22.5946 6.74814C22.7829 7.06918 22.6711 7.47969 22.3449 7.66504L18.4718 9.86535L21.7417 10.7275C22.1056 10.8234 22.3216 11.1915 22.2241 11.5496C22.1266 11.9076 21.7525 12.1201 21.3886 12.0242L16.8009 10.8146L12.9584 12.9975L16.8069 15.1838L21.3886 13.9758C21.7525 13.8799 22.1266 14.0924 22.2241 14.4504C22.3216 14.8085 22.1056 15.1766 21.7417 15.2725L18.4778 16.1331L22.3471 18.3312C22.6733 18.5166 22.7851 18.9271 22.5967 19.2481C22.4084 19.5692 21.9912 19.6792 21.6649 19.4938L17.7918 17.2935L18.668 20.5111C18.7655 20.8692 18.5496 21.2372 18.1857 21.3332C17.8218 21.4291 17.4477 21.2166 17.3502 20.8585L16.1209 16.3443L12.2741 14.1589V18.5407L15.6325 21.8454C15.8989 22.1075 15.8989 22.5325 15.6325 22.7946C15.3662 23.0567 14.9342 23.0567 14.6679 22.7946L12.2741 20.4392V24.8288C12.2741 25.1995 11.9687 25.5 11.5919 25.5C11.2152 25.5 10.9098 25.1995 10.9098 24.8288V20.4435L8.52039 22.7946C8.254 23.0567 7.82209 23.0567 7.5557 22.7946C7.28931 22.5325 7.28931 22.1075 7.5557 21.8454L10.9098 18.545V14.1613L7.05762 16.3498L5.82833 20.864C5.73083 21.2221 5.35678 21.4346 4.99288 21.3387C4.62899 21.2427 4.41303 20.8747 4.51054 20.5166L5.38672 17.299L1.52332 19.4938C1.19706 19.6792 0.779871 19.5692 0.591504 19.2481C0.403136 18.9271 0.514921 18.5166 0.841184 18.3312L4.7008 16.1386L1.43681 15.278C1.07291 15.1821 0.856957 14.814 0.954464 14.4559C1.05197 14.0979 1.42601 13.8854 1.78991 13.9813L6.3717 15.1893L10.2298 12.9975L6.37765 10.8091L1.78991 12.0187C1.42601 12.1146 1.05197 11.9021 0.95446 11.5441C0.856953 11.186 1.07291 10.8179 1.43681 10.722L4.70676 9.85984L0.843363 7.66504C0.517099 7.47969 0.405314 7.06918 0.593682 6.74814C0.78205 6.4271 1.19924 6.31711 1.5255 6.50246L5.38512 8.69512L4.51054 5.4834C4.41303 5.12533 4.62898 4.75728 4.99288 4.66134C5.35678 4.56539 5.73083 4.77789 5.82833 5.13596L7.05602 9.64436L10.9098 11.8337V7.46602L7.5557 4.16564C7.28931 3.90352 7.28931 3.47853 7.5557 3.2164C7.82209 2.95427 8.254 2.95427 8.52039 3.2164L10.9098 5.56754V1.17121C10.9098 0.800513 11.2152 0.5 11.5919 0.5Z" fill="#2962FF"/>
</svg>
\ No newline at end of file
const path = require('path');
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
// const history = require('connect-history-api-fallback');
const app = express();
// 处理单页应用路由
// app.use(history());
// 代理对象地址
// 读取本地配置的ip
app.use(
'/video',
createProxyMiddleware({
target: 'http://192.168.1.19:5000',
changeOrigin: true,
// pathRewrite: {
// '^api': '',
// },
})
);
// 加载静态资源
app.use(express.static('./dist'));
// 启动服务
app.listen(3001, () => {
console.log('success => http://localhost:3001');
});
<template>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</template>
<script setup lang="ts"></script>
<style lang="less">
#app {
}
</style>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
\ No newline at end of file
<template>
<div
class="custom-loading-box"
:style="{
width: width,
height: height,
background: background,
position: position,
}"
:class="{ 'custom-is-table-box': !isTable }"
>
<div
class="ball-beat"
:style="{ top: top, left: left }"
:class="{ 'custom-is-table-child': !isTable }"
>
<div></div>
<div></div>
<div></div>
</div>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
background?: string;
position?: string | any;
width?: string;
left?: string;
height?: string;
top?: string;
isTable?: boolean;
}>(),
{
background: '',
position: 'absolute',
width: '100%',
left: '0px',
height: '100%',
top: '0px',
isTable: false,
}
);
</script>
<style lang="less" scoped>
@import '@/style/flex.less';
.custom-loading-box {
left: 0;
top: 0;
// -webkit-transform: translateY(-50%) translateX(-50%);
// transform: translateY(-50%) translateX(-50%);
z-index: 100;
box-sizing: border-box;
.ball-beat {
position: sticky;
.dja();
}
.ball-beat > div {
background-color: #4999ff;
width: 14px;
height: 14px;
border-radius: 100% !important;
margin: 2px;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
display: inline-block;
-webkit-animation: ball-beat 0.7s 0s infinite linear;
animation: ball-beat 0.7s 0s infinite linear;
z-index: 300;
}
.ball-beat > div:nth-child(2n-1) {
-webkit-animation-delay: 0.35s !important;
animation-delay: 0.35s !important;
}
}
.custom-is-table-box {
.dja();
.custom-is-table-child {
.dj();
position: relative;
}
}
@-webkit-keyframes ball-beat {
50% {
opacity: 0.2;
-webkit-transform: scale(0.75);
transform: scale(0.75);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}
@keyframes ball-beat {
50% {
opacity: 0.2;
-webkit-transform: scale(0.75);
transform: scale(0.75);
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}
</style>
.custom-t-button {
height: 36px;
}
import { defineComponent } from 'vue';
import { Button as TButton } from 'tdesign-vue-next';
import './index.less';
export default defineComponent({
setup(props, { slots, emit }) {
return () => (
<TButton class="custom-t-button">
{slots.default ? slots.default() : ''}
</TButton>
);
},
});
export interface RulesType {
required?: boolean;
type?: string;
message?: string;
min?: number;
max?: number;
validator?: Function;
}
<template>
<div class="custom-input-global">
<div
class="custom-input-box"
:class="{ 'custom-input-error': ruleError.status }"
>
<slot name="leftIcon">
<span class="left-input-icon"></span>
</slot>
<input
:type="input_type"
v-model="input_value"
class="cust-input"
:disabled="disabled"
:placeholder="placeholder"
@focus="onInputFocus"
@blur="onInputBlur"
@input="numberInput(input_value)"
/>
<slot name="rightIcon">
<span v-if="type === 'password'" class="custom-pwd-hide-button">
<PrivatePwdSvg
v-if="Cur_pwd_type === 'private'"
@click="changePwd('text')"
></PrivatePwdSvg>
<PublicPwdSvg v-else @click="changePwd('password')"></PublicPwdSvg>
</span>
</slot>
<!-- remember -->
<transition name="remember-fade">
<div
class="remember-select-box"
v-if="needSelect && input_focus && selectList.length"
>
<div
v-for="item in selectList"
:key="item.account"
class="line"
@click="QuickInputAccount(item)"
>
<div class="account">{{ item.account }}</div>
<div class="password">********</div>
</div>
</div>
</transition>
</div>
<div class="custom-input-rule" v-if="rules && rules.length">
{{ ruleError.message }}
</div>
</div>
</template>
<script lang="ts" setup>
import PrivatePwdSvg from '@/assets/svg/login/privatePwd.svg?component';
import PublicPwdSvg from '@/assets/svg/login/publicPwd.svg?component';
import { reactive, ref } from '@vue/reactivity';
import { watch } from '@vue/runtime-core';
import { RulesType } from './Interfase';
import { emailReg } from '@/constants/token';
const input_value = ref<string>('');
const props = withDefaults(
defineProps<{
type?: string;
placeholder?: string;
num?: number;
rules?: Array<RulesType>;
modelValue: string;
disabled?: boolean;
needSelect?: boolean;
selectList?: any;
}>(),
{
// 输入框类型
type: 'text',
placeholder: '',
needSelect: false,
selectList: [],
// rules: [],
disabled: false,
}
);
const emit = defineEmits([
'update:modelValue',
'submitType',
'submitAccount',
'inputChange',
]);
// 是否聚焦
const input_focus = ref(false);
// 校验显示的错误
const ruleError = reactive({
status: false,
message: '',
});
const input_type = ref<string>('text');
// 当输入框类型为password时,展示私有密码svg
const Cur_pwd_type = ref<string>('private');
// 输入框校验
const numberInput = (e: string) => {
const { type } = props;
if (type == 'number') {
input_value.value = e.replace(/[^\d.]/g, '');
}
// 提交输入事件
emit('inputChange', input_value.value);
};
// 聚焦事件
const onInputFocus = () => {
if (props.needSelect) {
input_focus.value = true;
}
};
// 失去焦点
const onInputBlur = () => {
if (props.needSelect) {
input_focus.value = false;
}
};
// 错误状态
const errorinput = (rule: any) => {
ruleError.status = true;
ruleError.message = rule.message;
// 提交失败信息
emit('submitType', false);
};
// 提交用户的账号密码
const QuickInputAccount = (item: any) => {
emit('submitAccount', item);
};
// 重置输入框状态
const ResetInput = () => {
ruleError.status = false;
ruleError.message = '';
// 提交通过信息
emit('submitType', true);
};
if (props.type == 'password') {
input_type.value = props.type;
}
//
const changePwd = (value: string) => {
if (value === 'text') {
Cur_pwd_type.value = 'public';
} else {
Cur_pwd_type.value = 'private';
}
input_type.value = value;
};
// 表单校验
const FormValidation = () => {
const { rules } = props;
let v = input_value.value;
if (rules) {
for (let i in rules) {
let rule: any = rules[i];
if (rule.required && !v) {
// 校验空input
errorinput(rule);
return;
} else {
ResetInput();
}
// 自定义校验逻辑
if (rule.validator) {
let res = rule.validator(v);
if (!res.status) {
errorinput(res);
return;
} else {
ResetInput();
}
}
// 邮箱验证模块
if (rule.type == 'email') {
if (!emailReg.test(v)) {
// 邮箱校验不通过
errorinput(rule);
return;
} else {
ResetInput();
}
}
// 输入框最小长度校验
if (rule.min && v.length < rule.min) {
errorinput(rule);
return;
} else {
ResetInput();
}
}
}
};
watch(
() => input_value.value,
(v) => {
emit('update:modelValue', v);
// 判断输入的内容是否符合校验规则
// 校验
FormValidation();
}
);
//
watch(
() => props.num,
(v) => {
// 校验
FormValidation();
}
);
watch(
() => props.modelValue,
(v) => {
input_value.value = v;
}
);
</script>
<style lang="less">
@import '@/style/flex.less';
.custom-input-global {
height: 100%;
.custom-input-box {
height: 48px;
background: var(--theme-color-15);
/* 背景线条 */
border: 1px solid var(--theme-color-19);
border-radius: 8px;
transition: all 0.3s;
position: relative;
.da();
.cust-input {
height: 100%;
width: 0;
flex: 1;
outline: none;
border: none;
border-radius: 8px;
padding-left: 12px;
background: transparent;
color: var(--theme-color-4);
}
.left-input-icon {
.da();
}
.custom-pwd-hide-button {
.da();
padding: 0 8px;
}
.remember-select-box {
z-index: 200;
position: absolute;
top: 46px;
width: 100%;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.05),
0 8px 10px 1px rgba(0, 0, 0, 6%), 0 5px 5px -3px rgba(0, 0, 0, 10%);
background: var(--theme-color-34);
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
.line {
padding: 6px 12px;
font-weight: bold;
.account {
color: var(--theme-color-35);
}
.password {
color: var(--theme-color-4);
}
}
}
}
.custom-input-error {
border-color: #f05451 !important;
transition: all 0.3s;
}
.custom-input-rule {
color: #f05451;
font-weight: 400;
font-size: 12px;
line-height: 26px;
padding-left: 4px;
min-height: 26px;
}
}
//内容打开动画
.remember-fade-enter-active {
transition: all 0.15s ease-out;
}
.remember-fade-leave-active {
transition: all 0.15s cubic-bezier(1, 0.5, 0.8, 1);
}
.remember-fade-enter-from,
.remember-fade-leave-to {
transform: translateY(-6px);
opacity: 0;
}
</style>
.custom-t-loading {
.t-icon-loading {
font-size: 18px;
}
}
import { defineComponent } from 'vue';
import './index.less';
export default defineComponent({
props: {
loading: Boolean,
},
setup(props, { slots }) {
return () => (
<t-loading class="custom-t-loading" v-show={props.loading}></t-loading>
);
},
});
.custom-swiper {
height: 240px;
}
import { defineComponent } from 'vue';
import './index.less';
export default defineComponent({
setup(props, ctx) {
return () => (
<div class="custom-swiper">
<div class="center"></div>
</div>
);
},
});
import { defineComponent } from 'vue';
// 用来自定义swiper中的内容
export default defineComponent({
setup(props, ctx) {
return () => <div class="custom-swiper-item"></div>;
},
});
export const TOKEN_NAME = 'dexnav-token';
// app版本地cookie
export const APP_COOKIE = 'app-cookie';
import { ref, Ref, onUnmounted } from 'vue';
/**
* counter utils--验证码倒计时
* @param duration
* @returns
*/
export const useCounter = (duration = 60): [Ref<number>, any, () => void] => {
let intervalTimer: any;
onUnmounted(() => {
clearInterval(intervalTimer);
});
const countDown = ref(0);
const openInterval = () => {
countDown.value = duration;
intervalTimer = setInterval(() => {
if (countDown.value > 0) {
countDown.value -= 1;
} else {
clearInterval(intervalTimer);
countDown.value = 0;
}
}, 1000);
};
const closeInterval = () => {
if (intervalTimer) {
clearInterval(intervalTimer);
}
};
return [countDown, openInterval, closeInterval];
};
import useClipboard from 'vue-clipboard3';
import { MessagePlugin } from 'tdesign-vue-next';
export default function () {
const doCopy = (keyword: string, toast: boolean = true) => {
if (!keyword) {
return;
}
const { toClipboard } = useClipboard();
toClipboard(keyword)
.then(() => {
if (toast) {
MessagePlugin.success('复制成功');
}
})
.catch((e: any) => {
if (toast) {
MessagePlugin.error('复制失败');
}
});
};
return {
doCopy,
};
}
<template>
<div class="custom-layout">
<Header v-if="route.meta.header"></Header>
<div class="custom-content">
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</div>
</div>
</template>
<script lang="ts" setup>
import Header from './header.vue';
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<style lang="less">
@import '@/style/flex.less';
.custom-layout {
height: 100%;
.dj();
flex-direction: column;
height: 100vh;
background: #181818;
.custom-content {
flex: 1;
max-height: calc(100vh - 60px);
overflow: auto;
box-sizing: border-box;
width: 1597px;
margin: 0 auto;
}
}
</style>
<template>
<div class="custom-layout-head">
<div class="layout-head-left">
<span>AI GPT</span>
<div
class="layout-chose-button"
v-for="item in btns"
:key="item.path"
@click="changeBtn(item)"
:class="{ active: item.path === currentBtn }"
>
{{ item.label }}
</div>
</div>
<!-- <div class="layout-head-right">
<t-button v-if="token" class="logout" @click="logout"> 退出 </t-button>
</div> -->
</div>
</template>
<script lang="ts" setup>
import { ref } from '@vue/reactivity';
import { computed } from '@vue/runtime-core';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { useLogout } from '@/utils/api/userApi';
import { MessagePlugin, Button as TButton } from 'tdesign-vue-next';
import { watch } from 'vue';
const store = useStore();
const token = computed(() => store.getters['user/token']);
const route = useRoute();
const router = useRouter();
const currentBtn = ref(route.path);
watch(
() => route.path,
(v) => {
currentBtn.value = v;
}
);
const btns = [
{
label: '首页',
path: '/',
},
{
label: '产品',
path: '/Products',
},
{
label: 'AI聊天',
path: '/AIChat',
},
{
label: 'AI绘画',
path: '/AIPainting',
},
{
label: 'AI视频',
path: '/AIVideo',
},
// {
// label: '图片生成_自动化',
// path: '/',
// },
// {
// label: 'GPT对话',
// path: '/gpt',
// },
// {
// label: '图片生成',
// path: '/image',
// },
// {
// label: '图片生成stable',
// path: '/Image_stable',
// },
];
const logout = async () => {
try {
let res: any = await useLogout();
if (res.code == 0) {
store.commit('user/removeToken');
MessagePlugin.success('退出成功');
router.replace({
path: '/',
});
}
} catch (e) {
console.log(e);
}
};
const changeBtn = (item: any) => {
router.replace({
path: item.path,
});
};
</script>
<style lang="less">
@import '@/style/flex.less';
.custom-layout-head {
height: 67px;
background: #181818;
border-bottom: 1px solid #464646;
.dja(space-between);
padding: 0 12px;
.layout-head-left {
.da();
span {
font-style: normal;
font-weight: 800;
font-size: 40px;
color: #e6e6e6;
line-height: 48px;
white-space: nowrap;
}
.layout-chose-button {
margin-left: 50px;
font-style: normal;
font-weight: 600;
font-size: 18px;
color: #ffffff;
transition: all 0.1s;
cursor: pointer;
white-space: nowrap;
}
.active {
color: #00f9f9;
transition: all 0.1s;
}
}
.layout-head-right {
.da();
.logout {
background: #fd1753;
border: none;
margin-right: 20px;
--ripple-color: #fd6053 !important;
}
}
}
</style>
import { createApp } from 'vue';
import '@/style/index.less';
import router from './router';
import store from './store';
import App from './App.vue';
// 引入组件库全局样式资源
import 'tdesign-vue-next/es/style/index.css';
import '@/style/ui.less';
// 全局守卫
import './router/auth';
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');
<template>
<div class="custom-gpt-page">
<div class="message-box"></div>
<div class="input">
<t-input v-model="gpt_input"></t-input>
<t-button @click="submit">发送</t-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { Input as TInput, Button as TButton } from 'tdesign-vue-next';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { submitmessage } from '@/utils/api/gpt';
import { getUserCookie } from '@/utils/api/userApi';
import { ref } from 'vue';
const gpt_input = ref('');
// 提交消息
const submit = async () => {
try {
let res: any = await submitmessage({
prompt: gpt_input.value,
regen: false,
});
if (res.code == 0 && res.data.chat_id) {
console.log('发送成功');
// 发起会话
const eventSource = new EventSourcePolyfill(
`/api/gpt/stream?chat_id=${res.data.chat_id}`,
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
eventSource.onmessage = function (e: any) {
if (e.data == '[DONE]') {
eventSource.close();
} else {
let word = JSON.parse(e.data).choices[0].delta.content;
if (word !== undefined) {
console.log(word);
}
}
};
eventSource.onerror = function (e: any) {
console.log(e);
eventSource.close();
console.log('报错了');
};
}
} catch (e) {
console.log(e);
}
};
</script>
<style lang="less">
.custom-gpt-page {
padding: 40px;
box-sizing: border-box;
.message-box {
}
.input {
display: flex;
& > :nth-child(2) {
margin-left: 12px;
}
}
}
</style>
import { defineComponent } from 'vue';
import CustomSwiper from '@/components/custom/swiper';
export default defineComponent({
setup() {
return () => (
<div>
<CustomSwiper></CustomSwiper>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:footer="false"
class="custom-img-dialog"
>
<template #header>
<div>请耐心等待图片生成</div>
</template>
<template #body>
<div class="custom-dialog-body">
<div class="content narrow-scrollbar">
<div v-for="(item, index) in list" :key="item.id">
<div class="img-box">
<img class="img" :src="item.img" alt="" />
<template v-if="item.split_img">
<img class="split-img img" :src="item.split_img" alt="" />
</template>
<template v-else-if="item.loading">
<div class="split_img_loading">
<t-loading size="24px"></t-loading>
</div>
</template>
</div>
<div class="btns">
<t-button
v-for="it in btns"
:key="it.value"
@click="to_split(item.id, it.value, index)"
>{{ it.label }}</t-button
>
</div>
<div>
---------------------------------------------------------------
</div>
</div>
</div>
<Animation v-show="dialogloading"></Animation>
</div>
</template>
</t-dialog>
</template>
<script lang="ts" setup>
import {
Dialog as TDialog,
Button as TButton,
Loading as TLoading,
} from 'tdesign-vue-next';
import { PropType, ref, watch } from 'vue';
import Animation from '@/components/Animation.vue';
const btns = [
{
label: '第一张',
value: 1,
},
{
label: '第二张',
value: 2,
},
{
label: '第三张',
value: 3,
},
{
label: '第四张',
value: 4,
},
];
const props = defineProps({
modelValue: Boolean,
list: Array as PropType<any[]>,
dialogloading: Boolean,
});
const emit = defineEmits(['update:modelValue', 'SubmitSplit']);
const visible = ref(props.modelValue);
const to_split = (prompt_id: number, click_id: number, index: number) => {
if (prompt_id && click_id) {
// 发送切割图片任务
emit('SubmitSplit', {
prompt_id: prompt_id,
click_id: click_id,
index: index,
});
}
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-img-dialog {
.t-dialog {
width: 50vw;
max-width: 50vw;
.t-dialog__body {
overflow: hidden;
}
}
.custom-dialog-body {
height: 500px;
box-sizing: border-box;
.content {
height: 450px;
overflow-y: auto;
.img-box {
display: flex;
justify-content: space-between;
.img {
width: 300px;
height: 300px;
}
.split_img_loading {
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
}
.btns {
margin: 20px 0;
& > :not(:nth-child(1)) {
margin-left: 12px;
}
}
}
.custom-loading-box {
position: relative !important;
width: auto !important;
height: 50px !important;
}
}
}
</style>
.custom-img-to-img-content {
.keyword {
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
& > :nth-child(2) {
margin-bottom: 30px;
}
.t-textarea {
.t-textarea__inner {
height: 200px;
resize: none;
}
}
}
}
import { defineComponent, PropType, ref, watch } from 'vue';
import './index.less';
import { Textarea as TTextarea } from 'tdesign-vue-next';
export default defineComponent({
props: {
modelValue: {
type: String as PropType<string>,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const keyword = ref('');
watch(
() => keyword.value,
(v) => {
emit('update:modelValue', v);
}
);
return () => (
<div class="custom-img-to-img-content">
<div class="keyword">
<div class="label">关键词</div>
<TTextarea autosize={false} v-model={keyword.value}></TTextarea>
</div>
</div>
);
},
});
.custom-real-upload {
margin-top: 30px;
background: #ffffff;
box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.25);
border-radius: 10px;
height: 330px;
padding: 20px 46px;
position: relative;
display: flex;
justify-content: center;
flex-direction: column;
.real-upload-close-icon {
position: absolute;
right: 12px;
top: 12px;
cursor: pointer;
}
.real-upload-content {
margin-top: 6px;
display: flex;
justify-content: space-between;
.custom-real-upload-component {
width: 360px;
height: 200px;
border: 1px dashed #000000;
.t-upload {
width: 100%;
height: 100%;
.custom-upload-click-box {
border-radius: 8px;
width: 360px;
height: 200px;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
.title {
font-weight: 600;
font-size: 18px;
color: black;
}
.title2 {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
.custom-chose-file {
background: #fd1753;
border-radius: 8px;
border: none;
width: 200px;
height: 46px;
--ripple-color: none !important;
}
}
.t-upload__dragger {
padding: 0;
border: none;
width: 360px;
height: 200px;
}
}
.custom-uploading-stauts {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.uploading-title {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
}
.custom-UploadSuccess-stauts {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.UploadSuccess-img {
width: 100%;
height: 100%;
}
.title1 {
font-weight: 600;
font-size: 18px;
color: #000000;
}
}
}
}
.custom-real-upload-footer {
flex: 1;
display: flex;
align-items: center;
.t-button {
width: 164px;
height: 46px;
border: none;
--ripple-color: none !important;
border-radius: 8px;
font-size: 18px;
}
.submit {
background: #ebebeb;
color: #9a9a9a;
cursor: not-allowed;
}
.active {
background: #fd1753;
color: #ffffff;
cursor: pointer;
}
.reset-button {
border: 1px solid #dbdbdb;
background: white;
color: #000000;
margin-left: 12px;
}
}
}
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
import { computed, defineComponent, reactive, ref } from 'vue';
import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip.svg?component';
import {
MessagePlugin,
Button as TButton,
Upload as TUpload,
Progress as TProgress,
UploadFile,
RequestMethodResponse,
} from 'tdesign-vue-next';
import { useStore } from 'vuex';
import { getUserCookie } from '@/utils/api/userApi';
import request from '@/utils/otherRequest';
import { v4 } from 'uuid';
export default defineComponent({
props: {
modelValue: String,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const store = useStore();
// 后台配置的地址
const adminConfigUrl = computed(() => store.getters['user/getadminConfig']);
// 上传策略
const uploadStrategy = computed(
() => store.getters['user/getuploadStrategy']
);
const files = ref([]);
// 文件地址
const Curfile = reactive({
url: '',
status: 0,
// 当前上传模块提交的状态
uploadStatus: false,
});
const actionUrl = ref('');
// 上传进度条
const percentage = ref(0);
// 定时器
let percentageInterval: any = null;
// 上传进度定时器
const openpercentage = () => {
// 开启一个定时器,模拟上传进度
percentage.value = 0;
percentageInterval = setInterval(() => {
if (percentage.value == 99) {
return;
}
percentage.value += 1;
}, 100);
};
const beforeUpload = (file: File) => {
return true;
};
const handleFail = ({ file }: any) => {
MessagePlugin.error(`文件 ${file.name} 上传失败`);
};
// 上传成功回调
const UploadSuccessCallback = (uuid: any, url: any) => {
// 关闭定时器
window.clearInterval(percentageInterval);
MessagePlugin.success('上传成功');
// 将将完整url传给父组件
Curfile.url = url;
// 成功2
Curfile.status = 2;
emit('update:modelValue', Curfile.url);
};
// 上传失败回调
const UploadErrorCallback = () => {
// 关闭定时器
window.clearInterval(percentageInterval);
Curfile.url = '';
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
MessagePlugin.warning('上传失败');
};
// 内网上传-Intranet
const IntranetUpload = (file: any) => {
openpercentage();
return new Promise((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
if (import.meta.env.MODE == 'production') {
// 线上地址使用完整url
url = adminConfigUrl.value + 'video/' + uuid + '.png';
// url = `http://192.168.1.19:5000/video/` + uuid + '.png';
} else if (import.meta.env.MODE == 'app') {
// app
url = '/video/' + uuid + '.png';
} else {
// 本地
url = '/video/' + uuid + '.png';
}
setTimeout(() => {
request.put(url, file[0].raw).then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
let url = adminConfigUrl.value + 'video/' + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
});
}, 1000);
});
};
// 外网上传-func
const ExtranetUpload = (file: any) => {
openpercentage();
return new Promise<RequestMethodResponse>((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
const { config } = uploadStrategy.value;
url = 'https://' + config.host;
setTimeout(() => {
let formData = new FormData();
formData.append('key', config.dir + uuid + '.png');
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
// formData.append('name', uuid + '.png');
formData.append('file', file[0].raw);
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
// 外网url
let url = config.domain + config.dir + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
})
.catch((e) => {
console.log(e);
});
}, 1000);
});
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
};
// 未上传
const notUploadHtml = () => {
return (
<TUpload
v-model={files.value}
method="PUT"
requestMethod={requestSuccessMethod}
action={actionUrl.value}
headers={{
authorization: `Bearer ${getUserCookie()}`,
}}
accept={'video'}
theme="custom"
before-upload={beforeUpload}
multiple
max={1}
draggable={true}
onfail={handleFail}
>
<div class="custom-upload-click-box">
<div class="title">选择图片</div>
<div class="title2">或拖拽图片到此处</div>
<div>
<UploadTip></UploadTip>
</div>
<TButton class="custom-chose-file">选择文件</TButton>
</div>
</TUpload>
);
};
// 上传中
const UploadingHtml = () => {
return (
<div class="custom-uploading-stauts">
<TProgress
theme="circle"
percentage={percentage.value}
size={'small'}
/>
<div class="uploading-title">正在上传</div>
</div>
);
};
const UploadSuccess = () => {
return (
<div class="custom-UploadSuccess-stauts">
<img class="UploadSuccess-img" src={Curfile.url} alt="" />
</div>
);
};
// 获取当前上传状态
const currentUploadStatus = () => {
if (Curfile.status == 0) {
return notUploadHtml();
} else if (Curfile.status == 1) {
// 上传中
return UploadingHtml();
} else {
// 上传完成
return UploadSuccess();
}
};
return () => (
<div>
<div class="label">原图</div>
<div class="custom-real-upload">
<div class="real-upload-content">
<div class="custom-real-upload-component">
{currentUploadStatus()}
</div>
</div>
</div>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:confirm-on-enter="true"
:on-cancel="onCancel"
:on-confirm="onConfirmAnother"
>
<template #header>
<div>请确认是否提交</div>
</template>
<template #body> </template>
</t-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Dialog as TDialog } from 'tdesign-vue-next';
const props = defineProps({
modelValue: Boolean,
});
const emit = defineEmits(['update:modelValue', 'Submit']);
const visible = ref(false);
const onCancel = (context: any) => {
//
console.log('onCancel');
};
// 确认按钮
const onConfirmAnother = (context: any) => {
// 通知页面发起请求
visible.value = false;
emit('Submit');
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-confirm-dialog {
}
</style>
.img-to-img-page {
box-sizing: border-box;
padding: 30px;
.submit-button {
background: #fd1753;
border: none;
height: 36px;
--ripple-color: none !important;
margin-top: 20px;
&:hover {
background: #fd1753;
}
}
}
import { defineComponent, onMounted, reactive, ref } from 'vue';
import './index.less';
import EnterKeywords from './components/EnterKeywords';
import { useStore } from 'vuex';
import UploadImg from './components/UploadImg';
import { Button as TButton } from 'tdesign-vue-next';
import { Tasks } from '@/utils/api/Task';
import { filterRepeatTimestamp } from '@/utils/tool';
import {
useSubmitTask,
IntervalCheckTask,
SubmitSplitImgTask,
get_split_img_status,
} from '@/utils/api/userApi';
import CustomDialog from './components/Dialog/index.vue';
import Animation from '@/components/Animation.vue';
import ConfirmDIalog from './components/confimDialog.vue';
import { new_task_submit } from '@/utils/api/wf_task';
// wf
export default defineComponent({
setup(props, ctx) {
const store = useStore();
let custom_interval: any = null;
let split_interval: any = null;
const task_id = ref();
// 确认弹窗
const confirmDialogVisible = ref(false);
const prompt_num = ref();
const task_result_list = reactive<{
list: any[];
}>({
list: [],
});
// 关键词
const keywords = ref('');
const loading = ref(false);
const dialogloading = ref(true);
// 图片链接
const Img_url = ref('');
// 弹窗状态
const DialogVisible = ref(false);
// 测试
const test_num = ref(1);
onMounted(() => {
store.dispatch('user/AdminConfig');
});
// 获取返回的四宫格图片
const getTask = async () => {
try {
let res: any = await IntervalCheckTask({
task_id: task_id.value,
});
if (res.code == 0 && res.data.length) {
res.data.forEach((item: any) => {
if (item.loading === undefined) {
item.loading = false;
}
});
if (res.data.length > task_result_list.list.length) {
// 合并去重
filterRepeatTimestamp(task_result_list.list, res.data);
}
if (prompt_num.value == task_result_list.list.length) {
// 关闭定时器
closeInterval();
dialogloading.value = false;
} else {
dialogloading.value = true;
}
}
} catch (e) {
console.log(e);
}
};
// 获取返回的切割图片
const get_split_img = async (prompt_id: number) => {
try {
let res: any = await get_split_img_status({
prompt_id: prompt_id,
});
if (res.code == 0 && res.data.cut_img && res.data.id) {
// 切割成功--找到list对应的id,将cut_img添加进去
task_result_list.list.forEach((item: any) => {
if (item.id == res.data.id) {
item.split_img = res.data.cut_img;
item.loading = false;
}
});
// 关闭定时器
closeSplitTaskInterval();
}
} catch (e) {
console.log(e);
}
};
// 打开定时器
const openInterval = () => {
custom_interval = window.setInterval(() => {
getTask();
}, 3000);
};
// 打开图片切割任务定时器
const openSplitTaskInterval = (prompt_id: number) => {
split_interval = window.setInterval(() => {
get_split_img(prompt_id);
}, 3000);
};
// 关闭定时器
const closeInterval = () => {
if (custom_interval) {
window.clearInterval(custom_interval);
clearInterval(custom_interval);
custom_interval = null;
}
};
// 关闭图片切割任务定时器
const closeSplitTaskInterval = () => {
if (split_interval) {
window.clearInterval(split_interval);
clearInterval(split_interval);
split_interval = null;
}
};
const submit_before = () => {
if (!keywords.value) {
return;
}
// 打开的确认弹窗
confirmDialogVisible.value = true;
// submit();
};
// 确认对话框的确认事件
const CanSubmit = () => {
if (!keywords.value) {
return;
}
submit();
};
// 提交任务
const submit = async () => {
// prompt_img需要传数组
let params: any = {
type: Img_url.value ? Tasks.img_to_img : Tasks.text_to_img,
prompt: keywords.value,
prompt_img: [Img_url.value],
prompt_num: 5,
};
try {
loading.value = true;
// 请求
let res: any = await new_task_submit(params);
if (res.code == 0) {
task_id.value = res.data.task_id;
prompt_num.value = res.data.prompt_num;
// 打开弹窗
DialogVisible.value = true;
// 清空已返回的图片
task_result_list.list = [];
// 开启轮询
openInterval();
}
loading.value = false;
} catch (e) {
console.log(e);
loading.value = false;
}
};
// 提交切割任务
const SubmitSplit = async ({ prompt_id, click_id, index }: any) => {
try {
let res: any = await SubmitSplitImgTask({
prompt_id: prompt_id,
cut_id: click_id,
type: 2,
});
if (res.code == 0) {
// 检测切割任务是否完成
openSplitTaskInterval(res.data.prompt_id);
// 将对应的item.loading打开
task_result_list.list[index].loading = true;
}
} catch (e) {
console.log(e);
}
};
return () => (
<div class="img-to-img-page">
<div class="tips">提示:可不上传图片</div>
<EnterKeywords v-model={keywords.value}></EnterKeywords>
<UploadImg v-model={Img_url.value}></UploadImg>
<TButton
onClick={submit_before}
class={['submit-button', !keywords.value ? 'disabled' : '']}
>
提交
</TButton>
<CustomDialog
v-model={DialogVisible.value}
list={task_result_list.list}
dialogloading={dialogloading.value}
onSubmitSplit={SubmitSplit}
></CustomDialog>
<Animation position={'fixed'} v-show={loading.value}></Animation>
<ConfirmDIalog
v-model={confirmDialogVisible.value}
onSubmit={CanSubmit}
></ConfirmDIalog>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:footer="false"
class="custom-img-dialog"
>
<template #header>
<div>请耐心等待图片生成</div>
</template>
<template #body>
<div class="custom-dialog-body">
<div class="content narrow-scrollbar">
<div v-for="(item, index) in list" :key="item.id">
<div class="img-box">
<img class="img" :src="item.img" alt="" />
<template v-if="item.split_img">
<img class="split-img img" :src="item.split_img" alt="" />
</template>
<template v-else-if="item.loading">
<div class="split_img_loading">
<t-loading size="24px"></t-loading>
</div>
</template>
</div>
<div class="btns">
<t-button
v-for="it in btns"
:key="it.value"
@click="to_split(item.id, it.value, index)"
>{{ it.label }}</t-button
>
</div>
<div>
---------------------------------------------------------------
</div>
</div>
</div>
<Animation v-show="dialogloading"></Animation>
</div>
</template>
</t-dialog>
</template>
<script lang="ts" setup>
import {
Dialog as TDialog,
Button as TButton,
Loading as TLoading,
} from 'tdesign-vue-next';
import { PropType, ref, watch } from 'vue';
import Animation from '@/components/Animation.vue';
const btns = [
{
label: '第一张',
value: 1,
},
{
label: '第二张',
value: 2,
},
{
label: '第三张',
value: 3,
},
{
label: '第四张',
value: 4,
},
];
const props = defineProps({
modelValue: Boolean,
list: Array as PropType<any[]>,
dialogloading: Boolean,
});
const emit = defineEmits(['update:modelValue', 'SubmitSplit']);
const visible = ref(props.modelValue);
const to_split = (prompt_id: number, click_id: number, index: number) => {
if (prompt_id && click_id) {
// 发送切割图片任务
emit('SubmitSplit', {
prompt_id: prompt_id,
click_id: click_id,
index: index,
});
}
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-img-dialog {
.t-dialog {
width: 50vw;
max-width: 50vw;
.t-dialog__body {
overflow: hidden;
}
}
.custom-dialog-body {
height: 500px;
box-sizing: border-box;
.content {
height: 450px;
overflow-y: auto;
.img-box {
display: flex;
justify-content: space-between;
.img {
width: 300px;
height: 300px;
}
.split_img_loading {
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
}
.btns {
margin: 20px 0;
& > :not(:nth-child(1)) {
margin-left: 12px;
}
}
}
.custom-loading-box {
position: relative !important;
width: auto !important;
height: 50px !important;
}
}
}
</style>
.custom-img-to-img-content {
.keyword {
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
& > :nth-child(2) {
margin-bottom: 30px;
}
.t-textarea {
.t-textarea__inner {
height: 200px;
resize: none;
}
}
}
}
import { defineComponent, PropType, ref, watch } from 'vue';
import './index.less';
import { Textarea as TTextarea } from 'tdesign-vue-next';
export default defineComponent({
props: {
modelValue: {
type: String as PropType<string>,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const keyword = ref('');
watch(
() => keyword.value,
(v) => {
emit('update:modelValue', v);
}
);
return () => (
<div class="custom-img-to-img-content">
<div class="keyword">
<div class="label">关键词</div>
<TTextarea autosize={false} v-model={keyword.value}></TTextarea>
</div>
</div>
);
},
});
.custom-real-upload {
margin-top: 30px;
background: #ffffff;
box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.25);
border-radius: 10px;
height: 330px;
padding: 20px 46px;
position: relative;
display: flex;
justify-content: center;
flex-direction: column;
.real-upload-close-icon {
position: absolute;
right: 12px;
top: 12px;
cursor: pointer;
}
.real-upload-content {
margin-top: 6px;
display: flex;
justify-content: space-between;
.custom-real-upload-component {
width: 360px;
height: 200px;
border: 1px dashed #000000;
.t-upload {
width: 100%;
height: 100%;
.custom-upload-click-box {
border-radius: 8px;
width: 360px;
height: 200px;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
.title {
font-weight: 600;
font-size: 18px;
color: black;
}
.title2 {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
.custom-chose-file {
background: #fd1753;
border-radius: 8px;
border: none;
width: 200px;
height: 46px;
--ripple-color: none !important;
}
}
.t-upload__dragger {
padding: 0;
border: none;
width: 360px;
height: 200px;
}
}
.custom-uploading-stauts {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.uploading-title {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
}
.custom-UploadSuccess-stauts {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.UploadSuccess-img {
width: 100%;
height: 100%;
}
.title1 {
font-weight: 600;
font-size: 18px;
color: #000000;
}
}
}
}
.custom-real-upload-footer {
flex: 1;
display: flex;
align-items: center;
.t-button {
width: 164px;
height: 46px;
border: none;
--ripple-color: none !important;
border-radius: 8px;
font-size: 18px;
}
.submit {
background: #ebebeb;
color: #9a9a9a;
cursor: not-allowed;
}
.active {
background: #fd1753;
color: #ffffff;
cursor: pointer;
}
.reset-button {
border: 1px solid #dbdbdb;
background: white;
color: #000000;
margin-left: 12px;
}
}
}
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
import { computed, defineComponent, reactive, ref } from 'vue';
import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip.svg?component';
import {
MessagePlugin,
Button as TButton,
Upload as TUpload,
Progress as TProgress,
UploadFile,
RequestMethodResponse,
} from 'tdesign-vue-next';
import { useStore } from 'vuex';
import { getUserCookie } from '@/utils/api/userApi';
import request from '@/utils/otherRequest';
import { v4 } from 'uuid';
export default defineComponent({
props: {
modelValue: String,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const store = useStore();
// 后台配置的地址
const adminConfigUrl = computed(() => store.getters['user/getadminConfig']);
// 上传策略
const uploadStrategy = computed(
() => store.getters['user/getuploadStrategy']
);
const files = ref([]);
// 文件地址
const Curfile = reactive({
url: '',
status: 0,
// 当前上传模块提交的状态
uploadStatus: false,
});
const actionUrl = ref('');
// 上传进度条
const percentage = ref(0);
// 定时器
let percentageInterval: any = null;
// 上传进度定时器
const openpercentage = () => {
// 开启一个定时器,模拟上传进度
percentage.value = 0;
percentageInterval = setInterval(() => {
if (percentage.value == 99) {
return;
}
percentage.value += 1;
}, 100);
};
const beforeUpload = (file: File) => {
return true;
};
const handleFail = ({ file }: any) => {
MessagePlugin.error(`文件 ${file.name} 上传失败`);
};
// 上传成功回调
const UploadSuccessCallback = (uuid: any, url: any) => {
// 关闭定时器
window.clearInterval(percentageInterval);
MessagePlugin.success('上传成功');
// 将将完整url传给父组件
Curfile.url = url;
// 成功2
Curfile.status = 2;
emit('update:modelValue', Curfile.url);
};
// 上传失败回调
const UploadErrorCallback = () => {
// 关闭定时器
window.clearInterval(percentageInterval);
Curfile.url = '';
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
MessagePlugin.warning('上传失败');
};
// 内网上传-Intranet
const IntranetUpload = (file: any) => {
openpercentage();
return new Promise((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
if (import.meta.env.MODE == 'production') {
// 线上地址使用完整url
url = adminConfigUrl.value + 'video/' + uuid + '.png';
// url = `http://192.168.1.19:5000/video/` + uuid + '.png';
} else if (import.meta.env.MODE == 'app') {
// app
url = '/video/' + uuid + '.png';
} else {
// 本地
url = '/video/' + uuid + '.png';
}
setTimeout(() => {
request.put(url, file[0].raw).then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
let url = adminConfigUrl.value + 'video/' + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
});
}, 1000);
});
};
// 外网上传-func
const ExtranetUpload = (file: any) => {
openpercentage();
return new Promise<RequestMethodResponse>((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
const { config } = uploadStrategy.value;
url = 'https://' + config.host;
setTimeout(() => {
let formData = new FormData();
formData.append('key', config.dir + uuid + '.png');
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
// formData.append('name', uuid + '.png');
formData.append('file', file[0].raw);
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
// 外网url
let url = config.domain + config.dir + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
})
.catch((e) => {
console.log(e);
});
}, 1000);
});
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
};
// 未上传
const notUploadHtml = () => {
return (
<TUpload
v-model={files.value}
method="PUT"
requestMethod={requestSuccessMethod}
action={actionUrl.value}
headers={{
authorization: `Bearer ${getUserCookie()}`,
}}
accept={'video'}
theme="custom"
before-upload={beforeUpload}
multiple
max={1}
draggable={true}
onfail={handleFail}
>
<div class="custom-upload-click-box">
<div class="title">选择图片</div>
<div class="title2">或拖拽图片到此处</div>
<div>
<UploadTip></UploadTip>
</div>
<TButton class="custom-chose-file">选择文件</TButton>
</div>
</TUpload>
);
};
// 上传中
const UploadingHtml = () => {
return (
<div class="custom-uploading-stauts">
<TProgress
theme="circle"
percentage={percentage.value}
size={'small'}
/>
<div class="uploading-title">正在上传</div>
</div>
);
};
const UploadSuccess = () => {
return (
<div class="custom-UploadSuccess-stauts">
<img class="UploadSuccess-img" src={Curfile.url} alt="" />
</div>
);
};
// 获取当前上传状态
const currentUploadStatus = () => {
if (Curfile.status == 0) {
return notUploadHtml();
} else if (Curfile.status == 1) {
// 上传中
return UploadingHtml();
} else {
// 上传完成
return UploadSuccess();
}
};
return () => (
<div>
<div class="label">原图</div>
<div class="custom-real-upload">
<div class="real-upload-content">
<div class="custom-real-upload-component">
{currentUploadStatus()}
</div>
</div>
</div>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:confirm-on-enter="true"
:on-cancel="onCancel"
:on-confirm="onConfirmAnother"
>
<template #header>
<div>请确认是否提交</div>
</template>
<template #body> </template>
</t-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Dialog as TDialog } from 'tdesign-vue-next';
const props = defineProps({
modelValue: Boolean,
});
const emit = defineEmits(['update:modelValue', 'Submit']);
const visible = ref(false);
const onCancel = (context: any) => {
//
console.log('onCancel');
};
// 确认按钮
const onConfirmAnother = (context: any) => {
// 通知页面发起请求
visible.value = false;
emit('Submit');
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-confirm-dialog {
}
</style>
.img-to-img-page {
box-sizing: border-box;
padding: 30px;
.submit-button {
background: #fd1753;
border: none;
height: 36px;
--ripple-color: none !important;
margin-top: 20px;
&:hover {
background: #fd1753;
}
}
}
import { defineComponent, onMounted, reactive, ref } from 'vue';
import './index.less';
import EnterKeywords from './components/EnterKeywords';
import { useStore } from 'vuex';
import UploadImg from './components/UploadImg';
import { Button as TButton } from 'tdesign-vue-next';
import { Tasks } from '@/utils/api/Task';
import { filterRepeatTimestamp } from '@/utils/tool';
import {
useSubmitTask,
IntervalCheckTask,
SubmitSplitImgTask,
get_split_img_status,
} from '@/utils/api/userApi';
import CustomDialog from './components/Dialog/index.vue';
import Animation from '@/components/Animation.vue';
import ConfirmDIalog from './components/confimDialog.vue';
// wf
export default defineComponent({
setup(props, ctx) {
const store = useStore();
let custom_interval: any = null;
let split_interval: any = null;
const task_id = ref();
// 确认弹窗
const confirmDialogVisible = ref(false);
const prompt_num = ref();
const task_result_list = reactive<{
list: any[];
}>({
list: [],
});
// 关键词
const keywords = ref('');
const loading = ref(false);
const dialogloading = ref(true);
// 图片链接
const Img_url = ref('');
// 弹窗状态
const DialogVisible = ref(false);
// 测试
const test_num = ref(1);
onMounted(() => {
store.dispatch('user/AdminConfig');
});
// 获取返回的四宫格图片
const getTask = async () => {
try {
let res: any = await IntervalCheckTask({
task_id: task_id.value,
});
if (res.code == 0 && res.data.length) {
res.data.forEach((item: any) => {
if (item.loading === undefined) {
item.loading = false;
}
});
if (res.data.length > task_result_list.list.length) {
// 合并去重
filterRepeatTimestamp(task_result_list.list, res.data);
}
if (prompt_num.value == task_result_list.list.length) {
// 关闭定时器
closeInterval();
dialogloading.value = false;
} else {
dialogloading.value = true;
}
}
} catch (e) {
console.log(e);
}
};
// 获取返回的切割图片
const get_split_img = async (prompt_id: number) => {
try {
let res: any = await get_split_img_status({
prompt_id: prompt_id,
});
if (res.code == 0 && res.data.cut_img && res.data.id) {
// 切割成功--找到list对应的id,将cut_img添加进去
task_result_list.list.forEach((item: any) => {
if (item.id == res.data.id) {
item.split_img = res.data.cut_img;
item.loading = false;
}
});
// 关闭定时器
closeSplitTaskInterval();
}
} catch (e) {
console.log(e);
}
};
// 打开定时器
const openInterval = () => {
custom_interval = window.setInterval(() => {
getTask();
}, 3000);
};
// 打开图片切割任务定时器
const openSplitTaskInterval = (prompt_id: number) => {
split_interval = window.setInterval(() => {
get_split_img(prompt_id);
}, 3000);
};
// 关闭定时器
const closeInterval = () => {
if (custom_interval) {
window.clearInterval(custom_interval);
}
};
// 关闭图片切割任务定时器
const closeSplitTaskInterval = () => {
if (split_interval) {
window.clearInterval(split_interval);
}
};
const submit_before = () => {
if (!keywords.value) {
return;
}
// 打开的确认弹窗
confirmDialogVisible.value = true;
// submit();
};
// 确认对话框的确认事件
const CanSubmit = () => {
if (!keywords.value) {
return;
}
submit();
};
// 提交任务
const submit = async () => {
// prompt_img需要传数组
let params: any = {
type: Img_url.value ? Tasks.img_to_img : Tasks.text_to_img,
prompt: keywords.value,
prompt_img: [Img_url.value],
prompt_num: 5,
};
try {
loading.value = true;
// 请求
let res: any = await useSubmitTask(params);
if (res.code == 0) {
task_id.value = res.data.task_id;
prompt_num.value = res.data.prompt_num;
// 打开弹窗
DialogVisible.value = true;
// 清空已返回的图片
task_result_list.list = [];
// 开启轮询
openInterval();
}
loading.value = false;
} catch (e) {
console.log(e);
loading.value = false;
}
};
// 提交切割任务
const SubmitSplit = async ({ prompt_id, click_id, index }: any) => {
try {
let res: any = await SubmitSplitImgTask({
prompt_id: prompt_id,
cut_id: click_id,
});
if (res.code == 0) {
// 检测切割任务是否完成
openSplitTaskInterval(res.data.prompt_id);
// 将对应的item.loading打开
task_result_list.list[index].loading = true;
}
} catch (e) {
console.log(e);
}
};
return () => (
<div class="img-to-img-page">
<div class="tips">提示:可不上传图片</div>
<EnterKeywords v-model={keywords.value}></EnterKeywords>
<UploadImg v-model={Img_url.value}></UploadImg>
<TButton
onClick={submit_before}
class={['submit-button', !keywords.value ? 'disabled' : '']}
>
提交
</TButton>
<CustomDialog
v-model={DialogVisible.value}
list={task_result_list.list}
dialogloading={dialogloading.value}
onSubmitSplit={SubmitSplit}
></CustomDialog>
<Animation position={'fixed'} v-show={loading.value}></Animation>
<ConfirmDIalog
v-model={confirmDialogVisible.value}
onSubmit={CanSubmit}
></ConfirmDIalog>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:footer="false"
class="custom-img-dialog"
>
<template #header>
<div>请耐心等待图片生成</div>
</template>
<template #body>
<div class="custom-dialog-body">
<div class="content narrow-scrollbar">
<div v-for="(item, index) in list" :key="item.id">
<div class="img-box">
<img class="img" :src="item.img" alt="" />
<template v-if="item.split_img">
<img class="split-img img" :src="item.split_img" alt="" />
</template>
<template v-else-if="item.loading">
<div class="split_img_loading">
<t-loading size="24px"></t-loading>
</div>
</template>
</div>
<div class="btns">
<t-button
v-for="it in btns"
:key="it.value"
@click="to_split(item.id, it.value, index)"
>{{ it.label }}</t-button
>
</div>
<div>
---------------------------------------------------------------
</div>
</div>
</div>
<Animation v-show="dialogloading"></Animation>
</div>
</template>
</t-dialog>
</template>
<script lang="ts" setup>
import {
Dialog as TDialog,
Button as TButton,
Loading as TLoading,
} from 'tdesign-vue-next';
import { PropType, ref, watch } from 'vue';
import Animation from '@/components/Animation.vue';
const btns = [
{
label: '第一张',
value: 1,
},
{
label: '第二张',
value: 2,
},
{
label: '第三张',
value: 3,
},
{
label: '第四张',
value: 4,
},
];
const props = defineProps({
modelValue: Boolean,
list: Array as PropType<any[]>,
dialogloading: Boolean,
});
const emit = defineEmits(['update:modelValue', 'SubmitSplit']);
const visible = ref(props.modelValue);
const to_split = (prompt_id: number, click_id: number, index: number) => {
if (prompt_id && click_id) {
// 发送切割图片任务
emit('SubmitSplit', {
prompt_id: prompt_id,
click_id: click_id,
index: index,
});
}
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-img-dialog {
.t-dialog {
width: 50vw;
max-width: 50vw;
.t-dialog__body {
overflow: hidden;
}
}
.custom-dialog-body {
height: 500px;
box-sizing: border-box;
.content {
height: 450px;
overflow-y: auto;
.img-box {
display: flex;
justify-content: space-between;
.img {
width: 300px;
height: 300px;
}
.split_img_loading {
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
}
.btns {
margin: 20px 0;
& > :not(:nth-child(1)) {
margin-left: 12px;
}
}
}
.custom-loading-box {
position: relative !important;
width: auto !important;
height: 50px !important;
}
}
}
</style>
.custom-img-to-img-content {
.keyword {
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
& > :nth-child(2) {
margin-bottom: 30px;
}
.t-textarea {
.t-textarea__inner {
height: 200px;
resize: none;
}
}
}
}
import { defineComponent, PropType, ref, watch } from 'vue';
import './index.less';
import { Textarea as TTextarea } from 'tdesign-vue-next';
export default defineComponent({
props: {
modelValue: {
type: String as PropType<string>,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const keyword = ref('');
watch(
() => keyword.value,
(v) => {
emit('update:modelValue', v);
}
);
return () => (
<div class="custom-img-to-img-content">
<div class="keyword">
<div class="label">关键词</div>
<TTextarea autosize={false} v-model={keyword.value}></TTextarea>
</div>
</div>
);
},
});
.custom-real-upload {
margin-top: 30px;
background: #ffffff;
box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.25);
border-radius: 10px;
height: 330px;
padding: 20px 46px;
position: relative;
display: flex;
justify-content: center;
flex-direction: column;
.real-upload-close-icon {
position: absolute;
right: 12px;
top: 12px;
cursor: pointer;
}
.real-upload-content {
margin-top: 6px;
display: flex;
justify-content: space-between;
.custom-real-upload-component {
width: 360px;
height: 200px;
border: 1px dashed #000000;
.t-upload {
width: 100%;
height: 100%;
.custom-upload-click-box {
border-radius: 8px;
width: 360px;
height: 200px;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
.title {
font-weight: 600;
font-size: 18px;
color: black;
}
.title2 {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
.custom-chose-file {
background: #fd1753;
border-radius: 8px;
border: none;
width: 200px;
height: 46px;
--ripple-color: none !important;
}
}
.t-upload__dragger {
padding: 0;
border: none;
width: 360px;
height: 200px;
}
}
.custom-uploading-stauts {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.uploading-title {
font-weight: 400;
font-size: 15px;
color: #8b8b8b;
}
}
.custom-UploadSuccess-stauts {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.UploadSuccess-img {
width: 100%;
height: 100%;
}
.title1 {
font-weight: 600;
font-size: 18px;
color: #000000;
}
}
}
}
.custom-real-upload-footer {
flex: 1;
display: flex;
align-items: center;
.t-button {
width: 164px;
height: 46px;
border: none;
--ripple-color: none !important;
border-radius: 8px;
font-size: 18px;
}
.submit {
background: #ebebeb;
color: #9a9a9a;
cursor: not-allowed;
}
.active {
background: #fd1753;
color: #ffffff;
cursor: pointer;
}
.reset-button {
border: 1px solid #dbdbdb;
background: white;
color: #000000;
margin-left: 12px;
}
}
}
.label {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
import { computed, defineComponent, reactive, ref } from 'vue';
import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip.svg?component';
import {
MessagePlugin,
Button as TButton,
Upload as TUpload,
Progress as TProgress,
UploadFile,
RequestMethodResponse,
} from 'tdesign-vue-next';
import { useStore } from 'vuex';
import { getUserCookie } from '@/utils/api/userApi';
import request from '@/utils/otherRequest';
import { v4 } from 'uuid';
export default defineComponent({
props: {
modelValue: String,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const store = useStore();
// 后台配置的地址
const adminConfigUrl = computed(() => store.getters['user/getadminConfig']);
// 上传策略
const uploadStrategy = computed(
() => store.getters['user/getuploadStrategy']
);
const files = ref([]);
// 文件地址
const Curfile = reactive({
url: '',
status: 0,
// 当前上传模块提交的状态
uploadStatus: false,
});
const actionUrl = ref('');
// 上传进度条
const percentage = ref(0);
// 定时器
let percentageInterval: any = null;
// 上传进度定时器
const openpercentage = () => {
// 开启一个定时器,模拟上传进度
percentage.value = 0;
percentageInterval = setInterval(() => {
if (percentage.value == 99) {
return;
}
percentage.value += 1;
}, 100);
};
const beforeUpload = (file: File) => {
return true;
};
const handleFail = ({ file }: any) => {
MessagePlugin.error(`文件 ${file.name} 上传失败`);
};
// 上传成功回调
const UploadSuccessCallback = (uuid: any, url: any) => {
// 关闭定时器
window.clearInterval(percentageInterval);
MessagePlugin.success('上传成功');
// 将将完整url传给父组件
Curfile.url = url;
// 成功2
Curfile.status = 2;
emit('update:modelValue', Curfile.url);
};
// 上传失败回调
const UploadErrorCallback = () => {
// 关闭定时器
window.clearInterval(percentageInterval);
Curfile.url = '';
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
MessagePlugin.warning('上传失败');
};
// 内网上传-Intranet
const IntranetUpload = (file: any) => {
openpercentage();
return new Promise((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
if (import.meta.env.MODE == 'production') {
// 线上地址使用完整url
url = adminConfigUrl.value + 'video/' + uuid + '.png';
// url = `http://192.168.1.19:5000/video/` + uuid + '.png';
} else if (import.meta.env.MODE == 'app') {
// app
url = '/video/' + uuid + '.png';
} else {
// 本地
url = '/video/' + uuid + '.png';
}
setTimeout(() => {
request.put(url, file[0].raw).then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
let url = adminConfigUrl.value + 'video/' + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
});
}, 1000);
});
};
// 外网上传-func
const ExtranetUpload = (file: any) => {
openpercentage();
return new Promise<RequestMethodResponse>((resolve) => {
let uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
const { config } = uploadStrategy.value;
url = 'https://' + config.host;
setTimeout(() => {
let formData = new FormData();
formData.append('key', config.dir + uuid + '.png');
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
// formData.append('name', uuid + '.png');
formData.append('file', file[0].raw);
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
// 外网url
let url = config.domain + config.dir + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
})
.catch((e) => {
console.log(e);
});
}, 1000);
});
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
};
// 未上传
const notUploadHtml = () => {
return (
<TUpload
v-model={files.value}
method="PUT"
requestMethod={requestSuccessMethod}
action={actionUrl.value}
headers={{
authorization: `Bearer ${getUserCookie()}`,
}}
accept={'video'}
theme="custom"
before-upload={beforeUpload}
multiple
max={1}
draggable={true}
onfail={handleFail}
>
<div class="custom-upload-click-box">
<div class="title">选择图片</div>
<div class="title2">或拖拽图片到此处</div>
<div>
<UploadTip></UploadTip>
</div>
<TButton class="custom-chose-file">选择文件</TButton>
</div>
</TUpload>
);
};
// 上传中
const UploadingHtml = () => {
return (
<div class="custom-uploading-stauts">
<TProgress
theme="circle"
percentage={percentage.value}
size={'small'}
/>
<div class="uploading-title">正在上传</div>
</div>
);
};
const UploadSuccess = () => {
return (
<div class="custom-UploadSuccess-stauts">
<img class="UploadSuccess-img" src={Curfile.url} alt="" />
</div>
);
};
// 获取当前上传状态
const currentUploadStatus = () => {
if (Curfile.status == 0) {
return notUploadHtml();
} else if (Curfile.status == 1) {
// 上传中
return UploadingHtml();
} else {
// 上传完成
return UploadSuccess();
}
};
return () => (
<div>
<div class="label">原图</div>
<div class="custom-real-upload">
<div class="real-upload-content">
<div class="custom-real-upload-component">
{currentUploadStatus()}
</div>
</div>
</div>
</div>
);
},
});
<template>
<t-dialog
v-model:visible="visible"
attach="body"
placement="center"
:confirm-on-enter="true"
:on-cancel="onCancel"
:on-confirm="onConfirmAnother"
>
<template #header>
<div>请确认是否提交</div>
</template>
<template #body> </template>
</t-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Dialog as TDialog } from 'tdesign-vue-next';
const props = defineProps({
modelValue: Boolean,
});
const emit = defineEmits(['update:modelValue', 'Submit']);
const visible = ref(false);
const onCancel = (context: any) => {
//
console.log('onCancel');
};
// 确认按钮
const onConfirmAnother = (context: any) => {
// 通知页面发起请求
visible.value = false;
emit('Submit');
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
}
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
}
);
</script>
<style lang="less">
.custom-confirm-dialog {
}
</style>
.img-to-img-page {
box-sizing: border-box;
padding: 30px;
.submit-button {
background: #fd1753;
border: none;
height: 36px;
--ripple-color: none !important;
margin-top: 20px;
&:hover {
background: #fd1753;
}
}
}
import { defineComponent, onMounted, reactive, ref } from 'vue';
import './index.less';
import EnterKeywords from './components/EnterKeywords';
import { useStore } from 'vuex';
import UploadImg from './components/UploadImg';
import { Button as TButton } from 'tdesign-vue-next';
import { Tasks } from '@/utils/api/Task';
import { filterRepeatTimestamp } from '@/utils/tool';
import {
useSubmitTask,
IntervalCheckTask,
SubmitSplitImgTask,
get_split_img_status,
} from '@/utils/api/userApi';
import CustomDialog from './components/Dialog/index.vue';
import Animation from '@/components/Animation.vue';
import ConfirmDIalog from './components/confimDialog.vue';
export default defineComponent({
setup(props, ctx) {
const store = useStore();
let custom_interval: any = null;
let split_interval: any = null;
const task_id = ref();
// 确认弹窗
const confirmDialogVisible = ref(false);
const prompt_num = ref();
const task_result_list = reactive<{
list: any[];
}>({
list: [],
});
// 关键词
const keywords = ref('');
const loading = ref(false);
const dialogloading = ref(true);
// 图片链接
const Img_url = ref('');
// 弹窗状态
const DialogVisible = ref(false);
// 测试
const test_num = ref(1);
onMounted(() => {
store.dispatch('user/AdminConfig');
});
// 获取返回的四宫格图片
const getTask = async () => {
try {
let res: any = await IntervalCheckTask({
task_id: task_id.value,
});
if (res.code == 0 && res.data.length) {
res.data.forEach((item: any) => {
if (item.loading === undefined) {
item.loading = false;
}
});
if (res.data.length > task_result_list.list.length) {
// 合并去重
filterRepeatTimestamp(task_result_list.list, res.data);
}
if (prompt_num.value == task_result_list.list.length) {
// 关闭定时器
closeInterval();
dialogloading.value = false;
} else {
dialogloading.value = true;
}
}
} catch (e) {
console.log(e);
}
};
// 获取返回的切割图片
const get_split_img = async (prompt_id: number) => {
try {
let res: any = await get_split_img_status({
prompt_id: prompt_id,
});
if (res.code == 0 && res.data.cut_img && res.data.id) {
// 切割成功--找到list对应的id,将cut_img添加进去
task_result_list.list.forEach((item: any) => {
if (item.id == res.data.id) {
item.split_img = res.data.cut_img;
item.loading = false;
}
});
// 关闭定时器
closeSplitTaskInterval();
}
} catch (e) {
console.log(e);
}
};
// 打开定时器
const openInterval = () => {
custom_interval = window.setInterval(() => {
getTask();
}, 3000);
};
// 打开图片切割任务定时器
const openSplitTaskInterval = (prompt_id: number) => {
split_interval = window.setInterval(() => {
get_split_img(prompt_id);
}, 3000);
};
// 关闭定时器
const closeInterval = () => {
if (custom_interval) {
window.clearInterval(custom_interval);
}
};
// 关闭图片切割任务定时器
const closeSplitTaskInterval = () => {
if (split_interval) {
window.clearInterval(split_interval);
}
};
const submit_before = () => {
if (!keywords.value) {
return;
}
// 打开的确认弹窗
confirmDialogVisible.value = true;
// submit();
};
// 确认对话框的确认事件
const CanSubmit = () => {
if (!keywords.value) {
return;
}
submit();
};
// 提交任务
const submit = async () => {
// prompt_img需要传数组
let params: any = {
type: Img_url.value ? Tasks.img_to_img : Tasks.text_to_img,
prompt: keywords.value,
prompt_img: [Img_url.value],
prompt_num: 5,
};
try {
loading.value = true;
// 请求
let res: any = await useSubmitTask(params);
if (res.code == 0) {
task_id.value = res.data.task_id;
prompt_num.value = res.data.prompt_num;
// 打开弹窗
DialogVisible.value = true;
// 清空已返回的图片
task_result_list.list = [];
// 开启轮询
openInterval();
}
loading.value = false;
} catch (e) {
console.log(e);
loading.value = false;
}
};
// 提交切割任务
const SubmitSplit = async ({ prompt_id, click_id, index }: any) => {
try {
let res: any = await SubmitSplitImgTask({
prompt_id: prompt_id,
cut_id: click_id,
type: 1,
});
if (res.code == 0) {
// 检测切割任务是否完成
openSplitTaskInterval(res.data.prompt_id);
// 将对应的item.loading打开
task_result_list.list[index].loading = true;
}
} catch (e) {
console.log(e);
}
};
return () => (
<div class="img-to-img-page">
<div class="tips">提示:可不上传图片</div>
<EnterKeywords v-model={keywords.value}></EnterKeywords>
<UploadImg v-model={Img_url.value}></UploadImg>
<TButton
onClick={submit_before}
class={['submit-button', !keywords.value ? 'disabled' : '']}
>
提交
</TButton>
<CustomDialog
v-model={DialogVisible.value}
list={task_result_list.list}
dialogloading={dialogloading.value}
onSubmitSplit={SubmitSplit}
></CustomDialog>
<Animation position={'fixed'} v-show={loading.value}></Animation>
<ConfirmDIalog
v-model={confirmDialogVisible.value}
onSubmit={CanSubmit}
></ConfirmDIalog>
</div>
);
},
});
<template>
<div class="custom-login">
<div class="custom-login-title">登录</div>
<t-form
ref="form"
class="custom-login-form"
:data="formData"
:rules="FORM_RULES"
:colon="true"
:label-width="0"
@reset="onReset"
@submit="onSubmit"
>
<t-form-item name="account">
<t-input
v-model="formData.account"
clearable
placeholder="请输入账户名"
>
<template #prefix-icon>
<desktop-icon />
</template>
</t-input>
</t-form-item>
<t-form-item name="password">
<t-input
v-model="formData.password"
type="password"
clearable
placeholder="请输入密码"
>
<template #prefix-icon>
<lock-on-icon />
</template>
</t-input>
</t-form-item>
<t-form-item>
<t-button theme="primary" type="submit" block>登录</t-button>
</t-form-item>
</t-form>
<Animation
v-show="loading"
poistion="fixed"
background="rgba(200,200,200,0.2)"
></Animation>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref } from 'vue';
import {
MessagePlugin,
Form as TForm,
FormItem as TFormItem,
Input as TInput,
Button as TButton,
FormRule,
} from 'tdesign-vue-next';
import { DesktopIcon, LockOnIcon } from 'tdesign-icons-vue-next';
import { UserLogin } from '@/utils/api/userApi';
import { useStore } from 'vuex';
import Animation from '@/components/Animation.vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const store = useStore();
const loading = ref(false);
const formData = reactive({
account: '',
password: '',
});
const FORM_RULES = computed(() => {
return {
account: [
{ required: true, message: '账号不能为空', type: 'error' },
] as FormRule[],
password: [
{ required: true, message: '密码不能为空', type: 'error' },
] as FormRule[],
};
});
const onReset = () => {
MessagePlugin.success('重置成功');
};
const onSubmit = async ({ validateResult, firstError }: any) => {
if (validateResult === true) {
try {
loading.value = true;
let res: any = await UserLogin({
email: formData.account,
password: formData.password,
});
if (res.code == 0) {
MessagePlugin.success('登录成功');
store.commit('user/setToken', {
token: res.data.access_token,
time: res.data.expires_in,
});
router.replace({
path: '/',
});
}
loading.value = false;
} catch (e) {
console.log(e);
loading.value = false;
}
} else {
MessagePlugin.closeAll();
MessagePlugin.warning(firstError);
}
};
</script>
<style lang="less">
@import '@/style/flex.less';
.custom-login {
width: 400px;
margin: 0 auto;
.dj();
flex-direction: column;
.custom-login-title {
font-weight: 500;
font-size: 50px;
color: #000000;
text-align: center;
}
.custom-login-form {
margin-top: 40px;
}
}
</style>
<template>
<div class="custom-home-page-login">
<Login></Login>
</div>
</template>
<script lang="ts" setup>
import Login from './components/login.vue';
</script>
<style lang="less">
@import '@/style/variables.less';
@import '@/style/flex.less';
.custom-home-page-login {
background: #ffffff;
height: 100%;
.dja();
flex-direction: column;
}
</style>
import router from '@/router';
import { getUserCookie } from '@/utils/api/userApi';
router.beforeEach((to: any, from: any, next: any) => {
if (to.name == 'login') {
next();
return;
}
// 用户token
let token = getUserCookie();
if (!token) {
next('/login');
return;
}
next();
});
import {
createRouter,
createWebHistory,
RouteRecordRaw,
createWebHashHistory,
} from 'vue-router';
import homeRouters from './modules/home';
// 存放固定的路由
const defaultRouterList: Array<RouteRecordRaw> = [...homeRouters];
// 打包为app时,使用hash模式
const isHistory = import.meta.env.MODE == 'app';
const router = createRouter({
history: isHistory ? createWebHashHistory() : createWebHistory(),
routes: defaultRouterList,
scrollBehavior() {
return {
el: '#app',
top: 0,
behavior: 'smooth',
};
},
});
export default router;
export default [
{
path: '/',
name: 'layout',
component: () => import('@/layout/content.vue'),
children: [
{
path: '/',
name: 'Home',
component: () => import('@/pages/Home'),
meta: {
header: true,
},
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/Login/index.vue'),
meta: {
header: true,
},
},
// {
// path: '/gpt',
// name: 'gpt',
// component: () => import('@/pages/GPT/index.vue'),
// meta: {
// header: true,
// },
// },
// // image
// {
// path: '/image',
// name: 'image',
// component: () => import('@/pages/Image'),
// meta: {
// header: true,
// },
// },
// // Image_stable
// {
// path: '/Image_stable',
// name: 'Image_stable',
// component: () => import('@/pages/Image_stable'),
// meta: {
// header: true,
// },
// },
],
},
];
import 'vue-router';
declare module '*.vue' {
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module 'vue-router' {
interface RouteMeta {
login?: boolean; //是否需要登录
title?: string; // 标题
bread?: string; //面包屑名字
}
}
declare global {
interface Window {
tvWidget: any;
}
}
import { createStore } from 'vuex';
import user from './modules/user';
export const store = createStore({
modules: {
user,
},
});
export default store;
import { TOKEN_NAME, APP_COOKIE } from '@/config/global';
import Cookies from 'js-cookie';
import { getConfigPolicy } from '@/utils/api/userApi';
interface MyState {
token: String | undefined | null;
account: number | string;
adminConfig: string;
uploadStrategy: any;
options: any[];
}
// 获取cookie
const getUserCookie = () => {
let mode = import.meta.env.MODE;
if (mode == 'app') {
// 从本地取
return window.localStorage.getItem(APP_COOKIE);
} else {
return Cookies.get(TOKEN_NAME);
}
};
// 定义的state初始值
const state: MyState = {
token: getUserCookie(),
account: '',
adminConfig: '',
uploadStrategy: {
config: {},
},
options: [],
};
type StateType = typeof state;
const mutations = {
setToken(state: StateType, obj: any) {
Cookies.set(TOKEN_NAME, obj.token, {
expires: obj.time / 60 / 60 / 24,
});
// app版
if (import.meta.env.MODE == 'app') {
window.localStorage.setItem(APP_COOKIE, obj.token);
}
state.token = obj.token;
},
removeToken(state: StateType) {
Cookies.remove(TOKEN_NAME);
state.token = '';
// app版
if (import.meta.env.MODE == 'app') {
window.localStorage.setItem(APP_COOKIE, '');
}
},
// 更新上传策略
setuploadStrategy(state: StateType, config: any) {
// 上传配置
state.uploadStrategy.config = config.config;
},
};
const getters = {
token: (state: StateType) => {
return state.token;
},
getuploadStrategy: (state: StateType) => {
return state.uploadStrategy;
},
};
const actions = {
async AdminConfig({ commit }: any) {
try {
// 请求上传配置
let result: any = await getConfigPolicy({
// 视频上传策略
id: 1,
});
if (result.code == 0) {
commit('setuploadStrategy', {
config: result.data,
});
}
} catch (e) {
console.log(e);
}
},
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
.custom-disabled-button {
height: 48px;
width: 100%;
font-weight: 600;
font-size: 17px;
color: #848e9c;
transition: all 0.3s;
}
.custom-clickable-button {
background: rgba(41, 98, 255, 0.85);
color: #ffffff;
transition: all 0.3s;
}
//默认center
.dja(@flex:center,@align:center) {
display: flex;
justify-content: @flex;
align-items: @align;
}
.dj(@flex:center) {
display: flex;
justify-content: @flex;
}
.da(@align:center) {
display: flex;
align-items: @align;
}
@import './variables.less';
body {
font-size: 14px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
padding: 0;
margin: 0;
user-select: none;
}
ul,
dl,
li,
dd,
dt {
margin: 0;
padding: 0;
list-style: none;
}
figure,
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0;
}
* {
box-sizing: border-box;
}
// 页面重复的label
.custom-upload-label {
font-weight: 700;
font-size: 20px;
color: #000000;
}
*,
*:hover,
*:active,
::before,
::after {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
// * {
// -ms-touch-action: none;
// touch-action: none;
// }
// 头部导航栏高度
@navbarHeight: 6vh;
// 底部导航栏高度
@FooterHeight: 8vh;
// 页面padding
@pagepadding:0 12px;
// 动画
@anim-time-fn-easing: cubic-bezier(0.38, 0, 0.24, 1);
@anim-time-fn-ease-out: cubic-bezier(0, 0, 0.15, 1);
@anim-time-fn-ease-in: cubic-bezier(0.82, 0, 1, 0.9);
@anim-duration-base: 0.2s;
@anim-duration-moderate: 0.24s;
@anim-duration-slow: 0.28s;
export const isMobile = () => {
return navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
);
};
export const openApp = (url: string) => {
let res = isMobile();
if (!res) {
return;
}
// 是手机
// window.location.href = 'https://wa.me/';
let _href = 'https://api.whatsapp.com/send?';
_href += '&text=' + encodeURIComponent(url); //标题
// _href += '&url=' + encodeURIComponent(url); //链接
window.location.href = _href;
};
export const Tasks = {
img_to_img: 1,
text_to_img: 2,
};
import { getUserCookie } from './userApi';
import request from '@/utils/request';
// gpt对话
export const submitmessage = (data: any) => {
return request.post(
'/api/gpt/chat',
{
...data,
},
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
};
import request from '@/utils/request';
import store from '@/store';
// 获取cookie
export const getUserCookie = () => {
return store.getters['user/token'];
};
// 登录
export const UserLogin = (data: any) => {
return request.post('/api/users/login', {
...data,
});
};
// 获取后台配置
export const getAdminConfig = () => {
let token = getUserCookie();
return request.get('/api/users/config', {
headers: {
authorization: `Bearer ${token}`,
},
});
};
// 获取阿里云上传策略
export const getConfigPolicy = (data: any) => {
let token = getUserCookie();
return request.get('/api/users/config/policy', {
params: data,
headers: {
authorization: `Bearer ${token}`,
},
});
};
// 退出登录
export const useLogout = () => {
return request.post(
'/api/users/logout',
{},
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
};
// 提交任务
export const useSubmitTask = (data: any) => {
return request.post(
'/api/users/submit',
{ ...data },
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
};
// 轮询检测任务
export const IntervalCheckTask = (data: any) => {
return request.get('/api/users/task/callback', {
params: data,
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
});
};
// 提交切割图片任务
export const SubmitSplitImgTask = (data: any) => {
return request.post(
'/api/users/split',
{ ...data },
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
};
// 轮询获取图片切割任务状态
export const get_split_img_status = (data: any) => {
return request.get('/api/users/split/status', {
params: data,
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
});
};
import { getUserCookie } from './userApi';
import request from '@/utils/request';
// discord机器人版本
export const new_task_submit = (data: any) => {
return request.post(
'/api/users/api-submit',
{ ...data },
{
headers: {
authorization: `Bearer ${getUserCookie()}`,
},
}
);
};
export const getDomBounding = (dom: any) => {
let client = dom.getBoundingClientRect();
let bodyHeight = document.documentElement.clientHeight;
return {
client,
bodyHeight,
};
};
export function zipImg(file: File) {
return new Promise((resolve) => {
if (file && (file.size / 1024 > 500 || file.type !== 'image/gif')) {
let img = new Image();
img.src = URL.createObjectURL(file);
let cvs = document.createElement('canvas');
let maxRatio = 0.75; // 大图比率
let minRatio = 0.8; // 小图比率
let imgQulity = 0.2; // 图像质量
img.onload = async function () {
let ratio =
img.naturalWidth > 1000 || img.naturalHeight > 1000
? maxRatio
: minRatio;
cvs.width = img.naturalWidth * ratio;
cvs.height = img.naturalHeight * ratio;
let ctx: any = cvs.getContext('2d');
ctx.drawImage(img, 0, 0, cvs.width, cvs.height);
// 压缩后新图的 base64
let zipBase64 = cvs.toDataURL('image/jpeg', imgQulity);
let add = base642File(zipBase64);
resolve(add);
};
} else {
resolve(file);
}
});
}
// base64转图片
export function dataURLtoFile(dataurl: any, filename: any, mime: any) {
return new Promise((resolve) => {
let arr = dataurl.split(';base64,');
let bstr = atob(arr[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
let data = new File([u8arr], `${filename}`, {
type: mime,
});
resolve(data);
});
}
export function base642File(base64: any) {
const arr = base64.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
export const ChangeBlob = (value: string) => {
//将字符串 转换成 Blob 对象
let blob = new Blob([value], {
type: 'text/plain',
});
return blob;
};
export interface ColumnProps {
value: string;
label: string;
panel: any;
is_load?: boolean;
}
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
import router from '@/router';
const mode = import.meta.env.MODE;
const getBaseUrl = () => {
return '/';
};
const instance = axios.create({
// baseURL: getBaseUrl(),
timeout: 6000000,
// withCredentials: mode == 'development' ? false : true,
withCredentials: false,
});
// 请求头
instance.interceptors.request.use((config: any) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data, status } = response;
if (status == 201 || status == 200) {
return status;
}
if (data.code === 0) {
return data;
} else {
MessagePlugin.error(data.msg || '请求错误');
return Promise.reject(data.msg);
}
},
(err) => {
console.log(err);
if ('response' in err) {
const { message: msg } = err.response.data;
if (err.response.data.indexOf('<Code>UserDisable</Code>') !== -1) {
MessagePlugin.error('阿里云可能欠费');
} else {
MessagePlugin.error(msg || '请求错误');
}
return err.response;
}
}
);
export default instance;
import {
remember_password_phone,
remember_password_email,
} from '@/constants/token';
export const getRememberList = (type: string) => {
// localStorage.setItem(remember_password_phone, JSON.stringify([]));
// localStorage.setItem(remember_password_email, JSON.stringify([]));
let new_type = '';
if (type == 'phone') {
new_type = remember_password_phone;
} else {
new_type = remember_password_email;
}
let list = localStorage.getItem(new_type);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
export const setRememberList = (obj: any, type: string) => {
let list = [obj];
let new_type = '';
if (type == 'phone') {
new_type = remember_password_phone;
} else if (type == 'email') {
new_type = remember_password_email;
}
// 本地已经存储的地址
let locaList: any = getRememberList(new_type);
if (locaList.length) {
// 判断本地是否存在相同账号
let index = locaList.findIndex((item: any) => item.account == obj.account);
if (index !== -1) {
// 找到相同的,删除
locaList.splice(index, 1);
}
list = list.concat(locaList);
}
// 存储
localStorage.setItem(new_type, JSON.stringify(list));
};
import axios from 'axios';
import { store } from '@/store/index';
import { MessagePlugin } from 'tdesign-vue-next';
import router from '@/router';
const mode = import.meta.env.MODE;
const getBaseUrl = () => {
if (mode == 'app') {
return 'http://video_publish.test';
} else {
// 打包
return '';
}
};
const instance = axios.create({
baseURL: getBaseUrl(),
timeout: 60000,
// withCredentials: mode == 'development' ? false : true,
withCredentials: mode == 'app' ? false : true,
});
// 请求头
instance.interceptors.request.use((config: any) => {
const lang = store.getters['language/getLang'];
config.headers['lang'] = lang;
return config;
});
instance.defaults.timeout = 60000;
instance.interceptors.response.use(
(response) => {
const { data } = response;
if (data.code === 0 || data.code == 201) {
return data;
} else {
MessagePlugin.error(data.msg || '请求错误');
return Promise.reject(data.msg);
}
},
(err) => {
if ('response' in err) {
const { message: msg, status_code } = err.response.data;
if (status_code == 403) {
MessagePlugin.warning('请登录');
router.replace({
path: '/login',
});
return;
}
MessagePlugin.error(msg || '请求错误');
return err.response;
}
}
);
export default instance;
export const WatchColor = (value: string) => {
try {
if (value[0] === '-') {
return '#F05451';
} else {
return '#25A69A';
}
} catch (e) {
return '#25A69A';
}
};
/*
* @Author: walker.liu
* @Date: 2022-05-24 10:51:56
* @Copyright(C): 2019-2020 ZP Inc. All rights reserved.
*/
import store from '@/store';
import request from '@/utils/request';
import { PriceAccuracy, computedTime } from './tool';
declare const TradingView: any;
/**
* @key Server ding字段 supported_resolutions
*/
// 1min, 5min, 15min, 30min, 60min, 4hour, 1day, 1mon, 1week, 1year
export const intervalMap = {
'1min': '1',
'5min': '5',
'15min': '15',
'30min': '30',
'60min': '60',
'4hour': '240',
'1day': 'D',
'1week': 'W',
'1mon': 'M',
};
/** trading-view 的時間區間 */
export const supportedResolutions = [
'1',
'5',
'15',
'30',
'60',
'240',
'D',
'W',
'M',
];
export class DataFeed {
private symbolInfo: any;
private subscribers: any[] = [];
private interval: any = null;
private resolution = '1';
// 每个时间段都要给5次重试次数
private RetryTime: any = {
r1: 0,
r5: 0,
r15: 0,
r60: 0,
r240: 0,
r1D: 0,
};
private KPirce: any = null;
constructor(symbolInfo: any, timeInterval = 1000) {
this.KPirce = symbolInfo.KPirce;
// 将传过来的多余参数剔除
delete symbolInfo.KPirce;
this.symbolInfo = symbolInfo;
this.intervalGetBars(symbolInfo.ticker, timeInterval);
}
public onReady(onGameLoad: any) {
new Promise((resolve) => {
resolve(void 0);
}).then(() => {
onGameLoad({
supported_resolutions: supportedResolutions,
});
});
}
public searchSymbols() {}
public resolveSymbol(
symbolName: any,
onSymbolResolvedCallback: any,
onResolveErrorCallback: any
) {
new Promise((resolve) => {
resolve(void 0);
}).then(() => {
let PriceNum = 10000000000;
PriceNum = PriceAccuracy(this.KPirce);
onSymbolResolvedCallback({
session: '24x7',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
supported_resolutions: supportedResolutions,
has_intraday: true,
has_daily: true,
// has_no_volume: true,
minmov: 1,
// minmove2:0,
// 最多支持16位小数
pricescale: PriceNum,
...this.symbolInfo,
});
});
}
/**
* 订阅K线数据。图表库将调用onRealtimeCallback方法以更新实时数据
*/
public subscribeBars(
symbolInfo: any,
resolution: string,
onRealtimeCallback: any,
subscriberUID: any,
onResetCacheNeededCallback: () => void
) {
if (this.subscribers[subscriberUID]) {
return;
}
this.resolution = resolution;
this.subscribers[subscriberUID] = {
lastBarTime: null,
listener: onRealtimeCallback,
resolution: resolution,
symbolInfo: symbolInfo,
};
}
/**
* 取消订阅K线数据
*/
public unsubscribeBars(subscriberUID: any) {
if (!this.subscribers[subscriberUID]) {
return;
}
delete this.subscribers[subscriberUID];
}
public updateKLine(bar: any) {
for (const listenerGuid in this.subscribers) {
const subscriptionRecord = this.subscribers[listenerGuid];
if (
subscriptionRecord.lastBarTime !== null &&
bar.time < subscriptionRecord.lastBarTime
) {
continue;
}
subscriptionRecord.lastBarTime = bar.time;
subscriptionRecord.listener(bar);
}
}
public async getBars(
symbolInfo: any,
resolution: any,
periodParams: any,
onHistoryCallback: any,
onErrorCallback: any
) {
try {
const token = store.getters['user/token'];
let params = {
...periodParams,
resolution,
symbol: symbolInfo.ticker,
};
params.from = params.from * 1000;
params.to = params.to * 1000;
// 是否第一次請求歷史數據
if (periodParams.firstDataRequest) {
params.to = Date.now();
params.is_first = true;
// 添加limit
params.limit = params.countBack + 50;
delete params.firstDataRequest;
delete params.countBack;
} else {
params.is_first = false;
delete params.firstDataRequest;
// 添加limit
params.limit = params.countBack;
delete params.countBack;
}
request
.get(`/api/currencies/kline`, {
params: params,
headers: {
authorization: `Bearer ${token}`,
},
})
.then((res: any) => {
let result = {
bars: [],
meta: { noData: false },
};
if (
res.data.list &&
res.data.list.length > 0 &&
this.RetryTime[`r${resolution}`] < 5
) {
result.meta.noData = false;
result.bars = res.data.list.map((item: any) => {
return {
time: item.ts,
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume,
};
});
store.commit(
'token/setTradePrice',
res.data.list[res.data.list.length - 1].close
);
} else {
if (this.RetryTime[`r${resolution}`] < 5) {
this.RetryTime[`r${resolution}`] += 1;
} else {
result.meta.noData = true;
}
}
onHistoryCallback(result.bars, result.meta);
});
} catch (error) {
onHistoryCallback({
bars: [],
meta: { noData: true },
});
}
}
public intervalGetBars(symbol: any, timeInterval: number) {
this.interval = setInterval(() => {
const token = store.getters['user/token'];
let from = computedTime(this.resolution);
let current = new Date().getTime();
let params = {
resolution: this.resolution,
to: current,
// from: current - timeInterval,
from: from ? from : current - timeInterval,
// from: current - 5000,
symbol: symbol,
is_first: false,
limit: 2,
};
request
.get(`/api/currencies/kline`, {
params: params,
headers: {
authorization: `Bearer ${token}`,
},
})
.then((res: any) => {
if (res && res.data.list.length) {
res.data.list.forEach((item: any) => {
this.updateKLine({
time: item.ts,
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume,
});
});
store.commit(
'token/setTradePrice',
res.data.list[res.data.list.length - 1].close
);
}
});
}, 5000);
}
public clearIntervalGetBars() {
window.clearInterval(this.interval);
}
}
export class Widget {
private options: any;
public widget = null;
constructor(options = {}) {
const mode = store.getters['theme/getTheme'];
// 语言
const language = store.getters['language/getLang'];
// 获取当前语言
let CurLanguage = 'zh';
if (language == 'cn') {
CurLanguage = 'zh';
} else if (language == 'es') {
CurLanguage = 'es';
} else if (language == 'en') {
CurLanguage = 'en';
}
let VITE_MODE = import.meta.env.MODE;
this.options = {
// debug: true,
library_path:
VITE_MODE == 'app' ? './charting_library/' : '/charting_library/',
// 主题
theme: mode,
locale: CurLanguage,
autosize: 1,
container: 'tv_chart_container',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
interval: '1',
// toolbar_bg: '#ffffff', //左侧工具栏背景色
favorites: {
intervals: ['1', '5', '15', '60', '240', 'D', 'W', 'M'],
chartTypes: ['Candles', 'Line'],
},
// enabled_features: ['study_templates'],
disabled_features: [
'header_symbol_search',
'left_toolbar', // 隐藏左侧工具栏
'timeframes_toolbar', //底部tab
'header_compare',
'header_saveload',
'header_fullscreen_button',
'header_screenshot', //截图相机
'vert_touch_drag_scroll', //垂直移动
// 禁用本地存储功能
// 'use_localstorage_for_settings',
'save_chart_properties_to_local_storage',
],
overrides: {
// 默认展示的图表
'mainSeriesProperties.style': 1,
// 'mainSeriesProperties.highLowAvgPrice.highLowPriceLinesVisible': true, //高低线值
// 'mainSeriesProperties.highLowAvgPrice.highLowPriceLabelsVisible': true, //高低线名称
// 'mainSeriesProperties.highLowAvgPrice.averageClosePriceLineVisible':
// true, //均线
// 'mainSeriesProperties.highLowAvgPrice.averageClosePriceLabelVisible':
// true, //均线
},
custom_css_url:
VITE_MODE == 'app'
? './src/style/tradingview.less'
: '/src/style/tradingview.less',
...options,
};
this.initWidget();
}
public initWidget() {
try {
this.widget = window.tvWidget = new TradingView.widget(this.options);
} catch (e) {
console.log(e);
console.log('TradingView--bug');
}
}
}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment