Compare commits
9 Commits
abb8d279c4
...
1edf9601c0
| Author | SHA1 | Date |
|---|---|---|
|
|
1edf9601c0 | |
|
|
adcfea0d3c | |
|
|
33a10f55a5 | |
|
|
55588504a1 | |
|
|
d69d16e196 | |
|
|
5ba6c9f746 | |
|
|
0e94beb477 | |
|
|
5ae8614b1d | |
|
|
4bba35a426 |
|
|
@ -5,4 +5,4 @@ VUE_APP_TITLE = 管理系统
|
||||||
ENV = 'production'
|
ENV = 'production'
|
||||||
|
|
||||||
# 若依管理系统/生产环境
|
# 若依管理系统/生产环境
|
||||||
VUE_APP_BASE_API = 'http://47.86.170.114:8011'
|
VUE_APP_BASE_API = 'http://111.230.37.169:10009'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function listDept(query) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listDeptExcludeChild(deptId) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept/list/exclude/' + deptId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDept(deptId) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept/' + deptId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addDept(data) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDept(data) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delDept(deptId) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/dept/' + deptId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -81,3 +81,12 @@ export function updatePassword(id, newPassword) {
|
||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 分配归属部门,deptId 可省略或 null 表示清空 */
|
||||||
|
export function assignAiUserDept(data) {
|
||||||
|
return request({
|
||||||
|
url: '/ai/user/dept',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,344 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
|
||||||
|
<el-form-item label="部门名称" prop="deptName">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.deptName"
|
||||||
|
placeholder="请输入部门名称"
|
||||||
|
clearable
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in dict.type.sys_normal_disable"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
icon="el-icon-plus"
|
||||||
|
size="mini"
|
||||||
|
@click="handleAdd"
|
||||||
|
v-hasPermi="['ai:dept:add']"
|
||||||
|
>新增</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="info"
|
||||||
|
plain
|
||||||
|
icon="el-icon-sort"
|
||||||
|
size="mini"
|
||||||
|
@click="toggleExpandAll"
|
||||||
|
>展开/折叠</el-button>
|
||||||
|
</el-col>
|
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-if="refreshTable"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="deptList"
|
||||||
|
row-key="deptId"
|
||||||
|
:default-expand-all="isExpandAll"
|
||||||
|
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
|
||||||
|
>
|
||||||
|
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
|
||||||
|
<el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
|
||||||
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
@click="handleUpdate(scope.row)"
|
||||||
|
v-hasPermi="['ai:dept:edit']"
|
||||||
|
>修改</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-plus"
|
||||||
|
@click="handleAdd(scope.row)"
|
||||||
|
v-hasPermi="['ai:dept:add']"
|
||||||
|
>新增</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.parentId != 0"
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
@click="handleDelete(scope.row)"
|
||||||
|
v-hasPermi="['ai:dept:remove']"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||||
|
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24" v-if="form.parentId !== 0">
|
||||||
|
<el-form-item label="上级部门" prop="parentId">
|
||||||
|
<treeselect v-model="form.parentId" :options="deptOptions" :normalizer="normalizer" placeholder="选择上级部门" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="部门名称" prop="deptName">
|
||||||
|
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="显示排序" prop="orderNum">
|
||||||
|
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="负责人" prop="leader">
|
||||||
|
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="联系电话" prop="phone">
|
||||||
|
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="邮箱" prop="email">
|
||||||
|
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="部门状态">
|
||||||
|
<el-radio-group v-model="form.status">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in dict.type.sys_normal_disable"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>{{dict.label}}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row v-if="isSecondLevelCompanyForm">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="Byte API Key">
|
||||||
|
<el-input
|
||||||
|
v-model="form.byteApiKey"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
placeholder="选填"
|
||||||
|
maxlength="255"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="cancel">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/ai/dept"
|
||||||
|
import Treeselect from "@riophae/vue-treeselect"
|
||||||
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AiDept",
|
||||||
|
dicts: ['sys_normal_disable'],
|
||||||
|
components: { Treeselect },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
showSearch: true,
|
||||||
|
deptList: [],
|
||||||
|
deptOptions: [],
|
||||||
|
title: "",
|
||||||
|
open: false,
|
||||||
|
isExpandAll: true,
|
||||||
|
refreshTable: true,
|
||||||
|
queryParams: {
|
||||||
|
deptName: undefined,
|
||||||
|
status: undefined
|
||||||
|
},
|
||||||
|
form: {},
|
||||||
|
rules: {
|
||||||
|
parentId: [
|
||||||
|
{ required: true, message: "上级部门不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
deptName: [
|
||||||
|
{ required: true, message: "部门名称不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
orderNum: [
|
||||||
|
{ required: true, message: "显示排序不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{
|
||||||
|
type: "email",
|
||||||
|
message: "请输入正确的邮箱地址",
|
||||||
|
trigger: ["blur", "change"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
phone: [
|
||||||
|
{
|
||||||
|
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||||
|
message: "请输入正确的手机号码",
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isSecondLevelCompanyForm() {
|
||||||
|
if (this.form.ancestors === "0,100") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const pid = this.form.parentId
|
||||||
|
if (pid !== undefined && pid !== null && Number(pid) === 100) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
this.loading = true
|
||||||
|
listDept(this.queryParams).then(response => {
|
||||||
|
this.deptList = this.handleTree(response.data, "deptId")
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
normalizer(node) {
|
||||||
|
if (node.children && !node.children.length) {
|
||||||
|
delete node.children
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: node.deptId,
|
||||||
|
label: node.deptName,
|
||||||
|
children: node.children
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.open = false
|
||||||
|
this.reset()
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.form = {
|
||||||
|
deptId: undefined,
|
||||||
|
parentId: undefined,
|
||||||
|
ancestors: undefined,
|
||||||
|
deptName: undefined,
|
||||||
|
orderNum: undefined,
|
||||||
|
leader: undefined,
|
||||||
|
phone: undefined,
|
||||||
|
email: undefined,
|
||||||
|
byteApiKey: undefined,
|
||||||
|
status: "0"
|
||||||
|
}
|
||||||
|
this.resetForm("form")
|
||||||
|
},
|
||||||
|
handleQuery() {
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
resetQuery() {
|
||||||
|
this.resetForm("queryForm")
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
handleAdd(row) {
|
||||||
|
this.reset()
|
||||||
|
if (row != undefined) {
|
||||||
|
this.form.parentId = row.deptId
|
||||||
|
}
|
||||||
|
this.open = true
|
||||||
|
this.title = "添加部门"
|
||||||
|
listDept().then(response => {
|
||||||
|
this.deptOptions = this.handleTree(response.data, "deptId")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleExpandAll() {
|
||||||
|
this.refreshTable = false
|
||||||
|
this.isExpandAll = !this.isExpandAll
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.refreshTable = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleUpdate(row) {
|
||||||
|
this.reset()
|
||||||
|
getDept(row.deptId).then(response => {
|
||||||
|
this.form = response.data
|
||||||
|
this.open = true
|
||||||
|
this.title = "修改部门"
|
||||||
|
listDeptExcludeChild(row.deptId).then(response => {
|
||||||
|
this.deptOptions = this.handleTree(response.data, "deptId")
|
||||||
|
if (this.deptOptions.length == 0) {
|
||||||
|
const noResultsOptions = { deptId: this.form.parentId, deptName: this.form.parentName, children: [] }
|
||||||
|
this.deptOptions.push(noResultsOptions)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitForm: function() {
|
||||||
|
this.$refs["form"].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
if (this.form.deptId != undefined) {
|
||||||
|
updateDept(this.form).then(response => {
|
||||||
|
this.$modal.msgSuccess("修改成功")
|
||||||
|
this.open = false
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addDept(this.form).then(response => {
|
||||||
|
this.$modal.msgSuccess("新增成功")
|
||||||
|
this.open = false
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDelete(row) {
|
||||||
|
this.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {
|
||||||
|
return delDept(row.deptId)
|
||||||
|
}).then(() => {
|
||||||
|
this.getList()
|
||||||
|
this.$modal.msgSuccess("删除成功")
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -50,6 +50,16 @@
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="归属部门" prop="deptId">
|
||||||
|
<treeselect
|
||||||
|
v-model="queryParams.deptId"
|
||||||
|
:options="deptOptions"
|
||||||
|
:normalizer="deptNormalizer"
|
||||||
|
placeholder="全部"
|
||||||
|
clearable
|
||||||
|
style="width: 220px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
|
@ -78,6 +88,7 @@
|
||||||
<el-table-column label="上级ID" align="center" prop="superiorUuid" />
|
<el-table-column label="上级ID" align="center" prop="superiorUuid" />
|
||||||
<el-table-column label="上级账号" align="center" prop="superiorName" />
|
<el-table-column label="上级账号" align="center" prop="superiorName" />
|
||||||
<el-table-column label="用户昵称" align="center" prop="nickname" />
|
<el-table-column label="用户昵称" align="center" prop="nickname" />
|
||||||
|
<el-table-column label="归属部门" align="center" prop="deptName" width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="邮箱" align="center" prop="email" />
|
<el-table-column label="邮箱" align="center" prop="email" />
|
||||||
<el-table-column label="性别" align="center" prop="gender">
|
<el-table-column label="性别" align="center" prop="gender">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
|
@ -103,8 +114,15 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="余额" align="center" prop="balance" />
|
<el-table-column label="余额" align="center" prop="balance" />
|
||||||
<el-table-column label="source" align="center" prop="source" />
|
<el-table-column label="source" align="center" prop="source" />
|
||||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="250">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-office-building"
|
||||||
|
@click="handleOpenAssignDept(scope.row)"
|
||||||
|
v-hasPermi="['ai:user:edit']"
|
||||||
|
>分配部门</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
size="mini"
|
size="mini"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -182,6 +200,25 @@
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 分配部门 -->
|
||||||
|
<el-dialog title="分配归属部门" :visible.sync="assignDeptOpen" width="480px" append-to-body @close="cancelAssignDept">
|
||||||
|
<el-form label-width="88px">
|
||||||
|
<el-form-item label="归属部门">
|
||||||
|
<treeselect
|
||||||
|
v-model="assignForm.deptId"
|
||||||
|
:options="deptOptions"
|
||||||
|
:normalizer="deptNormalizer"
|
||||||
|
placeholder="不选则不归属任何部门"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitAssignDept">确 定</el-button>
|
||||||
|
<el-button @click="cancelAssignDept">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 修改余额对话框 -->
|
<!-- 修改余额对话框 -->
|
||||||
<el-dialog :title="title" :visible.sync="openUpdateBalance" width="500px" append-to-body>
|
<el-dialog :title="title" :visible.sync="openUpdateBalance" width="500px" append-to-body>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||||
|
|
@ -207,12 +244,17 @@ import {
|
||||||
updateUser,
|
updateUser,
|
||||||
changeBalance,
|
changeBalance,
|
||||||
changeUserStatus,
|
changeUserStatus,
|
||||||
updatePassword
|
updatePassword,
|
||||||
|
assignAiUserDept
|
||||||
} from "@/api/ai/user";
|
} from "@/api/ai/user";
|
||||||
|
import { listDept } from "@/api/ai/dept";
|
||||||
|
import Treeselect from "@riophae/vue-treeselect";
|
||||||
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "User",
|
name: "User",
|
||||||
dicts: ["sys_normal_disable", "sys_user_sex"],
|
dicts: ["sys_normal_disable", "sys_user_sex"],
|
||||||
|
components: { Treeselect },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// 遮罩层
|
// 遮罩层
|
||||||
|
|
@ -235,6 +277,12 @@ export default {
|
||||||
open: false,
|
open: false,
|
||||||
openUpdatePassword: false,
|
openUpdatePassword: false,
|
||||||
openUpdateBalance: false,
|
openUpdateBalance: false,
|
||||||
|
assignDeptOpen: false,
|
||||||
|
deptOptions: [],
|
||||||
|
assignForm: {
|
||||||
|
id: null,
|
||||||
|
deptId: null
|
||||||
|
},
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
|
|
@ -252,7 +300,8 @@ export default {
|
||||||
paymentUrl: null,
|
paymentUrl: null,
|
||||||
loginTime: null,
|
loginTime: null,
|
||||||
balance: null,
|
balance: null,
|
||||||
superiorName: null
|
superiorName: null,
|
||||||
|
deptId: null
|
||||||
},
|
},
|
||||||
// 表单参数
|
// 表单参数
|
||||||
form: {},
|
form: {},
|
||||||
|
|
@ -268,9 +317,49 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.loadDeptTree();
|
||||||
this.getList();
|
this.getList();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadDeptTree() {
|
||||||
|
listDept().then(res => {
|
||||||
|
this.deptOptions = this.handleTree(res.data, "deptId");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deptNormalizer(node) {
|
||||||
|
if (node.children && !node.children.length) {
|
||||||
|
delete node.children;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: node.deptId,
|
||||||
|
label: node.deptName,
|
||||||
|
children: node.children
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleOpenAssignDept(row) {
|
||||||
|
getUser(row.id).then(res => {
|
||||||
|
this.assignForm = {
|
||||||
|
id: res.data.id,
|
||||||
|
deptId: res.data.deptId
|
||||||
|
};
|
||||||
|
this.assignDeptOpen = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submitAssignDept() {
|
||||||
|
const payload = {
|
||||||
|
id: this.assignForm.id,
|
||||||
|
deptId: this.assignForm.deptId != null ? this.assignForm.deptId : null
|
||||||
|
};
|
||||||
|
assignAiUserDept(payload).then(() => {
|
||||||
|
this.$modal.msgSuccess("已保存");
|
||||||
|
this.assignDeptOpen = false;
|
||||||
|
this.getList();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancelAssignDept() {
|
||||||
|
this.assignDeptOpen = false;
|
||||||
|
this.assignForm = { id: null, deptId: null };
|
||||||
|
},
|
||||||
// 更多操作触发
|
// 更多操作触发
|
||||||
handleCommand(command, row) {
|
handleCommand(command, row) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,19 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row v-if="isSecondLevelCompanyForm">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="Byte API Key">
|
||||||
|
<el-input
|
||||||
|
v-model="form.byteApiKey"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
placeholder="选填"
|
||||||
|
maxlength="255"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
|
@ -222,6 +235,19 @@ export default {
|
||||||
created() {
|
created() {
|
||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
/** 二级公司:ancestors 为 0,100(即上级为根公司 dept_id=100) */
|
||||||
|
isSecondLevelCompanyForm() {
|
||||||
|
if (this.form.ancestors === "0,100") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const pid = this.form.parentId
|
||||||
|
if (pid !== undefined && pid !== null && Number(pid) === 100) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查询部门列表 */
|
/** 查询部门列表 */
|
||||||
getList() {
|
getList() {
|
||||||
|
|
@ -252,11 +278,13 @@ export default {
|
||||||
this.form = {
|
this.form = {
|
||||||
deptId: undefined,
|
deptId: undefined,
|
||||||
parentId: undefined,
|
parentId: undefined,
|
||||||
|
ancestors: undefined,
|
||||||
deptName: undefined,
|
deptName: undefined,
|
||||||
orderNum: undefined,
|
orderNum: undefined,
|
||||||
leader: undefined,
|
leader: undefined,
|
||||||
phone: undefined,
|
phone: undefined,
|
||||||
email: undefined,
|
email: undefined,
|
||||||
|
byteApiKey: undefined,
|
||||||
status: "0"
|
status: "0"
|
||||||
}
|
}
|
||||||
this.resetForm("form")
|
this.resetForm("form")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import i18n from '@/lang/i18n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'mf-forbidden',
|
name: 'mf-forbidden',
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -49,7 +51,13 @@ export default {
|
||||||
this.$router.replace('/403')
|
this.$router.replace('/403')
|
||||||
},
|
},
|
||||||
ok() {
|
ok() {
|
||||||
this.$store.dispatch('main/setForbidden', false)
|
// 满18+:关闭弹窗并直接进入视频生成
|
||||||
|
Promise.resolve(this.$store.dispatch('main/setForbidden', false)).finally(() => {
|
||||||
|
// 默认语言:繁体中文(zh_HK)
|
||||||
|
this.$store.dispatch('main/setLanguage', 'zh_HK')
|
||||||
|
i18n.global.locale = 'zh_HK'
|
||||||
|
this.$router.push({ name: 'video-gen' })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { nextTick, onMounted, ref, watch } from 'vue'
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { uploadFile } from '@/utils/file'
|
import { uploadFile, extractUploadUrlFromResponse, PORTAL_TENCENT_COS_UPLOAD_URL } from '@/utils/file'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RichTextEditor',
|
name: 'RichTextEditor',
|
||||||
|
|
@ -105,13 +105,13 @@ export default {
|
||||||
|
|
||||||
// 上传到后端
|
// 上传到后端
|
||||||
const res = await uploadFile({
|
const res = await uploadFile({
|
||||||
url: '/api/cos/upload', // 使用腾讯云COS上传接口
|
url: PORTAL_TENCENT_COS_UPLOAD_URL,
|
||||||
file: file,
|
file: file,
|
||||||
name: 'file'
|
name: 'file'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
const imageUrl = extractUploadUrlFromResponse(res)
|
||||||
const imageUrl = typeof res.data === 'string' ? res.data : (res.data.url || res.data)
|
if (res && (Number(res.code) === 200 || res.code === 200) && imageUrl) {
|
||||||
insertImage(imageUrl, file.name)
|
insertImage(imageUrl, file.name)
|
||||||
|
|
||||||
// 通知父组件有新图片上传成功(用于@功能)
|
// 通知父组件有新图片上传成功(用于@功能)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,591 @@
|
||||||
|
<template>
|
||||||
|
<div class="video-editor-root">
|
||||||
|
<div v-if="showToolbar" class="toolbar">
|
||||||
|
<button class="tool-btn" type="button" @click="openImagePicker">插入参考素材</button>
|
||||||
|
<button class="tool-btn" type="button" @click="clear">清空所有参考素材和文字</button>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref="fileInputRef"
|
||||||
|
class="hidden-input"
|
||||||
|
type="file"
|
||||||
|
accept="image/*,video/*,audio/*"
|
||||||
|
multiple
|
||||||
|
@change="handleSelectFiles" />
|
||||||
|
<div class="editor-wrapper">
|
||||||
|
<div
|
||||||
|
ref="editorRef"
|
||||||
|
class="user-input video-rich-editor"
|
||||||
|
contenteditable="true"
|
||||||
|
:data-placeholder="placeholder || '请输入文本生成视频...'"
|
||||||
|
@input="handleInput"
|
||||||
|
@paste="handlePaste"
|
||||||
|
@keyup="handleKeyup"
|
||||||
|
@click="handleEditorClick"></div>
|
||||||
|
<div v-if="mentionVisible" class="mention-panel">
|
||||||
|
<div
|
||||||
|
v-for="item in mentionImageList"
|
||||||
|
:key="item.url"
|
||||||
|
class="mention-item"
|
||||||
|
@mousedown.prevent="insertMentionImage(item)">
|
||||||
|
<img v-if="item.mediaType === 'image'" :src="item.url" class="mention-thumb" alt="" />
|
||||||
|
<span v-else class="mention-type-badge">{{ item.mediaType === 'video' ? '视频' : '音频' }}</span>
|
||||||
|
<span class="mention-label">参考{{ item.refNo }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mentionImageList.length === 0" class="mention-empty">暂无可引用素材</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { nextTick, onMounted, ref } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { uploadFile, extractUploadUrlFromResponse, PORTAL_TENCENT_COS_UPLOAD_URL } from '@/utils/file'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 文生视频等场景可隐藏工具栏,仅保留纯文本编辑 */
|
||||||
|
showToolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['text-change'])
|
||||||
|
|
||||||
|
const editorRef = ref(null)
|
||||||
|
const fileInputRef = ref(null)
|
||||||
|
const savedSelectionRange = ref(null)
|
||||||
|
const MAX_IMAGE_COUNT = 9
|
||||||
|
const mentionVisible = ref(false)
|
||||||
|
const mentionImageList = ref([])
|
||||||
|
|
||||||
|
const detectMediaType = (file) => {
|
||||||
|
const mime = (file?.type || '').toLowerCase()
|
||||||
|
if (mime.startsWith('image/')) return 'image'
|
||||||
|
if (mime.startsWith('video/')) return 'video'
|
||||||
|
if (mime.startsWith('audio/')) return 'audio'
|
||||||
|
const lowerName = (file?.name || '').toLowerCase()
|
||||||
|
if (/\.(png|jpe?g|gif|webp|bmp|svg)$/.test(lowerName)) return 'image'
|
||||||
|
if (/\.(mp4|mov|webm|m4v|avi|mkv)$/.test(lowerName)) return 'video'
|
||||||
|
if (/\.(mp3|wav|aac|m4a|ogg|flac)$/.test(lowerName)) return 'audio'
|
||||||
|
return 'image'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlainText = () => (editorRef.value?.innerText || '').trim()
|
||||||
|
|
||||||
|
const focusEditorToEnd = () => {
|
||||||
|
if (!editorRef.value) return
|
||||||
|
editorRef.value.focus()
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection) return
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(editorRef.value)
|
||||||
|
range.collapse(false)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
savedSelectionRange.value = range.cloneRange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSelection = () => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) return
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (editorRef.value?.contains(range.commonAncestorContainer)) {
|
||||||
|
savedSelectionRange.value = range.cloneRange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreSelection = () => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || !editorRef.value) return
|
||||||
|
selection.removeAllRanges()
|
||||||
|
if (savedSelectionRange.value) {
|
||||||
|
selection.addRange(savedSelectionRange.value)
|
||||||
|
} else {
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(editorRef.value)
|
||||||
|
range.collapse(false)
|
||||||
|
selection.addRange(range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInput = () => {
|
||||||
|
emit('text-change', getPlainText())
|
||||||
|
saveSelection()
|
||||||
|
syncMentionPanelVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertPlainTextAtCursor = (text) => {
|
||||||
|
if (!editorRef.value) return
|
||||||
|
const normalizedText = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||||
|
editorRef.value.focus()
|
||||||
|
restoreSelection()
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) return
|
||||||
|
let range = selection.getRangeAt(0)
|
||||||
|
if (!editorRef.value.contains(range.commonAncestorContainer)) {
|
||||||
|
focusEditorToEnd()
|
||||||
|
const currentSelection = window.getSelection()
|
||||||
|
if (!currentSelection || currentSelection.rangeCount === 0) return
|
||||||
|
range = currentSelection.getRangeAt(0)
|
||||||
|
}
|
||||||
|
range.deleteContents()
|
||||||
|
const lines = normalizedText.split('\n')
|
||||||
|
const fragment = document.createDocumentFragment()
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
if (line) fragment.appendChild(document.createTextNode(line))
|
||||||
|
if (index < lines.length - 1) fragment.appendChild(document.createElement('br'))
|
||||||
|
})
|
||||||
|
range.insertNode(fragment)
|
||||||
|
range.collapse(false)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
saveSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePaste = (event) => {
|
||||||
|
if (!editorRef.value) return
|
||||||
|
event.preventDefault()
|
||||||
|
const clipboardText = event.clipboardData?.getData('text/plain') || ''
|
||||||
|
if (!clipboardText) return
|
||||||
|
insertPlainTextAtCursor(clipboardText)
|
||||||
|
emit('text-change', getPlainText())
|
||||||
|
syncMentionPanelVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMentionImageList = () => {
|
||||||
|
mentionImageList.value = mentionImageList.value.slice(0, MAX_IMAGE_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideMentionPanel = () => {
|
||||||
|
mentionVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasActiveMentionTriggerBeforeCursor = () => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) return false
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!range.collapsed) return false
|
||||||
|
if (!editorRef.value?.contains(range.commonAncestorContainer)) return false
|
||||||
|
const container = range.startContainer
|
||||||
|
if (container.nodeType !== Node.TEXT_NODE) return false
|
||||||
|
const before = container.data.slice(0, range.startOffset)
|
||||||
|
const atIndex = before.lastIndexOf('@')
|
||||||
|
if (atIndex < 0) return false
|
||||||
|
const afterAt = before.slice(atIndex + 1)
|
||||||
|
return !/\s/.test(afterAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncMentionPanelVisibility = () => {
|
||||||
|
const shouldShow = hasActiveMentionTriggerBeforeCursor()
|
||||||
|
if (!shouldShow) {
|
||||||
|
hideMentionPanel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateMentionImageList()
|
||||||
|
mentionVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeMentionKeywordBeforeCursor = () => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) return
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!range.collapsed) return
|
||||||
|
const container = range.startContainer
|
||||||
|
if (container.nodeType !== Node.TEXT_NODE) return
|
||||||
|
const before = container.data.slice(0, range.startOffset)
|
||||||
|
const atIndex = before.lastIndexOf('@')
|
||||||
|
if (atIndex < 0) return
|
||||||
|
const afterAt = before.slice(atIndex + 1)
|
||||||
|
if (/\s/.test(afterAt)) return
|
||||||
|
const removeRange = document.createRange()
|
||||||
|
removeRange.setStart(container, atIndex)
|
||||||
|
removeRange.setEnd(container, range.startOffset)
|
||||||
|
removeRange.deleteContents()
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertReference = (item) => {
|
||||||
|
if (!item?.url || !editorRef.value) return
|
||||||
|
editorRef.value.focus()
|
||||||
|
restoreSelection()
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) return
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!editorRef.value.contains(range.commonAncestorContainer)) return
|
||||||
|
|
||||||
|
const refNoText = `[图${item.refNo}]`
|
||||||
|
let node
|
||||||
|
if (item.mediaType === 'image') {
|
||||||
|
const img = document.createElement('img')
|
||||||
|
img.src = item.url
|
||||||
|
img.alt = `reference-${item.refNo}`
|
||||||
|
img.className = 'inline-rich-image'
|
||||||
|
img.setAttribute('data-image-url', item.url)
|
||||||
|
img.setAttribute('data-mention-reference', '1')
|
||||||
|
img.setAttribute('data-reference-no', String(item.refNo))
|
||||||
|
node = img
|
||||||
|
} else {
|
||||||
|
const tag = document.createElement('span')
|
||||||
|
tag.className = 'inline-rich-reference'
|
||||||
|
tag.textContent = refNoText
|
||||||
|
tag.setAttribute('data-reference-url', item.url)
|
||||||
|
tag.setAttribute('data-mention-reference', '1')
|
||||||
|
tag.setAttribute('data-reference-no', String(item.refNo))
|
||||||
|
tag.setAttribute('data-reference-type', item.mediaType)
|
||||||
|
node = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
range.insertNode(node)
|
||||||
|
const space = document.createTextNode(' ')
|
||||||
|
range.setStartAfter(node)
|
||||||
|
range.insertNode(space)
|
||||||
|
range.setStartAfter(space)
|
||||||
|
range.collapse(true)
|
||||||
|
selection.removeAllRanges()
|
||||||
|
selection.addRange(range)
|
||||||
|
saveSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertMentionImage = (item) => {
|
||||||
|
if (!item?.url || !editorRef.value) return
|
||||||
|
editorRef.value.focus()
|
||||||
|
restoreSelection()
|
||||||
|
removeMentionKeywordBeforeCursor()
|
||||||
|
// 删除 @关键字 后必须重新记录选区,否则 insertReference 里 restoreSelection 仍用删除前的 Range,会插错位置甚至覆盖后文
|
||||||
|
saveSelection()
|
||||||
|
insertReference(item)
|
||||||
|
hideMentionPanel()
|
||||||
|
emit('text-change', getPlainText())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyup = (event) => {
|
||||||
|
saveSelection()
|
||||||
|
if (event.key === '@') {
|
||||||
|
syncMentionPanelVisibility()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
hideMentionPanel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncMentionPanelVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditorClick = () => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection || selection.rangeCount === 0) {
|
||||||
|
focusEditorToEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
if (!editorRef.value?.contains(range.commonAncestorContainer)) {
|
||||||
|
focusEditorToEnd()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saveSelection()
|
||||||
|
syncMentionPanelVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInsertedUniqueImageCount = () => mentionImageList.value.length
|
||||||
|
|
||||||
|
const openImagePicker = () => {
|
||||||
|
const current = getInsertedUniqueImageCount()
|
||||||
|
if (current >= MAX_IMAGE_COUNT) {
|
||||||
|
Message.warning(`最多插入 ${MAX_IMAGE_COUNT} 个参考素材`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileInputRef.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadToCos = async (file) => {
|
||||||
|
const res = await uploadFile({
|
||||||
|
url: PORTAL_TENCENT_COS_UPLOAD_URL,
|
||||||
|
file,
|
||||||
|
name: 'file'
|
||||||
|
})
|
||||||
|
const codeOk = res && (Number(res.code) === 200 || res.code === 200)
|
||||||
|
const url = extractUploadUrlFromResponse(res)
|
||||||
|
if (codeOk && url) return url
|
||||||
|
throw new Error(res?.msg || '上传失败:未返回文件地址')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectFiles = async (event) => {
|
||||||
|
const input = event.target
|
||||||
|
const files = Array.from(input.files || [])
|
||||||
|
if (!files.length) return
|
||||||
|
const remain = MAX_IMAGE_COUNT - getInsertedUniqueImageCount()
|
||||||
|
if (remain <= 0) {
|
||||||
|
Message.warning(`最多插入 ${MAX_IMAGE_COUNT} 个参考素材`)
|
||||||
|
input.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selected = files.slice(0, remain)
|
||||||
|
if (files.length > remain) {
|
||||||
|
Message.warning(`最多插入 ${MAX_IMAGE_COUNT} 个参考素材,本次仅插入 ${remain} 个`)
|
||||||
|
}
|
||||||
|
for (const file of selected) {
|
||||||
|
const mediaType = detectMediaType(file)
|
||||||
|
try {
|
||||||
|
const url = await uploadToCos(file)
|
||||||
|
if (url && !mentionImageList.value.some((item) => item.url === url)) {
|
||||||
|
const refNo = mentionImageList.value.length + 1
|
||||||
|
mentionImageList.value.push({ url, refNo, mediaType })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Message.error('参考素材上传失败')
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.value = ''
|
||||||
|
emit('text-change', getPlainText())
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContentItems = () => {
|
||||||
|
const editor = editorRef.value
|
||||||
|
const items = []
|
||||||
|
if (!editor) return items
|
||||||
|
let textBuffer = ''
|
||||||
|
const flushText = () => {
|
||||||
|
const normalized = textBuffer.replace(/\u00a0/g, ' ').replace(/\s+\n/g, '\n')
|
||||||
|
if (normalized.trim()) items.push({ type: 'text', text: normalized.trim() })
|
||||||
|
textBuffer = ''
|
||||||
|
}
|
||||||
|
const walk = (node) => {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
textBuffer += node.textContent || ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) return
|
||||||
|
const el = node
|
||||||
|
const referenceUrl = (
|
||||||
|
el.dataset?.referenceUrl ||
|
||||||
|
el.dataset?.imageUrl ||
|
||||||
|
el.getAttribute('src') ||
|
||||||
|
''
|
||||||
|
).trim()
|
||||||
|
if (el.dataset?.mentionReference === '1' && referenceUrl) {
|
||||||
|
flushText()
|
||||||
|
items.push({
|
||||||
|
type: 'image_url',
|
||||||
|
image_url: { url: referenceUrl },
|
||||||
|
role: 'reference_image'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (el.tagName === 'IMG' && (el.dataset?.imageUrl || el.getAttribute('src'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (el.tagName === 'BR') {
|
||||||
|
textBuffer += '\n'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (['DIV', 'P', 'LI'].includes(el.tagName) && textBuffer && !textBuffer.endsWith('\n')) textBuffer += '\n'
|
||||||
|
Array.from(el.childNodes).forEach(walk)
|
||||||
|
if (['DIV', 'P', 'LI'].includes(el.tagName) && textBuffer && !textBuffer.endsWith('\n')) textBuffer += '\n'
|
||||||
|
}
|
||||||
|
Array.from(editor.childNodes).forEach(walk)
|
||||||
|
flushText()
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
if (!editorRef.value) return
|
||||||
|
editorRef.value.innerHTML = ''
|
||||||
|
emit('text-change', '')
|
||||||
|
savedSelectionRange.value = null
|
||||||
|
mentionImageList.value = []
|
||||||
|
hideMentionPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 供父组件持久化:参考图模式的 HTML + 素材列表 */
|
||||||
|
const getDraftState = () => ({
|
||||||
|
html: editorRef.value ? editorRef.value.innerHTML : '',
|
||||||
|
mentionList: mentionImageList.value.map(({ url, refNo, mediaType }) => ({
|
||||||
|
url,
|
||||||
|
refNo,
|
||||||
|
mediaType
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyDraftState = (draft) => {
|
||||||
|
hideMentionPanel()
|
||||||
|
savedSelectionRange.value = null
|
||||||
|
if (!draft || typeof draft !== 'object') {
|
||||||
|
clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mentionImageList.value = Array.isArray(draft.mentionList)
|
||||||
|
? draft.mentionList.map((x) => ({
|
||||||
|
url: x.url,
|
||||||
|
refNo: Number(x.refNo) || 0,
|
||||||
|
mediaType: x.mediaType || 'image'
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
if (editorRef.value) {
|
||||||
|
editorRef.value.innerHTML = typeof draft.html === 'string' ? draft.html : ''
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
emit('text-change', getPlainText())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getContentItems,
|
||||||
|
getPlainText,
|
||||||
|
clear,
|
||||||
|
getDraftState,
|
||||||
|
applyDraftState
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
focusEditorToEnd()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.video-editor-root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.hidden-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.editor-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.tool-btn {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
.tool-btn:hover {
|
||||||
|
border-color: rgba(0, 202, 224, 0.4);
|
||||||
|
background: rgba(0, 202, 224, 0.08);
|
||||||
|
}
|
||||||
|
.video-rich-editor {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 220px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.9;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
caret-color: #00cae0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
.video-rich-editor:focus {
|
||||||
|
border-color: rgba(0, 202, 224, 0.45);
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 202, 224, 0.15);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.video-rich-editor:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: rgba(255, 255, 255, 0.35);
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.video-rich-editor :deep(.inline-rich-image) {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: min(260px, 100%);
|
||||||
|
max-height: 148px;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 6px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.video-rich-editor :deep(.inline-rich-reference) {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 6px;
|
||||||
|
margin: 0 3px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(0, 202, 224, 0.15);
|
||||||
|
color: #5eebf5;
|
||||||
|
font-size: 0.85em;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.mention-panel {
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
right: 8px;
|
||||||
|
bottom: 8px;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(22, 24, 30, 0.98);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.mention-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mention-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
.mention-thumb {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.mention-type-badge {
|
||||||
|
min-width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.mention-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
.mention-empty {
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -9,7 +9,8 @@ import ru_RU from '@/lang/ru_RU/index.js'
|
||||||
import ar_SA from '@/lang/ar_SA/index.js'
|
import ar_SA from '@/lang/ar_SA/index.js'
|
||||||
import fr_FR from '@/lang/fr_FR/index.js'
|
import fr_FR from '@/lang/fr_FR/index.js'
|
||||||
|
|
||||||
let locale = Cookies.get('language') || 'en_US'
|
// 多语言切换已禁用:全站固定繁体中文
|
||||||
|
let locale = 'zh_HK'
|
||||||
|
|
||||||
/** 各语言在界面上的显示名称 */
|
/** 各语言在界面上的显示名称 */
|
||||||
export const LOCALE_NAMES = {
|
export const LOCALE_NAMES = {
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
<a-menu-item :key="item.key">
|
<a-menu-item :key="item.key">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<a-image
|
<a-image
|
||||||
:width="24"
|
:width="28"
|
||||||
:preview="false"
|
:preview="false"
|
||||||
:height="24"
|
:height="28"
|
||||||
:src="`/images/nav/${
|
:src="`/images/nav/${
|
||||||
selectedKeys.indexOf(item.key) > -1
|
selectedKeys.indexOf(item.key) > -1
|
||||||
? item.icon + '-a'
|
? item.icon + '-a'
|
||||||
|
|
@ -50,6 +50,9 @@ import cloneDeep from 'lodash-es/cloneDeep'
|
||||||
import { constantRoutes } from '@/router/index'
|
import { constantRoutes } from '@/router/index'
|
||||||
import { generateTitle, generateLang } from '@/utils/i18n'
|
import { generateTitle, generateLang } from '@/utils/i18n'
|
||||||
|
|
||||||
|
/** 左侧导航仅显示这些路由(name 与 router/index.js 一致) */
|
||||||
|
const SIDEBAR_ONLY_ROUTE_NAMES = ['video-gen']
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
collapsed: Boolean
|
collapsed: Boolean
|
||||||
})
|
})
|
||||||
|
|
@ -62,9 +65,14 @@ const $base = inject('$base')
|
||||||
const $message = inject('$message')
|
const $message = inject('$message')
|
||||||
|
|
||||||
const menuItems = computed(() => {
|
const menuItems = computed(() => {
|
||||||
return constantRoutes
|
const root = constantRoutes.find((d) => d.path === '/')
|
||||||
.find((d) => d.path == '/')
|
const children = root?.children ?? []
|
||||||
.children.map((route) => ({
|
return children
|
||||||
|
.filter(
|
||||||
|
(r) =>
|
||||||
|
r.meta?.menuItem !== false && SIDEBAR_ONLY_ROUTE_NAMES.includes(r.name)
|
||||||
|
)
|
||||||
|
.map((route) => ({
|
||||||
key: route.name,
|
key: route.name,
|
||||||
label: generateTitle(route.meta?.title),
|
label: generateTitle(route.meta?.title),
|
||||||
meta: route.meta,
|
meta: route.meta,
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,8 @@
|
||||||
:title="$t('common.myAccount')">
|
:title="$t('common.myAccount')">
|
||||||
<UserAccount />
|
<UserAccount />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane
|
<!-- 隐藏有奖邀请 -->
|
||||||
key="money"
|
<!-- <a-tab-pane key="money" :title="$t('common.moneyInvite')"><Money /></a-tab-pane> -->
|
||||||
:title="$t('common.moneyInvite')">
|
|
||||||
<Money />
|
|
||||||
</a-tab-pane>
|
|
||||||
<!-- 隐藏快速充值入口 -->
|
<!-- 隐藏快速充值入口 -->
|
||||||
<!-- <a-tab-pane
|
<!-- <a-tab-pane
|
||||||
key="recharge"
|
key="recharge"
|
||||||
|
|
@ -40,11 +37,8 @@
|
||||||
:title="$t('common.myProduct')">
|
:title="$t('common.myProduct')">
|
||||||
<ResumeRecord />
|
<ResumeRecord />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane
|
<!-- 隐藏邀请消费记录 -->
|
||||||
key="invite"
|
<!-- <a-tab-pane key="invite" :title="$t('common.inviteRecord')"><RewardRecord /></a-tab-pane> -->
|
||||||
:title="$t('common.inviteRecord')">
|
|
||||||
<RewardRecord />
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
</mf-dialog>
|
</mf-dialog>
|
||||||
|
|
@ -64,12 +58,8 @@
|
||||||
<script>
|
<script>
|
||||||
import Forgot from './Forgot.vue'
|
import Forgot from './Forgot.vue'
|
||||||
import Register from './Register.vue'
|
import Register from './Register.vue'
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import i18n from '@/lang/i18n'
|
|
||||||
import Money from './Money.vue'
|
|
||||||
import UserAccount from './UserAccount.vue'
|
import UserAccount from './UserAccount.vue'
|
||||||
import ResumeRecord from './ResumeRecord.vue'
|
import ResumeRecord from './ResumeRecord.vue'
|
||||||
import RewardRecord from './RewardRecord.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -91,13 +81,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Forgot,
|
Forgot,
|
||||||
Register,
|
Register,
|
||||||
Money,
|
|
||||||
UserAccount,
|
UserAccount,
|
||||||
ResumeRecord,
|
ResumeRecord
|
||||||
RewardRecord
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(['lang'])
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.register) {
|
if (this.register) {
|
||||||
|
|
@ -123,12 +108,6 @@ export default {
|
||||||
this.registerVisible = true
|
this.registerVisible = true
|
||||||
this.$emit('cancel')
|
this.$emit('cancel')
|
||||||
},
|
},
|
||||||
changeLang(value) {
|
|
||||||
if (value != this.lang) {
|
|
||||||
this.$store.dispatch('main/setLanguage', value)
|
|
||||||
i18n.global.locale = value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
back() {
|
back() {
|
||||||
this.forgotVisible = false
|
this.forgotVisible = false
|
||||||
this.$emit('open')
|
this.$emit('open')
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import Breadcrumb from './Breadcrumb.vue'
|
import Breadcrumb from './Breadcrumb.vue'
|
||||||
|
const LAYOUT_MOBILE_MAX = 768
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app-main',
|
name: 'app-main',
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -33,6 +35,21 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Breadcrumb },
|
components: { Breadcrumb },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
layoutWidth:
|
||||||
|
typeof window !== 'undefined' ? window.innerWidth : LAYOUT_MOBILE_MAX + 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this._layoutOnResize = () => {
|
||||||
|
this.layoutWidth = window.innerWidth
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', this._layoutOnResize)
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this._layoutOnResize)
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['cachedViews', 'sidebar']),
|
...mapGetters(['cachedViews', 'sidebar']),
|
||||||
// 相同路由,不同参数缓存问题
|
// 相同路由,不同参数缓存问题
|
||||||
|
|
@ -43,7 +60,10 @@ export default {
|
||||||
return !this.sidebar.opened
|
return !this.sidebar.opened
|
||||||
},
|
},
|
||||||
style() {
|
style() {
|
||||||
let sidebarWidth = this.isCollapsed ? '50px' : this.sidebar.width
|
if (this.layoutWidth <= LAYOUT_MOBILE_MAX) {
|
||||||
|
return { width: '100%' }
|
||||||
|
}
|
||||||
|
const sidebarWidth = this.isCollapsed ? '56px' : this.sidebar.width
|
||||||
return {
|
return {
|
||||||
width: `calc(100% - ${sidebarWidth})`
|
width: `calc(100% - ${sidebarWidth})`
|
||||||
}
|
}
|
||||||
|
|
@ -64,13 +84,15 @@ export default {
|
||||||
.app-main {
|
.app-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
&-wrap {
|
&-wrap {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 768px) {
|
||||||
.app-main {
|
.app-main {
|
||||||
&-wrap {
|
&-wrap {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<div class="left-menu">
|
<div class="left-menu">
|
||||||
<mf-icon
|
<button
|
||||||
:class="['left-collapse', { isCollapse: isCollapse }]"
|
type="button"
|
||||||
cursor="pointer"
|
class="sidebar-trigger"
|
||||||
@click="$store.dispatch('main/toggleSideBar')"
|
:aria-expanded="sidebar.opened"
|
||||||
value="icon-shrink" />
|
aria-label="展开或收起侧栏"
|
||||||
|
@click="$store.dispatch('main/toggleSideBar')">
|
||||||
|
<mf-icon
|
||||||
|
class="sidebar-trigger__icon sidebar-trigger__icon--desktop"
|
||||||
|
:class="{ isCollapse: isCollapse }"
|
||||||
|
cursor="pointer"
|
||||||
|
value="icon-shrink" />
|
||||||
|
<span class="sidebar-trigger__icon sidebar-trigger__icon--mobile" aria-hidden="true">
|
||||||
|
<span class="hamburger-line" />
|
||||||
|
<span class="hamburger-line" />
|
||||||
|
<span class="hamburger-line" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<div
|
<div
|
||||||
class="logo-wrap"
|
class="logo-wrap"
|
||||||
|
|
@ -31,20 +43,9 @@
|
||||||
</mf-button>
|
</mf-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-menu-item language">
|
<div class="right-menu-item language">
|
||||||
<a-dropdown @select="handleSelect">
|
<mf-button type="text" disabled class="language-display">
|
||||||
<mf-button type="text">
|
{{ localeName }}
|
||||||
{{ localeName }}
|
</mf-button>
|
||||||
<icon-down />
|
|
||||||
</mf-button>
|
|
||||||
<template #content>
|
|
||||||
<a-doption
|
|
||||||
v-for="(name, code) in localeNames"
|
|
||||||
:key="code"
|
|
||||||
:value="code">
|
|
||||||
{{ name }}
|
|
||||||
</a-doption>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="right-menu-item user"
|
class="right-menu-item user"
|
||||||
|
|
@ -97,7 +98,7 @@ import { mapGetters, mapState } from 'vuex'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep'
|
import cloneDeep from 'lodash-es/cloneDeep'
|
||||||
import { constantRoutes } from '@/router/index.js'
|
import { constantRoutes } from '@/router/index.js'
|
||||||
import Login from './Login.vue'
|
import Login from './Login.vue'
|
||||||
import i18n, { LOCALE_NAMES } from '@/lang/i18n'
|
import { LOCALE_NAMES } from '@/lang/i18n'
|
||||||
import User from './User.vue'
|
import User from './User.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -126,9 +127,6 @@ export default {
|
||||||
demoEnv() {
|
demoEnv() {
|
||||||
return import.meta.env.MODE === 'demo'
|
return import.meta.env.MODE === 'demo'
|
||||||
},
|
},
|
||||||
localeNames() {
|
|
||||||
return LOCALE_NAMES
|
|
||||||
},
|
|
||||||
localeName() {
|
localeName() {
|
||||||
return LOCALE_NAMES[this.lang] || 'English'
|
return LOCALE_NAMES[this.lang] || 'English'
|
||||||
},
|
},
|
||||||
|
|
@ -219,12 +217,6 @@ export default {
|
||||||
this.openLogin()
|
this.openLogin()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSelect(value) {
|
|
||||||
if (value != this.lang) {
|
|
||||||
this.$store.dispatch('main/setLanguage', value)
|
|
||||||
i18n.global.locale = value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logout() {
|
logout() {
|
||||||
this.$store.dispatch('user/logout2').then((_) => {
|
this.$store.dispatch('user/logout2').then((_) => {
|
||||||
this.$router.replace('/')
|
this.$router.replace('/')
|
||||||
|
|
@ -246,39 +238,71 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
padding-left: 30px;
|
padding-left: 20px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
|
|
||||||
.left {
|
.left-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding-left: 4px;
|
||||||
|
height: 60px;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
&-collapse {
|
.sidebar-trigger {
|
||||||
display: none;
|
display: inline-flex;
|
||||||
transition: 0.25s;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
|
||||||
&.isCollapse {
|
&:hover {
|
||||||
transform: rotate(-180deg);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon--desktop {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.25s;
|
||||||
|
|
||||||
|
&.isCollapse {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon--mobile {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
width: 22px;
|
||||||
|
padding: 2px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-menu {
|
.hamburger-line {
|
||||||
padding-left: 8px;
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 1px;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
height: 60px;
|
cursor: pointer;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
|
||||||
.logo {
|
&-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-1);
|
|
||||||
|
|
||||||
&-wrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,6 +333,12 @@ export default {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.language-display[disabled] {
|
||||||
|
color: #999999 !important;
|
||||||
|
opacity: 0.85;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.logout {
|
&.logout {
|
||||||
|
|
@ -410,23 +440,23 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 768px) {
|
||||||
.navbar {
|
.navbar {
|
||||||
padding: 0 10px;
|
padding: 0 12px;
|
||||||
.left {
|
|
||||||
&-collapse {
|
.left-menu {
|
||||||
display: block;
|
.sidebar-trigger {
|
||||||
font-size: 18px;
|
&__icon--desktop {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon--mobile {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-menu {
|
.logo {
|
||||||
display: flex;
|
display: none;
|
||||||
.logo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.mf-icon {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ export default {
|
||||||
1 100%;
|
1 100%;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
width: 50px !important;
|
width: 56px !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
:deep(.arco-menu) {
|
:deep(.arco-menu) {
|
||||||
|
|
@ -134,20 +134,27 @@ export default {
|
||||||
|
|
||||||
:deep(.arco-menu) {
|
:deep(.arco-menu) {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding-left: 16px;
|
padding: 8px 12px 12px;
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
width: 180px;
|
min-height: 48px;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: 15px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
&.arco-menu-selected,
|
&.arco-menu-selected,
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgb(var(--primary-6));
|
background-color: rgb(var(--primary-6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.arco-menu-collapsed) .arco-menu-item {
|
||||||
|
width: calc(100% - 8px);
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toogle-menu {
|
.toogle-menu {
|
||||||
|
|
@ -172,18 +179,22 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
z-index: 10;
|
z-index: 100;
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
box-shadow: 8px 0 32px rgba(0, 0, 0, 0.35);
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
|
min-width: 0 !important;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="app-wrapper">
|
<div class="app-wrapper">
|
||||||
|
<div
|
||||||
|
v-show="showSidebar && mobileDrawerOpen"
|
||||||
|
class="sidebar-backdrop"
|
||||||
|
aria-hidden="true"
|
||||||
|
@click="closeMobileDrawer" />
|
||||||
<div class="fixed-header">
|
<div class="fixed-header">
|
||||||
<nav-bar class="nav-bar" />
|
<nav-bar class="nav-bar" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -18,6 +23,8 @@
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { sideBar, appMain, navBar, appFooter } from './components'
|
import { sideBar, appMain, navBar, appFooter } from './components'
|
||||||
|
|
||||||
|
const LAYOUT_MOBILE_MAX = 768
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -26,16 +33,44 @@ export default {
|
||||||
navBar,
|
navBar,
|
||||||
appFooter
|
appFooter
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
layoutWidth:
|
||||||
|
typeof window !== 'undefined' ? window.innerWidth : LAYOUT_MOBILE_MAX + 1
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['sidebar']),
|
...mapGetters(['sidebar']),
|
||||||
isCollapsed() {
|
isCollapsed() {
|
||||||
return !this.sidebar.opened
|
return !this.sidebar.opened
|
||||||
},
|
},
|
||||||
|
isMobileLayout() {
|
||||||
|
return this.layoutWidth <= LAYOUT_MOBILE_MAX
|
||||||
|
},
|
||||||
|
mobileDrawerOpen() {
|
||||||
|
return this.isMobileLayout && this.sidebar.opened
|
||||||
|
},
|
||||||
// 是否显示菜单
|
// 是否显示菜单
|
||||||
showSidebar() {
|
showSidebar() {
|
||||||
let { menu } = this.$route.query || {}
|
let { menu } = this.$route.query || {}
|
||||||
return menu != 0
|
return menu != 0
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this._layoutOnResize = () => {
|
||||||
|
this.layoutWidth = window.innerWidth
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', this._layoutOnResize)
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this._layoutOnResize)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeMobileDrawer() {
|
||||||
|
if (this.isMobileLayout && this.sidebar.opened) {
|
||||||
|
this.$store.dispatch('main/closeSideBar')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -49,6 +84,10 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #0f0f12;
|
background: #0f0f12;
|
||||||
|
|
||||||
|
.sidebar-backdrop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.fixed-header {
|
.fixed-header {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
.navbar {
|
.navbar {
|
||||||
|
|
@ -59,6 +98,18 @@ export default {
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-wrapper .sidebar-backdrop {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
inset: 60px 0 0 0;
|
||||||
|
z-index: 99;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ const state = {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened: isMobile() ? false : true,
|
opened: isMobile() ? false : true,
|
||||||
withoutAnimation: false,
|
withoutAnimation: false,
|
||||||
width: Cookies.get('sidebarWidth') || '230px'
|
// 左侧导航默认适中宽度(用户若需更窄/宽可本地调小范围会写 cookie)
|
||||||
|
width: Cookies.get('sidebarWidth') || '268px'
|
||||||
},
|
},
|
||||||
topMenu: Cookies.get('topMenu'),
|
topMenu: Cookies.get('topMenu'),
|
||||||
// 当前语言
|
// 当前语言
|
||||||
language: Cookies.get('language') || 'en_US',
|
// 多语言切换已禁用:全站固定繁体中文(zh_HK)
|
||||||
|
language: 'zh_HK',
|
||||||
// 主题 '':亮色 / dark:暗黑
|
// 主题 '':亮色 / dark:暗黑
|
||||||
theme: $storage.get('theme') || '',
|
theme: $storage.get('theme') || '',
|
||||||
// 系统端
|
// 系统端
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,12 @@ export const isFirefox = (_) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isMobile = _=> {
|
export const isMobile = (_) => {
|
||||||
const userAgent = navigator.userAgent;
|
const userAgent = navigator.userAgent
|
||||||
const isMobile = /iPad|Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
const uaMobile =
|
||||||
const isSmallScreen = window.innerWidth <= 576;
|
/iPad|Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||||
return isMobile || isSmallScreen;
|
userAgent
|
||||||
|
)
|
||||||
|
const isSmallScreen = typeof window !== 'undefined' && window.innerWidth <= 768
|
||||||
|
return uaMobile || isSmallScreen
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,23 @@ export const convertBase64ToUrl = (base64) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从若依 AjaxResult / 兼容仅返回顶层 url 的上传响应中解析访问地址
|
||||||
|
*/
|
||||||
|
export const extractUploadUrlFromResponse = (res) => {
|
||||||
|
if (!res) return ''
|
||||||
|
if (typeof res.data === 'string' && res.data.trim()) return res.data.trim()
|
||||||
|
if (res.data && typeof res.data === 'object') {
|
||||||
|
const u = res.data.url || res.data.path
|
||||||
|
if (typeof u === 'string' && u.trim()) return u.trim()
|
||||||
|
}
|
||||||
|
if (typeof res.url === 'string' && res.url.trim()) return res.url.trim()
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 门户统一腾讯云 COS:后端 FileController `/api/file/upload`,与 mf-image-upload 一致 */
|
||||||
|
export const PORTAL_TENCENT_COS_UPLOAD_URL = '/api/file/upload'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传
|
* 文件上传
|
||||||
* @param {Object} {url, file,name} 文件上传地址 file 文件 name 文件名参数
|
* @param {Object} {url, file,name} 文件上传地址 file 文件 name 文件名参数
|
||||||
|
|
@ -148,12 +165,10 @@ export const uploadFile = ({
|
||||||
formData.append(key, data[key])
|
formData.append(key, data[key])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 不要手动设置 Content-Type,否则缺少 boundary,服务端无法解析 multipart,文件参数字段为空
|
||||||
return request({
|
return request({
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: formData,
|
data: formData
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,71 @@
|
||||||
|
// vite.config.js
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
transformWithEsbuild
|
||||||
|
} from "file:///D:/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/%E5%B7%A5%E7%A8%8B%E4%BB%A3%E7%A0%81/portal-ui/node_modules/vite/dist/node/index.js";
|
||||||
|
import vue from "file:///D:/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/%E5%B7%A5%E7%A8%8B%E4%BB%A3%E7%A0%81/portal-ui/node_modules/@vitejs/plugin-vue/dist/index.mjs";
|
||||||
|
import vueJsx from "file:///D:/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/%E5%B7%A5%E7%A8%8B%E4%BB%A3%E7%A0%81/portal-ui/node_modules/@vitejs/plugin-vue-jsx/dist/index.mjs";
|
||||||
|
import {
|
||||||
|
vitePluginForArco
|
||||||
|
} from "file:///D:/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/%E5%B7%A5%E7%A8%8B%E4%BB%A3%E7%A0%81/portal-ui/node_modules/@arco-plugins/vite-vue/lib/index.js";
|
||||||
|
import {
|
||||||
|
resolve
|
||||||
|
} from "path";
|
||||||
|
import svgLoader from "file:///D:/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/AI%E9%A1%B9%E7%9B%AE%E4%BA%A4%E4%BB%98%E6%96%87%E4%BB%B6/%E5%B7%A5%E7%A8%8B%E4%BB%A3%E7%A0%81/portal-ui/node_modules/vite-svg-loader/index.js";
|
||||||
|
var __vite_injected_original_dirname = "D:\\AI\u9879\u76EE\u4EA4\u4ED8\u6587\u4EF6\\AI\u9879\u76EE\u4EA4\u4ED8\u6587\u4EF6\\\u5DE5\u7A0B\u4EE3\u7801\\portal-ui";
|
||||||
|
var vite_config_default = defineConfig({
|
||||||
|
publicDir: "static",
|
||||||
|
resolve: {
|
||||||
|
extensions: [".mjs", ".js", ".jsx", ".json"],
|
||||||
|
alias: [{
|
||||||
|
find: "@",
|
||||||
|
replacement: resolve(__vite_injected_original_dirname, "src")
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
less: {
|
||||||
|
modifyVars: {
|
||||||
|
"@size-9": "40px",
|
||||||
|
"arcoblue-6": "#e6217a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
template: {
|
||||||
|
transformAssetUrls: {
|
||||||
|
includeAbsolute: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
svgLoader(),
|
||||||
|
vueJsx({
|
||||||
|
include: /\.[jt]sx?$/
|
||||||
|
}),
|
||||||
|
vitePluginForArco(),
|
||||||
|
{
|
||||||
|
name: "treat-js-files-as-jsx",
|
||||||
|
async transform(code, id) {
|
||||||
|
if (!id.match(/src\/.*\.js$/)) return null;
|
||||||
|
return transformWithEsbuild(code, id, {
|
||||||
|
loader: "jsx",
|
||||||
|
jsx: "automatic"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
loader: {
|
||||||
|
".js": "jsx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exclude: ["__INDEX__"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
vite_config_default as default
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcuanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxBSVx1OTg3OVx1NzZFRVx1NEVBNFx1NEVEOFx1NjU4N1x1NEVGNlxcXFxBSVx1OTg3OVx1NzZFRVx1NEVBNFx1NEVEOFx1NjU4N1x1NEVGNlxcXFxcdTVERTVcdTdBMEJcdTRFRTNcdTc4MDFcXFxccG9ydGFsLXVpXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJEOlxcXFxBSVx1OTg3OVx1NzZFRVx1NEVBNFx1NEVEOFx1NjU4N1x1NEVGNlxcXFxBSVx1OTg3OVx1NzZFRVx1NEVBNFx1NEVEOFx1NjU4N1x1NEVGNlxcXFxcdTVERTVcdTdBMEJcdTRFRTNcdTc4MDFcXFxccG9ydGFsLXVpXFxcXHZpdGUuY29uZmlnLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9EOi9BSSVFOSVBMSVCOSVFNyU5QiVBRSVFNCVCQSVBNCVFNCVCQiU5OCVFNiU5NiU4NyVFNCVCQiVCNi9BSSVFOSVBMSVCOSVFNyU5QiVBRSVFNCVCQSVBNCVFNCVCQiU5OCVFNiU5NiU4NyVFNCVCQiVCNi8lRTUlQjclQTUlRTclQTglOEIlRTQlQkIlQTMlRTclQTAlODEvcG9ydGFsLXVpL3ZpdGUuY29uZmlnLmpzXCI7aW1wb3J0IHtcclxuXHRkZWZpbmVDb25maWcsXHJcblx0dHJhbnNmb3JtV2l0aEVzYnVpbGRcclxufSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSdcclxuaW1wb3J0IHZ1ZUpzeCBmcm9tICdAdml0ZWpzL3BsdWdpbi12dWUtanN4J1xyXG5pbXBvcnQge1xyXG5cdHZpdGVQbHVnaW5Gb3JBcmNvXHJcbn0gZnJvbSAnQGFyY28tcGx1Z2lucy92aXRlLXZ1ZSdcclxuaW1wb3J0IHtcclxuXHRyZXNvbHZlXHJcbn0gZnJvbSAncGF0aCdcclxuaW1wb3J0IHN2Z0xvYWRlciBmcm9tICd2aXRlLXN2Zy1sb2FkZXInXHJcblxyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG5cdHB1YmxpY0RpcjogJ3N0YXRpYycsXHJcblx0cmVzb2x2ZToge1xyXG5cdFx0ZXh0ZW5zaW9uczogWycubWpzJywgJy5qcycsICcuanN4JywgJy5qc29uJ10sXHJcblx0XHRhbGlhczogW3tcclxuXHRcdFx0ZmluZDogJ0AnLFxyXG5cdFx0XHRyZXBsYWNlbWVudDogcmVzb2x2ZShfX2Rpcm5hbWUsICdzcmMnKVxyXG5cdFx0fV1cclxuXHR9LFxyXG5cdGNzczoge1xyXG5cdFx0cHJlcHJvY2Vzc29yT3B0aW9uczoge1xyXG5cdFx0XHRsZXNzOiB7XHJcblx0XHRcdFx0bW9kaWZ5VmFyczoge1xyXG5cdFx0XHRcdFx0J0BzaXplLTknOiAnNDBweCcsXHJcblx0XHRcdFx0XHQnYXJjb2JsdWUtNic6ICcjZTYyMTdhJ1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH0sXHJcblx0cGx1Z2luczogW1xyXG5cdFx0dnVlKHtcclxuXHRcdFx0dGVtcGxhdGU6IHtcclxuXHRcdFx0XHR0cmFuc2Zvcm1Bc3NldFVybHM6IHtcclxuXHRcdFx0XHRcdGluY2x1ZGVBYnNvbHV0ZTogZmFsc2VcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHRcdH0pLFxyXG5cdFx0c3ZnTG9hZGVyKCksXHJcblx0XHR2dWVKc3goe1xyXG5cdFx0XHRpbmNsdWRlOiAvXFwuW2p0XXN4PyQvXHJcblx0XHR9KSxcclxuXHRcdHZpdGVQbHVnaW5Gb3JBcmNvKCksXHJcblx0XHR7XHJcblx0XHRcdG5hbWU6ICd0cmVhdC1qcy1maWxlcy1hcy1qc3gnLFxyXG5cdFx0XHRhc3luYyB0cmFuc2Zvcm0oY29kZSwgaWQpIHtcclxuXHRcdFx0XHRpZiAoIWlkLm1hdGNoKC9zcmNcXC8uKlxcLmpzJC8pKSByZXR1cm4gbnVsbFxyXG5cdFx0XHRcdHJldHVybiB0cmFuc2Zvcm1XaXRoRXNidWlsZChjb2RlLCBpZCwge1xyXG5cdFx0XHRcdFx0bG9hZGVyOiAnanN4JyxcclxuXHRcdFx0XHRcdGpzeDogJ2F1dG9tYXRpYydcclxuXHRcdFx0XHR9KVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0XSxcclxuXHRvcHRpbWl6ZURlcHM6IHtcclxuXHRcdGVzYnVpbGRPcHRpb25zOiB7XHJcblx0XHRcdGxvYWRlcjoge1xyXG5cdFx0XHRcdCcuanMnOiAnanN4J1xyXG5cdFx0XHR9XHJcblx0XHR9LFxyXG5cdFx0ZXhjbHVkZTogWydfX0lOREVYX18nXVxyXG5cdH1cclxufSkiXSwKICAibWFwcGluZ3MiOiAiO0FBQXFhO0FBQUEsRUFDcGE7QUFBQSxFQUNBO0FBQUEsT0FDTTtBQUNQLE9BQU8sU0FBUztBQUNoQixPQUFPLFlBQVk7QUFDbkI7QUFBQSxFQUNDO0FBQUEsT0FDTTtBQUNQO0FBQUEsRUFDQztBQUFBLE9BQ007QUFDUCxPQUFPLGVBQWU7QUFadEIsSUFBTSxtQ0FBbUM7QUFjekMsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDM0IsV0FBVztBQUFBLEVBQ1gsU0FBUztBQUFBLElBQ1IsWUFBWSxDQUFDLFFBQVEsT0FBTyxRQUFRLE9BQU87QUFBQSxJQUMzQyxPQUFPLENBQUM7QUFBQSxNQUNQLE1BQU07QUFBQSxNQUNOLGFBQWEsUUFBUSxrQ0FBVyxLQUFLO0FBQUEsSUFDdEMsQ0FBQztBQUFBLEVBQ0Y7QUFBQSxFQUNBLEtBQUs7QUFBQSxJQUNKLHFCQUFxQjtBQUFBLE1BQ3BCLE1BQU07QUFBQSxRQUNMLFlBQVk7QUFBQSxVQUNYLFdBQVc7QUFBQSxVQUNYLGNBQWM7QUFBQSxRQUNmO0FBQUEsTUFDRDtBQUFBLElBQ0Q7QUFBQSxFQUNEO0FBQUEsRUFDQSxTQUFTO0FBQUEsSUFDUixJQUFJO0FBQUEsTUFDSCxVQUFVO0FBQUEsUUFDVCxvQkFBb0I7QUFBQSxVQUNuQixpQkFBaUI7QUFBQSxRQUNsQjtBQUFBLE1BQ0Q7QUFBQSxJQUNELENBQUM7QUFBQSxJQUNELFVBQVU7QUFBQSxJQUNWLE9BQU87QUFBQSxNQUNOLFNBQVM7QUFBQSxJQUNWLENBQUM7QUFBQSxJQUNELGtCQUFrQjtBQUFBLElBQ2xCO0FBQUEsTUFDQyxNQUFNO0FBQUEsTUFDTixNQUFNLFVBQVUsTUFBTSxJQUFJO0FBQ3pCLFlBQUksQ0FBQyxHQUFHLE1BQU0sY0FBYyxFQUFHLFFBQU87QUFDdEMsZUFBTyxxQkFBcUIsTUFBTSxJQUFJO0FBQUEsVUFDckMsUUFBUTtBQUFBLFVBQ1IsS0FBSztBQUFBLFFBQ04sQ0FBQztBQUFBLE1BQ0Y7QUFBQSxJQUNEO0FBQUEsRUFDRDtBQUFBLEVBQ0EsY0FBYztBQUFBLElBQ2IsZ0JBQWdCO0FBQUEsTUFDZixRQUFRO0FBQUEsUUFDUCxPQUFPO0FBQUEsTUFDUjtBQUFBLElBQ0Q7QUFBQSxJQUNBLFNBQVMsQ0FBQyxXQUFXO0FBQUEsRUFDdEI7QUFDRCxDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
|
||||||
Loading…
Reference in New Issue